diff --git a/.agent/workflows/update_clawdbot.md b/.agent/workflows/update_clawdbot.md index 04a079aab41..0543e7c2a68 100644 --- a/.agent/workflows/update_clawdbot.md +++ b/.agent/workflows/update_clawdbot.md @@ -1,8 +1,8 @@ --- -description: Update Clawdbot from upstream when branch has diverged (ahead/behind) +description: Update OpenClaw from upstream when branch has diverged (ahead/behind) --- -# Clawdbot Upstream Sync Workflow +# OpenClaw Upstream Sync Workflow Use this workflow when your fork has diverged from upstream (e.g., "18 commits ahead, 29 commits behind"). @@ -132,16 +132,16 @@ pnpm mac:package ```bash # Kill running app -pkill -x "Clawdbot" || true +pkill -x "OpenClaw" || true # Move old version -mv /Applications/Clawdbot.app /tmp/Clawdbot-backup.app +mv /Applications/OpenClaw.app /tmp/OpenClaw-backup.app # Install new build -cp -R dist/Clawdbot.app /Applications/ +cp -R dist/OpenClaw.app /Applications/ # Launch -open /Applications/Clawdbot.app +open /Applications/OpenClaw.app ``` --- @@ -235,7 +235,7 @@ If upstream introduced new model configurations: # Check for OpenRouter API key requirements grep -r "openrouter\|OPENROUTER" src/ --include="*.ts" --include="*.js" -# Update clawdbot.json with fallback chains +# Update openclaw.json with fallback chains # Add model fallback configurations as needed ``` diff --git a/.agents/skills/openclaw-ghsa-maintainer/SKILL.md b/.agents/skills/openclaw-ghsa-maintainer/SKILL.md new file mode 100644 index 00000000000..44581974841 --- /dev/null +++ b/.agents/skills/openclaw-ghsa-maintainer/SKILL.md @@ -0,0 +1,87 @@ +--- +name: openclaw-ghsa-maintainer +description: Maintainer workflow for OpenClaw GitHub Security Advisories (GHSA). Use when Codex needs to inspect, patch, validate, or publish a repo advisory, verify private-fork state, prepare advisory Markdown or JSON payloads safely, handle GHSA API-specific publish constraints, or confirm advisory publish success. +--- + +# OpenClaw GHSA Maintainer + +Use this skill for repo security advisory workflow only. Keep general release work in `openclaw-release-maintainer`. + +## Respect advisory guardrails + +- Before reviewing or publishing a repo advisory, read `SECURITY.md`. +- Ask permission before any publish action. +- Treat this skill as GHSA-only. Do not use it for stable or beta release work. + +## Fetch and inspect advisory state + +Fetch the current advisory and the latest published npm version: + +```bash +gh api /repos/openclaw/openclaw/security-advisories/ +npm view openclaw version --userconfig "$(mktemp)" +``` + +Use the fetch output to confirm the advisory state, linked private fork, and vulnerability payload shape before patching. + +## Verify private fork PRs are closed + +Before publishing, verify that the advisory's private fork has no open PRs: + +```bash +fork=$(gh api /repos/openclaw/openclaw/security-advisories/ | jq -r .private_fork.full_name) +gh pr list -R "$fork" --state open +``` + +The PR list must be empty before publish. + +## Prepare advisory Markdown and JSON safely + +- Write advisory Markdown via heredoc to a temp file. Do not use escaped `\n` strings. +- Build PATCH payload JSON with `jq`, not hand-escaped shell JSON. + +Example pattern: + +```bash +cat > /tmp/ghsa.desc.md <<'EOF' + +EOF + +jq -n --rawfile desc /tmp/ghsa.desc.md \ + '{summary,severity,description:$desc,vulnerabilities:[...]}' \ + > /tmp/ghsa.patch.json +``` + +## Apply PATCH calls in the correct sequence + +- Do not set `severity` and `cvss_vector_string` in the same PATCH call. +- Use separate calls when the advisory requires both fields. +- Publish by PATCHing the advisory and setting `"state":"published"`. There is no separate `/publish` endpoint. + +Example shape: + +```bash +gh api -X PATCH /repos/openclaw/openclaw/security-advisories/ \ + --input /tmp/ghsa.patch.json +``` + +## Publish and verify success + +After publish, re-fetch the advisory and confirm: + +- `state=published` +- `published_at` is set +- the description does not contain literal escaped `\\n` + +Verification pattern: + +```bash +gh api /repos/openclaw/openclaw/security-advisories/ +jq -r .description < /tmp/ghsa.refetch.json | rg '\\\\n' +``` + +## Common GHSA footguns + +- Publishing fails with HTTP 422 if required fields are missing or the private fork still has open PRs. +- A payload that looks correct in shell can still be wrong if Markdown was assembled with escaped newline strings. +- Advisory PATCH sequencing matters; separate field updates when GHSA API constraints require it. diff --git a/.agents/skills/openclaw-parallels-smoke/SKILL.md b/.agents/skills/openclaw-parallels-smoke/SKILL.md new file mode 100644 index 00000000000..db12afa48aa --- /dev/null +++ b/.agents/skills/openclaw-parallels-smoke/SKILL.md @@ -0,0 +1,58 @@ +--- +name: openclaw-parallels-smoke +description: End-to-end Parallels smoke, upgrade, and rerun workflow for OpenClaw across macOS, Windows, and Linux guests. Use when Codex needs to run, rerun, debug, or interpret VM-based install, onboarding, gateway smoke tests, latest-release-to-main upgrade checks, fresh snapshot retests, or optional Discord roundtrip verification under Parallels. +--- + +# OpenClaw Parallels Smoke + +Use this skill for Parallels guest workflows and smoke interpretation. Do not load it for normal repo work. + +## Global rules + +- Use the snapshot most closely matching the requested fresh baseline. +- Gateway verification in smoke runs should use `openclaw gateway status --deep --require-rpc` unless the stable version being checked does not support it yet. +- Stable `2026.3.12` pre-upgrade diagnostics may require a plain `gateway status --deep` fallback. +- Treat `precheck=latest-ref-fail` on that stable pre-upgrade lane as baseline, not automatically a regression. +- Pass `--json` for machine-readable summaries. +- Per-phase logs land under `/tmp/openclaw-parallels-*`. +- Do not run local and gateway agent turns in parallel on the same fresh workspace or session. + +## macOS flow + +- Preferred entrypoint: `pnpm test:parallels:macos` +- Target the snapshot closest to `macOS 26.3.1 fresh`. +- `prlctl exec` is fine for deterministic repo commands, but use the guest Terminal or `prlctl enter` when installer parity or shell-sensitive behavior matters. +- On the fresh Tahoe snapshot, `brew` exists but `node` may be missing from PATH in noninteractive exec. Use `/opt/homebrew/bin/node` when needed. +- Fresh host-served tgz installs should install as guest root with `HOME=/var/root`, then run onboarding as the desktop user via `prlctl exec --current-user`. +- Root-installed tgz smoke can log plugin blocks for world-writable `extensions/*`; do not treat that as an onboarding or gateway failure unless plugin loading is the task. + +## Windows flow + +- Preferred entrypoint: `pnpm test:parallels:windows` +- Use the snapshot closest to `pre-openclaw-native-e2e-2026-03-12`. +- Always use `prlctl exec --current-user`; plain `prlctl exec` lands in `NT AUTHORITY\\SYSTEM`. +- Prefer explicit `npm.cmd` and `openclaw.cmd`. +- Use PowerShell only as the transport with `-ExecutionPolicy Bypass`, then call the `.cmd` shims from inside it. +- Keep onboarding and status output ASCII-clean in logs; fancy punctuation becomes mojibake in current capture paths. + +## Linux flow + +- Preferred entrypoint: `pnpm test:parallels:linux` +- Use the snapshot closest to fresh `Ubuntu 24.04.3 ARM64`. +- Use plain `prlctl exec`; `--current-user` is not the right transport on this snapshot. +- Fresh snapshots may be missing `curl`, and `apt-get update` can fail on clock skew. Bootstrap with `apt-get -o Acquire::Check-Date=false update` and install `curl ca-certificates`. +- Fresh `main` tgz smoke still needs the latest-release installer first because the snapshot has no Node or npm before bootstrap. +- This snapshot does not have a usable `systemd --user` session; managed daemon install is unsupported. +- `prlctl exec` reaps detached Linux child processes on this snapshot, so detached background gateway runs are not trustworthy smoke signals. + +## Discord roundtrip + +- Discord roundtrip is optional and should be enabled with: + - `--discord-token-env` + - `--discord-guild-id` + - `--discord-channel-id` +- Keep the Discord token only in a host env var. +- Use installed `openclaw message send/read`, not `node openclaw.mjs message ...`. +- Set `channels.discord.guilds` as one JSON object, not dotted config paths with snowflakes. +- Avoid long `prlctl enter` or expect-driven Discord config scripts; prefer `prlctl exec --current-user /bin/sh -lc ...` with short commands. +- For a narrower macOS-only Discord proof run, the existing `parallels-discord-roundtrip` skill is the deep-dive companion. diff --git a/.agents/skills/openclaw-pr-maintainer/SKILL.md b/.agents/skills/openclaw-pr-maintainer/SKILL.md new file mode 100644 index 00000000000..0bcba736e14 --- /dev/null +++ b/.agents/skills/openclaw-pr-maintainer/SKILL.md @@ -0,0 +1,75 @@ +--- +name: openclaw-pr-maintainer +description: Maintainer workflow for reviewing, triaging, preparing, closing, or landing OpenClaw pull requests and related issues. Use when Codex needs to validate bug-fix claims, search for related issues or PRs, apply or recommend close/reason labels, prepare GitHub comments safely, check review-thread follow-up, or perform maintainer-style PR decision making before merge or closure. +--- + +# OpenClaw PR Maintainer + +Use this skill for maintainer-facing GitHub workflow, not for ordinary code changes. + +## Apply close and triage labels correctly + +- If an issue or PR matches an auto-close reason, apply the label and let `.github/workflows/auto-response.yml` handle the comment/close/lock flow. +- Do not manually close plus manually comment for these reasons. +- `r:*` labels can be used on both issues and PRs. +- Current reasons: + - `r: skill` + - `r: support` + - `r: no-ci-pr` + - `r: too-many-prs` + - `r: testflight` + - `r: third-party-extension` + - `r: moltbook` + - `r: spam` + - `invalid` + - `dirty` for PRs only + +## Enforce the bug-fix evidence bar + +- Never merge a bug-fix PR based only on issue text, PR text, or AI rationale. +- Before landing, require: + 1. symptom evidence such as a repro, logs, or a failing test + 2. a verified root cause in code with file/line + 3. a fix that touches the implicated code path + 4. a regression test when feasible, or explicit manual verification plus a reason no test was added +- If the claim is unsubstantiated or likely wrong, request evidence or changes instead of merging. +- If the linked issue appears outdated or incorrect, correct triage first. Do not merge a speculative fix. + +## Handle GitHub text safely + +- For issue comments and PR comments, use literal multiline strings or `-F - <<'EOF'` for real newlines. Never embed `\n`. +- Do not use `gh issue/pr comment -b "..."` when the body contains backticks or shell characters. Prefer a single-quoted heredoc. +- Do not wrap issue or PR refs like `#24643` in backticks when you want auto-linking. +- PR landing comments should include clickable full commit links for landed and source SHAs when present. + +## Search broadly before deciding + +- Prefer targeted keyword search before proposing new work or closing something as duplicate. +- Use `--repo openclaw/openclaw` with `--match title,body` first. +- Add `--match comments` when triaging follow-up discussion. +- Do not stop at the first 500 results when the task requires a full search. + +Examples: + +```bash +gh search prs --repo openclaw/openclaw --match title,body --limit 50 -- "auto-update" +gh search issues --repo openclaw/openclaw --match title,body --limit 50 -- "auto-update" +gh search issues --repo openclaw/openclaw --match title,body --limit 50 \ + --json number,title,state,url,updatedAt -- "auto update" \ + --jq '.[] | "\(.number) | \(.state) | \(.title) | \(.url)"' +``` + +## Follow PR review and landing hygiene + +- If bot review conversations exist on your PR, address them and resolve them yourself once fixed. +- Leave a review conversation unresolved only when reviewer or maintainer judgment is still needed. +- When landing or merging any PR, follow the global `/landpr` process. +- Use `scripts/committer "" ` for scoped commits instead of manual `git add` and `git commit`. +- Keep commit messages concise and action-oriented. +- Group related changes; avoid bundling unrelated refactors. +- Use `.github/pull_request_template.md` for PR submissions and `.github/ISSUE_TEMPLATE/` for issues. + +## Extra safety + +- If a close or reopen action would affect more than 5 PRs, ask for explicit confirmation with the exact count and target query first. +- `sync` means: if the tree is dirty, commit all changes with a sensible Conventional Commit message, then `git pull --rebase`, then `git push`. Stop if rebase conflicts cannot be resolved safely. diff --git a/.agents/skills/openclaw-release-maintainer/SKILL.md b/.agents/skills/openclaw-release-maintainer/SKILL.md new file mode 100644 index 00000000000..fc7674a774d --- /dev/null +++ b/.agents/skills/openclaw-release-maintainer/SKILL.md @@ -0,0 +1,74 @@ +--- +name: openclaw-release-maintainer +description: Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts. +--- + +# OpenClaw Release Maintainer + +Use this skill for release and publish-time workflow. Keep ordinary development changes and GHSA-specific advisory work outside this skill. + +## Respect release guardrails + +- Do not change version numbers without explicit operator approval. +- Ask permission before any npm publish or release step. +- Use the private maintainer release docs for the actual runbook and `docs/reference/RELEASING.md` for public policy. + +## Keep release channel naming aligned + +- `stable`: tagged releases only, with npm dist-tag `latest` +- `beta`: prerelease tags like `vYYYY.M.D-beta.N`, with npm dist-tag `beta` +- Prefer `-beta.N`; do not mint new `-1` or `-2` beta suffixes +- `dev`: moving head on `main` +- When using a beta Git tag, publish npm with the matching beta version suffix so the plain version is not consumed or blocked + +## Handle versions and release files consistently + +- Version locations include: + - `package.json` + - `apps/android/app/build.gradle.kts` + - `apps/ios/Sources/Info.plist` + - `apps/ios/Tests/Info.plist` + - `apps/macos/Sources/OpenClaw/Resources/Info.plist` + - `docs/install/updating.md` + - Peekaboo Xcode project and plist version fields +- “Bump version everywhere” means all version locations above except `appcast.xml`. +- Release signing and notary credentials live outside the repo in the private maintainer docs. + +## Build changelog-backed release notes + +- Changelog entries should be user-facing, not internal release-process notes. +- When cutting a mac release with a beta GitHub prerelease: + - tag `vYYYY.M.D-beta.N` from the release commit + - create a prerelease titled `openclaw YYYY.M.D-beta.N` + - use release notes from the matching `CHANGELOG.md` version section + - attach at least the zip and dSYM zip, plus dmg if available +- Keep the top version entries in `CHANGELOG.md` sorted by impact: + - `### Changes` first + - `### Fixes` deduped with user-facing fixes first + +## Run publish-time validation + +Before tagging or publishing, run: + +```bash +node --import tsx scripts/release-check.ts +pnpm release:check +pnpm test:install:smoke +``` + +For a non-root smoke path: + +```bash +OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke +``` + +## Use the right auth flow + +- Core `openclaw` publish uses GitHub trusted publishing. +- Do not use `NPM_TOKEN` or the plugin OTP flow for core releases. +- `@openclaw/*` plugin publishes use a separate maintainer-only flow. +- Only publish plugins that already exist on npm; bundled disk-tree-only plugins stay unpublished. + +## GHSA advisory work + +- Use `openclaw-ghsa-maintainer` for GHSA advisory inspection, patch/publish flow, private-fork validation, and GHSA API-specific publish checks. diff --git a/.agents/skills/parallels-discord-roundtrip/SKILL.md b/.agents/skills/parallels-discord-roundtrip/SKILL.md new file mode 100644 index 00000000000..cbfffc21446 --- /dev/null +++ b/.agents/skills/parallels-discord-roundtrip/SKILL.md @@ -0,0 +1,62 @@ +--- +name: parallels-discord-roundtrip +description: Run the macOS Parallels smoke harness with Discord end-to-end roundtrip verification, including guest send, host verification, host reply, and guest readback. +--- + +# Parallels Discord Roundtrip + +Use when macOS Parallels smoke must prove Discord two-way delivery end to end. + +## Goal + +Cover: + +- install on fresh macOS snapshot +- onboard + gateway health +- guest `message send` to Discord +- host sees that message on Discord +- host posts a new Discord message +- guest `message read` sees that new message + +## Inputs + +- host env var with Discord bot token +- Discord guild ID +- Discord channel ID +- `OPENAI_API_KEY` + +## Preferred run + +```bash +export OPENCLAW_PARALLELS_DISCORD_TOKEN="$( + ssh peters-mac-studio-1 'jq -r ".channels.discord.token" ~/.openclaw/openclaw.json' | tr -d '\n' +)" + +pnpm test:parallels:macos \ + --discord-token-env OPENCLAW_PARALLELS_DISCORD_TOKEN \ + --discord-guild-id 1456350064065904867 \ + --discord-channel-id 1456744319972282449 \ + --json +``` + +## Notes + +- Snapshot target: closest to `macOS 26.3.1 fresh`. +- Snapshot resolver now prefers matching `*-poweroff*` clones when the base hint also matches. That lets the harness reuse disk-only recovery snapshots without passing a longer hint. +- If Windows/Linux snapshot restore logs show `PET_QUESTION_SNAPSHOT_STATE_INCOMPATIBLE_CPU`, drop the suspended state once, create a `*-poweroff*` replacement snapshot, and rerun. The smoke scripts now auto-start restored power-off snapshots. +- Harness configures Discord inside the guest; no checked-in token/config. +- Use the `openclaw` wrapper for guest `message send/read`; `node openclaw.mjs message ...` does not expose the lazy message subcommands the same way. +- Write `channels.discord.guilds` in one JSON object (`--strict-json`), not dotted `config set channels.discord.guilds....` paths; numeric snowflakes get treated like array indexes. +- Avoid `prlctl enter` / expect for long Discord setup scripts; it line-wraps/corrupts long commands. Use `prlctl exec --current-user /bin/sh -lc ...` for the Discord config phase. +- Full 3-OS sweeps: the shared build lock is safe in parallel, but snapshot restore is still a Parallels bottleneck. Prefer serialized Windows/Linux restore-heavy reruns if the host is already under load. +- Harness cleanup deletes the temporary Discord smoke messages at exit. +- Per-phase logs: `/tmp/openclaw-parallels-smoke.*` +- Machine summary: pass `--json` +- If roundtrip flakes, inspect `fresh.discord-roundtrip.log` and `discord-last-readback.json` in the run dir first. + +## Pass criteria + +- fresh lane or upgrade lane requested passes +- summary reports `discord=pass` for that lane +- guest outbound nonce appears in channel history +- host inbound nonce appears in `openclaw message read` output diff --git a/.dockerignore b/.dockerignore index 3a8e436d515..f24c490e9ad 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,11 @@ .git .worktrees + +# Sensitive files – docker-setup.sh writes .env with OPENCLAW_GATEWAY_TOKEN +# into the project root; keep it out of the build context. +.env +.env.* + .bun-cache .bun .tmp diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..253888ad7dc --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,54 @@ +# Protect the ownership rules themselves. +/.github/CODEOWNERS @steipete + +# WARNING: GitHub CODEOWNERS uses last-match-wins semantics. +# If you add overlapping rules below the secops block, include @openclaw/secops +# on those entries too or you can silently remove required secops review. +# Security-sensitive code, config, and docs require secops review. +/SECURITY.md @openclaw/secops +/.github/dependabot.yml @openclaw/secops +/.github/codeql/ @openclaw/secops +/.github/workflows/codeql.yml @openclaw/secops +/src/security/ @openclaw/secops +/src/secrets/ @openclaw/secops +/src/config/*secret*.ts @openclaw/secops +/src/config/**/*secret*.ts @openclaw/secops +/src/gateway/*auth*.ts @openclaw/secops +/src/gateway/**/*auth*.ts @openclaw/secops +/src/gateway/*secret*.ts @openclaw/secops +/src/gateway/**/*secret*.ts @openclaw/secops +/src/gateway/security-path*.ts @openclaw/secops +/src/gateway/resolve-configured-secret-input-string*.ts @openclaw/secops +/src/gateway/protocol/**/*secret*.ts @openclaw/secops +/src/gateway/server-methods/secrets*.ts @openclaw/secops +/src/agents/*auth*.ts @openclaw/secops +/src/agents/**/*auth*.ts @openclaw/secops +/src/agents/auth-profiles*.ts @openclaw/secops +/src/agents/auth-health*.ts @openclaw/secops +/src/agents/auth-profiles/ @openclaw/secops +/src/agents/sandbox.ts @openclaw/secops +/src/agents/sandbox-*.ts @openclaw/secops +/src/agents/sandbox/ @openclaw/secops +/src/infra/secret-file*.ts @openclaw/secops +/src/cron/stagger.ts @openclaw/secops +/src/cron/service/jobs.ts @openclaw/secops +/docs/security/ @openclaw/secops +/docs/gateway/authentication.md @openclaw/secops +/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md @openclaw/secops +/docs/gateway/sandboxing.md @openclaw/secops +/docs/gateway/secrets-plan-contract.md @openclaw/secops +/docs/gateway/secrets.md @openclaw/secops +/docs/gateway/security/ @openclaw/secops +/docs/cli/approvals.md @openclaw/secops +/docs/cli/sandbox.md @openclaw/secops +/docs/cli/security.md @openclaw/secops +/docs/cli/secrets.md @openclaw/secops +/docs/reference/secretref-credential-surface.md @openclaw/secops +/docs/reference/secretref-user-supplied-credentials-matrix.json @openclaw/secops + +# Release workflow and its supporting release-path checks. +/.github/workflows/openclaw-npm-release.yml @openclaw/openclaw-release-managers +/docs/reference/RELEASING.md @openclaw/openclaw-release-managers +/scripts/openclaw-npm-publish.sh @openclaw/openclaw-release-managers +/scripts/openclaw-npm-release-check.ts @openclaw/openclaw-release-managers +/scripts/release-check.ts @openclaw/openclaw-release-managers diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3be43c6740a..25fdcc0c805 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,7 +7,8 @@ body: - type: markdown attributes: value: | - Thanks for filing this report. Keep it concise, reproducible, and evidence-based. + Thanks for filing this report. Keep every answer concise, reproducible, and grounded in observed evidence. + Do not speculate or infer beyond the evidence. If a narrative section cannot be answered from the available evidence, respond with exactly `NOT_ENOUGH_INFO`. - type: dropdown id: bug_type attributes: @@ -23,35 +24,35 @@ body: id: summary attributes: label: Summary - description: One-sentence statement of what is broken. - placeholder: After upgrading to , behavior regressed from . + description: One-sentence statement of what is broken, based only on observed evidence. If the evidence is insufficient, respond with exactly `NOT_ENOUGH_INFO`. + placeholder: After upgrading from 2026.2.10 to 2026.2.17, Telegram thread replies stopped posting; reproduced twice and confirmed by gateway logs. validations: required: true - type: textarea id: repro attributes: label: Steps to reproduce - description: Provide the shortest deterministic repro path. + description: Provide the shortest deterministic repro path supported by direct observation. If the repro path cannot be grounded from the evidence, respond with exactly `NOT_ENOUGH_INFO`. placeholder: | - 1. Configure channel X. - 2. Send message Y. - 3. Run command Z. + 1. Start OpenClaw 2026.2.17 with the attached config. + 2. Send a Telegram thread reply in the affected chat. + 3. Observe no reply and confirm the attached `reply target not found` log line. validations: required: true - type: textarea id: expected attributes: label: Expected behavior - description: What should happen if the bug does not exist. - placeholder: Agent posts a reply in the same thread. + description: State the expected result using a concrete reference such as prior observed behavior, attached docs, or a known-good version. If no grounded reference exists, respond with exactly `NOT_ENOUGH_INFO`. + placeholder: In 2026.2.10, the agent posted replies in the same Telegram thread under the same workflow. validations: required: true - type: textarea id: actual attributes: label: Actual behavior - description: What happened instead, including user-visible errors. - placeholder: No reply is posted; gateway logs "reply target not found". + description: Describe only the observed result, including user-visible errors and cited evidence. If the observed result cannot be grounded from the evidence, respond with exactly `NOT_ENOUGH_INFO`. + placeholder: No reply is posted in the thread; the attached gateway log shows `reply target not found` at 14:23:08 UTC. validations: required: true - type: input @@ -92,12 +93,6 @@ body: placeholder: openclaw -> cloudflare-ai-gateway -> minimax validations: required: true - - type: input - id: config_location - attributes: - label: Config file / key location - description: Optional. Relevant config source or key path if this bug depends on overrides or custom provider setup. Redact secrets. - placeholder: ~/.openclaw/openclaw.json ; models.providers.cloudflare-ai-gateway.baseUrl ; ~/.openclaw/agents//agent/models.json - type: textarea id: provider_setup_details attributes: @@ -111,27 +106,28 @@ body: id: logs attributes: label: Logs, screenshots, and evidence - description: Include redacted logs/screenshots/recordings that prove the behavior. + description: Include the redacted logs, screenshots, recordings, docs, or version comparisons that support the grounded answers above. render: shell - type: textarea id: impact attributes: label: Impact and severity description: | - Explain who is affected, how severe it is, how often it happens, and the practical consequence. + Explain who is affected, how severe it is, how often it happens, and the practical consequence using only observed evidence. + If any part cannot be grounded from the evidence, respond with exactly `NOT_ENOUGH_INFO`. Include: - Affected users/systems/channels - Severity (annoying, blocks workflow, data risk, etc.) - Frequency (always/intermittent/edge case) - Consequence (missed messages, failed onboarding, extra cost, etc.) placeholder: | - Affected: Telegram group users on - Severity: High (blocks replies) - Frequency: 100% repro - Consequence: Agents cannot respond in threads + Affected: Telegram group users on 2026.2.17 + Severity: High (blocks thread replies) + Frequency: 4/4 observed attempts + Consequence: Agents do not respond in the affected threads - type: textarea id: additional_information attributes: label: Additional information - description: Add any context that helps triage but does not fit above. If this is a regression, include the last known good and first known bad versions. - placeholder: Last known good version <...>, first known bad version <...>, temporary workaround is ... + description: Add any remaining grounded context that helps triage but does not fit above. If this is a regression, include the last known good and first known bad versions when observed. If there is not enough evidence, respond with exactly `NOT_ENOUGH_INFO`. + placeholder: Last known good version 2026.2.10, first known bad version 2026.2.17, temporary workaround is sending a top-level message instead of a thread reply. diff --git a/.github/actions/setup-node-env/action.yml b/.github/actions/setup-node-env/action.yml index c46387517e4..41ca9eb98b0 100644 --- a/.github/actions/setup-node-env/action.yml +++ b/.github/actions/setup-node-env/action.yml @@ -1,12 +1,16 @@ name: Setup Node environment description: > - Initialize submodules with retry, install Node 22, pnpm, optionally Bun, + Initialize submodules with retry, install Node 24 by default, pnpm, optionally Bun, and optionally run pnpm install. Requires actions/checkout to run first. inputs: node-version: description: Node.js version to install. required: false - default: "22.x" + default: "24.x" + cache-key-suffix: + description: Suffix appended to the pnpm store cache key. + required: false + default: "node24" pnpm-version: description: pnpm version for corepack. required: false @@ -16,7 +20,7 @@ inputs: required: false default: "true" use-sticky-disk: - description: Use Blacksmith sticky disks for pnpm store caching. + description: Request Blacksmith sticky-disk pnpm caching on trusted runs; pull_request runs fall back to actions/cache. required: false default: "false" install-deps: @@ -45,7 +49,7 @@ runs: exit 1 - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@v6 with: node-version: ${{ inputs.node-version }} check-latest: false @@ -54,12 +58,12 @@ runs: uses: ./.github/actions/setup-pnpm-store-cache with: pnpm-version: ${{ inputs.pnpm-version }} - cache-key-suffix: "node22" + cache-key-suffix: ${{ inputs.cache-key-suffix }} use-sticky-disk: ${{ inputs.use-sticky-disk }} - name: Setup Bun if: inputs.install-bun == 'true' - uses: oven-sh/setup-bun@v2 + uses: oven-sh/setup-bun@v2.1.3 with: bun-version: "1.3.9" diff --git a/.github/actions/setup-pnpm-store-cache/action.yml b/.github/actions/setup-pnpm-store-cache/action.yml index e1e5a34abda..2f7c992a978 100644 --- a/.github/actions/setup-pnpm-store-cache/action.yml +++ b/.github/actions/setup-pnpm-store-cache/action.yml @@ -8,9 +8,9 @@ inputs: cache-key-suffix: description: Suffix appended to the cache key. required: false - default: "node22" + default: "node24" use-sticky-disk: - description: Use Blacksmith sticky disks instead of actions/cache for pnpm store. + description: Use Blacksmith sticky disks instead of actions/cache for pnpm store on trusted runs; pull_request runs fall back to actions/cache. required: false default: "false" use-restore-keys: @@ -18,7 +18,7 @@ inputs: required: false default: "true" use-actions-cache: - description: Whether to restore/save pnpm store with actions/cache. + description: Whether to restore/save pnpm store with actions/cache, including pull_request fallback when sticky disks are disabled. required: false default: "true" runs: @@ -51,22 +51,24 @@ runs: run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" - name: Mount pnpm store sticky disk - if: inputs.use-sticky-disk == 'true' + # Keep persistent sticky-disk state off untrusted PR runs. + if: inputs.use-sticky-disk == 'true' && github.event_name != 'pull_request' uses: useblacksmith/stickydisk@v1 with: - key: ${{ github.repository }}-pnpm-store-${{ runner.os }}-${{ inputs.cache-key-suffix }} + key: ${{ github.repository }}-pnpm-store-${{ runner.os }}-${{ github.ref_name }}-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }} path: ${{ steps.pnpm-store.outputs.path }} - name: Restore pnpm store cache (exact key only) - if: inputs.use-actions-cache == 'true' && inputs.use-sticky-disk != 'true' && inputs.use-restore-keys != 'true' - uses: actions/cache@v4 + # PRs that request sticky disks still need a safe cache restore path. + if: inputs.use-actions-cache == 'true' && (inputs.use-sticky-disk != 'true' || github.event_name == 'pull_request') && inputs.use-restore-keys != 'true' + uses: actions/cache@v5 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }} - name: Restore pnpm store cache (with fallback keys) - if: inputs.use-actions-cache == 'true' && inputs.use-sticky-disk != 'true' && inputs.use-restore-keys == 'true' - uses: actions/cache@v4 + if: inputs.use-actions-cache == 'true' && (inputs.use-sticky-disk != 'true' || github.event_name == 'pull_request') && inputs.use-restore-keys == 'true' + uses: actions/cache@v5 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }} diff --git a/.github/labeler.yml b/.github/labeler.yml index ffe55984ac6..7dcc038de4c 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -6,7 +6,6 @@ "channel: discord": - changed-files: - any-glob-to-any-file: - - "src/discord/**" - "extensions/discord/**" - "docs/channels/discord.md" "channel: irc": @@ -28,7 +27,6 @@ "channel: imessage": - changed-files: - any-glob-to-any-file: - - "src/imessage/**" - "extensions/imessage/**" - "docs/channels/imessage.md" "channel: line": @@ -64,19 +62,16 @@ "channel: signal": - changed-files: - any-glob-to-any-file: - - "src/signal/**" - "extensions/signal/**" - "docs/channels/signal.md" "channel: slack": - changed-files: - any-glob-to-any-file: - - "src/slack/**" - "extensions/slack/**" - "docs/channels/slack.md" "channel: telegram": - changed-files: - any-glob-to-any-file: - - "src/telegram/**" - "extensions/telegram/**" - "docs/channels/telegram.md" "channel: tlon": @@ -96,7 +91,6 @@ "channel: whatsapp-web": - changed-files: - any-glob-to-any-file: - - "src/web/**" - "extensions/whatsapp/**" - "docs/channels/whatsapp.md" "channel: zalo": @@ -204,14 +198,6 @@ - changed-files: - any-glob-to-any-file: - "extensions/diagnostics-otel/**" -"extensions: google-antigravity-auth": - - changed-files: - - any-glob-to-any-file: - - "extensions/google-antigravity-auth/**" -"extensions: google-gemini-cli-auth": - - changed-files: - - any-glob-to-any-file: - - "extensions/google-gemini-cli-auth/**" "extensions: llm-task": - changed-files: - any-glob-to-any-file: @@ -244,15 +230,91 @@ - changed-files: - any-glob-to-any-file: - "extensions/acpx/**" +"extensions: byteplus": + - changed-files: + - any-glob-to-any-file: + - "extensions/byteplus/**" +"extensions: anthropic": + - changed-files: + - any-glob-to-any-file: + - "extensions/anthropic/**" +"extensions: cloudflare-ai-gateway": + - changed-files: + - any-glob-to-any-file: + - "extensions/cloudflare-ai-gateway/**" "extensions: minimax-portal-auth": - changed-files: - any-glob-to-any-file: - "extensions/minimax-portal-auth/**" +"extensions: huggingface": + - changed-files: + - any-glob-to-any-file: + - "extensions/huggingface/**" +"extensions: kilocode": + - changed-files: + - any-glob-to-any-file: + - "extensions/kilocode/**" +"extensions: openai": + - changed-files: + - any-glob-to-any-file: + - "extensions/openai/**" +"extensions: kimi-coding": + - changed-files: + - any-glob-to-any-file: + - "extensions/kimi-coding/**" +"extensions: minimax": + - changed-files: + - any-glob-to-any-file: + - "extensions/minimax/**" +"extensions: modelstudio": + - changed-files: + - any-glob-to-any-file: + - "extensions/modelstudio/**" +"extensions: moonshot": + - changed-files: + - any-glob-to-any-file: + - "extensions/moonshot/**" +"extensions: nvidia": + - changed-files: + - any-glob-to-any-file: + - "extensions/nvidia/**" "extensions: phone-control": - changed-files: - any-glob-to-any-file: - "extensions/phone-control/**" +"extensions: qianfan": + - changed-files: + - any-glob-to-any-file: + - "extensions/qianfan/**" +"extensions: synthetic": + - changed-files: + - any-glob-to-any-file: + - "extensions/synthetic/**" "extensions: talk-voice": - changed-files: - any-glob-to-any-file: - "extensions/talk-voice/**" +"extensions: together": + - changed-files: + - any-glob-to-any-file: + - "extensions/together/**" +"extensions: venice": + - changed-files: + - any-glob-to-any-file: + - "extensions/venice/**" +"extensions: vercel-ai-gateway": + - changed-files: + - any-glob-to-any-file: + - "extensions/vercel-ai-gateway/**" +"extensions: volcengine": + - changed-files: + - any-glob-to-any-file: + - "extensions/volcengine/**" +"extensions: xiaomi": + - changed-files: + - any-glob-to-any-file: + - "extensions/xiaomi/**" +"extensions: fal": + - changed-files: + - any-glob-to-any-file: + - "extensions/fal/**" diff --git a/.github/workflows/auto-response.yml b/.github/workflows/auto-response.yml index d9d810bffa7..69dff002c7b 100644 --- a/.github/workflows/auto-response.yml +++ b/.github/workflows/auto-response.yml @@ -5,9 +5,12 @@ on: types: [opened, edited, labeled] issue_comment: types: [created] - pull_request_target: + pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned label automation; no untrusted checkout or code execution types: [labeled] +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + permissions: {} jobs: @@ -17,20 +20,20 @@ jobs: pull-requests: write runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token continue-on-error: true with: app-id: "2729701" private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token-fallback if: steps.app-token.outcome == 'failure' with: app-id: "2971289" private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }} - name: Handle labeled items - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@v8 with: github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} script: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2562d84d223..96ab35a297e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,10 @@ on: concurrency: group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} + cancel-in-progress: true + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" jobs: # Detect docs-only changes to skip heavy jobs (test, build, Windows, macOS, Android). @@ -19,7 +22,7 @@ jobs: docs_changed: ${{ steps.check.outputs.docs_changed }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 1 fetch-tags: false @@ -35,9 +38,8 @@ jobs: id: check uses: ./.github/actions/detect-docs-changes - # Detect which heavy areas are touched so PRs can skip unrelated expensive jobs. - # Push to main keeps broad coverage, but this job still needs to run so - # downstream jobs that list it in `needs` are not skipped. + # Detect which heavy areas are touched so CI can skip unrelated expensive jobs. + # Fail-safe: if detection fails, downstream jobs run. changed-scope: needs: [docs-scope] if: needs.docs-scope.outputs.docs_only != 'true' @@ -50,7 +52,7 @@ jobs: run_windows: ${{ steps.scope.outputs.run_windows }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 1 fetch-tags: false @@ -76,14 +78,58 @@ jobs: node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD + changed-extensions: + needs: [docs-scope, changed-scope] + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' + runs-on: blacksmith-16vcpu-ubuntu-2404 + outputs: + has_changed_extensions: ${{ steps.changed.outputs.has_changed_extensions }} + changed_extensions_matrix: ${{ steps.changed.outputs.changed_extensions_matrix }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 1 + fetch-tags: false + submodules: false + + - name: Ensure changed-extensions base commit + uses: ./.github/actions/ensure-base-commit + with: + base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }} + fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }} + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + install-deps: "false" + use-sticky-disk: "false" + + - name: Detect changed extensions + id: changed + env: + BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }} + run: | + node --input-type=module <<'EOF' + import { appendFileSync } from "node:fs"; + import { listChangedExtensionIds } from "./scripts/test-extension.mjs"; + + const extensionIds = listChangedExtensionIds({ base: process.env.BASE_SHA, head: "HEAD" }); + const matrix = JSON.stringify({ include: extensionIds.map((extension) => ({ extension })) }); + + appendFileSync(process.env.GITHUB_OUTPUT, `has_changed_extensions=${extensionIds.length > 0}\n`, "utf8"); + appendFileSync(process.env.GITHUB_OUTPUT, `changed_extensions_matrix=${matrix}\n`, "utf8"); + EOF + # Build dist once for Node-relevant changes and share it with downstream jobs. build-artifacts: needs: [docs-scope, changed-scope] - if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true') + if: github.event_name == 'push' && needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false @@ -98,13 +144,13 @@ jobs: uses: ./.github/actions/setup-node-env with: install-bun: "false" - use-sticky-disk: "true" + use-sticky-disk: "false" - name: Build dist run: pnpm build - name: Upload dist artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: dist-build path: dist/ @@ -117,7 +163,7 @@ jobs: runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false @@ -125,10 +171,10 @@ jobs: uses: ./.github/actions/setup-node-env with: install-bun: "false" - use-sticky-disk: "true" + use-sticky-disk: "false" - name: Download dist artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: dist-build path: dist/ @@ -138,7 +184,7 @@ jobs: checks: needs: [docs-scope, changed-scope] - if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true') + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 strategy: fail-fast: false @@ -146,10 +192,23 @@ jobs: include: - runtime: node task: test + shard_index: 1 + shard_count: 2 + command: pnpm canvas:a2ui:bundle && pnpm test + - runtime: node + task: test + shard_index: 2 + shard_count: 2 command: pnpm canvas:a2ui:bundle && pnpm test - runtime: node task: extensions command: pnpm test:extensions + - runtime: node + task: channels + command: pnpm test:channels + - runtime: node + task: contracts + command: pnpm test:contracts - runtime: node task: protocol command: pnpm protocol:check @@ -157,44 +216,53 @@ jobs: task: test command: pnpm canvas:a2ui:bundle && bunx vitest run --config vitest.unit.config.ts steps: - - name: Skip bun lane on push - if: github.event_name == 'push' && matrix.runtime == 'bun' - run: echo "Skipping bun test lane on push events." + - name: Skip bun lane on pull requests + if: github.event_name == 'pull_request' && matrix.runtime == 'bun' + run: echo "Skipping Bun compatibility lane on pull requests." - name: Checkout - if: github.event_name != 'push' || matrix.runtime != 'bun' - uses: actions/checkout@v4 + if: github.event_name != 'pull_request' || matrix.runtime != 'bun' + uses: actions/checkout@v6 with: submodules: false - name: Setup Node environment - if: matrix.runtime != 'bun' || github.event_name != 'push' + if: matrix.runtime != 'bun' || github.event_name != 'pull_request' uses: ./.github/actions/setup-node-env with: install-bun: "${{ matrix.runtime == 'bun' }}" - use-sticky-disk: "true" + use-sticky-disk: "false" - name: Configure Node test resources - if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node' + if: (github.event_name != 'pull_request' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node' + env: + SHARD_COUNT: ${{ matrix.shard_count || '' }} + SHARD_INDEX: ${{ matrix.shard_index || '' }} run: | # `pnpm test` runs `scripts/test-parallel.mjs`, which spawns multiple Node processes. # Default heap limits have been too low on Linux CI (V8 OOM near 4GB). echo "OPENCLAW_TEST_WORKERS=2" >> "$GITHUB_ENV" echo "OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144" >> "$GITHUB_ENV" + if [ -n "$SHARD_COUNT" ] && [ -n "$SHARD_INDEX" ]; then + echo "OPENCLAW_TEST_SHARDS=$SHARD_COUNT" >> "$GITHUB_ENV" + echo "OPENCLAW_TEST_SHARD_INDEX=$SHARD_INDEX" >> "$GITHUB_ENV" + fi - name: Run ${{ matrix.task }} (${{ matrix.runtime }}) - if: matrix.runtime != 'bun' || github.event_name != 'push' + if: matrix.runtime != 'bun' || github.event_name != 'pull_request' run: ${{ matrix.command }} - # Types, lint, and format check. - check: - name: "check" - needs: [docs-scope, changed-scope] - if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true') + extension-fast: + name: "extension-fast (${{ matrix.extension }})" + needs: [docs-scope, changed-scope, changed-extensions] + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' && needs.changed-extensions.outputs.has_changed_extensions == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.changed-extensions.outputs.changed_extensions_matrix) }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false @@ -202,7 +270,30 @@ jobs: uses: ./.github/actions/setup-node-env with: install-bun: "false" - use-sticky-disk: "true" + use-sticky-disk: "false" + + - name: Run changed extension tests + env: + OPENCLAW_CHANGED_EXTENSION: ${{ matrix.extension }} + run: pnpm test:extension "$OPENCLAW_CHANGED_EXTENSION" + + # Types, lint, and format check. + check: + name: "check" + needs: [docs-scope, changed-scope] + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' + runs-on: blacksmith-16vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: false + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + use-sticky-disk: "false" - name: Check types and lint and oxfmt run: pnpm check @@ -213,14 +304,14 @@ jobs: - name: Enforce safe external URL opening policy run: pnpm lint:ui:no-raw-window-open - # Validate docs (format, lint, broken links) only when docs files changed. - check-docs: - needs: [docs-scope] - if: needs.docs-scope.outputs.docs_changed == 'true' + plugin-extension-boundary: + name: "plugin-extension-boundary" + needs: [docs-scope, changed-scope] + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false @@ -228,23 +319,197 @@ jobs: uses: ./.github/actions/setup-node-env with: install-bun: "false" - use-sticky-disk: "true" + use-sticky-disk: "false" + + - name: Run plugin extension boundary guard + run: pnpm run lint:plugins:no-extension-imports + + web-search-provider-boundary: + name: "web-search-provider-boundary" + needs: [docs-scope, changed-scope] + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' + runs-on: blacksmith-16vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: false + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + use-sticky-disk: "false" + + - name: Run web search provider boundary guard + run: pnpm run lint:web-search-provider-boundaries + + extension-src-outside-plugin-sdk-boundary: + name: "extension-src-outside-plugin-sdk-boundary" + needs: [docs-scope, changed-scope] + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' + runs-on: blacksmith-16vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: false + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + use-sticky-disk: "false" + + - name: Run extension src boundary guard + run: pnpm run lint:extensions:no-src-outside-plugin-sdk + + extension-plugin-sdk-internal-boundary: + name: "extension-plugin-sdk-internal-boundary" + needs: [docs-scope, changed-scope] + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' + runs-on: blacksmith-16vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: false + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + use-sticky-disk: "false" + + - name: Run extension plugin-sdk-internal guard + run: pnpm run lint:extensions:no-plugin-sdk-internal + + build-smoke: + name: "build-smoke" + needs: [docs-scope, changed-scope] + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' + runs-on: blacksmith-16vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: false + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + use-sticky-disk: "false" + + - name: Build dist + run: pnpm build + + - name: Smoke test CLI launcher help + run: node openclaw.mjs --help + + - name: Smoke test CLI launcher status json + run: node openclaw.mjs status --json --timeout 1 + + - name: Smoke test built bundled plugin singleton + run: pnpm test:build:singleton + + - name: Check CLI startup memory + run: pnpm test:startup:memory + + gateway-watch-regression: + name: "gateway-watch-regression" + needs: [docs-scope, changed-scope] + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' + runs-on: blacksmith-16vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: false + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + use-sticky-disk: "false" + + - name: Run gateway watch regression harness + run: pnpm test:gateway:watch-regression + + - name: Upload gateway watch regression artifacts + if: always() + uses: actions/upload-artifact@v7 + with: + name: gateway-watch-regression + path: .local/gateway-watch-regression/ + retention-days: 7 + + # Validate docs (format, lint, broken links) only when docs files changed. + check-docs: + needs: [docs-scope] + if: needs.docs-scope.outputs.docs_changed == 'true' + runs-on: blacksmith-16vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: false + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + use-sticky-disk: "false" - name: Check docs run: pnpm check:docs - skills-python: + compat-node22: + name: "compat-node22" needs: [docs-scope, changed-scope] - if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true' || needs.changed-scope.outputs.run_skills_python == 'true') + if: github.event_name == 'push' && needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 + with: + submodules: false + + - name: Setup Node 22 compatibility environment + uses: ./.github/actions/setup-node-env + with: + node-version: "22.x" + cache-key-suffix: "node22" + install-bun: "false" + use-sticky-disk: "false" + + - name: Configure Node 22 test resources + run: | + # Keep the compatibility lane aligned with the default Node test lane. + echo "OPENCLAW_TEST_WORKERS=2" >> "$GITHUB_ENV" + echo "OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144" >> "$GITHUB_ENV" + + - name: Build under Node 22 + run: pnpm build + + - name: Run tests under Node 22 + run: pnpm test + + - name: Verify npm pack under Node 22 + run: pnpm release:check + + skills-python: + needs: [docs-scope, changed-scope] + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_skills_python == 'true' + runs-on: blacksmith-16vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v6 with: submodules: false - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.12" @@ -263,7 +528,7 @@ jobs: runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false @@ -282,7 +547,7 @@ jobs: - name: Setup Python id: setup-python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.12" cache: "pip" @@ -292,7 +557,7 @@ jobs: .github/workflows/ci.yml - name: Restore pre-commit cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.cache/pre-commit key: pre-commit-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }} @@ -306,21 +571,30 @@ jobs: run: pre-commit run --all-files detect-private-key - name: Audit changed GitHub workflows with zizmor + env: + BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }} run: | set -euo pipefail - if [ "${{ github.event_name }}" = "push" ]; then - BASE="${{ github.event.before }}" - else - BASE="${{ github.event.pull_request.base.sha }}" + if [ -z "${BASE_SHA:-}" ] || [ "${BASE_SHA}" = "0000000000000000000000000000000000000000" ]; then + echo "No usable base SHA detected; skipping zizmor." + exit 0 fi - mapfile -t workflow_files < <(git diff --name-only "$BASE" HEAD -- '.github/workflows/*.yml' '.github/workflows/*.yaml') + if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then + echo "Base SHA ${BASE_SHA} is unavailable; skipping zizmor." + exit 0 + fi + + mapfile -t workflow_files < <( + git diff --name-only "${BASE_SHA}" HEAD -- '.github/workflows/*.yml' '.github/workflows/*.yaml' + ) if [ "${#workflow_files[@]}" -eq 0 ]; then echo "No workflow changes detected; skipping zizmor." exit 0 fi + printf 'Auditing workflow files:\n%s\n' "${workflow_files[@]}" pre-commit run zizmor --files "${workflow_files[@]}" - name: Audit production dependencies @@ -328,7 +602,7 @@ jobs: checks-windows: needs: [docs-scope, changed-scope] - if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_windows == 'true') + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_windows == 'true' runs-on: blacksmith-32vcpu-windows-2025 timeout-minutes: 45 env: @@ -375,7 +649,7 @@ jobs: command: pnpm test steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false @@ -399,16 +673,16 @@ jobs: } - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@v6 with: - node-version: 22.x + node-version: 24.x check-latest: false - name: Setup pnpm + cache store uses: ./.github/actions/setup-pnpm-store-cache with: pnpm-version: "10.23.0" - cache-key-suffix: "node22" + cache-key-suffix: "node24" # Sticky disk mount currently retries/fails on every shard and adds ~50s # before install while still yielding zero pnpm store reuse. # Try exact-key actions/cache restores instead to recover store reuse @@ -461,7 +735,7 @@ jobs: runs-on: macos-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false @@ -497,7 +771,7 @@ jobs: swiftformat --lint apps/macos/Sources --config .swiftformat - name: Cache SwiftPM - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/Library/Caches/org.swift.swiftpm key: ${{ runner.os }}-swiftpm-${{ hashFiles('apps/macos/Package.resolved') }} @@ -533,7 +807,7 @@ jobs: runs-on: macos-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false @@ -690,7 +964,7 @@ jobs: android: needs: [docs-scope, changed-scope] - if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_android == 'true') + if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_android == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 strategy: fail-fast: false @@ -702,31 +976,45 @@ jobs: command: ./gradlew --no-daemon :app:assembleDebug steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin - # setup-android's sdkmanager currently crashes on JDK 21 in CI. + # Keep sdkmanager on the stable JDK path for Linux CI runners. java-version: 17 - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - with: - accept-android-sdk-licenses: false + - name: Setup Android SDK cmdline-tools + run: | + set -euo pipefail + ANDROID_SDK_ROOT="$HOME/.android-sdk" + CMDLINE_TOOLS_VERSION="12266719" + ARCHIVE="commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip" + URL="https://dl.google.com/android/repository/${ARCHIVE}" + + mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools" + curl -fsSL "$URL" -o "/tmp/${ARCHIVE}" + rm -rf "$ANDROID_SDK_ROOT/cmdline-tools/latest" + unzip -q "/tmp/${ARCHIVE}" -d "$ANDROID_SDK_ROOT/cmdline-tools" + mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest" + + echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV" + echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV" + echo "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" >> "$GITHUB_PATH" + echo "$ANDROID_SDK_ROOT/platform-tools" >> "$GITHUB_PATH" - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 with: gradle-version: 8.11.1 - name: Install Android SDK packages run: | - yes | sdkmanager --licenses >/dev/null - sdkmanager --install \ + yes | sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --licenses >/dev/null + sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --install \ "platform-tools" \ "platforms;android-36" \ "build-tools;36.0.0" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1d8e473af4f..79c041ef727 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -7,6 +7,9 @@ concurrency: group: codeql-${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + permissions: actions: read contents: read @@ -67,7 +70,7 @@ jobs: config_file: "" steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false @@ -76,17 +79,17 @@ jobs: uses: ./.github/actions/setup-node-env with: install-bun: "false" - use-sticky-disk: "true" + use-sticky-disk: "false" - name: Setup Python if: matrix.needs_python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.12" - name: Setup Java if: matrix.needs_java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: "21" diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 2cc29748c91..5eaba459957 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -12,19 +12,65 @@ on: - "**/*.mdx" - ".agents/**" - "skills/**" + workflow_dispatch: + inputs: + tag: + description: Existing release tag to backfill (for example v2026.3.13) + required: true + type: string concurrency: - group: docker-release-${{ github.workflow }}-${{ github.ref }} + group: docker-release-${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} cancel-in-progress: false env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: + validate_manual_backfill: + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: Validate tag input format + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-beta\.[1-9][0-9]*)?$ ]]; then + echo "Invalid release tag: ${RELEASE_TAG}" + exit 1 + fi + + - name: Checkout selected tag + uses: actions/checkout@v6 + with: + ref: refs/tags/${{ inputs.tag }} + fetch-depth: 0 + + approve_manual_backfill: + if: github.event_name == 'workflow_dispatch' + needs: validate_manual_backfill + # WARNING: KEEP MANUAL BACKFILLS GATED BY THE docker-release ENVIRONMENT. + runs-on: ubuntu-24.04 + environment: docker-release + steps: + - name: Approve Docker backfill + env: + RELEASE_TAG: ${{ inputs.tag }} + run: echo "Approved Docker backfill for $RELEASE_TAG" + + # KEEP THIS WORKFLOW ON GITHUB-HOSTED RUNNERS. + # DO NOT MOVE IT BACK TO BLACKSMITH WITHOUT RE-VALIDATING TAG BUILDS AND BACKFILLS. # Build amd64 images (default + slim share the build stage cache) build-amd64: - runs-on: blacksmith-16vcpu-ubuntu-2404 + needs: [approve_manual_backfill] + if: ${{ always() && (github.event_name != 'workflow_dispatch' || needs.approve_manual_backfill.result == 'success') }} + # WARNING: DO NOT REVERT THIS TO A BLACKSMITH RUNNER WITHOUT RE-VALIDATING TAG BACKFILLS. + runs-on: ubuntu-24.04 permissions: packages: write contents: read @@ -33,13 +79,16 @@ jobs: slim-digest: ${{ steps.build-slim.outputs.digest }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }} + fetch-depth: 0 - name: Set up Docker Builder - uses: useblacksmith/setup-docker-builder@v1 + uses: docker/setup-buildx-action@v4 - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} username: ${{ github.repository_owner }} @@ -50,21 +99,22 @@ jobs: shell: bash env: IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }} run: | set -euo pipefail tags=() slim_tags=() - if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then + if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then tags+=("${IMAGE}:main-amd64") slim_tags+=("${IMAGE}:main-slim-amd64") fi - if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then - version="${GITHUB_REF#refs/tags/v}" + if [[ "${SOURCE_REF}" == refs/tags/v* ]]; then + version="${SOURCE_REF#refs/tags/v}" tags+=("${IMAGE}:${version}-amd64") slim_tags+=("${IMAGE}:${version}-slim-amd64") fi if [[ ${#tags[@]} -eq 0 ]]; then - echo "::error::No amd64 tags resolved for ref ${GITHUB_REF}" + echo "::error::No amd64 tags resolved for ref ${SOURCE_REF}" exit 1 fi { @@ -81,19 +131,22 @@ jobs: - name: Resolve OCI labels (amd64) id: labels shell: bash + env: + SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }} run: | set -euo pipefail - version="${GITHUB_SHA}" - if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then + source_sha="$(git rev-parse HEAD)" + version="${source_sha}" + if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then version="main" fi - if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then - version="${GITHUB_REF#refs/tags/v}" + if [[ "${SOURCE_REF}" == refs/tags/v* ]]; then + version="${SOURCE_REF#refs/tags/v}" fi created="$(date -u +%Y-%m-%dT%H:%M:%SZ)" { echo "value< entry.id === \"matrix\"); + if (!matrix) { + throw new Error(\"matrix plugin missing from bundled plugin list\"); + } + const matrixDiag = (parsed.diagnostics || []).filter( + (diag) => + typeof diag.source === \"string\" && + diag.source.includes(\"/extensions/matrix\") && + typeof diag.message === \"string\" && + diag.message.includes(\"extension entry escapes package directory\"), + ); + if (matrixDiag.length > 0) { + throw new Error( + \"unexpected matrix diagnostics: \" + + matrixDiag.map((diag) => diag.message).join(\"; \"), + ); + } + " + ' - name: Build installer smoke image uses: useblacksmith/build-push-action@v2 diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 8de54a416f8..3a38e5213c3 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,7 +1,7 @@ name: Labeler on: - pull_request_target: + pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned triage workflow; no untrusted checkout or PR code execution types: [opened, synchronize, reopened] issues: types: [opened] @@ -16,6 +16,9 @@ on: required: false default: "50" +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + permissions: {} jobs: @@ -25,25 +28,25 @@ jobs: pull-requests: write runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token continue-on-error: true with: app-id: "2729701" private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token-fallback if: steps.app-token.outcome == 'failure' with: app-id: "2971289" private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }} - - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 + - uses: actions/labeler@v6 with: configuration-path: .github/labeler.yml repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} sync-labels: true - name: Apply PR size label - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@v8 with: github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} script: | @@ -132,7 +135,7 @@ jobs: labels: [targetSizeLabel], }); - name: Apply maintainer or trusted-contributor label - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@v8 with: github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} script: | @@ -203,7 +206,7 @@ jobs: // }); // } - name: Apply too-many-prs label - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@v8 with: github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} script: | @@ -381,20 +384,20 @@ jobs: pull-requests: write runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token continue-on-error: true with: app-id: "2729701" private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token-fallback if: steps.app-token.outcome == 'failure' with: app-id: "2971289" private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }} - name: Backfill PR labels - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@v8 with: github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} script: | @@ -629,20 +632,20 @@ jobs: issues: write runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token continue-on-error: true with: app-id: "2729701" private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token-fallback if: steps.app-token.outcome == 'failure' with: app-id: "2971289" private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }} - name: Apply maintainer or trusted-contributor label - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@v8 with: github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} script: | diff --git a/.github/workflows/openclaw-npm-release.yml b/.github/workflows/openclaw-npm-release.yml index 09126ed6ad2..c7f53567612 100644 --- a/.github/workflows/openclaw-npm-release.yml +++ b/.github/workflows/openclaw-npm-release.yml @@ -4,26 +4,149 @@ on: push: tags: - "v*" + workflow_dispatch: + inputs: + tag: + description: Release tag to publish (for example v2026.3.14, v2026.3.14-beta.1, or fallback v2026.3.14-1) + required: true + type: string concurrency: - group: openclaw-npm-release-${{ github.ref }} + group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} cancel-in-progress: false env: - NODE_VERSION: "22.x" + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + NODE_VERSION: "24.x" PNPM_VERSION: "10.23.0" jobs: + preview_openclaw_npm: + if: github.event_name == 'push' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + node-version: ${{ env.NODE_VERSION }} + pnpm-version: ${{ env.PNPM_VERSION }} + install-bun: "false" + use-sticky-disk: "false" + + - name: Print release plan + env: + RELEASE_TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + RELEASE_SHA=$(git rev-parse HEAD) + PACKAGE_VERSION=$(node -p "require('./package.json').version") + if [[ "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*-[1-9][0-9]*$ ]]; then + TAG_KIND="fallback correction" + else + TAG_KIND="standard" + fi + echo "Release plan for ${RELEASE_TAG}:" + echo "Resolved release SHA: ${RELEASE_SHA}" + echo "Resolved package version: ${PACKAGE_VERSION}" + echo "Resolved tag kind: ${TAG_KIND}" + if [[ "${TAG_KIND}" == "fallback correction" ]]; then + echo "Correction tag note: npm version remains ${PACKAGE_VERSION}" + fi + echo "Would run: git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main" + echo "Would run with env: RELEASE_SHA=${RELEASE_SHA} RELEASE_TAG=${RELEASE_TAG} RELEASE_MAIN_REF=origin/main pnpm release:openclaw:npm:check" + echo "Would run: npm view openclaw@${PACKAGE_VERSION} version" + echo "Would run: pnpm check" + echo "Would run: pnpm build" + echo "Would run: pnpm release:check" + + - name: Validate release tag and package metadata + env: + RELEASE_TAG: ${{ github.ref_name }} + RELEASE_MAIN_REF: origin/main + run: | + set -euxo pipefail + RELEASE_SHA=$(git rev-parse HEAD) + export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF + # Fetch the full main ref so merge-base ancestry checks keep working + # for older tagged commits that are still contained in main. + git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main + pnpm release:openclaw:npm:check + + - name: Ensure version is not already published + env: + RELEASE_TAG: ${{ github.ref_name }} + run: | + set -euxo pipefail + PACKAGE_VERSION=$(node -p "require('./package.json').version") + IS_CORRECTION_TAG=0 + if [[ "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*-[1-9][0-9]*$ ]]; then + IS_CORRECTION_TAG=1 + fi + + if npm view "openclaw@${PACKAGE_VERSION}" version >/dev/null 2>&1; then + if [[ "${IS_CORRECTION_TAG}" == "1" ]]; then + echo "openclaw@${PACKAGE_VERSION} is already published on npm." + echo "Correction tag ${RELEASE_TAG} is allowed as a fallback release tag, so preview will continue without treating this as an error." + exit 0 + fi + echo "openclaw@${PACKAGE_VERSION} is already published on npm." + exit 1 + fi + + if [[ "${IS_CORRECTION_TAG}" == "1" ]]; then + echo "Previewing fallback correction tag ${RELEASE_TAG} for npm version openclaw@${PACKAGE_VERSION}" + else + echo "Previewing openclaw@${PACKAGE_VERSION}" + fi + + - name: Check + run: | + set -euxo pipefail + pnpm check + + - name: Build + run: | + set -euxo pipefail + pnpm build + + - name: Verify release contents + run: | + set -euxo pipefail + pnpm release:check + + - name: Preview publish command + run: bash scripts/openclaw-npm-publish.sh --dry-run + publish_openclaw_npm: + if: github.event_name == 'workflow_dispatch' # npm trusted publishing + provenance requires a GitHub-hosted runner. runs-on: ubuntu-latest + environment: npm-release permissions: contents: read id-token: write steps: + - name: Validate tag input format + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then + echo "Invalid release tag format: ${RELEASE_TAG}" + exit 1 + fi + - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: + ref: refs/tags/${{ inputs.tag }} fetch-depth: 0 - name: Setup Node environment @@ -36,11 +159,12 @@ jobs: - name: Validate release tag and package metadata env: - RELEASE_SHA: ${{ github.sha }} - RELEASE_TAG: ${{ github.ref_name }} + RELEASE_TAG: ${{ inputs.tag }} RELEASE_MAIN_REF: origin/main run: | set -euo pipefail + RELEASE_SHA=$(git rev-parse HEAD) + export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF # Fetch the full main ref so merge-base ancestry checks keep working # for older tagged commits that are still contained in main. git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main @@ -68,12 +192,4 @@ jobs: run: pnpm release:check - name: Publish - run: | - set -euo pipefail - PACKAGE_VERSION=$(node -p "require('./package.json').version") - - if [[ "$PACKAGE_VERSION" == *-beta.* ]]; then - npm publish --access public --tag beta --provenance - else - npm publish --access public --provenance - fi + run: bash scripts/openclaw-npm-publish.sh --publish diff --git a/.github/workflows/plugin-npm-release.yml b/.github/workflows/plugin-npm-release.yml new file mode 100644 index 00000000000..3507a0b68a1 --- /dev/null +++ b/.github/workflows/plugin-npm-release.yml @@ -0,0 +1,214 @@ +name: Plugin NPM Release + +on: + push: + branches: + - main + paths: + - ".github/workflows/plugin-npm-release.yml" + - "extensions/**" + - "package.json" + - "scripts/lib/plugin-npm-release.ts" + - "scripts/plugin-npm-publish.sh" + - "scripts/plugin-npm-release-check.ts" + - "scripts/plugin-npm-release-plan.ts" + workflow_dispatch: + inputs: + publish_scope: + description: Publish the selected plugins or all publishable plugins from the ref + required: true + default: selected + type: choice + options: + - selected + - all-publishable + ref: + description: Commit SHA on main to publish from (copy from the preview run) + required: true + type: string + plugins: + description: Comma-separated plugin package names to publish when publish_scope=selected + required: false + type: string + +concurrency: + group: plugin-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }} + cancel-in-progress: false + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + NODE_VERSION: "24.x" + PNPM_VERSION: "10.23.0" + +jobs: + preview_plugins_npm: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + ref_sha: ${{ steps.ref.outputs.sha }} + has_candidates: ${{ steps.plan.outputs.has_candidates }} + candidate_count: ${{ steps.plan.outputs.candidate_count }} + matrix: ${{ steps.plan.outputs.matrix }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }} + fetch-depth: 0 + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + node-version: ${{ env.NODE_VERSION }} + pnpm-version: ${{ env.PNPM_VERSION }} + install-bun: "false" + use-sticky-disk: "false" + + - name: Resolve checked-out ref + id: ref + run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Validate ref is on main + run: | + set -euo pipefail + git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main + git merge-base --is-ancestor HEAD origin/main + + - name: Validate publishable plugin metadata + env: + PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }} + RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }} + BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }} + HEAD_REF: ${{ steps.ref.outputs.sha }} + run: | + set -euo pipefail + if [[ -n "${PUBLISH_SCOPE}" ]]; then + release_args=(--selection-mode "${PUBLISH_SCOPE}") + if [[ -n "${RELEASE_PLUGINS}" ]]; then + release_args+=(--plugins "${RELEASE_PLUGINS}") + fi + pnpm release:plugins:npm:check -- "${release_args[@]}" + elif [[ -n "${BASE_REF}" ]]; then + pnpm release:plugins:npm:check -- --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}" + else + pnpm release:plugins:npm:check + fi + + - name: Resolve plugin release plan + id: plan + env: + PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }} + RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }} + BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }} + HEAD_REF: ${{ steps.ref.outputs.sha }} + run: | + set -euo pipefail + mkdir -p .local + if [[ -n "${PUBLISH_SCOPE}" ]]; then + plan_args=(--selection-mode "${PUBLISH_SCOPE}") + if [[ -n "${RELEASE_PLUGINS}" ]]; then + plan_args+=(--plugins "${RELEASE_PLUGINS}") + fi + node --import tsx scripts/plugin-npm-release-plan.ts "${plan_args[@]}" > .local/plugin-npm-release-plan.json + elif [[ -n "${BASE_REF}" ]]; then + node --import tsx scripts/plugin-npm-release-plan.ts --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}" > .local/plugin-npm-release-plan.json + else + node --import tsx scripts/plugin-npm-release-plan.ts > .local/plugin-npm-release-plan.json + fi + + cat .local/plugin-npm-release-plan.json + + candidate_count="$(jq -r '.candidates | length' .local/plugin-npm-release-plan.json)" + has_candidates="false" + if [[ "${candidate_count}" != "0" ]]; then + has_candidates="true" + fi + matrix_json="$(jq -c '.candidates' .local/plugin-npm-release-plan.json)" + + { + echo "candidate_count=${candidate_count}" + echo "has_candidates=${has_candidates}" + echo "matrix=${matrix_json}" + } >> "$GITHUB_OUTPUT" + + echo "Plugin release candidates:" + jq -r '.candidates[]? | "- \(.packageName)@\(.version) [\(.publishTag)] from \(.packageDir)"' .local/plugin-npm-release-plan.json + + echo "Already published / skipped:" + jq -r '.skippedPublished[]? | "- \(.packageName)@\(.version)"' .local/plugin-npm-release-plan.json + + preview_plugin_pack: + needs: preview_plugins_npm + if: needs.preview_plugins_npm.outputs.has_candidates == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + plugin: ${{ fromJson(needs.preview_plugins_npm.outputs.matrix) }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ needs.preview_plugins_npm.outputs.ref_sha }} + fetch-depth: 1 + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + node-version: ${{ env.NODE_VERSION }} + pnpm-version: ${{ env.PNPM_VERSION }} + install-bun: "false" + use-sticky-disk: "false" + install-deps: "false" + + - name: Preview publish command + run: bash scripts/plugin-npm-publish.sh --dry-run "${{ matrix.plugin.packageDir }}" + + - name: Preview npm pack contents + working-directory: ${{ matrix.plugin.packageDir }} + run: npm pack --dry-run --json --ignore-scripts + + publish_plugins_npm: + needs: [preview_plugins_npm, preview_plugin_pack] + if: github.event_name == 'workflow_dispatch' && needs.preview_plugins_npm.outputs.has_candidates == 'true' + runs-on: ubuntu-latest + environment: npm-release + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + plugin: ${{ fromJson(needs.preview_plugins_npm.outputs.matrix) }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ needs.preview_plugins_npm.outputs.ref_sha }} + fetch-depth: 1 + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + node-version: ${{ env.NODE_VERSION }} + pnpm-version: ${{ env.PNPM_VERSION }} + install-bun: "false" + use-sticky-disk: "false" + install-deps: "false" + + - name: Ensure version is not already published + env: + PACKAGE_NAME: ${{ matrix.plugin.packageName }} + PACKAGE_VERSION: ${{ matrix.plugin.version }} + run: | + set -euo pipefail + if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version >/dev/null 2>&1; then + echo "${PACKAGE_NAME}@${PACKAGE_VERSION} is already published on npm." + exit 1 + fi + + - name: Publish + run: bash scripts/plugin-npm-publish.sh --publish "${{ matrix.plugin.packageDir }}" diff --git a/.github/workflows/sandbox-common-smoke.yml b/.github/workflows/sandbox-common-smoke.yml index 13688bd0f25..4a839b4d878 100644 --- a/.github/workflows/sandbox-common-smoke.yml +++ b/.github/workflows/sandbox-common-smoke.yml @@ -17,17 +17,20 @@ concurrency: group: sandbox-common-smoke-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + jobs: sandbox-common-smoke: runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: false - name: Set up Docker Builder - uses: useblacksmith/setup-docker-builder@v1 + uses: docker/setup-buildx-action@v4 - name: Build minimal sandbox base (USER sandbox) shell: bash diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index e6feef90e6b..95dc406da45 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -5,6 +5,9 @@ on: - cron: "17 3 * * *" workflow_dispatch: +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + permissions: {} jobs: @@ -14,13 +17,13 @@ jobs: pull-requests: write runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token continue-on-error: true with: app-id: "2729701" private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token-fallback continue-on-error: true with: @@ -29,7 +32,7 @@ jobs: - name: Mark stale issues and pull requests (primary) id: stale-primary continue-on-error: true - uses: actions/stale@v9 + uses: actions/stale@v10 with: repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }} days-before-issue-stale: 7 @@ -62,7 +65,7 @@ jobs: - name: Check stale state cache id: stale-state if: always() - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@v8 with: github-token: ${{ steps.app-token-fallback.outputs.token || steps.app-token.outputs.token }} script: | @@ -85,7 +88,7 @@ jobs: } - name: Mark stale issues and pull requests (fallback) if: (steps.stale-primary.outcome == 'failure' || steps.stale-state.outputs.has_state == 'true') && steps.app-token-fallback.outputs.token != '' - uses: actions/stale@v9 + uses: actions/stale@v10 with: repo-token: ${{ steps.app-token-fallback.outputs.token }} days-before-issue-stale: 7 @@ -121,13 +124,13 @@ jobs: issues: write runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + - uses: actions/create-github-app-token@v2 id: app-token with: app-id: "2729701" private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - name: Lock closed issues after 48h of no comments - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@v8 with: github-token: ${{ steps.app-token.outputs.token }} script: | diff --git a/.github/workflows/workflow-sanity.yml b/.github/workflows/workflow-sanity.yml index 19668e697ad..72b6874a5c1 100644 --- a/.github/workflows/workflow-sanity.yml +++ b/.github/workflows/workflow-sanity.yml @@ -4,17 +4,22 @@ on: pull_request: push: branches: [main] + workflow_dispatch: concurrency: group: workflow-sanity-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + jobs: no-tabs: + if: github.event_name != 'workflow_dispatch' runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Fail on tabs in workflow files run: | @@ -42,10 +47,11 @@ jobs: PY actionlint: + if: github.event_name != 'workflow_dispatch' runs-on: blacksmith-16vcpu-ubuntu-2404 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install actionlint shell: bash @@ -65,3 +71,19 @@ jobs: - name: Disallow direct inputs interpolation in composite run blocks run: python3 scripts/check-composite-action-input-interpolation.py + + config-docs-drift: + if: github.event_name == 'workflow_dispatch' + runs-on: blacksmith-16vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + use-sticky-disk: "false" + + - name: Check config docs drift statefile + run: pnpm config:docs:check diff --git a/.gitignore b/.gitignore index 4defa8acb33..82bf37a8164 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,15 @@ node_modules **/node_modules/ .env +docker-compose.override.yml docker-compose.extra.yml dist +dist-runtime pnpm-lock.yaml bun.lock bun.lockb coverage +__openclaw_vitest__/ __pycache__/ *.pyc .tsbuildinfo @@ -97,8 +100,6 @@ USER.md /local/ package-lock.json .claude/ -.agents/ -.agents .agent/ skills-lock.json @@ -123,3 +124,15 @@ dist/protocol.schema.json # Synthing **/.stfolder/ .dev-state +docs/superpowers/plans/2026-03-10-collapsed-side-nav.md +docs/superpowers/specs/2026-03-10-collapsed-side-nav-design.md +.gitignore +test/config-form.analyze.telegram.test.ts +ui/src/ui/theme-variants.browser.test.ts +ui/src/ui/__screenshots__ +ui/src/ui/views/__screenshots__ +ui/.vitest-attachments +docs/superpowers + +# Deprecated changelog fragment workflow +changelog/fragments/ diff --git a/.jscpd.json b/.jscpd.json new file mode 100644 index 00000000000..777b025b0c8 --- /dev/null +++ b/.jscpd.json @@ -0,0 +1,16 @@ +{ + "gitignore": true, + "noSymlinks": true, + "ignore": [ + "**/node_modules/**", + "**/dist/**", + "dist/**", + "**/.git/**", + "**/coverage/**", + "**/build/**", + "**/.build/**", + "**/.artifacts/**", + "docs/zh-CN/**", + "**/CHANGELOG.md" + ] +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000000..fcc490ae35d --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +**/node_modules/ +docs/.generated/ diff --git a/.npmrc b/.npmrc index 05620061611..bdf24a6c276 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,4 @@ # pnpm build-script allowlist lives in package.json -> pnpm.onlyBuiltDependencies. +# TS 7 native-preview fails to resolve packages reliably from pnpm's isolated linker. +# Keep the workspace on a hoisted layout so pnpm check/build stay stable. +node-linker=hoisted diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000000..8af8b9e55d1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +docs/.generated/ diff --git a/.secrets.baseline b/.secrets.baseline index 5a0c639b9e3..07641fb920b 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -12314,14 +12314,14 @@ "filename": "src/config/schema.help.ts", "hashed_secret": "9f4cda226d3868676ac7f86f59e4190eb94bd208", "is_verified": false, - "line_number": 653 + "line_number": 657 }, { "type": "Secret Keyword", "filename": "src/config/schema.help.ts", "hashed_secret": "01822c8bbf6a8b136944b14182cb885100ec2eae", "is_verified": false, - "line_number": 686 + "line_number": 690 } ], "src/config/schema.irc.ts": [ @@ -12360,14 +12360,14 @@ "filename": "src/config/schema.labels.ts", "hashed_secret": "e73c9fcad85cd4eecc74181ec4bdb31064d68439", "is_verified": false, - "line_number": 217 + "line_number": 219 }, { "type": "Secret Keyword", "filename": "src/config/schema.labels.ts", "hashed_secret": "2eda7cd978f39eebec3bf03e4410a40e14167fff", "is_verified": false, - "line_number": 326 + "line_number": 328 } ], "src/config/slack-http-config.test.ts": [ @@ -12991,7 +12991,7 @@ "filename": "ui/src/i18n/locales/en.ts", "hashed_secret": "de0ff6b974d6910aca8d6b830e1b761f076d8fe6", "is_verified": false, - "line_number": 61 + "line_number": 74 } ], "ui/src/i18n/locales/pt-BR.ts": [ @@ -13000,7 +13000,7 @@ "filename": "ui/src/i18n/locales/pt-BR.ts", "hashed_secret": "ef7b6f95faca2d7d3a5aa5a6434c89530c6dd243", "is_verified": false, - "line_number": 61 + "line_number": 73 } ], "vendor/a2ui/README.md": [ diff --git a/AGENTS.md b/AGENTS.md index 69b0df68faa..488bc0678fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,43 +2,7 @@ - Repo: https://github.com/openclaw/openclaw - In chat replies, file references must be repo-root relative only (example: `extensions/bluebubbles/src/channel.ts:80`); never absolute paths or `~/...`. -- GitHub issues/comments/PR comments: use literal multiline strings or `-F - <<'EOF'` (or $'...') for real newlines; never embed "\\n". -- GitHub comment footgun: never use `gh issue/pr comment -b "..."` when body contains backticks or shell chars. Always use single-quoted heredoc (`-F - <<'EOF'`) so no command substitution/escaping corruption. -- GitHub linking footgun: don’t wrap issue/PR refs like `#24643` in backticks when you want auto-linking. Use plain `#24643` (optionally add full URL). -- PR landing comments: always make commit SHAs clickable with full commit links (both landed SHA + source SHA when present). -- PR review conversations: if a bot leaves review conversations on your PR, address them and resolve those conversations yourself once fixed. Leave a conversation unresolved only when reviewer or maintainer judgment is still needed; do not leave bot-conversation cleanup to maintainers. -- GitHub searching footgun: don't limit yourself to the first 500 issues or PRs when wanting to search all. Unless you're supposed to look at the most recent, keep going until you've reached the last page in the search -- Security advisory analysis: before triage/severity decisions, read `SECURITY.md` to align with OpenClaw's trust model and design boundaries. - -## Auto-close labels (issues and PRs) - -- If an issue/PR matches one of the reasons below, apply the label and let `.github/workflows/auto-response.yml` handle comment/close/lock. -- Do not manually close + manually comment for these reasons. -- Why: keeps wording consistent, preserves automation behavior (`state_reason`, locking), and keeps triage/reporting searchable by label. -- `r:*` labels can be used on both issues and PRs. - -- `r: skill`: close with guidance to publish skills on Clawhub. -- `r: support`: close with redirect to Discord support + stuck FAQ. -- `r: no-ci-pr`: close test-fix-only PRs for failing `main` CI and post the standard explanation. -- `r: too-many-prs`: close when author exceeds active PR limit. -- `r: testflight`: close requests asking for TestFlight access/builds. OpenClaw does not provide TestFlight distribution yet, so use the standard response (“Not available, build from source.”) instead of ad-hoc replies. -- `r: third-party-extension`: close with guidance to ship as third-party plugin. -- `r: moltbook`: close + lock as off-topic (not affiliated). -- `r: spam`: close + lock as spam (`lock_reason: spam`). -- `invalid`: close invalid items (issues are closed as `not_planned`; PRs are closed). -- `dirty`: close PRs with too many unrelated/unexpected changes (PR-only label). - -## PR truthfulness and bug-fix validation - -- Never merge a bug-fix PR based only on issue text, PR text, or AI rationale. -- Before `/landpr`, run `/reviewpr` and require explicit evidence for bug-fix claims. -- Minimum merge gate for bug-fix PRs: - 1. symptom evidence (repro/log/failing test), - 2. verified root cause in code with file/line, - 3. fix touches the implicated code path, - 4. regression test (fail before/pass after) when feasible; if not feasible, include manual verification proof and why no test was added. -- If claim is unsubstantiated or likely hallucinated/BS: do not merge. Request evidence/changes, or close with `invalid` when appropriate. -- If linked issue appears wrong/outdated, correct triage first; do not merge speculative fixes. +- Do not edit files covered by security-focused `CODEOWNERS` rules unless a listed owner explicitly asked for the change or is already reviewing it with you. Treat those paths as restricted surfaces, not drive-by cleanup. ## Project Structure & Module Organization @@ -47,6 +11,7 @@ - Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`. - Plugins/extensions: live under `extensions/*` (workspace packages). Keep plugin-only deps in the extension `package.json`; do not add them to the root `package.json` unless core uses them. - Plugins: install runs `npm install --omit=dev` in plugin dir; runtime deps must live in `dependencies`. Avoid `workspace:*` in `dependencies` (npm install breaks); put `openclaw` in `devDependencies` or `peerDependencies` instead (runtime resolves `openclaw/plugin-sdk` via jiti alias). +- Import boundaries: extension production code should treat `openclaw/plugin-sdk/*` plus local `api.ts` / `runtime-api.ts` barrels as the public surface. Do not import core `src/**`, `src/plugin-sdk-internal/**`, or another extension's `src/**` directly. - Installers served from `https://openclaw.ai/*`: live in the sibling repo `../openclaw.ai` (`public/install.sh`, `public/install-cli.sh`, `public/install.ps1`). - Messaging channels: always consider **all** built-in + extension channels when refactoring shared logic (routing, allowlists, pairing, command gating, onboarding, docs). - Core channel docs: `docs/channels/` @@ -71,6 +36,8 @@ - `docs/zh-CN/**` is generated; do not edit unless the user explicitly asks. - Pipeline: update English docs → adjust glossary (`docs/.i18n/glossary.zh-CN.json`) → run `scripts/docs-i18n` → apply targeted fixes only if instructed. +- Before rerunning `scripts/docs-i18n`, add glossary entries for any new technical terms, page titles, or short nav labels that must stay in English or use a fixed translation (for example `Doctor` or `Polls`). +- `pnpm docs:check-i18n-glossary` enforces glossary coverage for changed English doc titles and short internal doc labels before translation reruns. - Translation memory: `docs/.i18n/zh-CN.tm.jsonl` (generated). - See `docs/.i18n/README.md`. - The pipeline can be slow/inefficient; if it’s dragging, ping @jospalmbier on Discord instead of hacking around it. @@ -96,13 +63,17 @@ - Prefer Bun for TypeScript execution (scripts, dev, tests): `bun ` / `bunx `. - Run CLI in dev: `pnpm openclaw ...` (bun) or `pnpm dev`. - Node remains supported for running built output (`dist/*`) and production installs. -- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch. Release checklist: `docs/platforms/mac/release.md`. +- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch. - Type-check/build: `pnpm build` - TypeScript checks: `pnpm tsgo` - Lint/format: `pnpm check` - Format check: `pnpm format` (oxfmt --check) - Format fix: `pnpm format:fix` (oxfmt --write) - Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage` +- Hard gate: before any commit, `pnpm check` MUST be run and MUST pass for the change being committed. +- Hard gate: before any push to `main`, `pnpm check` MUST be run and MUST pass, and `pnpm test` MUST be run and MUST pass. +- Hard gate: if the change can affect build output, packaging, lazy-loading/module boundaries, or published surfaces, `pnpm build` MUST be run and MUST pass before pushing `main`. +- Hard gate: do not commit or push with failing format, lint, type, build, or required test checks. ## Coding Style & Naming Conventions @@ -111,6 +82,9 @@ - Never add `@ts-nocheck` and do not disable `no-explicit-any`; fix root causes and update Oxlint/Oxfmt config only when required. - Dynamic import guardrail: do not mix `await import("x")` and static `import ... from "x"` for the same module in production code paths. If you need lazy loading, create a dedicated `*.runtime.ts` boundary (that re-exports from `x`) and dynamically import that boundary from lazy callers only. - Dynamic import verification: after refactors that touch lazy-loading/module boundaries, run `pnpm build` and check for `[INEFFECTIVE_DYNAMIC_IMPORT]` warnings before submitting. +- Extension SDK self-import guardrail: inside an extension package, do not import that same extension via `openclaw/plugin-sdk/` from production files. Route internal imports through a local barrel such as `./api.ts` or `./runtime-api.ts`, and keep the `plugin-sdk/` path as the external contract only. +- Extension package boundary guardrail: inside `extensions//**`, do not use relative imports/exports that resolve outside that same `extensions/` package root. If shared code belongs in the plugin SDK, import `openclaw/plugin-sdk/` instead of reaching into `src/plugin-sdk/**` or other repo paths via `../`. +- Extension API surface rule: `openclaw/plugin-sdk/` is the only public cross-package contract for extension-facing SDK code. If an extension needs a new seam, add a public subpath first; do not reach into `src/plugin-sdk/**` by relative path. - Never share class behavior via prototype mutation (`applyPrototypeMixins`, `Object.defineProperty` on `.prototype`, or exporting `Class.prototype` for merges). Use explicit inheritance/composition (`A extends B extends C`) or helper composition so TypeScript can typecheck. - If this pattern is needed, stop and get explicit approval before shipping; default behavior is to split/refactor into an explicit class hierarchy and keep members strongly typed. - In tests, prefer per-instance stubs over prototype mutation (`SomeClass.prototype.method = ...`) unless a test explicitly documents why prototype-level patching is required. @@ -118,23 +92,25 @@ - Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`. - Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability. - Naming: use **OpenClaw** for product/app/docs headings; use `openclaw` for CLI command, package/binary, paths, and config keys. +- Written English: use American spelling and grammar in code, comments, docs, and UI strings (e.g. "color" not "colour", "behavior" not "behaviour", "analyze" not "analyse"). -## Release Channels (Naming) +## Release / Advisory Workflows -- stable: tagged releases only (e.g. `vYYYY.M.D`), npm dist-tag `latest`. -- beta: prerelease tags `vYYYY.M.D-beta.N`, npm dist-tag `beta` (may ship without macOS app). -- beta naming: prefer `-beta.N`; do not mint new `-1/-2` betas. Legacy `vYYYY.M.D-` and `vYYYY.M.D.beta.N` remain recognized. -- dev: moving head on `main` (no tag; git checkout main). +- Use `$openclaw-release-maintainer` at `.agents/skills/openclaw-release-maintainer/SKILL.md` for release naming, version coordination, release auth, and changelog-backed release-note workflows. +- Use `$openclaw-ghsa-maintainer` at `.agents/skills/openclaw-ghsa-maintainer/SKILL.md` for GHSA advisory inspection, patch/publish flow, private-fork checks, and GHSA API validation. +- Release and publish remain explicit-approval actions even when using the skill. ## Testing Guidelines - Framework: Vitest with V8 coverage thresholds (70% lines/branches/functions/statements). - Naming: match source names with `*.test.ts`; e2e in `*.e2e.test.ts`. - Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic. +- Agents MUST NOT modify baseline, inventory, ignore, snapshot, or expected-failure files to silence failing checks without explicit approval in this chat. +- For targeted/local debugging, keep using the wrapper: `pnpm test -- [vitest args...]` (for example `pnpm test -- src/commands/onboard-search.test.ts -t "shows registered plugin providers"`); do not default to raw `pnpm vitest run ...` because it bypasses wrapper config/profile/pool routing. - Do not set test workers above 16; tried already. - If local Vitest runs cause memory pressure (common on non-Mac-Studio hosts), use `OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test` for land/gate runs. - Live tests (real keys): `CLAWDBOT_LIVE_TEST=1 pnpm test:live` (OpenClaw-only) or `LIVE=1 pnpm test:live` (includes provider live tests). Docker: `pnpm test:docker:live-models`, `pnpm test:docker:live-gateway`. Onboarding Docker E2E: `pnpm test:docker:onboard`. -- Full kit + what’s covered: `docs/testing.md`. +- Full kit + what’s covered: `docs/help/testing.md`. - Changelog: user-facing changes only; no internal/meta notes (version alignment, appcast reminders, release process). - Changelog placement: in the active version block, append new entries to the end of the target section (`### Changes` or `### Fixes`); do not insert new entries at the top of a section. - Changelog attribution: use at most one contributor mention per line; prefer `Thanks @author` and do not also add `by @author` on the same entry. @@ -143,7 +119,9 @@ ## Commit & Pull Request Guidelines -**Full maintainer PR workflow (optional):** If you want the repo's end-to-end maintainer workflow (triage order, quality bar, rebase rules, commit/changelog conventions, co-contributor policy, and the `review-pr` > `prepare-pr` > `merge-pr` pipeline), see `.agents/skills/PR_WORKFLOW.md`. Maintainers may use other workflows; when a maintainer specifies a workflow, follow that. If no workflow is specified, default to PR_WORKFLOW. +- Use `$openclaw-pr-maintainer` at `.agents/skills/openclaw-pr-maintainer/SKILL.md` for maintainer PR triage, review, close, search, and landing workflows. +- This includes auto-close labels, bug-fix evidence gates, GitHub comment/search footguns, and maintainer PR decision flow. +- For the repo's end-to-end maintainer PR workflow, use `$openclaw-pr-maintainer` at `.agents/skills/openclaw-pr-maintainer/SKILL.md`. - `/landpr` lives in the global Codex prompts (`~/.codex/prompts/landpr.md`); when landing or merging any PR, always follow that `/landpr` process. - Create commits with `scripts/committer "" `; avoid manual `git add`/`git commit` so staging stays scoped. @@ -152,62 +130,30 @@ - PR submission template (canonical): `.github/pull_request_template.md` - Issue submission templates (canonical): `.github/ISSUE_TEMPLATE/` -## Shorthand Commands - -- `sync`: if working tree is dirty, commit all changes (pick a sensible Conventional Commit message), then `git pull --rebase`; if rebase conflicts and cannot resolve, stop; otherwise `git push`. - ## Git Notes - If `git branch -d/-D ` is policy-blocked, delete the local ref directly: `git update-ref -d refs/heads/`. +- Agents MUST NOT create or push merge commits on `main`. If `main` has advanced, rebase local commits onto the latest `origin/main` before pushing. - Bulk PR close/reopen safety: if a close action would affect more than 5 PRs, first ask for explicit user confirmation with the exact PR count and target scope/query. -## GitHub Search (`gh`) - -- Prefer targeted keyword search before proposing new work or duplicating fixes. -- Use `--repo openclaw/openclaw` + `--match title,body` first; add `--match comments` when triaging follow-up threads. -- PRs: `gh search prs --repo openclaw/openclaw --match title,body --limit 50 -- "auto-update"` -- Issues: `gh search issues --repo openclaw/openclaw --match title,body --limit 50 -- "auto-update"` -- Structured output example: - `gh search issues --repo openclaw/openclaw --match title,body --limit 50 --json number,title,state,url,updatedAt -- "auto update" --jq '.[] | "\(.number) | \(.state) | \(.title) | \(.url)"'` - ## Security & Configuration Tips - Web provider stores creds at `~/.openclaw/credentials/`; rerun `openclaw login` if logged out. - Pi sessions live under `~/.openclaw/sessions/` by default; the base directory is not configurable. - Environment variables: see `~/.profile`. - Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples. -- Release flow: always read `docs/reference/RELEASING.md` and `docs/platforms/mac/release.md` before any release work; do not ask routine questions once those docs answer them. +- Release flow: use the private [maintainer release docs](https://github.com/openclaw/maintainers/blob/main/release/README.md) for the actual runbook, `docs/reference/RELEASING.md` for the public release policy, and `$openclaw-release-maintainer` for the maintainership workflow. -## GHSA (Repo Advisory) Patch/Publish - -- Before reviewing security advisories, read `SECURITY.md`. -- Fetch: `gh api /repos/openclaw/openclaw/security-advisories/` -- Latest npm: `npm view openclaw version --userconfig "$(mktemp)"` -- Private fork PRs must be closed: - `fork=$(gh api /repos/openclaw/openclaw/security-advisories/ | jq -r .private_fork.full_name)` - `gh pr list -R "$fork" --state open` (must be empty) -- Description newline footgun: write Markdown via heredoc to `/tmp/ghsa.desc.md` (no `"\\n"` strings) -- Build patch JSON via jq: `jq -n --rawfile desc /tmp/ghsa.desc.md '{summary,severity,description:$desc,vulnerabilities:[...]}' > /tmp/ghsa.patch.json` -- GHSA API footgun: cannot set `severity` and `cvss_vector_string` in the same PATCH; do separate calls. -- Patch + publish: `gh api -X PATCH /repos/openclaw/openclaw/security-advisories/ --input /tmp/ghsa.patch.json` (publish = include `"state":"published"`; no `/publish` endpoint) -- If publish fails (HTTP 422): missing `severity`/`description`/`vulnerabilities[]`, or private fork has open PRs -- Verify: re-fetch; ensure `state=published`, `published_at` set; `jq -r .description | rg '\\\\n'` returns nothing - -## Troubleshooting - -- Rebrand/migration issues or legacy config/service warnings: run `openclaw doctor` (see `docs/gateway/doctor.md`). - -## Agent-Specific Notes +## Local Runtime / Platform Notes - Vocabulary: "makeup" = "mac app". +- Rebrand/migration issues or legacy config/service warnings: run `openclaw doctor` (see `docs/gateway/doctor.md`). +- Use `$openclaw-parallels-smoke` at `.agents/skills/openclaw-parallels-smoke/SKILL.md` for Parallels smoke, rerun, upgrade, debug, and result-interpretation workflows across macOS, Windows, and Linux guests. +- For the macOS Discord roundtrip deep dive, use the narrower `.agents/skills/parallels-discord-roundtrip/SKILL.md` companion skill. - Never edit `node_modules` (global/Homebrew/npm/git installs too). Updates overwrite. Skill notes go in `tools.md` or `AGENTS.md`. +- If you need local-only `.agents` ignores, use `.git/info/exclude` instead of repo `.gitignore`. - When adding a new `AGENTS.md` anywhere in the repo, also add a `CLAUDE.md` symlink pointing to it (example: `ln -s AGENTS.md CLAUDE.md`). - Signal: "update fly" => `fly ssh console -a flawd-bot -C "bash -lc 'cd /data/clawd/openclaw && git pull --rebase origin main'"` then `fly machines restart e825232f34d058 -a flawd-bot`. -- When working on a GitHub Issue or PR, print the full URL at the end of the task. -- When answering questions, respond with high-confidence answers only: verify in code; do not guess. -- Never update the Carbon dependency. -- Any dependency with `pnpm.patchedDependencies` must use an exact version (no `^`/`~`). -- Patching dependencies (pnpm patches, overrides, or vendored changes) requires explicit approval; do not do this by default. - CLI progress: use `src/cli/progress.ts` (`osc-progress` + `@clack/prompts` spinner); don’t hand-roll spinners/bars. - Status output: keep tables + ANSI-safe wrapping (`src/terminal/table.ts`); `status --all` = read-only/pasteable, `status --deep` = probes. - Gateway currently runs only as the menubar app; there is no separate LaunchAgent/helper label installed. Restart via the OpenClaw Mac app or `scripts/restart-mac.sh`; to verify/kill use `launchctl print gui/$UID | grep openclaw` rather than assuming a fixed label. **When debugging on macOS, start/stop the gateway via the app, not ad-hoc tmux sessions; kill any temporary tunnels before handoff.** @@ -215,14 +161,27 @@ - If shared guardrails are available locally, review them; otherwise follow this repo's guidance. - SwiftUI state management (iOS/macOS): prefer the `Observation` framework (`@Observable`, `@Bindable`) over `ObservableObject`/`@StateObject`; don’t introduce new `ObservableObject` unless required for compatibility, and migrate existing usages when touching related code. - Connection providers: when adding a new connection, update every UI surface and docs (macOS app, web UI, mobile if applicable, onboarding/overview docs) and add matching status + configuration forms so provider lists and settings stay in sync. -- Version locations: `package.json` (CLI), `apps/android/app/build.gradle.kts` (versionName/versionCode), `apps/ios/Sources/Info.plist` + `apps/ios/Tests/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `apps/macos/Sources/OpenClaw/Resources/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `docs/install/updating.md` (pinned npm version), `docs/platforms/mac/release.md` (APP_VERSION/APP_BUILD examples), Peekaboo Xcode projects/Info.plists (MARKETING_VERSION/CURRENT_PROJECT_VERSION). +- Version locations: `package.json` (CLI), `apps/android/app/build.gradle.kts` (versionName/versionCode), `apps/ios/Sources/Info.plist` + `apps/ios/Tests/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `apps/macos/Sources/OpenClaw/Resources/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `docs/install/updating.md` (pinned npm version), and Peekaboo Xcode projects/Info.plists (MARKETING_VERSION/CURRENT_PROJECT_VERSION). - "Bump version everywhere" means all version locations above **except** `appcast.xml` (only touch appcast when cutting a new macOS Sparkle release). - **Restart apps:** “restart iOS/Android apps” means rebuild (recompile/install) and relaunch, not just kill/launch. - **Device checks:** before testing, verify connected real devices (iOS/Android) before reaching for simulators/emulators. - iOS Team ID lookup: `security find-identity -p codesigning -v` → use Apple Development (…) TEAMID. Fallback: `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`. - A2UI bundle hash: `src/canvas-host/a2ui/.bundle.hash` is auto-generated; ignore unexpected changes, and only regenerate via `pnpm canvas:a2ui:bundle` (or `scripts/bundle-a2ui.sh`) when needed. Commit the hash as a separate commit. -- Release signing/notary keys are managed outside the repo; follow internal release docs. -- Notary auth env vars (`APP_STORE_CONNECT_ISSUER_ID`, `APP_STORE_CONNECT_KEY_ID`, `APP_STORE_CONNECT_API_KEY_P8`) are expected in your environment (per internal release docs). +- Release signing/notary credentials are managed outside the repo; maintainers keep that setup in the private [maintainer release docs](https://github.com/openclaw/maintainers/tree/main/release). +- Lobster palette: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed. +- When asked to open a “session” file, open the Pi session logs under `~/.openclaw/agents//sessions/*.jsonl` (use the `agent=` value in the Runtime line of the system prompt; newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there. +- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac. +- Voice wake forwarding tips: + - Command template should stay `openclaw-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Don’t add extra quotes. + - launchd PATH is minimal; ensure the app’s launch agent PATH includes standard system paths plus your pnpm bin (typically `$HOME/Library/pnpm`) so `pnpm`/`openclaw` binaries resolve when invoked via `openclaw-mac`. + +## Collaboration / Safety Notes + +- When working on a GitHub Issue or PR, print the full URL at the end of the task. +- When answering questions, respond with high-confidence answers only: verify in code; do not guess. +- Never update the Carbon dependency. +- Any dependency with `pnpm.patchedDependencies` must use an exact version (no `^`/`~`). +- Patching dependencies (pnpm patches, overrides, or vendored changes) requires explicit approval; do not do this by default. - **Multi-agent safety:** do **not** create/apply/drop `git stash` entries unless explicitly requested (this includes `git pull --rebase --autostash`). Assume other agents may be working; keep unrelated WIP untouched and avoid cross-cutting state changes. - **Multi-agent safety:** when the user says "push", you may `git pull --rebase` to integrate latest changes (never discard other agents' work). When the user says "commit", scope to your changes only. When the user says "commit all", commit everything in grouped chunks. - **Multi-agent safety:** do **not** create/remove/modify `git worktree` checkouts (or edit `.worktrees/*`) unless explicitly requested. @@ -233,64 +192,12 @@ - If staged+unstaged diffs are formatting-only, auto-resolve without asking. - If commit/push already requested, auto-stage and include formatting-only follow-ups in the same commit (or a tiny follow-up commit if needed), no extra confirmation. - Only ask when changes are semantic (logic/data/behavior). -- Lobster seam: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed. - **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant. - Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause. - Code style: add brief comments for tricky logic; keep files under ~500 LOC when feasible (split/refactor as needed). - Tool schema guardrails (google-antigravity): avoid `Type.Union` in tool input schemas; no `anyOf`/`oneOf`/`allOf`. Use `stringEnum`/`optionalStringEnum` (Type.Unsafe enum) for string lists, and `Type.Optional(...)` instead of `... | null`. Keep top-level tool schema as `type: "object"` with `properties`. - Tool schema guardrails: avoid raw `format` property names in tool schemas; some validators treat `format` as a reserved keyword and reject the schema. -- When asked to open a “session” file, open the Pi session logs under `~/.openclaw/agents//sessions/*.jsonl` (use the `agent=` value in the Runtime line of the system prompt; newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there. -- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac. - Never send streaming/partial replies to external messaging surfaces (WhatsApp, Telegram); only final replies should be delivered there. Streaming/tool events may still go to internal UIs/control channel. -- Voice wake forwarding tips: - - Command template should stay `openclaw-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Don’t add extra quotes. - - launchd PATH is minimal; ensure the app’s launch agent PATH includes standard system paths plus your pnpm bin (typically `$HOME/Library/pnpm`) so `pnpm`/`openclaw` binaries resolve when invoked via `openclaw-mac`. - For manual `openclaw message send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tool’s escaping. - Release guardrails: do not change version numbers without operator’s explicit consent; always ask permission before running any npm publish/release step. - Beta release guardrail: when using a beta Git tag (for example `vYYYY.M.D-beta.N`), publish npm with a matching beta version suffix (for example `YYYY.M.D-beta.N`) rather than a plain version on `--tag beta`; otherwise the plain version name gets consumed/blocked. - -## NPM + 1Password (publish/verify) - -- Use the 1password skill; all `op` commands must run inside a fresh tmux session. -- Correct 1Password path for npm release auth: `op://Private/Npmjs` (use that item; OTP stays `op://Private/Npmjs/one-time password?attribute=otp`). -- Sign in: `eval "$(op signin --account my.1password.com)"` (app unlocked + integration on). -- OTP: `op read 'op://Private/Npmjs/one-time password?attribute=otp'`. -- Publish: `npm publish --access public --otp=""` (run from the package dir). -- Verify without local npmrc side effects: `npm view version --userconfig "$(mktemp)"`. -- Kill the tmux session after publish. - -## Plugin Release Fast Path (no core `openclaw` publish) - -- Release only already-on-npm plugins. Source list is in `docs/reference/RELEASING.md` under "Current npm plugin list". -- Run all CLI `op` calls and `npm publish` inside tmux to avoid hangs/interruption: - - `tmux new -d -s release-plugins-$(date +%Y%m%d-%H%M%S)` - - `eval "$(op signin --account my.1password.com)"` -- 1Password helpers: - - password used by `npm login`: - `op item get Npmjs --format=json | jq -r '.fields[] | select(.id=="password").value'` - - OTP: - `op read 'op://Private/Npmjs/one-time password?attribute=otp'` -- Fast publish loop (local helper script in `/tmp` is fine; keep repo clean): - - compare local plugin `version` to `npm view version` - - only run `npm publish --access public --otp=""` when versions differ - - skip if package is missing on npm or version already matches. -- Keep `openclaw` untouched: never run publish from repo root unless explicitly requested. -- Post-check for each release: - - per-plugin: `npm view @openclaw/ version --userconfig "$(mktemp)"` should be `2026.2.17` - - core guard: `npm view openclaw version --userconfig "$(mktemp)"` should stay at previous version unless explicitly requested. - -## Changelog Release Notes - -- When cutting a mac release with beta GitHub prerelease: - - Tag `vYYYY.M.D-beta.N` from the release commit (example: `v2026.2.15-beta.1`). - - Create prerelease with title `openclaw YYYY.M.D-beta.N`. - - Use release notes from `CHANGELOG.md` version section (`Changes` + `Fixes`, no title duplicate). - - Attach at least `OpenClaw-YYYY.M.D.zip` and `OpenClaw-YYYY.M.D.dSYM.zip`; include `.dmg` if available. - -- Keep top version entries in `CHANGELOG.md` sorted by impact: - - `### Changes` first. - - `### Fixes` deduped and ranked with user-facing fixes first. -- Before tagging/publishing, run: - - `node --import tsx scripts/release-check.ts` - - `pnpm release:check` - - `pnpm test:install:smoke` or `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke` for non-root smoke path. diff --git a/CHANGELOG.md b/CHANGELOG.md index aba53517973..c5a376f35bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,93 +6,496 @@ Docs: https://docs.openclaw.ai ### Changes -- Gateway/node pending work: add narrow in-memory pending-work queue primitives (`node.pending.enqueue` / `node.pending.drain`) and wake-helper reuse as a foundation for dormant-node work delivery. (#41409) Thanks @mbelinky. -- Git/runtime state: ignore the gateway-generated `.dev-state` file so local runtime state does not show up as untracked repo noise. (#41848) Thanks @smysle. -- ACP/sessions_spawn: add optional `resumeSessionId` for `runtime: "acp"` so spawned ACP sessions can resume an existing ACPX/Codex conversation instead of always starting fresh. (#41847) Thanks @pejmanjohn. -- Exec/child commands: mark child command environments with `OPENCLAW_CLI` so subprocesses can detect when they were launched from the OpenClaw CLI. (#41411) Thanks @vincentkoc. -- iOS/Home canvas: add a bundled welcome screen with a live agent overview that refreshes on connect, reconnect, and foreground return, and move the compact connection pill off the top-left canvas overlay. (#42456) Thanks @ngutman. -- iOS/Home canvas: replace floating controls with a docked toolbar, make the bundled home scaffold adapt to smaller phones, and open chat in the resolved main session instead of a synthetic `ios` session. (#42456) Thanks @ngutman. -- Discord/auto threads: add `autoArchiveDuration` channel config for auto-created threads so Discord thread archiving can stay at 1 hour, 1 day, 3 days, or 1 week instead of always using the 1-hour default. (#35065) Thanks @davidguttman. -- OpenCode/onboarding: add new OpenCode Go provider, treat Zen and Go as one OpenCode setup in the wizard/docs while keeping the runtime providers split, store one shared OpenCode key for both profiles, and stop overriding the built-in `opencode-go` catalog routing. (#42313) Thanks @ImLukeF and @vincentkoc. -- macOS/chat UI: add a chat model picker, persist explicit thinking-level selections across relaunch, and harden provider-aware session model sync for the shared chat composer. (#42314) Thanks @ImLukeF. - -### Breaking - -- Cron/doctor: tighten isolated cron delivery so cron jobs can no longer notify through ad hoc agent sends or fallback main-session summaries, and add `openclaw doctor --fix` migration for legacy cron storage and legacy notify/webhook delivery metadata. (#40998) Thanks @mbelinky. +- Commands/btw: add `/btw` side questions for quick tool-less answers about the current session without changing future session context, with dismissible in-session TUI answers and explicit BTW replies on external channels. (#45444) Thanks @ngutman. +- Gateway/docs: clarify that empty URL input allowlists are treated as unset, document `allowUrl: false` as the deny-all switch, and add regression coverage for the normalization path. +- Sandbox/runtime: add pluggable sandbox backends, ship an OpenShell backend with `mirror` and `remote` workspace modes, and make sandbox list/recreate/prune backend-aware instead of Docker-only. +- Sandbox/SSH: add a core SSH sandbox backend with secret-backed key, certificate, and known_hosts inputs, move shared remote exec/filesystem tooling into core, and keep OpenShell focused on sandbox lifecycle plus optional `mirror` mode. +- Web tools/Firecrawl: add Firecrawl as an `onboard`/configure search provider via a bundled plugin, expose explicit `firecrawl_search` and `firecrawl_scrape` tools, and align core `web_fetch` fallback behavior with Firecrawl base-URL/env fallback plus guarded endpoint fetches. +- Plugins/bundles: add compatible Codex, Claude, and Cursor bundle discovery/install support, map bundle skills into OpenClaw skills, and apply Claude bundle `settings.json` defaults to embedded Pi with shell overrides sanitized. +- Plugins/providers: move OpenRouter, GitHub Copilot, and OpenAI Codex provider/runtime logic into bundled plugins, including dynamic model fallback, runtime auth exchange, stream wrappers, capability hints, and cache-TTL policy. +- Plugins/agent integrations: broaden the plugin surface for app-server integrations with channel-aware commands, interactive callbacks, inbound claims, and Discord/Telegram conversation binding support. (#45318) Thanks @huntharo and @vincentkoc. +- Install/update: allow package-manager installs from GitHub `main` via `openclaw update --tag main`, installer `--version main`, or direct npm/pnpm git specs. (#47630) Thanks @vincentkoc. +- Gateway/health monitor: add configurable stale-event thresholds and restart limits, plus per-channel and per-account `healthMonitor.enabled` overrides, while keeping the existing global disable path on `gateway.channelHealthCheckMinutes=0`. (#42107) Thanks @rstar327. +- Android/mobile: add a system-aware dark theme across onboarding and post-onboarding screens so the app follows the device theme through setup, chat, and voice flows. (#46249) Thanks @sibbl. +- Feishu/ACP: add current-conversation ACP and subagent session binding for supported DMs and topic conversations, including completion delivery back to the originating Feishu conversation. (#46819) Thanks @Takhoffman. +- Plugins/marketplaces: add Claude marketplace registry resolution, `plugin@marketplace` installs, marketplace listing, and update support, plus Docker E2E coverage for local and official marketplace flows. (#48058) Thanks @vincentkoc. +- Commands/plugins: add owner-gated `/plugins` and `/plugin` chat commands for plugin list/show and enable/disable flows, alongside explicit `commands.plugins` config gating. Thanks @vincentkoc. +- Feishu/cards: add structured interactive approval and quick-action launcher cards, preserve callback user and conversation context through routing, and keep legacy card-action fallback behavior so common actions can run without typing raw commands. (#47873) Thanks @Takhoffman. +- Feishu/streaming: add `onReasoningStream` and `onReasoningEnd` support to streaming cards, so `/reasoning stream` renders thinking tokens as markdown blockquotes in the same card — matching the Telegram channel's reasoning lane behavior. (#46029) Thanks @day253. +- Feishu/cards: add identity-aware structured card headers and note footers for Feishu replies and direct sends, while keeping that presentation wired through the shared outbound identity path. (#29938) Thanks @nszhsl. +- Android/nodes: add `callLog.search` plus shared Call Log permission wiring so Android nodes can search recent call history through the gateway. (#44073) Thanks @lixuankai. +- Android/nodes: add `sms.search` plus shared SMS permission wiring so Android nodes can search device text messages through the gateway. (#48299) Thanks @lixuankai. +- Plugins/MiniMax: merge the bundled MiniMax API and MiniMax OAuth plugin surfaces into a single default-on `minimax` plugin, while keeping legacy `minimax-portal-auth` config ids aliased for compatibility. +- Telegram/actions: add `topic-edit` for forum-topic renames and icon updates while sharing the same Telegram topic-edit transport used by the plugin runtime. (#47798) Thanks @obviyus. +- Telegram/error replies: add a default-off `channels.telegram.silentErrorReplies` setting so bot error replies can be delivered silently across regular replies, native commands, and fallback sends. (#19776) Thanks @ImLukeF. +- Refactor/channels: remove the legacy channel shim directories and point channel-specific imports directly at the extension-owned implementations. (#45967) Thanks @scoootscooob. +- Docs/Zalo: clarify the Marketplace-bot support matrix and config guidance so the Zalo channel docs match current Bot Creator behavior more closely. (#47552) Thanks @No898. +- secrets: harden read-only SecretRef command paths and diagnostics. (#47794) Thanks @joshavant. +- Browser/existing-session: support `browser.profiles..userDataDir` so Chrome DevTools MCP can attach to Brave, Edge, and other Chromium-based browsers through their own user data directories. (#48170) Thanks @velvet-shark. +- Skills/prompt budget: preserve all registered skills via a compact catalog fallback before dropping entries when the full prompt format exceeds `maxSkillsPromptChars`. (#47553) Thanks @snese. +- Models/OpenAI: add native forward-compat support for `gpt-5.4-mini` and `gpt-5.4-nano` in the OpenAI provider catalog, runtime resolution, and reasoning capability gates. Thanks @vincentkoc. +- Plugins/bundles: make enabled bundle MCP servers expose runnable tools in embedded Pi, and default relative bundle MCP launches to the bundle root so marketplace bundles like Context7 work through Pi instead of stopping at config import. +- Scope message SecretRef resolution and harden doctor/status paths. (#48728) Thanks @joshavant. +- Plugins/testing: add a public `openclaw/plugin-sdk/testing` surface for plugin-author test helpers, and move bundled-extension-only test bridges out of `extensions/` into private repo test helpers. +- Plugins/Chutes: add a bundled Chutes provider with plugin-owned OAuth/API-key auth, dynamic model discovery, and default-on extension wiring. (#41416) Thanks @Veightor. +- Plugins/binding: add `onConversationBindingResolved(...)` so plugins can react immediately after bind approvals or denies without blocking channel interaction acknowledgements. (#48678) Thanks @huntharo. +- CLI/config: expand `config set` with SecretRef and provider builder modes, JSON/batch assignment support, and `--dry-run` validation with structured JSON output. (#49296) Thanks @joshavant. +- Control UI/appearance: unify theme border radii across Claw, Knot, and Dash, and add a Roundness slider to the Appearance settings so users can adjust corner radius from sharp to fully rounded. Thanks @BunsDev. +- Control UI/chat: add an expand-to-canvas button on assistant chat bubbles and in-app session navigation from Sessions and Cron views. Thanks @BunsDev. +- Plugins/context engines: expose `delegateCompactionToRuntime(...)` on the public plugin SDK, refactor the legacy engine to use the shared helper, and clarify `ownsCompaction` delegation semantics for non-owning engines. (#49061) Thanks @jalehman. +- Plugins/MiniMax: add MiniMax-M2.7 and MiniMax-M2.7-highspeed models and update the default model from M2.5 to M2.7. (#49691) Thanks @liyuan97. ### Fixes +- CLI/Ollama onboarding: keep the interactive model picker for explicit `openclaw onboard --auth-choice ollama` runs so setup still selects a default model without reintroducing pre-picker auto-pulls. (#49249) Thanks @BruceMacD. +- Plugins/bundler TDZ: fix `RESERVED_COMMANDS` temporal dead zone error that prevented device-pair, phone-control, and talk-voice plugins from registering when the bundler placed the commands module after call sites in the same output chunk. Thanks @BunsDev. +- Plugins/imports: fix stale googlechat runtime-api import paths and signal SDK circular re-exports broken by recent plugin-sdk refactors. Thanks @BunsDev. +- Google auth/Node 25: patch `gaxios` to use native fetch without injecting `globalThis.window`, while translating proxy and mTLS transport settings so Google Vertex and Google Chat auth keep working on Node 25. (#47914) Thanks @pdd-cli. +- Gateway/startup: load bundled channel plugins from compiled `dist/extensions` entries in built installs, so gateway boot no longer recompiles bundled extension TypeScript on every startup and WhatsApp-class cold starts drop back to seconds instead of tens of seconds or worse. (#47560) Thanks @ngutman. +- Agents/openai-responses: strip `prompt_cache_key` and `prompt_cache_retention` for non-OpenAI-compatible Responses endpoints while keeping them on direct OpenAI and Azure OpenAI paths, so third-party OpenAI-compatible providers no longer reject those requests with HTTP 400. (#49877) Thanks @ShaunTsai. +- Plugins/context engines: enforce owner-aware context-engine registration on both loader and public SDK paths so plugins cannot spoof privileged ownership, claim the core `legacy` engine id, or overwrite an existing engine id through direct SDK imports. (#47595) Thanks @vincentkoc. +- Browser/remote CDP: honor strict browser SSRF policy during remote CDP reachability and `/json/version` discovery checks, redact sensitive `cdpUrl` tokens from status output, and warn when remote CDP targets private/internal hosts. +- Gateway/plugins: pin runtime webhook routes to the gateway startup registry so channel webhooks keep working across plugin-registry churn, and make plugin auth + dispatch resolve routes from the same live HTTP-route registry. (#47902) Fixes #46924 and #47041. Thanks @steipete. +- Gateway/auth: ignore spoofed loopback hops in trusted forwarding chains and block device approvals that request scopes above the caller session. (#46800) Thanks @vincentkoc. +- Gateway/restart: defer externally signaled unmanaged restarts through the in-process idle drain, and preserve the restored subagent run as remap fallback during orphan recovery so resumed sessions do not duplicate work. (#47719) Thanks @joeykrug. +- Control UI/session routing: preserve established external delivery routes when webchat views or sends in externally originated sessions, so subagent completions still return to the original channel instead of the dashboard. (#47797) Thanks @brokemac79. +- Configure/startup: move outbound send-deps resolution into a lightweight helper so `openclaw configure` no longer stalls after the banner while eagerly loading channel plugins. (#46301) Thanks @scoootscooob. +- CLI/startup: lazy-load channel add and root help startup paths to trim avoidable RSS and help latency on constrained hosts. (#46784) Thanks @vincentkoc. +- CLI/onboarding: import static provider definitions directly for onboarding model/config helpers so those paths no longer pull provider discovery just for built-in defaults. (#47467) Thanks @vincentkoc. +- CLI/auth choice: lazy-load plugin/provider fallback resolution so mapped auth choices stay on the static path and only unknown choices pay the heavy provider load. (#47495) Thanks @vincentkoc. +- CLI: avoid loading provider discovery during startup model normalization. (#46522) Thanks @ItsAditya-xyz and @vincentkoc. +- Security/device pairing: harden `device.token.rotate` deny handling by keeping public failures generic while logging internal deny reasons and preserving approved-baseline enforcement. (`GHSA-7jrw-x62h-64p8`) +- Inbound policy hardening: tighten callback and webhook sender checks across Mattermost and Google Chat, match Nextcloud Talk rooms by stable room token, and treat explicit empty Twitch allowlists as deny-all. (#46787) Thanks @zpbrent, @ijxpwastaken and @vincentkoc. +- Webhooks/runtime: move auth earlier and tighten pre-auth body limits and timeouts across bundled webhook handlers, including slow-body handling for Mattermost slash commands. (#46802) Thanks @vincentkoc. +- Email/webhook wrapping: sanitize sender and subject metadata before external-content wrapping so metadata fields cannot break the wrapper structure. (#46816) Thanks @vincentkoc. +- Tools/apply-patch: revalidate workspace-only delete and directory targets immediately before mutating host paths. (#46803) Thanks @vincentkoc. +- Gateway/config views: strip embedded credentials from URL-based endpoint fields before returning read-only account and config snapshots. (#46799) Thanks @vincentkoc. +- ACP/approvals: use canonical tool identity for prompting decisions and fail closed when conflicting tool identity hints are present. (#46817) Thanks @zpbrent and @vincentkoc. +- ACP: require admin scope for mutating internal actions. (#46789) Thanks @tdjackey and @vincentkoc. +- Subagents/follow-ups: require the same controller ownership checks for `/subagents send` as other control actions, so leaf sessions cannot message nested child runs they do not control. (#46801) Thanks @vincentkoc. +- macOS/canvas actions: keep unattended local agent actions on trusted in-app canvas surfaces only, and stop exposing the deep-link fallback key to arbitrary page scripts. (#46790) Thanks @vincentkoc. +- Agents/compaction: extend the enclosing run deadline once while compaction is actively in flight, and abort the underlying SDK compaction on timeout/cancel so large-session compactions stop freezing mid-run. (#46889) Thanks @asyncjason. +- Agents/openai-compatible tool calls: deduplicate repeated tool call ids across live assistant messages and replayed history so OpenAI-compatible backends no longer reject duplicate `tool_call_id` values with HTTP 400. (#40996) Thanks @xaeon2026. +- Models/openai-completions: default non-native OpenAI-compatible providers to omit tool-definition `strict` fields unless users explicitly opt back in, so tool calling keeps working on providers that reject that option. (#45497) Thanks @sahancava. +- Models/OpenRouter runtime capabilities: fetch uncatalogued OpenRouter model metadata on first use so newly added vision models keep image input instead of silently degrading to text-only, with top-level capability field fallbacks for `/api/v1/models`. (#45824) Thanks @DJjjjhao. +- Channels/plugins: keep shared interactive payloads merge-ready by fixing Slack custom callback routing and repeat-click dedupe, allowing interactive-only sends, and preserving ordered Discord shared text blocks. (#47715) Thanks @vincentkoc. +- Slack/interactive replies: preserve `channelData.slack.blocks` through live DM delivery and preview-finalized edits so Block Kit button and select directives render instead of falling back to raw text. (#45890) Thanks @vincentkoc. +- Feishu/actions: expand the runtime action surface with message read/edit, explicit thread replies, pinning, and operator-facing chat/member inspection so Feishu can operate more of the workspace directly. (#47968) Thanks @Takhoffman. +- Feishu/topic threads: fetch full thread context, including prior bot replies, when starting a topic-thread session so follow-up turns in Feishu topics keep the right conversation state. (#45254) Thanks @Coobiw. +- Feishu/media: keep native image, file, audio, and video/media handling aligned across outbound sends, inbound downloads, thread replies, directory/action aliases, and capability docs so unsupported areas are explicit instead of implied. (#47968) Thanks @Takhoffman. +- Feishu/webhooks: harden signed webhook verification to use constant-time signature comparison and keep malformed short signatures fail-closed in webhook E2E coverage. +- WhatsApp/reconnect: restore the append recency filter in the extension inbox monitor and handle protobuf `Long` timestamps correctly, so fresh post-reconnect append messages are processed while stale history sync stays suppressed. (#42588) Thanks @MonkeyLeeT. +- WhatsApp/login: wait for pending creds writes before reopening after Baileys `515` pairing restarts in both QR login and `channels login` flows, and keep the restart coverage pinned to the real wrapped error shape plus per-account creds queues. (#27910) Thanks @asyncjason. +- Telegram/message send: forward `--force-document` through the `sendPayload` path as well as `sendMedia`, so Telegram payload sends with `channelData` keep uploading images as documents instead of silently falling back to compressed photo sends. (#47119) Thanks @thepagent. +- Telegram/message chunking: preserve spaces, paragraph separators, and word boundaries when HTML overflow rechunking splits formatted replies. (#47274) Thanks @obviyus. +- Z.AI/onboarding: detect a working default model even for explicit `zai-coding-*` endpoint choices, so Coding Plan setup can keep the selected endpoint while defaulting to `glm-5` when available or `glm-4.7` as fallback. (#45969) Thanks @obviyus. +- 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. +- 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. +- Plugins/install precedence: keep bundled plugins ahead of auto-discovered globals by default, but let an explicitly installed plugin record win its own duplicate-id tie so installed channel plugins load from `~/.openclaw/extensions` after `openclaw plugins install`. (#46722) Thanks @Takhoffman. +- Control UI/logging: make browser-safe logger imports avoid eager temp-dir resolution so the bundled Control UI no longer crashes to a blank screen when logging reaches `tmp-openclaw-dir`. (#48469) Fixes #48062. Thanks @7inspire. +- Plugins/scoped ids: preserve scoped plugin ids during install and config keying, and keep bundled plugins ahead of discovered duplicate ids by default so `@scope/name` plugins no longer collide with unscoped installs. (#47413) Thanks @vincentkoc. +- 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 `dist/extensions/*/openclaw.plugin.json` manifests after a rebuild. Thanks @gumadeiras. +- Control UI/chat sessions: show human-readable labels in the grouped session dropdown again, keep unique scoped fallbacks when metadata is missing, and disambiguate duplicate labels only when needed. (#45130) Thanks @luzhidong. +- Control UI: scope persisted session selection per gateway, prevent stale session bleed across tokenized gateway opens, and cap stored gateway session history. (#47453) Thanks @sallyom. +- Control UI/dashboard: preserve structured gateway shutdown reasons across restart disconnects so config-triggered restarts no longer fall back to `disconnected (1006): no reason`. (#46580) Fixes #46532. Thanks @vincentkoc. +- Android/chat: theme the thinking dropdown and TLS trust dialogs explicitly so popup surfaces match the active app theme instead of falling back to mismatched Material defaults. +- Group mention gating: reject invalid and unsafe nested-repetition `mentionPatterns`, reuse the shared safe config-regex compiler across mention stripping and detection, and cache strip-time regex compilation so noisy groups avoid repeated recompiles. +- Browser/profiles: drop the auto-created `chrome-relay` browser profile; users who need the Chrome extension relay must now create their own profile via `openclaw browser create-profile`. (#46596) Fixes #45777. Thanks @odysseus0. +- CI/channel test routing: move the built-in channel suites into `test:channels` and keep them out of `test:extensions`, so extension CI no longer fails after the channel migration while targeted test routing still sends Slack, Signal, and iMessage suites to the right lane. (#46066) Thanks @scoootscooob. +- Docs/Mintlify: fix MDX marker syntax on Perplexity, Model Providers, Moonshot, and exec approvals pages so local docs preview no longer breaks rendering or leaves stale pages unpublished. (#46695) Thanks @velvet-shark. +- Gateway/config validation: stop treating the implicit default memory slot as a required explicit plugin config, so startup no longer fails with `plugins.slots.memory: plugin not found: memory-core` when `memory-core` was only inferred. (#47494) Thanks @ngutman. +- Tlon: honor explicit empty allowlists and defer cite expansion. (#46788) Thanks @zpbrent and @vincentkoc. +- Tlon/DM auth: defer cited-message expansion until after DM authorization and owner command handling, so unauthorized DMs and owner approval/admin commands no longer trigger cross-channel cite fetches before the deny or command path. +- Docs/security audit: spell out that `gateway.controlUi.allowedOrigins: ["*"]` is an explicit allow-all browser-origin policy and should be avoided outside tightly controlled local testing. +- Gateway/auth: clear self-declared scopes for device-less trusted-proxy Control UI sessions so proxy-authenticated connects cannot claim admin or secrets scopes without a bound device identity. +- Nodes/pending actions: re-check queued foreground actions against the current node command policy before returning them to the node. (#46815) Thanks @zpbrent and @vincentkoc. +- Node/startup: remove leftover debug `console.log("node host PATH: ...")` that printed the resolved PATH on every `openclaw node run` invocation. (#46515) Fixes #46411. Thanks @ademczuk. +- CLI/completion: reduce recursive completion-script string churn and fix nested PowerShell command-path matching so generated nested completions resolve on PowerShell too. (#45537) Thanks @yiShanXin and @vincentkoc. +- Slack/startup: harden `@slack/bolt` import interop across current bundled runtime shapes so Slack monitors no longer crash with `App is not a constructor` after plugin-sdk bundling changes. (#45953) Thanks @merc1305. +- Windows/gateway status: accept `schtasks` `Last Result` output as an alias for `Last Run Result`, so running scheduled-task installs no longer show `Runtime: unknown`. (#47844) Thanks @MoerAI. +- ACP/acpx: resolve the bundled plugin root from the actual plugin directory so plugin-local installs stay under `dist/extensions/acpx` instead of escaping to `dist/extensions` and failing runtime setup. (#47601) Thanks @ngutman. +- Gateway/websocket pairing bypass for disabled auth: skip device-pairing enforcement for Control UI operator sessions when `gateway.auth.mode=none`, so reverse-proxied dashboards no longer get stuck on `pairing required` despite auth being explicitly disabled. (#47148) Thanks @ademczuk. +- Control UI/model switching: preserve the selected provider prefix when switching models from the chat dropdown, so multi-provider setups no longer send `anthropic/gpt-5.2`-style mismatches when the user picked `openai/gpt-5.2`. (#47581) Thanks @chrishham. +- Control UI/storage: scope persisted settings keys by gateway base path, with migration from the legacy shared key, so multiple gateways under one domain stop overwriting each other's dashboard preferences. (#47932) Thanks @bobBot-claw. +- Agents/usage tracking: stop forcing `supportsUsageInStreaming: false` on non-native OpenAI-completions providers so compatible backends report token usage and cost again instead of showing all zeros. (#46500) Fixes #46142. Thanks @ademczuk. +- ACP/acpx: keep plugin-local backend installs under `extensions/acpx` in live repo checkouts so rebuilds no longer delete the runtime binary, and avoid package-lock churn during runtime repair. +- Plugins/subagents: preserve gateway-owned plugin subagent access across runtime, tool, and embedded-runner load paths so gateway plugin tools and context engines can still spawn and manage subagents after the loader cache split. (#46648) Thanks @jalehman. +- Control UI/overview: keep the language dropdown aligned with the persisted locale during dashboard startup so refreshing the page does not fall back to English before locale hydration completes. (#48019) Thanks @git-jxj. +- Agents/compaction: rerun transcript repair after `session.compact()` so orphaned `tool_result` blocks cannot survive compaction and break later Anthropic requests. (#16095) thanks @claw-sylphx. +- Agents/compaction: trigger overflow recovery from the tool-result guard once post-compaction context still exceeds the safe threshold, so long tool loops compact before the next model call hard-fails. (#29371) thanks @keshav55. +- macOS/exec approvals: harden exec-host request HMAC verification to use a timing-safe compare and keep malformed or truncated signatures fail-closed in focused IPC auth coverage. +- Gateway/exec approvals: surface requested env override keys in gateway-host approval prompts so operators can review surviving env context without inheriting noisy base host env. +- Telegram/network: preserve sticky IPv4 fallback state across polling restarts so hosts with unstable IPv6 to `api.telegram.org` stop re-triggering repeated Telegram timeouts after each restart. (#48282) Thanks @yassinebkr. +- Plugins/subagents: forward per-run provider and model overrides through gateway plugin subagent dispatch so plugin-launched agent delegations honor explicit model selection again. (#48277) Thanks @jalehman. +- Agents/compaction: write minimal boundary summaries for empty preparations while keeping split-turn prefixes on the normal path, so no-summarizable-message sessions stop retriggering the safeguard loop. (#42215) thanks @lml2468. +- Models/chat commands: keep `/model ...@YYYYMMDD` version suffixes intact by default, but still honor matching stored numeric auth-profile overrides for the same provider. (#48896) Thanks @Alix-007. +- Gateway/channels: serialize per-account channel startup so overlapping starts do not boot the same provider twice, preventing MS Teams `EADDRINUSE` crash loops during startup and restart. (#49583) Thanks @sudie-codes. +- Tests/OpenAI Codex auth: align login expectations with the default `gpt-5.4` model so CI coverage stays consistent with the current OpenAI Codex default. (#44367) Thanks @jrrcdev. +- Discord: enforce strict DM component allowlist auth (#49997) Thanks @joshavant. +- Stabilize plugin loader and Docker extension smoke (#50058) Thanks @joshavant. +- Telegram: stabilize pairing/session/forum routing and reply formatting tests (#50155) Thanks @joshavant. + +### Fixes + +- Agents/bootstrap warnings: move bootstrap truncation warnings out of the system prompt and into the per-turn prompt body so prompt-cache reuse stays stable when truncation warnings appear or disappear. (#48753) Thanks @scoootscooob and @obviyus. +- Telegram/DM topic session keys: route named-account DM topics through the same per-account base session key across inbound messages, native commands, and session-state lookups so `/status` and thread recovery stop creating phantom `agent:main:main:thread:...` sessions. (#48204) Thanks @vincentkoc. +- macOS/node service startup: use `openclaw node start/stop --json` from the Mac app instead of the removed `openclaw service node ...` command shape, so current CLI installs expose the full node exec surface again. (#46843) Fixes #43171. Thanks @Br1an67. +- macOS/launch at login: stop emitting `KeepAlive` for the desktop app launch agent so OpenClaw no longer relaunches immediately after a manual quit while launch at login remains enabled. (#40213) Thanks @stablegenius49. +- ACP/gateway startup: use direct Telegram and Discord startup/status helpers instead of routing probes through the plugin runtime, and prepend the selected daemon Node bin dir to service PATH so plugin-local installs can still find `npm` and `pnpm`. +- ACP/configured bindings: reinitialize configured ACP sessions that are stuck in `error` state instead of reusing the failed runtime. +- Mattermost/DM send: retry transient direct-channel creation failures for DM deliveries, with configurable backoff and per-request timeout. (#42398) Thanks @JonathanJing. +- Telegram/network: unify API and media fetches under the same sticky IPv4 and pinned-IP fallback chain, and re-validate pinned override addresses against SSRF policy. (#49148) Thanks @obviyus. +- Agents/prompt composition: append bootstrap truncation warnings to the current-turn prompt and add regression coverage for stable system-prompt cache invariants. (#49237) Thanks @scoootscooob. +- Gateway/auth: add regression coverage that keeps device-less trusted-proxy Control UI sessions off privileged pairing approval RPCs. Thanks @vincentkoc. +- Plugins/runtime-api: pin extension runtime-api export surfaces with explicit guardrail coverage so future surface creep becomes a deliberate diff. Thanks @vincentkoc. +- Telegram/security: add regression coverage proving pinned fallback host overrides stay bound to Telegram and delegate non-matching hostnames back to the original lookup path. Thanks @vincentkoc. +- Secrets/exec refs: require explicit `--allow-exec` for `secrets apply` write plans that contain exec SecretRefs/providers, and align audit/configure/apply dry-run behavior to skip exec checks unless opted in to prevent unexpected command side effects. (#49417) Thanks @restriction and @joshavant. +- Tools/image generation: add bundled fal image generation support so `image_generate` can target `fal/*` models with `FAL_KEY`, including single-image edit flows via FLUX image-to-image. Thanks @vincentkoc. +- xAI/web search: add missing Grok credential metadata so the bundled provider registration type-checks again. (#49472) thanks @scoootscooob. +- Signal/runtime API: re-export `SignalAccountConfig` so Signal account resolution type-checks again. (#49470) Thanks @scoootscooob. +- Google Chat/runtime API: thin the private runtime barrel onto the curated public SDK surface while keeping public Google Chat exports intact. (#49504) Thanks @scoootscooob. +- WhatsApp: stabilize inbound monitor and setup tests (#50007) Thanks @joshavant. +- Matrix: make onboarding status runtime-safe (#49995) Thanks @joshavant. +- Channels: stabilize lane harness and monitor tests (#50167) Thanks @joshavant. +- WhatsApp/active-listener: pin the active listener registry to a `globalThis` singleton so split WhatsApp bundle chunks share one listener map and outbound sends stop missing the registered session. (#47433) Thanks @clawdia67. + +### Breaking + +- Skills/image generation: remove the bundled `nano-banana-pro` skill wrapper. Use `agents.defaults.imageGenerationModel.primary: "google/gemini-3-pro-image-preview"` for the native Nano Banana-style path instead. + +- Browser/Chrome MCP: remove the legacy Chrome extension relay path, bundled extension assets, `driver: "extension"`, and `browser.relayBindHost`. Run `openclaw doctor --fix` to migrate host-local browser config to `existing-session` / `user`; Docker, headless, sandbox, and remote browser flows still use raw CDP. (#47893) Thanks @vincentkoc. +- Plugins/runtime: remove the public `openclaw/extension-api` surface with no compatibility shim. Bundled plugins must use injected runtime for host-side operations (for example `api.runtime.agent.runEmbeddedPiAgent`) and any remaining direct imports must come from narrow `openclaw/plugin-sdk/*` subpaths instead of the monolithic SDK root. +- Tools/image generation: standardize the stock image create/edit path on the core `image_generate` tool. The old `nano-banana-pro` docs/examples are gone; if you previously copied that sample-skill config, switch to `agents.defaults.imageGenerationModel` for built-in image generation or install a separate third-party skill explicitly. +- Skills/image generation: remove the bundled `nano-banana-pro` skill wrapper. Use `agents.defaults.imageGenerationModel.primary: "google/gemini-3-pro-image-preview"` for the native Nano Banana-style path instead. +- Plugins/message discovery: require `ChannelMessageActionAdapter.describeMessageTool(...)` for shared `message` tool discovery. The legacy `listActions`, `getCapabilities`, and `getToolSchema` adapter methods are removed. Plugin authors should migrate message discovery to `describeMessageTool(...)` and keep channel-specific action runtime code inside the owning plugin package. Thanks @gumadeiras. +- Exec/env sandbox: block build-tool JVM injection (`MAVEN_OPTS`, `SBT_OPTS`, `GRADLE_OPTS`, `ANT_OPTS`), glibc tunable exploitation (`GLIBC_TUNABLES`), and .NET dependency resolution hijack (`DOTNET_ADDITIONAL_DEPS`) from the host exec environment, and restrict Gradle init script redirect (`GRADLE_USER_HOME`) as an override-only block so user-configured Gradle homes still propagate. (#49702) +- Plugins/Matrix: add a new Matrix plugin backed by the official `matrix-js-sdk`. If you are upgrading from the previous public Matrix plugin, follow the migration guide: https://docs.openclaw.ai/install/migrating-matrix Thanks @gumadeiras. + +## 2026.3.13 + +### Changes + +- Android/chat settings: redesign the chat settings sheet with grouped device and media sections, refresh the Connect and Voice tabs, and tighten the chat composer/session header for a denser mobile layout. (#44894) Thanks @obviyus. +- iOS/onboarding: add a first-run welcome pager before gateway setup, stop auto-opening the QR scanner, and show `/pair qr` instructions on the connect step. (#45054) Thanks @ngutman. +- Browser/existing-session: add an official Chrome DevTools MCP attach mode for signed-in live Chrome sessions, with docs for `chrome://inspect/#remote-debugging` enablement and direct backlinks to Chrome’s own setup guides. +- Browser/agents: add built-in `profile="user"` for the logged-in host browser and `profile="chrome-relay"` for the extension relay, so agent browser calls can prefer the real signed-in browser without the extra `browserSession` selector. +- Browser/act automation: add batched actions, selector targeting, and delayed clicks for browser act requests with normalized batch dispatch. Thanks @vincentkoc. +- Docker/timezone override: add `OPENCLAW_TZ` so `docker-setup.sh` can pin gateway and CLI containers to a chosen IANA timezone instead of inheriting the daemon default. (#34119) Thanks @Lanfei. +- Dependencies/pi: bump `@mariozechner/pi-agent-core`, `@mariozechner/pi-ai`, `@mariozechner/pi-coding-agent`, and `@mariozechner/pi-tui` to `0.58.0`. +- Cron/sessions: add `sessionTarget: "current"` and `session:` support so cron jobs can bind to the creating session or a persistent named session instead of only `main` or `isolated`. Thanks @kkhomej33-netizen and @ImLukeF. +- Telegram/message send: add `--force-document` so Telegram image and GIF sends can upload as documents without compression. (#45111) Thanks @thepagent. + +### Fixes + +- Dashboard/chat UI: stop reloading full chat history on every live tool result in dashboard v2 so tool-heavy runs no longer trigger UI freeze/re-render storms while the final event still refreshes persisted history. (#45541) Thanks @BunsDev. +- Gateway/client requests: reject unanswered gateway RPC calls after a bounded timeout and clear their pending state, so stalled connections no longer leak hanging `GatewayClient.request()` promises indefinitely. +- Build/plugin-sdk bundling: bundle plugin-sdk subpath entries in one shared build pass so published packages stop duplicating shared chunks and avoid the recent plugin-sdk memory blow-up. (#45426) Thanks @TarasShyn. +- Ollama/reasoning visibility: stop promoting native `thinking` and `reasoning` fields into final assistant text so local reasoning models no longer leak internal thoughts in normal replies. (#45330) Thanks @xi7ang. +- Android/onboarding QR scan: switch setup QR scanning to Google Code Scanner so onboarding uses a more reliable scanner instead of the legacy embedded ZXing flow. (#45021) Thanks @obviyus. +- Browser/existing-session: harden driver validation and session lifecycle so transport errors trigger reconnects while tool-level errors preserve the session, and extract shared ARIA role sets to deduplicate Playwright and Chrome MCP snapshot paths. (#45682) Thanks @odysseus0. +- Browser/existing-session: accept text-only `list_pages` and `new_page` responses from Chrome DevTools MCP so live-session tab discovery and new-tab open flows keep working when the server omits structured page metadata. +- Control UI/insecure auth: preserve explicit shared token and password auth on plain-HTTP Control UI connects so LAN and reverse-proxy sessions no longer drop shared auth before the first WebSocket handshake. (#45088) Thanks @velvet-shark. +- Gateway/session reset: preserve `lastAccountId` and `lastThreadId` across gateway session resets so replies keep routing back to the same account and thread after `/reset`. (#44773) Thanks @Lanfei. +- macOS/onboarding: avoid self-restarting freshly bootstrapped launchd gateways and give new daemon installs longer to become healthy, so `openclaw onboard --install-daemon` no longer false-fails on slower Macs and fresh VM snapshots. +- Gateway/status: add `openclaw gateway status --require-rpc` and clearer Linux non-interactive daemon-install failure reporting so automation can fail hard on probe misses instead of treating a printed RPC error as green. +- macOS/exec approvals: respect per-agent exec approval settings in the gateway prompter, including allowlist fallback when the native prompt cannot be shown, so gateway-triggered `system.run` requests follow configured policy instead of always prompting or denying unexpectedly. (#13707) Thanks @sliekens. +- Telegram/media downloads: thread the same direct or proxy transport policy into SSRF-guarded file fetches so inbound attachments keep working when Telegram falls back between env-proxy and direct networking. (#44639) Thanks @obviyus. +- Telegram/inbound media IPv4 fallback: retry SSRF-guarded Telegram file downloads once with the same IPv4 fallback policy as Bot API calls so fresh installs on IPv6-broken hosts no longer fail to download inbound images. +- Commands/onboarding: split static auth-choice help from the plugin-backed onboarding catalog so `openclaw onboard` registration no longer pulls provider-wizard imports just to describe `--auth-choice`. (#47545) Thanks @vincentkoc. +- Windows/gateway install: bound `schtasks` calls and fall back to the Startup-folder login item when task creation hangs, so native `openclaw gateway install` fails fast instead of wedging forever on broken Scheduled Task setups. +- Windows/gateway stop: resolve Startup-folder fallback listeners from the installed `gateway.cmd` port, so `openclaw gateway stop` now actually kills fallback-launched gateway processes before restart. +- Windows/gateway status: reuse the installed service command environment when reading runtime status, so startup-fallback gateways keep reporting the configured port and running state in `gateway status --json` instead of falling back to `gateway port unknown`. +- Windows/gateway auth: stop attaching device identity on local loopback shared-token and password gateway calls, so native Windows agent replies no longer log stale `device signature expired` fallback noise before succeeding. +- Discord/gateway startup: treat plain-text and transient `/gateway/bot` metadata fetch failures as transient startup errors so Discord gateway boot no longer crashes on unhandled rejections. (#44397) Thanks @jalehman. +- Slack/probe: keep `auth.test()` bot and team metadata mapping stable while simplifying the probe result path. (#44775) Thanks @Cafexss. +- Dashboard/chat UI: render oversized plain-text replies as normal paragraphs instead of capped gray code blocks, so long desktop chat responses stay readable without tab-switching refreshes. +- Dashboard/chat UI: restore the `chat-new-messages` class on the New messages scroll pill so the button uses its existing compact styling instead of rendering as a full-screen SVG overlay. (#44856) Thanks @Astro-Han. +- Gateway/Control UI: restore the operator-only device-auth bypass and classify browser connect failures so origin and device-identity problems no longer show up as auth errors in the Control UI and web chat. (#45512) thanks @sallyom. +- macOS/voice wake: stop crashing wake-word command extraction when speech segment ranges come from a different transcript instance. +- Discord/allowlists: honor raw `guild_id` when hydrated guild objects are missing so allowlisted channels and threads like `#maintainers` no longer get false-dropped before channel allowlist checks. +- macOS/runtime locator: require Node >=22.16.0 during macOS runtime discovery so the app no longer accepts Node versions that the main runtime guard rejects later. Thanks @sumleo. +- Agents/custom providers: preserve blank API keys for loopback OpenAI-compatible custom providers by clearing the synthetic Authorization header at runtime, while keeping explicit apiKey and oauth/token config from silently downgrading into fake bearer auth. (#45631) Thanks @xinhuagu. +- Models/google-vertex Gemini flash-lite normalization: apply existing bare-ID preview normalization to `google-vertex` model refs and provider configs so `google-vertex/gemini-3.1-flash-lite` resolves as `gemini-3.1-flash-lite-preview`. (#42435) thanks @scoootscooob. +- iMessage/remote attachments: reject unsafe remote attachment paths before spawning SCP, so sender-controlled filenames can no longer inject shell metacharacters into remote media staging. Thanks @lintsinghua. +- Telegram/webhook auth: validate the Telegram webhook secret before reading or parsing request bodies, so unauthenticated requests are rejected immediately instead of consuming up to 1 MB first. Thanks @space08. +- Security/device pairing: make bootstrap setup codes single-use so pending device pairing requests cannot be silently replayed and widened to admin before approval. Thanks @tdjackey. +- Security/external content: strip zero-width and soft-hyphen marker-splitting characters during boundary sanitization so spoofed `EXTERNAL_UNTRUSTED_CONTENT` markers fall back to the existing hardening path instead of bypassing marker normalization. +- CLI/startup: stop `openclaw devices list` and similar loopback gateway commands from failing during startup by isolating heavy import-time side effects from the normal CLI path. (#50212) Thanks @obviyus. +- Security/exec approvals: unwrap more `pnpm` runtime forms during approval binding, including `pnpm --reporter ... exec` and direct `pnpm node` file runs, with matching regression coverage and docs updates. +- Security/exec approvals: fail closed for Perl `-M` and `-I` approval flows so preload and load-path module resolution stays outside approval-backed runtime execution unless the operator uses a broader explicit trust path. +- Security/exec approvals: recognize PowerShell `-File` and `-f` wrapper forms during inline-command extraction so approval and command-analysis paths treat file-based PowerShell launches like the existing `-Command` variants. +- Security/exec approvals: unwrap `env` dispatch wrappers inside shell-segment allowlist resolution on macOS so `env FOO=bar /path/to/bin` resolves against the effective executable instead of the wrapper token. +- Security/exec approvals: treat backslash-newline as shell line continuation during macOS shell-chain parsing so line-continued `$(` substitutions fail closed instead of slipping past command-substitution checks. +- Security/exec approvals: bind macOS skill auto-allow trust to both executable name and resolved path so same-basename binaries no longer inherit trust from unrelated skill bins. +- Build/plugin-sdk bundling: bundle plugin-sdk subpath entries in one shared build pass so published packages stop duplicating shared chunks and avoid the recent plugin-sdk memory blow-up. (#45426) Thanks @TarasShyn. +- Cron/isolated sessions: route nested cron-triggered embedded runner work onto the nested lane so isolated cron jobs no longer deadlock when compaction or other queued inner work runs. Thanks @vincentkoc. +- Agents/OpenAI-compatible compat overrides: respect explicit user `models[].compat` opt-ins for non-native `openai-completions` endpoints so usage-in-streaming capability overrides no longer get forced off when the endpoint actually supports them. (#44432) Thanks @cheapestinference. +- Agents/Azure OpenAI startup prompts: rephrase the built-in `/new`, `/reset`, and post-compaction startup instruction so Azure OpenAI deployments no longer hit HTTP 400 false positives from the content filter. (#43403) Thanks @xingsy97. +- Agents/compaction: compare post-compaction token sanity checks against full-session pre-compaction totals and skip the check when token estimation fails, so sessions with large bootstrap context keep real token counts instead of falling back to unknown. (#28347) thanks @efe-arv. +- Agents/compaction: preserve safeguard compaction summary language continuity via default and configurable custom instructions so persona drift is reduced after auto-compaction. (#10456) Thanks @keepitmello. +- Agents/tool warnings: distinguish gated core tools like `apply_patch` from plugin-only unknown entries in `tools.profile` warnings, so unavailable core tools now report current runtime/provider/model/config gating instead of suggesting a missing plugin. +- Config/validation: accept documented `agents.list[].params` per-agent overrides in strict config validation so `openclaw config validate` no longer rejects runtime-supported `cacheRetention`, `temperature`, and `maxTokens` settings. (#41171) Thanks @atian8179. +- Config/web fetch: restore runtime validation for documented `tools.web.fetch.readability` and `tools.web.fetch.firecrawl` settings so valid web fetch configs no longer fail with unrecognized-key errors. (#42583) Thanks @stim64045-spec. +- Signal/config validation: add `channels.signal.groups` schema support so per-group `requireMention`, `tools`, and `toolsBySender` overrides no longer get rejected during config validation. (#27199) Thanks @unisone. +- Config/discovery: accept `discovery.wideArea.domain` in strict config validation so unicast DNS-SD gateway configs no longer fail with an unrecognized-key error. (#35615) Thanks @ingyukoh. +- Telegram/media errors: redact Telegram file URLs before building media fetch errors so failed inbound downloads do not leak bot tokens into logs. Thanks @space08. +- Agents/failover: normalize abort-wrapped `429 RESOURCE_EXHAUSTED` provider failures before abort short-circuiting so wrapped Google/Vertex rate limits continue across configured fallback models, including the embedded runner prompt-error path. (#39820) Thanks @lupuletic. +- Mattermost/thread routing: non-inbound reply paths (TUI/WebUI turns, tool-call callbacks, subagent responses) now correctly route to the originating Mattermost thread when `replyToMode: "all"` is active; also prevents stale `origin.threadId` metadata from resurrecting cleared thread routes. (#44283) thanks @teconomix +- Gateway/websocket pairing bypass for disabled auth: skip device-pairing enforcement when `gateway.auth.mode=none` so Control UI connections behind reverse proxies no longer get stuck on `pairing required` (code 1008) despite auth being explicitly disabled. (#42931) +- Auth/login lockout recovery: clear stale `auth_permanent` and `billing` disabled state for all profiles matching the target provider when `openclaw models auth login` is invoked, so users locked out by expired or revoked OAuth tokens can recover by re-authenticating instead of waiting for the cooldown timer to expire. (#43057) +- Auto-reply/context-engine compaction: persist the exact embedded-run metadata compaction count for main and followup runner session accounting, so metadata-only auto-compactions no longer undercount multi-compaction runs. (#42629) thanks @uf-hy. +- Auth/Codex CLI reuse: sync reused Codex CLI credentials into the supported `openai-codex:default` OAuth profile instead of reviving the deprecated `openai-codex:codex-cli` slot, so doctor cleanup no longer loops. (#45353) thanks @Gugu-sugar. +- Deps/audit: bump the pinned `fast-xml-parser` override to the first patched release so `pnpm audit --prod --audit-level=high` no longer fails on the AWS Bedrock XML builder path. Thanks @vincentkoc. +- Hooks/after_compaction: forward `sessionFile` for direct/manual compaction events and add `sessionFile` plus `sessionKey` to wired auto-compaction hook context so plugins receive the session metadata already declared in the hook types. (#40781) Thanks @jarimustonen. +- Sessions/BlueBubbles/cron: persist outbound session routing and transcript mirroring for new targets, auto-create BlueBubbles chats before attachment sends, and only suppress isolated cron deliveries when the run started hours late instead of merely finishing late. (#50092) + +### Breaking + +- **BREAKING:** Agents now load at most one root memory bootstrap file. `MEMORY.md` wins; `memory.md` is only used when `MEMORY.md` is absent. If you intentionally kept both files and depended on both being injected, merge them before upgrade. This also fixes duplicate memory injection on case-insensitive Docker mounts. (#26054) Thanks @Lanfei. + +## 2026.3.12 + +### Changes + +- Control UI/dashboard-v2: refresh the gateway dashboard with modular overview, chat, config, agent, and session views, plus a command palette, mobile bottom tabs, and richer chat tools like slash commands, search, export, and pinned messages. (#41503) Thanks @BunsDev. +- OpenAI/GPT-5.4 fast mode: add configurable session-level fast toggles across `/fast`, TUI, Control UI, and ACP, with per-model config defaults and OpenAI/Codex request shaping. +- Anthropic/Claude fast mode: map the shared `/fast` toggle and `params.fastMode` to direct Anthropic API-key `service_tier` requests, with live verification for both Anthropic and OpenAI fast-mode tiers. +- Models/plugins: move Ollama, vLLM, and SGLang onto the provider-plugin architecture, with provider-owned onboarding, discovery, model-picker setup, and post-selection hooks so core provider wiring is more modular. +- Docs/Kubernetes: Add a starter K8s install path with raw manifests, Kind setup, and deployment docs. Thanks @sallyom @dzianisv @egkristi +- Agents/subagents: add `sessions_yield` so orchestrators can end the current turn immediately, skip queued tool work, and carry a hidden follow-up payload into the next session turn. (#36537) thanks @jriff +- Slack/agent replies: support `channelData.slack.blocks` in the shared reply delivery path so agents can send Block Kit messages through standard Slack outbound delivery. (#44592) Thanks @vincentkoc. +- Slack/interactive replies: add opt-in Slack button and select reply directives behind `channels.slack.capabilities.interactiveReplies`, disabled by default unless explicitly enabled. (#44607) Thanks @vincentkoc. + +### Fixes + +- Security/device pairing: switch `/pair` and `openclaw qr` setup codes to short-lived bootstrap tokens so the next release no longer embeds shared gateway credentials in chat or QR pairing payloads. Thanks @lintsinghua. +- Security/plugins: disable implicit workspace plugin auto-load so cloned repositories cannot execute workspace plugin code without an explicit trust decision. (`GHSA-99qw-6mr3-36qr`)(#44174) Thanks @lintsinghua and @vincentkoc. +- Models/Kimi Coding: send `anthropic-messages` tools in native Anthropic format again so `kimi-coding` stops degrading tool calls into XML/plain-text pseudo invocations instead of real `tool_use` blocks. (#38669, #39907, #40552) Thanks @opriz. +- TUI/chat log: reuse the active assistant message component for the same streaming run so `openclaw tui` no longer renders duplicate assistant replies. (#35364) Thanks @lisitan. +- Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in `/models` button validation. (#40105) Thanks @avirweb. +- Cron/proactive delivery: keep isolated direct cron sends out of the write-ahead resend queue so transient-send retries do not replay duplicate proactive messages after restart. (#40646) Thanks @openperf and @vincentkoc. +- Models/Kimi Coding: send the built-in `User-Agent: claude-code/0.1.0` header by default for `kimi-coding` while still allowing explicit provider headers to override it, so Kimi Code subscription auth can work without a local header-injection proxy. (#30099) Thanks @Amineelfarssi and @vincentkoc. +- Models/OpenAI Codex Spark: keep `gpt-5.3-codex-spark` working on the `openai-codex/*` path via resolver fallbacks and clearer Codex-only handling, while continuing to suppress the stale direct `openai/*` Spark row that OpenAI rejects live. +- Ollama/Kimi Cloud: apply the Moonshot Kimi payload compatibility wrapper to Ollama-hosted Kimi models like `kimi-k2.5:cloud`, so tool routing no longer breaks when thinking is enabled. (#41519) Thanks @vincentkoc. +- Moonshot CN API: respect explicit `baseUrl` (api.moonshot.cn) in implicit provider resolution so platform.moonshot.cn API keys authenticate correctly instead of returning HTTP 401. (#33637) Thanks @chengzhichao-xydt. +- Kimi Coding/provider config: respect explicit `models.providers["kimi-coding"].baseUrl` when resolving the implicit provider so custom Kimi Coding endpoints no longer get overwritten by the built-in default. (#36353) Thanks @2233admin. +- Gateway/main-session routing: keep TUI and other `mode:UI` main-session sends on the internal surface when `deliver` is enabled, so replies no longer inherit the session's persisted Telegram/WhatsApp route. (#43918) Thanks @obviyus. +- BlueBubbles/self-chat echo dedupe: drop reflected duplicate webhook copies only when a matching `fromMe` event was just seen for the same chat, body, and timestamp, preventing self-chat loops without broad webhook suppression. Related to #32166. (#38442) Thanks @vincentkoc. +- iMessage/self-chat echo dedupe: drop reflected duplicate copies only when a matching `is_from_me` event was just seen for the same chat, text, and `created_at`, preventing self-chat loops without broad text-only suppression. Related to #32166. (#38440) Thanks @vincentkoc. +- Subagents/completion announce retries: raise the default announce timeout to 90 seconds and stop retrying gateway-timeout failures for externally delivered completion announces, preventing duplicate user-facing completion messages after slow gateway responses. Fixes #41235. Thanks @vasujain00 and @vincentkoc. +- Mattermost/block streaming: fix duplicate message delivery (one threaded, one top-level) when block streaming is active by excluding `replyToId` from the block reply dedup key and adding an explicit `threading` dock to the Mattermost plugin. (#41362) Thanks @mathiasnagler and @vincentkoc. +- Mattermost/reply media delivery: pass agent-scoped `mediaLocalRoots` through shared reply delivery so allowed local files upload correctly from button, slash-command, and model-picker replies. (#44021) Thanks @LyleLiu666. +- macOS/Reminders: add the missing `NSRemindersUsageDescription` to the bundled app so `apple-reminders` can trigger the system permission prompt from OpenClaw.app. (#8559) Thanks @dinakars777. +- Gateway/session discovery: discover disk-only and retired ACP session stores under custom templated `session.store` roots so ACP reconciliation, session-id/session-label targeting, and run-id fallback keep working after restart. (#44176) thanks @gumadeiras. +- Plugins/env-scoped roots: fix plugin discovery/load caches and provenance tracking so same-process `HOME`/`OPENCLAW_HOME` changes no longer reuse stale plugin state or misreport `~/...` plugins as untracked. (#44046) thanks @gumadeiras. +- Models/OpenRouter native ids: canonicalize native OpenRouter model keys across config writes, runtime lookups, fallback management, and `models list --plain`, and migrate legacy duplicated `openrouter/openrouter/...` config entries forward on write. +- Windows/native update: make package installs use the npm update path instead of the git path, carry portable Git into native Windows updates, and mirror the installer's Windows npm env so `openclaw update` no longer dies early on missing `git` or `node-llama-cpp` download setup. +- Sandbox/write: preserve pinned mutation-helper payload stdin so sandboxed `write` no longer reports success while creating empty files. (#43876) Thanks @glitch418x. +- Security/exec approvals: escape invisible Unicode format characters in approval prompts so zero-width command text renders as visible `\u{...}` escapes instead of spoofing the reviewed command. (`GHSA-pcqg-f7rg-xfvv`)(#43687) Thanks @EkiXu and @vincentkoc. +- Hooks/loader: fail closed when workspace hook paths cannot be resolved with `realpath`, so unreadable or broken internal hook paths are skipped instead of falling back to unresolved imports. (#44437) Thanks @vincentkoc. +- Hooks/agent deliveries: dedupe repeated hook requests by optional idempotency key so webhook retries can reuse the first run instead of launching duplicate agent executions. (#44438) Thanks @vincentkoc. +- Security/exec detection: normalize compatibility Unicode and strip invisible formatting code points before obfuscation checks so zero-width and fullwidth command tricks no longer suppress heuristic detection. (`GHSA-9r3v-37xh-2cf6`)(#44091) Thanks @wooluo and @vincentkoc. +- Security/exec allowlist: preserve POSIX case sensitivity and keep `?` within a single path segment so exact-looking allowlist patterns no longer overmatch executables across case or directory boundaries. (`GHSA-f8r2-vg7x-gh8m`)(#43798) Thanks @zpbrent and @vincentkoc. +- Security/commands: require sender ownership for `/config` and `/debug` so authorized non-owner senders can no longer reach owner-only config and runtime debug surfaces. (`GHSA-r7vr-gr74-94p8`)(#44305) Thanks @tdjackey and @vincentkoc. +- Security/gateway auth: clear unbound client-declared scopes on shared-token WebSocket connects so device-less shared-token operators cannot self-declare elevated scopes. (`GHSA-rqpp-rjj8-7wv8`)(#44306) Thanks @LUOYEcode and @vincentkoc. +- Security/browser.request: block persistent browser profile create/delete routes from write-scoped `browser.request` so callers can no longer persist admin-only browser profile changes through the browser control surface. (`GHSA-vmhq-cqm9-6p7q`)(#43800) Thanks @tdjackey and @vincentkoc. +- Security/agent: reject public spawned-run lineage fields and keep workspace inheritance on the internal spawned-session path so external `agent` callers can no longer override the gateway workspace boundary. (`GHSA-2rqg-gjgv-84jm`)(#43801) Thanks @tdjackey and @vincentkoc. +- Security/session_status: enforce sandbox session-tree visibility and shared agent-to-agent access guards before reading or mutating target session state, so sandboxed subagents can no longer inspect parent session metadata or write parent model overrides via `session_status`. (`GHSA-wcxr-59v9-rxr8`)(#43754) Thanks @tdjackey and @vincentkoc. +- Security/agent tools: mark `nodes` as explicitly owner-only and document/test that `canvas` remains a shared trusted-operator surface unless a real boundary bypass exists. +- Security/exec approvals: fail closed for Ruby approval flows that use `-r`, `--require`, or `-I` so approval-backed commands no longer bind only the main script while extra local code-loading flags remain outside the reviewed file snapshot. +- Security/device pairing: cap issued and verified device-token scopes to each paired device's approved scope baseline so stale or overbroad tokens cannot exceed approved access. (`GHSA-2pwv-x786-56f8`)(#43686) Thanks @tdjackey and @vincentkoc. +- Docs/onboarding: align the legacy wizard reference and `openclaw onboard` command docs with the Ollama onboarding flow so all onboarding reference paths now document `--auth-choice ollama`, Cloud + Local mode, and non-interactive usage. (#43473) Thanks @BruceMacD. +- Models/secrets: enforce source-managed SecretRef markers in generated `models.json` so runtime-resolved provider secrets are not persisted when runtime projection is skipped. (#43759) Thanks @joshavant. +- Security/WebSocket preauth: shorten unauthenticated handshake retention and reject oversized pre-auth frames before application-layer parsing to reduce pre-pairing exposure on unsupported public deployments. (`GHSA-jv4g-m82p-2j93`)(#44089) (`GHSA-xwx2-ppv2-wx98`)(#44089) Thanks @ez-lbz and @vincentkoc. +- Security/proxy attachments: restore the shared media-store size cap for persisted browser proxy files so oversized payloads are rejected instead of overriding the intended 5 MB limit. (`GHSA-6rph-mmhp-h7h9`)(#43684) Thanks @tdjackey and @vincentkoc. +- Security/host env: block inherited `GIT_EXEC_PATH` from sanitized host exec environments so Git helper resolution cannot be steered by host environment state. (`GHSA-jf5v-pqgw-gm5m`)(#43685) Thanks @zpbrent and @vincentkoc. +- Security/Feishu webhook: require `encryptKey` alongside `verificationToken` in webhook mode so unsigned forged events are rejected instead of being processed with token-only configuration. (`GHSA-g353-mgv3-8pcj`)(#44087) Thanks @lintsinghua and @vincentkoc. +- Security/Feishu reactions: preserve looked-up group chat typing and fail closed on ambiguous reaction context so group authorization and mention gating cannot be bypassed through synthetic `p2p` reactions. (`GHSA-m69h-jm2f-2pv8`)(#44088) Thanks @zpbrent and @vincentkoc. +- Security/LINE webhook: require signatures for empty-event POST probes too so unsigned requests no longer confirm webhook reachability with a `200` response. (`GHSA-mhxh-9pjm-w7q5`)(#44090) Thanks @TerminalsandCoffee and @vincentkoc. +- Security/Zalo webhook: rate limit invalid secret guesses before auth so weak webhook secrets cannot be brute-forced through unauthenticated churned requests without pre-auth `429` responses. (`GHSA-5m9r-p9g7-679c`)(#44173) Thanks @zpbrent and @vincentkoc. +- Security/Zalouser groups: require stable group IDs for allowlist auth by default and gate mutable group-name matching behind `channels.zalouser.dangerouslyAllowNameMatching`. Thanks @zpbrent. +- Security/Slack and Teams routing: require stable channel and team IDs for allowlist routing by default, with mutable name matching only via each channel's `dangerouslyAllowNameMatching` break-glass flag. +- Security/exec approvals: fail closed for ambiguous inline loader and shell-payload script execution, bind the real script after POSIX shell value-taking flags, and unwrap `pnpm`/`npm exec`/`npx` script runners before approval binding. (`GHSA-57jw-9722-6rf2`)(`GHSA-jvqh-rfmh-jh27`)(`GHSA-x7pp-23xv-mmr4`)(`GHSA-jc5j-vg4r-j5jx`)(#44247) Thanks @tdjackey and @vincentkoc. +- Doctor/gateway service audit: canonicalize service entrypoint paths before comparing them so symlink-vs-realpath installs no longer trigger false "entrypoint does not match the current install" repair prompts. (#43882) Thanks @ngutman. +- Doctor/gateway service audit: earlier groundwork for this fix landed in the superseded #28338 branch. Thanks @realriphub. +- Gateway/session stores: regenerate the Swift push-test protocol models and align Windows native session-store realpath handling so protocol checks and sync session discovery stop drifting on Windows. (#44266) thanks @jalehman. +- Context engine/session routing: forward optional `sessionKey` through context-engine lifecycle calls so plugins can see structured routing metadata during bootstrap, assembly, post-turn ingestion, and compaction. (#44157) thanks @jalehman. +- Agents/failover: classify z.ai `network_error` stop reasons as retryable timeouts so provider connectivity failures trigger fallback instead of surfacing raw unhandled-stop-reason errors. (#43884) Thanks @hougangdev. +- Config/Anthropic startup: inline Anthropic alias normalization during config load so gateway startup no longer crashes on dated Anthropic model refs like `anthropic/claude-sonnet-4-20250514`. (#45520) Thanks @BunsDev. +- Memory/session sync: add mode-aware post-compaction session reindexing with `agents.defaults.compaction.postIndexSync` plus `agents.defaults.memorySearch.sync.sessions.postCompactionForce`, so compacted session memory can refresh immediately without forcing every deployment into synchronous reindexing. (#25561) thanks @rodrigouroz. +- Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in `/models` button validation. (#40105) Thanks @avirweb. +- Telegram/native command sync: suppress expected `BOT_COMMANDS_TOO_MUCH` retry error noise, add a final fallback summary log, and document the difference between command-menu overflow and real Telegram network failures. +- Mattermost/reply media delivery: pass agent-scoped `mediaLocalRoots` through shared reply delivery so allowed local files upload correctly from button, slash-command, and model-picker replies. (#44021) Thanks @LyleLiu666. +- Plugins/env-scoped roots: fix plugin discovery/load caches and provenance tracking so same-process `HOME`/`OPENCLAW_HOME` changes no longer reuse stale plugin state or misreport `~/...` plugins as untracked. (#44046) thanks @gumadeiras. +- Gateway/session discovery: discover disk-only and retired ACP session stores under custom templated `session.store` roots so ACP reconciliation, session-id/session-label targeting, and run-id fallback keep working after restart. (#44176) thanks @gumadeiras. +- Browser/existing-session: stop reporting fake CDP ports/URLs for live attached Chrome sessions, render `transport: chrome-mcp` in CLI/status output instead of `port: 0`, and keep timeout diagnostics transport-aware when no direct CDP URL exists. +- Models/OpenRouter native ids: canonicalize native OpenRouter model keys across config writes, runtime lookups, fallback management, and `models list --plain`, and migrate legacy duplicated `openrouter/openrouter/...` config entries forward on write. +- Feishu/event dedupe: keep early duplicate suppression aligned with the shared Feishu message-id contract and release the pre-queue dedupe marker after failed dispatch so retried events can recover instead of being dropped until the short TTL expires. (#43762) Thanks @yunweibang. +- Gateway/hooks: bucket hook auth failures by forwarded client IP behind trusted proxies and warn when `hooks.allowedAgentIds` leaves hook routing unrestricted. +- Agents/compaction: skip the post-compaction `cache-ttl` marker write when a compaction completed in the same attempt, preventing the next turn from immediately triggering a second tiny compaction. (#28548) thanks @MoerAI. +- Native chat/macOS: add `/new`, `/reset`, and `/clear` reset triggers, keep shared main-session aliases aligned, and ignore stale model-selection completions so native chat state stays in sync across reset and fast model changes. (#10898) Thanks @Nachx639. +- Agents/compaction safeguard: route missing-model and missing-API-key cancellation warnings through the shared subsystem logger so they land in structured and file logs. (#9974) Thanks @dinakars777. +- Cron/doctor: stop flagging canonical `agentTurn` and `systemEvent` payload kinds as legacy cron storage, while still normalizing whitespace-padded and non-canonical variants. (#44012) Thanks @shuicici. +- ACP/client final-message delivery: preserve terminal assistant text snapshots before resolving `end_turn`, so ACP clients no longer drop the last visible reply when the gateway sends the final message body on the terminal chat event. (#17615) Thanks @pjeby. +- Telegram/Discord status reactions: show a temporary compacting reaction during auto-compaction pauses and restore thinking afterward so the bot no longer appears frozen while context is being compacted. (#35474) thanks @Cypherm. +- Delivery/dedupe: trim completed direct-cron delivery cache correctly and keep mirrored transcript dedupe active even when transcript files contain malformed lines. (#44666) thanks @frankekn. +- CLI/thinking help: add the missing `xhigh` level hints to `openclaw cron add`, `openclaw cron edit`, and `openclaw agent` so the help text matches the levels already accepted at runtime. (#44819) Thanks @kiki830621. +- Agents/Anthropic replay: drop replayed assistant thinking blocks for native Anthropic and Bedrock Claude providers so persisted follow-up turns no longer fail on stored thinking blocks. (#44843) Thanks @jmcte. +- Docs/Brave pricing: escape literal dollar signs in Brave Search cost text so the docs render the free credit and per-request pricing correctly. (#44989) Thanks @keelanfh. +- Feishu/file uploads: preserve literal UTF-8 filenames in `im.file.create` so Chinese and other non-ASCII filenames no longer appear percent-encoded in chat. (#34262) Thanks @fabiaodemianyang and @KangShuaiFu. +- Agents/compaction safeguard: trim large kept `toolResult` payloads consistently for budgeting, pruning, and identifier seeding, then restore preserved payloads after prune so oversized safeguard summaries stay stable. (#44133) thanks @SayrWolfridge. +- Agents/compaction: compare post-compaction token sanity checks against full-session pre-compaction totals and skip the check when token estimation fails, so sessions with large bootstrap context keep real token counts instead of falling back to unknown. (#28347) thanks @efe-arv. +- Discord/gateway startup: treat plain-text and transient `/gateway/bot` metadata fetch failures as transient startup errors so Discord gateway boot no longer crashes on unhandled rejections. (#44397) Thanks @jalehman. +- Agents/Ollama overflow: rewrite Ollama `prompt too long` API payloads through the normal context-overflow sanitizer so embedded sessions keep the friendly overflow copy and auto-compaction trigger. (#34019) thanks @lishuaigit. + +- Control UI/auth: restore one-time legacy `?token=` imports for shared Control UI links while keeping `#token=` preferred, and carry pending query tokens through gateway URL confirmation so compatibility links still authenticate after confirmation. (#43979) Thanks @stim64045-spec. +- Plugins/context engines: retry legacy lifecycle calls once without `sessionKey` when older plugins reject that field, memoize legacy mode after the first strict-schema fallback, and preserve non-compat runtime errors without retry. (#44779) thanks @hhhhao28. + +## 2026.3.11 + +### Changes + +- OpenRouter/models: add temporary Hunter Alpha and Healer Alpha entries to the built-in catalog so OpenRouter users can try the new free stealth models during their roughly one-week availability window. (#43642) Thanks @ping-Toven. +- iOS/Home canvas: add a bundled welcome screen with a live agent overview that refreshes on connect, reconnect, and foreground return, and move the compact connection pill off the top-left canvas overlay. (#42456) Thanks @ngutman. +- iOS/Home canvas: replace floating controls with a docked toolbar, make the bundled home scaffold adapt to smaller phones, and open chat in the resolved main session instead of a synthetic `ios` session. (#42456) Thanks @ngutman. +- macOS/chat UI: add a chat model picker, persist explicit thinking-level selections across relaunch, and harden provider-aware session model sync for the shared chat composer. (#42314) Thanks @ImLukeF. +- Onboarding/Ollama: add first-class Ollama setup with Local or Cloud + Local modes, browser-based cloud sign-in, curated model suggestions, and cloud-model handling that skips unnecessary local pulls. (#41529) Thanks @BruceMacD. +- OpenCode/onboarding: add new OpenCode Go provider, treat Zen and Go as one OpenCode setup in the wizard/docs while keeping the runtime providers split, store one shared OpenCode key for both profiles, and stop overriding the built-in `opencode-go` catalog routing. (#42313) Thanks @ImLukeF and @vincentkoc. +- Memory: add opt-in multimodal image and audio indexing for `memorySearch.extraPaths` with Gemini `gemini-embedding-2-preview`, strict fallback gating, and scope-based reindexing. (#43460) Thanks @gumadeiras. +- Memory/Gemini: add `gemini-embedding-2-preview` memory-search support with configurable output dimensions and automatic reindexing when the configured dimensions change. (#42501) Thanks @BillChirico and @gumadeiras. +- macOS/onboarding: detect when remote gateways need a shared auth token, explain where to find it on the gateway host, and clarify when a successful check used paired-device auth instead. (#43100) Thanks @ngutman. +- Discord/auto threads: add `autoArchiveDuration` channel config for auto-created threads so Discord thread archiving can stay at 1 hour, 1 day, 3 days, or 1 week instead of always using the 1-hour default. (#35065) Thanks @davidguttman. +- iOS/TestFlight: add a local beta release flow with Fastlane prepare/archive/upload support, canonical beta bundle IDs, and watch-app archive fixes. (#42991) Thanks @ngutman. +- ACP/sessions_spawn: add optional `resumeSessionId` for `runtime: "acp"` so spawned ACP sessions can resume an existing ACPX/Codex conversation instead of always starting fresh. (#41847) Thanks @pejmanjohn. +- Gateway/node pending work: add narrow in-memory pending-work queue primitives (`node.pending.enqueue` / `node.pending.drain`) and wake-helper reuse as a foundation for dormant-node work delivery. (#41409) Thanks @mbelinky. +- Git/runtime state: ignore the gateway-generated `.dev-state` file so local runtime state does not show up as untracked repo noise. (#41848) Thanks @smysle. +- Exec/child commands: mark child command environments with `OPENCLAW_CLI` so subprocesses can detect when they were launched from the OpenClaw CLI. (#41411) Thanks @vincentkoc. +- LLM Task/Lobster: add an optional `thinking` override so workflow calls can explicitly set embedded reasoning level with shared validation for invalid values and unsupported `xhigh` modes. (#15606) Thanks @xadenryan and @ImLukeF. +- Mattermost/reply threading: add `channels.mattermost.replyToMode` for channel and group messages so top-level posts can start thread-scoped sessions without the manual reply-then-thread workaround. (#29587) Thanks @teconomix. +- iOS/push relay: add relay-backed official-build push delivery with App Attest + receipt verification, gateway-bound send delegation, and config-based relay URL setup on the gateway. (#43369) Thanks @ngutman. + +### Fixes + +- Windows/install: stop auto-installing `node-llama-cpp` during normal npm CLI installs so `openclaw@latest` no longer fails on Windows while building optional local-embedding dependencies. +- Windows/update: mirror the native installer environment during global npm updates, including portable Git fallback and Windows-safe npm shell settings, so `openclaw update` works again on native Windows installs. +- Gateway/status: expose `runtimeVersion` in gateway status output so install/update smoke tests can verify the running version before and after updates. +- Windows/onboarding: explain when non-interactive local onboarding is waiting for an already-running gateway, and surface native Scheduled Task admin requirements more clearly instead of failing with an opaque gateway timeout. +- Windows/gateway install: fall back from denied Scheduled Task creation to a per-user Startup-folder login item, so native `openclaw gateway install` and `--install-daemon` keep working without an elevated PowerShell shell. - Agents/text sanitization: strip leaked model control tokens (`<|...|>` and full-width `<|...|>` variants) from user-facing assistant text, preventing GLM-5 and DeepSeek internal delimiters from reaching end users. (#42173) Thanks @imwyvern. -- Resolve web tool SecretRefs atomically at runtime. (#41599) Thanks @joshavant. -- Feishu/local image auto-convert: pass `mediaLocalRoots` through the `sendText` local-image shim so allowed local image paths upload as Feishu images again instead of falling back to raw path text. (#40623) Thanks @ayanesakura. -- ACP/ACPX plugin: bump the bundled `acpx` pin to `0.1.16` so plugin-local installs and strict version checks match the latest published CLI. (#41975) Thanks @dutifulbob. -- macOS/LaunchAgent install: tighten LaunchAgent directory and plist permissions during install so launchd bootstrap does not fail when the target home path or generated plist inherited group/world-writable modes. +- iOS/gateway foreground recovery: reconnect immediately on foreground return after stale background sockets are torn down, so the app no longer stays disconnected until a later wake path happens. (#41384) Thanks @mbelinky. - Gateway/Control UI: keep dashboard auth tokens in session-scoped browser storage so same-tab refreshes preserve remote token auth without restoring long-lived localStorage token persistence, while scoping tokens to the selected gateway URL and fragment-only bootstrap flow. (#40892) thanks @velvet-shark. +- Gateway/macOS launchd restarts: keep the LaunchAgent registered during explicit restarts, hand off self-restarts through a detached launchd helper, and recover config/hot reload restart paths without unloading the service. Fixes #43311, #43406, #43035, and #43049. +- macOS/LaunchAgent install: tighten LaunchAgent directory and plist permissions during install so launchd bootstrap does not fail when the target home path or generated plist inherited group/world-writable modes. +- Discord/reply chunking: resolve the effective `maxLinesPerMessage` config across live reply paths and preserve `chunkMode` in the fast send path so long Discord replies no longer split unexpectedly at the default 17-line limit. (#40133) thanks @rbutera. +- Feishu/local image auto-convert: pass `mediaLocalRoots` through the `sendText` local-image shim so allowed local image paths upload as Feishu images again instead of falling back to raw path text. (#40623) Thanks @ayanesakura. +- Models/Kimi Coding: send `anthropic-messages` tools in native Anthropic format again so `kimi-coding` stops degrading tool calls into XML/plain-text pseudo invocations instead of real `tool_use` blocks. (#38669, #39907, #40552) Thanks @opriz. +- Telegram/outbound HTML sends: chunk long HTML-mode messages, preserve plain-text fallback and silent-delivery params across retries, and cut over to plain text when HTML chunk planning cannot safely preserve the full message. (#42240) thanks @obviyus. +- Telegram/final preview delivery: split active preview lifecycle from cleanup retention so missing archived preview edits avoid duplicate fallback sends without clearing the live preview or blocking later in-place finalization. (#41662) thanks @hougangdev. +- Telegram/final preview delivery followup: keep ambiguous missing-`message_id` finals only when a preview was already visible, while first-preview/no-id cases still fall back so Telegram users do not lose the final reply. (#41932) thanks @hougangdev. +- Telegram/final preview cleanup follow-up: clear stale cleanup-retain state only for transient preview finals so archived-preview retains no longer leave a stale partial bubble beside a later fallback-sent final. (#41763) Thanks @obviyus. +- Telegram/poll restarts: scope process-level polling restarts to real Telegram `getUpdates` failures so unrelated network errors, such as Slack DNS misses, no longer bounce Telegram polling. (#43799) Thanks @obviyus. +- Gateway/auth: allow one trusted device-token retry on shared-token mismatch with recovery hints to prevent reconnect churn during token drift. (#42507) Thanks @joshavant. +- Gateway/config errors: surface up to three validation issues in top-level `config.set`, `config.patch`, and `config.apply` error messages while preserving structured issue details. (#42664) Thanks @huntharo. +- Agents/Azure OpenAI Responses: include the `azure-openai` provider in the Responses API store override so Azure OpenAI multi-turn cron jobs and embedded agent runs no longer fail with HTTP 400 "store is set to false". (#42934, fixes #42800) Thanks @ademczuk. +- Agents/error rendering: ignore stale assistant `errorMessage` fields on successful turns so background/tool-side failures no longer prepend synthetic billing errors over valid replies. (#40616) Thanks @ingyukoh. +- Agents/billing recovery: probe single-provider billing cooldowns on the existing throttle so topping up credits can recover without a manual gateway restart. (#41422) thanks @altaywtf. +- Agents/fallback: treat HTTP 499 responses as transient in both raw-text and structured failover paths so Anthropic-style client-closed overload responses trigger model fallback reliably. (#41468) thanks @zeroasterisk. +- Agents/fallback: recognize Venice `402 Insufficient USD or Diem balance` billing errors so configured model fallbacks trigger instead of surfacing the raw provider error. (#43205) Thanks @Squabble9. +- Agents/fallback: recognize Poe `402 You've used up your points!` billing errors so configured model fallbacks trigger instead of surfacing the raw provider error. (#42278) Thanks @CryUshio. +- Agents/failover: treat Gemini `MALFORMED_RESPONSE` stop reasons as retryable timeouts so preview-model enum drift falls back cleanly instead of crashing the run, without also reclassifying malformed function-call errors. (#42292) Thanks @jnMetaCode. +- Agents/cooldowns: default cooldown windows with no recorded failure history to `unknown` instead of `rate_limit`, avoiding false API rate-limit warnings while preserving cooldown recovery probes. (#42911) Thanks @VibhorGautam. +- Auth/cooldowns: reset expired auth-profile cooldown error counters before computing the next backoff so stale on-disk counters do not re-escalate into long cooldown loops after expiry. (#41028) thanks @zerone0x. +- Agents/memory flush: forward `memoryFlushWritePath` through `runEmbeddedPiAgent` so memory-triggered flush turns keep the append-only write guard without aborting before tool setup. Follows up on #38574. (#41761) Thanks @frankekn. +- Agents/context pruning: prune image-only tool results during soft-trim, align context-pruning coverage with the new tool-result contract, and extend historical image cleanup to the same screenshot-heavy session path. (#43045) Thanks @MoerAI. +- Sessions/reset model recompute: clear stale runtime model, context-token, and system-prompt metadata before session resets recompute the replacement session, so resets pick up current defaults and explicit overrides instead of reusing old runtime model state. (#41173) thanks @PonyX-lab. +- Channels/allowlists: remove stale matcher caching so same-array allowlist edits and wildcard replacements take effect immediately, with regression coverage for in-place mutation cases. +- Discord/Telegram outbound runtime config: thread runtime-resolved config through Discord and Telegram send paths so SecretRef-based credentials stay resolved during message delivery. (#42352) Thanks @joshavant. +- Tools/web search: treat Brave `llm-context` grounding snippets as plain strings so `web_search` no longer returns empty snippet arrays in LLM Context mode. (#41387) thanks @zheliu2. +- Tools/web search: recover OpenRouter Perplexity citation extraction from `message.annotations` when chat-completions responses omit top-level citations. (#40881) Thanks @laurieluo. +- CLI/skills JSON: strip ANSI and C1 control bytes from `skills list --json`, `skills info --json`, and `skills check --json` so machine-readable output stays valid for terminals and skill metadata with embedded control characters. Fixes #27530. Related #27557. Thanks @Jimmy-xuzimo and @vincentkoc. +- CLI/tables: default shared tables to ASCII borders on legacy Windows consoles while keeping Unicode borders on modern Windows terminals, so commands like `openclaw skills` stop rendering mojibake under GBK/936 consoles. Fixes #40853. Related #41015. Thanks @ApacheBin and @vincentkoc. +- CLI/memory teardown: close cached memory search/index managers in the one-shot CLI shutdown path so watcher-backed memory caches no longer keep completed CLI runs alive after output finishes. (#40389) thanks @Julbarth. +- Control UI/Sessions: restore single-column session table collapse on narrow viewport or container widths by moving the responsive table override next to the base grid rule and enabling inline-size container queries. (#12175) Thanks @benjipeng. +- Telegram/network env-proxy: apply configured transport policy to proxied HTTPS dispatchers as well as direct `NO_PROXY` bypasses, so resolver-scoped IPv4 fallback and network settings work consistently for env-proxied Telegram traffic. (#40740) Thanks @sircrumpet. +- Mattermost/Markdown formatting: preserve first-line indentation when stripping bot mentions so nested list items and indented code blocks keep their structure, and render Mattermost tables natively by default instead of fenced-code fallback. (#18655) thanks @echo931. +- Mattermost/plugin send actions: normalize direct `replyTo` fallback handling so threaded plugin sends trim blank IDs and reuse the correct reply target again. (#41176) Thanks @hnykda. +- MS Teams/allowlist resolution: use the General channel conversation ID as the resolved team key (with Graph GUID fallback) so Bot Framework runtime `channelData.team.id` matching works for team and team/channel allowlist entries. (#41838) Thanks @BradGroux. +- Signal/config schema: accept `channels.signal.accountUuid` in strict config validation so loop-protection configs no longer fail with an unrecognized-key error. (#35578) Thanks @ingyukoh. +- Telegram/config schema: accept `channels.telegram.actions.editMessage` and `createForumTopic` in strict config validation so existing Telegram action toggles no longer fail as unrecognized keys. (#35498) Thanks @ingyukoh. +- Telegram/docs: clarify that `channels.telegram.groups` allowlists chats while `groupAllowFrom` allowlists users inside those chats, and point invalid negative chat IDs at the right config key. (#42451) Thanks @altaywtf. +- Discord/config typing: expose channel-level `autoThread` on the canonical guild-channel config type so strict config loading matches the existing Discord schema and runtime behavior. (#35608) Thanks @ingyukoh. +- fix(models): guard optional model.input capability checks (#42096) thanks @andyliu +- Models/Alibaba Cloud Model Studio: wire `MODELSTUDIO_API_KEY` through shared env auth, implicit provider discovery, and shell-env fallback so onboarding works outside the wizard too. (#40634) Thanks @pomelo-nwu. +- Resolve web tool SecretRefs atomically at runtime. (#41599) Thanks @joshavant. - Secret files: harden CLI and channel credential file reads against path-swap races by requiring direct regular files for `*File` secret inputs and rejecting symlink-backed secret files. - Archive extraction: harden TAR and external `tar.bz2` installs against destination symlink and pre-existing child-symlink escapes by extracting into staging first and merging into the canonical destination with safe file opens. -- Models/Kimi Coding: send `anthropic-messages` tools in native Anthropic format again so `kimi-coding` stops degrading tool calls into XML/plain-text pseudo invocations instead of real `tool_use` blocks. (#38669, #39907, #40552) Thanks @opriz. -- Context engine/tests: add bundled-registry regression coverage for cross-chunk resolution, plugin-sdk re-exports, and concurrent chunk registration. (#40460) thanks @dsantoreis. -- Agents/embedded runner: bound compaction retry waiting and drain embedded runs during SIGUSR1 restart so session lanes recover instead of staying blocked behind compaction. (#40324) thanks @cgdusek. +- Secrets/SecretRef: reject exec SecretRef traversal ids across schema, runtime, and gateway. (#42370) Thanks @joshavant. +- Sandbox/fs bridge: pin staged writes to verified parent directories so temporary write files cannot materialize outside the allowed mount before atomic replace. Thanks @tdjackey. +- Gateway/auth: fail closed when local `gateway.auth.*` SecretRefs are configured but unavailable, instead of silently falling back to `gateway.remote.*` credentials in local mode. (#42672) Thanks @joshavant. +- Commands/config writes: enforce `configWrites` against both the originating account and the targeted account scope for `/config` and config-backed `/allowlist` edits, blocking sibling-account mutations while preserving gateway `operator.admin` flows. Thanks @tdjackey for reporting. +- Security/system.run: fail closed for approval-backed interpreter/runtime commands when OpenClaw cannot bind exactly one concrete local file operand, while extending best-effort direct-file binding to additional runtime forms. Thanks @tdjackey for reporting. +- Gateway/session reset auth: split conversation `/new` and `/reset` handling away from the admin-only `sessions.reset` control-plane RPC so write-scoped gateway callers can no longer reach the privileged reset path through `agent`. Thanks @tdjackey for reporting. +- Security/plugin runtime: stop unauthenticated plugin HTTP routes from inheriting synthetic admin gateway scopes when they call `runtime.subagent.*`, so admin-only methods like `sessions.delete` stay blocked without gateway auth. +- Security/nodes: treat the `nodes` agent tool as owner-only fallback policy so non-owner senders cannot reach paired-node approval or invoke paths through the shared tool set. +- Sandbox/sessions_spawn: restore real workspace handoff for read-only sandboxed sessions so spawned subagents mount the configured workspace at `/agent` instead of inheriting the sandbox copy. Related #40582. +- Security/external content: treat whitespace-delimited `EXTERNAL UNTRUSTED CONTENT` boundary markers like underscore-delimited variants so prompt wrappers cannot bypass marker sanitization. (#35983) Thanks @urianpaul94. +- Telegram/exec approvals: reject `/approve` commands aimed at other bots, keep deterministic approval prompts visible when tool-result delivery fails, and stop resolved exact IDs from matching other pending approvals by prefix. (#37233) Thanks @huntharo. +- Subagents/authority: persist leaf vs orchestrator control scope at spawn time and route tool plus slash-command control through shared ownership checks, so leaf sessions cannot regain orchestration privileges after restore or flat-key lookups. Thanks @tdjackey. +- ACP/ACPX plugin: bump the bundled `acpx` pin to `0.1.16` so plugin-local installs and strict version checks match the latest published CLI. (#41975) Thanks @dutifulbob. - ACP/sessions.patch: allow `spawnedBy` and `spawnDepth` lineage fields on ACP session keys so `sessions_spawn` with `runtime: "acp"` no longer fails during child-session setup. Fixes #40971. (#40995) thanks @xaeon2026. - ACP/stop reason mapping: resolve gateway chat `state: "error"` completions as ACP `end_turn` instead of `refusal` so transient backend failures are not surfaced as deliberate refusals. (#41187) thanks @pejmanjohn. - ACP/setSessionMode: propagate gateway `sessions.patch` failures back to ACP clients so rejected mode changes no longer return silent success. (#41185) thanks @pejmanjohn. -- Agents/embedded logs: add structured, sanitized lifecycle and failover observation events so overload and provider failures are easier to tail and filter. (#41336) thanks @altaywtf. -- iOS/gateway foreground recovery: reconnect immediately on foreground return after stale background sockets are torn down, so the app no longer stays disconnected until a later wake path happens. (#41384) Thanks @mbelinky. -- Cron/subagent followup: do not misclassify empty or `NO_REPLY` cron responses as interim acknowledgements that need a rerun, so deliberately silent cron jobs are no longer retried. (#41383) thanks @jackal092927. -- Auth/cooldowns: reset expired auth-profile cooldown error counters before computing the next backoff so stale on-disk counters do not re-escalate into long cooldown loops after expiry. (#41028) thanks @zerone0x. -- Gateway/node pending drain followup: keep `hasMore` true when the deferred baseline status item still needs delivery, and avoid allocating empty pending-work state for drain-only nodes with no queued work. (#41429) Thanks @mbelinky. - ACP/bridge mode: reject unsupported per-session MCP server setup and propagate rejected session-mode changes so IDE clients see explicit bridge limitations instead of silent success. (#41424) Thanks @mbelinky. - ACP/session UX: replay stored user and assistant text on `loadSession`, expose Gateway-backed session controls and metadata, and emit approximate session usage updates so IDE clients restore context more faithfully. (#41425) Thanks @mbelinky. - ACP/tool streaming: enrich `tool_call` and `tool_call_update` events with best-effort text content and file-location hints so IDE clients can follow bridge tool activity more naturally. (#41442) Thanks @mbelinky. - ACP/runtime attachments: forward normalized inbound image attachments into ACP runtime turns so ACPX sessions can preserve image prompt content on the runtime path. (#41427) Thanks @mbelinky. - ACP/regressions: add gateway RPC coverage for ACP lineage patching, ACPX runtime coverage for image prompt serialization, and an operator smoke-test procedure for live ACP spawn verification. (#41456) Thanks @mbelinky. -- Agents/billing recovery: probe single-provider billing cooldowns on the existing throttle so topping up credits can recover without a manual gateway restart. (#41422) thanks @altaywtf. - ACP/follow-up hardening: make session restore and prompt completion degrade gracefully on transcript/update failures, enforce bounded tool-location traversal, and skip non-image ACPX turns the runtime cannot serialize. (#41464) Thanks @mbelinky. -- Agents/fallback observability: add structured, sanitized model-fallback decision and auth-profile failure-state events with correlated run IDs so cooldown probes and failover paths are easier to trace in logs. (#41337) thanks @altaywtf. -- Protocol/Swift model sync: regenerate pending node work Swift bindings after the landed `node.pending.*` schema additions so generated protocol artifacts are consistent again. (#41477) Thanks @mbelinky. -- Discord/reply chunking: resolve the effective `maxLinesPerMessage` config across live reply paths and preserve `chunkMode` in the fast send path so long Discord replies no longer split unexpectedly at the default 17-line limit. (#40133) thanks @rbutera. -- Logging/probe observations: suppress structured embedded and model-fallback probe warnings on the console without hiding error or fatal output. (#41338) thanks @altaywtf. -- Agents/fallback: treat HTTP 499 responses as transient in both raw-text and structured failover paths so Anthropic-style client-closed overload responses trigger model fallback reliably. (#41468) thanks @zeroasterisk. +- ACP/sessions_spawn: implicitly stream `mode="run"` ACP spawns to parent only for eligible subagent orchestrator sessions (heartbeat `target: "last"` with a usable session-local route), restoring parent progress relays without thread binding. (#42404) Thanks @davidguttman. +- ACP/main session aliases: canonicalize `main` before ACP session lookup so restarted ACP main sessions rehydrate instead of failing closed with `Session is not ACP-enabled: main`. (#43285, fixes #25692) - Plugins/context-engine model auth: expose `runtime.modelAuth` and plugin-sdk auth helpers so plugins can resolve provider/model API keys through the normal auth pipeline. (#41090) thanks @xinhuagu. -- CLI/memory teardown: close cached memory search/index managers in the one-shot CLI shutdown path so watcher-backed memory caches no longer keep completed CLI runs alive after output finishes. (#40389) thanks @Julbarth. -- Tools/web search: treat Brave `llm-context` grounding snippets as plain strings so `web_search` no longer returns empty snippet arrays in LLM Context mode. (#41387) thanks @zheliu2. -- Telegram/exec approvals: reject `/approve` commands aimed at other bots, keep deterministic approval prompts visible when tool-result delivery fails, and stop resolved exact IDs from matching other pending approvals by prefix. (#37233) Thanks @huntharo. -- Control UI/Sessions: restore single-column session table collapse on narrow viewport or container widths by moving the responsive table override next to the base grid rule and enabling inline-size container queries. (#12175) Thanks @benjipeng. -- Telegram/final preview delivery: split active preview lifecycle from cleanup retention so missing archived preview edits avoid duplicate fallback sends without clearing the live preview or blocking later in-place finalization. (#41662) thanks @hougangdev. +- Hooks/plugin context parity followup: pass `trigger` and `channelId` through embedded `llm_input`, `agent_end`, and `llm_output` hook contexts so plugins receive the same agent metadata across hook phases. (#42362) Thanks @zhoulf1006. +- Plugins/global hook runner: harden singleton state handling so shared global hook runner reuse does not leak or corrupt runner state across executions. (#40184) Thanks @vincentkoc. +- Context engine/tests: add bundled-registry regression coverage for cross-chunk resolution, plugin-sdk re-exports, and concurrent chunk registration. (#40460) thanks @dsantoreis. +- Agents/embedded runner: bound compaction retry waiting and drain embedded runs during SIGUSR1 restart so session lanes recover instead of staying blocked behind compaction. (#40324) thanks @cgdusek. +- Agents/embedded logs: add structured, sanitized lifecycle and failover observation events so overload and provider failures are easier to tail and filter. (#41336) thanks @altaywtf. +- Agents/embedded overload logs: include the failing model and provider in error-path console output, with lifecycle regression coverage for the rendered and sanitized `consoleMessage`. (#41236) thanks @jiarung. +- Agents/fallback observability: add structured, sanitized model-fallback decision and auth-profile failure-state events with correlated run IDs so cooldown probes and failover paths are easier to trace in logs. (#41337) thanks @altaywtf. +- Logging/probe observations: suppress structured embedded and model-fallback probe warnings on the console without hiding error or fatal output. (#41338) thanks @altaywtf. +- Agents/context-engine compaction: guard thrown engine-owned overflow compaction attempts and fire compaction hooks for `ownsCompaction` engines so overflow recovery no longer crashes and plugin subscribers still observe compact runs. (#41361) thanks @davidrudduck. +- Gateway/node pending drain followup: keep `hasMore` true when the deferred baseline status item still needs delivery, and avoid allocating empty pending-work state for drain-only nodes with no queued work. (#41429) Thanks @mbelinky. +- Protocol/Swift model sync: regenerate pending node work Swift bindings after the landed `node.pending.*` schema additions so generated protocol artifacts are consistent again. (#41477) Thanks @mbelinky. +- Cron/subagent followup: do not misclassify empty or `NO_REPLY` cron responses as interim acknowledgements that need a rerun, so deliberately silent cron jobs are no longer retried. (#41383) thanks @jackal092927. - Cron/state errors: record `lastErrorReason` in cron job state and keep the gateway schema aligned with the full failover-reason set, including regression coverage for protocol conformance. (#14382) thanks @futuremind2026. -- Tools/web search: recover OpenRouter Perplexity citation extraction from `message.annotations` when chat-completions responses omit top-level citations. (#40881) Thanks @laurieluo. -- Security/external content: treat whitespace-delimited `EXTERNAL UNTRUSTED CONTENT` boundary markers like underscore-delimited variants so prompt wrappers cannot bypass marker sanitization. (#35983) Thanks @urianpaul94. -- Telegram/network env-proxy: apply configured transport policy to proxied HTTPS dispatchers as well as direct `NO_PROXY` bypasses, so resolver-scoped IPv4 fallback and network settings work consistently for env-proxied Telegram traffic. (#40740) Thanks @sircrumpet. -- Agents/memory flush: forward `memoryFlushWritePath` through `runEmbeddedPiAgent` so memory-triggered flush turns keep the append-only write guard without aborting before tool setup. Follows up on #38574. (#41761) Thanks @frankekn. +- Browser/Browserbase 429 handling: surface stable no-retry rate-limit guidance without buffering discarded HTTP 429 response bodies from remote browser services. (#40491) thanks @mvanhorn. - CI/CodeQL Swift toolchain: select Xcode 26.1 before installing Swift build tools so the CodeQL Swift job uses Swift tools 6.2 on `macos-latest`. (#41787) thanks @BunsDev. - Sandbox/subagents: pass the real configured workspace through `sessions_spawn` inheritance when a parent agent runs in a copied-workspace sandbox, so child `/agent` mounts point at the configured workspace instead of the parent sandbox copy. (#40757) Thanks @dsantoreis. -- Mattermost/plugin send actions: normalize direct `replyTo` fallback handling so threaded plugin sends trim blank IDs and reuse the correct reply target again. (#41176) Thanks @hnykda. -- MS Teams/allowlist resolution: use the General channel conversation ID as the resolved team key (with Graph GUID fallback) so Bot Framework runtime `channelData.team.id` matching works for team and team/channel allowlist entries. (#41838) Thanks @BradGroux. -- Mattermost/Markdown formatting: preserve first-line indentation when stripping bot mentions so nested list items and indented code blocks keep their structure, and render Mattermost tables natively by default instead of fenced-code fallback. (#18655) thanks @echo931. - Agents/fallback cooldown probing: cap cooldown-bypass probing to one attempt per provider per fallback run so multi-model same-provider cooldown chains can continue to cross-provider fallbacks instead of repeatedly stalling on duplicate cooldown probes. (#41711) Thanks @cgdusek. - Telegram/direct delivery: bridge direct delivery sends to internal `message:sent` hooks so internal hook listeners observe successful Telegram deliveries. (#40185) Thanks @vincentkoc. -- Plugins/global hook runner: harden singleton state handling so shared global hook runner reuse does not leak or corrupt runner state across executions. (#40184) Thanks @vincentkoc. -- Agents/fallback: recognize Poe `402 You've used up your points!` billing errors so configured model fallbacks trigger instead of surfacing the raw provider error. (#42278) Thanks @CryUshio. -- Telegram/outbound HTML sends: chunk long HTML-mode messages, preserve plain-text fallback and silent-delivery params across retries, and cut over to plain text when HTML chunk planning cannot safely preserve the full message. (#42240) thanks @obviyus. -- Agents/embedded overload logs: include the failing model and provider in error-path console output, with lifecycle regression coverage for the rendered and sanitized `consoleMessage`. (#41236) thanks @jiarung. -- Agents/failover: treat Gemini `MALFORMED_RESPONSE` stop reasons as retryable timeouts so preview-model enum drift falls back cleanly instead of crashing the run, without also reclassifying malformed function-call errors. (#42292) Thanks @jnMetaCode. -- Discord/Telegram outbound runtime config: thread runtime-resolved config through Discord and Telegram send paths so SecretRef-based credentials stay resolved during message delivery. (#42352) Thanks @joshavant. -- Secrets/SecretRef: reject exec SecretRef traversal ids across schema, runtime, and gateway. (#42370) Thanks @joshavant. -- Telegram/docs: clarify that `channels.telegram.groups` allowlists chats while `groupAllowFrom` allowlists users inside those chats, and point invalid negative chat IDs at the right config key. (#42451) Thanks @altaywtf. -- Models/Alibaba Cloud Model Studio: wire `MODELSTUDIO_API_KEY` through shared env auth, implicit provider discovery, and shell-env fallback so onboarding works outside the wizard too. (#40634) Thanks @pomelo-nwu. -- Subagents/authority: persist leaf vs orchestrator control scope at spawn time and route tool plus slash-command control through shared ownership checks, so leaf sessions cannot regain orchestration privileges after restore or flat-key lookups. Thanks @tdjackey. -- ACP/sessions_spawn: implicitly stream `mode="run"` ACP spawns to parent only for eligible subagent orchestrator sessions (heartbeat `target: "last"` with a usable session-local route), restoring parent progress relays without thread binding. (#42404) Thanks @davidguttman. -- Sessions/reset model recompute: clear stale runtime model, context-token, and system-prompt metadata before session resets recompute the replacement session, so resets pick up current defaults and explicit overrides instead of reusing old runtime model state. (#41173) thanks @PonyX-lab. -- Browser/Browserbase 429 handling: surface stable no-retry rate-limit guidance without buffering discarded HTTP 429 response bodies from remote browser services. (#40491) thanks @mvanhorn. -- Gateway/auth: allow one trusted device-token retry on shared-token mismatch with recovery hints to prevent reconnect churn during token drift. (#42507) Thanks @joshavant. -- Channels/allowlists: remove stale matcher caching so same-array allowlist edits and wildcard replacements take effect immediately, with regression coverage for in-place mutation cases. -- Gateway/auth: fail closed when local `gateway.auth.*` SecretRefs are configured but unavailable, instead of silently falling back to `gateway.remote.*` credentials in local mode. (#42672) Thanks @joshavant. -- Sandbox/fs bridge: pin staged writes to verified parent directories so temporary write files cannot materialize outside the allowed mount before atomic replace. Thanks @tdjackey. -- Commands/config writes: enforce `configWrites` against both the originating account and the targeted account scope for `/config` and config-backed `/allowlist` edits, blocking sibling-account mutations while preserving gateway `operator.admin` flows. Thanks @tdjackey for reporting. -- Security/system.run: fail closed for approval-backed interpreter/runtime commands when OpenClaw cannot bind exactly one concrete local file operand, while extending best-effort direct-file binding to additional runtime forms. Thanks @tdjackey for reporting. -- Gateway/session reset auth: split conversation `/new` and `/reset` handling away from the admin-only `sessions.reset` control-plane RPC so write-scoped gateway callers can no longer reach the privileged reset path through `agent`. Thanks @tdjackey for reporting. -- Telegram/final preview delivery followup: keep ambiguous missing-`message_id` finals only when a preview was already visible, while first-preview/no-id cases still fall back so Telegram users do not lose the final reply. (#41932) thanks @hougangdev. -- Agents/Azure OpenAI Responses: include the `azure-openai` provider in the Responses API store override so Azure OpenAI multi-turn cron jobs and embedded agent runs no longer fail with HTTP 400 "store is set to false". (#42934, fixes #42800) Thanks @ademczuk. +- Dependencies: refresh workspace dependencies except the pinned Carbon package, and harden ACP session-config writes against non-string SDK values so newer ACP clients fail fast instead of tripping type/runtime mismatches. +- Telegram/polling restarts: clear bounded cleanup timeout handles after `runner.stop()` and `bot.stop()` settle so stall recovery no longer leaves stray 15-second timers behind on clean shutdown. (#43188) thanks @kyohwang. +- Gateway/config errors: surface up to three validation issues in top-level `config.set`, `config.patch`, and `config.apply` error messages while preserving structured issue details. (#42664) Thanks @huntharo. +- Hooks/plugin context parity followup: pass `trigger` and `channelId` through embedded `llm_input`, `agent_end`, and `llm_output` hook contexts so plugins receive the same agent metadata across hook phases. (#42362) Thanks @zhoulf1006. +- Status/context windows: normalize provider-qualified override cache keys so `/status` resolves the active provider's configured context window even when `models.providers` keys use mixed case or surrounding whitespace. (#36389) Thanks @haoruilee. +- ACP/main session aliases: canonicalize `main` before ACP session lookup so restarted ACP main sessions rehydrate instead of failing closed with `Session is not ACP-enabled: main`. (#43285, fixes #25692) +- Agents/embedded runner: recover canonical allowlisted tool names from malformed `toolCallId` and malformed non-blank tool-name variants before dispatch, while failing closed on ambiguous matches. (#34485) thanks @yuweuii. +- Agents/failover: classify ZenMux quota-refresh `402` responses as `rate_limit` so model fallback retries continue instead of stopping on a temporary subscription window. (#43917) thanks @bwjoke. +- Agents/failover: classify HTTP 422 malformed-request responses as `format` and recognize OpenRouter "requires more credits" billing errors so provider fallback triggers instead of surfacing raw errors. (#43823) thanks @jnMetaCode. +- Memory/QMD Windows: fail closed when `qmd.cmd` or `mcporter.cmd` wrappers cannot be resolved to a direct entrypoint, so memory search no longer falls back to shell execution on Windows. +- macOS/remote gateway: stop PortGuardian from killing Docker Desktop and other external listeners on the gateway port in remote mode, so containerized and tunneled gateway setups no longer lose their port-forward owner on app startup. (#6755) Thanks @teslamint. +- Feishu/streaming recovery: clear stale `streamingStartPromise` when card creation fails (HTTP 400) so subsequent messages can retry streaming instead of silently dropping all future replies. Fixes #43322. +- Exec/env sandbox: block JVM agent injection (`JAVA_TOOL_OPTIONS`, `_JAVA_OPTIONS`, `JDK_JAVA_OPTIONS`), Python breakpoint hijack (`PYTHONBREAKPOINT`), and .NET startup hooks (`DOTNET_STARTUP_HOOKS`) from the host exec environment. (#49025) + +### Security + +- Gateway/WebSocket: enforce browser origin validation for all browser-originated connections regardless of whether proxy headers are present, closing a cross-site WebSocket hijacking path in `trusted-proxy` mode that could grant untrusted origins `operator.admin` access. (GHSA-5wcw-8jjv-m286) + +### Breaking + +- Cron/doctor: tighten isolated cron delivery so cron jobs can no longer notify through ad hoc agent sends or fallback main-session summaries, and add `openclaw doctor --fix` migration for legacy cron storage and legacy notify/webhook delivery metadata. (#40998) Thanks @mbelinky. ## 2026.3.8 @@ -157,12 +560,21 @@ Docs: https://docs.openclaw.ai - Cron/owner-only tools: pass trusted isolated cron runs into the embedded agent with owner context so `cron`/`gateway` tooling remains available after the owner-auth hardening narrowed direct-message ownership inference. - Browser/SSRF: block private-network intermediate redirect hops in strict browser navigation flows and fail closed when remote tab-open paths cannot inspect redirect chains. Thanks @zpbrent. - MS Teams/authz: keep `groupPolicy: "allowlist"` enforcing sender allowlists even when a team/channel route allowlist is configured, so route matches no longer widen group access to every sender in that route. Thanks @zpbrent. +- Security/Gateway: block `device.token.rotate` from minting operator scopes broader than the caller session already holds, closing the critical paired-device token privilege escalation reported as GHSA-4jpw-hj22-2xmc. - Security/system.run: bind approved `bun` and `deno run` script operands to on-disk file snapshots so post-approval script rewrites are denied before execution. - Skills/download installs: pin the validated per-skill tools root before writing downloaded archives, so rebinding the lexical tools path cannot redirect download writes outside the intended tools directory. Thanks @tdjackey. - Control UI/Debug: replace the Manual RPC free-text method field with a sorted dropdown sourced from gateway-advertised methods, and stack the form vertically for narrower layouts. (#14967) thanks @rixau. - Auth/profile resolution: log debug details when auto-discovered auth profiles fail during provider API-key resolution, so `--debug` output surfaces the real refresh/keychain/credential-store failure instead of only the generic missing-key message. (#41271) thanks @he-yufeng. - ACP/cancel scoping: scope `chat.abort` and shared-session ACP event routing by `runId` so one session cannot cancel or consume another session's run when they share the same gateway session key. (#41331) Thanks @pejmanjohn. - SecretRef/models: harden custom/provider secret persistence and reuse across models.json snapshots, merge behavior, runtime headers, and secret audits. (#42554) Thanks @joshavant. +- macOS/browser proxy: serialize non-GET browser proxy request bodies through `AnyCodable.foundationValue` so nested JSON bodies no longer crash the macOS app with `Invalid type in JSON write (__SwiftValue)`. (#43069) Thanks @Effet. +- CLI/skills tables: keep terminal table borders aligned for wide graphemes, use full reported terminal width, and switch a few ambiguous skill icons to Terminal-safe emoji so `openclaw skills` renders more consistently in Terminal.app and iTerm. Thanks @vincentkoc. +- Memory/Gemini: normalize returned Gemini embeddings across direct query, direct batch, and async batch paths so memory search uses consistent vector handling for Gemini too. (#43409) Thanks @gumadeiras. +- Agents/failover: recognize additional serialized network errno strings plus `EHOSTDOWN` and `EPIPE` structured codes so transient transport failures trigger timeout failover more reliably. (#42830) Thanks @jnMetaCode. +- Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in `/models` button validation. (#40105) Thanks @avirweb. +- Agents/embedded runner: carry provider-observed overflow token counts into compaction so overflow retries and diagnostics use the rejected live prompt size instead of only transcript estimates. (#40357) thanks @rabsef-bicrym. +- Agents/compaction transcript updates: emit a transcript-update event immediately after successful embedded compaction so downstream listeners observe the post-compact transcript without waiting for a later write. (#25558) thanks @rodrigouroz. +- Agents/sessions_spawn: use the target agent workspace for cross-agent spawned runs instead of inheriting the caller workspace, so child sessions load the correct workspace-scoped instructions and persona files. (#40176) Thanks @moshehbenavraham. ## 2026.3.7 @@ -197,10 +609,6 @@ Docs: https://docs.openclaw.ai - Google/Gemini 3.1 Flash-Lite: add first-class `google/gemini-3.1-flash-lite-preview` support across model-id normalization, default aliases, media-understanding image lookups, Google Gemini CLI forward-compat fallback, and docs. - Agents/compaction model override: allow `agents.defaults.compaction.model` to route compaction summarization through a different model than the main session, and document the override across config help/reference surfaces. (#38753) thanks @starbuck100. -### Breaking - -- **BREAKING:** Gateway auth now requires explicit `gateway.auth.mode` when both `gateway.auth.token` and `gateway.auth.password` are configured (including SecretRefs). Set `gateway.auth.mode` to `token` or `password` before upgrade to avoid startup/pairing/TUI failures. (#35094) Thanks @joshavant. - ### Fixes - Models/MiniMax: stop advertising removed `MiniMax-M2.5-Lightning` in built-in provider catalogs, onboarding metadata, and docs; keep the supported fast-tier model as `MiniMax-M2.5-highspeed`. @@ -271,6 +679,7 @@ Docs: https://docs.openclaw.ai - Control UI/markdown fallback regression coverage: add explicit regression assertions for parser-error fallback behavior so malformed markdown no longer risks reintroducing hard-crash rendering paths in future markdown/parser upgrades. (#36445) Thanks @BinHPdev. - Web UI/config form: treat `additionalProperties: true` object schemas as editable map entries instead of unsupported fields so Accounts-style maps stay editable in form mode. (#35380, supersedes #32072) Thanks @stakeswky and @liuxiaopai-ai. - Feishu/streaming card delivery synthesis: unify snapshot and delta streaming merge semantics, apply overlap-aware final merge, suppress duplicate final text delivery (including text+media final packets), prefer topic-thread `message.reply` routing when a reply target exists, and tune card print cadence to avoid duplicate incremental rendering. (from #33245, #32896, #33840) Thanks @rexl2018, @kcinzgg, and @aerelune. +- macOS/tray menu: keep injected sessions and device rows below the controls section so toggles and action buttons stay visible even when many sessions are active. (#38079) Thanks @bernesto. - Feishu/group mention detection: carry startup-probed bot display names through monitor dispatch so `requireMention` checks compare against current bot identity instead of stale config names, fixing missed `@bot` handling in groups while preserving multi-bot false-positive guards. (#36317, #34271) Thanks @liuxiaopai-ai. - Security/dependency audit: patch transitive Hono vulnerabilities by pinning `hono` to `4.12.5` and `@hono/node-server` to `1.19.10` in production resolution paths. Thanks @shakkernerd. - Security/dependency audit: bump `tar` to `7.5.10` (from `7.5.9`) to address the high-severity hardlink path traversal advisory (`GHSA-qffp-2rhf-9h96`). Thanks @shakkernerd. @@ -523,6 +932,11 @@ Docs: https://docs.openclaw.ai - Browser/config schema: accept `browser.profiles.*.driver: "openclaw"` while preserving legacy `"clawd"` compatibility in validated config. (#39374; based on #35621) Thanks @gambletan and @ingyukoh. - Memory flush/bootstrap file protection: restrict memory-flush runs to append-only `read`/`write` tools and route host-side memory appends through root-enforced safe file handles so flush turns cannot overwrite bootstrap files via `exec` or unsafe raw rewrites. (#38574) Thanks @frankekn. - Mattermost/DM media uploads: resolve bare 26-character Mattermost IDs user-first for direct messages so media sends no longer fail with `403 Forbidden` when targets are configured as unprefixed user IDs. (#29925) Thanks @teconomix. +- Voice-call/OpenAI TTS config parity: add missing `speed`, `instructions`, and `baseUrl` fields to the OpenAI TTS config schema and gate `instructions` to supported models so voice-call overrides validate and route cleanly through core TTS. (#39226) Thanks @ademczuk. + +### Breaking + +- **BREAKING:** Gateway auth now requires explicit `gateway.auth.mode` when both `gateway.auth.token` and `gateway.auth.password` are configured (including SecretRefs). Set `gateway.auth.mode` to `token` or `password` before upgrade to avoid startup/pairing/TUI failures. (#35094) Thanks @joshavant. ## 2026.3.2 @@ -552,13 +966,6 @@ Docs: https://docs.openclaw.ai - Gateway/input_image MIME validation: sniff uploaded image bytes before MIME allowlist enforcement again so declared image types cannot mask concrete non-image payloads, while keeping HEIC/HEIF normalization behavior scoped to actual HEIC inputs. Thanks @vincentkoc. - Zalo Personal plugin (`@openclaw/zalouser`): keep canonical DM routing while preserving legacy DM session continuity on upgrade, and preserve provider-native `g-`/`u-` target ids in outbound send and directory flows so #33992 lands without breaking existing sessions or stored targets. (#33992) Thanks @darkamenosa. -### Breaking - -- **BREAKING:** Onboarding now defaults `tools.profile` to `messaging` for new local installs (interactive + non-interactive). New setups no longer start with broad coding/system tools unless explicitly configured. -- **BREAKING:** ACP dispatch now defaults to enabled unless explicitly disabled (`acp.dispatch.enabled=false`). If you need to pause ACP turn routing while keeping `/acp` controls, set `acp.dispatch.enabled=false`. Docs: https://docs.openclaw.ai/tools/acp-agents -- **BREAKING:** Plugin SDK removed `api.registerHttpHandler(...)`. Plugins must register explicit HTTP routes via `api.registerHttpRoute({ path, auth, match, handler })`, and dynamic webhook lifecycles should use `registerPluginHttpRoute(...)`. -- **BREAKING:** Zalo Personal plugin (`@openclaw/zalouser`) no longer depends on external `zca`-compatible CLI binaries (`openzca`, `zca-cli`) for runtime send/listen/login; operators should use `openclaw channels login --channel zalouser` after upgrade to refresh sessions in the new JS-native path. - ### Fixes - Feishu/Outbound render mode: respect Feishu account `renderMode` in outbound sends so card mode (and auto-detected markdown tables/code blocks) uses markdown card delivery instead of always sending plain text. (#31562) Thanks @arkyu2077. @@ -745,6 +1152,13 @@ Docs: https://docs.openclaw.ai - Tests/Subagent announce: set `OPENCLAW_TEST_FAST=1` before importing `subagent-announce` format suites so module-level fast-mode constants are captured deterministically on Windows CI, preventing timeout flakes in nested completion announce coverage. (#31370) Thanks @zwffff. - Control UI/markdown recursion fallback: catch markdown parser failures and safely render escaped plain-text fallback instead of crashing the Control UI on pathological markdown history payloads. (#36445, fixes #36213) Thanks @BinHPdev. +### Breaking + +- **BREAKING:** Onboarding now defaults `tools.profile` to `messaging` for new local installs (interactive + non-interactive). New setups no longer start with broad coding/system tools unless explicitly configured. +- **BREAKING:** ACP dispatch now defaults to enabled unless explicitly disabled (`acp.dispatch.enabled=false`). If you need to pause ACP turn routing while keeping `/acp` controls, set `acp.dispatch.enabled=false`. Docs: https://docs.openclaw.ai/tools/acp-agents +- **BREAKING:** Plugin SDK removed `api.registerHttpHandler(...)`. Plugins must register explicit HTTP routes via `api.registerHttpRoute({ path, auth, match, handler })`, and dynamic webhook lifecycles should use `registerPluginHttpRoute(...)`. +- **BREAKING:** Zalo Personal plugin (`@openclaw/zalouser`) no longer depends on external `zca`-compatible CLI binaries (`openzca`, `zca-cli`) for runtime send/listen/login; operators should use `openclaw channels login --channel zalouser` after upgrade to refresh sessions in the new JS-native path. + ## 2026.3.1 ### Changes @@ -772,11 +1186,6 @@ Docs: https://docs.openclaw.ai - OpenAI/WebSocket warm-up: add optional OpenAI Responses WebSocket warm-up (`response.create` with `generate:false`), enable it by default for `openai/*`, and expose `params.openaiWsWarmup` for per-model enable/disable control. - Agents/Subagents runtime events: replace ad-hoc subagent completion system-message handoff with typed internal completion events (`task_completion`) that are rendered consistently across direct and queued announce paths, with gateway/CLI plumbing for structured `internalEvents`. -### Breaking - -- **BREAKING:** Node exec approval payloads now require `systemRunPlan`. `host=node` approval requests without that plan are rejected. -- **BREAKING:** Node `system.run` execution now pins path-token commands to the canonical executable path (`realpath`) in both allowlist and approval execution flows. Integrations/tests that asserted token-form argv (for example `tr`) must now accept canonical paths (for example `/usr/bin/tr`). - ### Fixes - Feishu/Streaming card text fidelity: merge throttled/fragmented partial updates without dropping content and avoid newline injection when stitching chunk-style deltas so card-stream output matches final reply text. (#29616) Thanks @HaoHuaqing. @@ -871,7 +1280,12 @@ Docs: https://docs.openclaw.ai - Signal/Sync message null-handling: treat `syncMessage` presence (including `null`) as sync envelope traffic so replayed sentTranscript payloads cannot bypass loop guards after daemon restart. Landed from contributor PR #31138 by @Sid-Qin. Thanks @Sid-Qin. - Infra/fs-safe: sanitize directory-read failures so raw `EISDIR` text never leaks to messaging surfaces, with regression tests for both root-scoped and direct safe reads. Landed from contributor PR #31205 by @polooooo. Thanks @polooooo. -## Unreleased +### Breaking + +- **BREAKING:** Node exec approval payloads now require `systemRunPlan`. `host=node` approval requests without that plan are rejected. +- **BREAKING:** Node `system.run` execution now pins path-token commands to the canonical executable path (`realpath`) in both allowlist and approval execution flows. Integrations/tests that asserted token-form argv (for example `tr`) must now accept canonical paths (for example `/usr/bin/tr`). + +## 2026.2.27 ### Changes @@ -1030,6 +1444,7 @@ Docs: https://docs.openclaw.ai - Browser/Navigate: resolve the correct `targetId` in navigate responses after renderer swaps. (#25326) Thanks @stone-jin and @vincentkoc. - FS/Sandbox workspace boundaries: add a dedicated `outside-workspace` safe-open error code for root-escape checks, and propagate specific outside-workspace messages across edit/browser/media consumers instead of generic not-found/invalid-path fallbacks. (#29715) Thanks @YuzuruS. - Diagnostics/Stuck session signal: add configurable stuck-session warning threshold via `diagnostics.stuckSessionWarnMs` (default 120000ms) to reduce false-positive warnings on long multi-tool turns. (#31032) +- Agents/error classification: check billing errors before context overflow heuristics in the agent runner catch block so spend-limit and quota errors show the billing-specific message instead of being misclassified as "Context overflow: prompt too large". (#40409) Thanks @ademczuk. ## 2026.2.26 @@ -1145,10 +1560,6 @@ Docs: https://docs.openclaw.ai - Agents/Config: remind agents to call `config.schema` before config edits or config-field questions to avoid guessing. Thanks @thewilloftheshadow. - Dependencies: update workspace dependency pins and lockfile (Bedrock SDK `3.998.0`, `@mariozechner/pi-*` `0.55.1`, TypeScript native preview `7.0.0-dev.20260225.1`) while keeping `@buape/carbon` pinned. -### Breaking - -- **BREAKING:** Heartbeat direct/DM delivery default is now `allow` again. To keep DM-blocked behavior from `2026.2.24`, set `agents.defaults.heartbeat.directPolicy: "block"` (or per-agent override). - ### Fixes - Slack/Identity: thread agent outbound identity (`chat:write.customize` overrides) through the channel reply delivery path so per-agent username, icon URL, and icon emoji are applied to all Slack replies including media messages. (#27134) Thanks @hou-rong. @@ -1212,6 +1623,10 @@ Docs: https://docs.openclaw.ai - Tests/Low-memory stability: disable Vitest `vmForks` by default on low-memory local hosts (`<64 GiB`), keep low-profile extension lane parallelism at 4 workers, and align cron isolated-agent tests with `setSessionRuntimeModel` usage to avoid deterministic suite failures. (#26324) Thanks @ngutman. - Feishu/WebSocket proxy: pass a proxy agent to Feishu WS clients from standard proxy environment variables and include plugin-local runtime dependency wiring so websocket mode works in proxy-constrained installs. (#26397) Thanks @colin719. +### Breaking + +- **BREAKING:** Heartbeat direct/DM delivery default is now `allow` again. To keep DM-blocked behavior from `2026.2.24`, set `agents.defaults.heartbeat.directPolicy: "block"` (or per-agent override). + ## 2026.2.24 ### Changes @@ -1222,11 +1637,6 @@ Docs: https://docs.openclaw.ai - Security/Audit: add `security.trust_model.multi_user_heuristic` to flag likely shared-user ingress and clarify the personal-assistant trust model, with hardening guidance for intentional multi-user setups (`sandbox.mode="all"`, workspace-scoped FS, reduced tool surface, no personal/private identities on shared runtimes). - Dependencies: refresh key runtime and tooling packages across the workspace (Bedrock SDK, pi runtime stack, OpenAI, Google auth, and oxlint/oxfmt), while intentionally keeping `@buape/carbon` pinned. -### Breaking - -- **BREAKING:** Heartbeat delivery now blocks direct/DM targets when destination parsing identifies a direct chat (for example `user:`, Telegram user chat IDs, or WhatsApp direct numbers/JIDs). Heartbeat runs still execute, but direct-message delivery is skipped and only non-DM destinations (for example channel/group targets) can receive outbound heartbeat messages. -- **BREAKING:** Security/Sandbox: block Docker `network: "container:"` namespace-join mode by default for sandbox and sandbox-browser containers. To keep that behavior intentionally, set `agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true` (break-glass). Thanks @tdjackey for reporting. - ### Fixes - Routing/Session isolation: harden followup routing so explicit cross-channel origin replies never fall back to the active dispatcher on route failure, preserve queued overflow summary routing metadata (`channel`/`to`/`thread`) across followup drain, and prefer originating channel context over internal provider tags for embedded followup runs. This prevents webchat/control-ui context from hijacking Discord-targeted replies in shared sessions. (#25864) Thanks @Gamedesigner. @@ -1306,6 +1716,11 @@ Docs: https://docs.openclaw.ai - Agents/Compaction: harden summarization prompts to preserve opaque identifiers verbatim (UUIDs, IDs, tokens, host/IP/port, URLs), reducing post-compaction identifier drift and hallucinated identifier reconstruction. - Security/Sandbox: canonicalize bind-mount source paths via existing-ancestor realpath so symlink-parent + non-existent-leaf paths cannot bypass allowed-source-roots or blocked-path checks. Thanks @tdjackey. +### Breaking + +- **BREAKING:** Heartbeat delivery now blocks direct/DM targets when destination parsing identifies a direct chat (for example `user:`, Telegram user chat IDs, or WhatsApp direct numbers/JIDs). Heartbeat runs still execute, but direct-message delivery is skipped and only non-DM destinations (for example channel/group targets) can receive outbound heartbeat messages. +- **BREAKING:** Security/Sandbox: block Docker `network: "container:"` namespace-join mode by default for sandbox and sandbox-browser containers. To keep that behavior intentionally, set `agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true` (break-glass). Thanks @tdjackey for reporting. + ## 2026.2.23 ### Changes @@ -1320,10 +1735,6 @@ Docs: https://docs.openclaw.ai - Agents/Config: support per-agent `params` overrides merged on top of model defaults (including `cacheRetention`) so mixed-traffic agents can tune cache behavior independently. (#17470, #17112) Thanks @rrenamed. - Agents/Bootstrap: cache bootstrap file snapshots per session key and clear them on session reset/delete, reducing prompt-cache invalidations from in-session `AGENTS.md`/`MEMORY.md` writes. (#22220) Thanks @anisoptera. -### Breaking - -- **BREAKING:** browser SSRF policy now defaults to trusted-network mode (`browser.ssrfPolicy.dangerouslyAllowPrivateNetwork=true` when unset), and canonical config uses `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` instead of `browser.ssrfPolicy.allowPrivateNetwork`. `openclaw doctor --fix` migrates the legacy key automatically. - ### Fixes - Security/Config: redact sensitive-looking dynamic catchall keys in `config.get` snapshots (for example `env.*` and `skills.entries.*.env.*`) and preserve round-trip restore behavior for those redacted sentinels. Thanks @merc1305. @@ -1369,6 +1780,10 @@ Docs: https://docs.openclaw.ai - Skills/Python: harden skill script packaging and validation edge cases (self-including `.skill` outputs, CRLF frontmatter parsing, strict `--days` validation, and safer image file loading), with expanded Python regression coverage. Thanks @vincentkoc. - Skills/Python: add CI + pre-commit linting (`ruff`) and pytest discovery coverage for Python scripts/tests under `skills/`, including package test execution from repo root. Thanks @vincentkoc. +### Breaking + +- **BREAKING:** browser SSRF policy now defaults to trusted-network mode (`browser.ssrfPolicy.dangerouslyAllowPrivateNetwork=true` when unset), and canonical config uses `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` instead of `browser.ssrfPolicy.allowPrivateNetwork`. `openclaw doctor --fix` migrates the legacy key automatically. + ## 2026.2.22 ### Changes @@ -1393,14 +1808,6 @@ Docs: https://docs.openclaw.ai - Skills: remove bundled `food-order` skill from this repo; manage/install it from ClawHub instead. - Docs/Subagents: make thread-bound session guidance channel-first instead of Discord-specific, and list thread-supporting channels explicitly. (#23589) Thanks @osolmaz. -### Breaking - -- **BREAKING:** removed Google Antigravity provider support and the bundled `google-antigravity-auth` plugin. Existing `google-antigravity/*` model/profile configs no longer work; migrate to `google-gemini-cli` or other supported providers. -- **BREAKING:** tool-failure replies now hide raw error details by default. OpenClaw still sends a failure summary, but detailed error suffixes (for example provider/runtime messages and local path fragments) now require `/verbose on` or `/verbose full`. -- **BREAKING:** CLI local onboarding now sets `session.dmScope` to `per-channel-peer` by default for new/implicit DM scope configuration. If you depend on shared DM continuity across senders, explicitly set `session.dmScope` to `main`. (#23468) Thanks @bmendonca3. -- **BREAKING:** unify channel preview-streaming config to `channels..streaming` with enum values `off | partial | block | progress`, and move Slack native stream toggle to `channels.slack.nativeStreaming`. Legacy keys (`streamMode`, Slack boolean `streaming`) are still read and migrated by `openclaw doctor --fix`, but canonical saved config/docs now use the unified names. -- **BREAKING:** remove legacy Gateway device-auth signature `v1`. Device-auth clients must now sign `v2` payloads with the per-connection `connect.challenge` nonce and send `device.nonce`; nonce-less connects are rejected. - ### Fixes - Sessions/Resilience: ignore invalid persisted `sessionFile` metadata and fall back to the derived safe transcript path instead of aborting session resolution for handlers and tooling. (#16061) Thanks @haoyifan and @vincentkoc. @@ -1627,6 +2034,14 @@ Docs: https://docs.openclaw.ai - Gateway/Daemon: verify gateway health after daemon restart. - Agents/UI text: stop rewriting normal assistant billing/payment language outside explicit error contexts. (#17834) Thanks @niceysam. +### Breaking + +- **BREAKING:** removed Google Antigravity provider support and the bundled `google-antigravity-auth` plugin. Existing `google-antigravity/*` model/profile configs no longer work; migrate to `google-gemini-cli` or other supported providers. +- **BREAKING:** tool-failure replies now hide raw error details by default. OpenClaw still sends a failure summary, but detailed error suffixes (for example provider/runtime messages and local path fragments) now require `/verbose on` or `/verbose full`. +- **BREAKING:** CLI local onboarding now sets `session.dmScope` to `per-channel-peer` by default for new/implicit DM scope configuration. If you depend on shared DM continuity across senders, explicitly set `session.dmScope` to `main`. (#23468) Thanks @bmendonca3. +- **BREAKING:** unify channel preview-streaming config to `channels..streaming` with enum values `off | partial | block | progress`, and move Slack native stream toggle to `channels.slack.nativeStreaming`. Legacy keys (`streamMode`, Slack boolean `streaming`) are still read and migrated by `openclaw doctor --fix`, but canonical saved config/docs now use the unified names. +- **BREAKING:** remove legacy Gateway device-auth signature `v1`. Device-auth clients must now sign `v2` payloads with the per-connection `connect.challenge` nonce and send `device.nonce`; nonce-less connects are rejected. + ## 2026.2.21 ### Changes @@ -2275,10 +2690,6 @@ Docs: https://docs.openclaw.ai - Onboarding/Providers: add first-class Hugging Face Inference provider support (provider wiring, onboarding auth choice/API key flow, and default-model selection), and preserve Hugging Face auth intent in auth-choice remapping (`tokenProvider=huggingface` with `authChoice=apiKey`) while skipping env-override prompts when an explicit token is provided. (#13472) Thanks @Josephrp. - Onboarding/Providers: add `minimax-api-key-cn` auth choice for the MiniMax China API endpoint. (#15191) Thanks @liuy. -### Breaking - -- Config/State: removed legacy `.moltbot` auto-detection/migration and `moltbot.json` config candidates. If you still have state/config under `~/.moltbot`, move it to `~/.openclaw` (recommended) or set `OPENCLAW_STATE_DIR` / `OPENCLAW_CONFIG_PATH` explicitly. - ### Fixes - Gateway/Auth: add trusted-proxy mode hardening follow-ups by keeping `OPENCLAW_GATEWAY_*` env compatibility, auto-normalizing invalid setup combinations in interactive `gateway configure` (trusted-proxy forces `bind=lan` and disables Tailscale serve/funnel), and suppressing shared-secret/rate-limit audit findings that do not apply to trusted-proxy deployments. (#15940) Thanks @nickytonline. @@ -2381,6 +2792,10 @@ Docs: https://docs.openclaw.ai - Docs/Mermaid: remove hardcoded Mermaid init theme blocks from four docs diagrams so dark mode inherits readable theme defaults. (#15157) Thanks @heytulsiprasad. - Security/Pairing: generate 256-bit base64url device and node pairing tokens and use byte-safe constant-time verification to avoid token-compare edge-case failures. (#16535) Thanks @FaizanKolega, @gumadeiras. +### Breaking + +- Config/State: removed legacy `.moltbot` auto-detection/migration and `moltbot.json` config candidates. If you still have state/config under `~/.moltbot`, move it to `~/.openclaw` (recommended) or set `OPENCLAW_STATE_DIR` / `OPENCLAW_CONFIG_PATH` explicitly. + ## 2026.2.12 ### Changes @@ -2392,10 +2807,6 @@ Docs: https://docs.openclaw.ai - Discord: add role-based allowlists and role-based agent routing. (#10650) Thanks @Minidoracat. - Config: avoid redacting `maxTokens`-like fields during config snapshot redaction, preventing round-trip validation failures in `/config`. (#14006) Thanks @constansino. -### Breaking - -- Hooks: `POST /hooks/agent` now rejects payload `sessionKey` overrides by default. To keep fixed hook context, set `hooks.defaultSessionKey` (recommended with `hooks.allowedSessionKeyPrefixes: ["hook:"]`). If you need legacy behavior, explicitly set `hooks.allowRequestSessionKey: true`. Thanks @alpernae for reporting. - ### Fixes - Gateway/OpenResponses: harden URL-based `input_file`/`input_image` handling with explicit SSRF deny policy, hostname allowlists (`files.urlAllowlist` / `images.urlAllowlist`), per-request URL input caps (`maxUrlParts`), blocked-fetch audit logging, and regression coverage/docs updates. @@ -2478,6 +2889,10 @@ Docs: https://docs.openclaw.ai - Tests: update thread ID handling in Slack message collection tests. (#14108) Thanks @swizzmagik. - Update/Daemon: fix post-update restart compatibility by generating `dist/cli/daemon-cli.js` with alias-aware exports from hashed daemon bundles, preventing `registerDaemonCli` import failures during `openclaw update`. +### Breaking + +- Hooks: `POST /hooks/agent` now rejects payload `sessionKey` overrides by default. To keep fixed hook context, set `hooks.defaultSessionKey` (recommended with `hooks.allowedSessionKeyPrefixes: ["hook:"]`). If you need legacy behavior, explicitly set `hooks.allowRequestSessionKey: true`. Thanks @alpernae for reporting. + ## 2026.2.9 ### Added @@ -2567,6 +2982,12 @@ Docs: https://docs.openclaw.ai ## 2026.2.6 +### Added + +- Cron: run history deep-links to session chat from the dashboard. (#10776) Thanks @tyler6204. +- Cron: per-run session keys in run log entries and default labels for cron sessions. (#10776) Thanks @tyler6204. +- Cron: legacy payload field compatibility (`deliver`, `channel`, `to`, `bestEffortDeliver`) in schema. (#10776) Thanks @tyler6204. + ### Changes - Cron: default `wakeMode` is now `"now"` for new jobs (was `"next-heartbeat"`). (#10776) Thanks @tyler6204. @@ -2582,12 +3003,6 @@ Docs: https://docs.openclaw.ai - CI: optimize pipeline throughput (macOS consolidation, Windows perf, workflow concurrency). (#10784) Thanks @mcaxtr. - Agents: bump pi-mono to 0.52.7; add embedded forward-compat fallback for Opus 4.6 model ids. -### Added - -- Cron: run history deep-links to session chat from the dashboard. (#10776) Thanks @tyler6204. -- Cron: per-run session keys in run log entries and default labels for cron sessions. (#10776) Thanks @tyler6204. -- Cron: legacy payload field compatibility (`deliver`, `channel`, `to`, `bestEffortDeliver`) in schema. (#10776) Thanks @tyler6204. - ### Fixes - TTS: add missing OpenAI voices (ballad, cedar, juniper, marin, verse) to the allowlist so they are recognized instead of silently falling back to Edge TTS. (#2393) @@ -2886,10 +3301,6 @@ Docs: https://docs.openclaw.ai - Docs: keep docs header sticky so navbar stays visible while scrolling. (#2445) Thanks @chenyuan99. - Docs: update exe.dev install instructions. (#https://github.com/openclaw/openclaw/pull/3047) Thanks @zackerthescar. -### Breaking - -- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed). - ### Fixes - Skills: update session-logs paths to use ~/.openclaw. (#4502) Thanks @bonald. @@ -2942,6 +3353,10 @@ Docs: https://docs.openclaw.ai - Gateway: treat loopback + non-local Host connections as remote unless trusted proxy headers are present. - Onboarding: remove unsupported gateway auth "off" choice from onboarding/configure flows and CLI flags. +### Breaking + +- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed). + ## 2026.1.24-3 ### Fixes @@ -3085,7 +3500,7 @@ Docs: https://docs.openclaw.ai - Agents: add CLI log hint to "agent failed before reply" messages. (#1550) Thanks @sweepies. - Agents: warn and ignore tool allowlists that only reference unknown or unloaded plugin tools. (#1566) - Agents: treat plugin-only tool allowlists as opt-ins; keep core tools enabled. (#1467) -- Agents: honor enqueue overrides for embedded runs to avoid queue deadlocks in tests. (commit 084002998) +- Agents: honor enqueue overrides for embedded runs to avoid queue deadlocks in tests. (#45459) Thanks @LyttonFeng and @vincentkoc. - Slack: honor open groupPolicy for unlisted channels in message + slash gating. (#1563) Thanks @itsjaydesu. - Discord: limit autoThread mention bypass to bot-owned threads; keep ack reactions mention-gated. (#1511) Thanks @pvoo. - Discord: retry rate-limited allowlist resolution + command deploy to avoid gateway crashes. (commit f70ac0c7c) @@ -3173,11 +3588,6 @@ Docs: https://docs.openclaw.ai - Docs: add /model allowlist troubleshooting note. (#1405) - Docs: add per-message Gmail search example for gog. (#1220) Thanks @mbelinky. -### Breaking - -- **BREAKING:** Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set `gateway.controlUi.allowInsecureAuth: true` to allow token-only auth. https://docs.openclaw.ai/web/control-ui#insecure-http -- **BREAKING:** Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert. - ### Fixes - Nodes/macOS: prompt on allowlist miss for node exec approvals, persist allowlist decisions, and flatten node invoke errors. (#1394) Thanks @ngutman. @@ -3200,6 +3610,11 @@ Docs: https://docs.openclaw.ai - macOS: default distribution packaging to universal binaries. (#1396) Thanks @JustYannicc. - Embedded runner: forward sender identity into attempt execution so Feishu doc auto-grant receives requester context again. (#32915) Thanks @cszhouwei. +### Breaking + +- **BREAKING:** Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set `gateway.controlUi.allowInsecureAuth: true` to allow token-only auth. https://docs.openclaw.ai/web/control-ui#insecure-http +- **BREAKING:** Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert. + ## 2026.1.20 ### Changes @@ -3281,10 +3696,6 @@ Docs: https://docs.openclaw.ai - macOS: stop syncing Peekaboo in postinstall. - Swabble: use the tagged Commander Swift package release. -### Breaking - -- **BREAKING:** Reject invalid/unknown config entries and refuse to start the gateway for safety. Run `openclaw doctor --fix` to repair, then update plugins (`openclaw plugins update`) if you use any. - ### Fixes - Discovery: shorten Bonjour DNS-SD service type to `_moltbot-gw._tcp` and update discovery clients/docs. @@ -3383,6 +3794,10 @@ Docs: https://docs.openclaw.ai Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @NicholaiVogel, @RyanLisse, @ThePickle31, @VACInc, @Whoaa512, @YuriNachos, @aaronveklabs, @abdaraxus, @alauppe, @ameno-, @artuskg, @austinm911, @bradleypriest, @cheeeee, @dougvk, @fogboots, @gnarco, @gumadeiras, @jdrhyne, @joelklabo, @longmaba, @mukhtharcm, @odysseus0, @oscargavin, @rhjoh, @sebslight, @sibbl, @sleontenko, @steipete, @suminhthanh, @thewilloftheshadow, @tyler6204, @vignesh07, @visionik, @ysqander, @zerone0x. +### Breaking + +- **BREAKING:** Reject invalid/unknown config entries and refuse to start the gateway for safety. Run `openclaw doctor --fix` to repair, then update plugins (`openclaw plugins update`) if you use any. + ## 2026.1.16-2 ### Changes @@ -3401,15 +3816,6 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Sessions: add `session.identityLinks` for cross-platform DM session li nking. (#1033) — thanks @thewilloftheshadow. https://docs.openclaw.ai/concepts/session - Web search: add `country`/`language` parameters (schema + Brave API) and docs. (#1046) — thanks @YuriNachos. https://docs.openclaw.ai/tools/web -### Breaking - -- **BREAKING:** `openclaw message` and message tool now require `target` (dropping `to`/`channelId` for destinations). (#1034) — thanks @tobalsan. -- **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow. -- **BREAKING:** Drop legacy `chatType: "room"` support; use `chatType: "channel"`. -- **BREAKING:** remove legacy provider-specific target resolution fallbacks; target resolution is centralized with plugin hints + directory lookups. -- **BREAKING:** `openclaw hooks` is now `openclaw webhooks`; hooks live under `openclaw hooks`. https://docs.openclaw.ai/cli/webhooks -- **BREAKING:** `openclaw plugins install ` now copies into `~/.openclaw/extensions` (use `--link` to keep path-based loading). - ### Changes - Plugins: ship bundled plugins disabled by default and allow overrides by installed versions. (#1066) — thanks @ItzR3NO. @@ -3501,6 +3907,15 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Discord: preserve whitespace when chunking long lines so message splits keep spacing intact. - Skills: fix skills watcher ignored list typing (tsc). +### Breaking + +- **BREAKING:** `openclaw message` and message tool now require `target` (dropping `to`/`channelId` for destinations). (#1034) — thanks @tobalsan. +- **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow. +- **BREAKING:** Drop legacy `chatType: "room"` support; use `chatType: "channel"`. +- **BREAKING:** remove legacy provider-specific target resolution fallbacks; target resolution is centralized with plugin hints + directory lookups. +- **BREAKING:** `openclaw hooks` is now `openclaw webhooks`; hooks live under `openclaw hooks`. https://docs.openclaw.ai/cli/webhooks +- **BREAKING:** `openclaw plugins install ` now copies into `~/.openclaw/extensions` (use `--link` to keep path-based loading). + ## 2026.1.15 ### Highlights @@ -3510,11 +3925,6 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Heartbeat: per-agent configuration + 24h duplicate suppression. (#980) — thanks @voidserf. - Security: audit warns on weak model tiers; app nodes store auth tokens encrypted (Keychain/SecurePrefs). -### Breaking - -- **BREAKING:** iOS minimum version is now 18.0 to support Textual markdown rendering in native chat. (#702) -- **BREAKING:** Microsoft Teams is now a plugin; install `@openclaw/msteams` via `openclaw plugins install @openclaw/msteams`. - ### Changes - UI/Apps: move channel/config settings to schema-driven forms and rename Connections → Channels. (#1040) — thanks @thewilloftheshadow. @@ -3587,6 +3997,11 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Fix: allow local Tailscale Serve hostnames without treating tailnet clients as direct. (#885) — thanks @oswalpalash. - Fix: reset sessions after role-ordering conflicts to recover from consecutive user turns. (#998) +### Breaking + +- **BREAKING:** iOS minimum version is now 18.0 to support Textual markdown rendering in native chat. (#702) +- **BREAKING:** Microsoft Teams is now a plugin; install `@openclaw/msteams` via `openclaw plugins install @openclaw/msteams`. + ## 2026.1.14-1 ### Highlights @@ -3723,10 +4138,6 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Gateway: allow Tailscale Serve identity headers to satisfy token auth; rebuild Control UI assets when protocol schema is newer. (#823) — thanks @roshanasingh4; (#786) — thanks @meaningfool. - Heartbeat: default `ackMaxChars` to 300 so short `HEARTBEAT_OK` replies stay internal. -### Installer - -- Install: run `openclaw doctor --non-interactive` after git installs/updates and nudge daemon restarts when detected. - ### Fixes - Doctor: warn on pnpm workspace mismatches, missing Control UI assets, and missing tsx binaries; offer UI rebuilds. @@ -3752,6 +4163,10 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Tools/UI: harden tool input schemas for strict providers; drop null-only union variants for Gemini schema cleanup; treat `maxChars: 0` as unlimited; keep TUI last streamed response instead of "(no output)". (#782) — thanks @AbhisekBasu1; (#796) — thanks @gabriel-trigo; (#747) — thanks @thewilloftheshadow. - Connections UI: polish multi-account account cards. (#816) — thanks @steipete. +### Installer + +- Install: run `openclaw doctor --non-interactive` after git installs/updates and nudge daemon restarts when detected. + ### Maintenance - Dependencies: bump Pi packages to 0.45.3 and refresh patched pi-ai. @@ -3803,15 +4218,6 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Gateway: require `client.id` in WebSocket connect params; use `client.instanceId` for presence de-dupe; update docs/tests. - macOS: remove the attach-only gateway setting; local mode now always manages launchd while still attaching to an existing gateway if present. -### Installer - -- Postinstall: replace `git apply` with builtin JS patcher (works npm/pnpm/bun; no git dependency) plus regression tests. -- Postinstall: skip pnpm patch fallback when the new patcher is active. -- Installer tests: add root+non-root docker smokes, CI workflow to fetch openclaw.ai scripts and run install sh/cli with onboarding skipped. -- Installer UX: support `CLAWDBOT_NO_ONBOARD=1` for non-interactive installs; fix npm prefix on Linux and auto-install git. -- Installer UX: add `install.sh --help` with flags/env and git install hint. -- Installer UX: add `--install-method git|npm` and auto-detect source checkouts (prompt to update git checkout vs migrate to npm). - ### Fixes - Models/Onboarding: configure MiniMax (minimax.io) via Anthropic-compatible `/anthropic` endpoint by default (keep `minimax-api` as a legacy alias). @@ -3850,6 +4256,15 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Sandbox/Gateway: treat `agent::main` as a main-session alias when `session.mainKey` is customized (backwards compatible). - Auto-reply: fast-path allowlisted slash commands (inline `/help`/`/commands`/`/status`/`/whoami` stripped before model). +### Installer + +- Postinstall: replace `git apply` with builtin JS patcher (works npm/pnpm/bun; no git dependency) plus regression tests. +- Postinstall: skip pnpm patch fallback when the new patcher is active. +- Installer tests: add root+non-root docker smokes, CI workflow to fetch openclaw.ai scripts and run install sh/cli with onboarding skipped. +- Installer UX: support `CLAWDBOT_NO_ONBOARD=1` for non-interactive installs; fix npm prefix on Linux and auto-install git. +- Installer UX: add `install.sh --help` with flags/env and git install hint. +- Installer UX: add `--install-method git|npm` and auto-detect source checkouts (prompt to update git checkout vs migrate to npm). + ## 2026.1.10 ### Highlights @@ -3958,11 +4373,6 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Auto-reply + status: block-streaming controls, reasoning handling, usage/cost reporting. - Control UI/TUI: queued messages, session links, reasoning view, mobile polish, logs UX. -### Breaking - -- CLI: `openclaw message` now subcommands (`message send|poll|...`) and requires `--provider` unless only one provider configured. -- Commands/Tools: `/restart` and gateway restart tool disabled by default; enable with `commands.restart=true`. - ### New Features and Changes - Models/Auth: OpenCode Zen onboarding (#623) — thanks @magimetal; MiniMax Anthropic-compatible API + hosted onboarding (#590, #495) — thanks @mneves75, @tobiasbischoff. @@ -4002,6 +4412,12 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Gateway/Daemon/Doctor: atomic config writes; repair gateway service entrypoint + install switches; non-interactive legacy migrations; systemd unit alignment + KillMode=process; node bridge keepalive/pings; Launch at Login persistence; bundle MoltbotKit resources + Swift 6.2 compat dylib; relay version check + remove smoke test; regen Swift GatewayModels + keep agent provider string; cron jobId alias + channel alias migration + main session key normalization; heartbeat Telegram accountId resolution; avoid WhatsApp fallback for internal runs; gateway listener error wording; serveBaseUrl param; honor gateway --dev; fix wide-area discovery updates; align agents.defaults schema; provider account metadata in daemon status; refresh Carbon patch for gateway fixes; restore doctor prompter initialValue handling. - Control UI/TUI: persist per-session verbose off + hide tool cards; logs tab opens at bottom; relative asset paths + landing cleanup; session labels lookup/persistence; stop pinning main session in recents; start logs at bottom; TUI status bar refresh + timeout handling + hide reasoning label when off. - Onboarding/Configure: QuickStart single-select provider picker; avoid Codex CLI false-expiry warnings; clarify WhatsApp owner prompt; fix Minimax hosted onboarding (agents.defaults + msteams heartbeat target); remove configure Control UI prompt; honor gateway --dev flag. +- Agent loop: guard overflow compaction throws and restore compaction hooks for engine-owned context engines. (#41361) — thanks @davidrudduck + +### Breaking + +- CLI: `openclaw message` now subcommands (`message send|poll|...`) and requires `--provider` unless only one provider configured. +- Commands/Tools: `/restart` and gateway restart tool disabled by default; enable with `commands.restart=true`. ### Maintenance @@ -4022,6 +4438,18 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Control UI: logs tab, streaming stability, focus mode, and large-output rendering fixes. - CLI/Gateway/Doctor: daemon/logs/status, auth migration, and diagnostics significantly expanded. +### Fixes + +- **CLI/Gateway/Doctor:** daemon runtime selection + improved logs/status/health/errors; auth/password handling for local CLI; richer close/timeout details; auto-migrate legacy config/sessions/state; integrity checks + repair prompts; `--yes`/`--non-interactive`; `--deep` gateway scans; better restart/service hints. +- **Agent loop + compaction:** compaction/pruning tuning, overflow handling, safer bootstrap context, and per-provider threading/confirmations; opt-in tool-result pruning + compact tracking. +- **Sandbox + tools:** per-agent sandbox overrides, workspaceAccess controls, session tool visibility, tool policy overrides, process isolation, and tool schema/timeout/reaction unification. +- **Providers (Telegram/WhatsApp/Discord/Slack/Signal/iMessage):** retry/backoff, threading, reactions, media groups/attachments, mention gating, typing behavior, and error/log stability; long polling + forum topic isolation for Telegram. +- **Gateway/CLI UX:** `openclaw logs`, cron list colors/aliases, docs search, agents list/add/delete flows, status usage snapshots, runtime/auth source display, and `/status`/commands auth unification. +- **Control UI/Web:** logs tab, focus mode polish, config form resilience, streaming stability, tool output caps, windowed chat history, and reconnect/password URL auth. +- **macOS/Android/TUI/Build:** macOS gateway races, QR bundling, JSON5 config safety, Voice Wake hardening; Android EXIF rotation + APK naming/versioning; TUI key handling; tooling/bundling fixes. +- **Packaging/compat:** npm dist folder coverage, Node 25 qrcode-terminal import fixes, Bun/Playwright/WebSocket patches, and Docker Bun install. +- **Docs:** new FAQ/ClawHub/config examples/showcase entries and clarified auth, sandbox, and systemd docs. + ### Breaking - **SECURITY (update ASAP):** inbound DMs are now **locked down by default** on Telegram/WhatsApp/Signal/iMessage/Discord/Slack. @@ -4037,18 +4465,6 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Auto-reply: removed `autoReply` from Discord/Slack/Telegram channel configs; use `requireMention` instead (Telegram topics now support `requireMention` overrides). - CLI: remove `update`, `gateway-daemon`, `gateway {install|uninstall|start|stop|restart|daemon status|wake|send|agent}`, and `telegram` commands; move `login/logout` to `providers login/logout` (top-level aliases hidden); use `daemon` for service control, `send`/`agent`/`wake` for RPC, and `nodes canvas` for canvas ops. -### Fixes - -- **CLI/Gateway/Doctor:** daemon runtime selection + improved logs/status/health/errors; auth/password handling for local CLI; richer close/timeout details; auto-migrate legacy config/sessions/state; integrity checks + repair prompts; `--yes`/`--non-interactive`; `--deep` gateway scans; better restart/service hints. -- **Agent loop + compaction:** compaction/pruning tuning, overflow handling, safer bootstrap context, and per-provider threading/confirmations; opt-in tool-result pruning + compact tracking. -- **Sandbox + tools:** per-agent sandbox overrides, workspaceAccess controls, session tool visibility, tool policy overrides, process isolation, and tool schema/timeout/reaction unification. -- **Providers (Telegram/WhatsApp/Discord/Slack/Signal/iMessage):** retry/backoff, threading, reactions, media groups/attachments, mention gating, typing behavior, and error/log stability; long polling + forum topic isolation for Telegram. -- **Gateway/CLI UX:** `openclaw logs`, cron list colors/aliases, docs search, agents list/add/delete flows, status usage snapshots, runtime/auth source display, and `/status`/commands auth unification. -- **Control UI/Web:** logs tab, focus mode polish, config form resilience, streaming stability, tool output caps, windowed chat history, and reconnect/password URL auth. -- **macOS/Android/TUI/Build:** macOS gateway races, QR bundling, JSON5 config safety, Voice Wake hardening; Android EXIF rotation + APK naming/versioning; TUI key handling; tooling/bundling fixes. -- **Packaging/compat:** npm dist folder coverage, Node 25 qrcode-terminal import fixes, Bun/Playwright/WebSocket patches, and Docker Bun install. -- **Docs:** new FAQ/ClawHub/config examples/showcase entries and clarified auth, sandbox, and systemd docs. - ### Maintenance - Skills additions (Himalaya email, CodexBar, 1Password). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7808db9cf8..8914ffc1f31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ Welcome to the lobster tank! 🦞 - **Jos** - Telegram, API, Nix mode - GitHub: [@joshp123](https://github.com/joshp123) · X: [@jjpcodes](https://x.com/jjpcodes) -- **Ayaan Zaidi** - Telegram subsystem, iOS app +- **Ayaan Zaidi** - Telegram subsystem, Android app - GitHub: [@obviyus](https://github.com/obviyus) · X: [@0bviyus](https://x.com/0bviyus) - **Tyler Yust** - Agents/subagents, cron, BlueBubbles, macOS app @@ -47,7 +47,7 @@ Welcome to the lobster tank! 🦞 - **Christoph Nakazawa** - JS Infra - GitHub: [@cpojer](https://github.com/cpojer) · X: [@cnakazawa](https://x.com/cnakazawa) -- **Gustavo Madeira Santana** - Multi-agents, CLI, web UI +- **Gustavo Madeira Santana** - Multi-agents, CLI, Performance, Plugins, Matrix - GitHub: [@gumadeiras](https://github.com/gumadeiras) · X: [@gumadeiras](https://x.com/gumadeiras) - **Onur Solmaz** - Agents, dev workflows, ACP integrations, MS Teams @@ -61,7 +61,7 @@ Welcome to the lobster tank! 🦞 - **Josh Lehman** - Compaction, Tlon/Urbit subsystem - GitHub [@jalehman](https://github.com/jalehman) · X: [@jlehman\_](https://x.com/jlehman_) -- **Radek Sienkiewicz** - Control UI + WebChat correctness +- **Radek Sienkiewicz** - Docs, Control UI - GitHub [@velvet-shark](https://github.com/velvet-shark) · X: [@velvet_shark](https://twitter.com/velvet_shark) - **Muhammed Mukhthar** - Mattermost, CLI @@ -76,22 +76,35 @@ Welcome to the lobster tank! 🦞 - **Tengji (George) Zhang** - Chinese model APIs, cloud, pi - GitHub: [@odysseus0](https://github.com/odysseus0) · X: [@odysseus0z](https://x.com/odysseus0z) +- **Andrew (Bubbles) Demczuk** - Agents/Gateway/TTS/VTT + - GitHub: [@ademczuk](https://github.com/ademczuk) · X: [@ademczuk](https://x.com/ademczuk) + ## How to Contribute 1. **Bugs & small fixes** → Open a PR! 2. **New features / architecture** → Start a [GitHub Discussion](https://github.com/openclaw/openclaw/discussions) or ask in Discord first -3. **Questions** → Discord [#help](https://discord.com/channels/1456350064065904867/1459642797895319552) / [#users-helping-users](https://discord.com/channels/1456350064065904867/1459007081603403828) +3. **Test/CI-only PRs for known `main` failures** → Don't open a PR, the Maintainer team is already tracking it and such PRs will be closed automatically. If you've spotted a _new_ regression not yet shown in main CI, report it as an issue first. +4. **Questions** → Discord [#help](https://discord.com/channels/1456350064065904867/1459642797895319552) / [#users-helping-users](https://discord.com/channels/1456350064065904867/1459007081603403828) ## Before You PR - Test locally with your OpenClaw instance - Run tests: `pnpm build && pnpm check && pnpm test` +- For extension/plugin changes, run the fast local lane first: + - `pnpm test:extension ` + - `pnpm test:extension --list` to see valid extension ids + - If you changed shared plugin or channel surfaces, run `pnpm test:contracts` + - For targeted shared-surface work, use `pnpm test:contracts:channels` or `pnpm test:contracts:plugins` + - If you changed broader runtime behavior, still run the relevant wider lanes (`pnpm test:extensions`, `pnpm test:channels`, or `pnpm test`) before asking for review - If you have access to Codex, run `codex review --base origin/main` locally before opening or updating your PR. Treat this as the current highest standard of AI review, even if GitHub Codex review also runs. +- Do not submit test or CI-config fixes for failures already red on `main` CI. If a failure is already visible in the [main branch CI runs](https://github.com/openclaw/openclaw/actions), it's a known issue the Maintainer team is tracking, and a PR that only addresses those failures will be closed automatically. If you spot a _new_ regression not yet shown in main CI, report it as an issue first. - Ensure CI checks pass - Keep PRs focused (one thing per PR; do not mix unrelated concerns) - Describe what & why - Reply to or resolve bot review conversations you addressed before asking for review again - **Include screenshots** — one showing the problem/before, one showing the fix/after (for UI or visual changes) +- Use American English spelling and grammar in code, comments, docs, and UI strings +- Do not edit files covered by `CODEOWNERS` security ownership unless a listed owner explicitly asked for the change or is already reviewing it with you. Treat those paths as restricted review surfaces, not opportunistic cleanup targets. ## Review Conversations Are Author-Owned diff --git a/Dockerfile b/Dockerfile index d6923365b4b..fa97f83323a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,14 +14,14 @@ # Slim (bookworm-slim): docker build --build-arg OPENCLAW_VARIANT=slim . ARG OPENCLAW_EXTENSIONS="" ARG OPENCLAW_VARIANT=default -ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:22-bookworm@sha256:b501c082306a4f528bc4038cbf2fbb58095d583d0419a259b2114b5ac53d12e9" -ARG OPENCLAW_NODE_BOOKWORM_DIGEST="sha256:b501c082306a4f528bc4038cbf2fbb58095d583d0419a259b2114b5ac53d12e9" -ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:22-bookworm-slim@sha256:9c2c405e3ff9b9afb2873232d24bb06367d649aa3e6259cbe314da59578e81e9" -ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST="sha256:9c2c405e3ff9b9afb2873232d24bb06367d649aa3e6259cbe314da59578e81e9" +ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:24-bookworm@sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b" +ARG OPENCLAW_NODE_BOOKWORM_DIGEST="sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b" +ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:24-bookworm-slim@sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb" +ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST="sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb" # Base images are pinned to SHA256 digests for reproducible builds. # Trade-off: digests must be updated manually when upstream tags move. -# To update, run: docker manifest inspect node:22-bookworm (or podman) +# To update, run: docker buildx imagetools inspect node:24-bookworm (or podman) # and replace the digest below with the current multi-arch manifest list entry. FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS ext-deps @@ -39,8 +39,18 @@ RUN mkdir -p /out && \ # ── Stage 2: Build ────────────────────────────────────────────── FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS build -# Install Bun (required for build scripts) -RUN curl -fsSL https://bun.sh/install | bash +# Install Bun (required for build scripts). Retry the whole bootstrap flow to +# tolerate transient 5xx failures from bun.sh/GitHub during CI image builds. +RUN set -eux; \ + for attempt in 1 2 3 4 5; do \ + if curl --retry 5 --retry-all-errors --retry-delay 2 -fsSL https://bun.sh/install | bash; then \ + break; \ + fi; \ + if [ "$attempt" -eq 5 ]; then \ + exit 1; \ + fi; \ + sleep $((attempt * 2)); \ + done ENV PATH="/root/.bun/bin:${PATH}" RUN corepack enable @@ -92,12 +102,12 @@ RUN CI=true pnpm prune --prod && \ # ── Runtime base images ───────────────────────────────────────── FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS base-default ARG OPENCLAW_NODE_BOOKWORM_DIGEST -LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm" \ +LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm" \ org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_DIGEST}" FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-slim ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST -LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm-slim" \ +LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm-slim" \ org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST}" # ── Stage 3: Runtime ──────────────────────────────────────────── @@ -122,8 +132,9 @@ WORKDIR /app RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \ apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - procps hostname curl git openssl + procps hostname curl git lsof openssl RUN chown node:node /app @@ -135,13 +146,25 @@ COPY --from=runtime-assets --chown=node:node /app/extensions ./extensions COPY --from=runtime-assets --chown=node:node /app/skills ./skills COPY --from=runtime-assets --chown=node:node /app/docs ./docs +# In npm-installed Docker images, prefer the copied source extension tree for +# bundled discovery so package metadata that points at source entries stays valid. +ENV OPENCLAW_BUNDLED_PLUGINS_DIR=/app/extensions + # Keep pnpm available in the runtime image for container-local workflows. # Use a shared Corepack home so the non-root `node` user does not need a # first-run network fetch when invoking pnpm. ENV COREPACK_HOME=/usr/local/share/corepack RUN install -d -m 0755 "$COREPACK_HOME" && \ corepack enable && \ - corepack prepare "$(node -p "require('./package.json').packageManager")" --activate && \ + for attempt in 1 2 3 4 5; do \ + if corepack prepare "$(node -p "require('./package.json').packageManager")" --activate; then \ + break; \ + fi; \ + if [ "$attempt" -eq 5 ]; then \ + exit 1; \ + fi; \ + sleep $((attempt * 2)); \ + done && \ chmod -R a+rX "$COREPACK_HOME" # Install additional system packages needed by your skills or extensions. @@ -209,7 +232,7 @@ RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \ ENV NODE_ENV=production # Security hardening: Run as non-root user -# The node:22-bookworm image includes a 'node' user (uid 1000) +# The node:24-bookworm image includes a 'node' user (uid 1000) # This reduces the attack surface by preventing container escape via root privileges USER node diff --git a/Dockerfile.sandbox b/Dockerfile.sandbox index 8b50c7a6745..37cdab5fcd2 100644 --- a/Dockerfile.sandbox +++ b/Dockerfile.sandbox @@ -7,6 +7,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,id=openclaw-sandbox-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,id=openclaw-sandbox-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \ apt-get update \ + && apt-get upgrade -y --no-install-recommends \ && apt-get install -y --no-install-recommends \ bash \ ca-certificates \ diff --git a/Dockerfile.sandbox-browser b/Dockerfile.sandbox-browser index f04e4a82a62..e8e8bb59f84 100644 --- a/Dockerfile.sandbox-browser +++ b/Dockerfile.sandbox-browser @@ -7,6 +7,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,id=openclaw-sandbox-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,id=openclaw-sandbox-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \ apt-get update \ + && apt-get upgrade -y --no-install-recommends \ && apt-get install -y --no-install-recommends \ bash \ ca-certificates \ diff --git a/Dockerfile.sandbox-common b/Dockerfile.sandbox-common index 39eaa3692b4..fba29a5df3d 100644 --- a/Dockerfile.sandbox-common +++ b/Dockerfile.sandbox-common @@ -24,6 +24,7 @@ ENV PATH=${BUN_INSTALL_DIR}/bin:${BREW_INSTALL_DIR}/bin:${BREW_INSTALL_DIR}/sbin RUN --mount=type=cache,id=openclaw-sandbox-common-apt-cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,id=openclaw-sandbox-common-apt-lists,target=/var/lib/apt,sharing=locked \ apt-get update \ + && apt-get upgrade -y --no-install-recommends \ && apt-get install -y --no-install-recommends ${PACKAGES} RUN if [ "${INSTALL_PNPM}" = "1" ]; then npm install -g pnpm; fi diff --git a/README.md b/README.md index 767f4bc2141..e483bcc9446 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@

- - OpenClaw + + OpenClaw

@@ -23,10 +23,10 @@ It answers you on the channels you already use (WhatsApp, Telegram, Slack, Disco If you want a personal, single-user assistant that feels local, fast, and always-on, this is it. -[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [Vision](VISION.md) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/help/faq) · [Wizard](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-openclaw) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd) +[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [Vision](VISION.md) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/help/faq) · [Onboarding](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-openclaw) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd) -Preferred setup: run the onboarding wizard (`openclaw onboard`) in your terminal. -The wizard guides you step by step through setting up the gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**. +Preferred setup: run `openclaw onboard` in your terminal. +OpenClaw Onboard guides you step by step through setting up the gateway, workspace, channels, and skills. It is the recommended CLI setup path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**. Works with npm, pnpm, or bun. New install? Start here: [Getting started](https://docs.openclaw.ai/start/getting-started) @@ -58,7 +58,7 @@ npm install -g openclaw@latest openclaw onboard --install-daemon ``` -The wizard installs the Gateway daemon (launchd/systemd user service) so it stays running. +OpenClaw Onboard installs the Gateway daemon (launchd/systemd user service) so it stays running. ## Quick start (TL;DR) @@ -103,7 +103,7 @@ pnpm build pnpm openclaw onboard --install-daemon -# Dev loop (auto-reload on TS changes) +# Dev loop (auto-reload on source/config changes) pnpm gateway:watch ``` @@ -132,7 +132,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies. - **[Live Canvas](https://docs.openclaw.ai/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui). - **[First-class tools](https://docs.openclaw.ai/tools)** — browser, canvas, nodes, cron, sessions, and Discord/Slack actions. - **[Companion apps](https://docs.openclaw.ai/platforms/macos)** — macOS menu bar app + iOS/Android [nodes](https://docs.openclaw.ai/nodes). -- **[Onboarding](https://docs.openclaw.ai/start/wizard) + [skills](https://docs.openclaw.ai/tools/skills)** — wizard-driven setup with bundled/managed/workspace skills. +- **[Onboarding](https://docs.openclaw.ai/start/wizard) + [skills](https://docs.openclaw.ai/tools/skills)** — onboarding-driven setup with bundled/managed/workspace skills. ## Star History @@ -143,7 +143,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies. ### Core platform - [Gateway WS control plane](https://docs.openclaw.ai/gateway) with sessions, presence, config, cron, webhooks, [Control UI](https://docs.openclaw.ai/web), and [Canvas host](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui). -- [CLI surface](https://docs.openclaw.ai/tools/agent-send): gateway, agent, send, [wizard](https://docs.openclaw.ai/start/wizard), and [doctor](https://docs.openclaw.ai/gateway/doctor). +- [CLI surface](https://docs.openclaw.ai/tools/agent-send): gateway, agent, send, [onboarding](https://docs.openclaw.ai/start/wizard), and [doctor](https://docs.openclaw.ai/gateway/doctor). - [Pi agent runtime](https://docs.openclaw.ai/concepts/agent) in RPC mode with tool streaming and block streaming. - [Session model](https://docs.openclaw.ai/concepts/session): `main` for direct chats, group isolation, activation modes, queue modes, reply-back. Group rules: [Groups](https://docs.openclaw.ai/channels/groups). - [Media pipeline](https://docs.openclaw.ai/nodes/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.openclaw.ai/nodes/audio). @@ -293,7 +293,7 @@ If you plan to build/run companion apps, follow the platform runbooks below. - WebChat + debug tools. - Remote gateway control over SSH. -Note: signed builds required for macOS permissions to stick across rebuilds (see `docs/mac/permissions.md`). +Note: signed builds required for macOS permissions to stick across rebuilds (see [macOS Permissions](https://docs.openclaw.ai/platforms/mac/permissions)). ### iOS node (optional) @@ -364,7 +364,7 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker ### [Discord](https://docs.openclaw.ai/channels/discord) -- Set `DISCORD_BOT_TOKEN` or `channels.discord.token` (env wins). +- Set `DISCORD_BOT_TOKEN` or `channels.discord.token`. - Optional: set `commands.native`, `commands.text`, or `commands.useAccessGroups`, plus `channels.discord.allowFrom`, `channels.discord.guilds`, or `channels.discord.mediaMaxMb` as needed. ```json5 @@ -422,7 +422,7 @@ Use these when you’re past the onboarding flow and want the deeper reference. - [Run the Gateway by the book with the operational runbook.](https://docs.openclaw.ai/gateway) - [Learn how the Control UI/Web surfaces work and how to expose them safely.](https://docs.openclaw.ai/web) - [Understand remote access over SSH tunnels or tailnets.](https://docs.openclaw.ai/gateway/remote) -- [Follow the onboarding wizard flow for a guided setup.](https://docs.openclaw.ai/start/wizard) +- [Follow OpenClaw Onboard for a guided setup.](https://docs.openclaw.ai/start/wizard) - [Wire external triggers via the webhook surface.](https://docs.openclaw.ai/automation/webhook) - [Set up Gmail Pub/Sub triggers.](https://docs.openclaw.ai/automation/gmail-pubsub) - [Learn the macOS menu bar companion details.](https://docs.openclaw.ai/platforms/mac/menu-bar) diff --git a/SECURITY.md b/SECURITY.md index 204dadbf36d..bef814525a5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -37,6 +37,7 @@ For fastest triage, include all of the following: - Exact vulnerable path (`file`, function, and line range) on a current revision. - Tested version details (OpenClaw version and/or commit SHA). - Reproducible PoC against latest `main` or latest released version. +- If the claim targets a released version, evidence from the shipped tag and published artifact/package for that exact version (not only `main`). - Demonstrated impact tied to OpenClaw's documented trust boundaries. - For exposed-secret reports: proof the credential is OpenClaw-owned (or grants access to OpenClaw-operated infrastructure/services). - Explicit statement that the report does not rely on adversarial operators sharing one gateway host/config. @@ -55,6 +56,7 @@ These are frequently reported but are typically closed with no code change: - Authorized user-triggered local actions presented as privilege escalation. Example: an allowlisted/owner sender running `/export-session /absolute/path.html` to write on the host. In this trust model, authorized user actions are trusted host actions unless you demonstrate an auth/sandbox/boundary bypass. - Reports that only show a malicious plugin executing privileged actions after a trusted operator installs/enables it. - Reports that assume per-user multi-tenant authorization on a shared gateway host/config. +- Reports that treat the Gateway HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) as if they implemented scoped operator auth (`operator.write` vs `operator.admin`). These endpoints authenticate the shared Gateway bearer secret/password and are documented full operator-access surfaces, not per-user/per-scope boundaries. - Reports that only show differences in heuristic detection/parity (for example obfuscation-pattern detection on one exec path but not another, such as `node.invoke -> system.run` parity gaps) without demonstrating bypass of auth, approvals, allowlist enforcement, sandboxing, or other documented trust boundaries. - ReDoS/DoS claims that require trusted operator configuration input (for example catastrophic regex in `sessionFilter` or `logging.redactPatterns`) without a trust-boundary bypass. - Archive/install extraction claims that require pre-existing local filesystem priming in trusted state (for example planting symlink/hardlink aliases under destination directories such as skills/tools paths) without showing an untrusted path that can create/control that primitive. @@ -65,6 +67,7 @@ These are frequently reported but are typically closed with no code change: - Discord inbound webhook signature findings for paths not used by this repo's Discord integration. - Claims that Microsoft Teams `fileConsent/invoke` `uploadInfo.uploadUrl` is attacker-controlled without demonstrating one of: auth boundary bypass, a real authenticated Teams/Bot Framework event carrying attacker-chosen URL, or compromise of the Microsoft/Bot trust path. - Scanner-only claims against stale/nonexistent paths, or claims without a working repro. +- Reports that restate an already-fixed issue against later released versions without showing the vulnerable path still exists in the shipped tag or published artifact for that later version. ### Duplicate Report Handling @@ -90,6 +93,7 @@ When patching a GHSA via `gh api`, include `X-GitHub-Api-Version: 2022-11-28` (o OpenClaw does **not** model one gateway as a multi-tenant, adversarial user boundary. - Authenticated Gateway callers are treated as trusted operators for that gateway instance. +- The HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) are in that same trusted-operator bucket. Passing Gateway bearer auth there is equivalent to operator access for that gateway; they do not implement a narrower `operator.write` vs `operator.admin` trust split. - Session identifiers (`sessionKey`, session IDs, labels) are routing controls, not per-user authorization boundaries. - If one operator can view data from another operator on the same gateway, that is expected in this trust model. - OpenClaw can technically run multiple gateway instances on one machine, but recommended operations are clean separation by trust boundary. @@ -145,6 +149,7 @@ OpenClaw security guidance assumes: OpenClaw's security model is "personal assistant" (one trusted operator, potentially many agents), not "shared multi-tenant bus." - If multiple people can message the same tool-enabled agent (for example a shared Slack workspace), they can all steer that agent within its granted permissions. +- Non-owner sender status only affects owner-only tools/commands. If a non-owner can still access a non-owner-only tool on that same agent (for example `canvas`), that is within the granted tool boundary unless the report demonstrates an auth, policy, allowlist, approval, or sandbox bypass. - Session or memory scoping reduces context bleed, but does **not** create per-user host authorization boundaries. - For mixed-trust or adversarial users, isolate by OS user/host/gateway and use separate credentials per boundary. - A company-shared agent can be a valid setup when users are in the same trust boundary and the agent is strictly business-only. diff --git a/Swabble/Sources/SwabbleKit/WakeWordGate.swift b/Swabble/Sources/SwabbleKit/WakeWordGate.swift index 27c952a8d1b..1a1479b630b 100644 --- a/Swabble/Sources/SwabbleKit/WakeWordGate.swift +++ b/Swabble/Sources/SwabbleKit/WakeWordGate.swift @@ -101,25 +101,19 @@ public enum WakeWordGate { } public static func commandText( - transcript: String, + transcript _: String, segments: [WakeWordSegment], triggerEndTime: TimeInterval) -> String { let threshold = triggerEndTime + 0.001 + var commandWords: [String] = [] + commandWords.reserveCapacity(segments.count) for segment in segments where segment.start >= threshold { - if normalizeToken(segment.text).isEmpty { continue } - if let range = segment.range { - let slice = transcript[range.lowerBound...] - return String(slice).trimmingCharacters(in: Self.whitespaceAndPunctuation) - } - break + let normalized = normalizeToken(segment.text) + if normalized.isEmpty { continue } + commandWords.append(segment.text) } - - let text = segments - .filter { $0.start >= threshold && !normalizeToken($0.text).isEmpty } - .map(\.text) - .joined(separator: " ") - return text.trimmingCharacters(in: Self.whitespaceAndPunctuation) + return commandWords.joined(separator: " ").trimmingCharacters(in: Self.whitespaceAndPunctuation) } public static func matchesTextOnly(text: String, triggers: [String]) -> Bool { diff --git a/Swabble/Tests/SwabbleKitTests/WakeWordGateTests.swift b/Swabble/Tests/SwabbleKitTests/WakeWordGateTests.swift index 5cc283c35ae..7e5b4abdd74 100644 --- a/Swabble/Tests/SwabbleKitTests/WakeWordGateTests.swift +++ b/Swabble/Tests/SwabbleKitTests/WakeWordGateTests.swift @@ -46,6 +46,25 @@ import Testing let match = WakeWordGate.match(transcript: transcript, segments: segments, config: config) #expect(match?.command == "do it") } + + @Test func commandTextHandlesForeignRangeIndices() { + let transcript = "hey clawd do thing" + let other = "do thing" + let foreignRange = other.range(of: "do") + let segments = [ + WakeWordSegment(text: "hey", start: 0.0, duration: 0.1, range: transcript.range(of: "hey")), + WakeWordSegment(text: "clawd", start: 0.2, duration: 0.1, range: transcript.range(of: "clawd")), + WakeWordSegment(text: "do", start: 0.9, duration: 0.1, range: foreignRange), + WakeWordSegment(text: "thing", start: 1.1, duration: 0.1, range: nil), + ] + + let command = WakeWordGate.commandText( + transcript: transcript, + segments: segments, + triggerEndTime: 0.3) + + #expect(command == "do thing") + } } private func makeSegments( diff --git a/appcast.xml b/appcast.xml index 4bceb205614..c1919972b22 100644 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,174 @@ OpenClaw + + 2026.3.13 + Sat, 14 Mar 2026 05:19:48 +0000 + https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml + 2026031390 + 2026.3.13 + 15.0 + OpenClaw 2026.3.13 +

Changes

+
    +
  • Android/chat settings: redesign the chat settings sheet with grouped device and media sections, refresh the Connect and Voice tabs, and tighten the chat composer/session header for a denser mobile layout. (#44894) Thanks @obviyus.
  • +
  • iOS/onboarding: add a first-run welcome pager before gateway setup, stop auto-opening the QR scanner, and show /pair qr instructions on the connect step. (#45054) Thanks @ngutman.
  • +
  • Browser/existing-session: add an official Chrome DevTools MCP attach mode for signed-in live Chrome sessions, with docs for chrome://inspect/#remote-debugging enablement and direct backlinks to Chrome’s own setup guides.
  • +
  • Browser/agents: add built-in profile="user" for the logged-in host browser and profile="chrome-relay" for the extension relay, so agent browser calls can prefer the real signed-in browser without the extra browserSession selector.
  • +
  • Browser/act automation: add batched actions, selector targeting, and delayed clicks for browser act requests with normalized batch dispatch. Thanks @vincentkoc.
  • +
  • Docker/timezone override: add OPENCLAW_TZ so docker-setup.sh can pin gateway and CLI containers to a chosen IANA timezone instead of inheriting the daemon default. (#34119) Thanks @Lanfei.
  • +
  • Dependencies/pi: bump @mariozechner/pi-agent-core, @mariozechner/pi-ai, @mariozechner/pi-coding-agent, and @mariozechner/pi-tui to 0.58.0.
  • +
+

Fixes

+
    +
  • Dashboard/chat UI: stop reloading full chat history on every live tool result in dashboard v2 so tool-heavy runs no longer trigger UI freeze/re-render storms while the final event still refreshes persisted history. (#45541) Thanks @BunsDev.
  • +
  • Gateway/client requests: reject unanswered gateway RPC calls after a bounded timeout and clear their pending state, so stalled connections no longer leak hanging GatewayClient.request() promises indefinitely.
  • +
  • Build/plugin-sdk bundling: bundle plugin-sdk subpath entries in one shared build pass so published packages stop duplicating shared chunks and avoid the recent plugin-sdk memory blow-up. (#45426) Thanks @TarasShyn.
  • +
  • Ollama/reasoning visibility: stop promoting native thinking and reasoning fields into final assistant text so local reasoning models no longer leak internal thoughts in normal replies. (#45330) Thanks @xi7ang.
  • +
  • Android/onboarding QR scan: switch setup QR scanning to Google Code Scanner so onboarding uses a more reliable scanner instead of the legacy embedded ZXing flow. (#45021) Thanks @obviyus.
  • +
  • Browser/existing-session: harden driver validation and session lifecycle so transport errors trigger reconnects while tool-level errors preserve the session, and extract shared ARIA role sets to deduplicate Playwright and Chrome MCP snapshot paths. (#45682) Thanks @odysseus0.
  • +
  • Browser/existing-session: accept text-only list_pages and new_page responses from Chrome DevTools MCP so live-session tab discovery and new-tab open flows keep working when the server omits structured page metadata.
  • +
  • Control UI/insecure auth: preserve explicit shared token and password auth on plain-HTTP Control UI connects so LAN and reverse-proxy sessions no longer drop shared auth before the first WebSocket handshake. (#45088) Thanks @velvet-shark.
  • +
  • Gateway/session reset: preserve lastAccountId and lastThreadId across gateway session resets so replies keep routing back to the same account and thread after /reset. (#44773) Thanks @Lanfei.
  • +
  • macOS/onboarding: avoid self-restarting freshly bootstrapped launchd gateways and give new daemon installs longer to become healthy, so openclaw onboard --install-daemon no longer false-fails on slower Macs and fresh VM snapshots.
  • +
  • Gateway/status: add openclaw gateway status --require-rpc and clearer Linux non-interactive daemon-install failure reporting so automation can fail hard on probe misses instead of treating a printed RPC error as green.
  • +
  • macOS/exec approvals: respect per-agent exec approval settings in the gateway prompter, including allowlist fallback when the native prompt cannot be shown, so gateway-triggered system.run requests follow configured policy instead of always prompting or denying unexpectedly. (#13707) Thanks @sliekens.
  • +
  • Telegram/media downloads: thread the same direct or proxy transport policy into SSRF-guarded file fetches so inbound attachments keep working when Telegram falls back between env-proxy and direct networking. (#44639) Thanks @obviyus.
  • +
  • Telegram/inbound media IPv4 fallback: retry SSRF-guarded Telegram file downloads once with the same IPv4 fallback policy as Bot API calls so fresh installs on IPv6-broken hosts no longer fail to download inbound images.
  • +
  • Windows/gateway install: bound schtasks calls and fall back to the Startup-folder login item when task creation hangs, so native openclaw gateway install fails fast instead of wedging forever on broken Scheduled Task setups.
  • +
  • Windows/gateway stop: resolve Startup-folder fallback listeners from the installed gateway.cmd port, so openclaw gateway stop now actually kills fallback-launched gateway processes before restart.
  • +
  • Windows/gateway status: reuse the installed service command environment when reading runtime status, so startup-fallback gateways keep reporting the configured port and running state in gateway status --json instead of falling back to gateway port unknown.
  • +
  • Windows/gateway auth: stop attaching device identity on local loopback shared-token and password gateway calls, so native Windows agent replies no longer log stale device signature expired fallback noise before succeeding.
  • +
  • Discord/gateway startup: treat plain-text and transient /gateway/bot metadata fetch failures as transient startup errors so Discord gateway boot no longer crashes on unhandled rejections. (#44397) Thanks @jalehman.
  • +
  • Slack/probe: keep auth.test() bot and team metadata mapping stable while simplifying the probe result path. (#44775) Thanks @Cafexss.
  • +
  • Dashboard/chat UI: render oversized plain-text replies as normal paragraphs instead of capped gray code blocks, so long desktop chat responses stay readable without tab-switching refreshes.
  • +
  • Dashboard/chat UI: restore the chat-new-messages class on the New messages scroll pill so the button uses its existing compact styling instead of rendering as a full-screen SVG overlay. (#44856) Thanks @Astro-Han.
  • +
  • Gateway/Control UI: restore the operator-only device-auth bypass and classify browser connect failures so origin and device-identity problems no longer show up as auth errors in the Control UI and web chat. (#45512) thanks @sallyom.
  • +
  • macOS/voice wake: stop crashing wake-word command extraction when speech segment ranges come from a different transcript instance.
  • +
  • Discord/allowlists: honor raw guild_id when hydrated guild objects are missing so allowlisted channels and threads like #maintainers no longer get false-dropped before channel allowlist checks.
  • +
  • macOS/runtime locator: require Node >=22.16.0 during macOS runtime discovery so the app no longer accepts Node versions that the main runtime guard rejects later. Thanks @sumleo.
  • +
  • Agents/custom providers: preserve blank API keys for loopback OpenAI-compatible custom providers by clearing the synthetic Authorization header at runtime, while keeping explicit apiKey and oauth/token config from silently downgrading into fake bearer auth. (#45631) Thanks @xinhuagu.
  • +
  • Models/google-vertex Gemini flash-lite normalization: apply existing bare-ID preview normalization to google-vertex model refs and provider configs so google-vertex/gemini-3.1-flash-lite resolves as gemini-3.1-flash-lite-preview. (#42435) thanks @scoootscooob.
  • +
  • iMessage/remote attachments: reject unsafe remote attachment paths before spawning SCP, so sender-controlled filenames can no longer inject shell metacharacters into remote media staging. Thanks @lintsinghua.
  • +
  • Telegram/webhook auth: validate the Telegram webhook secret before reading or parsing request bodies, so unauthenticated requests are rejected immediately instead of consuming up to 1 MB first. Thanks @space08.
  • +
  • Security/device pairing: make bootstrap setup codes single-use so pending device pairing requests cannot be silently replayed and widened to admin before approval. Thanks @tdjackey.
  • +
  • Security/external content: strip zero-width and soft-hyphen marker-splitting characters during boundary sanitization so spoofed EXTERNAL_UNTRUSTED_CONTENT markers fall back to the existing hardening path instead of bypassing marker normalization.
  • +
  • Security/exec approvals: unwrap more pnpm runtime forms during approval binding, including pnpm --reporter ... exec and direct pnpm node file runs, with matching regression coverage and docs updates.
  • +
  • Security/exec approvals: fail closed for Perl -M and -I approval flows so preload and load-path module resolution stays outside approval-backed runtime execution unless the operator uses a broader explicit trust path.
  • +
  • Security/exec approvals: recognize PowerShell -File and -f wrapper forms during inline-command extraction so approval and command-analysis paths treat file-based PowerShell launches like the existing -Command variants.
  • +
  • Security/exec approvals: unwrap env dispatch wrappers inside shell-segment allowlist resolution on macOS so env FOO=bar /path/to/bin resolves against the effective executable instead of the wrapper token.
  • +
  • Security/exec approvals: treat backslash-newline as shell line continuation during macOS shell-chain parsing so line-continued $( substitutions fail closed instead of slipping past command-substitution checks.
  • +
  • Security/exec approvals: bind macOS skill auto-allow trust to both executable name and resolved path so same-basename binaries no longer inherit trust from unrelated skill bins.
  • +
  • Build/plugin-sdk bundling: bundle plugin-sdk subpath entries in one shared build pass so published packages stop duplicating shared chunks and avoid the recent plugin-sdk memory blow-up. (#45426) Thanks @TarasShyn.
  • +
  • Cron/isolated sessions: route nested cron-triggered embedded runner work onto the nested lane so isolated cron jobs no longer deadlock when compaction or other queued inner work runs. Thanks @vincentkoc.
  • +
  • Agents/OpenAI-compatible compat overrides: respect explicit user models[].compat opt-ins for non-native openai-completions endpoints so usage-in-streaming capability overrides no longer get forced off when the endpoint actually supports them. (#44432) Thanks @cheapestinference.
  • +
  • Agents/Azure OpenAI startup prompts: rephrase the built-in /new, /reset, and post-compaction startup instruction so Azure OpenAI deployments no longer hit HTTP 400 false positives from the content filter. (#43403) Thanks @xingsy97.
  • +
  • Agents/memory bootstrap: load only one root memory file, preferring MEMORY.md and using memory.md as a fallback, so case-insensitive Docker mounts no longer inject duplicate memory context. (#26054) Thanks @Lanfei.
  • +
  • Agents/compaction: compare post-compaction token sanity checks against full-session pre-compaction totals and skip the check when token estimation fails, so sessions with large bootstrap context keep real token counts instead of falling back to unknown. (#28347) thanks @efe-arv.
  • +
  • Agents/compaction: preserve safeguard compaction summary language continuity via default and configurable custom instructions so persona drift is reduced after auto-compaction. (#10456) Thanks @keepitmello.
  • +
  • Agents/tool warnings: distinguish gated core tools like apply_patch from plugin-only unknown entries in tools.profile warnings, so unavailable core tools now report current runtime/provider/model/config gating instead of suggesting a missing plugin.
  • +
  • Config/validation: accept documented agents.list[].params per-agent overrides in strict config validation so openclaw config validate no longer rejects runtime-supported cacheRetention, temperature, and maxTokens settings. (#41171) Thanks @atian8179.
  • +
  • Config/web fetch: restore runtime validation for documented tools.web.fetch.readability and tools.web.fetch.firecrawl settings so valid web fetch configs no longer fail with unrecognized-key errors. (#42583) Thanks @stim64045-spec.
  • +
  • Signal/config validation: add channels.signal.groups schema support so per-group requireMention, tools, and toolsBySender overrides no longer get rejected during config validation. (#27199) Thanks @unisone.
  • +
  • Config/discovery: accept discovery.wideArea.domain in strict config validation so unicast DNS-SD gateway configs no longer fail with an unrecognized-key error. (#35615) Thanks @ingyukoh.
  • +
  • Telegram/media errors: redact Telegram file URLs before building media fetch errors so failed inbound downloads do not leak bot tokens into logs. Thanks @space08.
  • +
+

View full changelog

+]]>
+ +
+ + 2026.3.12 + Fri, 13 Mar 2026 04:25:50 +0000 + https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml + 2026031290 + 2026.3.12 + 15.0 + OpenClaw 2026.3.12 +

Changes

+
    +
  • Control UI/dashboard-v2: refresh the gateway dashboard with modular overview, chat, config, agent, and session views, plus a command palette, mobile bottom tabs, and richer chat tools like slash commands, search, export, and pinned messages. (#41503) Thanks @BunsDev.
  • +
  • OpenAI/GPT-5.4 fast mode: add configurable session-level fast toggles across /fast, TUI, Control UI, and ACP, with per-model config defaults and OpenAI/Codex request shaping.
  • +
  • Anthropic/Claude fast mode: map the shared /fast toggle and params.fastMode to direct Anthropic API-key service_tier requests, with live verification for both Anthropic and OpenAI fast-mode tiers.
  • +
  • Models/plugins: move Ollama, vLLM, and SGLang onto the provider-plugin architecture, with provider-owned onboarding, discovery, model-picker setup, and post-selection hooks so core provider wiring is more modular.
  • +
  • Docs/Kubernetes: Add a starter K8s install path with raw manifests, Kind setup, and deployment docs. Thanks @sallyom @dzianisv @egkristi
  • +
  • Agents/subagents: add sessions_yield so orchestrators can end the current turn immediately, skip queued tool work, and carry a hidden follow-up payload into the next session turn. (#36537) thanks @jriff
  • +
  • Slack/agent replies: support channelData.slack.blocks in the shared reply delivery path so agents can send Block Kit messages through standard Slack outbound delivery. (#44592) Thanks @vincentkoc.
  • +
+

Fixes

+
    +
  • Security/device pairing: switch /pair and openclaw qr setup codes to short-lived bootstrap tokens so the next release no longer embeds shared gateway credentials in chat or QR pairing payloads. Thanks @lintsinghua.
  • +
  • Security/plugins: disable implicit workspace plugin auto-load so cloned repositories cannot execute workspace plugin code without an explicit trust decision. (GHSA-99qw-6mr3-36qr)(#44174) Thanks @lintsinghua and @vincentkoc.
  • +
  • Models/Kimi Coding: send anthropic-messages tools in native Anthropic format again so kimi-coding stops degrading tool calls into XML/plain-text pseudo invocations instead of real tool_use blocks. (#38669, #39907, #40552) Thanks @opriz.
  • +
  • TUI/chat log: reuse the active assistant message component for the same streaming run so openclaw tui no longer renders duplicate assistant replies. (#35364) Thanks @lisitan.
  • +
  • Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in /models button validation. (#40105) Thanks @avirweb.
  • +
  • Cron/proactive delivery: keep isolated direct cron sends out of the write-ahead resend queue so transient-send retries do not replay duplicate proactive messages after restart. (#40646) Thanks @openperf and @vincentkoc.
  • +
  • Models/Kimi Coding: send the built-in User-Agent: claude-code/0.1.0 header by default for kimi-coding while still allowing explicit provider headers to override it, so Kimi Code subscription auth can work without a local header-injection proxy. (#30099) Thanks @Amineelfarssi and @vincentkoc.
  • +
  • Models/OpenAI Codex Spark: keep gpt-5.3-codex-spark working on the openai-codex/* path via resolver fallbacks and clearer Codex-only handling, while continuing to suppress the stale direct openai/* Spark row that OpenAI rejects live.
  • +
  • Ollama/Kimi Cloud: apply the Moonshot Kimi payload compatibility wrapper to Ollama-hosted Kimi models like kimi-k2.5:cloud, so tool routing no longer breaks when thinking is enabled. (#41519) Thanks @vincentkoc.
  • +
  • Moonshot CN API: respect explicit baseUrl (api.moonshot.cn) in implicit provider resolution so platform.moonshot.cn API keys authenticate correctly instead of returning HTTP 401. (#33637) Thanks @chengzhichao-xydt.
  • +
  • Kimi Coding/provider config: respect explicit models.providers["kimi-coding"].baseUrl when resolving the implicit provider so custom Kimi Coding endpoints no longer get overwritten by the built-in default. (#36353) Thanks @2233admin.
  • +
  • Gateway/main-session routing: keep TUI and other mode:UI main-session sends on the internal surface when deliver is enabled, so replies no longer inherit the session's persisted Telegram/WhatsApp route. (#43918) Thanks @obviyus.
  • +
  • BlueBubbles/self-chat echo dedupe: drop reflected duplicate webhook copies only when a matching fromMe event was just seen for the same chat, body, and timestamp, preventing self-chat loops without broad webhook suppression. Related to #32166. (#38442) Thanks @vincentkoc.
  • +
  • iMessage/self-chat echo dedupe: drop reflected duplicate copies only when a matching is_from_me event was just seen for the same chat, text, and created_at, preventing self-chat loops without broad text-only suppression. Related to #32166. (#38440) Thanks @vincentkoc.
  • +
  • Subagents/completion announce retries: raise the default announce timeout to 90 seconds and stop retrying gateway-timeout failures for externally delivered completion announces, preventing duplicate user-facing completion messages after slow gateway responses. Fixes #41235. Thanks @vasujain00 and @vincentkoc.
  • +
  • Mattermost/block streaming: fix duplicate message delivery (one threaded, one top-level) when block streaming is active by excluding replyToId from the block reply dedup key and adding an explicit threading dock to the Mattermost plugin. (#41362) Thanks @mathiasnagler and @vincentkoc.
  • +
  • Mattermost/reply media delivery: pass agent-scoped mediaLocalRoots through shared reply delivery so allowed local files upload correctly from button, slash-command, and model-picker replies. (#44021) Thanks @LyleLiu666.
  • +
  • macOS/Reminders: add the missing NSRemindersUsageDescription to the bundled app so apple-reminders can trigger the system permission prompt from OpenClaw.app. (#8559) Thanks @dinakars777.
  • +
  • Gateway/session discovery: discover disk-only and retired ACP session stores under custom templated session.store roots so ACP reconciliation, session-id/session-label targeting, and run-id fallback keep working after restart. (#44176) thanks @gumadeiras.
  • +
  • Plugins/env-scoped roots: fix plugin discovery/load caches and provenance tracking so same-process HOME/OPENCLAW_HOME changes no longer reuse stale plugin state or misreport ~/... plugins as untracked. (#44046) thanks @gumadeiras.
  • +
  • Models/OpenRouter native ids: canonicalize native OpenRouter model keys across config writes, runtime lookups, fallback management, and models list --plain, and migrate legacy duplicated openrouter/openrouter/... config entries forward on write.
  • +
  • Windows/native update: make package installs use the npm update path instead of the git path, carry portable Git into native Windows updates, and mirror the installer's Windows npm env so openclaw update no longer dies early on missing git or node-llama-cpp download setup.
  • +
  • Sandbox/write: preserve pinned mutation-helper payload stdin so sandboxed write no longer reports success while creating empty files. (#43876) Thanks @glitch418x.
  • +
  • Security/exec approvals: escape invisible Unicode format characters in approval prompts so zero-width command text renders as visible \u{...} escapes instead of spoofing the reviewed command. (GHSA-pcqg-f7rg-xfvv)(#43687) Thanks @EkiXu and @vincentkoc.
  • +
  • Hooks/loader: fail closed when workspace hook paths cannot be resolved with realpath, so unreadable or broken internal hook paths are skipped instead of falling back to unresolved imports. (#44437) Thanks @vincentkoc.
  • +
  • Hooks/agent deliveries: dedupe repeated hook requests by optional idempotency key so webhook retries can reuse the first run instead of launching duplicate agent executions. (#44438) Thanks @vincentkoc.
  • +
  • Security/exec detection: normalize compatibility Unicode and strip invisible formatting code points before obfuscation checks so zero-width and fullwidth command tricks no longer suppress heuristic detection. (GHSA-9r3v-37xh-2cf6)(#44091) Thanks @wooluo and @vincentkoc.
  • +
  • Security/exec allowlist: preserve POSIX case sensitivity and keep ? within a single path segment so exact-looking allowlist patterns no longer overmatch executables across case or directory boundaries. (GHSA-f8r2-vg7x-gh8m)(#43798) Thanks @zpbrent and @vincentkoc.
  • +
  • Security/commands: require sender ownership for /config and /debug so authorized non-owner senders can no longer reach owner-only config and runtime debug surfaces. (GHSA-r7vr-gr74-94p8)(#44305) Thanks @tdjackey and @vincentkoc.
  • +
  • Security/gateway auth: clear unbound client-declared scopes on shared-token WebSocket connects so device-less shared-token operators cannot self-declare elevated scopes. (GHSA-rqpp-rjj8-7wv8)(#44306) Thanks @LUOYEcode and @vincentkoc.
  • +
  • Security/browser.request: block persistent browser profile create/delete routes from write-scoped browser.request so callers can no longer persist admin-only browser profile changes through the browser control surface. (GHSA-vmhq-cqm9-6p7q)(#43800) Thanks @tdjackey and @vincentkoc.
  • +
  • Security/agent: reject public spawned-run lineage fields and keep workspace inheritance on the internal spawned-session path so external agent callers can no longer override the gateway workspace boundary. (GHSA-2rqg-gjgv-84jm)(#43801) Thanks @tdjackey and @vincentkoc.
  • +
  • Security/session_status: enforce sandbox session-tree visibility and shared agent-to-agent access guards before reading or mutating target session state, so sandboxed subagents can no longer inspect parent session metadata or write parent model overrides via session_status. (GHSA-wcxr-59v9-rxr8)(#43754) Thanks @tdjackey and @vincentkoc.
  • +
  • Security/agent tools: mark nodes as explicitly owner-only and document/test that canvas remains a shared trusted-operator surface unless a real boundary bypass exists.
  • +
  • Security/exec approvals: fail closed for Ruby approval flows that use -r, --require, or -I so approval-backed commands no longer bind only the main script while extra local code-loading flags remain outside the reviewed file snapshot.
  • +
  • Security/device pairing: cap issued and verified device-token scopes to each paired device's approved scope baseline so stale or overbroad tokens cannot exceed approved access. (GHSA-2pwv-x786-56f8)(#43686) Thanks @tdjackey and @vincentkoc.
  • +
  • Docs/onboarding: align the legacy wizard reference and openclaw onboard command docs with the Ollama onboarding flow so all onboarding reference paths now document --auth-choice ollama, Cloud + Local mode, and non-interactive usage. (#43473) Thanks @BruceMacD.
  • +
  • Models/secrets: enforce source-managed SecretRef markers in generated models.json so runtime-resolved provider secrets are not persisted when runtime projection is skipped. (#43759) Thanks @joshavant.
  • +
  • Security/WebSocket preauth: shorten unauthenticated handshake retention and reject oversized pre-auth frames before application-layer parsing to reduce pre-pairing exposure on unsupported public deployments. (GHSA-jv4g-m82p-2j93)(#44089) (GHSA-xwx2-ppv2-wx98)(#44089) Thanks @ez-lbz and @vincentkoc.
  • +
  • Security/proxy attachments: restore the shared media-store size cap for persisted browser proxy files so oversized payloads are rejected instead of overriding the intended 5 MB limit. (GHSA-6rph-mmhp-h7h9)(#43684) Thanks @tdjackey and @vincentkoc.
  • +
  • Security/host env: block inherited GIT_EXEC_PATH from sanitized host exec environments so Git helper resolution cannot be steered by host environment state. (GHSA-jf5v-pqgw-gm5m)(#43685) Thanks @zpbrent and @vincentkoc.
  • +
  • Security/Feishu webhook: require encryptKey alongside verificationToken in webhook mode so unsigned forged events are rejected instead of being processed with token-only configuration. (GHSA-g353-mgv3-8pcj)(#44087) Thanks @lintsinghua and @vincentkoc.
  • +
  • Security/Feishu reactions: preserve looked-up group chat typing and fail closed on ambiguous reaction context so group authorization and mention gating cannot be bypassed through synthetic p2p reactions. (GHSA-m69h-jm2f-2pv8)(#44088) Thanks @zpbrent and @vincentkoc.
  • +
  • Security/LINE webhook: require signatures for empty-event POST probes too so unsigned requests no longer confirm webhook reachability with a 200 response. (GHSA-mhxh-9pjm-w7q5)(#44090) Thanks @TerminalsandCoffee and @vincentkoc.
  • +
  • Security/Zalo webhook: rate limit invalid secret guesses before auth so weak webhook secrets cannot be brute-forced through unauthenticated churned requests without pre-auth 429 responses. (GHSA-5m9r-p9g7-679c)(#44173) Thanks @zpbrent and @vincentkoc.
  • +
  • Security/Zalouser groups: require stable group IDs for allowlist auth by default and gate mutable group-name matching behind channels.zalouser.dangerouslyAllowNameMatching. Thanks @zpbrent.
  • +
  • Security/Slack and Teams routing: require stable channel and team IDs for allowlist routing by default, with mutable name matching only via each channel's dangerouslyAllowNameMatching break-glass flag.
  • +
  • Security/exec approvals: fail closed for ambiguous inline loader and shell-payload script execution, bind the real script after POSIX shell value-taking flags, and unwrap pnpm/npm exec/npx script runners before approval binding. (GHSA-57jw-9722-6rf2)(GHSA-jvqh-rfmh-jh27)(GHSA-x7pp-23xv-mmr4)(GHSA-jc5j-vg4r-j5jx)(#44247) Thanks @tdjackey and @vincentkoc.
  • +
  • Doctor/gateway service audit: canonicalize service entrypoint paths before comparing them so symlink-vs-realpath installs no longer trigger false "entrypoint does not match the current install" repair prompts. (#43882) Thanks @ngutman.
  • +
  • Doctor/gateway service audit: earlier groundwork for this fix landed in the superseded #28338 branch. Thanks @realriphub.
  • +
  • Gateway/session stores: regenerate the Swift push-test protocol models and align Windows native session-store realpath handling so protocol checks and sync session discovery stop drifting on Windows. (#44266) thanks @jalehman.
  • +
  • Context engine/session routing: forward optional sessionKey through context-engine lifecycle calls so plugins can see structured routing metadata during bootstrap, assembly, post-turn ingestion, and compaction. (#44157) thanks @jalehman.
  • +
  • Agents/failover: classify z.ai network_error stop reasons as retryable timeouts so provider connectivity failures trigger fallback instead of surfacing raw unhandled-stop-reason errors. (#43884) Thanks @hougangdev.
  • +
  • Memory/session sync: add mode-aware post-compaction session reindexing with agents.defaults.compaction.postIndexSync plus agents.defaults.memorySearch.sync.sessions.postCompactionForce, so compacted session memory can refresh immediately without forcing every deployment into synchronous reindexing. (#25561) thanks @rodrigouroz.
  • +
  • Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in /models button validation. (#40105) Thanks @avirweb.
  • +
  • Telegram/native command sync: suppress expected BOT_COMMANDS_TOO_MUCH retry error noise, add a final fallback summary log, and document the difference between command-menu overflow and real Telegram network failures.
  • +
  • Mattermost/reply media delivery: pass agent-scoped mediaLocalRoots through shared reply delivery so allowed local files upload correctly from button, slash-command, and model-picker replies. (#44021) Thanks @LyleLiu666.
  • +
  • Plugins/env-scoped roots: fix plugin discovery/load caches and provenance tracking so same-process HOME/OPENCLAW_HOME changes no longer reuse stale plugin state or misreport ~/... plugins as untracked. (#44046) thanks @gumadeiras.
  • +
  • Gateway/session discovery: discover disk-only and retired ACP session stores under custom templated session.store roots so ACP reconciliation, session-id/session-label targeting, and run-id fallback keep working after restart. (#44176) thanks @gumadeiras.
  • +
  • Models/OpenRouter native ids: canonicalize native OpenRouter model keys across config writes, runtime lookups, fallback management, and models list --plain, and migrate legacy duplicated openrouter/openrouter/... config entries forward on write.
  • +
  • Gateway/hooks: bucket hook auth failures by forwarded client IP behind trusted proxies and warn when hooks.allowedAgentIds leaves hook routing unrestricted.
  • +
  • Agents/compaction: skip the post-compaction cache-ttl marker write when a compaction completed in the same attempt, preventing the next turn from immediately triggering a second tiny compaction. (#28548) thanks @MoerAI.
  • +
  • Native chat/macOS: add /new, /reset, and /clear reset triggers, keep shared main-session aliases aligned, and ignore stale model-selection completions so native chat state stays in sync across reset and fast model changes. (#10898) Thanks @Nachx639.
  • +
  • Agents/compaction safeguard: route missing-model and missing-API-key cancellation warnings through the shared subsystem logger so they land in structured and file logs. (#9974) Thanks @dinakars777.
  • +
  • Cron/doctor: stop flagging canonical agentTurn and systemEvent payload kinds as legacy cron storage, while still normalizing whitespace-padded and non-canonical variants. (#44012) Thanks @shuicici.
  • +
  • ACP/client final-message delivery: preserve terminal assistant text snapshots before resolving end_turn, so ACP clients no longer drop the last visible reply when the gateway sends the final message body on the terminal chat event. (#17615) Thanks @pjeby.
  • +
  • Telegram/Discord status reactions: show a temporary compacting reaction during auto-compaction pauses and restore thinking afterward so the bot no longer appears frozen while context is being compacted. (#35474) thanks @Cypherm.
  • +
+

View full changelog

+]]>
+ +
2026.3.8-beta.1 Mon, 09 Mar 2026 07:19:57 +0000 @@ -76,587 +244,5 @@ ]]> - - 2026.3.7 - Sun, 08 Mar 2026 04:42:35 +0000 - https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml - 2026030790 - 2026.3.7 - 15.0 - OpenClaw 2026.3.7 -

Changes

-
    -
  • Agents/context engine plugin interface: add ContextEngine plugin slot with full lifecycle hooks (bootstrap, ingest, assemble, compact, afterTurn, prepareSubagentSpawn, onSubagentEnded), slot-based registry with config-driven resolution, LegacyContextEngine wrapper preserving existing compaction behavior, scoped subagent runtime for plugin runtimes via AsyncLocalStorage, and sessions.get gateway method. Enables plugins like lossless-claw to provide alternative context management strategies without modifying core compaction logic. Zero behavior change when no context engine plugin is configured. (#22201) thanks @jalehman.
  • -
  • ACP/persistent channel bindings: add durable Discord channel and Telegram topic binding storage, routing resolution, and CLI/docs support so ACP thread targets survive restarts and can be managed consistently. (#34873) Thanks @dutifulbob.
  • -
  • Telegram/ACP topic bindings: accept Telegram Mac Unicode dash option prefixes in /acp spawn, support Telegram topic thread binding (--thread here|auto), route bound-topic follow-ups to ACP sessions, add actionable Telegram approval buttons with prefixed approval-id resolution, and pin successful bind confirmations in-topic. (#36683) Thanks @huntharo.
  • -
  • Telegram/topic agent routing: support per-topic agentId overrides in forum groups and DM topics so topics can route to dedicated agents with isolated sessions. (#33647; based on #31513) Thanks @kesor and @Sid-Qin.
  • -
  • Web UI/i18n: add Spanish (es) locale support in the Control UI, including locale detection, lazy loading, and language picker labels across supported locales. (#35038) Thanks @DaoPromociones.
  • -
  • Onboarding/web search: add provider selection step and full provider list in configure wizard, with SecretRef ref-mode support during onboarding. (#34009) Thanks @kesku and @thewilloftheshadow.
  • -
  • Tools/Web search: switch Perplexity provider to Search API with structured results plus new language/region/time filters. (#33822) Thanks @kesku.
  • -
  • Gateway: add SecretRef support for gateway.auth.token with auth-mode guardrails. (#35094) Thanks @joshavant.
  • -
  • Docker/Podman extension dependency baking: add OPENCLAW_EXTENSIONS so container builds can preinstall selected bundled extension npm dependencies into the image for faster and more reproducible startup in container deployments. (#32223) Thanks @sallyom.
  • -
  • Plugins/before_prompt_build system-context fields: add prependSystemContext and appendSystemContext so static plugin guidance can be placed in system prompt space for provider caching and lower repeated prompt token cost. (#35177) thanks @maweibin.
  • -
  • Plugins/hook policy: add plugins.entries..hooks.allowPromptInjection, validate unknown typed hook names at runtime, and preserve legacy before_agent_start model/provider overrides while stripping prompt-mutating fields when prompt injection is disabled. (#36567) thanks @gumadeiras.
  • -
  • Hooks/Compaction lifecycle: emit session:compact:before and session:compact:after internal events plus plugin compaction callbacks with session/count metadata, so automations can react to compaction runs consistently. (#16788) thanks @vincentkoc.
  • -
  • Agents/compaction post-context configurability: add agents.defaults.compaction.postCompactionSections so deployments can choose which AGENTS.md sections are re-injected after compaction, while preserving legacy fallback behavior when the documented default pair is configured in any order. (#34556) thanks @efe-arv.
  • -
  • TTS/OpenAI-compatible endpoints: add messages.tts.openai.baseUrl config support with config-over-env precedence, endpoint-aware directive validation, and OpenAI TTS request routing to the resolved base URL. (#34321) thanks @RealKai42.
  • -
  • Slack/DM typing feedback: add channels.slack.typingReaction so Socket Mode DMs can show reaction-based processing status even when Slack native assistant typing is unavailable. (#19816) Thanks @dalefrieswthat.
  • -
  • Discord/allowBots mention gating: add allowBots: "mentions" to only accept bot-authored messages that mention the bot. Thanks @thewilloftheshadow.
  • -
  • Agents/tool-result truncation: preserve important tail diagnostics by using head+tail truncation for oversized tool results while keeping configurable truncation options. (#20076) thanks @jlwestsr.
  • -
  • Cron/job snapshot persistence: skip backup during normalization persistence in ensureLoaded so jobs.json.bak keeps the pre-edit snapshot for recovery, while preserving backup creation on explicit user-driven writes. (#35234) Thanks @0xsline.
  • -
  • CLI: make read-only SecretRef status flows degrade safely (#37023) thanks @joshavant.
  • -
  • Tools/Diffs guidance: restore a short system-prompt hint for enabled diffs while keeping the detailed instructions in the companion skill, so diffs usage guidance stays out of user-prompt space. (#36904) thanks @gumadeiras.
  • -
  • Tools/Diffs guidance loading: move diffs usage guidance from unconditional prompt-hook injection to the plugin companion skill path, reducing unrelated-turn prompt noise while keeping diffs tool behavior unchanged. (#32630) thanks @sircrumpet.
  • -
  • Docs/Web search: remove outdated Brave free-tier wording and replace prescriptive AI ToS guidance with neutral compliance language in Brave setup docs. (#26860) Thanks @HenryLoenwind.
  • -
  • Config/Compaction safeguard tuning: expose agents.defaults.compaction.recentTurnsPreserve and quality-guard retry knobs through the validated config surface and embedded-runner wiring, with regression coverage for real config loading and schema metadata. (#25557) thanks @rodrigouroz.
  • -
  • iOS/App Store Connect release prep: align iOS bundle identifiers under ai.openclaw.client, refresh Watch app icons, add Fastlane metadata/screenshot automation, and support Keychain-backed ASC auth for uploads. (#38936) Thanks @ngutman.
  • -
  • Mattermost/model picker: add Telegram-style interactive provider/model browsing for /oc_model and /oc_models, fix picker callback updates, and emit a normal confirmation reply when a model is selected. (#38767) thanks @mukhtharcm.
  • -
  • Docker/multi-stage build: restructure Dockerfile as a multi-stage build to produce a minimal runtime image without build tools, source code, or Bun; add OPENCLAW_VARIANT=slim build arg for a bookworm-slim variant. (#38479) Thanks @sallyom.
  • -
  • Google/Gemini 3.1 Flash-Lite: add first-class google/gemini-3.1-flash-lite-preview support across model-id normalization, default aliases, media-understanding image lookups, Google Gemini CLI forward-compat fallback, and docs.
  • -
-

Breaking

-
    -
  • BREAKING: Gateway auth now requires explicit gateway.auth.mode when both gateway.auth.token and gateway.auth.password are configured (including SecretRefs). Set gateway.auth.mode to token or password before upgrade to avoid startup/pairing/TUI failures. (#35094) Thanks @joshavant.
  • -
-

Fixes

-
    -
  • Models/MiniMax: stop advertising removed MiniMax-M2.5-Lightning in built-in provider catalogs, onboarding metadata, and docs; keep the supported fast-tier model as MiniMax-M2.5-highspeed.
  • -
  • Security/Config: fail closed when loadConfig() hits validation or read errors so invalid configs cannot silently fall back to permissive runtime defaults. (#9040) Thanks @joetomasone.
  • -
  • Memory/Hybrid search: preserve negative FTS5 BM25 relevance ordering in bm25RankToScore() so stronger keyword matches rank above weaker ones instead of collapsing or reversing scores. (#33757) Thanks @lsdcc01.
  • -
  • LINE/requireMention group gating: align inbound and reply-stage LINE group policy resolution across raw, group:, and room: keys (including account-scoped group config), preserve plugin-backed reply-stage fallback behavior, and add regression coverage for prefixed-only group/room config plus reply-stage policy resolution. (#35847) Thanks @kirisame-wang.
  • -
  • Onboarding/local setup: default unset local tools.profile to coding instead of messaging, restoring file/runtime tools for fresh local installs while preserving explicit user-set profiles. (from #38241, overlap with #34958) Thanks @cgdusek.
  • -
  • Gateway/Telegram stale-socket restart guard: only apply stale-socket restarts to channels that publish event-liveness timestamps, preventing Telegram providers from being misclassified as stale solely due to long uptime and avoiding restart/pairing storms after upgrade. (openclaw#38464)
  • -
  • Onboarding/headless Linux daemon probe hardening: treat systemctl --user is-enabled probe failures as non-fatal during daemon install flow so onboarding no longer crashes on SSH/headless VPS environments before showing install guidance. (#37297) Thanks @acarbajal-web.
  • -
  • Memory/QMD mcporter Windows spawn hardening: when mcporter.cmd launch fails with spawn EINVAL, retry via bare mcporter shell resolution so QMD recall can continue instead of falling back to builtin memory search. (#27402) Thanks @i0ivi0i.
  • -
  • Tools/web_search Brave language-code validation: align search_lang handling with Brave-supported codes (including zh-hans, zh-hant, en-gb, and pt-br), map common alias inputs (zh, ja) to valid Brave values, and reject unsupported codes before upstream requests to prevent 422 failures. (#37260) Thanks @heyanming.
  • -
  • Models/openai-completions streaming compatibility: force compat.supportsUsageInStreaming=false for non-native OpenAI-compatible endpoints during model normalization, preventing usage-only stream chunks from triggering choices[0] parser crashes in provider streams. (#8714) Thanks @nonanon1.
  • -
  • Tools/xAI native web-search collision guard: drop OpenClaw web_search from tool registration when routing to xAI/Grok model providers (including OpenRouter x-ai/*) to avoid duplicate tool-name request failures against provider-native web_search. (#14749) Thanks @realsamrat.
  • -
  • TUI/token copy-safety rendering: treat long credential-like mixed alphanumeric tokens (including quoted forms) as copy-sensitive in render sanitization so formatter hard-wrap guards no longer inject visible spaces into auth-style values before display. (#26710) Thanks @jasonthane.
  • -
  • WhatsApp/self-chat response prefix fallback: stop forcing "[openclaw]" as the implicit outbound response prefix when no identity name or response prefix is configured, so blank/default prefix settings no longer inject branding text unexpectedly in self-chat flows. (#27962) Thanks @ecanmor.
  • -
  • Memory/QMD search result decoding: accept qmd search hits that only include file URIs (for example qmd://collection/path.md) without docid, resolve them through managed collection roots, and keep multi-collection results keyed by file fallback so valid QMD hits no longer collapse to empty memory_search output. (#28181) Thanks @0x76696265.
  • -
  • Memory/QMD collection-name conflict recovery: when qmd collection add fails because another collection already occupies the same path + pattern, detect the conflicting collection from collection list, remove it, and retry add so agent-scoped managed collections are created deterministically instead of being silently skipped; also add warning-only fallback when qmd metadata is unavailable to avoid destructive guesses. (#25496) Thanks @Ramsbaby.
  • -
  • Slack/app_mention race dedupe: when app_mention dispatch wins while same-ts message prepare is still in-flight, suppress the later message dispatch so near-simultaneous Slack deliveries do not produce duplicate replies; keep single-retry behavior and add regression coverage for both dropped and successful message-prepare outcomes. (#37033) Thanks @Takhoffman.
  • -
  • Gateway/chat streaming tool-boundary text retention: merge assistant delta segments into per-run chat buffers so pre-tool text is preserved in live chat deltas/finals when providers emit post-tool assistant segments as non-prefix snapshots. (#36957) Thanks @Datyedyeguy.
  • -
  • TUI/model indicator freshness: prevent stale session snapshots from overwriting freshly patched model selection (and reset per-session freshness when switching session keys) so /model updates reflect immediately instead of lagging by one or more commands. (#21255) Thanks @kowza.
  • -
  • TUI/final-error rendering fallback: when a chat final event has no renderable assistant content but includes envelope errorMessage, render the formatted error text instead of collapsing to "(no output)", preserving actionable failure context in-session. (#14687) Thanks @Mquarmoc.
  • -
  • TUI/session-key alias event matching: treat chat events whose session keys are canonical aliases (for example agent::main vs main) as the same session while preserving cross-agent isolation, so assistant replies no longer disappear or surface in another terminal window due to strict key-form mismatch. (#33937) Thanks @yjh1412.
  • -
  • OpenAI Codex OAuth/login parity: keep openclaw models auth login --provider openai-codex on the built-in path even without provider plugins, preserve Pi-generated authorize URLs without local scope rewriting, and stop validating successful Codex sign-ins against the public OpenAI Responses API after callback. (#37558; follow-up to #36660 and #24720) Thanks @driesvints, @Skippy-Gunboat, and @obviyus.
  • -
  • Agents/config schema lookup: add gateway tool action config.schema.lookup so agents can inspect one config path at a time before edits without loading the full schema into prompt context. (#37266) Thanks @gumadeiras.
  • -
  • Onboarding/API key input hardening: strip non-Latin1 Unicode artifacts from normalized secret input (while preserving Latin-1 content and internal spaces) so malformed copied API keys cannot trigger HTTP header ByteString construction crashes; adds regression coverage for shared normalization and MiniMax auth header usage. (#24496) Thanks @fa6maalassaf.
  • -
  • Kimi Coding/Anthropic tools compatibility: normalize anthropic-messages tool payloads to OpenAI-style tools[].function + compatible tool_choice when targeting Kimi Coding endpoints, restoring tool-call workflows that regressed after v2026.3.2. (#37038) Thanks @mochimochimochi-hub.
  • -
  • Heartbeat/workspace-path guardrails: append explicit workspace HEARTBEAT.md path guidance (and docs/heartbeat.md avoidance) to heartbeat prompts so heartbeat runs target workspace checklists reliably across packaged install layouts. (#37037) Thanks @stofancy.
  • -
  • Subagents/kill-complete announce race: when a late subagent-complete lifecycle event arrives after an earlier kill marker, clear stale kill suppression/cleanup flags and re-run announce cleanup so finished runs no longer get silently swallowed. (#37024) Thanks @cmfinlan.
  • -
  • Agents/tool-result cleanup timeout hardening: on embedded runner teardown idle timeouts, clear pending tool-call state without persisting synthetic missing tool result entries, preventing timeout cleanups from poisoning follow-up turns; adds regression coverage for timeout clear-vs-flush behavior. (#37081) Thanks @Coyote-Den.
  • -
  • Agents/openai-completions stream timeout hardening: ensure runtime undici global dispatchers use extended streaming body/header timeouts (including env-proxy dispatcher mode) before embedded runs, reducing forced mid-stream terminated failures on long generations; adds regression coverage for dispatcher selection and idempotent reconfiguration. (#9708) Thanks @scottchguard.
  • -
  • Agents/fallback cooldown probe execution: thread explicit rate-limit cooldown probe intent from model fallback into embedded runner auth-profile selection so same-provider fallback attempts can actually run when all profiles are cooldowned for rate_limit (instead of failing pre-run as No available auth profile), while preserving default cooldown skip behavior and adding regression tests at both fallback and runner layers. (#13623) Thanks @asfura.
  • -
  • Cron/OpenAI Codex OAuth refresh hardening: when openai-codex token refresh fails specifically on account-id extraction, reuse the cached access token instead of failing the run immediately, with regression coverage to keep non-Codex and unrelated refresh failures unchanged. (#36604) Thanks @laulopezreal.
  • -
  • TUI/session isolation for /new: make /new allocate a unique tui- session key instead of resetting the shared agent session, so multiple TUI clients on the same agent stop receiving each other’s replies; also sanitize /new and /reset failure text before rendering in-terminal. Landed from contributor PR #39238 by @widingmarcus-cyber. Thanks @widingmarcus-cyber.
  • -
  • Synology Chat/rate-limit env parsing: honor SYNOLOGY_RATE_LIMIT=0 as an explicit value while still falling back to the default limit for malformed env values instead of partially parsing them. Landed from contributor PR #39197 by @scoootscooob. Thanks @scoootscooob.
  • -
  • Voice-call/OpenAI Realtime STT config defaults: honor explicit vadThreshold: 0 and silenceDurationMs: 0 instead of silently replacing them with defaults. Landed from contributor PR #39196 by @scoootscooob. Thanks @scoootscooob.
  • -
  • Voice-call/OpenAI TTS speed config: honor explicit speed: 0 instead of silently replacing it with the default speed. Landed from contributor PR #39318 by @ql-wade. Thanks @ql-wade.
  • -
  • launchd/runtime PID parsing: reject pid <= 0 from launchctl print so the daemon state parser no longer treats kernel/non-running sentinel values as real process IDs. Landed from contributor PR #39281 by @mvanhorn. Thanks @mvanhorn.
  • -
  • Cron/file permission hardening: enforce owner-only (0600) cron store/backup/run-log files and harden cron store + run-log directories to 0700, including pre-existing directories from older installs. (#36078) Thanks @aerelune.
  • -
  • Gateway/remote WS break-glass hostname support: honor OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1 for ws:// hostname URLs (not only private IP literals) across onboarding validation and runtime gateway connection checks, while still rejecting public IP literals and non-unicast IPv6 endpoints. (#36930) Thanks @manju-rn.
  • -
  • Routing/binding lookup scalability: pre-index route bindings by channel/account and avoid full binding-list rescans on channel-account cache rollover, preventing multi-second resolveAgentRoute stalls in large binding configurations. (#36915) Thanks @songchenghao.
  • -
  • Browser/session cleanup: track browser tabs opened by session-scoped browser tool runs and close tracked tabs during sessions.reset/sessions.delete runtime cleanup, preventing orphaned tabs and unbounded browser memory growth after session teardown. (#36666) Thanks @Harnoor6693.
  • -
  • Plugin/hook install rollback hardening: stage installs under the canonical install base, validate and run dependency installs before publish, and restore updates by rename instead of deleting the target path, reducing partial-replace and symlink-rebind risk during install failures.
  • -
  • Slack/local file upload allowlist parity: propagate mediaLocalRoots through the Slack send action pipeline so workspace-rooted attachments pass assertLocalMediaAllowed checks while non-allowlisted paths remain blocked. (synthesis: #36656; overlap considered from #36516, #36496, #36493, #36484, #32648, #30888) Thanks @2233admin.
  • -
  • Agents/compaction safeguard pre-check: skip embedded compaction before entering the Pi SDK when a session has no real conversation messages, avoiding unnecessary LLM API calls on idle sessions. (#36451) thanks @Sid-Qin.
  • -
  • Config/schema cache key stability: build merged schema cache keys with incremental hashing to avoid large single-string serialization and prevent RangeError: Invalid string length on high-cardinality plugin/channel metadata. (#36603) Thanks @powermaster888.
  • -
  • iMessage/cron completion announces: strip leaked inline reply tags (for example [[reply_to:6100]]) from user-visible completion text so announcement deliveries do not expose threading metadata. (#24600) Thanks @vincentkoc.
  • -
  • Control UI/iMessage duplicate reply routing: keep internal webchat turns on dispatcher delivery (instead of origin-channel reroute) so Control UI chats do not duplicate replies into iMessage, while preserving webchat-provider relayed routing for external surfaces. Fixes #33483. Thanks @alicexmolt.
  • -
  • Sessions/daily reset transcript archival: archive prior transcript files during stale-session scheduled/daily resets by capturing the previous session entry before rollover, preventing orphaned transcript files on disk. (#35493) Thanks @byungsker.
  • -
  • Feishu/group slash command detection: normalize group mention wrappers before command-authorization probing so mention-prefixed commands (for example @Bot/model and @Bot /reset) are recognized as gateway commands instead of being forwarded to the agent. (#35994) Thanks @liuxiaopai-ai.
  • -
  • Control UI/auth token separation: keep the shared gateway token in browser auth validation while reserving cached device tokens for signed device payloads, preventing false device token mismatch disconnects after restart/rotation. Landed from contributor PR #37382 by @FradSer. Thanks @FradSer.
  • -
  • Gateway/browser auth reconnect hardening: stop counting missing token/password submissions as auth rate-limit failures, and stop auto-reconnecting Control UI clients on non-recoverable auth errors so misconfigured browser tabs no longer lock out healthy sessions. Landed from contributor PR #38725 by @ademczuk. Thanks @ademczuk.
  • -
  • Gateway/service token drift repair: stop persisting shared auth tokens into installed gateway service units, flag stale embedded service tokens for reinstall, and treat tokenless service env as canonical so token rotation/reboot flows stay aligned with config/env resolution. Landed from contributor PR #28428 by @l0cka. Thanks @l0cka.
  • -
  • Control UI/agents-page selection: keep the edited agent selected after saving agent config changes and reloading the agents list, so /agents no longer snaps back to the default agent. Landed from contributor PR #39301 by @MumuTW. Thanks @MumuTW.
  • -
  • Gateway/auth follow-up hardening: preserve systemd EnvironmentFile= precedence/source provenance in daemon audits and doctor repairs, block shared-password override flows from piggybacking cached device tokens, and fail closed when config-first gateway SecretRefs cannot resolve. Follow-up to #39241.
  • -
  • Agents/context pruning: guard assistant thinking/text char estimation against malformed blocks (missing thinking/text strings or null entries) so pruning no longer crashes with malformed provider content. (openclaw#35146) thanks @Sid-Qin.
  • -
  • Agents/transcript policy: set preserveSignatures to Anthropic-only handling in resolveTranscriptPolicy so Anthropic thinking signatures are preserved while non-Anthropic providers remain unchanged. (#32813) thanks @Sid-Qin.
  • -
  • Agents/schema cleaning: detect Venice + Grok model IDs as xAI-proxied targets so unsupported JSON Schema keywords are stripped before requests, preventing Venice/Grok Invalid arguments failures. (openclaw#35355) thanks @Sid-Qin.
  • -
  • Skills/native command deduplication: centralize skill command dedupe by canonical skillName in listSkillCommandsForAgents so duplicate suffixed variants (for example _2) are no longer surfaced across interfaces outside Discord. (#27521) thanks @shivama205.
  • -
  • Agents/xAI tool-call argument decoding: decode HTML-entity encoded xAI/Grok tool-call argument values (&, ", <, >, numeric entities) before tool execution so commands with shell operators and quotes no longer fail with parse errors. (#35276) Thanks @Sid-Qin.
  • -
  • Linux/WSL2 daemon install hardening: add regression coverage for WSL environment detection, WSL-specific systemd guidance, and systemctl --user is-enabled failure paths so WSL2/headless onboarding keeps treating bus-unavailable probes as non-fatal while preserving real permission errors. Related: #36495. Thanks @vincentkoc.
  • -
  • Linux/systemd status and degraded-session handling: treat degraded-but-reachable systemctl --user status results as available, preserve early errors for truly unavailable user-bus cases, and report externally managed running services as running instead of not installed. Thanks @vincentkoc.
  • -
  • Agents/thinking-tag promotion hardening: guard promoteThinkingTagsToBlocks against malformed assistant content entries (null/undefined) before block.type reads so malformed provider payloads no longer crash session processing while preserving pass-through behavior. (#35143) thanks @Sid-Qin.
  • -
  • Gateway/Control UI version reporting: align runtime and browser client version metadata to avoid dev placeholders, wait for bootstrap version before first UI websocket connect, and only forward bootstrap serverVersion to same-origin gateway targets to prevent cross-target version leakage. (from #35230, #30928, #33928) Thanks @Sid-Qin, @joelnishanth, and @MoerAI.
  • -
  • Control UI/markdown parser crash fallback: catch marked.parse() failures and fall back to escaped plain-text
     rendering so malformed recursive markdown no longer crashes Control UI session rendering on load. (#36445) Thanks @BinHPdev.
  • -
  • Control UI/markdown fallback regression coverage: add explicit regression assertions for parser-error fallback behavior so malformed markdown no longer risks reintroducing hard-crash rendering paths in future markdown/parser upgrades. (#36445) Thanks @BinHPdev.
  • -
  • Web UI/config form: treat additionalProperties: true object schemas as editable map entries instead of unsupported fields so Accounts-style maps stay editable in form mode. (#35380, supersedes #32072) Thanks @stakeswky and @liuxiaopai-ai.
  • -
  • Feishu/streaming card delivery synthesis: unify snapshot and delta streaming merge semantics, apply overlap-aware final merge, suppress duplicate final text delivery (including text+media final packets), prefer topic-thread message.reply routing when a reply target exists, and tune card print cadence to avoid duplicate incremental rendering. (from #33245, #32896, #33840) Thanks @rexl2018, @kcinzgg, and @aerelune.
  • -
  • Feishu/group mention detection: carry startup-probed bot display names through monitor dispatch so requireMention checks compare against current bot identity instead of stale config names, fixing missed @bot handling in groups while preserving multi-bot false-positive guards. (#36317, #34271) Thanks @liuxiaopai-ai.
  • -
  • Security/dependency audit: patch transitive Hono vulnerabilities by pinning hono to 4.12.5 and @hono/node-server to 1.19.10 in production resolution paths. Thanks @shakkernerd.
  • -
  • Security/dependency audit: bump tar to 7.5.10 (from 7.5.9) to address the high-severity hardlink path traversal advisory (GHSA-qffp-2rhf-9h96). Thanks @shakkernerd.
  • -
  • Cron/announce delivery robustness: bypass pending-descendant announce guards for cron completion sends, ensure named-agent announce routes have outbound session entries, and fall back to direct delivery only when an announce send was actually attempted and failed. (from #35185, #32443, #34987) Thanks @Sid-Qin, @scoootscooob, and @bmendonca3.
  • -
  • Cron/announce best-effort fallback: run direct outbound fallback after attempted announce failures even when delivery is configured as best-effort, so Telegram cron sends are not left as attempted-but-undelivered after cron announce delivery failed warnings.
  • -
  • Auto-reply/system events: restore runtime system events to the message timeline (System: lines), preserve think-hint parsing with prepended events, and carry events into deferred followup/collect/steer-backlog prompts to keep cache behavior stable without dropping queued metadata. (#34794) Thanks @anisoptera.
  • -
  • Security/audit account handling: avoid prototype-chain account IDs in audit validation by using own-property checks for accounts. (#34982) Thanks @HOYALIM.
  • -
  • Cron/restart catch-up semantics: replay interrupted recurring jobs and missed immediate cron slots on startup without replaying interrupted one-shot jobs, with guarded missed-slot probing to avoid malformed-schedule startup aborts and duplicate-trigger drift after restart. (from #34466, #34896, #34625, #33206) Thanks @dunamismax, @dsantoreis, @Octane0411, and @Sid-Qin.
  • -
  • Venice/provider onboarding hardening: align per-model Venice completion-token limits with discovery metadata, clamp untrusted discovery values to safe bounds, sync the static Venice fallback catalog with current live model metadata, and disable tool wiring for Venice models that do not support function calling so default Venice setups no longer fail with max_completion_tokens or unsupported-tools 400s. Fixes #38168. Thanks @Sid-Qin, @powermaster888 and @vincentkoc.
  • -
  • Agents/session usage tracking: preserve accumulated usage metadata on embedded Pi runner error exits so failed turns still update session totalTokens from real usage instead of stale prior values. (#34275) thanks @RealKai42.
  • -
  • Slack/reaction thread context routing: carry Slack native DM channel IDs through inbound context and threading tool resolution so reaction targets resolve consistently for DM To=user:* sessions (including toolContext.currentChannelId fallback behavior). (from #34831; overlaps #34440, #34502, #34483, #32754) Thanks @dunamismax.
  • -
  • Subagents/announce completion scoping: scope nested direct-child completion aggregation to the current requester run window, harden frozen completion capture for deterministic descendant synthesis, and route completion announce delivery through parent-agent announce turns with provenance-aware internal events. (#35080) Thanks @tyler6204.
  • -
  • Nodes/system.run approval hardening: use explicit argv-mutation signaling when regenerating prepared rawCommand, and cover the system.run.prepare -> system.run handoff so direct PATH-based nodes.run commands no longer fail with rawCommand does not match command. (#33137) thanks @Sid-Qin.
  • -
  • Models/custom provider headers: propagate models.providers..headers across inline, fallback, and registry-found model resolution so header-authenticated proxies consistently receive configured request headers. (#27490) thanks @Sid-Qin.
  • -
  • Ollama/remote provider auth fallback: synthesize a local runtime auth key for explicitly configured models.providers.ollama entries that omit apiKey, so remote Ollama endpoints run without requiring manual dummy-key setup while preserving env/profile/config key precedence and missing-config failures. (#11283) Thanks @cpreecs.
  • -
  • Ollama/custom provider headers: forward resolved model headers into native Ollama stream requests so header-authenticated Ollama proxies receive configured request headers. (#24337) thanks @echoVic.
  • -
  • Ollama/compaction and summarization: register custom api: "ollama" handling for compaction, branch-style internal summarization, and TTS text summarization on current main, so native Ollama models no longer fail with No API provider registered for api: ollama outside the main run loop. Thanks @JaviLib.
  • -
  • Daemon/systemd install robustness: treat systemctl --user is-enabled exit-code-4 not-found responses as not-enabled by combining stderr/stdout detail parsing, so Ubuntu fresh installs no longer fail with systemctl is-enabled unavailable. (#33634) Thanks @Yuandiaodiaodiao.
  • -
  • Slack/system-event session routing: resolve reaction/member/pin/interaction system-event session keys through channel/account bindings (with sender-aware DM routing) so inbound Slack events target the correct agent session in multi-account setups instead of defaulting to agent:main. (#34045) Thanks @paulomcg, @daht-mad and @vincentkoc.
  • -
  • Slack/native streaming markdown conversion: stop pre-normalizing text passed to Slack native markdown_text in streaming start/append/stop paths to prevent Markdown style corruption from double conversion. (#34931)
  • -
  • Gateway/HTTP tools invoke media compatibility: preserve raw media payload access for direct /tools/invoke clients by allowing media nodes invoke commands only in HTTP tool context, while keeping agent-context media invoke blocking to prevent base64 prompt bloat. (#34365) Thanks @obviyus.
  • -
  • Security/archive ZIP hardening: extract ZIP entries via same-directory temp files plus atomic rename, then re-open and reject post-rename hardlink alias races outside the destination root.
  • -
  • Agents/Nodes media outputs: add dedicated photos_latest action handling, block media-returning nodes invoke commands, keep metadata-only camera.list invoke allowed, and normalize empty photos_latest results to a consistent response shape to prevent base64 context bloat. (#34332) Thanks @obviyus.
  • -
  • TUI/session-key canonicalization: normalize openclaw tui --session values to lowercase so uppercase session names no longer drop real-time streaming updates due to gateway/TUI key mismatches. (#33866, #34013) thanks @lynnzc.
  • -
  • iMessage/echo loop hardening: strip leaked assistant-internal scaffolding from outbound iMessage replies, drop reflected assistant-content messages before they re-enter inbound processing, extend echo-cache text retention for delayed reflections, and suppress repeated loop traffic before it amplifies into queue overflow. (#33295) Thanks @joelnishanth.
  • -
  • Skills/workspace boundary hardening: reject workspace and extra-dir skill roots or SKILL.md files whose realpath escapes the configured source root, and skip syncing those escaped skills into sandbox workspaces.
  • -
  • Outbound/send config threading: pass resolved SecretRef config through outbound adapters and helper send paths so send flows do not reload unresolved runtime config. (#33987) Thanks @joshavant.
  • -
  • gateway: harden shared auth resolution across systemd, discord, and node host (#39241) Thanks @joshavant.
  • -
  • Secrets/models.json persistence hardening: keep SecretRef-managed api keys + headers from persisting in generated models.json, expand audit/apply coverage, and harden marker handling/serialization. (#38955) Thanks @joshavant.
  • -
  • Sessions/subagent attachments: remove attachments[].content.maxLength from sessions_spawn schema to avoid llama.cpp GBNF repetition overflow, and preflight UTF-8 byte size before buffer allocation while keeping runtime file-size enforcement unchanged. (#33648) Thanks @anisoptera.
  • -
  • Runtime/tool-state stability: recover from dangling Anthropic tool_use after compaction, serialize long-running Discord handler runs without blocking new inbound events, and prevent stale busy snapshots from suppressing stuck-channel recovery. (from #33630, #33583) Thanks @kevinWangSheng and @theotarr.
  • -
  • ACP/Discord startup hardening: clean up stuck ACP worker children on gateway restart, unbind stale ACP thread bindings during Discord startup reconciliation, and add per-thread listener watchdog timeouts so wedged turns cannot block later messages. (#33699) Thanks @dutifulbob.
  • -
  • Extensions/media local-root propagation: consistently forward mediaLocalRoots through extension sendMedia adapters (Google Chat, Slack, iMessage, Signal, WhatsApp), preserving non-local media behavior while restoring local attachment resolution from configured roots. Synthesis of #33581, #33545, #33540, #33536, #33528. Thanks @bmendonca3.
  • -
  • Gateway/plugin HTTP auth hardening: require gateway auth when any overlapping matched route needs it, block mixed-auth fallthrough at dispatch, and reject mixed-auth exact/prefix route overlaps during plugin registration.
  • -
  • Feishu/video media send contract: keep mp4-like outbound payloads on msg_type: "media" (including reply and reply-in-thread paths) so videos render as media instead of degrading to file-link behavior, while preserving existing non-video file subtype handling. (from #33720, #33808, #33678) Thanks @polooooo, @dingjianrui, and @kevinWangSheng.
  • -
  • Gateway/security default response headers: add Permissions-Policy: camera=(), microphone=(), geolocation=() to baseline gateway HTTP security headers for all responses. (#30186) thanks @habakan.
  • -
  • Plugins/startup loading: lazily initialize plugin runtime, split startup-critical plugin SDK imports into openclaw/plugin-sdk/core and openclaw/plugin-sdk/telegram, and preserve api.runtime reflection semantics for plugin compatibility. (#28620) thanks @hmemcpy.
  • -
  • Plugins/startup performance: reduce bursty plugin discovery/manifest overhead with short in-process caches, skip importing bundled memory plugins that are disabled by slot selection, and speed legacy root openclaw/plugin-sdk compatibility via runtime root-alias routing while preserving backward compatibility. Thanks @gumadeiras.
  • -
  • Build/lazy runtime boundaries: replace ineffective dynamic import sites with dedicated lazy runtime boundaries across Slack slash handling, Telegram audit, CLI send deps, memory fallback, and outbound delivery paths while preserving behavior. (#33690) thanks @gumadeiras.
  • -
  • Gateway/password CLI hardening: add openclaw gateway run --password-file, warn when inline --password is used because it can leak via process listings, and document env/file-backed password input as the preferred startup path. Fixes #27948. Thanks @vibewrk and @vincentkoc.
  • -
  • Config/heartbeat legacy-path handling: auto-migrate top-level heartbeat into agents.defaults.heartbeat (with merge semantics that preserve explicit defaults), and keep startup failures on non-migratable legacy entries in the detailed invalid-config path instead of generic migration-failed errors. (#32706) thanks @xiwan.
  • -
  • Plugins/SDK subpath parity: expand plugin SDK subpaths across bundled channels/extensions (Discord, Slack, Signal, iMessage, WhatsApp, LINE, and bundled companion plugins), with build/export/type/runtime wiring so scoped imports resolve consistently in source and dist while preserving compatibility. (#33737) thanks @gumadeiras.
  • -
  • Google/Gemini Flash model selection: switch built-in gemini-flash defaults and docs/examples from the nonexistent google/gemini-3.1-flash-preview ID to the working google/gemini-3-flash-preview, while normalizing legacy OpenClaw config that still uses the old Flash 3.1 alias.
  • -
  • Plugins/bundled scoped-import migration: migrate bundled plugins from monolithic openclaw/plugin-sdk imports to scoped subpaths (or openclaw/plugin-sdk/core) across registration and startup-sensitive runtime files, add CI/release guardrails to prevent regressions, and keep root openclaw/plugin-sdk support for external/community plugins. Thanks @gumadeiras.
  • -
  • Routing/session duplicate suppression synthesis: align shared session delivery-context inheritance, channel-paired route-field merges, and reply-surface target matching so dmScope=main turns avoid cross-surface duplicate replies while thread-aware forwarding keeps intended routing semantics. (from #33629, #26889, #17337, #33250) Thanks @Yuandiaodiaodiao, @kevinwildenradt, @Glucksberg, and @bmendonca3.
  • -
  • Routing/legacy session route inheritance: preserve external route metadata inheritance for legacy channel session keys (agent::: and ...:thread:) so chat.send does not incorrectly fall back to webchat when valid delivery context exists. Follow-up to #33786.
  • -
  • Routing/legacy route guard tightening: require legacy session-key channel hints to match the saved delivery channel before inheriting external routing metadata, preventing custom namespaced keys like agent::work: from inheriting stale non-webchat routes.
  • -
  • Gateway/internal client routing continuity: prevent webchat/TUI/UI turns from inheriting stale external reply routes by requiring explicit deliver: true for external delivery, keeping main-session external inheritance scoped to non-Webchat/UI clients, and honoring configured session.mainKey when identifying main-session continuity. (from #35321, #34635, #35356) Thanks @alexyyyander and @Octane0411.
  • -
  • Security/auth labels: remove token and API-key snippets from user-facing auth status labels so /status and /models do not expose credential fragments. (#33262) thanks @cu1ch3n.
  • -
  • Models/MiniMax portal vision routing: add MiniMax-VL-01 to the minimax-portal provider, route portal image understanding through the MiniMax VLM endpoint, and align media auto-selection plus Telegram sticker description with the shared portal image provider path. (#33953) Thanks @tars90percent.
  • -
  • Auth/credential semantics: align profile eligibility + probe diagnostics with SecretRef/expiry rules and harden browser download atomic writes. (#33733) thanks @joshavant.
  • -
  • Security/audit denyCommands guidance: suggest likely exact node command IDs for unknown gateway.nodes.denyCommands entries so ineffective denylist entries are easier to correct. (#29713) thanks @liquidhorizon88-bot.
  • -
  • Agents/overload failover handling: classify overloaded provider failures separately from rate limits/status timeouts, add short overload backoff before retry/failover, record overloaded prompt/assistant failures as transient auth-profile cooldowns (with probeable same-provider fallback) instead of treating them like persistent auth/billing failures, and keep one-shot cron retry classification aligned so overloaded fallback summaries still count as transient retries.
  • -
  • Docs/security hardening guidance: document Docker DOCKER-USER + UFW policy and add cross-linking from Docker install docs for VPS/public-host setups. (#27613) thanks @dorukardahan.
  • -
  • Docs/security threat-model links: replace relative .md links with Mintlify-compatible root-relative routes in security docs to prevent broken internal navigation. (#27698) thanks @clawdoo.
  • -
  • Plugins/Update integrity drift: avoid false integrity drift prompts when updating npm-installed plugins from unpinned specs, while keeping drift checks for exact pinned versions. (#37179) Thanks @vincentkoc.
  • -
  • iOS/Voice timing safety: guard system speech start/finish callbacks to the active utterance to avoid misattributed start events during rapid stop/restart cycles. (#33304) thanks @mbelinky; original implementation direction by @ngutman.
  • -
  • Gateway/chat.send command scopes: require operator.admin for persistent /config set|unset writes routed through gateway chat clients while keeping /config show available to normal write-scoped operator clients, preserving messaging-channel config command behavior without widening RPC write scope into admin config mutation. Thanks @tdjackey for reporting.
  • -
  • iOS/Talk incremental speech pacing: allow long punctuation-free assistant chunks to start speaking at safe whitespace boundaries so voice responses begin sooner instead of waiting for terminal punctuation. (#33305) thanks @mbelinky; original implementation by @ngutman.
  • -
  • iOS/Watch reply reliability: make watch session activation waiters robust under concurrent requests so status/send calls no longer hang intermittently, and align delegate callbacks with Swift 6 actor safety. (#33306) thanks @mbelinky; original implementation by @Rocuts.
  • -
  • Docs/tool-loop detection config keys: align docs/tools/loop-detection.md examples and field names with the current tools.loopDetection schema to prevent copy-paste validation failures from outdated keys. (#33182) Thanks @Mylszd.
  • -
  • Gateway/session agent discovery: include disk-scanned agent IDs in listConfiguredAgentIds even when agents.list is configured, so disk-only/ACP agent sessions remain visible in gateway session aggregation and listings. (#32831) thanks @Sid-Qin.
  • -
  • Discord/inbound debouncer: skip bot-own MESSAGE_CREATE events before they reach the debounce queue to avoid self-triggered slowdowns in busy servers. Thanks @thewilloftheshadow.
  • -
  • Discord/Agent-scoped media roots: pass mediaLocalRoots through Discord monitor reply delivery (message + component interaction paths) so local media attachments honor per-agent workspace roots instead of falling back to default global roots. Thanks @thewilloftheshadow.
  • -
  • Discord/slash command handling: intercept text-based slash commands in channels, register plugin commands as native, and send fallback acknowledgments for empty slash runs so interactions do not hang. Thanks @thewilloftheshadow.
  • -
  • Discord/thread session lifecycle: reset thread-scoped sessions when a thread is archived so reopening a thread starts fresh without deleting transcript history. Thanks @thewilloftheshadow.
  • -
  • Discord/presence defaults: send an online presence update on ready when no custom presence is configured so bots no longer appear offline by default. Thanks @thewilloftheshadow.
  • -
  • Discord/typing cleanup: stop typing indicators after silent/NO_REPLY runs by marking the run complete before dispatch idle cleanup. Thanks @thewilloftheshadow.
  • -
  • ACP/sandbox spawn parity: block /acp spawn from sandboxed requester sessions with the same host-runtime guard already enforced for sessions_spawn({ runtime: "acp" }), preserving non-sandbox ACP flows while closing the command-path policy gap. Thanks @patte.
  • -
  • Discord/config SecretRef typing: align Discord account token config typing with SecretInput so SecretRef tokens typecheck. (#32490) Thanks @scoootscooob.
  • -
  • Discord/voice messages: request upload slots with JSON fetch calls so voice message uploads no longer fail with content-type errors. Thanks @thewilloftheshadow.
  • -
  • Discord/voice decoder fallback: drop the native Opus dependency and use opusscript for voice decoding to avoid native-opus installs. Thanks @thewilloftheshadow.
  • -
  • Discord/auto presence health signal: add runtime availability-driven presence updates plus connected-state reporting to improve health monitoring and operator visibility. (#33277) Thanks @thewilloftheshadow.
  • -
  • HEIC image inputs: accept HEIC/HEIF input_image sources in Gateway HTTP APIs, normalize them to JPEG before provider delivery, and document the expanded default MIME allowlist. Thanks @vincentkoc.
  • -
  • Gateway/HEIC input follow-up: keep non-HEIC input_image MIME handling unchanged, make HEIC tests hermetic, and enforce chat-completions maxTotalImageBytes against post-normalization image payload size. Thanks @vincentkoc.
  • -
  • Telegram/draft-stream boundary stability: materialize DM draft previews at assistant-message/tool boundaries, serialize lane-boundary callbacks before final delivery, and scope preview cleanup to the active preview so multi-step Telegram streams no longer lose, overwrite, or leave stale preview bubbles. (#33842) Thanks @ngutman.
  • -
  • Telegram/DM draft finalization reliability: require verified final-text draft emission before treating preview finalization as delivered, and fall back to normal payload send when final draft delivery is not confirmed (preventing missing final responses and preserving media/button delivery). (#32118) Thanks @OpenCils.
  • -
  • Telegram/DM draft final delivery: materialize text-only sendMessageDraft previews into one permanent final message and skip duplicate final payload sends, while preserving fallback behavior when materialization fails. (#34318) Thanks @Brotherinlaw-13.
  • -
  • Telegram/DM draft duplicate display: clear stale DM draft previews after materializing the real final message, including threadless fallback when DM topic lookup fails, so partial streaming no longer briefly shows duplicate replies. (#36746) Thanks @joelnishanth.
  • -
  • Telegram/draft preview boundary + silent-token reliability: stabilize answer-lane message boundaries across late-partial/message-start races, preserve/reset finalized preview state at the correct boundaries, and suppress NO_REPLY lead-fragment leaks without broad heartbeat-prefix false positives. (#33169) Thanks @obviyus.
  • -
  • Telegram/native commands commands.allowFrom precedence: make native Telegram commands honor commands.allowFrom as the command-specific authorization source, including group chats, instead of falling back to channel sender allowlists. (#28216) Thanks @toolsbybuddy and @vincentkoc.
  • -
  • Telegram/groupAllowFrom sender-ID validation: restore sender-only runtime validation so negative chat/group IDs remain invalid entries instead of appearing accepted while still being unable to authorize group access. (#37134) Thanks @qiuyuemartin-max and @vincentkoc.
  • -
  • Telegram/native group command auth: authorize native commands in groups and forum topics against groupAllowFrom and per-group/topic sender overrides, while keeping auth rejection replies in the originating topic thread. (#39267) Thanks @edwluo.
  • -
  • Telegram/named-account DMs: restore non-default-account DM routing when a named Telegram account falls back to the default agent by keeping groups fail-closed but deriving a per-account session key for DMs, including identity-link canonicalization and regression coverage for account isolation. (from #32426; fixes #32351) Thanks @chengzhichao-xydt.
  • -
  • Discord/audit wildcard warnings: ignore "\*" wildcard keys when counting unresolved guild channels so doctor/status no longer warns on allow-all configs. (#33125) Thanks @thewilloftheshadow.
  • -
  • Discord/channel resolution: default bare numeric recipients to channels, harden allowlist numeric ID handling with safe fallbacks, and avoid inbound WS heartbeat stalls. (#33142) Thanks @thewilloftheshadow.
  • -
  • Discord/chunk delivery reliability: preserve chunk ordering when using a REST client and retry chunk sends on 429/5xx using account retry settings. (#33226) Thanks @thewilloftheshadow.
  • -
  • Discord/mention handling: add id-based mention formatting + cached rewrites, resolve inbound mentions to display names, and add optional ignoreOtherMentions gating (excluding @everyone/@here). (#33224) Thanks @thewilloftheshadow.
  • -
  • Discord/media SSRF allowlist: allow Discord CDN hostnames (including wildcard domains) in inbound media SSRF policy to prevent proxy/VPN fake-ip blocks. (#33275) Thanks @thewilloftheshadow.
  • -
  • Telegram/device pairing notifications: auto-arm one-shot notify on /pair qr, auto-ping on new pairing requests, and add manual fallback via /pair approve latest if the ping does not arrive. (#33299) thanks @mbelinky.
  • -
  • Exec heartbeat routing: scope exec-triggered heartbeat wakes to agent session keys so unrelated agents are no longer awakened by exec events, while preserving legacy unscoped behavior for non-canonical session keys. (#32724) thanks @altaywtf
  • -
  • macOS/Tailscale remote gateway discovery: add a Tailscale Serve fallback peer probe path (wss://.ts.net) when Bonjour and wide-area DNS-SD discovery return no gateways, and refresh both discovery paths from macOS onboarding. (#32860) Thanks @ngutman.
  • -
  • iOS/Gateway keychain hardening: move gateway metadata and TLS fingerprints to device keychain storage with safer migration behavior and rollback-safe writes to reduce credential loss risk during upgrades. (#33029) thanks @mbelinky.
  • -
  • iOS/Concurrency stability: replace risky shared-state access in camera and gateway connection paths with lock-protected access patterns to reduce crash risk under load. (#33241) thanks @mbelinky.
  • -
  • iOS/Security guardrails: limit production API-key sourcing to app config and make deep-link confirmation prompts safer by coalescing queued requests instead of silently dropping them. (#33031) thanks @mbelinky.
  • -
  • iOS/TTS playback fallback: keep voice playback resilient by switching from PCM to MP3 when provider format support is unavailable, while avoiding sticky fallback on generic local playback errors. (#33032) thanks @mbelinky.
  • -
  • Plugin outbound/text-only adapter compatibility: allow direct-delivery channel plugins that only implement sendText (without sendMedia) to remain outbound-capable, gracefully fall back to text delivery for media payloads when sendMedia is absent, and fail explicitly for media-only payloads with no text fallback. (#32788) thanks @liuxiaopai-ai.
  • -
  • Telegram/multi-account default routing clarity: warn only for ambiguous (2+) account setups without an explicit default, add openclaw doctor warnings for missing/invalid multi-account defaults across channels, and document explicit-default guidance for channel routing and Telegram config. (#32544) thanks @Sid-Qin.
  • -
  • Telegram/plugin outbound hook parity: run message_sending + message_sent in Telegram reply delivery, include reply-path hook metadata (mediaUrls, threadId), and report message_sent.success=false when hooks blank text and no outbound message is delivered. (#32649) Thanks @KimGLee.
  • -
  • CLI/Coding-agent reliability: switch default claude-cli non-interactive args to --permission-mode bypassPermissions, auto-normalize legacy --dangerously-skip-permissions backend overrides to the modern permission-mode form, align coding-agent + live-test docs with the non-PTY Claude path, and emit session system-event heartbeat notices when CLI watchdog no-output timeouts terminate runs. (#28610, #31149, #34055). Thanks @niceysam, @cryptomaltese and @vincentkoc.
  • -
  • Gateway/OpenAI chat completions: parse active-turn image_url content parts (including parameterized data URIs and guarded URL sources), forward them as multimodal images, accept image-only user turns, enforce per-request image-part/byte budgets, default URL-based image fetches to disabled unless explicitly enabled by config, and redact image base64 data in cache-trace/provider payload diagnostics. (#17685) Thanks @vincentkoc
  • -
  • ACP/ACPX session bootstrap: retry with sessions new when sessions ensure returns no session identifiers so ACP spawns avoid NO_SESSION/ACP_TURN_FAILED failures on affected agents. (#28786, #31338, #34055). Thanks @Sid-Qin and @vincentkoc.
  • -
  • ACP/sessions_spawn parent stream visibility: add streamTo: "parent" for runtime: "acp" to forward initial child-run progress/no-output/completion updates back into the requester session as system events (instead of direct child delivery), and emit a tail-able session-scoped relay log (.acp-stream.jsonl, returned as streamLogPath when available), improving orchestrator visibility for blocked or long-running harness turns. (#34310, #29909; reopened from #34055). Thanks @vincentkoc.
  • -
  • Agents/bootstrap truncation warning handling: unify bootstrap budget/truncation analysis across embedded + CLI runtime, /context, and openclaw doctor; add agents.defaults.bootstrapPromptTruncationWarning (off|once|always, default once) and persist warning-signature metadata so truncation warnings are consistent and deduped across turns. (#32769) Thanks @gumadeiras.
  • -
  • Agents/Skills runtime loading: propagate run config into embedded attempt and compaction skill-entry loading so explicitly enabled bundled companion skills are discovered consistently when skill snapshots do not already provide resolved entries. Thanks @gumadeiras.
  • -
  • Agents/Session startup date grounding: substitute YYYY-MM-DD placeholders in startup/post-compaction AGENTS context and append runtime current-time lines for /new and /reset prompts so daily-memory references resolve correctly. (#32381) Thanks @chengzhichao-xydt.
  • -
  • Agents/Compaction template heading alignment: update AGENTS template section names to Session Startup/Red Lines and keep legacy Every Session/Safety fallback extraction so post-compaction context remains intact across template versions. (#25098) thanks @echoVic.
  • -
  • Agents/Compaction continuity: expand staged-summary merge instructions to preserve active task status, batch progress, latest user request, and follow-up commitments so compaction handoffs retain in-flight work context. (#8903) thanks @joetomasone.
  • -
  • Agents/Compaction safeguard structure hardening: require exact fallback summary headings, sanitize untrusted compaction instruction text before prompt embedding, and keep structured sections when preserving all turns. (#25555) thanks @rodrigouroz.
  • -
  • Gateway/status self version reporting: make Gateway self version in openclaw status prefer runtime VERSION (while preserving explicit OPENCLAW_VERSION override), preventing stale post-upgrade app version output. (#32655) thanks @liuxiaopai-ai.
  • -
  • Memory/QMD index isolation: set QMD_CONFIG_DIR alongside XDG_CONFIG_HOME so QMD config state stays per-agent despite upstream XDG handling bugs, preventing cross-agent collection indexing and excess disk/CPU usage. (#27028) thanks @HenryLoenwind.
  • -
  • Memory/QMD collection safety: stop destructive collection rebinds when QMD collection list only reports names without path metadata, preventing memory search from dropping existing collections if re-add fails. (#36870) Thanks @Adnannnnnnna.
  • -
  • Memory/QMD duplicate-document recovery: detect UNIQUE constraint failed: documents.collection, documents.path update failures, rebuild managed collections once, and retry update so periodic QMD syncs recover instead of failing every run; includes regression coverage to avoid over-matching unrelated unique constraints. (#27649) Thanks @MiscMich.
  • -
  • Memory/local embedding initialization hardening: add regression coverage for transient initialization retry and mixed embedQuery + embedBatch concurrent startup to lock single-flight initialization behavior. (#15639) thanks @SubtleSpark.
  • -
  • CLI/Coding-agent reliability: switch default claude-cli non-interactive args to --permission-mode bypassPermissions, auto-normalize legacy --dangerously-skip-permissions backend overrides to the modern permission-mode form, align coding-agent + live-test docs with the non-PTY Claude path, and emit session system-event heartbeat notices when CLI watchdog no-output timeouts terminate runs. Related to #28261. Landed from contributor PRs #28610 and #31149. Thanks @niceysam, @cryptomaltese and @vincentkoc.
  • -
  • ACP/ACPX session bootstrap: retry with sessions new when sessions ensure returns no session identifiers so ACP spawns avoid NO_SESSION/ACP_TURN_FAILED failures on affected agents. Related to #28786. Landed from contributor PR #31338. Thanks @Sid-Qin and @vincentkoc.
  • -
  • LINE/auth boundary hardening synthesis: enforce strict LINE webhook authn/z boundary semantics across pairing-store account scoping, DM/group allowlist separation, fail-closed webhook auth/runtime behavior, and replay/duplication controls (including in-flight replay reservation and post-success dedupe marking). (from #26701, #26683, #25978, #17593, #16619, #31990, #26047, #30584, #18777) Thanks @bmendonca3, @davidahmann, @harshang03, @haosenwang1018, @liuxiaopai-ai, @coygeek, and @Takhoffman.
  • -
  • LINE/media download synthesis: fix file-media download handling and M4A audio classification across overlapping LINE regressions. (from #26386, #27761, #27787, #29509, #29755, #29776, #29785, #32240) Thanks @kevinWangSheng, @loiie45e, @carrotRakko, @Sid-Qin, @codeafridi, and @bmendonca3.
  • -
  • LINE/context and routing synthesis: fix group/room peer routing and command-authorization context propagation, and keep processing later events in mixed-success webhook batches. (from #21955, #24475, #27035, #28286) Thanks @lailoo, @mcaxtr, @jervyclaw, @Glucksberg, and @Takhoffman.
  • -
  • LINE/status/config/webhook synthesis: fix status false positives from snapshot/config state and accept LINE webhook HEAD probes for compatibility. (from #10487, #25726, #27537, #27908, #31387) Thanks @BlueBirdBack, @stakeswky, @loiie45e, @puritysb, and @mcaxtr.
  • -
  • LINE cleanup/test follow-ups: fold cleanup/test learnings into the synthesis review path while keeping runtime changes focused on regression fixes. (from #17630, #17289) Thanks @Clawborn and @davidahmann.
  • -
  • Mattermost/interactive buttons: add interactive button send/callback support with directory-based channel/user target resolution, and harden callbacks via account-scoped HMAC verification plus sender-scoped DM routing. (#19957) thanks @tonydehnke.
  • -
  • Feishu/groupPolicy legacy alias compatibility: treat legacy groupPolicy: "allowall" as open in both schema parsing and runtime policy checks so intended open-group configs no longer silently drop group messages when groupAllowFrom is empty. (from #36358) Thanks @Sid-Qin.
  • -
  • Mattermost/plugin SDK import policy: replace remaining monolithic openclaw/plugin-sdk imports in Mattermost mention-gating paths/tests with scoped subpaths (openclaw/plugin-sdk/compat and openclaw/plugin-sdk/mattermost) so pnpm check passes lint:plugins:no-monolithic-plugin-sdk-entry-imports on baseline. (#36480) Thanks @Takhoffman.
  • -
  • Telegram/polls: add Telegram poll action support to channel action discovery and tool/CLI poll flows, with multi-account discoverability gated to accounts that can actually execute polls (sendMessage + poll). (#36547) thanks @gumadeiras.
  • -
  • Agents/failover cooldown classification: stop treating generic cooling down text as provider rate_limit so healthy models no longer show false global cooldown/rate-limit warnings while explicit model_cooldown markers still trigger failover. (#32972) thanks @stakeswky.
  • -
  • Agents/failover service-unavailable handling: stop treating bare proxy/CDN service unavailable errors as provider overload while keeping them retryable via the timeout/failover path, so transient outages no longer show false rate-limit warnings or block fallback. (#36646) thanks @jnMetaCode.
  • -
  • Plugins/HTTP route migration diagnostics: rewrite legacy api.registerHttpHandler(...) loader failures into actionable migration guidance so doctor/plugin diagnostics point operators to api.registerHttpRoute(...) or registerPluginHttpRoute(...). (#36794) Thanks @vincentkoc
  • -
  • Doctor/Heartbeat upgrade diagnostics: warn when heartbeat delivery is configured with an implicit directPolicy so upgrades pin direct/DM behavior explicitly instead of relying on the current default. (#36789) Thanks @vincentkoc.
  • -
  • Agents/current-time UTC anchor: append a machine-readable UTC suffix alongside local Current time: lines in shared cron-style prompt contexts so agents can compare UTC-stamped workspace timestamps without doing timezone math. (#32423) thanks @jriff.
  • -
  • Ollama/local model handling: preserve explicit lower contextWindow / maxTokens overrides during merge refresh, and keep native Ollama streamed replies from surfacing fallback thinking / reasoning text once real content starts streaming. (#39292) Thanks @vincentkoc.
  • -
  • TUI/webchat command-owner scope alignment: treat internal-channel gateway sessions with operator.admin as owner-authorized in command auth, restoring cron/gateway/connector tool access for affected TUI/webchat sessions while keeping external channels on identity-based owner checks. (from #35666, #35673, #35704) Thanks @Naylenv, @Octane0411, and @Sid-Qin.
  • -
  • Discord/inbound timeout isolation: separate inbound worker timeout tracking from listener timeout budgets so queued Discord replies are no longer dropped when listener watchdog windows expire mid-run. (#36602) Thanks @dutifulbob.
  • -
  • Memory/doctor SecretRef handling: treat SecretRef-backed memory-search API keys as configured, and fail embedding setup with explicit unresolved-secret errors instead of crashing. (#36835) Thanks @joshavant.
  • -
  • Memory/flush default prompt: ban timestamped variant filenames during default memory flush runs so durable notes stay in the canonical daily memory/YYYY-MM-DD.md file. (#34951) thanks @zerone0x.
  • -
  • Agents/reply delivery timing: flush embedded Pi block replies before waiting on compaction retries so already-generated assistant replies reach channels before compaction wait completes. (#35489) thanks @Sid-Qin.
  • -
  • Agents/gateway config guidance: stop exposing config.schema through the agent gateway tool, remove prompt/docs guidance that told agents to call it, and keep agents on config.get plus config.patch/config.apply for config changes. (#7382) thanks @kakuteki.
  • -
  • Provider/KiloCode: Keep duplicate models after malformed discovery rows, and strip legacy reasoning_effort when proxy reasoning injection is skipped. (#32352) Thanks @pandemicsyn and @vincentkoc.
  • -
  • Agents/failover: classify periodic provider limit exhaustion text (for example Weekly/Monthly Limit Exhausted) as rate_limit while keeping explicit 402 Payment Required variants in billing, so failover continues without misclassifying billing-wrapped quota errors. (#33813) thanks @zhouhe-xydt.
  • -
  • Mattermost/interactive button callbacks: allow external callback base URLs and stop requiring loopback-origin requests so button clicks work when Mattermost reaches the gateway over Tailscale, LAN, or a reverse proxy. (#37543) thanks @mukhtharcm.
  • -
  • Gateway/chat.send route inheritance: keep explicit external delivery for channel-scoped sessions while preventing shared-main and other channel-agnostic webchat sessions from inheriting stale external routes, so Control UI replies stay on webchat without breaking selected channel-target sessions. (#34669) Thanks @vincentkoc.
  • -
  • Telegram/Discord media upload caps: make outbound uploads honor channel mediaMaxMb config, raise Telegram's default media cap to 100MB, and remove MIME fallback limits that kept some Telegram uploads at 16MB. Thanks @vincentkoc.
  • -
  • Skills/nano-banana-pro resolution override: respect explicit --resolution values during image editing and only auto-detect output size from input images when the flag is omitted. (#36880) Thanks @shuofengzhang and @vincentkoc.
  • -
  • Skills/openai-image-gen CLI validation: validate --background and --style inputs early, normalize supported values, and warn when those flags are ignored for incompatible models. (#36762) Thanks @shuofengzhang and @vincentkoc.
  • -
  • Skills/openai-image-gen output formats: validate --output-format values early, normalize aliases like jpg -> jpeg, and warn when the flag is ignored for incompatible models. (#36648) Thanks @shuofengzhang and @vincentkoc.
  • -
  • ACP/skill env isolation: strip skill-injected API keys from ACP harness child-process environments so tools like Codex CLI keep their own auth flow instead of inheriting billed provider keys from active skills. (#36316) Thanks @taw0002 and @vincentkoc.
  • -
  • WhatsApp media upload caps: make outbound media sends and auto-replies honor channels.whatsapp.mediaMaxMb with per-account overrides so inbound and outbound limits use the same channel config. Thanks @vincentkoc.
  • -
  • Windows/Plugin install: when OpenClaw runs on Windows via Bun and npm-cli.js is not colocated with the runtime binary, fall back to npm.cmd/npx.cmd through the existing cmd.exe wrapper so openclaw plugins install no longer fails with spawn EINVAL. (#38056) Thanks @0xlin2023.
  • -
  • Telegram/send retry classification: retry grammY Network request ... failed after N attempts envelopes in send flows without reclassifying plain Network request ... failed! wrappers as transient, restoring the intended retry path while keeping broad send-context message matching tight. (#38056) Thanks @0xlin2023.
  • -
  • Gateway/probes: keep /health, /healthz, /ready, and /readyz reachable when the Control UI is mounted at /, preserve plugin-owned route precedence on those paths, and make /ready and /readyz report channel-backed readiness with startup grace plus 503 on disconnected managed channels, while /health and /healthz stay shallow liveness probes. (#18446) Thanks @vibecodooor, @mahsumaktas, and @vincentkoc.
  • -
  • Feishu/media downloads: drop invalid timeout fields from SDK method calls now that client-level httpTimeoutMs applies to requests. (#38267) Thanks @ant1eicher and @thewilloftheshadow.
  • -
  • PI embedded runner/Feishu docs: propagate sender identity into embedded attempts so Feishu doc auto-grant restores requester access for embedded-runner executions. (#32915) thanks @cszhouwei.
  • -
  • Agents/usage normalization: normalize missing or partial assistant usage snapshots before compaction accounting so openclaw agent --json no longer crashes when provider payloads omit totalTokens or related usage fields. (#34977) thanks @sp-hk2ldn.
  • -
  • Venice/default model refresh: switch the built-in Venice default to kimi-k2-5, update onboarding aliasing, and refresh Venice provider docs/recommendations to match the current private and anonymized catalog. (from #12964) Fixes #20156. Thanks @sabrinaaquino and @vincentkoc.
  • -
  • Agents/skill API write pacing: add a global prompt guardrail that treats skill-driven external API writes as rate-limited by default, so runners prefer batched writes, avoid tight request loops, and respect 429/Retry-After. Thanks @vincentkoc.
  • -
  • Google Chat/multi-account webhook auth fallback: when channels.googlechat.accounts.default carries shared webhook audience/path settings (for example after config normalization), inherit those defaults for named accounts while preserving top-level and per-account overrides, so inbound webhook verification no longer fails silently for named accounts missing duplicated audience fields. Fixes #38369.
  • -
  • Models/tool probing: raise the tool-capability probe budget from 32 to 256 tokens so reasoning models that spend tokens on thinking before returning a required tool call are less likely to be misclassified as not supporting tools. (#7521) Thanks @jakobdylanc.
  • -
  • Gateway/transient network classification: treat wrapped ...: fetch failed transport messages as transient while avoiding broad matches like Web fetch failed (404): ..., preventing Discord reconnect wrappers from crashing the gateway without suppressing non-network tool failures. (#38530) Thanks @xinhuagu.
  • -
  • ACP/console silent reply suppression: filter ACP NO_REPLY lead fragments and silent-only finals before openclaw agent logging/delivery so console-backed ACP sessions no longer leak NO/NO_REPLY placeholders. (#38436) Thanks @ql-wade.
  • -
  • Feishu/reply delivery reliability: disable block streaming in Feishu reply options so plain-text auto-render replies are no longer silently dropped before final delivery. (#38258) Thanks @xinhuagu.
  • -
  • Agents/reply MEDIA delivery: normalize local assistant MEDIA: paths before block/final delivery, keep media dedupe aligned with message-tool sends, and contain malformed media normalization failures so generated files send reliably instead of falling back to empty responses. (#38572) Thanks @obviyus.
  • -
  • Sessions/bootstrap cache rollover invalidation: clear cached workspace bootstrap snapshots whenever an existing sessionKey rolls to a new sessionId across auto-reply, command, and isolated cron session resolvers, so AGENTS.md/MEMORY.md/USER.md updates are reloaded after daily, idle, or forced session resets instead of staying stale until gateway restart. (#38494) Thanks @LivingInDrm.
  • -
  • Gateway/Telegram polling health monitor: skip stale-socket restarts for Telegram long-polling channels and thread channel identity through shared health evaluation so polling connections are not restarted on the WebSocket stale-socket heuristic. (#38395) Thanks @ql-wade and @Takhoffman.
  • -
  • Daemon/systemd fresh-install probe: check for OpenClaw's managed user unit before running systemctl --user is-enabled, so first-time Linux installs no longer fail on generic missing-unit probe errors. (#38819) Thanks @adaHubble.
  • -
  • Gateway/container lifecycle: allow openclaw gateway stop to SIGTERM unmanaged gateway listeners and openclaw gateway restart to SIGUSR1 a single unmanaged listener when no service manager is installed, so container and supervisor-based deployments are no longer blocked by service disabled no-op responses. Fixes #36137. Thanks @vincentkoc.
  • -
  • Gateway/Windows restart supervision: relaunch task-managed gateways through Scheduled Task with quoted helper-script command paths, distinguish restart-capable supervisors per platform, and stop orphaned Windows gateway children during self-restart. (#38825) Thanks @obviyus.
  • -
  • Telegram/native topic command routing: resolve forum-topic native commands through the same conversation route as inbound messages so topic agentId overrides and bound topic sessions target the active session instead of the default topic-parent session. (#38871) Thanks @obviyus.
  • -
  • Markdown/assistant image hardening: flatten remote markdown images to plain text across the Control UI, exported HTML, and shared Swift chat while keeping inline data:image/... markdown renderable, so model output no longer triggers automatic remote image fetches. (#38895) Thanks @obviyus.
  • -
  • Config/compaction safeguard settings: regression-test agents.defaults.compaction.recentTurnsPreserve through loadConfig() and cover the new help metadata entry so the exposed preserve knob stays wired through schema validation and config UX. (#25557) thanks @rodrigouroz.
  • -
  • iOS/Quick Setup presentation: skip automatic Quick Setup when a gateway is already configured (active connect config, last-known connection, preferred gateway, or manual host), so reconnecting installs no longer get prompted to connect again. (#38964) Thanks @ngutman.
  • -
  • CLI/Docs memory help accuracy: clarify openclaw memory status --deep behavior and align memory command examples/docs with the current search options. (#31803) Thanks @JasonOA888 and @Avi974.
  • -
  • Auto-reply/allowlist store account scoping: keep /allowlist ... --store writes scoped to the selected account and clear legacy unscoped entries when removing default-account store access, preventing cross-account default allowlist bleed-through from legacy pairing-store reads. Thanks @tdjackey for reporting and @vincentkoc for the fix.
  • -
  • Security/Nostr: harden profile mutation/import loopback guards by failing closed on non-loopback forwarded client headers (x-forwarded-for / x-real-ip) and rejecting sec-fetch-site: cross-site; adds regression coverage for proxy-forwarded and browser cross-site mutation attempts.
  • -
  • CLI/bootstrap Node version hint maintenance: replace hardcoded nvm 22 instructions in openclaw.mjs with MIN_NODE_MAJOR interpolation so future minimum-Node bumps keep startup guidance in sync automatically. (#39056) Thanks @onstash.
  • -
  • Discord/native slash command auth: honor commands.allowFrom.discord (and commands.allowFrom["*"]) in guild slash-command pre-dispatch authorization so allowlisted senders are no longer incorrectly rejected as unauthorized. (#38794) Thanks @jskoiz and @thewilloftheshadow.
  • -
  • Outbound/message target normalization: ignore empty legacy to/channelId fields when explicit target is provided so valid target-based sends no longer fail legacy-param validation; includes regression coverage. (#38944) Thanks @Narcooo.
  • -
  • Models/auth token prompts: guard cancelled manual token prompts so Symbol(clack:cancel) values cannot be persisted into auth profiles; adds regression coverage for cancelled models auth paste-token. (#38951) Thanks @MumuTW.
  • -
  • Gateway/loopback announce URLs: treat http:// and https:// aliases with the same loopback/private-network policy as websocket URLs so loopback cron announce delivery no longer fails secure URL validation. (#39064) Thanks @Narcooo.
  • -
  • Models/default provider fallback: when the hardcoded default provider is removed from models.providers, resolve defaults from configured providers instead of reporting stale removed-provider defaults in status output. (#38947) Thanks @davidemanuelDEV.
  • -
  • Agents/cache-trace stability: guard stable stringify against circular references in trace payloads so near-limit payloads no longer crash with Maximum call stack size exceeded; adds regression coverage. (#38935) Thanks @MumuTW.
  • -
  • Extensions/diffs CI stability: add headers to the localReq test helper in extensions/diffs/index.test.ts so forwarding-hint checks no longer crash with req.headers undefined. (supersedes #39063) Thanks @Shennng.
  • -
  • Agents/compaction thresholding: apply agents.defaults.contextTokens cap to the model passed into embedded run and /compact session creation so auto-compaction thresholds use the effective context window, not native model max context. (#39099) Thanks @MumuTW.
  • -
  • Models/merge mode provider precedence: when models.mode: "merge" is active and config explicitly sets a provider baseUrl, keep config as source of truth instead of preserving stale runtime models.json baseUrl values; includes normalized provider-key coverage. (#39103) Thanks @BigUncle.
  • -
  • UI/Control chat tool streaming: render tool events live in webchat without requiring refresh by enabling tool-events capability, fixing stream/event correlation, and resetting/reloading stream state around tool results and terminal events. (#39104) Thanks @jakepresent.
  • -
  • Models/provider apiKey persistence hardening: when a provider apiKey value equals a known provider env var value, persist the canonical env var name into models.json instead of resolved plaintext secrets. (#38889) Thanks @gambletan.
  • -
  • Discord/model picker persistence check: add a short post-dispatch settle delay before reading back session model state so picker confirmations stop reporting false mismatch warnings after successful model switches. (#39105) Thanks @akropp.
  • -
  • Agents/OpenAI WS compat store flag: omit store from response.create payloads when model compat sets supportsStore: false, preventing strict OpenAI-compatible providers from rejecting websocket requests with unknown-field errors. (#39113) Thanks @scoootscooob.
  • -
  • Config/validation log sanitization: sanitize config-validation issue paths/messages before logging so control characters and ANSI escape sequences cannot inject misleading terminal output from crafted config content. (#39116) Thanks @powermaster888.
  • -
  • Agents/compaction counter accuracy: count successful overflow-triggered auto-compactions (willRetry=true) in the compaction counter while still excluding aborted/no-result events, so /status reflects actual safeguard compaction activity. (#39123) Thanks @MumuTW.
  • -
  • Gateway/chat delta ordering: flush buffered assistant deltas before emitting tool start events so pre-tool text is delivered to Control UI before tool cards, avoiding transient text/tool ordering artifacts in streaming. (#39128) Thanks @0xtangping.
  • -
  • Voice-call plugin schema parity: add missing manifest configSchema fields (webhookSecurity, streaming.preStartTimeoutMs|maxPendingConnections|maxPendingConnectionsPerIp|maxConnections, staleCallReaperSeconds) so gateway AJV validation accepts already-supported runtime config instead of failing with additionalProperties errors. (#38892) Thanks @giumex.
  • -
  • Agents/OpenAI WS reconnect retry accounting: avoid double retry scheduling when reconnect failures emit both error and close, so retry budgets track actual reconnect attempts instead of exhausting early. (#39133) Thanks @scoootscooob.
  • -
  • Daemon/Windows schtasks runtime detection: use locale-invariant Last Run Result running codes (0x41301/267009) as the primary running signal so openclaw node status no longer misreports active tasks as stopped on non-English Windows locales. (#39076) Thanks @ademczuk.
  • -
  • Usage/token count formatting: round near-million token counts to millions (1.0m) instead of 1000k, with explicit boundary coverage for 999_499 and 999_500. (#39129) Thanks @CurryMessi.
  • -
  • Gateway/session bootstrap cache invalidation ordering: clear bootstrap snapshots only after active embedded-run shutdown wait completes, preventing dying runs from repopulating stale cache between /new/sessions.reset turns. (#38873) Thanks @MumuTW.
  • -
  • Browser/dispatcher error clarity: preserve dispatcher-side failure context in browser fetch errors while still appending operator guidance and explicit no-retry model hints, preventing misleading "Can't reach service" wrapping and avoiding LLM retry loops. (#39090) Thanks @NewdlDewdl.
  • -
  • Telegram/polling offset safety: confirm persisted offsets before polling startup while validating stored lastUpdateId values as non-negative safe integers (with overflow guards) so malformed offset state cannot cause update skipping/dropping. (#39111) Thanks @MumuTW.
  • -
  • Telegram/status SecretRef read-only resolution: resolve env-backed bot-token SecretRefs in config-only/status inspection while respecting provider source/defaults and env allowlists, so status no longer crashes or reports false-ready tokens for disallowed providers. (#39130) Thanks @neocody.
  • -
  • Agents/OpenAI WS max-token zero forwarding: treat maxTokens: 0 as an explicit value in websocket response.create payloads (instead of dropping it as falsy), with regression coverage for zero-token forwarding. (#39148) Thanks @scoootscooob.
  • -
  • Podman/.env gateway bind precedence: evaluate OPENCLAW_GATEWAY_BIND after sourcing .env in run-openclaw-podman.sh so env-file overrides are honored. (#38785) Thanks @majinyu666.
  • -
  • Models/default alias refresh: bump gpt to openai/gpt-5.4 and Gemini defaults to gemini-3.1 preview aliases (including normalization/default wiring) to track current model IDs. (#38638) Thanks @ademczuk.
  • -
  • Config/env substitution degraded mode: convert missing ${VAR} resolution in config reads from hard-fail to warning-backed degraded behavior, while preventing unresolved placeholders from being accepted as gateway credentials. (#39050) Thanks @akz142857.
  • -
  • Discord inbound listener non-blocking dispatch: make MESSAGE_CREATE listener handoff asynchronous (no per-listener queue blocking), so long runs no longer stall unrelated incoming events. (#39154) Thanks @yaseenkadlemakki.
  • -
  • Daemon/Windows PATH freeze fix: stop persisting install-time PATH snapshots into Scheduled Task scripts so runtime tool lookup follows current host PATH updates; also refresh local TUI history on silent local finals. (#39139) Thanks @Narcooo.
  • -
  • Gateway/systemd service restart hardening: clear stale gateway listeners by explicit run-port before service bind, add restart stale-pid port-override support, tune systemd start/stop/exit handling, and disable detached child mode only in service-managed runtime so cgroup stop semantics clean up descendants reliably. (#38463) Thanks @spirittechie.
  • -
  • Discord/plugin native command aliases: let plugins declare provider-specific slash names so native Discord registration can avoid built-in command collisions; the bundled Talk voice plugin now uses /talkvoice natively on Discord while keeping text /voice.
  • -
  • Daemon/Windows schtasks status normalization: derive runtime state from locale-neutral numeric Last Run Result codes only (without language string matching) and surface unknown when numeric result data is unavailable, preventing locale-specific misclassification drift. (#39153) Thanks @scoootscooob.
  • -
  • Telegram/polling conflict recovery: reset the polling webhookCleared latch on getUpdates 409 conflicts so webhook cleanup re-runs on restart cycles and polling avoids infinite conflict loops. (#39205) Thanks @amittell.
  • -
  • Heartbeat/requests-in-flight scheduling: stop advancing nextDueMs and avoid immediate scheduleNext() timer overrides on requests-in-flight skips, so wake-layer retry cooldowns are honored and heartbeat cadence no longer drifts under sustained contention. (#39182) Thanks @MumuTW.
  • -
  • Memory/SQLite contention resilience: re-apply PRAGMA busy_timeout on every sync-store and QMD connection open so process restarts/reopens no longer revert to immediate SQLITE_BUSY failures under lock contention. (#39183) Thanks @MumuTW.
  • -
  • Gateway/webchat route safety: block webchat/control-ui clients from inheriting stored external delivery routes on channel-scoped sessions (while preserving route inheritance for UI/TUI clients), preventing cross-channel leakage from scoped chats. (#39175) Thanks @widingmarcus-cyber.
  • -
  • Telegram error-surface resilience: return a user-visible fallback reply when dispatch/debounce processing fails instead of going silent, while preserving draft-stream cleanup and best-effort thread-scoped fallback delivery. (#39209) Thanks @riftzen-bit.
  • -
  • Gateway/password auth startup diagnostics: detect unresolved provider-reference objects in gateway.auth.password and fail with a specific bootstrap-secrets error message instead of generic misconfiguration output. (#39230) Thanks @ademczuk.
  • -
  • Agents/OpenAI-responses compatibility: strip unsupported store payload fields when supportsStore=false (including OpenAI-compatible non-OpenAI providers) while preserving server-compaction payload behavior. (#39219) Thanks @ademczuk.
  • -
  • Agents/model fallback visibility: warn when configured model IDs cannot be resolved and fallback is applied, with log-safe sanitization of model text to prevent control-sequence injection in warning output. (#39215) Thanks @ademczuk.
  • -
  • Outbound delivery replay safety: use two-phase delivery ACK markers (.json -> .delivered -> unlink) and startup marker cleanup so crash windows between send and cleanup do not replay already-delivered messages. (#38668) Thanks @Gundam98.
  • -
  • Nodes/system.run approval binding: carry prepared approval plans through gateway forwarding and bind interpreter-style script operands across approval to execution, so post-approval script rewrites are denied while unchanged approved script runs keep working. Thanks @tdjackey for reporting.
  • -
  • Nodes/system.run PowerShell wrapper parsing: treat pwsh/powershell -EncodedCommand forms as shell-wrapper payloads so allowlist mode still requires approval instead of falling back to plain argv analysis. Thanks @tdjackey for reporting.
  • -
  • Control UI/auth error reporting: map generic browser Fetch failed websocket close errors back to actionable gateway auth messages (gateway token mismatch, authentication failed, retry later) so dashboard disconnects stop hiding credential problems. Landed from contributor PR #28608 by @KimGLee. Thanks @KimGLee.
  • -
  • Media/mime unknown-kind handling: return undefined (not "unknown") for missing/unrecognized MIME kinds and use document-size fallback caps for unknown remote media, preventing phantom Signal events from being treated as real messages. (#39199) Thanks @nicolasgrasset.
  • -
  • Nodes/system.run allow-always persistence: honor shell comment semantics during allowlist analysis so #-tailed payloads that never execute are not persisted as trusted follow-up commands. Thanks @tdjackey for reporting.
  • -
  • Signal/inbound attachment fan-in: forward all successfully fetched inbound attachments through MediaPaths/MediaUrls/MediaTypes (instead of only the first), and improve multi-attachment placeholder summaries in mention-gated pending history. (#39212) Thanks @joeykrug.
  • -
  • Nodes/system.run dispatch-wrapper boundary: keep shell-wrapper approval classification active at the depth boundary so env wrapper stacks cannot reach /bin/sh -c execution without the expected approval gate. Thanks @tdjackey for reporting.
  • -
  • Docker/token persistence on reconfigure: reuse the existing .env gateway token during docker-setup.sh reruns and align compose token env defaults, so Docker installs stop silently rotating tokens and breaking existing dashboard sessions. Landed from contributor PR #33097 by @chengzhichao-xydt. Thanks @chengzhichao-xydt.
  • -
  • Agents/strict OpenAI turn ordering: apply assistant-first transcript bootstrap sanitization to strict OpenAI-compatible providers (for example vLLM/Gemma via openai-completions) without adding Google-specific session markers, preventing assistant-first history rejections. (#39252) Thanks @scoootscooob.
  • -
  • Discord/exec approvals gateway auth: pass resolved shared gateway credentials into the Discord exec-approvals gateway client so token-auth installs stop failing approvals with gateway token mismatch. Related to #38179. Thanks @0riginal-claw for the adjacent PR #35147 investigation.
  • -
  • Subagents/workspace inheritance: propagate parent workspace directory to spawned subagent runs so child sessions reliably inherit workspace-scoped instructions (AGENTS.md, SOUL.md, etc.) without exposing workspace override through tool-call arguments. (#39247) Thanks @jasonQin6.
  • -
  • Exec approvals/gateway-node policy: honor explicit ask=off from exec-approvals.json even when runtime defaults are stricter, so trusted full/off setups stop re-prompting on gateway and node exec paths. Landed from contributor PR #26789 by @pandego. Thanks @pandego.
  • -
  • Exec approvals/config fallback: inherit ask from exec-approvals.json when tools.exec.ask is unset, so local full/off defaults no longer fall back to on-miss for exec tool and nodes run. Landed from contributor PR #29187 by @Bartok9. Thanks @Bartok9.
  • -
  • Exec approvals/allow-always shell scripts: persist and match script paths for wrapper invocations like bash scripts/foo.sh while still blocking -c/-s wrapper bypasses. Landed from contributor PR #35137 by @yuweuii. Thanks @yuweuii.
  • -
  • Queue/followup dedupe across drain restarts: dedupe queued redelivery message_id values after queue recreation so busy-session followups no longer duplicate on replayed inbound events. Landed from contributor PR #33168 by @rylena. Thanks @rylena.
  • -
  • Telegram/preview-final edit idempotence: treat message is not modified errors during preview finalization as delivered so partial-stream final replies do not fall back to duplicate sends. Landed from contributor PR #34983 by @HOYALIM. Thanks @HOYALIM.
  • -
  • Telegram/DM streaming transport parity: use message preview transport for all DM streaming lanes so final delivery can edit the active preview instead of sending duplicate finals. Landed from contributor PR #38906 by @gambletan. Thanks @gambletan.
  • -
  • Telegram/DM draft streaming restoration: restore native sendMessageDraft preview transport for DM answer streaming while keeping reasoning on message transport, with regression coverage to keep draft finalization from sending duplicate finals. (#39398) Thanks @obviyus.
  • -
  • Telegram/send retry safety: retry non-idempotent send paths only for pre-connect failures and make custom retry predicates strict, preventing ambiguous reconnect retries from sending duplicate messages. Landed from contributor PR #34238 by @hal-crackbot. Thanks @hal-crackbot.
  • -
  • ACP/run spawn delivery bootstrap: stop reusing requester inline delivery targets for one-shot mode: "run" ACP spawns, so fresh run-mode workers bootstrap in isolation instead of inheriting thread-bound session delivery behavior. (#39014) Thanks @lidamao633.
  • -
  • Discord/DM session-key normalization: rewrite legacy discord:dm:* and phantom direct-message discord:channel: session keys to discord:direct:* when the sender matches, so multi-agent Discord DMs stop falling into empty channel-shaped sessions and resume replying correctly.
  • -
  • Discord/native slash session fallback: treat empty configured bound-session keys as missing so /status and other native commands fall back to the routed slash session and routed channel session instead of blanking Discord session keys in normal channel bindings.
  • -
  • Agents/tool-call dispatch normalization: normalize provider-prefixed tool names before dispatch across toolCall, toolUse, and functionCall blocks, while preserving multi-segment tool suffixes when stripping provider wrappers so malformed-but-recoverable tool names no longer fail with Tool not found. (#39328) Thanks @vincentkoc.
  • -
  • Agents/parallel tool-call compatibility: honor parallel_tool_calls / parallelToolCalls extra params only for openai-completions and openai-responses payloads, preserve higher-precedence alias overrides across config and runtime layers, and ignore invalid non-boolean values so single-tool-call providers like NVIDIA-hosted Kimi stop failing on forced parallel tool-call payloads. (#37048) Thanks @vincentkoc.
  • -
  • Config/invalid-load fail-closed: stop converting INVALID_CONFIG into an empty runtime config, keep valid settings available only through explicit best-effort diagnostic reads, and route read-only CLI diagnostics through that path so unknown keys no longer silently drop security-sensitive config. (#28140) Thanks @bobsahur-robot and @vincentkoc.
  • -
  • Agents/codex-cli sandbox defaults: switch the built-in Codex backend from read-only to workspace-write so spawned coding runs can edit files out of the box. Landed from contributor PR #39336 by @0xtangping. Thanks @0xtangping.
  • -
  • Gateway/health-monitor restart reason labeling: report disconnected instead of stuck for clean channel disconnect restarts, so operator logs distinguish socket drops from genuinely stuck channels. (#36436) Thanks @Sid-Qin.
  • -
  • Control UI/agents-page overrides: auto-create minimal per-agent config entries when editing inherited agents, so model/tool/skill changes enable Save and inherited model fallbacks can be cleared by writing a primary-only override. Landed from contributor PR #39326 by @dunamismax. Thanks @dunamismax.
  • -
  • Gateway/Telegram webhook-mode recovery: add webhookCertPath to re-upload self-signed certificates during webhook registration and skip stale-socket detection for webhook-mode channels, so Telegram webhook setups survive health-monitor restarts. Landed from contributor PR #39313 by @fellanH. Thanks @fellanH.
  • -
  • Discord/config schema parity: add channels.discord.agentComponents to the strict Zod config schema so valid agentComponents.enabled settings (root and account-scoped) no longer fail with unrecognized-key validation errors. Landed from contributor PR #39378 by @gambletan. Thanks @gambletan and @thewilloftheshadow.
  • -
  • ACPX/MCP session bootstrap: inject configured MCP servers into ACP session/new and session/load for acpx-backed sessions, restoring Canva and other external MCP tools. Landed from contributor PR #39337. Thanks @goodspeed-apps.
  • -
  • Control UI/Telegram sender labels: preserve inbound sender labels in sanitized chat history so dashboard user-message groups split correctly and show real group-member names instead of You. (#39414) Thanks @obviyus.
  • -
-

View full changelog

-]]>
- -
- - 2026.3.2 - Tue, 03 Mar 2026 04:30:29 +0000 - https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml - 2026030290 - 2026.3.2 - 15.0 - OpenClaw 2026.3.2 -

Changes

-
    -
  • Secrets/SecretRef coverage: expand SecretRef support across the full supported user-supplied credential surface (64 targets total), including runtime collectors, openclaw secrets planning/apply/audit flows, onboarding SecretInput UX, and related docs; unresolved refs now fail fast on active surfaces while inactive surfaces report non-blocking diagnostics. (#29580) Thanks @joshavant.
  • -
  • Tools/PDF analysis: add a first-class pdf tool with native Anthropic and Google PDF provider support, extraction fallback for non-native models, configurable defaults (agents.defaults.pdfModel, pdfMaxBytesMb, pdfMaxPages), and docs/tests covering routing, validation, and registration. (#31319) Thanks @tyler6204.
  • -
  • Outbound adapters/plugins: add shared sendPayload support across direct-text-media, Discord, Slack, WhatsApp, Zalo, and Zalouser with multi-media iteration and chunk-aware text fallback. (#30144) Thanks @nohat.
  • -
  • Models/MiniMax: add first-class MiniMax-M2.5-highspeed support across built-in provider catalogs, onboarding flows, and MiniMax OAuth plugin defaults, while keeping legacy MiniMax-M2.5-Lightning compatibility for existing configs.
  • -
  • Sessions/Attachments: add inline file attachment support for sessions_spawn (subagent runtime only) with base64/utf8 encoding, transcript content redaction, lifecycle cleanup, and configurable limits via tools.sessions_spawn.attachments. (#16761) Thanks @napetrov.
  • -
  • Telegram/Streaming defaults: default channels.telegram.streaming to partial (from off) so new Telegram setups get live preview streaming out of the box, with runtime fallback to message-edit preview when native drafts are unavailable.
  • -
  • Telegram/DM streaming: use sendMessageDraft for private preview streaming, keep reasoning/answer preview lanes separated in DM reasoning-stream mode. (#31824) Thanks @obviyus.
  • -
  • Telegram/voice mention gating: add optional disableAudioPreflight on group/topic config to skip mention-detection preflight transcription for inbound voice notes where operators want text-only mention checks. (#23067) Thanks @yangnim21029.
  • -
  • CLI/Config validation: add openclaw config validate (with --json) to validate config files before gateway startup, and include detailed invalid-key paths in startup invalid-config errors. (#31220) thanks @Sid-Qin.
  • -
  • Tools/Diffs: add PDF file output support and rendering quality customization controls (fileQuality, fileScale, fileMaxWidth) for generated diff artifacts, and document PDF as the preferred option when messaging channels compress images. (#31342) Thanks @gumadeiras.
  • -
  • Memory/Ollama embeddings: add memorySearch.provider = "ollama" and memorySearch.fallback = "ollama" support, honor models.providers.ollama settings for memory embedding requests, and document Ollama embedding usage. (#26349) Thanks @nico-hoff.
  • -
  • Zalo Personal plugin (@openclaw/zalouser): rebuilt channel runtime to use native zca-js integration in-process, removing external CLI transport usage and keeping QR/login + send/listen flows fully inside OpenClaw.
  • -
  • Plugin SDK/channel extensibility: expose channelRuntime on ChannelGatewayContext so external channel plugins can access shared runtime helpers (reply/routing/session/text/media/commands) without internal imports. (#25462) Thanks @guxiaobo.
  • -
  • Plugin runtime/STT: add api.runtime.stt.transcribeAudioFile(...) so extensions can transcribe local audio files through OpenClaw's configured media-understanding audio providers. (#22402) Thanks @benthecarman.
  • -
  • Plugin hooks/session lifecycle: include sessionKey in session_start/session_end hook events and contexts so plugins can correlate lifecycle callbacks with routing identity. (#26394) Thanks @tempeste.
  • -
  • Hooks/message lifecycle: add internal hook events message:transcribed and message:preprocessed, plus richer outbound message:sent context (isGroup, groupId) for group-conversation correlation and post-transcription automations. (#9859) Thanks @Drickon.
  • -
  • Media understanding/audio echo: add optional tools.media.audio.echoTranscript + echoFormat to send a pre-agent transcript confirmation message to the originating chat, with echo disabled by default. (#32150) Thanks @AytuncYildizli.
  • -
  • Plugin runtime/system: expose runtime.system.requestHeartbeatNow(...) so extensions can wake targeted sessions immediately after enqueueing system events. (#19464) Thanks @AustinEral.
  • -
  • Plugin runtime/events: expose runtime.events.onAgentEvent and runtime.events.onSessionTranscriptUpdate for extension-side subscriptions, and isolate transcript-listener failures so one faulty listener cannot break the entire update fanout. (#16044) Thanks @scifantastic.
  • -
  • CLI/Banner taglines: add cli.banner.taglineMode (random | default | off) to control funny tagline behavior in startup output, with docs + FAQ guidance and regression tests for config override behavior.
  • -
-

Breaking

-
    -
  • BREAKING: Onboarding now defaults tools.profile to messaging for new local installs (interactive + non-interactive). New setups no longer start with broad coding/system tools unless explicitly configured.
  • -
  • BREAKING: ACP dispatch now defaults to enabled unless explicitly disabled (acp.dispatch.enabled=false). If you need to pause ACP turn routing while keeping /acp controls, set acp.dispatch.enabled=false. Docs: https://docs.openclaw.ai/tools/acp-agents
  • -
  • BREAKING: Plugin SDK removed api.registerHttpHandler(...). Plugins must register explicit HTTP routes via api.registerHttpRoute({ path, auth, match, handler }), and dynamic webhook lifecycles should use registerPluginHttpRoute(...).
  • -
  • BREAKING: Zalo Personal plugin (@openclaw/zalouser) no longer depends on external zca-compatible CLI binaries (openzca, zca-cli) for runtime send/listen/login; operators should use openclaw channels login --channel zalouser after upgrade to refresh sessions in the new JS-native path.
  • -
-

Fixes

-
    -
  • Plugin command/runtime hardening: validate and normalize plugin command name/description at registration boundaries, and guard Telegram native menu normalization paths so malformed plugin command specs cannot crash startup (trim on undefined). (#31997) Fixes #31944. Thanks @liuxiaopai-ai.
  • -
  • Telegram: guard duplicate-token checks and gateway startup token normalization when account tokens are missing, preventing token.trim() crashes during status/start flows. (#31973) Thanks @ningding97.
  • -
  • Discord/lifecycle startup status: push an immediate connected status snapshot when the gateway is already connected before lifecycle debug listeners attach, with abort-guarding to avoid contradictory status flips during pre-aborted startup. (#32336) Thanks @mitchmcalister.
  • -
  • Feishu/LINE group system prompts: forward per-group systemPrompt config into inbound context GroupSystemPrompt for Feishu and LINE group/room events so configured group-specific behavior actually applies at dispatch time. (#31713) Thanks @whiskyboy.
  • -
  • Mentions/Slack formatting hardening: add null-safe guards for runtime text normalization paths so malformed/undefined text payloads do not crash mention stripping or mrkdwn conversion. (#31865) Thanks @stone-jin.
  • -
  • Feishu/Plugin sdk compatibility: add safe webhook default fallbacks when loading Feishu monitor state so mixed-version installs no longer crash if older openclaw/plugin-sdk builds omit webhook default constants. (#31606)
  • -
  • Feishu/group broadcast dispatch: add configurable multi-agent group broadcast dispatch with observer-session isolation, cross-account dedupe safeguards, and non-mention history buffering rules that avoid duplicate replay in broadcast/topic workflows. (#29575) Thanks @ohmyskyhigh.
  • -
  • Gateway/Subagent TLS pairing: allow authenticated local gateway-client backend self-connections to skip device pairing while still requiring pairing for non-local/direct-host paths, restoring sessions_spawn with gateway.tls.enabled=true in Docker/LAN setups. Fixes #30740. Thanks @Sid-Qin and @vincentkoc.
  • -
  • Browser/CDP startup diagnostics: include Chrome stderr output and a Linux no-sandbox hint in startup timeout errors so failed launches are easier to diagnose. (#29312) Thanks @veast.
  • -
  • Synology Chat/webhook ingress hardening: enforce bounded body reads (size + timeout) via shared request-body guards to prevent unauthenticated slow-body hangs before token validation. (#25831) Thanks @bmendonca3.
  • -
  • Feishu/Dedup restart resilience: warm persistent dedup state into memory on monitor startup so retry events after gateway restart stay suppressed without requiring initial on-disk probe misses. (#31605)
  • -
  • Voice-call/runtime lifecycle: prevent EADDRINUSE loops by resetting failed runtime promises, making webhook start() idempotent with the actual bound port, and fully cleaning up webhook/tunnel/tailscale resources after startup failures. (#32395) Thanks @scoootscooob.
  • -
  • Gateway/Security hardening: tie loopback-origin dev allowance to actual local socket clients (not Host header claims), add explicit warnings/metrics when gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback accepts websocket origins, harden safe-regex detection for quantified ambiguous alternation patterns (for example (a|aa)+), and bound large regex-evaluation inputs for session-filter and log-redaction paths.
  • -
  • Gateway/Plugin HTTP hardening: require explicit auth for plugin route registration, add route ownership guards for duplicate path+match registrations, centralize plugin path matching/auth logic into dedicated modules, and share webhook target-route lifecycle wiring across channel monitors to avoid stale or conflicting registrations. Thanks @tdjackey for reporting.
  • -
  • Browser/Profile defaults: prefer openclaw profile over chrome in headless/no-sandbox environments unless an explicit defaultProfile is configured. (#14944) Thanks @BenediktSchackenberg.
  • -
  • Gateway/WS security: keep plaintext ws:// loopback-only by default, with explicit break-glass private-network opt-in via OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1; align onboarding/client/call validation and tests to this strict-default policy. (#28670) Thanks @dashed, @vincentkoc.
  • -
  • OpenAI Codex OAuth/TLS prerequisites: add an OAuth TLS cert-chain preflight with actionable remediation for cert trust failures, and gate doctor TLS prerequisite probing to OpenAI Codex OAuth-configured installs (or explicit doctor --deep) to avoid unconditional outbound probe latency. (#32051) Thanks @alexfilatov.
  • -
  • Security/Webhook request hardening: enforce auth-before-body parsing for BlueBubbles and Google Chat webhook handlers, add strict pre-auth body/time budgets for webhook auth paths (including LINE signature verification), and add shared in-flight/request guardrails plus regression tests/lint checks to prevent reintroducing unauthenticated slow-body DoS patterns. Thanks @GCXWLP for reporting.
  • -
  • CLI/Config validation and routing hardening: dedupe openclaw config validate failures to a single authoritative report, expose allowed-values metadata/hints across core Zod and plugin AJV validation (including --json fields), sanitize terminal-rendered validation text, and make command-path parsing root-option-aware across preaction/route/lazy registration (including routed config get/unset with split root options). Thanks @gumadeiras.
  • -
  • Browser/Extension relay reconnect tolerance: keep /json/version and /cdp reachable during short MV3 worker disconnects when attached targets still exist, and retain clients across reconnect grace windows. (#30232) Thanks @Sid-Qin.
  • -
  • CLI/Browser start timeout: honor openclaw browser --timeout start and stop by removing the fixed 15000ms override so slower Chrome startups can use caller-provided timeouts. (#22412, #23427) Thanks @vincentkoc.
  • -
  • Synology Chat/gateway lifecycle: keep startAccount pending until abort for inactive and active account paths to prevent webhook route restart loops under gateway supervision. (#23074) Thanks @druide67.
  • -
  • Exec approvals/allowlist matching: escape regex metacharacters in path-pattern literals (while preserving glob wildcards), preventing crashes on allowlisted executables like /usr/bin/g++ and correctly matching mixed wildcard/literal token paths. (#32162) Thanks @stakeswky.
  • -
  • Synology Chat/webhook compatibility: accept JSON and alias payload fields, allow token resolution from body/query/header sources, and ACK webhook requests with 204 to avoid persistent Processing... states in Synology Chat clients. (#26635) Thanks @memphislee09-source.
  • -
  • Voice-call/Twilio signature verification: retry signature validation across deterministic URL port variants (with/without port) to handle mixed Twilio signing behavior behind reverse proxies and non-standard ports. (#25140) Thanks @drvoss.
  • -
  • Slack/Bolt startup compatibility: remove invalid message.channels and message.groups event registrations so Slack providers no longer crash on startup with Bolt 4.6+; channel/group traffic continues through the unified message handler (channel_type). (#32033) Thanks @mahopan.
  • -
  • Slack/socket auth failure handling: fail fast on non-recoverable auth errors (account_inactive, invalid_auth, etc.) during startup and reconnect instead of retry-looping indefinitely, including unable_to_socket_mode_start error payload propagation. (#32377) Thanks @scoootscooob.
  • -
  • Gateway/macOS LaunchAgent hardening: write Umask=077 in generated gateway LaunchAgent plists so npm upgrades preserve owner-only default file permissions for gateway-created state files. (#31919) Fixes #31905. Thanks @liuxiaopai-ai.
  • -
  • macOS/LaunchAgent security defaults: write Umask=63 (octal 077) into generated gateway launchd plists so post-update service reinstalls keep owner-only file permissions by default instead of falling back to system 022. (#32022) Fixes #31905. Thanks @liuxiaopai-ai.
  • -
  • Media understanding/provider HTTP proxy routing: pass a proxy-aware fetch function from HTTPS_PROXY/HTTP_PROXY env vars into audio/video provider calls (with graceful malformed-proxy fallback) so transcription/video requests honor configured outbound proxies. (#27093) Thanks @mcaxtr.
  • -
  • Sandbox/workspace mount permissions: make primary /workspace bind mounts read-only whenever workspaceAccess is not rw (including none) across both core sandbox container and sandbox browser create flows. (#32227) Thanks @guanyu-zhang.
  • -
  • Tools/fsPolicy propagation: honor tools.fs.workspaceOnly for image/pdf local-root allowlists so non-sandbox media paths outside workspace are rejected when workspace-only mode is enabled. (#31882) Thanks @justinhuangcode.
  • -
  • Daemon/Homebrew runtime pinning: resolve Homebrew Cellar Node paths to stable Homebrew-managed symlinks (including versioned formulas like node@22) so gateway installs keep the intended runtime across brew upgrades. (#32185) Thanks @scoootscooob.
  • -
  • Browser/Security output boundary hardening: replace check-then-rename output commits with root-bound fd-verified writes, unify install/skills canonical path-boundary checks, and add regression coverage for symlink-rebind race paths across browser output and shared fs-safe write flows. Thanks @tdjackey for reporting.
  • -
  • Gateway/Security canonicalization hardening: decode plugin route path variants to canonical fixpoint (with bounded depth), fail closed on canonicalization anomalies, and enforce gateway auth for deeply encoded /api/channels/* variants to prevent alternate-path auth bypass through plugin handlers. Thanks @tdjackey for reporting.
  • -
  • Browser/Gateway hardening: preserve env credentials for OPENCLAW_GATEWAY_URL / CLAWDBOT_GATEWAY_URL while treating explicit --url as override-only auth, and make container browser hardening flags optional with safer defaults for Docker/LXC stability. (#31504) Thanks @vincentkoc.
  • -
  • Gateway/Control UI basePath webhook passthrough: let non-read methods under configured controlUiBasePath fall through to plugin routes (instead of returning Control UI 405), restoring webhook handlers behind basePath mounts. (#32311) Thanks @ademczuk.
  • -
  • Control UI/Legacy browser compatibility: replace toSorted-dependent cron suggestion sorting in app-render with a compatibility helper so older browsers without Array.prototype.toSorted no longer white-screen. (#31775) Thanks @liuxiaopai-ai.
  • -
  • macOS/PeekabooBridge: add compatibility socket symlinks for legacy clawdbot, clawdis, and moltbot Application Support socket paths so pre-rename clients can still connect. (#6033) Thanks @lumpinif and @vincentkoc.
  • -
  • Gateway/message tool reliability: avoid false Unknown channel failures when message.* actions receive platform-specific channel ids by falling back to toolContext.currentChannelProvider, and prevent health-monitor restart thrash for channels that just (re)started by adding a per-channel startup-connect grace window. (from #32367) Thanks @MunemHashmi.
  • -
  • Windows/Spawn canonicalization: unify non-core Windows spawn handling across ACP client, QMD/mcporter memory paths, and sandbox Docker execution using the shared wrapper-resolution policy, with targeted regression coverage for .cmd shim unwrapping and shell fallback behavior. (#31750) Thanks @Takhoffman.
  • -
  • Security/ACP sandbox inheritance: enforce fail-closed runtime guardrails for sessions_spawn with runtime="acp" by rejecting ACP spawns from sandboxed requester sessions and rejecting sandbox="require" for ACP runtime, preventing sandbox-boundary bypass via host-side ACP initialization. (#32254) Thanks @tdjackey for reporting, and @dutifulbob for the fix.
  • -
  • Security/Web tools SSRF guard: keep DNS pinning for untrusted web_fetch and citation-redirect URL checks when proxy env vars are set, and require explicit dangerous opt-in before env-proxy routing can bypass pinned dispatch for trusted/operator-controlled endpoints. Thanks @tdjackey for reporting.
  • -
  • Gemini schema sanitization: coerce malformed JSON Schema properties values (null, arrays, primitives) to {} before provider validation, preventing downstream strict-validator crashes on invalid plugin/tool schemas. (#32332) Thanks @webdevtodayjason.
  • -
  • Media understanding/malformed attachment guards: harden attachment selection and decision summary formatting against non-array or malformed attachment payloads to prevent runtime crashes on invalid inbound metadata shapes. (#28024) Thanks @claw9267.
  • -
  • Browser/Extension navigation reattach: preserve debugger re-attachment when relay is temporarily disconnected by deferring relay attach events until reconnect/re-announce, reducing post-navigation tab loss. (#28725) Thanks @stone-jin.
  • -
  • Browser/Extension relay stale tabs: evict stale cached targets from /json/list when extension targets are destroyed/crashed or commands fail with missing target/session errors. (#6175) Thanks @vincentkoc.
  • -
  • Browser/CDP startup readiness: wait for CDP websocket readiness after launching Chrome and cleanly stop/reset when readiness never arrives, reducing follow-up PortInUseError races after browser start/open. (#29538) Thanks @AaronWander.
  • -
  • OpenAI/Responses WebSocket tool-call id hygiene: normalize blank/whitespace streamed tool-call ids before persistence, and block empty function_call_output.call_id payloads in the WS conversion path to avoid OpenAI 400 errors (Invalid 'input[n].call_id': empty string), with regression coverage for both inbound stream normalization and outbound payload guards.
  • -
  • Security/Nodes camera URL downloads: bind node camera.snap/camera.clip URL payload downloads to the resolved node host, enforce fail-closed behavior when node remoteIp is unavailable, and use SSRF-guarded fetch with redirect host/protocol checks to prevent off-node fetch pivots. Thanks @tdjackey for reporting.
  • -
  • Config/backups hardening: enforce owner-only (0600) permissions on rotated config backups and clean orphan .bak.* files outside the managed backup ring, reducing credential leakage risk from stale or permissive backup artifacts. (#31718) Thanks @YUJIE2002.
  • -
  • Telegram/inbound media filenames: preserve original file_name metadata for document/audio/video/animation downloads (with fetch/path fallbacks), so saved inbound attachments keep sender-provided names instead of opaque Telegram file paths. (#31837) Thanks @Kay-051.
  • -
  • Gateway/OpenAI chat completions: honor x-openclaw-message-channel when building agentCommand input for /v1/chat/completions, preserving caller channel identity instead of forcing webchat. (#30462) Thanks @bmendonca3.
  • -
  • Plugin SDK/runtime hardening: add package export verification in CI/release checks to catch missing runtime exports before publish-time regressions. (#28575) Thanks @Glucksberg.
  • -
  • Media/MIME normalization: normalize parameterized/case-variant MIME strings in kindFromMime (for example Audio/Ogg; codecs=opus) so WhatsApp voice notes are classified as audio and routed through transcription correctly. (#32280) Thanks @Lucenx9.
  • -
  • Discord/audio preflight mentions: detect audio attachments via Discord content_type and gate preflight transcription on typed text (not media placeholders), so guild voice-note mentions are transcribed and matched correctly. (#32136) Thanks @jnMetaCode.
  • -
  • Feishu/topic session routing: use thread_id as topic session scope fallback when root_id is absent, keep first-turn topic keys stable across thread creation, and force thread replies when inbound events already carry topic/thread context. (#29788) Thanks @songyaolun.
  • -
  • Gateway/Webchat NO_REPLY streaming: suppress assistant lead-fragment deltas that are prefixes of NO_REPLY and keep final-message buffering in sync, preventing partial NO leaks on silent-response runs while preserving legitimate short replies. (#32073) Thanks @liuxiaopai-ai.
  • -
  • Telegram/models picker callbacks: keep long model buttons selectable by falling back to compact callback payloads and resolving provider ids on selection (with provider re-prompt on ambiguity), avoiding Telegram 64-byte callback truncation failures. (#31857) Thanks @bmendonca3.
  • -
  • Context-window metadata warmup: add exponential config-load retry backoff (1s -> 2s -> 4s, capped at 60s) so transient startup failures recover automatically without hot-loop retries.
  • -
  • Voice-call/Twilio external outbound: auto-register webhook-first outbound-api calls (initiated outside OpenClaw) so media streams are accepted and call direction metadata stays accurate. (#31181) Thanks @scoootscooob.
  • -
  • Feishu/topic root replies: prefer root_id as outbound replyTargetMessageId when present, and parse millisecond message_create_time values correctly so topic replies anchor to the root message in grouped thread flows. (#29968) Thanks @bmendonca3.
  • -
  • Feishu/DM pairing reply target: send pairing challenge replies to chat: instead of user: so Lark/Feishu private chats with user-id-only sender payloads receive pairing messages reliably. (#31403) Thanks @stakeswky.
  • -
  • Feishu/Lark private DM routing: treat inbound chat_type: "private" as direct-message context for pairing/mention-forward/reaction synthetic handling so Lark private chats behave like Feishu p2p DMs. (#31400) Thanks @stakeswky.
  • -
  • Signal/message actions: allow react to fall back to toolContext.currentMessageId when messageId is omitted, matching Telegram behavior and unblocking agent-initiated reactions on inbound turns. (#32217) Thanks @dunamismax.
  • -
  • Discord/message actions: allow react to fall back to toolContext.currentMessageId when messageId is omitted, matching Telegram/Signal reaction ergonomics in inbound turns.
  • -
  • Synology Chat/reply delivery: resolve webhook usernames to Chat API user_id values for outbound chatbot replies, avoiding mismatches between webhook user IDs and method=chatbot recipient IDs in multi-account setups. (#23709) Thanks @druide67.
  • -
  • Slack/thread context payloads: only inject thread starter/history text on first thread turn for new sessions while preserving thread metadata, reducing repeated context-token bloat on long-lived thread sessions. (#32133) Thanks @sourman.
  • -
  • Slack/session routing: keep top-level channel messages in one shared session when replyToMode=off, while preserving thread-scoped keys for true thread replies and non-off modes. (#32193) Thanks @bmendonca3.
  • -
  • Voice-call/webhook routing: require exact webhook path matches (instead of prefix matches) so lookalike paths cannot reach provider verification/dispatch logic. (#31930) Thanks @afurm.
  • -
  • Zalo/Pairing auth tests: add webhook regression coverage asserting DM pairing-store reads/writes remain account-scoped, preventing cross-account authorization bleed in multi-account setups. (#26121) Thanks @bmendonca3.
  • -
  • Zalouser/Pairing auth tests: add account-scoped DM pairing-store regression coverage (monitor.account-scope.test.ts) to prevent cross-account allowlist bleed in multi-account setups. (#26672) Thanks @bmendonca3.
  • -
  • Feishu/Send target prefixes: normalize explicit group:/dm: send targets and preserve explicit receive-id routing hints when resolving outbound Feishu targets. (#31594) Thanks @liuxiaopai-ai.
  • -
  • Webchat/Feishu session continuation: preserve routable OriginatingChannel/OriginatingTo metadata from session delivery context in chat.send, and prefer provider-normalized channel when deciding cross-channel route dispatch so Webchat replies continue on the selected Feishu session instead of falling back to main/internal session routing. (#31573)
  • -
  • Telegram/implicit mention forum handling: exclude Telegram forum system service messages (forum_topic_*, general_forum_topic_*) from reply-chain implicit mention detection so requireMention does not get bypassed inside bot-created topic lifecycle events. (#32262) Thanks @scoootscooob.
  • -
  • Slack/inbound debounce routing: isolate top-level non-DM message debounce keys by message timestamp to avoid cross-thread collisions, preserve DM batching, and flush pending top-level buffers before immediate non-debounce follow-ups to keep ordering stable. (#31951) Thanks @scoootscooob.
  • -
  • Feishu/Duplicate replies: suppress same-target reply dispatch when message-tool sends use generic provider metadata (provider: "message") and normalize lark/feishu provider aliases during duplicate-target checks, preventing double-delivery in Feishu sessions. (#31526)
  • -
  • Webchat/silent token leak: filter assistant NO_REPLY-only transcript entries from chat.history responses and add client-side defense-in-depth guards in the chat controller so internal silent tokens never render as visible chat bubbles. (#32015) Consolidates overlap from #32183, #32082, #32045, #32052, #32172, and #32112. Thanks @ademczuk, @liuxiaopai-ai, @ningding97, @bmendonca3, and @x4v13r1120.
  • -
  • Doctor/local memory provider checks: stop false-positive local-provider warnings when provider=local and no explicit modelPath is set by honoring default local model fallback while still warning when gateway probe reports local embeddings not ready. (#32014) Fixes #31998. Thanks @adhishthite.
  • -
  • Media understanding/parakeet CLI output parsing: read parakeet-mlx transcripts from --output-dir/.txt when txt output is requested (or default), with stdout fallback for non-txt formats. (#9177) Thanks @mac-110.
  • -
  • Media understanding/audio transcription guard: skip tiny/empty audio files (<1024 bytes) before provider/CLI transcription to avoid noisy invalid-audio failures and preserve clean fallback behavior. (#8388) Thanks @Glucksberg.
  • -
  • Gateway/Plugin HTTP route precedence: run explicit plugin HTTP routes before the Control UI SPA catch-all so registered plugin webhook/custom paths remain reachable, while unmatched paths still fall through to Control UI handling. (#31885) Thanks @Sid-Qin.
  • -
  • Gateway/Node browser proxy routing: honor profile from browser.request JSON body when query params omit it, while preserving query-profile precedence when both are present. (#28852) Thanks @Sid-Qin.
  • -
  • Gateway/Control UI basePath POST handling: return 405 for POST on exact basePath routes (for example /openclaw) instead of redirecting, and add end-to-end regression coverage that root-mounted webhook POST paths still pass through to plugin handlers. (#31349) Thanks @Sid-Qin.
  • -
  • Browser/default profile selection: default browser.defaultProfile behavior now prefers openclaw (managed standalone CDP) when no explicit default is configured, while still auto-provisioning the chrome relay profile for explicit opt-in use. (#32031) Fixes #31907. Thanks @liuxiaopai-ai.
  • -
  • Sandbox/mkdirp boundary checks: allow existing in-boundary directories to pass mkdirp boundary validation when directory open probes return platform-specific I/O errors, with regression coverage for directory-safe fallback behavior. (#31547) Thanks @stakeswky.
  • -
  • Models/config env propagation: apply config.env.vars before implicit provider discovery in models bootstrap so config-scoped credentials are visible to implicit provider resolution paths. (#32295) Thanks @hsiaoa.
  • -
  • Models/Codex usage labels: infer weekly secondary usage windows from reset cadence when API window seconds are ambiguously reported as 24h, so openclaw models status no longer mislabels weekly limits as daily. (#31938) Thanks @bmendonca3.
  • -
  • Gateway/Heartbeat model reload: treat models.* and agents.defaults.model config updates as heartbeat hot-reload triggers so heartbeat picks up model changes without a full gateway restart. (#32046) Thanks @stakeswky.
  • -
  • Memory/LanceDB embeddings: forward configured embedding.dimensions into OpenAI embeddings requests so vector size and API output dimensions stay aligned when dimensions are explicitly configured. (#32036) Thanks @scotthuang.
  • -
  • Gateway/Control UI method guard: allow POST requests to non-UI routes to fall through when no base path is configured, and add POST regression coverage for fallthrough and base-path 405 behavior. (#23970) Thanks @tyler6204.
  • -
  • Browser/CDP status accuracy: require a successful Browser.getVersion response over the CDP websocket (not just socket-open) before reporting cdpReady, so stale idle command channels are surfaced as unhealthy. (#23427) Thanks @vincentkoc.
  • -
  • Daemon/systemd checks in containers: treat missing systemctl invocations (including spawn systemctl ENOENT/EACCES) as unavailable service state during is-enabled checks, preventing container flows from failing with Gateway service check failed before install/status handling can continue. (#26089) Thanks @sahilsatralkar and @vincentkoc.
  • -
  • Security/Node exec approvals: revalidate approval-bound cwd identity immediately before execution/forwarding and fail closed with an explicit denial when cwd drifts after approval hardening.
  • -
  • Security audit/skills workspace hardening: add skills.workspace.symlink_escape warning in openclaw security audit when workspace skills/**/SKILL.md resolves outside the workspace root (for example symlink-chain drift), plus docs coverage in the security glossary.
  • -
  • Security/Node exec approvals: preserve shell/dispatch-wrapper argv semantics during approval hardening so approved wrapper commands (for example env sh -c ...) cannot drift into a different runtime command shape, and add regression coverage for both approval-plan generation and approved runtime execution paths. Thanks @tdjackey for reporting.
  • -
  • Security/fs-safe write hardening: make writeFileWithinRoot use same-directory temp writes plus atomic rename, add post-write inode/hardlink revalidation with security warnings on boundary drift, and avoid truncating existing targets when final rename fails.
  • -
  • Security/Skills archive extraction: unify tar extraction safety checks across tar.gz and tar.bz2 install flows, enforce tar compressed-size limits, and fail closed if tar.bz2 archives change between preflight and extraction to prevent bypasses of entry-type/size guardrails. Thanks @GCXWLP for reporting.
  • -
  • Security/Prompt spoofing hardening: stop injecting queued runtime events into user-role prompt text, route them through trusted system-prompt context, and neutralize inbound spoof markers like [System Message] and line-leading System: in untrusted message content. (#30448)
  • -
  • Sandbox/Docker setup command parsing: accept agents.*.sandbox.docker.setupCommand as either a string or a string array, and normalize arrays to newline-delimited shell scripts so multi-step setup commands no longer concatenate without separators. (#31953) Thanks @liuxiaopai-ai.
  • -
  • Sandbox/Bootstrap context boundary hardening: reject symlink/hardlink alias bootstrap seed files that resolve outside the source workspace and switch post-compaction AGENTS.md context reads to boundary-verified file opens, preventing host file content from being injected via workspace aliasing. Thanks @tdjackey for reporting.
  • -
  • Agents/Sandbox workdir mapping: map container workdir paths (for example /workspace) back to the host workspace before sandbox path validation so exec requests keep the intended directory in containerized runs instead of falling back to an unavailable host path. (#31841) Thanks @liuxiaopai-ai.
  • -
  • Docker/Sandbox bootstrap hardening: make OPENCLAW_SANDBOX opt-in parsing explicit (1|true|yes|on), support custom Docker socket paths via OPENCLAW_DOCKER_SOCKET, defer docker.sock exposure until sandbox prerequisites pass, and reset/roll back persisted sandbox mode to off when setup is skipped or partially fails to avoid stale broken sandbox state. (#29974) Thanks @jamtujest and @vincentkoc.
  • -
  • Hooks/webhook ACK compatibility: return 200 (instead of 202) for successful /hooks/agent requests so providers that require 200 (for example Forward Email) accept dispatched agent hook deliveries. (#28204) Thanks @Glucksberg.
  • -
  • Feishu/Run channel fallback: prefer Provider over Surface when inferring queued run messageProvider fallback (when OriginatingChannel is missing), preventing Feishu turns from being mislabeled as webchat in mixed relay metadata contexts. (#31880) Fixes #31859. Thanks @liuxiaopai-ai.
  • -
  • Skills/sherpa-onnx-tts: run the sherpa-onnx-tts bin under ESM (replace CommonJS require imports) and add regression coverage to prevent require is not defined in ES module scope startup crashes. (#31965) Thanks @bmendonca3.
  • -
  • Inbound metadata/direct relay context: restore direct-channel conversation metadata blocks for external channels (for example WhatsApp) while preserving webchat-direct suppression, so relay agents recover sender/message identifiers without reintroducing internal webchat metadata noise. (#31969) Fixes #29972. Thanks @Lucenx9.
  • -
  • Slack/Channel message subscriptions: register explicit message.channels and message.groups monitor handlers (alongside generic message) so channel/group event subscriptions are consumed even when Slack dispatches typed message event names. Fixes #31674.
  • -
  • Hooks/session-scoped memory context: expose ephemeral sessionId in embedded plugin tool contexts and before_tool_call/after_tool_call hook contexts (including compaction and client-tool wiring) so plugins can isolate per-conversation state across /new and /reset. Related #31253 and #31304. Thanks @Sid-Qin and @Servo-AIpex.
  • -
  • Voice-call/Twilio inbound greeting: run answered-call initial notify greeting for Twilio instead of skipping the manager speak path, with regression coverage for both Twilio and Plivo notify flows. (#29121) Thanks @xinhuagu.
  • -
  • Voice-call/stale call hydration: verify active calls with the provider before loading persisted in-progress calls so stale locally persisted records do not block or misroute new call handling after restarts. (#4325) Thanks @garnetlyx.
  • -
  • Feishu/File upload filenames: percent-encode non-ASCII/special-character file_name values in Feishu multipart uploads so Chinese/symbol-heavy filenames are sent as proper attachments instead of plain text links. (#31179) Thanks @Kay-051.
  • -
  • Media/MIME channel parity: route Telegram/Signal/iMessage media-kind checks through normalized kindFromMime so mixed-case/parameterized MIME values classify consistently across message channels.
  • -
  • WhatsApp/inbound self-message context: propagate inbound fromMe through the web inbox pipeline and annotate direct self messages as (self) in envelopes so agents can distinguish owner-authored turns from contact turns. (#32167) Thanks @scoootscooob.
  • -
  • Webchat/stream finalization: persist streamed assistant text when final events omit message, while keeping final payload precedence and skipping empty stream buffers to prevent disappearing replies after tool turns. (#31920) Thanks @Sid-Qin.
  • -
  • Feishu/Inbound ordering: serialize message handling per chat while preserving cross-chat concurrency to avoid same-chat race drops under bursty inbound traffic. (#31807)
  • -
  • Feishu/Typing notification suppression: skip typing keepalive reaction re-adds when the indicator is already active, preventing duplicate notification pings from repeated identical emoji adds. (#31580)
  • -
  • Feishu/Probe failure backoff: cache API and timeout probe failures for one minute per account key while preserving abort-aware probe timeouts, reducing repeated health-check retries during transient credential/network outages. (#29970)
  • -
  • Feishu/Streaming block fallback: preserve markdown block stream text as final streaming-card content when final payload text is missing, while still suppressing non-card internal block chunk delivery. (#30663)
  • -
  • Feishu/Bitable API errors: unify Feishu Bitable tool error handling with structured LarkApiError responses and consistent API/context attribution across wiki/base metadata, field, and record operations. (#31450)
  • -
  • Feishu/Missing-scope grant URL fix: rewrite known invalid scope aliases (contact:contact.base:readonly) to valid scope names in permission grant links, so remediation URLs open with correct Feishu consent scopes. (#31943)
  • -
  • BlueBubbles/Message metadata: harden send response ID extraction, include sender identity in DM context, and normalize inbound message_id selection to avoid duplicate ID metadata. (#23970) Thanks @tyler6204.
  • -
  • WebChat/markdown tables: ensure GitHub-flavored markdown table parsing is explicitly enabled at render time and add horizontal overflow handling for wide tables, with regression coverage for table-only and mixed text+table content. (#32365) Thanks @BlueBirdBack.
  • -
  • Feishu/default account resolution: always honor explicit channels.feishu.defaultAccount during outbound account selection (including top-level-credential setups where the preferred id is not present in accounts), instead of silently falling back to another account id. (#32253) Thanks @bmendonca3.
  • -
  • Feishu/Sender lookup permissions: suppress user-facing grant prompts for stale non-existent scope errors (contact:contact.base:readonly) during best-effort sender-name resolution so inbound messages continue without repeated false permission notices. (#31761)
  • -
  • Discord/dispatch + Slack formatting: restore parallel outbound dispatch across Discord channels with per-channel queues while preserving in-channel ordering, and run Slack preview/stream update text through mrkdwn normalization for consistent formatting. (#31927) Thanks @Sid-Qin.
  • -
  • Feishu/Inbound debounce: debounce rapid same-chat sender bursts into one ordered dispatch turn, skip already-processed retries when composing merged text, and preserve bot-mention intent across merged entries to reduce duplicate or late inbound handling. (#31548)
  • -
  • Tests/Sandbox + archive portability: use junction-compatible directory-link setup on Windows and explicit file-symlink platform guards in symlink escape tests where unprivileged file symlinks are unavailable, reducing false Windows CI failures while preserving traversal checks on supported paths. (#28747) Thanks @arosstale.
  • -
  • Browser/Extension re-announce reliability: keep relay state in connecting when re-announce forwarding fails and extend debugger re-attach retries after navigation to reduce false attached states and post-nav disconnect loops. (#27630) Thanks @markmusson.
  • -
  • Browser/Act request compatibility: accept legacy flattened action="act" params (kind/ref/text/...) in addition to request={...} so browser act calls no longer fail with request required. (#15120) Thanks @vincentkoc.
  • -
  • OpenRouter/x-ai compatibility: skip reasoning.effort injection for x-ai/* models (for example Grok) so OpenRouter requests no longer fail with invalid-arguments errors on unsupported reasoning params. (#32054) Thanks @scoootscooob.
  • -
  • Models/openai-completions developer-role compatibility: force supportsDeveloperRole=false for non-native endpoints, treat unparseable baseUrl values as non-native, and add regression coverage for empty/malformed baseUrl plus explicit-true override behavior. (#29479) thanks @akramcodez.
  • -
  • Browser/Profile attach-only override: support browser.profiles..attachOnly (fallback to global browser.attachOnly) so loopback proxy profiles can skip local launch/port-ownership checks without forcing attach-only mode for every profile. (#20595) Thanks @unblockedgamesstudio and @vincentkoc.
  • -
  • Sessions/Lock recovery: detect recycled Linux PIDs by comparing lock-file starttime with /proc//stat starttime, so stale .jsonl.lock files are reclaimed immediately in containerized PID-reuse scenarios while preserving compatibility for older lock files. (#26443) Fixes #27252. Thanks @HirokiKobayashi-R and @vincentkoc.
  • -
  • Cron/isolated delivery target fallback: remove early unresolved-target return so cron delivery can flow through shared outbound target resolution (including per-channel resolveDefaultTo fallback) when delivery.to is omitted. (#32364) Thanks @hclsys.
  • -
  • OpenAI media capabilities: include audio in the OpenAI provider capability list so audio transcription models are eligible in media-understanding provider selection. (#12717) Thanks @openjay.
  • -
  • Browser/Managed tab cap: limit loopback managed openclaw page tabs to 8 via best-effort cleanup after tab opens to reduce long-running renderer buildup while preserving attach-only and remote profile behavior. (#29724) Thanks @pandego.
  • -
  • Docker/Image health checks: add Dockerfile HEALTHCHECK that probes gateway GET /healthz so container runtimes can mark unhealthy instances without requiring auth credentials in the probe command. (#11478) Thanks @U-C4N and @vincentkoc.
  • -
  • Gateway/Node dangerous-command parity: include sms.send in default onboarding node denyCommands, share onboarding deny defaults with the gateway dangerous-command source of truth, and include sms.send in phone-control /phone arm writes handling so SMS follows the same break-glass flow as other dangerous node commands. Thanks @zpbrent.
  • -
  • Pairing/AllowFrom account fallback: handle omitted accountId values in readChannelAllowFromStore and readChannelAllowFromStoreSync as default, while preserving legacy unscoped allowFrom merges for default-account flows. Thanks @Sid-Qin and @vincentkoc.
  • -
  • Browser/Remote CDP ownership checks: skip local-process ownership errors for non-loopback remote CDP profiles when HTTP is reachable but the websocket handshake fails, and surface the remote websocket attach/retry path instead. (#15582) Landed from contributor (#28780) Thanks @stubbi, @bsormagec, @unblockedgamesstudio and @vincentkoc.
  • -
  • Browser/CDP proxy bypass: force direct loopback agent paths and scoped NO_PROXY expansion for localhost CDP HTTP/WS connections when proxy env vars are set, so browser relay/control still works behind global proxy settings. (#31469) Thanks @widingmarcus-cyber.
  • -
  • Sessions/idle reset correctness: preserve existing updatedAt during inbound metadata-only writes so idle-reset boundaries are not unintentionally refreshed before actual user turns. (#32379) Thanks @romeodiaz.
  • -
  • Sessions/lock recovery: reclaim orphan legacy same-PID lock files missing starttime when no in-process lock ownership exists, avoiding false lock timeouts after PID reuse while preserving active lock safety checks. (#32081) Thanks @bmendonca3.
  • -
  • Sessions/store cache invalidation: reload cached session stores when file size changes within the same mtime tick by keying cache validation on a single file-stat snapshot (mtimeMs + sizeBytes), with regression coverage for same-tick rewrites. (#32191) Thanks @jalehman.
  • -
  • Agents/Subagents sessions_spawn: reject malformed agentId inputs before normalization (for example error-message/path-like strings) to prevent unintended synthetic agent IDs and ghost workspace/session paths; includes strict validation regression coverage. (#31381) Thanks @openperf.
  • -
  • CLI/installer Node preflight: enforce Node.js v22.12+ consistently in both openclaw.mjs runtime bootstrap and installer active-shell checks, with actionable nvm recovery guidance for mismatched shell PATH/defaults. (#32356) Thanks @jasonhargrove.
  • -
  • Web UI/config form: support SecretInput string-or-secret-ref unions in map additionalProperties, so provider API key fields stay editable instead of being marked unsupported. (#31866) Thanks @ningding97.
  • -
  • Auto-reply/inline command cleanup: preserve newline structure when stripping inline /status and extracting inline slash commands by collapsing only horizontal whitespace, preventing paragraph flattening in multi-line replies. (#32224) Thanks @scoootscooob.
  • -
  • Config/raw redaction safety: preserve non-sensitive literals during raw redaction round-trips, scope SecretRef redaction to secret IDs (not structural fields like source/provider), and fall back to structured raw redaction when text replacement cannot restore the original config shape. (#32174) Thanks @bmendonca3.
  • -
  • Hooks/runtime stability: keep the internal hook handler registry on a globalThis singleton so hook registration/dispatch remains consistent when bundling emits duplicate module copies. (#32292) Thanks @Drickon.
  • -
  • Hooks/after_tool_call: include embedded session context (sessionKey, agentId) and fire the hook exactly once per tool execution by removing duplicate adapter-path dispatch in embedded runs. (#32201) Thanks @jbeno, @scoootscooob, @vincentkoc.
  • -
  • Hooks/tool-call correlation: include runId and toolCallId in plugin tool hook payloads/context and scope tool start/adjusted-param tracking by run to prevent cross-run collisions in before_tool_call and after_tool_call. (#32360) Thanks @vincentkoc.
  • -
  • Plugins/install diagnostics: reject legacy plugin package shapes without openclaw.extensions and return an explicit upgrade hint with troubleshooting docs for repackaging. (#32055) Thanks @liuxiaopai-ai.
  • -
  • Hooks/plugin context parity: ensure llm_input hooks in embedded attempts receive the same trigger and channelId-aware hookCtx used by the other hook phases, preserving channel/trigger-scoped plugin behavior. (#28623) Thanks @davidrudduck and @vincentkoc.
  • -
  • Plugins/hardlink install compatibility: allow bundled plugin manifests and entry files to load when installed via hardlink-based package managers (pnpm, bun) while keeping hardlink rejection enabled for non-bundled plugin sources. (#32119) Fixes #28175, #28404, #29455. Thanks @markfietje.
  • -
  • Cron/session reaper reliability: move cron session reaper sweeps into onTimer finally and keep pruning active even when timer ticks fail early (for example cron store parse failures), preventing stale isolated run sessions from accumulating indefinitely. (#31996) Fixes #31946. Thanks @scoootscooob.
  • -
  • Cron/HEARTBEAT_OK summary leak: suppress fallback main-session enqueue for heartbeat/internal ack summaries in isolated announce mode so HEARTBEAT_OK noise never appears in user chat while real summaries still forward. (#32093) Thanks @scoootscooob.
  • -
  • Authentication: classify permission_error as auth_permanent for profile fallback. (#31324) Thanks @Sid-Qin.
  • -
  • Agents/host edit reliability: treat host edit-tool throws as success only when on-disk post-check confirms replacement likely happened (newText present and oldText absent), preventing false failure reports while avoiding pre-write false positives. (#32383) Thanks @polooooo.
  • -
  • Plugins/install fallback safety: resolve bare install specs to bundled plugin ids before npm lookup (for example diffs -> bundled @openclaw/diffs), keep npm fallback limited to true package-not-found errors, and continue rejecting non-plugin npm packages that fail manifest validation. (#32096) Thanks @scoootscooob.
  • -
  • Web UI/inline code copy fidelity: disable forced mid-token wraps on inline spans so copied UUID/hash/token strings preserve exact content instead of inserting line-break spaces. (#32346) Thanks @hclsys.
  • -
  • Restart sentinel formatting: avoid duplicate Reason: lines when restart message text already matches stats.reason, keeping restart notifications concise for users and downstream parsers. (#32083) Thanks @velamints2.
  • -
  • Auto-reply/followup queue: avoid stale callback reuse across idle-window restarts by caching the followup runner only when a drain actually starts, preserving enqueue ordering after empty-finalize paths. (#31902) Thanks @Lanfei.
  • -
  • Agents/tool-result guard: always clear pending tool-call state on interruptions even when synthetic tool results are disabled, preventing orphaned tool-use transcripts that cause follow-up provider request failures. (#32120) Thanks @jnMetaCode.
  • -
  • Failover/error classification: treat HTTP 529 (provider overloaded, common with Anthropic-compatible APIs) as rate_limit so model failover can engage instead of misclassifying the error path. (#31854) Thanks @bugkill3r.
  • -
  • Logging: use local time for logged timestamps instead of UTC, aligning log output with documented local timezone behavior and avoiding confusion during local diagnostics. (#28434) Thanks @liuy.
  • -
  • Agents/Subagent announce cleanup: keep completion-message runs pending while descendants settle, add a 30 minute hard-expiry backstop to avoid indefinite pending state, and keep retry bookkeeping resumable across deferred wakes. (#23970) Thanks @tyler6204.
  • -
  • Secrets/exec resolver timeout defaults: use provider timeoutMs as the default inactivity (noOutputTimeoutMs) watchdog for exec secret providers, preventing premature no-output kills for resolvers that start producing output after 2s. (#32235) Thanks @bmendonca3.
  • -
  • Auto-reply/reminder guard note suppression: when a turn makes reminder-like commitments but schedules no new cron jobs, suppress the unscheduled-reminder warning note only if an enabled cron already exists for the same session; keep warnings for unrelated sessions, disabled jobs, or unreadable cron store paths. (#32255) Thanks @scoootscooob.
  • -
  • Cron/isolated announce heartbeat suppression: treat multi-payload runs as skippable when any payload is a heartbeat ack token and no payload has media, preventing internal narration + trailing HEARTBEAT_OK from being delivered to users. (#32131) Thanks @adhishthite.
  • -
  • Cron/store migration: normalize legacy cron jobs with string schedule and top-level command/timeout fields into canonical schedule/payload/session-target shape on load, preventing schedule-error loops on old persisted stores. (#31926) Thanks @bmendonca3.
  • -
  • Tests/Windows backup rotation: skip chmod-only backup permission assertions on Windows while retaining compose/rotation/prune coverage across platforms to avoid false CI failures from Windows non-POSIX mode semantics. (#32286) Thanks @jalehman.
  • -
  • Tests/Subagent announce: set OPENCLAW_TEST_FAST=1 before importing subagent-announce format suites so module-level fast-mode constants are captured deterministically on Windows CI, preventing timeout flakes in nested completion announce coverage. (#31370) Thanks @zwffff.
  • -
-

View full changelog

-]]>
- - -
\ No newline at end of file diff --git a/apps/android/README.md b/apps/android/README.md index 0a92e4c8ec5..008941ecda7 100644 --- a/apps/android/README.md +++ b/apps/android/README.md @@ -30,8 +30,12 @@ cd apps/android ./gradlew :app:assembleDebug ./gradlew :app:installDebug ./gradlew :app:testDebugUnitTest +cd ../.. +bun run android:bundle:release ``` +`bun run android:bundle:release` auto-bumps Android `versionName`/`versionCode` in `apps/android/app/build.gradle.kts`, then builds a signed release `.aab`. + ## Kotlin Lint + Format ```bash @@ -172,6 +176,45 @@ More details: `docs/platforms/android.md`. - `CAMERA` for `camera.snap` and `camera.clip` - `RECORD_AUDIO` for `camera.clip` when `includeAudio=true` +## Google Play Restricted Permissions + +As of March 19, 2026, these manifest permissions are the main Google Play policy risk for this app: + +- `READ_SMS` +- `SEND_SMS` +- `READ_CALL_LOG` + +Why these matter: + +- Google Play treats SMS and Call Log access as highly restricted. In most cases, Play only allows them for the default SMS app, default Phone app, default Assistant, or a narrow policy exception. +- Review usually involves a `Permissions Declaration Form`, policy justification, and demo video evidence in Play Console. +- If we want a Play-safe build, these should be the first permissions removed behind a dedicated product flavor / variant. + +Current OpenClaw Android implication: + +- APK / sideload build can keep SMS and Call Log features. +- Google Play build should exclude SMS send/search and Call Log search unless the product is intentionally positioned and approved as a default-handler exception case. + +Policy links: + +- [Google Play SMS and Call Log policy](https://support.google.com/googleplay/android-developer/answer/10208820?hl=en) +- [Google Play sensitive permissions policy hub](https://support.google.com/googleplay/android-developer/answer/16558241) +- [Android default handlers guide](https://developer.android.com/guide/topics/permissions/default-handlers) + +Other Play-restricted surfaces to watch if added later: + +- `ACCESS_BACKGROUND_LOCATION` +- `MANAGE_EXTERNAL_STORAGE` +- `QUERY_ALL_PACKAGES` +- `REQUEST_INSTALL_PACKAGES` +- `AccessibilityService` + +Reference links: + +- [Background location policy](https://support.google.com/googleplay/android-developer/answer/9799150) +- [AccessibilityService policy](https://support.google.com/googleplay/android-developer/answer/10964491?hl=en-GB) +- [Photo and Video Permissions policy](https://support.google.com/googleplay/android-developer/answer/14594990) + ## Integration Capability Test (Preconditioned) This suite assumes setup is already done manually. It does **not** install/run/pair automatically. diff --git a/apps/android/app/build.gradle.kts b/apps/android/app/build.gradle.kts index 3b52bcf50de..46afccbc3bf 100644 --- a/apps/android/app/build.gradle.kts +++ b/apps/android/app/build.gradle.kts @@ -1,5 +1,7 @@ import com.android.build.api.variant.impl.VariantOutputImpl +val dnsjavaInetAddressResolverService = "META-INF/services/java.net.spi.InetAddressResolverProvider" + val androidStoreFile = providers.gradleProperty("OPENCLAW_ANDROID_STORE_FILE").orNull?.takeIf { it.isNotBlank() } val androidStorePassword = providers.gradleProperty("OPENCLAW_ANDROID_STORE_PASSWORD").orNull?.takeIf { it.isNotBlank() } val androidKeyAlias = providers.gradleProperty("OPENCLAW_ANDROID_KEY_ALIAS").orNull?.takeIf { it.isNotBlank() } @@ -63,8 +65,8 @@ android { applicationId = "ai.openclaw.app" minSdk = 31 targetSdk = 36 - versionCode = 202603090 - versionName = "2026.3.9" + versionCode = 2026031400 + versionName = "2026.3.14" ndk { // Support all major ABIs — native libs are tiny (~47 KB per ABI) abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") @@ -78,6 +80,9 @@ android { } isMinifyEnabled = true isShrinkResources = true + ndk { + debugSymbolLevel = "SYMBOL_TABLE" + } proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } debug { @@ -104,6 +109,10 @@ android { "/META-INF/LICENSE*.txt", "DebugProbesKt.bin", "kotlin-tooling-metadata.json", + "org/bouncycastle/pqc/crypto/picnic/lowmcL1.bin.properties", + "org/bouncycastle/pqc/crypto/picnic/lowmcL3.bin.properties", + "org/bouncycastle/pqc/crypto/picnic/lowmcL5.bin.properties", + "org/bouncycastle/x509/CertPathReviewerMessages*.properties", ) } } @@ -168,7 +177,6 @@ dependencies { // material-icons-extended pulled in full icon set (~20 MB DEX). Only ~18 icons used. // R8 will tree-shake unused icons when minify is enabled on release builds. implementation("androidx.compose.material:material-icons-extended") - implementation("androidx.navigation:navigation-compose:2.9.7") debugImplementation("androidx.compose.ui:ui-tooling") @@ -193,8 +201,7 @@ dependencies { implementation("androidx.camera:camera-camera2:1.5.2") implementation("androidx.camera:camera-lifecycle:1.5.2") implementation("androidx.camera:camera-video:1.5.2") - implementation("androidx.camera:camera-view:1.5.2") - implementation("com.journeyapps:zxing-android-embedded:4.3.0") + implementation("com.google.android.gms:play-services-code-scanner:16.1.0") // Unicast DNS-SD (Wide-Area Bonjour) for tailnet discovery domains. implementation("dnsjava:dnsjava:3.6.4") @@ -211,3 +218,45 @@ dependencies { tasks.withType().configureEach { useJUnitPlatform() } + +val stripReleaseDnsjavaServiceDescriptor = + tasks.register("stripReleaseDnsjavaServiceDescriptor") { + val mergedJar = + layout.buildDirectory.file( + "intermediates/merged_java_res/release/mergeReleaseJavaResource/base.jar", + ) + + inputs.file(mergedJar) + outputs.file(mergedJar) + + doLast { + val jarFile = mergedJar.get().asFile + if (!jarFile.exists()) { + return@doLast + } + + val unpackDir = temporaryDir.resolve("merged-java-res") + delete(unpackDir) + copy { + from(zipTree(jarFile)) + into(unpackDir) + exclude(dnsjavaInetAddressResolverService) + } + delete(jarFile) + ant.invokeMethod( + "zip", + mapOf( + "destfile" to jarFile.absolutePath, + "basedir" to unpackDir.absolutePath, + ), + ) + } + } + +tasks.matching { it.name == "stripReleaseDnsjavaServiceDescriptor" }.configureEach { + dependsOn("mergeReleaseJavaResource") +} + +tasks.matching { it.name == "minifyReleaseWithR8" }.configureEach { + dependsOn(stripReleaseDnsjavaServiceDescriptor) +} diff --git a/apps/android/app/proguard-rules.pro b/apps/android/app/proguard-rules.pro index 78e4a363919..7c04b96833a 100644 --- a/apps/android/app/proguard-rules.pro +++ b/apps/android/app/proguard-rules.pro @@ -1,26 +1,6 @@ -# ── App classes ─────────────────────────────────────────────────── --keep class ai.openclaw.app.** { *; } - -# ── Bouncy Castle ───────────────────────────────────────────────── --keep class org.bouncycastle.** { *; } -dontwarn org.bouncycastle.** - -# ── CameraX ─────────────────────────────────────────────────────── --keep class androidx.camera.** { *; } - -# ── kotlinx.serialization ──────────────────────────────────────── --keep class kotlinx.serialization.** { *; } --keepclassmembers class * { - @kotlinx.serialization.Serializable *; -} --keepattributes *Annotation*, InnerClasses - -# ── OkHttp ──────────────────────────────────────────────────────── -dontwarn okhttp3.** -dontwarn okio.** --keep class okhttp3.internal.platform.** { *; } - -# ── Misc suppressions ──────────────────────────────────────────── -dontwarn com.sun.jna.** -dontwarn javax.naming.** -dontwarn lombok.Generated diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml index f9bf03b1a3d..283daae601f 100644 --- a/apps/android/app/src/main/AndroidManifest.xml +++ b/apps/android/app/src/main/AndroidManifest.xml @@ -12,6 +12,7 @@ + + diff --git a/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt b/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt index 40cabebd17c..d9ad83175b4 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt @@ -18,14 +18,13 @@ import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { private val viewModel: MainViewModel by viewModels() private lateinit var permissionRequester: PermissionRequester + private var didAttachRuntimeUi = false + private var didStartNodeService = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) permissionRequester = PermissionRequester(this) - viewModel.camera.attachLifecycleOwner(this) - viewModel.camera.attachPermissionRequester(permissionRequester) - viewModel.sms.attachPermissionRequester(permissionRequester) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -39,6 +38,20 @@ class MainActivity : ComponentActivity() { } } + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.runtimeInitialized.collect { ready -> + if (!ready || didAttachRuntimeUi) return@collect + viewModel.attachRuntimeUi(owner = this@MainActivity, permissionRequester = permissionRequester) + didAttachRuntimeUi = true + if (!didStartNodeService) { + NodeForegroundService.start(this@MainActivity) + didStartNodeService = true + } + } + } + } + setContent { OpenClawTheme { Surface(modifier = Modifier) { @@ -46,9 +59,6 @@ class MainActivity : ComponentActivity() { } } } - - // Keep startup path lean: start foreground service after first frame. - window.decorView.post { NodeForegroundService.start(this) } } override fun onStart() { diff --git a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt index a1b6ba3d353..82fe643314c 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt @@ -2,201 +2,268 @@ package ai.openclaw.app import android.app.Application import androidx.lifecycle.AndroidViewModel -import ai.openclaw.app.gateway.GatewayEndpoint +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.viewModelScope +import ai.openclaw.app.chat.ChatMessage +import ai.openclaw.app.chat.ChatPendingToolCall +import ai.openclaw.app.chat.ChatSessionEntry import ai.openclaw.app.chat.OutgoingAttachment +import ai.openclaw.app.gateway.GatewayEndpoint import ai.openclaw.app.node.CameraCaptureManager import ai.openclaw.app.node.CanvasController import ai.openclaw.app.node.SmsManager import ai.openclaw.app.voice.VoiceConversationEntry +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn +@OptIn(ExperimentalCoroutinesApi::class) class MainViewModel(app: Application) : AndroidViewModel(app) { - private val runtime: NodeRuntime = (app as NodeApp).runtime + private val nodeApp = app as NodeApp + private val prefs = nodeApp.prefs + private val runtimeRef = MutableStateFlow(null) + private var foreground = true - val canvas: CanvasController = runtime.canvas - val canvasCurrentUrl: StateFlow = runtime.canvas.currentUrl - val canvasA2uiHydrated: StateFlow = runtime.canvasA2uiHydrated - val canvasRehydratePending: StateFlow = runtime.canvasRehydratePending - val canvasRehydrateErrorText: StateFlow = runtime.canvasRehydrateErrorText - val camera: CameraCaptureManager = runtime.camera - val sms: SmsManager = runtime.sms + private fun ensureRuntime(): NodeRuntime { + runtimeRef.value?.let { return it } + val runtime = nodeApp.ensureRuntime() + runtime.setForeground(foreground) + runtimeRef.value = runtime + return runtime + } - val gateways: StateFlow> = runtime.gateways - val discoveryStatusText: StateFlow = runtime.discoveryStatusText + private fun runtimeState( + initial: T, + selector: (NodeRuntime) -> StateFlow, + ): StateFlow = + runtimeRef + .flatMapLatest { runtime -> runtime?.let(selector) ?: flowOf(initial) } + .stateIn(viewModelScope, SharingStarted.Eagerly, initial) - val isConnected: StateFlow = runtime.isConnected - val isNodeConnected: StateFlow = runtime.nodeConnected - val statusText: StateFlow = runtime.statusText - val serverName: StateFlow = runtime.serverName - val remoteAddress: StateFlow = runtime.remoteAddress - val pendingGatewayTrust: StateFlow = runtime.pendingGatewayTrust - val isForeground: StateFlow = runtime.isForeground - val seamColorArgb: StateFlow = runtime.seamColorArgb - val mainSessionKey: StateFlow = runtime.mainSessionKey + val runtimeInitialized: StateFlow = + runtimeRef + .flatMapLatest { runtime -> flowOf(runtime != null) } + .stateIn(viewModelScope, SharingStarted.Eagerly, false) - val cameraHud: StateFlow = runtime.cameraHud - val cameraFlashToken: StateFlow = runtime.cameraFlashToken + val canvasCurrentUrl: StateFlow = runtimeState(initial = null) { it.canvas.currentUrl } + val canvasA2uiHydrated: StateFlow = runtimeState(initial = false) { it.canvasA2uiHydrated } + val canvasRehydratePending: StateFlow = runtimeState(initial = false) { it.canvasRehydratePending } + val canvasRehydrateErrorText: StateFlow = runtimeState(initial = null) { it.canvasRehydrateErrorText } - val instanceId: StateFlow = runtime.instanceId - val displayName: StateFlow = runtime.displayName - val cameraEnabled: StateFlow = runtime.cameraEnabled - val locationMode: StateFlow = runtime.locationMode - val locationPreciseEnabled: StateFlow = runtime.locationPreciseEnabled - val preventSleep: StateFlow = runtime.preventSleep - val micEnabled: StateFlow = runtime.micEnabled - val micCooldown: StateFlow = runtime.micCooldown - val micStatusText: StateFlow = runtime.micStatusText - val micLiveTranscript: StateFlow = runtime.micLiveTranscript - val micIsListening: StateFlow = runtime.micIsListening - val micQueuedMessages: StateFlow> = runtime.micQueuedMessages - val micConversation: StateFlow> = runtime.micConversation - val micInputLevel: StateFlow = runtime.micInputLevel - val micIsSending: StateFlow = runtime.micIsSending - val speakerEnabled: StateFlow = runtime.speakerEnabled - val manualEnabled: StateFlow = runtime.manualEnabled - val manualHost: StateFlow = runtime.manualHost - val manualPort: StateFlow = runtime.manualPort - val manualTls: StateFlow = runtime.manualTls - val gatewayToken: StateFlow = runtime.gatewayToken - val onboardingCompleted: StateFlow = runtime.onboardingCompleted - val canvasDebugStatusEnabled: StateFlow = runtime.canvasDebugStatusEnabled + val gateways: StateFlow> = runtimeState(initial = emptyList()) { it.gateways } + val discoveryStatusText: StateFlow = runtimeState(initial = "Searching…") { it.discoveryStatusText } - val chatSessionKey: StateFlow = runtime.chatSessionKey - val chatSessionId: StateFlow = runtime.chatSessionId - val chatMessages = runtime.chatMessages - val chatError: StateFlow = runtime.chatError - val chatHealthOk: StateFlow = runtime.chatHealthOk - val chatThinkingLevel: StateFlow = runtime.chatThinkingLevel - val chatStreamingAssistantText: StateFlow = runtime.chatStreamingAssistantText - val chatPendingToolCalls = runtime.chatPendingToolCalls - val chatSessions = runtime.chatSessions - val pendingRunCount: StateFlow = runtime.pendingRunCount + val isConnected: StateFlow = runtimeState(initial = false) { it.isConnected } + val isNodeConnected: StateFlow = runtimeState(initial = false) { it.nodeConnected } + val statusText: StateFlow = runtimeState(initial = "Offline") { it.statusText } + val serverName: StateFlow = runtimeState(initial = null) { it.serverName } + val remoteAddress: StateFlow = runtimeState(initial = null) { it.remoteAddress } + val pendingGatewayTrust: StateFlow = runtimeState(initial = null) { it.pendingGatewayTrust } + val seamColorArgb: StateFlow = runtimeState(initial = 0xFF0EA5E9) { it.seamColorArgb } + val mainSessionKey: StateFlow = runtimeState(initial = "main") { it.mainSessionKey } + + val cameraHud: StateFlow = runtimeState(initial = null) { it.cameraHud } + val cameraFlashToken: StateFlow = runtimeState(initial = 0L) { it.cameraFlashToken } + + val instanceId: StateFlow = prefs.instanceId + val displayName: StateFlow = prefs.displayName + val cameraEnabled: StateFlow = prefs.cameraEnabled + val locationMode: StateFlow = prefs.locationMode + val locationPreciseEnabled: StateFlow = prefs.locationPreciseEnabled + val preventSleep: StateFlow = prefs.preventSleep + val manualEnabled: StateFlow = prefs.manualEnabled + val manualHost: StateFlow = prefs.manualHost + val manualPort: StateFlow = prefs.manualPort + val manualTls: StateFlow = prefs.manualTls + val gatewayToken: StateFlow = prefs.gatewayToken + val onboardingCompleted: StateFlow = prefs.onboardingCompleted + val canvasDebugStatusEnabled: StateFlow = prefs.canvasDebugStatusEnabled + val speakerEnabled: StateFlow = prefs.speakerEnabled + val micEnabled: StateFlow = prefs.talkEnabled + + val micCooldown: StateFlow = runtimeState(initial = false) { it.micCooldown } + val micStatusText: StateFlow = runtimeState(initial = "Mic off") { it.micStatusText } + val micLiveTranscript: StateFlow = runtimeState(initial = null) { it.micLiveTranscript } + val micIsListening: StateFlow = runtimeState(initial = false) { it.micIsListening } + val micQueuedMessages: StateFlow> = runtimeState(initial = emptyList()) { it.micQueuedMessages } + val micConversation: StateFlow> = runtimeState(initial = emptyList()) { it.micConversation } + val micInputLevel: StateFlow = runtimeState(initial = 0f) { it.micInputLevel } + val micIsSending: StateFlow = runtimeState(initial = false) { it.micIsSending } + + val chatSessionKey: StateFlow = runtimeState(initial = "main") { it.chatSessionKey } + val chatSessionId: StateFlow = runtimeState(initial = null) { it.chatSessionId } + val chatMessages: StateFlow> = runtimeState(initial = emptyList()) { it.chatMessages } + val chatError: StateFlow = runtimeState(initial = null) { it.chatError } + val chatHealthOk: StateFlow = runtimeState(initial = false) { it.chatHealthOk } + val chatThinkingLevel: StateFlow = runtimeState(initial = "off") { it.chatThinkingLevel } + val chatStreamingAssistantText: StateFlow = runtimeState(initial = null) { it.chatStreamingAssistantText } + val chatPendingToolCalls: StateFlow> = runtimeState(initial = emptyList()) { it.chatPendingToolCalls } + val chatSessions: StateFlow> = runtimeState(initial = emptyList()) { it.chatSessions } + val pendingRunCount: StateFlow = runtimeState(initial = 0) { it.pendingRunCount } + + init { + if (prefs.onboardingCompleted.value) { + ensureRuntime() + } + } + + val canvas: CanvasController + get() = ensureRuntime().canvas + + val camera: CameraCaptureManager + get() = ensureRuntime().camera + + val sms: SmsManager + get() = ensureRuntime().sms + + fun attachRuntimeUi(owner: LifecycleOwner, permissionRequester: PermissionRequester) { + val runtime = runtimeRef.value ?: return + runtime.camera.attachLifecycleOwner(owner) + runtime.camera.attachPermissionRequester(permissionRequester) + runtime.sms.attachPermissionRequester(permissionRequester) + } fun setForeground(value: Boolean) { - runtime.setForeground(value) + foreground = value + runtimeRef.value?.setForeground(value) } fun setDisplayName(value: String) { - runtime.setDisplayName(value) + prefs.setDisplayName(value) } fun setCameraEnabled(value: Boolean) { - runtime.setCameraEnabled(value) + prefs.setCameraEnabled(value) } fun setLocationMode(mode: LocationMode) { - runtime.setLocationMode(mode) + prefs.setLocationMode(mode) } fun setLocationPreciseEnabled(value: Boolean) { - runtime.setLocationPreciseEnabled(value) + prefs.setLocationPreciseEnabled(value) } fun setPreventSleep(value: Boolean) { - runtime.setPreventSleep(value) + prefs.setPreventSleep(value) } fun setManualEnabled(value: Boolean) { - runtime.setManualEnabled(value) + prefs.setManualEnabled(value) } fun setManualHost(value: String) { - runtime.setManualHost(value) + prefs.setManualHost(value) } fun setManualPort(value: Int) { - runtime.setManualPort(value) + prefs.setManualPort(value) } fun setManualTls(value: Boolean) { - runtime.setManualTls(value) + prefs.setManualTls(value) } fun setGatewayToken(value: String) { - runtime.setGatewayToken(value) + prefs.setGatewayToken(value) + } + + fun setGatewayBootstrapToken(value: String) { + prefs.setGatewayBootstrapToken(value) } fun setGatewayPassword(value: String) { - runtime.setGatewayPassword(value) + prefs.setGatewayPassword(value) } fun setOnboardingCompleted(value: Boolean) { - runtime.setOnboardingCompleted(value) + if (value) { + ensureRuntime() + } + prefs.setOnboardingCompleted(value) } fun setCanvasDebugStatusEnabled(value: Boolean) { - runtime.setCanvasDebugStatusEnabled(value) + prefs.setCanvasDebugStatusEnabled(value) } fun setVoiceScreenActive(active: Boolean) { - runtime.setVoiceScreenActive(active) + ensureRuntime().setVoiceScreenActive(active) } fun setMicEnabled(enabled: Boolean) { - runtime.setMicEnabled(enabled) + ensureRuntime().setMicEnabled(enabled) } fun setSpeakerEnabled(enabled: Boolean) { - runtime.setSpeakerEnabled(enabled) + ensureRuntime().setSpeakerEnabled(enabled) } fun refreshGatewayConnection() { - runtime.refreshGatewayConnection() + ensureRuntime().refreshGatewayConnection() } fun connect(endpoint: GatewayEndpoint) { - runtime.connect(endpoint) + ensureRuntime().connect(endpoint) } fun connectManual() { - runtime.connectManual() + ensureRuntime().connectManual() } fun disconnect() { - runtime.disconnect() + runtimeRef.value?.disconnect() } fun acceptGatewayTrustPrompt() { - runtime.acceptGatewayTrustPrompt() + runtimeRef.value?.acceptGatewayTrustPrompt() } fun declineGatewayTrustPrompt() { - runtime.declineGatewayTrustPrompt() + runtimeRef.value?.declineGatewayTrustPrompt() } fun handleCanvasA2UIActionFromWebView(payloadJson: String) { - runtime.handleCanvasA2UIActionFromWebView(payloadJson) + ensureRuntime().handleCanvasA2UIActionFromWebView(payloadJson) } fun requestCanvasRehydrate(source: String = "screen_tab") { - runtime.requestCanvasRehydrate(source = source, force = true) + ensureRuntime().requestCanvasRehydrate(source = source, force = true) + } + + fun refreshHomeCanvasOverviewIfConnected() { + ensureRuntime().refreshHomeCanvasOverviewIfConnected() } fun loadChat(sessionKey: String) { - runtime.loadChat(sessionKey) + ensureRuntime().loadChat(sessionKey) } fun refreshChat() { - runtime.refreshChat() + ensureRuntime().refreshChat() } fun refreshChatSessions(limit: Int? = null) { - runtime.refreshChatSessions(limit = limit) + ensureRuntime().refreshChatSessions(limit = limit) } fun setChatThinkingLevel(level: String) { - runtime.setChatThinkingLevel(level) + ensureRuntime().setChatThinkingLevel(level) } fun switchChatSession(sessionKey: String) { - runtime.switchChatSession(sessionKey) + ensureRuntime().switchChatSession(sessionKey) } fun abortChat() { - runtime.abortChat() + ensureRuntime().abortChat() } fun sendChat(message: String, thinking: String, attachments: List) { - runtime.sendChat(message = message, thinking = thinking, attachments = attachments) + ensureRuntime().sendChat(message = message, thinking = thinking, attachments = attachments) } } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeApp.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeApp.kt index 0d172a8abe7..adfd4b73907 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeApp.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeApp.kt @@ -4,7 +4,18 @@ import android.app.Application import android.os.StrictMode class NodeApp : Application() { - val runtime: NodeRuntime by lazy { NodeRuntime(this) } + val prefs: SecurePrefs by lazy { SecurePrefs(this) } + + @Volatile private var runtimeInstance: NodeRuntime? = null + + fun ensureRuntime(): NodeRuntime { + runtimeInstance?.let { return it } + return synchronized(this) { + runtimeInstance ?: NodeRuntime(this, prefs).also { runtimeInstance = it } + } + } + + fun peekRuntime(): NodeRuntime? = runtimeInstance override fun onCreate() { super.onCreate() diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt index 5761567ebcc..4c7ccdd56e5 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt @@ -28,7 +28,11 @@ class NodeForegroundService : Service() { val initial = buildNotification(title = "OpenClaw Node", text = "Starting…") startForegroundWithTypes(notification = initial) - val runtime = (application as NodeApp).runtime + val runtime = (application as NodeApp).peekRuntime() + if (runtime == null) { + stopSelf() + return + } notificationJob = scope.launch { combine( @@ -59,7 +63,7 @@ class NodeForegroundService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { ACTION_STOP -> { - (application as NodeApp).runtime.disconnect() + (application as NodeApp).peekRuntime()?.disconnect() stopSelf() return START_NOT_STICKY } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt index c4e5f6a5b1d..3b37c5b01e2 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt @@ -33,6 +33,8 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject @@ -41,11 +43,12 @@ import kotlinx.serialization.json.buildJsonObject import java.util.UUID import java.util.concurrent.atomic.AtomicLong -class NodeRuntime(context: Context) { +class NodeRuntime( + context: Context, + val prefs: SecurePrefs = SecurePrefs(context.applicationContext), +) { private val appContext = context.applicationContext private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - - val prefs = SecurePrefs(appContext) private val deviceAuthStore = DeviceAuthStore(prefs) val canvas = CanvasController() val camera = CameraCaptureManager(appContext) @@ -108,6 +111,10 @@ class NodeRuntime(context: Context) { appContext = appContext, ) + private val callLogHandler: CallLogHandler = CallLogHandler( + appContext = appContext, + ) + private val motionHandler: MotionHandler = MotionHandler( appContext = appContext, ) @@ -130,7 +137,8 @@ class NodeRuntime(context: Context) { voiceWakeMode = { VoiceWakeMode.Off }, motionActivityAvailable = { motionHandler.isActivityAvailable() }, motionPedometerAvailable = { motionHandler.isPedometerAvailable() }, - smsAvailable = { sms.canSendSms() }, + sendSmsAvailable = { sms.canSendSms() }, + readSmsAvailable = { sms.canReadSms() }, hasRecordAudioPermission = { hasRecordAudioPermission() }, manualTls = { manualTls.value }, ) @@ -149,10 +157,12 @@ class NodeRuntime(context: Context) { smsHandler = smsHandlerImpl, a2uiHandler = a2uiHandler, debugHandler = debugHandler, + callLogHandler = callLogHandler, isForeground = { _isForeground.value }, cameraEnabled = { cameraEnabled.value }, locationEnabled = { locationMode.value != LocationMode.Off }, - smsAvailable = { sms.canSendSms() }, + sendSmsAvailable = { sms.canSendSms() }, + readSmsAvailable = { sms.canReadSms() }, debugBuild = { BuildConfig.DEBUG }, refreshNodeCanvasCapability = { nodeSession.refreshNodeCanvasCapability() }, onCanvasA2uiPush = { @@ -210,7 +220,8 @@ class NodeRuntime(context: Context) { private val _isForeground = MutableStateFlow(true) val isForeground: StateFlow = _isForeground.asStateFlow() - private var lastAutoA2uiUrl: String? = null + private var gatewayDefaultAgentId: String? = null + private var gatewayAgents: List = emptyList() private var didAutoRequestCanvasRehydrate = false private val canvasRehydrateSeq = AtomicLong(0) private var operatorConnected = false @@ -232,7 +243,7 @@ class NodeRuntime(context: Context) { updateStatus() micCapture.onGatewayConnectionChanged(true) scope.launch { - refreshBrandingFromGateway() + refreshHomeCanvasOverviewIfConnected() if (voiceReplySpeakerLazy.isInitialized()) { voiceReplySpeaker.refreshConfig() } @@ -270,7 +281,7 @@ class NodeRuntime(context: Context) { _canvasRehydratePending.value = false _canvasRehydrateErrorText.value = null updateStatus() - maybeNavigateToA2uiOnConnect() + showLocalCanvasOnConnect() }, onDisconnected = { message -> _nodeConnected.value = false @@ -396,6 +407,7 @@ class NodeRuntime(context: Context) { _mainSessionKey.value = trimmed talkMode.setMainSessionKey(trimmed) chat.applyMainSessionKey(trimmed) + updateHomeCanvasState() } private fun updateStatus() { @@ -415,6 +427,7 @@ class NodeRuntime(context: Context) { operator.isNotBlank() && operator != "Offline" -> operator else -> node } + updateHomeCanvasState() } private fun resolveMainSessionKey(): String { @@ -422,23 +435,31 @@ class NodeRuntime(context: Context) { return if (trimmed.isEmpty()) "main" else trimmed } - private fun maybeNavigateToA2uiOnConnect() { - val a2uiUrl = a2uiHandler.resolveA2uiHostUrl() ?: return - val current = canvas.currentUrl()?.trim().orEmpty() - if (current.isEmpty() || current == lastAutoA2uiUrl) { - lastAutoA2uiUrl = a2uiUrl - canvas.navigate(a2uiUrl) - } - } - - private fun showLocalCanvasOnDisconnect() { - lastAutoA2uiUrl = null + private fun showLocalCanvasOnConnect() { _canvasA2uiHydrated.value = false _canvasRehydratePending.value = false _canvasRehydrateErrorText.value = null canvas.navigate("") } + private fun showLocalCanvasOnDisconnect() { + _canvasA2uiHydrated.value = false + _canvasRehydratePending.value = false + _canvasRehydrateErrorText.value = null + canvas.navigate("") + } + + fun refreshHomeCanvasOverviewIfConnected() { + if (!operatorConnected) { + updateHomeCanvasState() + return + } + scope.launch { + refreshBrandingFromGateway() + refreshAgentsFromGateway() + } + } + fun requestCanvasRehydrate(source: String = "manual", force: Boolean = true) { scope.launch { if (!_nodeConnected.value) { @@ -503,6 +524,7 @@ class NodeRuntime(context: Context) { val gatewayToken: StateFlow = prefs.gatewayToken val onboardingCompleted: StateFlow = prefs.onboardingCompleted fun setGatewayToken(value: String) = prefs.setGatewayToken(value) + fun setGatewayBootstrapToken(value: String) = prefs.setGatewayBootstrapToken(value) fun setGatewayPassword(value: String) = prefs.setGatewayPassword(value) fun setOnboardingCompleted(value: Boolean) = prefs.setOnboardingCompleted(value) val lastDiscoveredStableId: StateFlow = prefs.lastDiscoveredStableId @@ -601,6 +623,8 @@ class NodeRuntime(context: Context) { canvas.setDebugStatus(status, server ?: remote) } } + + updateHomeCanvasState() } fun setForeground(value: Boolean) { @@ -698,10 +722,25 @@ class NodeRuntime(context: Context) { operatorStatusText = "Connecting…" updateStatus() val token = prefs.loadGatewayToken() + val bootstrapToken = prefs.loadGatewayBootstrapToken() val password = prefs.loadGatewayPassword() val tls = connectionManager.resolveTlsParams(endpoint) - operatorSession.connect(endpoint, token, password, connectionManager.buildOperatorConnectOptions(), tls) - nodeSession.connect(endpoint, token, password, connectionManager.buildNodeConnectOptions(), tls) + operatorSession.connect( + endpoint, + token, + bootstrapToken, + password, + connectionManager.buildOperatorConnectOptions(), + tls, + ) + nodeSession.connect( + endpoint, + token, + bootstrapToken, + password, + connectionManager.buildNodeConnectOptions(), + tls, + ) operatorSession.reconnect() nodeSession.reconnect() } @@ -726,9 +765,24 @@ class NodeRuntime(context: Context) { nodeStatusText = "Connecting…" updateStatus() val token = prefs.loadGatewayToken() + val bootstrapToken = prefs.loadGatewayBootstrapToken() val password = prefs.loadGatewayPassword() - operatorSession.connect(endpoint, token, password, connectionManager.buildOperatorConnectOptions(), tls) - nodeSession.connect(endpoint, token, password, connectionManager.buildNodeConnectOptions(), tls) + operatorSession.connect( + endpoint, + token, + bootstrapToken, + password, + connectionManager.buildOperatorConnectOptions(), + tls, + ) + nodeSession.connect( + endpoint, + token, + bootstrapToken, + password, + connectionManager.buildNodeConnectOptions(), + tls, + ) } fun acceptGatewayTrustPrompt() { @@ -897,11 +951,177 @@ class NodeRuntime(context: Context) { val parsed = parseHexColorArgb(raw) _seamColorArgb.value = parsed ?: DEFAULT_SEAM_COLOR_ARGB + updateHomeCanvasState() } catch (_: Throwable) { // ignore } } + private suspend fun refreshAgentsFromGateway() { + if (!operatorConnected) return + try { + val res = operatorSession.request("agents.list", "{}") + val root = json.parseToJsonElement(res).asObjectOrNull() ?: return + val defaultAgentId = root["defaultId"].asStringOrNull()?.trim().orEmpty() + val mainKey = normalizeMainKey(root["mainKey"].asStringOrNull()) + val agents = + (root["agents"] as? JsonArray)?.mapNotNull { item -> + val obj = item.asObjectOrNull() ?: return@mapNotNull null + val id = obj["id"].asStringOrNull()?.trim().orEmpty() + if (id.isEmpty()) return@mapNotNull null + val name = obj["name"].asStringOrNull()?.trim() + val emoji = obj["identity"].asObjectOrNull()?.get("emoji").asStringOrNull()?.trim() + GatewayAgentSummary( + id = id, + name = name?.takeIf { it.isNotEmpty() }, + emoji = emoji?.takeIf { it.isNotEmpty() }, + ) + } ?: emptyList() + + gatewayDefaultAgentId = defaultAgentId.ifEmpty { null } + gatewayAgents = agents + applyMainSessionKey(mainKey) + updateHomeCanvasState() + } catch (_: Throwable) { + // ignore + } + } + + private fun updateHomeCanvasState() { + val payload = + try { + json.encodeToString(makeHomeCanvasPayload()) + } catch (_: Throwable) { + null + } + canvas.updateHomeCanvasState(payload) + } + + private fun makeHomeCanvasPayload(): HomeCanvasPayload { + val state = resolveHomeCanvasGatewayState() + val gatewayName = normalized(_serverName.value) + val gatewayAddress = normalized(_remoteAddress.value) + val gatewayLabel = gatewayName ?: gatewayAddress ?: "Gateway" + val activeAgentId = resolveActiveAgentId() + val agents = homeCanvasAgents(activeAgentId) + + return when (state) { + HomeCanvasGatewayState.Connected -> + HomeCanvasPayload( + gatewayState = "connected", + eyebrow = "Connected to $gatewayLabel", + title = "Your agents are ready", + subtitle = + "This phone stays dormant until the gateway needs it, then wakes, syncs, and goes back to sleep.", + gatewayLabel = gatewayLabel, + activeAgentName = resolveActiveAgentName(activeAgentId), + activeAgentBadge = agents.firstOrNull { it.isActive }?.badge ?: "OC", + activeAgentCaption = "Selected on this phone", + agentCount = agents.size, + agents = agents.take(6), + footer = "The overview refreshes on reconnect and when this screen opens.", + ) + HomeCanvasGatewayState.Connecting -> + HomeCanvasPayload( + gatewayState = "connecting", + eyebrow = "Reconnecting", + title = "OpenClaw is syncing back up", + subtitle = + "The gateway session is coming back online. Agent shortcuts should settle automatically in a moment.", + gatewayLabel = gatewayLabel, + activeAgentName = resolveActiveAgentName(activeAgentId), + activeAgentBadge = "OC", + activeAgentCaption = "Gateway session in progress", + agentCount = agents.size, + agents = agents.take(4), + footer = "If the gateway is reachable, reconnect should complete without intervention.", + ) + HomeCanvasGatewayState.Error, HomeCanvasGatewayState.Offline -> + HomeCanvasPayload( + gatewayState = if (state == HomeCanvasGatewayState.Error) "error" else "offline", + eyebrow = "Welcome to OpenClaw", + title = "Your phone stays quiet until it is needed", + subtitle = + "Pair this device to your gateway to wake it only for real work, keep a live agent overview handy, and avoid battery-draining background loops.", + gatewayLabel = gatewayLabel, + activeAgentName = "Main", + activeAgentBadge = "OC", + activeAgentCaption = "Connect to load your agents", + agentCount = agents.size, + agents = agents.take(4), + footer = "When connected, the gateway can wake the phone with a silent push instead of holding an always-on session.", + ) + } + } + + private fun resolveHomeCanvasGatewayState(): HomeCanvasGatewayState { + val lower = _statusText.value.trim().lowercase() + return when { + _isConnected.value -> HomeCanvasGatewayState.Connected + lower.contains("connecting") || lower.contains("reconnecting") -> HomeCanvasGatewayState.Connecting + lower.contains("error") || lower.contains("failed") -> HomeCanvasGatewayState.Error + else -> HomeCanvasGatewayState.Offline + } + } + + private fun resolveActiveAgentId(): String { + val mainKey = _mainSessionKey.value.trim() + if (mainKey.startsWith("agent:")) { + val agentId = mainKey.removePrefix("agent:").substringBefore(':').trim() + if (agentId.isNotEmpty()) return agentId + } + return gatewayDefaultAgentId?.trim().orEmpty() + } + + private fun resolveActiveAgentName(activeAgentId: String): String { + if (activeAgentId.isNotEmpty()) { + gatewayAgents.firstOrNull { it.id == activeAgentId }?.let { agent -> + return normalized(agent.name) ?: agent.id + } + return activeAgentId + } + return gatewayAgents.firstOrNull()?.let { normalized(it.name) ?: it.id } ?: "Main" + } + + private fun homeCanvasAgents(activeAgentId: String): List { + val defaultAgentId = gatewayDefaultAgentId?.trim().orEmpty() + return gatewayAgents + .map { agent -> + val isActive = activeAgentId.isNotEmpty() && agent.id == activeAgentId + val isDefault = defaultAgentId.isNotEmpty() && agent.id == defaultAgentId + HomeCanvasAgentCard( + id = agent.id, + name = normalized(agent.name) ?: agent.id, + badge = homeCanvasBadge(agent), + caption = + when { + isActive -> "Active on this phone" + isDefault -> "Default agent" + else -> "Ready" + }, + isActive = isActive, + ) + }.sortedWith(compareByDescending { it.isActive }.thenBy { it.name.lowercase() }) + } + + private fun homeCanvasBadge(agent: GatewayAgentSummary): String { + val emoji = normalized(agent.emoji) + if (emoji != null) return emoji + val initials = + (normalized(agent.name) ?: agent.id) + .split(' ', '-', '_') + .filter { it.isNotBlank() } + .take(2) + .mapNotNull { token -> token.firstOrNull()?.uppercaseChar()?.toString() } + .joinToString("") + return if (initials.isNotEmpty()) initials else "OC" + } + + private fun normalized(value: String?): String? { + val trimmed = value?.trim().orEmpty() + return trimmed.ifEmpty { null } + } + private fun triggerCameraFlash() { // Token is used as a pulse trigger; value doesn't matter as long as it changes. _cameraFlashToken.value = SystemClock.elapsedRealtimeNanos() @@ -920,3 +1140,40 @@ class NodeRuntime(context: Context) { } } + +private enum class HomeCanvasGatewayState { + Connected, + Connecting, + Error, + Offline, +} + +private data class GatewayAgentSummary( + val id: String, + val name: String?, + val emoji: String?, +) + +@Serializable +private data class HomeCanvasPayload( + val gatewayState: String, + val eyebrow: String, + val title: String, + val subtitle: String, + val gatewayLabel: String, + val activeAgentName: String, + val activeAgentBadge: String, + val activeAgentCaption: String, + val agentCount: Int, + val agents: List, + val footer: String, +) + +@Serializable +private data class HomeCanvasAgentCard( + val id: String, + val name: String, + val badge: String, + val caption: String, + val isActive: Boolean, +) diff --git a/apps/android/app/src/main/java/ai/openclaw/app/SecurePrefs.kt b/apps/android/app/src/main/java/ai/openclaw/app/SecurePrefs.kt index b7e72ee4126..a1aabeb1b3c 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/SecurePrefs.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/SecurePrefs.kt @@ -15,7 +15,10 @@ import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonPrimitive import java.util.UUID -class SecurePrefs(context: Context) { +class SecurePrefs( + context: Context, + private val securePrefsOverride: SharedPreferences? = null, +) { companion object { val defaultWakeWords: List = listOf("openclaw", "claude") private const val displayNameKey = "node.displayName" @@ -35,7 +38,7 @@ class SecurePrefs(context: Context) { .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() } - private val securePrefs: SharedPreferences by lazy { createSecurePrefs(appContext, securePrefsName) } + private val securePrefs: SharedPreferences by lazy { securePrefsOverride ?: createSecurePrefs(appContext, securePrefsName) } private val _instanceId = MutableStateFlow(loadOrCreateInstanceId()) val instanceId: StateFlow = _instanceId @@ -76,6 +79,9 @@ class SecurePrefs(context: Context) { private val _gatewayToken = MutableStateFlow("") val gatewayToken: StateFlow = _gatewayToken + private val _gatewayBootstrapToken = MutableStateFlow("") + val gatewayBootstrapToken: StateFlow = _gatewayBootstrapToken + private val _onboardingCompleted = MutableStateFlow(plainPrefs.getBoolean("onboarding.completed", false)) val onboardingCompleted: StateFlow = _onboardingCompleted @@ -165,6 +171,10 @@ class SecurePrefs(context: Context) { saveGatewayPassword(value) } + fun setGatewayBootstrapToken(value: String) { + saveGatewayBootstrapToken(value) + } + fun setOnboardingCompleted(value: Boolean) { plainPrefs.edit { putBoolean("onboarding.completed", value) } _onboardingCompleted.value = value @@ -193,6 +203,26 @@ class SecurePrefs(context: Context) { securePrefs.edit { putString(key, token.trim()) } } + fun loadGatewayBootstrapToken(): String? { + val key = "gateway.bootstrapToken.${_instanceId.value}" + val stored = + _gatewayBootstrapToken.value.trim().ifEmpty { + val persisted = securePrefs.getString(key, null)?.trim().orEmpty() + if (persisted.isNotEmpty()) { + _gatewayBootstrapToken.value = persisted + } + persisted + } + return stored.takeIf { it.isNotEmpty() } + } + + fun saveGatewayBootstrapToken(token: String) { + val key = "gateway.bootstrapToken.${_instanceId.value}" + val trimmed = token.trim() + securePrefs.edit { putString(key, trimmed) } + _gatewayBootstrapToken.value = trimmed + } + fun loadGatewayPassword(): String? { val key = "gateway.password.${_instanceId.value}" val stored = securePrefs.getString(key, null)?.trim() diff --git a/apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt b/apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt index be430480fb0..37bb3f472ee 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt @@ -265,7 +265,7 @@ class ChatController( } val historyJson = session.request("chat.history", """{"sessionKey":"$key"}""") - val history = parseHistory(historyJson, sessionKey = key) + val history = parseHistory(historyJson, sessionKey = key, previousMessages = _messages.value) _messages.value = history.messages _sessionId.value = history.sessionId history.thinkingLevel?.trim()?.takeIf { it.isNotEmpty() }?.let { _thinkingLevel.value = it } @@ -336,7 +336,7 @@ class ChatController( try { val historyJson = session.request("chat.history", """{"sessionKey":"${_sessionKey.value}"}""") - val history = parseHistory(historyJson, sessionKey = _sessionKey.value) + val history = parseHistory(historyJson, sessionKey = _sessionKey.value, previousMessages = _messages.value) _messages.value = history.messages _sessionId.value = history.sessionId history.thinkingLevel?.trim()?.takeIf { it.isNotEmpty() }?.let { _thinkingLevel.value = it } @@ -450,7 +450,11 @@ class ChatController( } } - private fun parseHistory(historyJson: String, sessionKey: String): ChatHistory { + private fun parseHistory( + historyJson: String, + sessionKey: String, + previousMessages: List, + ): ChatHistory { val root = json.parseToJsonElement(historyJson).asObjectOrNull() ?: return ChatHistory(sessionKey, null, null, emptyList()) val sid = root["sessionId"].asStringOrNull() val thinkingLevel = root["thinkingLevel"].asStringOrNull() @@ -470,7 +474,12 @@ class ChatController( ) } - return ChatHistory(sessionKey = sessionKey, sessionId = sid, thinkingLevel = thinkingLevel, messages = messages) + return ChatHistory( + sessionKey = sessionKey, + sessionId = sid, + thinkingLevel = thinkingLevel, + messages = reconcileMessageIds(previous = previousMessages, incoming = messages), + ) } private fun parseMessageContent(el: JsonElement): ChatMessageContent? { @@ -519,6 +528,47 @@ class ChatController( } } +internal fun reconcileMessageIds(previous: List, incoming: List): List { + if (previous.isEmpty() || incoming.isEmpty()) return incoming + + val idsByKey = LinkedHashMap>() + for (message in previous) { + val key = messageIdentityKey(message) ?: continue + idsByKey.getOrPut(key) { ArrayDeque() }.addLast(message.id) + } + + return incoming.map { message -> + val key = messageIdentityKey(message) ?: return@map message + val ids = idsByKey[key] ?: return@map message + val reusedId = ids.removeFirstOrNull() ?: return@map message + if (ids.isEmpty()) { + idsByKey.remove(key) + } + if (reusedId == message.id) return@map message + message.copy(id = reusedId) + } +} + +internal fun messageIdentityKey(message: ChatMessage): String? { + val role = message.role.trim().lowercase() + if (role.isEmpty()) return null + + val timestamp = message.timestampMs?.toString().orEmpty() + val contentFingerprint = + message.content.joinToString(separator = "\u001E") { part -> + listOf( + part.type.trim().lowercase(), + part.text?.trim().orEmpty(), + part.mimeType?.trim()?.lowercase().orEmpty(), + part.fileName?.trim().orEmpty(), + part.base64?.hashCode()?.toString().orEmpty(), + ).joinToString(separator = "\u001F") + } + + if (timestamp.isEmpty() && contentFingerprint.isEmpty()) return null + return listOf(role, timestamp, contentFingerprint).joinToString(separator = "|") +} + private fun JsonElement?.asObjectOrNull(): JsonObject? = this as? JsonObject private fun JsonElement?.asArrayOrNull(): JsonArray? = this as? JsonArray diff --git a/apps/android/app/src/main/java/ai/openclaw/app/gateway/DeviceAuthStore.kt b/apps/android/app/src/main/java/ai/openclaw/app/gateway/DeviceAuthStore.kt index d1ac63a90ff..202ea4820e1 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/gateway/DeviceAuthStore.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/gateway/DeviceAuthStore.kt @@ -5,6 +5,7 @@ import ai.openclaw.app.SecurePrefs interface DeviceAuthTokenStore { fun loadToken(deviceId: String, role: String): String? fun saveToken(deviceId: String, role: String, token: String) + fun clearToken(deviceId: String, role: String) } class DeviceAuthStore(private val prefs: SecurePrefs) : DeviceAuthTokenStore { @@ -18,7 +19,7 @@ class DeviceAuthStore(private val prefs: SecurePrefs) : DeviceAuthTokenStore { prefs.putString(key, token.trim()) } - fun clearToken(deviceId: String, role: String) { + override fun clearToken(deviceId: String, role: String) { val key = tokenKey(deviceId, role) prefs.remove(key) } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewaySession.kt b/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewaySession.kt index aee47eaada8..55e371a57c7 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewaySession.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewaySession.kt @@ -52,6 +52,33 @@ data class GatewayConnectOptions( val userAgent: String? = null, ) +private enum class GatewayConnectAuthSource { + DEVICE_TOKEN, + SHARED_TOKEN, + BOOTSTRAP_TOKEN, + PASSWORD, + NONE, +} + +data class GatewayConnectErrorDetails( + val code: String?, + val canRetryWithDeviceToken: Boolean, + val recommendedNextStep: String?, +) + +private data class SelectedConnectAuth( + val authToken: String?, + val authBootstrapToken: String?, + val authDeviceToken: String?, + val authPassword: String?, + val signatureToken: String?, + val authSource: GatewayConnectAuthSource, + val attemptedDeviceTokenRetry: Boolean, +) + +private class GatewayConnectFailure(val gatewayError: GatewaySession.ErrorShape) : + IllegalStateException(gatewayError.message) + class GatewaySession( private val scope: CoroutineScope, private val identityStore: DeviceIdentityStore, @@ -83,7 +110,11 @@ class GatewaySession( } } - data class ErrorShape(val code: String, val message: String) + data class ErrorShape( + val code: String, + val message: String, + val details: GatewayConnectErrorDetails? = null, + ) private val json = Json { ignoreUnknownKeys = true } private val writeLock = Mutex() @@ -95,6 +126,7 @@ class GatewaySession( private data class DesiredConnection( val endpoint: GatewayEndpoint, val token: String?, + val bootstrapToken: String?, val password: String?, val options: GatewayConnectOptions, val tls: GatewayTlsParams?, @@ -103,15 +135,22 @@ class GatewaySession( private var desired: DesiredConnection? = null private var job: Job? = null @Volatile private var currentConnection: Connection? = null + @Volatile private var pendingDeviceTokenRetry = false + @Volatile private var deviceTokenRetryBudgetUsed = false + @Volatile private var reconnectPausedForAuthFailure = false fun connect( endpoint: GatewayEndpoint, token: String?, + bootstrapToken: String?, password: String?, options: GatewayConnectOptions, tls: GatewayTlsParams? = null, ) { - desired = DesiredConnection(endpoint, token, password, options, tls) + desired = DesiredConnection(endpoint, token, bootstrapToken, password, options, tls) + pendingDeviceTokenRetry = false + deviceTokenRetryBudgetUsed = false + reconnectPausedForAuthFailure = false if (job == null) { job = scope.launch(Dispatchers.IO) { runLoop() } } @@ -119,6 +158,9 @@ class GatewaySession( fun disconnect() { desired = null + pendingDeviceTokenRetry = false + deviceTokenRetryBudgetUsed = false + reconnectPausedForAuthFailure = false currentConnection?.closeQuietly() scope.launch(Dispatchers.IO) { job?.cancelAndJoin() @@ -130,6 +172,7 @@ class GatewaySession( } fun reconnect() { + reconnectPausedForAuthFailure = false currentConnection?.closeQuietly() } @@ -219,6 +262,7 @@ class GatewaySession( private inner class Connection( private val endpoint: GatewayEndpoint, private val token: String?, + private val bootstrapToken: String?, private val password: String?, private val options: GatewayConnectOptions, private val tls: GatewayTlsParams?, @@ -344,15 +388,48 @@ class GatewaySession( private suspend fun sendConnect(connectNonce: String) { val identity = identityStore.loadOrCreate() - val storedToken = deviceAuthStore.loadToken(identity.deviceId, options.role) - val trimmedToken = token?.trim().orEmpty() - // QR/setup/manual shared token must take precedence; stale role tokens can survive re-onboarding. - val authToken = if (trimmedToken.isNotBlank()) trimmedToken else storedToken.orEmpty() - val payload = buildConnectParams(identity, connectNonce, authToken, password?.trim()) + val storedToken = deviceAuthStore.loadToken(identity.deviceId, options.role)?.trim() + val selectedAuth = + selectConnectAuth( + endpoint = endpoint, + tls = tls, + role = options.role, + explicitGatewayToken = token?.trim()?.takeIf { it.isNotEmpty() }, + explicitBootstrapToken = bootstrapToken?.trim()?.takeIf { it.isNotEmpty() }, + explicitPassword = password?.trim()?.takeIf { it.isNotEmpty() }, + storedToken = storedToken?.takeIf { it.isNotEmpty() }, + ) + if (selectedAuth.attemptedDeviceTokenRetry) { + pendingDeviceTokenRetry = false + } + val payload = + buildConnectParams( + identity = identity, + connectNonce = connectNonce, + selectedAuth = selectedAuth, + ) val res = request("connect", payload, timeoutMs = CONNECT_RPC_TIMEOUT_MS) if (!res.ok) { - val msg = res.error?.message ?: "connect failed" - throw IllegalStateException(msg) + val error = res.error ?: ErrorShape("UNAVAILABLE", "connect failed") + val shouldRetryWithDeviceToken = + shouldRetryWithStoredDeviceToken( + error = error, + explicitGatewayToken = token?.trim()?.takeIf { it.isNotEmpty() }, + storedToken = storedToken?.takeIf { it.isNotEmpty() }, + attemptedDeviceTokenRetry = selectedAuth.attemptedDeviceTokenRetry, + endpoint = endpoint, + tls = tls, + ) + if (shouldRetryWithDeviceToken) { + pendingDeviceTokenRetry = true + deviceTokenRetryBudgetUsed = true + } else if ( + selectedAuth.attemptedDeviceTokenRetry && + shouldClearStoredDeviceTokenAfterRetry(error) + ) { + deviceAuthStore.clearToken(identity.deviceId, options.role) + } + throw GatewayConnectFailure(error) } handleConnectSuccess(res, identity.deviceId) connectDeferred.complete(Unit) @@ -361,6 +438,9 @@ class GatewaySession( private fun handleConnectSuccess(res: RpcResponse, deviceId: String) { val payloadJson = res.payloadJson ?: throw IllegalStateException("connect failed: missing payload") val obj = json.parseToJsonElement(payloadJson).asObjectOrNull() ?: throw IllegalStateException("connect failed") + pendingDeviceTokenRetry = false + deviceTokenRetryBudgetUsed = false + reconnectPausedForAuthFailure = false val serverName = obj["server"].asObjectOrNull()?.get("host").asStringOrNull() val authObj = obj["auth"].asObjectOrNull() val deviceToken = authObj?.get("deviceToken").asStringOrNull() @@ -380,8 +460,7 @@ class GatewaySession( private fun buildConnectParams( identity: DeviceIdentity, connectNonce: String, - authToken: String, - authPassword: String?, + selectedAuth: SelectedConnectAuth, ): JsonObject { val client = options.client val locale = Locale.getDefault().toLanguageTag() @@ -397,16 +476,20 @@ class GatewaySession( client.modelIdentifier?.let { put("modelIdentifier", JsonPrimitive(it)) } } - val password = authPassword?.trim().orEmpty() val authJson = when { - authToken.isNotEmpty() -> + selectedAuth.authToken != null -> buildJsonObject { - put("token", JsonPrimitive(authToken)) + put("token", JsonPrimitive(selectedAuth.authToken)) + selectedAuth.authDeviceToken?.let { put("deviceToken", JsonPrimitive(it)) } } - password.isNotEmpty() -> + selectedAuth.authBootstrapToken != null -> buildJsonObject { - put("password", JsonPrimitive(password)) + put("bootstrapToken", JsonPrimitive(selectedAuth.authBootstrapToken)) + } + selectedAuth.authPassword != null -> + buildJsonObject { + put("password", JsonPrimitive(selectedAuth.authPassword)) } else -> null } @@ -420,7 +503,7 @@ class GatewaySession( role = options.role, scopes = options.scopes, signedAtMs = signedAtMs, - token = if (authToken.isNotEmpty()) authToken else null, + token = selectedAuth.signatureToken, nonce = connectNonce, platform = client.platform, deviceFamily = client.deviceFamily, @@ -483,7 +566,16 @@ class GatewaySession( frame["error"]?.asObjectOrNull()?.let { obj -> val code = obj["code"].asStringOrNull() ?: "UNAVAILABLE" val msg = obj["message"].asStringOrNull() ?: "request failed" - ErrorShape(code, msg) + val detailObj = obj["details"].asObjectOrNull() + val details = + detailObj?.let { + GatewayConnectErrorDetails( + code = it["code"].asStringOrNull(), + canRetryWithDeviceToken = it["canRetryWithDeviceToken"].asBooleanOrNull() == true, + recommendedNextStep = it["recommendedNextStep"].asStringOrNull(), + ) + } + ErrorShape(code, msg, details) } pending.remove(id)?.complete(RpcResponse(id, ok, payloadJson, error)) } @@ -607,6 +699,10 @@ class GatewaySession( delay(250) continue } + if (reconnectPausedForAuthFailure) { + delay(250) + continue + } try { onDisconnected(if (attempt == 0) "Connecting…" else "Reconnecting…") @@ -615,6 +711,13 @@ class GatewaySession( } catch (err: Throwable) { attempt += 1 onDisconnected("Gateway error: ${err.message ?: err::class.java.simpleName}") + if ( + err is GatewayConnectFailure && + shouldPauseReconnectAfterAuthFailure(err.gatewayError) + ) { + reconnectPausedForAuthFailure = true + continue + } val sleepMs = minOf(8_000L, (350.0 * Math.pow(1.7, attempt.toDouble())).toLong()) delay(sleepMs) } @@ -622,7 +725,15 @@ class GatewaySession( } private suspend fun connectOnce(target: DesiredConnection) = withContext(Dispatchers.IO) { - val conn = Connection(target.endpoint, target.token, target.password, target.options, target.tls) + val conn = + Connection( + target.endpoint, + target.token, + target.bootstrapToken, + target.password, + target.options, + target.tls, + ) currentConnection = conn try { conn.connect() @@ -698,6 +809,100 @@ class GatewaySession( if (host == "0.0.0.0" || host == "::") return true return host.startsWith("127.") } + + private fun selectConnectAuth( + endpoint: GatewayEndpoint, + tls: GatewayTlsParams?, + role: String, + explicitGatewayToken: String?, + explicitBootstrapToken: String?, + explicitPassword: String?, + storedToken: String?, + ): SelectedConnectAuth { + val shouldUseDeviceRetryToken = + pendingDeviceTokenRetry && + explicitGatewayToken != null && + storedToken != null && + isTrustedDeviceRetryEndpoint(endpoint, tls) + val authToken = + explicitGatewayToken + ?: if ( + explicitPassword == null && + (explicitBootstrapToken == null || storedToken != null) + ) { + storedToken + } else { + null + } + val authDeviceToken = if (shouldUseDeviceRetryToken) storedToken else null + val authBootstrapToken = if (authToken == null) explicitBootstrapToken else null + val authSource = + when { + authDeviceToken != null || (explicitGatewayToken == null && authToken != null) -> + GatewayConnectAuthSource.DEVICE_TOKEN + authToken != null -> GatewayConnectAuthSource.SHARED_TOKEN + authBootstrapToken != null -> GatewayConnectAuthSource.BOOTSTRAP_TOKEN + explicitPassword != null -> GatewayConnectAuthSource.PASSWORD + else -> GatewayConnectAuthSource.NONE + } + return SelectedConnectAuth( + authToken = authToken, + authBootstrapToken = authBootstrapToken, + authDeviceToken = authDeviceToken, + authPassword = explicitPassword, + signatureToken = authToken ?: authBootstrapToken, + authSource = authSource, + attemptedDeviceTokenRetry = shouldUseDeviceRetryToken, + ) + } + + private fun shouldRetryWithStoredDeviceToken( + error: ErrorShape, + explicitGatewayToken: String?, + storedToken: String?, + attemptedDeviceTokenRetry: Boolean, + endpoint: GatewayEndpoint, + tls: GatewayTlsParams?, + ): Boolean { + if (deviceTokenRetryBudgetUsed) return false + if (attemptedDeviceTokenRetry) return false + if (explicitGatewayToken == null || storedToken == null) return false + if (!isTrustedDeviceRetryEndpoint(endpoint, tls)) return false + val detailCode = error.details?.code + val recommendedNextStep = error.details?.recommendedNextStep + return error.details?.canRetryWithDeviceToken == true || + recommendedNextStep == "retry_with_device_token" || + detailCode == "AUTH_TOKEN_MISMATCH" + } + + private fun shouldPauseReconnectAfterAuthFailure(error: ErrorShape): Boolean { + return when (error.details?.code) { + "AUTH_TOKEN_MISSING", + "AUTH_BOOTSTRAP_TOKEN_INVALID", + "AUTH_PASSWORD_MISSING", + "AUTH_PASSWORD_MISMATCH", + "AUTH_RATE_LIMITED", + "PAIRING_REQUIRED", + "CONTROL_UI_DEVICE_IDENTITY_REQUIRED", + "DEVICE_IDENTITY_REQUIRED" -> true + "AUTH_TOKEN_MISMATCH" -> deviceTokenRetryBudgetUsed && !pendingDeviceTokenRetry + else -> false + } + } + + private fun shouldClearStoredDeviceTokenAfterRetry(error: ErrorShape): Boolean { + return error.details?.code == "AUTH_DEVICE_TOKEN_MISMATCH" + } + + private fun isTrustedDeviceRetryEndpoint( + endpoint: GatewayEndpoint, + tls: GatewayTlsParams?, + ): Boolean { + if (isLoopbackHost(endpoint.host)) { + return true + } + return tls?.expectedFingerprint?.trim()?.isNotEmpty() == true + } } private fun JsonElement?.asObjectOrNull(): JsonObject? = this as? JsonObject diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/CallLogHandler.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/CallLogHandler.kt new file mode 100644 index 00000000000..af242dfac69 --- /dev/null +++ b/apps/android/app/src/main/java/ai/openclaw/app/node/CallLogHandler.kt @@ -0,0 +1,247 @@ +package ai.openclaw.app.node + +import android.Manifest +import android.content.Context +import android.provider.CallLog +import androidx.core.content.ContextCompat +import ai.openclaw.app.gateway.GatewaySession +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.put + +private const val DEFAULT_CALL_LOG_LIMIT = 25 + +internal data class CallLogRecord( + val number: String?, + val cachedName: String?, + val date: Long, + val duration: Long, + val type: Int, +) + +internal data class CallLogSearchRequest( + val limit: Int, // Number of records to return + val offset: Int, // Offset value + val cachedName: String?, // Search by contact name + val number: String?, // Search by phone number + val date: Long?, // Search by time (timestamp, deprecated, use dateStart/dateEnd) + val dateStart: Long?, // Query start time (timestamp) + val dateEnd: Long?, // Query end time (timestamp) + val duration: Long?, // Search by duration (seconds) + val type: Int?, // Search by call log type +) + +internal interface CallLogDataSource { + fun hasReadPermission(context: Context): Boolean + + fun search(context: Context, request: CallLogSearchRequest): List +} + +private object SystemCallLogDataSource : CallLogDataSource { + override fun hasReadPermission(context: Context): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_CALL_LOG + ) == android.content.pm.PackageManager.PERMISSION_GRANTED + } + + override fun search(context: Context, request: CallLogSearchRequest): List { + val resolver = context.contentResolver + val projection = arrayOf( + CallLog.Calls.NUMBER, + CallLog.Calls.CACHED_NAME, + CallLog.Calls.DATE, + CallLog.Calls.DURATION, + CallLog.Calls.TYPE, + ) + + // Build selection and selectionArgs for filtering + val selections = mutableListOf() + val selectionArgs = mutableListOf() + + request.cachedName?.let { + selections.add("${CallLog.Calls.CACHED_NAME} LIKE ?") + selectionArgs.add("%$it%") + } + + request.number?.let { + selections.add("${CallLog.Calls.NUMBER} LIKE ?") + selectionArgs.add("%$it%") + } + + // Support time range query + if (request.dateStart != null && request.dateEnd != null) { + selections.add("${CallLog.Calls.DATE} >= ? AND ${CallLog.Calls.DATE} <= ?") + selectionArgs.add(request.dateStart.toString()) + selectionArgs.add(request.dateEnd.toString()) + } else if (request.dateStart != null) { + selections.add("${CallLog.Calls.DATE} >= ?") + selectionArgs.add(request.dateStart.toString()) + } else if (request.dateEnd != null) { + selections.add("${CallLog.Calls.DATE} <= ?") + selectionArgs.add(request.dateEnd.toString()) + } else if (request.date != null) { + // Compatible with the old date parameter (exact match) + selections.add("${CallLog.Calls.DATE} = ?") + selectionArgs.add(request.date.toString()) + } + + request.duration?.let { + selections.add("${CallLog.Calls.DURATION} = ?") + selectionArgs.add(it.toString()) + } + + request.type?.let { + selections.add("${CallLog.Calls.TYPE} = ?") + selectionArgs.add(it.toString()) + } + + val selection = if (selections.isNotEmpty()) selections.joinToString(" AND ") else null + val selectionArgsArray = if (selectionArgs.isNotEmpty()) selectionArgs.toTypedArray() else null + + val sortOrder = "${CallLog.Calls.DATE} DESC" + + resolver.query( + CallLog.Calls.CONTENT_URI, + projection, + selection, + selectionArgsArray, + sortOrder, + ).use { cursor -> + if (cursor == null) return emptyList() + + val numberIndex = cursor.getColumnIndex(CallLog.Calls.NUMBER) + val cachedNameIndex = cursor.getColumnIndex(CallLog.Calls.CACHED_NAME) + val dateIndex = cursor.getColumnIndex(CallLog.Calls.DATE) + val durationIndex = cursor.getColumnIndex(CallLog.Calls.DURATION) + val typeIndex = cursor.getColumnIndex(CallLog.Calls.TYPE) + + // Skip offset rows + if (request.offset > 0 && cursor.moveToPosition(request.offset - 1)) { + // Successfully moved to offset position + } + + val out = mutableListOf() + var count = 0 + while (cursor.moveToNext() && count < request.limit) { + out += CallLogRecord( + number = cursor.getString(numberIndex), + cachedName = cursor.getString(cachedNameIndex), + date = cursor.getLong(dateIndex), + duration = cursor.getLong(durationIndex), + type = cursor.getInt(typeIndex), + ) + count++ + } + return out + } + } +} + +class CallLogHandler private constructor( + private val appContext: Context, + private val dataSource: CallLogDataSource, +) { + constructor(appContext: Context) : this(appContext = appContext, dataSource = SystemCallLogDataSource) + + fun handleCallLogSearch(paramsJson: String?): GatewaySession.InvokeResult { + if (!dataSource.hasReadPermission(appContext)) { + return GatewaySession.InvokeResult.error( + code = "CALL_LOG_PERMISSION_REQUIRED", + message = "CALL_LOG_PERMISSION_REQUIRED: grant Call Log permission", + ) + } + + val request = parseSearchRequest(paramsJson) + ?: return GatewaySession.InvokeResult.error( + code = "INVALID_REQUEST", + message = "INVALID_REQUEST: expected JSON object", + ) + + return try { + val callLogs = dataSource.search(appContext, request) + GatewaySession.InvokeResult.ok( + buildJsonObject { + put( + "callLogs", + buildJsonArray { + callLogs.forEach { add(callLogJson(it)) } + }, + ) + }.toString(), + ) + } catch (err: Throwable) { + GatewaySession.InvokeResult.error( + code = "CALL_LOG_UNAVAILABLE", + message = "CALL_LOG_UNAVAILABLE: ${err.message ?: "call log query failed"}", + ) + } + } + + private fun parseSearchRequest(paramsJson: String?): CallLogSearchRequest? { + if (paramsJson.isNullOrBlank()) { + return CallLogSearchRequest( + limit = DEFAULT_CALL_LOG_LIMIT, + offset = 0, + cachedName = null, + number = null, + date = null, + dateStart = null, + dateEnd = null, + duration = null, + type = null, + ) + } + + val params = try { + Json.parseToJsonElement(paramsJson).asObjectOrNull() + } catch (_: Throwable) { + null + } ?: return null + + val limit = ((params["limit"] as? JsonPrimitive)?.content?.toIntOrNull() ?: DEFAULT_CALL_LOG_LIMIT) + .coerceIn(1, 200) + val offset = ((params["offset"] as? JsonPrimitive)?.content?.toIntOrNull() ?: 0) + .coerceAtLeast(0) + val cachedName = (params["cachedName"] as? JsonPrimitive)?.content?.takeIf { it.isNotBlank() } + val number = (params["number"] as? JsonPrimitive)?.content?.takeIf { it.isNotBlank() } + val date = (params["date"] as? JsonPrimitive)?.content?.toLongOrNull() + val dateStart = (params["dateStart"] as? JsonPrimitive)?.content?.toLongOrNull() + val dateEnd = (params["dateEnd"] as? JsonPrimitive)?.content?.toLongOrNull() + val duration = (params["duration"] as? JsonPrimitive)?.content?.toLongOrNull() + val type = (params["type"] as? JsonPrimitive)?.content?.toIntOrNull() + + return CallLogSearchRequest( + limit = limit, + offset = offset, + cachedName = cachedName, + number = number, + date = date, + dateStart = dateStart, + dateEnd = dateEnd, + duration = duration, + type = type, + ) + } + + private fun callLogJson(callLog: CallLogRecord): JsonObject { + return buildJsonObject { + put("number", JsonPrimitive(callLog.number)) + put("cachedName", JsonPrimitive(callLog.cachedName)) + put("date", JsonPrimitive(callLog.date)) + put("duration", JsonPrimitive(callLog.duration)) + put("type", JsonPrimitive(callLog.type)) + } + } + + companion object { + internal fun forTesting( + appContext: Context, + dataSource: CallLogDataSource, + ): CallLogHandler = CallLogHandler(appContext = appContext, dataSource = dataSource) + } +} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/CanvasController.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/CanvasController.kt index 9efb2a924d7..0eab9d75a5b 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/node/CanvasController.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/node/CanvasController.kt @@ -34,6 +34,7 @@ class CanvasController { @Volatile private var debugStatusEnabled: Boolean = false @Volatile private var debugStatusTitle: String? = null @Volatile private var debugStatusSubtitle: String? = null + @Volatile private var homeCanvasStateJson: String? = null private val _currentUrl = MutableStateFlow(null) val currentUrl: StateFlow = _currentUrl.asStateFlow() @@ -56,6 +57,7 @@ class CanvasController { this.webView = webView reload() applyDebugStatus() + applyHomeCanvasState() } fun detach(webView: WebView) { @@ -88,6 +90,12 @@ class CanvasController { fun onPageFinished() { applyDebugStatus() + applyHomeCanvasState() + } + + fun updateHomeCanvasState(json: String?) { + homeCanvasStateJson = json + applyHomeCanvasState() } private inline fun withWebViewOnMain(crossinline block: (WebView) -> Unit) { @@ -142,6 +150,22 @@ class CanvasController { } } + private fun applyHomeCanvasState() { + val payload = homeCanvasStateJson ?: "null" + withWebViewOnMain { wv -> + val js = """ + (() => { + try { + const api = globalThis.__openclaw; + if (!api || typeof api.renderHome !== 'function') return; + api.renderHome($payload); + } catch (_) {} + })(); + """.trimIndent() + wv.evaluateJavascript(js, null) + } + } + suspend fun eval(javaScript: String): String = withContext(Dispatchers.Main) { val wv = webView ?: throw IllegalStateException("no webview") diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/ConnectionManager.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/ConnectionManager.kt index d1593f4829a..ce9c9d77bfc 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/node/ConnectionManager.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/node/ConnectionManager.kt @@ -17,7 +17,8 @@ class ConnectionManager( private val voiceWakeMode: () -> VoiceWakeMode, private val motionActivityAvailable: () -> Boolean, private val motionPedometerAvailable: () -> Boolean, - private val smsAvailable: () -> Boolean, + private val sendSmsAvailable: () -> Boolean, + private val readSmsAvailable: () -> Boolean, private val hasRecordAudioPermission: () -> Boolean, private val manualTls: () -> Boolean, ) { @@ -78,7 +79,8 @@ class ConnectionManager( NodeRuntimeFlags( cameraEnabled = cameraEnabled(), locationEnabled = locationMode() != LocationMode.Off, - smsAvailable = smsAvailable(), + sendSmsAvailable = sendSmsAvailable(), + readSmsAvailable = readSmsAvailable(), voiceWakeEnabled = voiceWakeMode() != VoiceWakeMode.Off && hasRecordAudioPermission(), motionActivityAvailable = motionActivityAvailable(), motionPedometerAvailable = motionPedometerAvailable(), diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/DeviceHandler.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/DeviceHandler.kt index de3b24df193..b888e3edaea 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/node/DeviceHandler.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/node/DeviceHandler.kt @@ -212,6 +212,13 @@ class DeviceHandler( promptableWhenDenied = true, ), ) + put( + "callLog", + permissionStateJson( + granted = hasPermission(Manifest.permission.READ_CALL_LOG), + promptableWhenDenied = true, + ), + ) put( "motion", permissionStateJson( diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/InvokeCommandRegistry.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/InvokeCommandRegistry.kt index 5ce86340965..3e903098196 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/node/InvokeCommandRegistry.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/node/InvokeCommandRegistry.kt @@ -5,6 +5,7 @@ import ai.openclaw.app.protocol.OpenClawCanvasA2UICommand import ai.openclaw.app.protocol.OpenClawCanvasCommand import ai.openclaw.app.protocol.OpenClawCameraCommand import ai.openclaw.app.protocol.OpenClawCapability +import ai.openclaw.app.protocol.OpenClawCallLogCommand import ai.openclaw.app.protocol.OpenClawContactsCommand import ai.openclaw.app.protocol.OpenClawDeviceCommand import ai.openclaw.app.protocol.OpenClawLocationCommand @@ -17,7 +18,8 @@ import ai.openclaw.app.protocol.OpenClawSystemCommand data class NodeRuntimeFlags( val cameraEnabled: Boolean, val locationEnabled: Boolean, - val smsAvailable: Boolean, + val sendSmsAvailable: Boolean, + val readSmsAvailable: Boolean, val voiceWakeEnabled: Boolean, val motionActivityAvailable: Boolean, val motionPedometerAvailable: Boolean, @@ -28,7 +30,8 @@ enum class InvokeCommandAvailability { Always, CameraEnabled, LocationEnabled, - SmsAvailable, + SendSmsAvailable, + ReadSmsAvailable, MotionActivityAvailable, MotionPedometerAvailable, DebugBuild, @@ -84,6 +87,7 @@ object InvokeCommandRegistry { name = OpenClawCapability.Motion.rawValue, availability = NodeCapabilityAvailability.MotionAvailable, ), + NodeCapabilitySpec(name = OpenClawCapability.CallLog.rawValue), ) val all: List = @@ -185,7 +189,14 @@ object InvokeCommandRegistry { ), InvokeCommandSpec( name = OpenClawSmsCommand.Send.rawValue, - availability = InvokeCommandAvailability.SmsAvailable, + availability = InvokeCommandAvailability.SendSmsAvailable, + ), + InvokeCommandSpec( + name = OpenClawSmsCommand.Search.rawValue, + availability = InvokeCommandAvailability.ReadSmsAvailable, + ), + InvokeCommandSpec( + name = OpenClawCallLogCommand.Search.rawValue, ), InvokeCommandSpec( name = "debug.logs", @@ -208,7 +219,7 @@ object InvokeCommandRegistry { NodeCapabilityAvailability.Always -> true NodeCapabilityAvailability.CameraEnabled -> flags.cameraEnabled NodeCapabilityAvailability.LocationEnabled -> flags.locationEnabled - NodeCapabilityAvailability.SmsAvailable -> flags.smsAvailable + NodeCapabilityAvailability.SmsAvailable -> flags.sendSmsAvailable || flags.readSmsAvailable NodeCapabilityAvailability.VoiceWakeEnabled -> flags.voiceWakeEnabled NodeCapabilityAvailability.MotionAvailable -> flags.motionActivityAvailable || flags.motionPedometerAvailable } @@ -223,7 +234,8 @@ object InvokeCommandRegistry { InvokeCommandAvailability.Always -> true InvokeCommandAvailability.CameraEnabled -> flags.cameraEnabled InvokeCommandAvailability.LocationEnabled -> flags.locationEnabled - InvokeCommandAvailability.SmsAvailable -> flags.smsAvailable + InvokeCommandAvailability.SendSmsAvailable -> flags.sendSmsAvailable + InvokeCommandAvailability.ReadSmsAvailable -> flags.readSmsAvailable InvokeCommandAvailability.MotionActivityAvailable -> flags.motionActivityAvailable InvokeCommandAvailability.MotionPedometerAvailable -> flags.motionPedometerAvailable InvokeCommandAvailability.DebugBuild -> flags.debugBuild diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/InvokeDispatcher.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/InvokeDispatcher.kt index f2b79159009..2ed0773bc43 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/node/InvokeDispatcher.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/node/InvokeDispatcher.kt @@ -5,6 +5,7 @@ import ai.openclaw.app.protocol.OpenClawCalendarCommand import ai.openclaw.app.protocol.OpenClawCanvasA2UICommand import ai.openclaw.app.protocol.OpenClawCanvasCommand import ai.openclaw.app.protocol.OpenClawCameraCommand +import ai.openclaw.app.protocol.OpenClawCallLogCommand import ai.openclaw.app.protocol.OpenClawContactsCommand import ai.openclaw.app.protocol.OpenClawDeviceCommand import ai.openclaw.app.protocol.OpenClawLocationCommand @@ -27,10 +28,12 @@ class InvokeDispatcher( private val smsHandler: SmsHandler, private val a2uiHandler: A2UIHandler, private val debugHandler: DebugHandler, + private val callLogHandler: CallLogHandler, private val isForeground: () -> Boolean, private val cameraEnabled: () -> Boolean, private val locationEnabled: () -> Boolean, - private val smsAvailable: () -> Boolean, + private val sendSmsAvailable: () -> Boolean, + private val readSmsAvailable: () -> Boolean, private val debugBuild: () -> Boolean, private val refreshNodeCanvasCapability: suspend () -> Boolean, private val onCanvasA2uiPush: () -> Unit, @@ -160,6 +163,10 @@ class InvokeDispatcher( // SMS command OpenClawSmsCommand.Send.rawValue -> smsHandler.handleSmsSend(paramsJson) + OpenClawSmsCommand.Search.rawValue -> smsHandler.handleSmsSearch(paramsJson) + + // CallLog command + OpenClawCallLogCommand.Search.rawValue -> callLogHandler.handleCallLogSearch(paramsJson) // Debug commands "debug.ed25519" -> debugHandler.handleEd25519() @@ -251,8 +258,17 @@ class InvokeDispatcher( message = "PEDOMETER_UNAVAILABLE: step counter not available", ) } - InvokeCommandAvailability.SmsAvailable -> - if (smsAvailable()) { + InvokeCommandAvailability.SendSmsAvailable -> + if (sendSmsAvailable()) { + null + } else { + GatewaySession.InvokeResult.error( + code = "SMS_UNAVAILABLE", + message = "SMS_UNAVAILABLE: SMS not available on this device", + ) + } + InvokeCommandAvailability.ReadSmsAvailable -> + if (readSmsAvailable()) { null } else { GatewaySession.InvokeResult.error( diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/SmsHandler.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/SmsHandler.kt index 0c76ac24587..f2885e23d73 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/node/SmsHandler.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/node/SmsHandler.kt @@ -16,4 +16,16 @@ class SmsHandler( return GatewaySession.InvokeResult.error(code = code, message = error) } } + + suspend fun handleSmsSearch(paramsJson: String?): GatewaySession.InvokeResult { + val res = sms.search(paramsJson) + if (res.ok) { + return GatewaySession.InvokeResult.ok(res.payloadJson) + } else { + val error = res.error ?: "SMS_SEARCH_FAILED" + val idx = error.indexOf(':') + val code = if (idx > 0) error.substring(0, idx).trim() else "SMS_SEARCH_FAILED" + return GatewaySession.InvokeResult.error(code = code, message = error) + } + } } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/SmsManager.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/SmsManager.kt index 3c5184b0247..0256125b354 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/node/SmsManager.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/node/SmsManager.kt @@ -3,19 +3,27 @@ package ai.openclaw.app.node import android.Manifest import android.content.Context import android.content.pm.PackageManager +import android.database.Cursor +import android.net.Uri +import android.provider.ContactsContract +import android.provider.Telephony import android.telephony.SmsManager as AndroidSmsManager import androidx.core.content.ContextCompat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.encodeToString +import kotlinx.serialization.Serializable import ai.openclaw.app.PermissionRequester /** * Sends SMS messages via the Android SMS API. * Requires SEND_SMS permission to be granted. + * + * Also provides SMS query functionality with READ_SMS permission. */ class SmsManager(private val context: Context) { @@ -30,6 +38,30 @@ class SmsManager(private val context: Context) { val payloadJson: String, ) + /** + * Represents a single SMS message + */ + @Serializable + data class SmsMessage( + val id: Long, + val threadId: Long, + val address: String?, + val person: String?, + val date: Long, + val dateSent: Long, + val read: Boolean, + val type: Int, + val body: String?, + val status: Int, + ) + + data class SearchResult( + val ok: Boolean, + val messages: List, + val error: String? = null, + val payloadJson: String, + ) + internal data class ParsedParams( val to: String, val message: String, @@ -44,12 +76,30 @@ class SmsManager(private val context: Context) { ) : ParseResult() } + internal data class QueryParams( + val startTime: Long? = null, + val endTime: Long? = null, + val contactName: String? = null, + val phoneNumber: String? = null, + val keyword: String? = null, + val type: Int? = null, + val isRead: Boolean? = null, + val limit: Int = DEFAULT_SMS_LIMIT, + val offset: Int = 0, + ) + + internal sealed class QueryParseResult { + data class Ok(val params: QueryParams) : QueryParseResult() + data class Error(val error: String) : QueryParseResult() + } + internal data class SendPlan( val parts: List, val useMultipart: Boolean, ) companion object { + private const val DEFAULT_SMS_LIMIT = 25 internal val JsonConfig = Json { ignoreUnknownKeys = true } internal fun parseParams(paramsJson: String?, json: Json = JsonConfig): ParseResult { @@ -88,6 +138,52 @@ class SmsManager(private val context: Context) { return ParseResult.Ok(ParsedParams(to = to, message = message)) } + internal fun parseQueryParams(paramsJson: String?, json: Json = JsonConfig): QueryParseResult { + val params = paramsJson?.trim().orEmpty() + if (params.isEmpty()) { + return QueryParseResult.Ok(QueryParams()) + } + + val obj = try { + json.parseToJsonElement(params).jsonObject + } catch (_: Throwable) { + return QueryParseResult.Error("INVALID_REQUEST: expected JSON object") + } + + val startTime = (obj["startTime"] as? JsonPrimitive)?.content?.toLongOrNull() + val endTime = (obj["endTime"] as? JsonPrimitive)?.content?.toLongOrNull() + val contactName = (obj["contactName"] as? JsonPrimitive)?.content?.trim() + val phoneNumber = (obj["phoneNumber"] as? JsonPrimitive)?.content?.trim() + val keyword = (obj["keyword"] as? JsonPrimitive)?.content?.trim() + val type = (obj["type"] as? JsonPrimitive)?.content?.toIntOrNull() + val isRead = (obj["isRead"] as? JsonPrimitive)?.content?.toBooleanStrictOrNull() + val limit = ((obj["limit"] as? JsonPrimitive)?.content?.toIntOrNull() ?: DEFAULT_SMS_LIMIT) + .coerceIn(1, 200) + val offset = ((obj["offset"] as? JsonPrimitive)?.content?.toIntOrNull() ?: 0) + .coerceAtLeast(0) + + // Validate time range + if (startTime != null && endTime != null && startTime > endTime) { + return QueryParseResult.Error("INVALID_REQUEST: startTime must be less than or equal to endTime") + } + + return QueryParseResult.Ok(QueryParams( + startTime = startTime, + endTime = endTime, + contactName = contactName, + phoneNumber = phoneNumber, + keyword = keyword, + type = type, + isRead = isRead, + limit = limit, + offset = offset, + )) + } + + private fun normalizePhoneNumber(phone: String): String { + return phone.replace(Regex("""[\s\-()]"""), "") + } + internal fun buildSendPlan( message: String, divider: (String) -> List, @@ -112,6 +208,25 @@ class SmsManager(private val context: Context) { } return json.encodeToString(JsonObject.serializer(), JsonObject(payload)) } + + internal fun buildQueryPayloadJson( + json: Json = JsonConfig, + ok: Boolean, + messages: List, + error: String? = null, + ): String { + val messagesArray = json.encodeToString(messages) + val messagesElement = json.parseToJsonElement(messagesArray) + val payload = mutableMapOf( + "ok" to JsonPrimitive(ok), + "count" to JsonPrimitive(messages.size), + "messages" to messagesElement + ) + if (!ok && error != null) { + payload["error"] = JsonPrimitive(error) + } + return json.encodeToString(JsonObject.serializer(), JsonObject(payload)) + } } fun hasSmsPermission(): Boolean { @@ -121,10 +236,28 @@ class SmsManager(private val context: Context) { ) == PackageManager.PERMISSION_GRANTED } + fun hasReadSmsPermission(): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_SMS + ) == PackageManager.PERMISSION_GRANTED + } + + fun hasReadContactsPermission(): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_CONTACTS + ) == PackageManager.PERMISSION_GRANTED + } + fun canSendSms(): Boolean { return hasSmsPermission() && hasTelephonyFeature() } + fun canReadSms(): Boolean { + return hasReadSmsPermission() && hasTelephonyFeature() + } + fun hasTelephonyFeature(): Boolean { return context.packageManager?.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) == true } @@ -208,6 +341,20 @@ class SmsManager(private val context: Context) { return results[Manifest.permission.SEND_SMS] == true } + private suspend fun ensureReadSmsPermission(): Boolean { + if (hasReadSmsPermission()) return true + val requester = permissionRequester ?: return false + val results = requester.requestIfMissing(listOf(Manifest.permission.READ_SMS)) + return results[Manifest.permission.READ_SMS] == true + } + + private suspend fun ensureReadContactsPermission(): Boolean { + if (hasReadContactsPermission()) return true + val requester = permissionRequester ?: return false + val results = requester.requestIfMissing(listOf(Manifest.permission.READ_CONTACTS)) + return results[Manifest.permission.READ_CONTACTS] == true + } + private fun okResult(to: String, message: String): SendResult { return SendResult( ok = true, @@ -227,4 +374,240 @@ class SmsManager(private val context: Context) { payloadJson = buildPayloadJson(json = json, ok = false, to = to, error = error), ) } + + /** + * search SMS messages with the specified parameters. + * + * @param paramsJson JSON with optional fields: + * - startTime (Long): Start time in milliseconds + * - endTime (Long): End time in milliseconds + * - contactName (String): Contact name to search + * - phoneNumber (String): Phone number to search (supports partial matching) + * - keyword (String): Keyword to search in message body + * - type (Int): SMS type (1=Inbox, 2=Sent, 3=Draft, etc.) + * - isRead (Boolean): Read status + * - limit (Int): Number of records to return (default: 25, range: 1-200) + * - offset (Int): Number of records to skip (default: 0) + * @return SearchResult containing the list of SMS messages or an error + */ + suspend fun search(paramsJson: String?): SearchResult = withContext(Dispatchers.IO) { + if (!hasTelephonyFeature()) { + return@withContext SearchResult( + ok = false, + messages = emptyList(), + error = "SMS_UNAVAILABLE: telephony not available", + payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = "SMS_UNAVAILABLE: telephony not available") + ) + } + + if (!ensureReadSmsPermission()) { + return@withContext SearchResult( + ok = false, + messages = emptyList(), + error = "SMS_PERMISSION_REQUIRED: grant READ_SMS permission", + payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = "SMS_PERMISSION_REQUIRED: grant READ_SMS permission") + ) + } + + val parseResult = parseQueryParams(paramsJson, json) + if (parseResult is QueryParseResult.Error) { + return@withContext SearchResult( + ok = false, + messages = emptyList(), + error = parseResult.error, + payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = parseResult.error) + ) + } + val params = (parseResult as QueryParseResult.Ok).params + + return@withContext try { + // Get phone numbers from contact name if provided + val phoneNumbers = if (!params.contactName.isNullOrEmpty()) { + if (!ensureReadContactsPermission()) { + return@withContext SearchResult( + ok = false, + messages = emptyList(), + error = "CONTACTS_PERMISSION_REQUIRED: grant READ_CONTACTS permission", + payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = "CONTACTS_PERMISSION_REQUIRED: grant READ_CONTACTS permission") + ) + } + getPhoneNumbersFromContactName(params.contactName) + } else { + emptyList() + } + + val messages = querySmsMessages(params, phoneNumbers) + SearchResult( + ok = true, + messages = messages, + error = null, + payloadJson = buildQueryPayloadJson(json, ok = true, messages = messages) + ) + } catch (e: SecurityException) { + SearchResult( + ok = false, + messages = emptyList(), + error = "SMS_PERMISSION_REQUIRED: ${e.message}", + payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = "SMS_PERMISSION_REQUIRED: ${e.message}") + ) + } catch (e: Throwable) { + SearchResult( + ok = false, + messages = emptyList(), + error = "SMS_QUERY_FAILED: ${e.message ?: "unknown error"}", + payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = "SMS_QUERY_FAILED: ${e.message ?: "unknown error"}") + ) + } + } + + /** + * Get all phone numbers associated with a contact name + */ + private fun getPhoneNumbersFromContactName(contactName: String): List { + val phoneNumbers = mutableListOf() + val selection = "${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} LIKE ?" + val selectionArgs = arrayOf("%$contactName%") + + val cursor = context.contentResolver.query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER), + selection, + selectionArgs, + null + ) + + cursor?.use { + val numberIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) + while (it.moveToNext()) { + val number = it.getString(numberIndex) + if (!number.isNullOrBlank()) { + phoneNumbers.add(normalizePhoneNumber(number)) + } + } + } + + return phoneNumbers + } + + /** + * Query SMS messages based on the provided parameters + */ + private fun querySmsMessages(params: QueryParams, phoneNumbers: List): List { + val messages = mutableListOf() + + // Build selection and selectionArgs + val selections = mutableListOf() + val selectionArgs = mutableListOf() + + // Time range + if (params.startTime != null) { + selections.add("${Telephony.Sms.DATE} >= ?") + selectionArgs.add(params.startTime.toString()) + } + if (params.endTime != null) { + selections.add("${Telephony.Sms.DATE} <= ?") + selectionArgs.add(params.endTime.toString()) + } + + // Phone numbers (from contact name or direct phone number) + val allPhoneNumbers = if (!params.phoneNumber.isNullOrEmpty()) { + phoneNumbers + normalizePhoneNumber(params.phoneNumber) + } else { + phoneNumbers + } + + if (allPhoneNumbers.isNotEmpty()) { + val addressSelection = allPhoneNumbers.joinToString(" OR ") { + "${Telephony.Sms.ADDRESS} LIKE ?" + } + selections.add("($addressSelection)") + allPhoneNumbers.forEach { + selectionArgs.add("%$it%") + } + } + + // Keyword in body + if (!params.keyword.isNullOrEmpty()) { + selections.add("${Telephony.Sms.BODY} LIKE ?") + selectionArgs.add("%${params.keyword}%") + } + + // Type + if (params.type != null) { + selections.add("${Telephony.Sms.TYPE} = ?") + selectionArgs.add(params.type.toString()) + } + + // Read status + if (params.isRead != null) { + selections.add("${Telephony.Sms.READ} = ?") + selectionArgs.add(if (params.isRead) "1" else "0") + } + + val selection = if (selections.isNotEmpty()) { + selections.joinToString(" AND ") + } else { + null + } + + val selectionArgsArray = if (selectionArgs.isNotEmpty()) { + selectionArgs.toTypedArray() + } else { + null + } + + // Query SMS with SQL-level LIMIT and OFFSET to avoid loading all matching rows + val sortOrder = "${Telephony.Sms.DATE} DESC LIMIT ${params.limit} OFFSET ${params.offset}" + val cursor = context.contentResolver.query( + Telephony.Sms.CONTENT_URI, + arrayOf( + Telephony.Sms._ID, + Telephony.Sms.THREAD_ID, + Telephony.Sms.ADDRESS, + Telephony.Sms.PERSON, + Telephony.Sms.DATE, + Telephony.Sms.DATE_SENT, + Telephony.Sms.READ, + Telephony.Sms.TYPE, + Telephony.Sms.BODY, + Telephony.Sms.STATUS + ), + selection, + selectionArgsArray, + sortOrder + ) + + cursor?.use { + val idIndex = it.getColumnIndex(Telephony.Sms._ID) + val threadIdIndex = it.getColumnIndex(Telephony.Sms.THREAD_ID) + val addressIndex = it.getColumnIndex(Telephony.Sms.ADDRESS) + val personIndex = it.getColumnIndex(Telephony.Sms.PERSON) + val dateIndex = it.getColumnIndex(Telephony.Sms.DATE) + val dateSentIndex = it.getColumnIndex(Telephony.Sms.DATE_SENT) + val readIndex = it.getColumnIndex(Telephony.Sms.READ) + val typeIndex = it.getColumnIndex(Telephony.Sms.TYPE) + val bodyIndex = it.getColumnIndex(Telephony.Sms.BODY) + val statusIndex = it.getColumnIndex(Telephony.Sms.STATUS) + + var count = 0 + while (it.moveToNext() && count < params.limit) { + val message = SmsMessage( + id = it.getLong(idIndex), + threadId = it.getLong(threadIdIndex), + address = it.getString(addressIndex), + person = it.getString(personIndex), + date = it.getLong(dateIndex), + dateSent = it.getLong(dateSentIndex), + read = it.getInt(readIndex) == 1, + type = it.getInt(typeIndex), + body = it.getString(bodyIndex), + status = it.getInt(statusIndex) + ) + messages.add(message) + count++ + } + } + + return messages + } } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/protocol/OpenClawProtocolConstants.kt b/apps/android/app/src/main/java/ai/openclaw/app/protocol/OpenClawProtocolConstants.kt index 95ba2912b09..ceed86f767b 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/protocol/OpenClawProtocolConstants.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/protocol/OpenClawProtocolConstants.kt @@ -13,6 +13,7 @@ enum class OpenClawCapability(val rawValue: String) { Contacts("contacts"), Calendar("calendar"), Motion("motion"), + CallLog("callLog"), } enum class OpenClawCanvasCommand(val rawValue: String) { @@ -52,6 +53,7 @@ enum class OpenClawCameraCommand(val rawValue: String) { enum class OpenClawSmsCommand(val rawValue: String) { Send("sms.send"), + Search("sms.search"), ; companion object { @@ -137,3 +139,12 @@ enum class OpenClawMotionCommand(val rawValue: String) { const val NamespacePrefix: String = "motion." } } + +enum class OpenClawCallLogCommand(val rawValue: String) { + Search("callLog.search"), + ; + + companion object { + const val NamespacePrefix: String = "callLog." + } +} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt index 4b8ac2c8e5d..603902b1907 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt @@ -1,13 +1,14 @@ package ai.openclaw.app.ui -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.BorderStroke +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -18,8 +19,12 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Cloud +import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material.icons.filled.Link +import androidx.compose.material.icons.filled.PowerSettingsNew import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -45,8 +50,10 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight 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.ui.mobileCardSurface private enum class ConnectInputMode { SetupCode, @@ -55,6 +62,7 @@ private enum class ConnectInputMode { @Composable fun ConnectTabScreen(viewModel: MainViewModel) { + val context = LocalContext.current val statusText by viewModel.statusText.collectAsState() val isConnected by viewModel.isConnected.collectAsState() val remoteAddress by viewModel.remoteAddress.collectAsState() @@ -87,20 +95,28 @@ fun ConnectTabScreen(viewModel: MainViewModel) { val prompt = pendingTrust!! AlertDialog( onDismissRequest = { viewModel.declineGatewayTrustPrompt() }, - title = { Text("Trust this gateway?") }, + containerColor = mobileCardSurface, + title = { Text("Trust this gateway?", style = mobileHeadline, color = mobileText) }, text = { Text( "First-time TLS connection.\n\nVerify this SHA-256 fingerprint before trusting:\n${prompt.fingerprintSha256}", style = mobileCallout, + color = mobileText, ) }, confirmButton = { - TextButton(onClick = { viewModel.acceptGatewayTrustPrompt() }) { + TextButton( + onClick = { viewModel.acceptGatewayTrustPrompt() }, + colors = ButtonDefaults.textButtonColors(contentColor = mobileAccent), + ) { Text("Trust and continue") } }, dismissButton = { - TextButton(onClick = { viewModel.declineGatewayTrustPrompt() }) { + TextButton( + onClick = { viewModel.declineGatewayTrustPrompt() }, + colors = ButtonDefaults.textButtonColors(contentColor = mobileTextSecondary), + ) { Text("Cancel") } }, @@ -121,100 +137,190 @@ fun ConnectTabScreen(viewModel: MainViewModel) { } } - val primaryLabel = if (isConnected) "Disconnect Gateway" else "Connect Gateway" + val showDiagnostics = !isConnected && gatewayStatusHasDiagnostics(statusText) + val statusLabel = gatewayStatusForDisplay(statusText) Column( modifier = Modifier.verticalScroll(rememberScrollState()).padding(horizontal = 20.dp, vertical = 16.dp), verticalArrangement = Arrangement.spacedBy(14.dp), ) { Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { - Text("Connection Control", style = mobileCaption1.copy(fontWeight = FontWeight.Bold), color = mobileAccent) Text("Gateway Connection", style = mobileTitle1, color = mobileText) Text( - "One primary action. Open advanced controls only when needed.", + if (isConnected) "Your gateway is active and ready." else "Connect to your gateway to get started.", style = mobileCallout, color = mobileTextSecondary, ) } + // Status cards in a unified card group Surface( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(14.dp), - color = mobileSurface, + color = mobileCardSurface, border = BorderStroke(1.dp, mobileBorder), ) { - Column(modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { - Text("Active endpoint", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) - Text(activeEndpoint, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText) + Column { + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Surface( + shape = RoundedCornerShape(10.dp), + color = mobileAccentSoft, + ) { + Icon( + imageVector = Icons.Default.Link, + contentDescription = null, + modifier = Modifier.padding(8.dp).size(18.dp), + tint = mobileAccent, + ) + } + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text("Endpoint", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) + Text(activeEndpoint, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText) + } + } + HorizontalDivider(color = mobileBorder) + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Surface( + shape = RoundedCornerShape(10.dp), + color = if (isConnected) mobileSuccessSoft else mobileSurface, + ) { + Icon( + imageVector = Icons.Default.Cloud, + contentDescription = null, + modifier = Modifier.padding(8.dp).size(18.dp), + tint = if (isConnected) mobileSuccess else mobileTextTertiary, + ) + } + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text("Status", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) + Text(statusText, style = mobileBody, color = if (isConnected) mobileSuccess else mobileText) + } + } } } - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(14.dp), - color = mobileSurface, - border = BorderStroke(1.dp, mobileBorder), - ) { - Column(modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { - Text("Gateway state", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) - Text(statusText, style = mobileBody, color = mobileText) - } - } - - Button( - onClick = { - if (isConnected) { + if (isConnected) { + // Outlined secondary button when connected — don't scream "danger" + Button( + onClick = { viewModel.disconnect() validationText = null - return@Button - } - if (statusText.contains("operator offline", ignoreCase = true)) { + }, + modifier = Modifier.fillMaxWidth().height(48.dp), + shape = RoundedCornerShape(14.dp), + colors = + ButtonDefaults.buttonColors( + containerColor = mobileCardSurface, + contentColor = mobileDanger, + ), + border = BorderStroke(1.dp, mobileDanger.copy(alpha = 0.4f)), + ) { + Icon(Icons.Default.PowerSettingsNew, contentDescription = null, modifier = Modifier.size(18.dp)) + Spacer(modifier = Modifier.width(8.dp)) + Text("Disconnect", style = mobileHeadline.copy(fontWeight = FontWeight.SemiBold)) + } + } else { + Button( + onClick = { + if (statusText.contains("operator offline", ignoreCase = true)) { + validationText = null + viewModel.refreshGatewayConnection() + return@Button + } + + val config = + resolveGatewayConnectConfig( + useSetupCode = inputMode == ConnectInputMode.SetupCode, + setupCode = setupCode, + manualHost = manualHostInput, + manualPort = manualPortInput, + manualTls = manualTlsInput, + fallbackToken = gatewayToken, + fallbackPassword = passwordInput, + ) + + if (config == null) { + validationText = + if (inputMode == ConnectInputMode.SetupCode) { + "Paste a valid setup code to connect." + } else { + "Enter a valid manual host and port to connect." + } + return@Button + } + validationText = null - viewModel.refreshGatewayConnection() - return@Button - } + viewModel.setManualEnabled(true) + viewModel.setManualHost(config.host) + viewModel.setManualPort(config.port) + viewModel.setManualTls(config.tls) + viewModel.setGatewayBootstrapToken(config.bootstrapToken) + if (config.token.isNotBlank()) { + viewModel.setGatewayToken(config.token) + } else if (config.bootstrapToken.isNotBlank()) { + viewModel.setGatewayToken("") + } + viewModel.setGatewayPassword(config.password) + viewModel.connectManual() + }, + modifier = Modifier.fillMaxWidth().height(52.dp), + shape = RoundedCornerShape(14.dp), + colors = + ButtonDefaults.buttonColors( + containerColor = mobileAccent, + contentColor = Color.White, + ), + ) { + Text("Connect Gateway", style = mobileHeadline.copy(fontWeight = FontWeight.Bold)) + } + } - val config = - resolveGatewayConnectConfig( - useSetupCode = inputMode == ConnectInputMode.SetupCode, - setupCode = setupCode, - manualHost = manualHostInput, - manualPort = manualPortInput, - manualTls = manualTlsInput, - fallbackToken = gatewayToken, - fallbackPassword = passwordInput, - ) - - if (config == null) { - validationText = - if (inputMode == ConnectInputMode.SetupCode) { - "Paste a valid setup code to connect." - } else { - "Enter a valid manual host and port to connect." - } - return@Button + if (showDiagnostics) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(14.dp), + color = mobileWarningSoft, + border = BorderStroke(1.dp, mobileWarning.copy(alpha = 0.25f)), + ) { + Column( + modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 14.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Text("Last gateway error", style = mobileHeadline, color = mobileWarning) + Text(statusLabel, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText) + Text("OpenClaw Android ${openClawAndroidVersionLabel()}", style = mobileCaption1, color = mobileTextSecondary) + Button( + onClick = { + copyGatewayDiagnosticsReport( + context = context, + screen = "connect tab", + gatewayAddress = activeEndpoint, + statusText = statusLabel, + ) + }, + modifier = Modifier.fillMaxWidth().height(46.dp), + shape = RoundedCornerShape(12.dp), + colors = + ButtonDefaults.buttonColors( + containerColor = mobileCardSurface, + contentColor = mobileWarning, + ), + border = BorderStroke(1.dp, mobileWarning.copy(alpha = 0.3f)), + ) { + Icon(Icons.Default.ContentCopy, contentDescription = null, modifier = Modifier.size(18.dp)) + Spacer(modifier = Modifier.width(8.dp)) + Text("Copy Report for Claw", style = mobileCallout.copy(fontWeight = FontWeight.Bold)) + } } - - validationText = null - viewModel.setManualEnabled(true) - viewModel.setManualHost(config.host) - viewModel.setManualPort(config.port) - viewModel.setManualTls(config.tls) - if (config.token.isNotBlank()) { - viewModel.setGatewayToken(config.token) - } - viewModel.setGatewayPassword(config.password) - viewModel.connectManual() - }, - modifier = Modifier.fillMaxWidth().height(52.dp), - shape = RoundedCornerShape(14.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = if (isConnected) mobileDanger else mobileAccent, - contentColor = Color.White, - ), - ) { - Text(primaryLabel, style = mobileHeadline.copy(fontWeight = FontWeight.Bold)) + } } Surface( @@ -245,7 +351,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) { Surface( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(14.dp), - color = Color.White, + color = mobileCardSurface, border = BorderStroke(1.dp, mobileBorder), ) { Column( @@ -427,7 +533,7 @@ private fun MethodChip(label: String, active: Boolean, onClick: () -> Unit) { containerColor = if (active) mobileAccent else mobileSurface, contentColor = if (active) Color.White else mobileText, ), - border = BorderStroke(1.dp, if (active) Color(0xFF184DAF) else mobileBorderStrong), + border = BorderStroke(1.dp, if (active) mobileAccentBorderStrong else mobileBorderStrong), ) { Text(label, style = mobileCaption1.copy(fontWeight = FontWeight.Bold)) } @@ -456,10 +562,10 @@ private fun CommandBlock(command: String) { modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(12.dp), color = mobileCodeBg, - border = BorderStroke(1.dp, Color(0xFF2B2E35)), + border = BorderStroke(1.dp, mobileCodeBorder), ) { Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { - Box(modifier = Modifier.width(3.dp).height(42.dp).background(Color(0xFF3FC97A))) + Box(modifier = Modifier.width(3.dp).height(42.dp).background(mobileCodeAccent)) Text( text = command, modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp), diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt index 93b4fc1bb60..3416900ed5b 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt @@ -1,8 +1,8 @@ package ai.openclaw.app.ui -import androidx.core.net.toUri import java.util.Base64 import java.util.Locale +import java.net.URI import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive @@ -18,6 +18,7 @@ internal data class GatewayEndpointConfig( internal data class GatewaySetupCode( val url: String, + val bootstrapToken: String?, val token: String?, val password: String?, ) @@ -26,6 +27,7 @@ internal data class GatewayConnectConfig( val host: String, val port: Int, val tls: Boolean, + val bootstrapToken: String, val token: String, val password: String, ) @@ -44,12 +46,26 @@ internal fun resolveGatewayConnectConfig( if (useSetupCode) { val setup = decodeGatewaySetupCode(setupCode) ?: return null val parsed = parseGatewayEndpoint(setup.url) ?: return null + val setupBootstrapToken = setup.bootstrapToken?.trim().orEmpty() + val sharedToken = + when { + !setup.token.isNullOrBlank() -> setup.token.trim() + setupBootstrapToken.isNotEmpty() -> "" + else -> fallbackToken.trim() + } + val sharedPassword = + when { + !setup.password.isNullOrBlank() -> setup.password.trim() + setupBootstrapToken.isNotEmpty() -> "" + else -> fallbackPassword.trim() + } return GatewayConnectConfig( host = parsed.host, port = parsed.port, tls = parsed.tls, - token = setup.token ?: fallbackToken.trim(), - password = setup.password ?: fallbackPassword.trim(), + bootstrapToken = setupBootstrapToken, + token = sharedToken, + password = sharedPassword, ) } @@ -59,6 +75,7 @@ internal fun resolveGatewayConnectConfig( host = parsed.host, port = parsed.port, tls = parsed.tls, + bootstrapToken = "", token = fallbackToken.trim(), password = fallbackPassword.trim(), ) @@ -69,7 +86,7 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? { if (raw.isEmpty()) return null val normalized = if (raw.contains("://")) raw else "https://$raw" - val uri = normalized.toUri() + val uri = runCatching { URI(normalized) }.getOrNull() ?: return null val host = uri.host?.trim().orEmpty() if (host.isEmpty()) return null @@ -80,7 +97,7 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? { "wss", "https" -> true else -> true } - val port = uri.port.takeIf { it in 1..65535 } ?: 18789 + val port = uri.port.takeIf { it in 1..65535 } ?: if (tls) 443 else 18789 val displayUrl = "${if (tls) "https" else "http"}://$host:$port" return GatewayEndpointConfig(host = host, port = port, tls = tls, displayUrl = displayUrl) @@ -104,9 +121,10 @@ internal fun decodeGatewaySetupCode(rawInput: String): GatewaySetupCode? { val obj = parseJsonObject(decoded) ?: return null val url = jsonField(obj, "url").orEmpty() if (url.isEmpty()) return null + val bootstrapToken = jsonField(obj, "bootstrapToken") val token = jsonField(obj, "token") val password = jsonField(obj, "password") - GatewaySetupCode(url = url, token = token, password = password) + GatewaySetupCode(url = url, bootstrapToken = bootstrapToken, token = token, password = password) } catch (_: IllegalArgumentException) { null } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayDiagnostics.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayDiagnostics.kt new file mode 100644 index 00000000000..90737e51bc1 --- /dev/null +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayDiagnostics.kt @@ -0,0 +1,77 @@ +package ai.openclaw.app.ui + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.os.Build +import android.widget.Toast +import ai.openclaw.app.BuildConfig + +internal fun openClawAndroidVersionLabel(): String { + val versionName = BuildConfig.VERSION_NAME.trim().ifEmpty { "dev" } + return if (BuildConfig.DEBUG && !versionName.contains("dev", ignoreCase = true)) { + "$versionName-dev" + } else { + versionName + } +} + +internal fun gatewayStatusForDisplay(statusText: String): String { + return statusText.trim().ifEmpty { "Offline" } +} + +internal fun gatewayStatusHasDiagnostics(statusText: String): Boolean { + val lower = gatewayStatusForDisplay(statusText).lowercase() + return lower != "offline" && !lower.contains("connecting") +} + +internal fun gatewayStatusLooksLikePairing(statusText: String): Boolean { + val lower = gatewayStatusForDisplay(statusText).lowercase() + return lower.contains("pair") || lower.contains("approve") +} + +internal fun buildGatewayDiagnosticsReport( + screen: String, + gatewayAddress: String, + statusText: String, +): String { + val device = + listOfNotNull(Build.MANUFACTURER, Build.MODEL) + .joinToString(" ") + .trim() + .ifEmpty { "Android" } + val androidVersion = Build.VERSION.RELEASE?.trim().orEmpty().ifEmpty { Build.VERSION.SDK_INT.toString() } + val endpoint = gatewayAddress.trim().ifEmpty { "unknown" } + val status = gatewayStatusForDisplay(statusText) + return """ + Help diagnose this OpenClaw Android gateway connection failure. + + Please: + - pick one route only: same machine, same LAN, Tailscale, or public URL + - classify this as pairing/auth, TLS trust, wrong advertised route, wrong address/port, or gateway down + - quote the exact app status/error below + - tell me whether `openclaw devices list` should show a pending pairing request + - if more signal is needed, ask for `openclaw qr --json`, `openclaw devices list`, and `openclaw nodes status` + - give the next exact command or tap + + Debug info: + - screen: $screen + - app version: ${openClawAndroidVersionLabel()} + - device: $device + - android: $androidVersion (SDK ${Build.VERSION.SDK_INT}) + - gateway address: $endpoint + - status/error: $status + """.trimIndent() +} + +internal fun copyGatewayDiagnosticsReport( + context: Context, + screen: String, + gatewayAddress: String, + statusText: String, +) { + val clipboard = context.getSystemService(ClipboardManager::class.java) ?: return + val report = buildGatewayDiagnosticsReport(screen = screen, gatewayAddress = gatewayAddress, statusText = statusText) + clipboard.setPrimaryClip(ClipData.newPlainText("OpenClaw gateway diagnostics", report)) + Toast.makeText(context, "Copied gateway diagnostics", Toast.LENGTH_SHORT).show() +} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/MobileUiTokens.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/MobileUiTokens.kt index 5f93ed04cfa..d8521242ee5 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/MobileUiTokens.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/MobileUiTokens.kt @@ -1,5 +1,7 @@ package ai.openclaw.app.ui +import androidx.compose.runtime.Composable +import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle @@ -9,32 +11,147 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import ai.openclaw.app.R -internal val mobileBackgroundGradient = - Brush.verticalGradient( - listOf( - Color(0xFFFFFFFF), - Color(0xFFF7F8FA), - Color(0xFFEFF1F5), - ), +// --------------------------------------------------------------------------- +// MobileColors – semantic color tokens with light + dark variants +// --------------------------------------------------------------------------- + +internal data class MobileColors( + val surface: Color, + val surfaceStrong: Color, + val cardSurface: Color, + val border: Color, + val borderStrong: Color, + val text: Color, + val textSecondary: Color, + val textTertiary: Color, + val accent: Color, + val accentSoft: Color, + val accentBorderStrong: Color, + val success: Color, + val successSoft: Color, + val warning: Color, + val warningSoft: Color, + val danger: Color, + val dangerSoft: Color, + val codeBg: Color, + val codeText: Color, + val codeBorder: Color, + val codeAccent: Color, + val chipBorderConnected: Color, + val chipBorderConnecting: Color, + val chipBorderWarning: Color, + val chipBorderError: Color, +) + +internal fun lightMobileColors() = + MobileColors( + surface = Color(0xFFF6F7FA), + surfaceStrong = Color(0xFFECEEF3), + cardSurface = Color(0xFFFFFFFF), + border = Color(0xFFE5E7EC), + borderStrong = Color(0xFFD6DAE2), + text = Color(0xFF17181C), + textSecondary = Color(0xFF5D6472), + textTertiary = Color(0xFF99A0AE), + accent = Color(0xFF1D5DD8), + accentSoft = Color(0xFFECF3FF), + accentBorderStrong = Color(0xFF184DAF), + success = Color(0xFF2F8C5A), + successSoft = Color(0xFFEEF9F3), + warning = Color(0xFFC8841A), + warningSoft = Color(0xFFFFF8EC), + danger = Color(0xFFD04B4B), + dangerSoft = Color(0xFFFFF2F2), + codeBg = Color(0xFF15171B), + codeText = Color(0xFFE8EAEE), + codeBorder = Color(0xFF2B2E35), + codeAccent = Color(0xFF3FC97A), + chipBorderConnected = Color(0xFFCFEBD8), + chipBorderConnecting = Color(0xFFD5E2FA), + chipBorderWarning = Color(0xFFEED8B8), + chipBorderError = Color(0xFFF3C8C8), ) -internal val mobileSurface = Color(0xFFF6F7FA) -internal val mobileSurfaceStrong = Color(0xFFECEEF3) -internal val mobileBorder = Color(0xFFE5E7EC) -internal val mobileBorderStrong = Color(0xFFD6DAE2) -internal val mobileText = Color(0xFF17181C) -internal val mobileTextSecondary = Color(0xFF5D6472) -internal val mobileTextTertiary = Color(0xFF99A0AE) -internal val mobileAccent = Color(0xFF1D5DD8) -internal val mobileAccentSoft = Color(0xFFECF3FF) -internal val mobileSuccess = Color(0xFF2F8C5A) -internal val mobileSuccessSoft = Color(0xFFEEF9F3) -internal val mobileWarning = Color(0xFFC8841A) -internal val mobileWarningSoft = Color(0xFFFFF8EC) -internal val mobileDanger = Color(0xFFD04B4B) -internal val mobileDangerSoft = Color(0xFFFFF2F2) -internal val mobileCodeBg = Color(0xFF15171B) -internal val mobileCodeText = Color(0xFFE8EAEE) +internal fun darkMobileColors() = + MobileColors( + surface = Color(0xFF1A1C20), + surfaceStrong = Color(0xFF24262B), + cardSurface = Color(0xFF1E2024), + border = Color(0xFF2E3038), + borderStrong = Color(0xFF3A3D46), + text = Color(0xFFE4E5EA), + textSecondary = Color(0xFFA0A6B4), + textTertiary = Color(0xFF6B7280), + accent = Color(0xFF6EA8FF), + accentSoft = Color(0xFF1A2A44), + accentBorderStrong = Color(0xFF5B93E8), + success = Color(0xFF5FBB85), + successSoft = Color(0xFF152E22), + warning = Color(0xFFE8A844), + warningSoft = Color(0xFF2E2212), + danger = Color(0xFFE87070), + dangerSoft = Color(0xFF2E1616), + codeBg = Color(0xFF111317), + codeText = Color(0xFFE8EAEE), + codeBorder = Color(0xFF2B2E35), + codeAccent = Color(0xFF3FC97A), + chipBorderConnected = Color(0xFF1E4A30), + chipBorderConnecting = Color(0xFF1E3358), + chipBorderWarning = Color(0xFF3E3018), + chipBorderError = Color(0xFF3E1E1E), + ) + +internal val LocalMobileColors = staticCompositionLocalOf { lightMobileColors() } + +internal object MobileColorsAccessor { + val current: MobileColors + @Composable get() = LocalMobileColors.current +} + +// --------------------------------------------------------------------------- +// Backward-compatible top-level accessors (composable getters) +// --------------------------------------------------------------------------- +// These allow existing call sites to keep using `mobileSurface`, `mobileText`, etc. +// without converting every file at once. Each resolves to the themed value. + +internal val mobileSurface: Color @Composable get() = LocalMobileColors.current.surface +internal val mobileSurfaceStrong: Color @Composable get() = LocalMobileColors.current.surfaceStrong +internal val mobileCardSurface: Color @Composable get() = LocalMobileColors.current.cardSurface +internal val mobileBorder: Color @Composable get() = LocalMobileColors.current.border +internal val mobileBorderStrong: Color @Composable get() = LocalMobileColors.current.borderStrong +internal val mobileText: Color @Composable get() = LocalMobileColors.current.text +internal val mobileTextSecondary: Color @Composable get() = LocalMobileColors.current.textSecondary +internal val mobileTextTertiary: Color @Composable get() = LocalMobileColors.current.textTertiary +internal val mobileAccent: Color @Composable get() = LocalMobileColors.current.accent +internal val mobileAccentSoft: Color @Composable get() = LocalMobileColors.current.accentSoft +internal val mobileAccentBorderStrong: Color @Composable get() = LocalMobileColors.current.accentBorderStrong +internal val mobileSuccess: Color @Composable get() = LocalMobileColors.current.success +internal val mobileSuccessSoft: Color @Composable get() = LocalMobileColors.current.successSoft +internal val mobileWarning: Color @Composable get() = LocalMobileColors.current.warning +internal val mobileWarningSoft: Color @Composable get() = LocalMobileColors.current.warningSoft +internal val mobileDanger: Color @Composable get() = LocalMobileColors.current.danger +internal val mobileDangerSoft: Color @Composable get() = LocalMobileColors.current.dangerSoft +internal val mobileCodeBg: Color @Composable get() = LocalMobileColors.current.codeBg +internal val mobileCodeText: Color @Composable get() = LocalMobileColors.current.codeText +internal val mobileCodeBorder: Color @Composable get() = LocalMobileColors.current.codeBorder +internal val mobileCodeAccent: Color @Composable get() = LocalMobileColors.current.codeAccent + +// Background gradient – light fades white→gray, dark fades near-black→dark-gray +internal val mobileBackgroundGradient: Brush + @Composable get() { + val colors = LocalMobileColors.current + return Brush.verticalGradient( + listOf( + colors.surface, + colors.surfaceStrong, + colors.surfaceStrong, + ), + ) + } + +// --------------------------------------------------------------------------- +// Typography tokens (theme-independent) +// --------------------------------------------------------------------------- internal val mobileFontFamily = FontFamily( @@ -44,6 +161,15 @@ internal val mobileFontFamily = Font(resId = R.font.manrope_700_bold, weight = FontWeight.Bold), ) +internal val mobileDisplay = + TextStyle( + fontFamily = mobileFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 34.sp, + lineHeight = 40.sp, + letterSpacing = (-0.8).sp, + ) + internal val mobileTitle1 = TextStyle( fontFamily = mobileFontFamily, diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt index 8810ea93fcb..1f4774a537d 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt @@ -9,6 +9,7 @@ import android.hardware.SensorManager import android.net.Uri import android.os.Build import android.provider.Settings +import androidx.compose.foundation.BorderStroke import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility @@ -57,8 +58,17 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.ChatBubble +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.Cloud +import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material.icons.filled.Link +import androidx.compose.material.icons.filled.Security +import androidx.compose.material.icons.filled.Tune +import androidx.compose.material.icons.filled.Wifi +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState @@ -68,11 +78,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.draw.clip import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType @@ -85,10 +95,10 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner import ai.openclaw.app.LocationMode import ai.openclaw.app.MainViewModel -import ai.openclaw.app.R import ai.openclaw.app.node.DeviceNotificationListenerService -import com.journeyapps.barcodescanner.ScanContract -import com.journeyapps.barcodescanner.ScanOptions +import com.google.mlkit.vision.barcode.common.Barcode +import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions +import com.google.mlkit.vision.codescanner.GmsBarcodeScanning private enum class OnboardingStep(val index: Int, val label: String) { Welcome(1, "Welcome"), @@ -113,101 +123,87 @@ private enum class PermissionToggle { Calendar, Motion, Sms, + CallLog, } private enum class SpecialAccessToggle { NotificationListener, } -private val onboardingBackgroundGradient = - listOf( - Color(0xFFFFFFFF), - Color(0xFFF7F8FA), - Color(0xFFEFF1F5), - ) -private val onboardingSurface = Color(0xFFF6F7FA) -private val onboardingBorder = Color(0xFFE5E7EC) -private val onboardingBorderStrong = Color(0xFFD6DAE2) -private val onboardingText = Color(0xFF17181C) -private val onboardingTextSecondary = Color(0xFF4D5563) -private val onboardingTextTertiary = Color(0xFF8A92A2) -private val onboardingAccent = Color(0xFF1D5DD8) -private val onboardingAccentSoft = Color(0xFFECF3FF) -private val onboardingSuccess = Color(0xFF2F8C5A) -private val onboardingWarning = Color(0xFFC8841A) -private val onboardingCommandBg = Color(0xFF15171B) -private val onboardingCommandBorder = Color(0xFF2B2E35) -private val onboardingCommandAccent = Color(0xFF3FC97A) -private val onboardingCommandText = Color(0xFFE8EAEE) +private val onboardingBackgroundGradient: Brush + @Composable get() = mobileBackgroundGradient -private val onboardingFontFamily = - FontFamily( - Font(resId = R.font.manrope_400_regular, weight = FontWeight.Normal), - Font(resId = R.font.manrope_500_medium, weight = FontWeight.Medium), - Font(resId = R.font.manrope_600_semibold, weight = FontWeight.SemiBold), - Font(resId = R.font.manrope_700_bold, weight = FontWeight.Bold), - ) +private val onboardingSurface: Color + @Composable get() = mobileCardSurface -private val onboardingDisplayStyle = - TextStyle( - fontFamily = onboardingFontFamily, - fontWeight = FontWeight.Bold, - fontSize = 34.sp, - lineHeight = 40.sp, - letterSpacing = (-0.8).sp, - ) +private val onboardingBorder: Color + @Composable get() = mobileBorder -private val onboardingTitle1Style = - TextStyle( - fontFamily = onboardingFontFamily, - fontWeight = FontWeight.SemiBold, - fontSize = 24.sp, - lineHeight = 30.sp, - letterSpacing = (-0.5).sp, - ) +private val onboardingBorderStrong: Color + @Composable get() = mobileBorderStrong -private val onboardingHeadlineStyle = - TextStyle( - fontFamily = onboardingFontFamily, - fontWeight = FontWeight.SemiBold, - fontSize = 16.sp, - lineHeight = 22.sp, - letterSpacing = (-0.1).sp, - ) +private val onboardingText: Color + @Composable get() = mobileText -private val onboardingBodyStyle = - TextStyle( - fontFamily = onboardingFontFamily, - fontWeight = FontWeight.Medium, - fontSize = 15.sp, - lineHeight = 22.sp, - ) +private val onboardingTextSecondary: Color + @Composable get() = mobileTextSecondary -private val onboardingCalloutStyle = - TextStyle( - fontFamily = onboardingFontFamily, - fontWeight = FontWeight.Medium, - fontSize = 14.sp, - lineHeight = 20.sp, - ) +private val onboardingTextTertiary: Color + @Composable get() = mobileTextTertiary -private val onboardingCaption1Style = - TextStyle( - fontFamily = onboardingFontFamily, - fontWeight = FontWeight.Medium, - fontSize = 12.sp, - lineHeight = 16.sp, - letterSpacing = 0.2.sp, - ) +private val onboardingAccent: Color + @Composable get() = mobileAccent -private val onboardingCaption2Style = - TextStyle( - fontFamily = onboardingFontFamily, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 14.sp, - letterSpacing = 0.4.sp, - ) +private val onboardingAccentSoft: Color + @Composable get() = mobileAccentSoft + +private val onboardingAccentBorderStrong: Color + @Composable get() = mobileAccentBorderStrong + +private val onboardingSuccess: Color + @Composable get() = mobileSuccess + +private val onboardingSuccessSoft: Color + @Composable get() = mobileSuccessSoft + +private val onboardingWarning: Color + @Composable get() = mobileWarning + +private val onboardingWarningSoft: Color + @Composable get() = mobileWarningSoft + +private val onboardingCommandBg: Color + @Composable get() = mobileCodeBg + +private val onboardingCommandBorder: Color + @Composable get() = mobileCodeBorder + +private val onboardingCommandAccent: Color + @Composable get() = mobileCodeAccent + +private val onboardingCommandText: Color + @Composable get() = mobileCodeText + +private val onboardingDisplayStyle: TextStyle + get() = mobileDisplay + +private val onboardingTitle1Style: TextStyle + get() = mobileTitle1 + +private val onboardingHeadlineStyle: TextStyle + get() = mobileHeadline + +private val onboardingBodyStyle: TextStyle + get() = mobileBody + +private val onboardingCalloutStyle: TextStyle + get() = mobileCallout + +private val onboardingCaption1Style: TextStyle + get() = mobileCaption1 + +private val onboardingCaption2Style: TextStyle + get() = mobileCaption2 @Composable fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { @@ -232,6 +228,13 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { var attemptedConnect by rememberSaveable { mutableStateOf(false) } val lifecycleOwner = LocalLifecycleOwner.current + val qrScannerOptions = + remember { + GmsBarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_QR_CODE) + .build() + } + val qrScanner = remember(context, qrScannerOptions) { GmsBarcodeScanning.getClient(context, qrScannerOptions) } val smsAvailable = remember(context) { @@ -286,7 +289,15 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { } var enableSms by rememberSaveable { - mutableStateOf(smsAvailable && isPermissionGranted(context, Manifest.permission.SEND_SMS)) + mutableStateOf( + smsAvailable && + isPermissionGranted(context, Manifest.permission.SEND_SMS) && + isPermissionGranted(context, Manifest.permission.READ_SMS) + ) + } + var enableCallLog by + rememberSaveable { + mutableStateOf(isPermissionGranted(context, Manifest.permission.READ_CALL_LOG)) } var pendingPermissionToggle by remember { mutableStateOf(null) } @@ -304,6 +315,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { PermissionToggle.Calendar -> enableCalendar = enabled PermissionToggle.Motion -> enableMotion = enabled && motionAvailable PermissionToggle.Sms -> enableSms = enabled && smsAvailable + PermissionToggle.CallLog -> enableCallLog = enabled } } @@ -330,7 +342,10 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { !motionPermissionRequired || isPermissionGranted(context, Manifest.permission.ACTIVITY_RECOGNITION) PermissionToggle.Sms -> - !smsAvailable || isPermissionGranted(context, Manifest.permission.SEND_SMS) + !smsAvailable || + (isPermissionGranted(context, Manifest.permission.SEND_SMS) && + isPermissionGranted(context, Manifest.permission.READ_SMS)) + PermissionToggle.CallLog -> isPermissionGranted(context, Manifest.permission.READ_CALL_LOG) } fun setSpecialAccessToggleEnabled(toggle: SpecialAccessToggle, enabled: Boolean) { @@ -352,6 +367,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { enableCalendar, enableMotion, enableSms, + enableCallLog, smsAvailable, motionAvailable, ) { @@ -367,6 +383,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { if (enableCalendar) enabled += "Calendar" if (enableMotion && motionAvailable) enabled += "Motion" if (smsAvailable && enableSms) enabled += "SMS" + if (enableCallLog) enabled += "Call Log" if (enabled.isEmpty()) "None selected" else enabled.joinToString(", ") } @@ -451,40 +468,32 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } - val qrScanLauncher = - rememberLauncherForActivityResult(ScanContract()) { result -> - val contents = result.contents?.trim().orEmpty() - if (contents.isEmpty()) { - return@rememberLauncherForActivityResult - } - val scannedSetupCode = resolveScannedSetupCode(contents) - if (scannedSetupCode == null) { - gatewayError = "QR code did not contain a valid setup code." - return@rememberLauncherForActivityResult - } - setupCode = scannedSetupCode - gatewayInputMode = GatewayInputMode.SetupCode - gatewayError = null - attemptedConnect = false - } - if (pendingTrust != null) { val prompt = pendingTrust!! AlertDialog( onDismissRequest = { viewModel.declineGatewayTrustPrompt() }, - title = { Text("Trust this gateway?") }, + containerColor = onboardingSurface, + title = { Text("Trust this gateway?", style = onboardingHeadlineStyle, color = onboardingText) }, text = { Text( "First-time TLS connection.\n\nVerify this SHA-256 fingerprint before trusting:\n${prompt.fingerprintSha256}", + style = onboardingCalloutStyle, + color = onboardingText, ) }, confirmButton = { - TextButton(onClick = { viewModel.acceptGatewayTrustPrompt() }) { + TextButton( + onClick = { viewModel.acceptGatewayTrustPrompt() }, + colors = ButtonDefaults.textButtonColors(contentColor = onboardingAccent), + ) { Text("Trust and continue") } }, dismissButton = { - TextButton(onClick = { viewModel.declineGatewayTrustPrompt() }) { + TextButton( + onClick = { viewModel.declineGatewayTrustPrompt() }, + colors = ButtonDefaults.textButtonColors(contentColor = onboardingTextSecondary), + ) { Text("Cancel") } }, @@ -495,7 +504,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { modifier = modifier .fillMaxSize() - .background(Brush.verticalGradient(onboardingBackgroundGradient)), + .background(onboardingBackgroundGradient), ) { Column( modifier = @@ -513,25 +522,20 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { ) { Column( modifier = Modifier.padding(top = 12.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), ) { Text( - "FIRST RUN", - style = onboardingCaption1Style.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.5.sp), - color = onboardingAccent, - ) - Text( - "OpenClaw\nMobile Setup", - style = onboardingDisplayStyle.copy(lineHeight = 38.sp), + "OpenClaw", + style = onboardingDisplayStyle, color = onboardingText, ) Text( - "Step ${step.index} of 4", - style = onboardingCaption1Style, - color = onboardingAccent, + "Mobile Setup", + style = onboardingTitle1Style, + color = onboardingTextSecondary, ) } - StepRailWrap(current = step) + StepRail(current = step) when (step) { OnboardingStep.Welcome -> WelcomeStep() @@ -548,14 +552,28 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { gatewayError = gatewayError, onScanQrClick = { gatewayError = null - qrScanLauncher.launch( - ScanOptions().apply { - setDesiredBarcodeFormats(ScanOptions.QR_CODE) - setPrompt("Scan OpenClaw onboarding QR") - setBeepEnabled(false) - setOrientationLocked(false) - }, - ) + qrScanner.startScan() + .addOnSuccessListener { barcode -> + val contents = barcode.rawValue?.trim().orEmpty() + if (contents.isEmpty()) { + return@addOnSuccessListener + } + val scannedSetupCode = resolveScannedSetupCode(contents) + if (scannedSetupCode == null) { + gatewayError = "QR code did not contain a valid setup code." + return@addOnSuccessListener + } + setupCode = scannedSetupCode + gatewayInputMode = GatewayInputMode.SetupCode + gatewayError = null + attemptedConnect = false + } + .addOnCanceledListener { + // User dismissed the scanner; preserve current form state. + } + .addOnFailureListener { + gatewayError = qrScannerErrorMessage() + } }, onAdvancedOpenChange = { gatewayAdvancedOpen = it }, onInputModeChange = { @@ -594,6 +612,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { motionPermissionRequired = motionPermissionRequired, enableSms = enableSms, smsAvailable = smsAvailable, + enableCallLog = enableCallLog, context = context, onDiscoveryChange = { checked -> requestPermissionToggle( @@ -687,10 +706,17 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { requestPermissionToggle( PermissionToggle.Sms, checked, - listOf(Manifest.permission.SEND_SMS), + listOf(Manifest.permission.SEND_SMS, Manifest.permission.READ_SMS), ) } }, + onCallLogChange = { checked -> + requestPermissionToggle( + PermissionToggle.CallLog, + checked, + listOf(Manifest.permission.READ_CALL_LOG), + ) + }, ) OnboardingStep.FinalCheck -> FinalStep( @@ -746,13 +772,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { onClick = { step = OnboardingStep.Gateway }, modifier = Modifier.weight(1f).height(52.dp), shape = RoundedCornerShape(14.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = onboardingAccent, - contentColor = Color.White, - disabledContainerColor = onboardingAccent.copy(alpha = 0.45f), - disabledContentColor = Color.White, - ), + colors = onboardingPrimaryButtonColors(), ) { Text("Next", style = onboardingHeadlineStyle.copy(fontWeight = FontWeight.Bold)) } @@ -772,8 +792,18 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { return@Button } gatewayUrl = parsedSetup.url - parsedSetup.token?.let { viewModel.setGatewayToken(it) } - gatewayPassword = parsedSetup.password.orEmpty() + viewModel.setGatewayBootstrapToken(parsedSetup.bootstrapToken.orEmpty()) + val sharedToken = parsedSetup.token.orEmpty().trim() + val password = parsedSetup.password.orEmpty().trim() + if (sharedToken.isNotEmpty()) { + viewModel.setGatewayToken(sharedToken) + } else if (!parsedSetup.bootstrapToken.isNullOrBlank()) { + viewModel.setGatewayToken("") + } + gatewayPassword = password + if (password.isEmpty() && !parsedSetup.bootstrapToken.isNullOrBlank()) { + viewModel.setGatewayPassword("") + } } else { val manualUrl = composeGatewayManualUrl(manualHost, manualPort, manualTls) val parsedGateway = manualUrl?.let(::parseGatewayEndpoint) @@ -782,18 +812,13 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { return@Button } gatewayUrl = parsedGateway.displayUrl + viewModel.setGatewayBootstrapToken("") } step = OnboardingStep.Permissions }, modifier = Modifier.weight(1f).height(52.dp), shape = RoundedCornerShape(14.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = onboardingAccent, - contentColor = Color.White, - disabledContainerColor = onboardingAccent.copy(alpha = 0.45f), - disabledContentColor = Color.White, - ), + colors = onboardingPrimaryButtonColors(), ) { Text("Next", style = onboardingHeadlineStyle.copy(fontWeight = FontWeight.Bold)) } @@ -807,13 +832,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { }, modifier = Modifier.weight(1f).height(52.dp), shape = RoundedCornerShape(14.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = onboardingAccent, - contentColor = Color.White, - disabledContainerColor = onboardingAccent.copy(alpha = 0.45f), - disabledContentColor = Color.White, - ), + colors = onboardingPrimaryButtonColors(), ) { Text("Next", style = onboardingHeadlineStyle.copy(fontWeight = FontWeight.Bold)) } @@ -824,13 +843,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { onClick = { viewModel.setOnboardingCompleted(true) }, modifier = Modifier.weight(1f).height(52.dp), shape = RoundedCornerShape(14.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = onboardingAccent, - contentColor = Color.White, - disabledContainerColor = onboardingAccent.copy(alpha = 0.45f), - disabledContentColor = Color.White, - ), + colors = onboardingPrimaryButtonColors(), ) { Text("Finish", style = onboardingHeadlineStyle.copy(fontWeight = FontWeight.Bold)) } @@ -850,21 +863,20 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { viewModel.setManualHost(parsed.host) viewModel.setManualPort(parsed.port) viewModel.setManualTls(parsed.tls) + if (gatewayInputMode == GatewayInputMode.Manual) { + viewModel.setGatewayBootstrapToken("") + } if (token.isNotEmpty()) { viewModel.setGatewayToken(token) + } else { + viewModel.setGatewayToken("") } viewModel.setGatewayPassword(password) viewModel.connectManual() }, modifier = Modifier.weight(1f).height(52.dp), shape = RoundedCornerShape(14.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = onboardingAccent, - contentColor = Color.White, - disabledContainerColor = onboardingAccent.copy(alpha = 0.45f), - disabledContentColor = Color.White, - ), + colors = onboardingPrimaryButtonColors(), ) { Text("Connect", style = onboardingHeadlineStyle.copy(fontWeight = FontWeight.Bold)) } @@ -877,13 +889,34 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { } @Composable -private fun StepRailWrap(current: OnboardingStep) { - Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { - HorizontalDivider(color = onboardingBorder) - StepRail(current = current) - HorizontalDivider(color = onboardingBorder) - } -} +private fun onboardingPrimaryButtonColors() = + ButtonDefaults.buttonColors( + containerColor = onboardingAccent, + contentColor = Color.White, + disabledContainerColor = onboardingAccent.copy(alpha = 0.45f), + disabledContentColor = Color.White.copy(alpha = 0.9f), + ) + +@Composable +private fun onboardingTextFieldColors() = + OutlinedTextFieldDefaults.colors( + focusedContainerColor = onboardingSurface, + unfocusedContainerColor = onboardingSurface, + focusedBorderColor = onboardingAccent, + unfocusedBorderColor = onboardingBorder, + focusedTextColor = onboardingText, + unfocusedTextColor = onboardingText, + cursorColor = onboardingAccent, + ) + +@Composable +private fun onboardingSwitchColors() = + SwitchDefaults.colors( + checkedTrackColor = onboardingAccent, + uncheckedTrackColor = onboardingBorderStrong, + checkedThumbColor = Color.White, + uncheckedThumbColor = Color.White, + ) @Composable private fun StepRail(current: OnboardingStep) { @@ -926,11 +959,31 @@ private fun StepRail(current: OnboardingStep) { @Composable private fun WelcomeStep() { - StepShell(title = "What You Get") { - Bullet("Control the gateway and operator chat from one mobile surface.") - Bullet("Connect with setup code and recover pairing with CLI commands.") - Bullet("Enable only the permissions and capabilities you want.") - Bullet("Finish with a real connection check before entering the app.") + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + FeatureCard( + icon = Icons.Default.Wifi, + title = "Connect to your gateway", + subtitle = "Scan a QR code or enter your host manually", + accentColor = onboardingAccent, + ) + FeatureCard( + icon = Icons.Default.Tune, + title = "Choose your permissions", + subtitle = "Enable only what you need, change anytime", + accentColor = Color(0xFF7C5AC7), + ) + FeatureCard( + icon = Icons.Default.ChatBubble, + title = "Chat, voice, and screen", + subtitle = "Full operator control from your phone", + accentColor = onboardingSuccess, + ) + FeatureCard( + icon = Icons.Default.CheckCircle, + title = "Verify your connection", + subtitle = "Live check before you enter the app", + accentColor = Color(0xFFC8841A), + ) } } @@ -959,20 +1012,17 @@ private fun GatewayStep( val manualResolvedEndpoint = remember(manualHost, manualPort, manualTls) { composeGatewayManualUrl(manualHost, manualPort, manualTls)?.let { parseGatewayEndpoint(it)?.displayUrl } } StepShell(title = "Gateway Connection") { - GuideBlock(title = "Scan onboarding QR") { - Text("Run these on the gateway host:", style = onboardingCalloutStyle, color = onboardingTextSecondary) - CommandBlock("openclaw qr") - Text("Then scan with this device.", style = onboardingCalloutStyle, color = onboardingTextSecondary) - } + Text( + "Run `openclaw qr` on your gateway host, then scan the code with this device.", + style = onboardingCalloutStyle, + color = onboardingTextSecondary, + ) + CommandBlock("openclaw qr") Button( onClick = onScanQrClick, modifier = Modifier.fillMaxWidth().height(48.dp), shape = RoundedCornerShape(12.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = onboardingAccent, - contentColor = Color.White, - ), + colors = onboardingPrimaryButtonColors(), ) { Text("Scan QR code", style = onboardingHeadlineStyle.copy(fontWeight = FontWeight.Bold)) } @@ -1007,21 +1057,6 @@ private fun GatewayStep( AnimatedVisibility(visible = advancedOpen) { Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { - GuideBlock(title = "Manual setup commands") { - Text("Run these on the gateway host:", style = onboardingCalloutStyle, color = onboardingTextSecondary) - CommandBlock("openclaw qr --setup-code-only") - CommandBlock("openclaw qr --json") - Text( - "`--json` prints `setupCode` and `gatewayUrl`.", - style = onboardingCalloutStyle, - color = onboardingTextSecondary, - ) - Text( - "Auto URL discovery is not wired yet. Android emulator uses `10.0.2.2`; real devices need LAN/Tailscale host.", - style = onboardingCalloutStyle, - color = onboardingTextSecondary, - ) - } GatewayModeToggle(inputMode = inputMode, onInputModeChange = onInputModeChange) if (inputMode == GatewayInputMode.SetupCode) { @@ -1037,15 +1072,7 @@ private fun GatewayStep( textStyle = onboardingBodyStyle.copy(fontFamily = FontFamily.Monospace, color = onboardingText), shape = RoundedCornerShape(14.dp), colors = - OutlinedTextFieldDefaults.colors( - focusedContainerColor = onboardingSurface, - unfocusedContainerColor = onboardingSurface, - focusedBorderColor = onboardingAccent, - unfocusedBorderColor = onboardingBorder, - focusedTextColor = onboardingText, - unfocusedTextColor = onboardingText, - cursorColor = onboardingAccent, - ), + onboardingTextFieldColors(), ) if (!resolvedEndpoint.isNullOrBlank()) { ResolvedEndpoint(endpoint = resolvedEndpoint) @@ -1075,15 +1102,7 @@ private fun GatewayStep( textStyle = onboardingBodyStyle.copy(color = onboardingText), shape = RoundedCornerShape(14.dp), colors = - OutlinedTextFieldDefaults.colors( - focusedContainerColor = onboardingSurface, - unfocusedContainerColor = onboardingSurface, - focusedBorderColor = onboardingAccent, - unfocusedBorderColor = onboardingBorder, - focusedTextColor = onboardingText, - unfocusedTextColor = onboardingText, - cursorColor = onboardingAccent, - ), + onboardingTextFieldColors(), ) Text("PORT", style = onboardingCaption1Style.copy(letterSpacing = 0.9.sp), color = onboardingTextSecondary) @@ -1097,15 +1116,7 @@ private fun GatewayStep( textStyle = onboardingBodyStyle.copy(fontFamily = FontFamily.Monospace, color = onboardingText), shape = RoundedCornerShape(14.dp), colors = - OutlinedTextFieldDefaults.colors( - focusedContainerColor = onboardingSurface, - unfocusedContainerColor = onboardingSurface, - focusedBorderColor = onboardingAccent, - unfocusedBorderColor = onboardingBorder, - focusedTextColor = onboardingText, - unfocusedTextColor = onboardingText, - cursorColor = onboardingAccent, - ), + onboardingTextFieldColors(), ) Row( @@ -1121,12 +1132,7 @@ private fun GatewayStep( checked = manualTls, onCheckedChange = onManualTlsChange, colors = - SwitchDefaults.colors( - checkedTrackColor = onboardingAccent, - uncheckedTrackColor = onboardingBorderStrong, - checkedThumbColor = Color.White, - uncheckedThumbColor = Color.White, - ), + onboardingSwitchColors(), ) } @@ -1141,15 +1147,7 @@ private fun GatewayStep( textStyle = onboardingBodyStyle.copy(color = onboardingText), shape = RoundedCornerShape(14.dp), colors = - OutlinedTextFieldDefaults.colors( - focusedContainerColor = onboardingSurface, - unfocusedContainerColor = onboardingSurface, - focusedBorderColor = onboardingAccent, - unfocusedBorderColor = onboardingBorder, - focusedTextColor = onboardingText, - unfocusedTextColor = onboardingText, - cursorColor = onboardingAccent, - ), + onboardingTextFieldColors(), ) Text("PASSWORD (OPTIONAL)", style = onboardingCaption1Style.copy(letterSpacing = 0.9.sp), color = onboardingTextSecondary) @@ -1163,15 +1161,7 @@ private fun GatewayStep( textStyle = onboardingBodyStyle.copy(color = onboardingText), shape = RoundedCornerShape(14.dp), colors = - OutlinedTextFieldDefaults.colors( - focusedContainerColor = onboardingSurface, - unfocusedContainerColor = onboardingSurface, - focusedBorderColor = onboardingAccent, - unfocusedBorderColor = onboardingBorder, - focusedTextColor = onboardingText, - unfocusedTextColor = onboardingText, - cursorColor = onboardingAccent, - ), + onboardingTextFieldColors(), ) if (!manualResolvedEndpoint.isNullOrBlank()) { @@ -1239,7 +1229,7 @@ private fun GatewayModeChip( containerColor = if (active) onboardingAccent else onboardingSurface, contentColor = if (active) Color.White else onboardingText, ), - border = androidx.compose.foundation.BorderStroke(1.dp, if (active) Color(0xFF184DAF) else onboardingBorderStrong), + border = androidx.compose.foundation.BorderStroke(1.dp, if (active) onboardingAccentBorderStrong else onboardingBorderStrong), ) { Text( text = label, @@ -1290,13 +1280,9 @@ private fun StepShell( title: String, content: @Composable ColumnScope.() -> Unit, ) { - Column(verticalArrangement = Arrangement.spacedBy(0.dp)) { - HorizontalDivider(color = onboardingBorder) - Column(modifier = Modifier.padding(vertical = 14.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - Text(title, style = onboardingTitle1Style, color = onboardingText) - content() - } - HorizontalDivider(color = onboardingBorder) + Column(modifier = Modifier.padding(vertical = 4.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text(title, style = onboardingTitle1Style, color = onboardingText) + content() } } @@ -1321,6 +1307,7 @@ private fun PermissionsStep( motionPermissionRequired: Boolean, enableSms: Boolean, smsAvailable: Boolean, + enableCallLog: Boolean, context: Context, onDiscoveryChange: (Boolean) -> Unit, onLocationChange: (Boolean) -> Unit, @@ -1333,6 +1320,7 @@ private fun PermissionsStep( onCalendarChange: (Boolean) -> Unit, onMotionChange: (Boolean) -> Unit, onSmsChange: (Boolean) -> Unit, + onCallLogChange: (Boolean) -> Unit, ) { val discoveryPermission = if (Build.VERSION.SDK_INT >= 33) Manifest.permission.NEARBY_WIFI_DEVICES else Manifest.permission.ACCESS_FINE_LOCATION val locationGranted = @@ -1362,13 +1350,15 @@ private fun PermissionsStep( StepShell(title = "Permissions") { Text( - "Enable only what you need now. You can change everything later in Settings.", + "Enable only what you need. You can change these anytime in Settings.", style = onboardingCalloutStyle, color = onboardingTextSecondary, ) + + PermissionSectionHeader("System") PermissionToggleRow( title = "Gateway discovery", - subtitle = if (Build.VERSION.SDK_INT >= 33) "Nearby devices" else "Location (for NSD)", + subtitle = "Find gateways on your local network", checked = enableDiscovery, granted = isPermissionGranted(context, discoveryPermission), onCheckedChange = onDiscoveryChange, @@ -1376,7 +1366,7 @@ private fun PermissionsStep( InlineDivider() PermissionToggleRow( title = "Location", - subtitle = "location.get (while app is open)", + subtitle = "Share device location while app is open", checked = enableLocation, granted = locationGranted, onCheckedChange = onLocationChange, @@ -1385,7 +1375,7 @@ private fun PermissionsStep( if (Build.VERSION.SDK_INT >= 33) { PermissionToggleRow( title = "Notifications", - subtitle = "system.notify and foreground alerts", + subtitle = "Alerts and foreground service notices", checked = enableNotifications, granted = isPermissionGranted(context, Manifest.permission.POST_NOTIFICATIONS), onCheckedChange = onNotificationsChange, @@ -1394,15 +1384,16 @@ private fun PermissionsStep( } PermissionToggleRow( title = "Notification listener", - subtitle = "notifications.list and notifications.actions (opens Android Settings)", + subtitle = "Read and act on your notifications", checked = enableNotificationListener, granted = notificationListenerGranted, onCheckedChange = onNotificationListenerChange, ) - InlineDivider() + + PermissionSectionHeader("Media") PermissionToggleRow( title = "Microphone", - subtitle = "Foreground Voice tab transcription", + subtitle = "Voice transcription in the Voice tab", checked = enableMicrophone, granted = isPermissionGranted(context, Manifest.permission.RECORD_AUDIO), onCheckedChange = onMicrophoneChange, @@ -1410,7 +1401,7 @@ private fun PermissionsStep( InlineDivider() PermissionToggleRow( title = "Camera", - subtitle = "camera.snap and camera.clip", + subtitle = "Take photos and short video clips", checked = enableCamera, granted = isPermissionGranted(context, Manifest.permission.CAMERA), onCheckedChange = onCameraChange, @@ -1418,15 +1409,16 @@ private fun PermissionsStep( InlineDivider() PermissionToggleRow( title = "Photos", - subtitle = "photos.latest", + subtitle = "Access your recent photos", checked = enablePhotos, granted = isPermissionGranted(context, photosPermission), onCheckedChange = onPhotosChange, ) - InlineDivider() + + PermissionSectionHeader("Personal Data") PermissionToggleRow( title = "Contacts", - subtitle = "contacts.search and contacts.add", + subtitle = "Search and add contacts", checked = enableContacts, granted = contactsGranted, onCheckedChange = onContactsChange, @@ -1434,7 +1426,7 @@ private fun PermissionsStep( InlineDivider() PermissionToggleRow( title = "Calendar", - subtitle = "calendar.events and calendar.add", + subtitle = "Read and create calendar events", checked = enableCalendar, granted = calendarGranted, onCheckedChange = onCalendarChange, @@ -1442,7 +1434,7 @@ private fun PermissionsStep( InlineDivider() PermissionToggleRow( title = "Motion", - subtitle = "motion.activity and motion.pedometer", + subtitle = "Activity and step tracking", checked = enableMotion, granted = motionGranted, onCheckedChange = onMotionChange, @@ -1453,16 +1445,36 @@ private fun PermissionsStep( InlineDivider() PermissionToggleRow( title = "SMS", - subtitle = "Allow gateway-triggered SMS sending", + subtitle = "Send and search text messages via the gateway", checked = enableSms, - granted = isPermissionGranted(context, Manifest.permission.SEND_SMS), + granted = + isPermissionGranted(context, Manifest.permission.SEND_SMS) && + isPermissionGranted(context, Manifest.permission.READ_SMS), onCheckedChange = onSmsChange, ) } + InlineDivider() + PermissionToggleRow( + title = "Call Log", + subtitle = "callLog.search", + checked = enableCallLog, + granted = isPermissionGranted(context, Manifest.permission.READ_CALL_LOG), + onCheckedChange = onCallLogChange, + ) Text("All settings can be changed later in Settings.", style = onboardingCalloutStyle, color = onboardingTextSecondary) } } +@Composable +private fun PermissionSectionHeader(title: String) { + Text( + title.uppercase(), + style = onboardingCaption1Style.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.2.sp), + color = onboardingAccent, + modifier = Modifier.padding(top = 8.dp), + ) +} + @Composable private fun PermissionToggleRow( title: String, @@ -1473,6 +1485,12 @@ private fun PermissionToggleRow( statusOverride: String? = null, onCheckedChange: (Boolean) -> Unit, ) { + val statusText = statusOverride ?: if (granted) "Granted" else "Not granted" + val statusColor = when { + statusOverride != null -> onboardingTextTertiary + granted -> onboardingSuccess + else -> onboardingWarning + } Row( modifier = Modifier.fillMaxWidth().heightIn(min = 50.dp), verticalAlignment = Alignment.CenterVertically, @@ -1481,23 +1499,13 @@ private fun PermissionToggleRow( Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { Text(title, style = onboardingHeadlineStyle, color = onboardingText) Text(subtitle, style = onboardingCalloutStyle.copy(lineHeight = 18.sp), color = onboardingTextSecondary) - Text( - statusOverride ?: if (granted) "Granted" else "Not granted", - style = onboardingCaption1Style, - color = if (granted) onboardingSuccess else onboardingTextSecondary, - ) + Text(statusText, style = onboardingCaption1Style, color = statusColor) } Switch( checked = checked, onCheckedChange = onCheckedChange, enabled = enabled, - colors = - SwitchDefaults.colors( - checkedTrackColor = onboardingAccent, - uncheckedTrackColor = onboardingBorderStrong, - checkedThumbColor = Color.White, - uncheckedThumbColor = Color.White, - ), + colors = onboardingSwitchColors(), ) } } @@ -1513,23 +1521,193 @@ private fun FinalStep( enabledPermissions: String, methodLabel: String, ) { - StepShell(title = "Review") { - SummaryField(label = "Method", value = methodLabel) - SummaryField(label = "Gateway", value = parsedGateway?.displayUrl ?: "Invalid gateway URL") - SummaryField(label = "Enabled Permissions", value = enabledPermissions) + val context = androidx.compose.ui.platform.LocalContext.current + val gatewayAddress = parsedGateway?.displayUrl ?: "Invalid gateway URL" + val statusLabel = gatewayStatusForDisplay(statusText) + val showDiagnostics = gatewayStatusHasDiagnostics(statusText) + val pairingRequired = gatewayStatusLooksLikePairing(statusText) + + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text("Review", style = onboardingTitle1Style, color = onboardingText) + + SummaryCard( + icon = Icons.Default.Link, + label = "Method", + value = methodLabel, + accentColor = onboardingAccent, + ) + SummaryCard( + icon = Icons.Default.Cloud, + label = "Gateway", + value = gatewayAddress, + accentColor = Color(0xFF7C5AC7), + ) + SummaryCard( + icon = Icons.Default.Security, + label = "Permissions", + value = enabledPermissions, + accentColor = onboardingSuccess, + ) if (!attemptedConnect) { - Text("Press Connect to verify gateway reachability and auth.", style = onboardingCalloutStyle, color = onboardingTextSecondary) + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(14.dp), + color = onboardingAccentSoft, + border = androidx.compose.foundation.BorderStroke(1.dp, onboardingAccent.copy(alpha = 0.2f)), + ) { + Row( + modifier = Modifier.padding(14.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(42.dp) + .background(onboardingAccent.copy(alpha = 0.1f), RoundedCornerShape(11.dp)), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = Icons.Default.Wifi, + contentDescription = null, + tint = onboardingAccent, + modifier = Modifier.size(22.dp), + ) + } + Text( + "Tap Connect to verify your gateway is reachable.", + style = onboardingCalloutStyle, + color = onboardingAccent, + ) + } + } + } else if (isConnected) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(14.dp), + color = onboardingSuccessSoft, + border = androidx.compose.foundation.BorderStroke(1.dp, onboardingSuccess.copy(alpha = 0.2f)), + ) { + Row( + modifier = Modifier.padding(14.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(42.dp) + .background(onboardingSuccess.copy(alpha = 0.1f), RoundedCornerShape(11.dp)), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = Icons.Default.CheckCircle, + contentDescription = null, + tint = onboardingSuccess, + modifier = Modifier.size(22.dp), + ) + } + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text("Connected", style = onboardingHeadlineStyle, color = onboardingSuccess) + Text( + serverName ?: remoteAddress ?: "gateway", + style = onboardingCalloutStyle, + color = onboardingSuccess.copy(alpha = 0.8f), + ) + } + } + } } else { - Text("Status: $statusText", style = onboardingCalloutStyle, color = if (isConnected) onboardingSuccess else onboardingTextSecondary) - if (isConnected) { - Text("Connected to ${serverName ?: remoteAddress ?: "gateway"}", style = onboardingCalloutStyle, color = onboardingSuccess) - } else { - GuideBlock(title = "Pairing Required") { - Text("Run these on the gateway host:", style = onboardingCalloutStyle, color = onboardingTextSecondary) - CommandBlock("openclaw devices list") - CommandBlock("openclaw devices approve ") - Text("Then tap Connect again.", style = onboardingCalloutStyle, color = onboardingTextSecondary) + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(14.dp), + color = onboardingWarningSoft, + border = BorderStroke(1.dp, onboardingWarning.copy(alpha = 0.2f)), + ) { + Column( + modifier = Modifier.padding(14.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(42.dp) + .background(onboardingWarning.copy(alpha = 0.1f), RoundedCornerShape(11.dp)), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = Icons.Default.Link, + contentDescription = null, + tint = onboardingWarning, + modifier = Modifier.size(22.dp), + ) + } + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + if (pairingRequired) "Pairing Required" else "Connection Failed", + style = onboardingHeadlineStyle, + color = onboardingWarning, + ) + Text( + if (pairingRequired) { + "Approve this phone on the gateway host, or copy the report below." + } else { + "Copy this report and give it to your Claw." + }, + style = onboardingCalloutStyle, + color = onboardingTextSecondary, + ) + } + } + 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, + color = onboardingTextSecondary, + ) + Button( + onClick = { + copyGatewayDiagnosticsReport( + context = context, + screen = "onboarding final check", + gatewayAddress = gatewayAddress, + statusText = statusLabel, + ) + }, + modifier = Modifier.fillMaxWidth().height(48.dp), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors(containerColor = onboardingSurface, contentColor = onboardingWarning), + border = BorderStroke(1.dp, onboardingWarning.copy(alpha = 0.3f)), + ) { + Icon(Icons.Default.ContentCopy, contentDescription = null, modifier = Modifier.size(18.dp)) + Spacer(modifier = Modifier.width(8.dp)) + Text("Copy Report for Claw", style = onboardingCalloutStyle.copy(fontWeight = FontWeight.Bold)) + } + } + if (pairingRequired) { + CommandBlock("openclaw devices list") + CommandBlock("openclaw devices approve ") + Text("Then tap Connect again.", style = onboardingCalloutStyle, color = onboardingTextSecondary) + } } } } @@ -1537,15 +1715,46 @@ private fun FinalStep( } @Composable -private fun SummaryField(label: String, value: String) { - Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - Text( - label, - style = onboardingCaption2Style.copy(fontWeight = FontWeight.SemiBold, letterSpacing = 0.6.sp), - color = onboardingTextSecondary, - ) - Text(value, style = onboardingHeadlineStyle, color = onboardingText) - HorizontalDivider(color = onboardingBorder) +private fun SummaryCard( + icon: ImageVector, + label: String, + value: String, + accentColor: Color, +) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(14.dp), + color = onboardingSurface, + border = androidx.compose.foundation.BorderStroke(1.dp, onboardingBorder), + ) { + Row( + modifier = Modifier.padding(14.dp), + horizontalArrangement = Arrangement.spacedBy(14.dp), + verticalAlignment = Alignment.Top, + ) { + Box( + modifier = + Modifier + .size(42.dp) + .background(accentColor.copy(alpha = 0.1f), RoundedCornerShape(11.dp)), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = accentColor, + modifier = Modifier.size(22.dp), + ) + } + Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + label.uppercase(), + style = onboardingCaption1Style.copy(fontWeight = FontWeight.Bold, letterSpacing = 0.8.sp), + color = onboardingTextSecondary, + ) + Text(value, style = onboardingHeadlineStyle, color = onboardingText) + } + } } } @@ -1555,10 +1764,12 @@ private fun CommandBlock(command: String) { modifier = Modifier .fillMaxWidth() - .background(onboardingCommandBg, RoundedCornerShape(12.dp)) + .height(IntrinsicSize.Min) + .clip(RoundedCornerShape(12.dp)) + .background(onboardingCommandBg) .border(width = 1.dp, color = onboardingCommandBorder, shape = RoundedCornerShape(12.dp)), ) { - Box(modifier = Modifier.width(3.dp).height(42.dp).background(onboardingCommandAccent)) + Box(modifier = Modifier.width(3.dp).fillMaxHeight().background(onboardingCommandAccent)) Text( command, modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp), @@ -1570,23 +1781,42 @@ private fun CommandBlock(command: String) { } @Composable -private fun Bullet(text: String) { - Row(horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.Top) { - Box( - modifier = - Modifier - .padding(top = 7.dp) - .size(8.dp) - .background(onboardingAccentSoft, CircleShape), - ) - Box( - modifier = - Modifier - .padding(top = 9.dp) - .size(4.dp) - .background(onboardingAccent, CircleShape), - ) - Text(text, style = onboardingBodyStyle, color = onboardingTextSecondary, modifier = Modifier.weight(1f)) +private fun FeatureCard( + icon: ImageVector, + title: String, + subtitle: String, + accentColor: Color, +) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(14.dp), + color = onboardingSurface, + border = androidx.compose.foundation.BorderStroke(1.dp, onboardingBorder), + ) { + Row( + modifier = Modifier.padding(14.dp), + horizontalArrangement = Arrangement.spacedBy(14.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(42.dp) + .background(accentColor.copy(alpha = 0.1f), RoundedCornerShape(11.dp)), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = accentColor, + modifier = Modifier.size(22.dp), + ) + } + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text(title, style = onboardingHeadlineStyle, color = onboardingText) + Text(subtitle, style = onboardingCalloutStyle, color = onboardingTextSecondary) + } + } } } @@ -1594,6 +1824,10 @@ private fun isPermissionGranted(context: Context, permission: String): Boolean { return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED } +private fun qrScannerErrorMessage(): String { + return "Google Code Scanner could not start. Update Google Play services or use the setup code manually." +} + private fun isNotificationListenerEnabled(context: Context): Boolean { return DeviceNotificationListenerService.isAccessEnabled(context) } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/OpenClawTheme.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/OpenClawTheme.kt index e3f0cfaac9c..cfcceb4f3da 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/OpenClawTheme.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/OpenClawTheme.kt @@ -5,6 +5,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -13,8 +14,11 @@ fun OpenClawTheme(content: @Composable () -> Unit) { val context = LocalContext.current val isDark = isSystemInDarkTheme() val colorScheme = if (isDark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + val mobileColors = if (isDark) darkMobileColors() else lightMobileColors() - MaterialTheme(colorScheme = colorScheme, content = content) + CompositionLocalProvider(LocalMobileColors provides mobileColors) { + MaterialTheme(colorScheme = colorScheme, content = content) + } } @Composable diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/PostOnboardingTabs.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/PostOnboardingTabs.kt index 0642f9b3a7e..5e04d905407 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/PostOnboardingTabs.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/PostOnboardingTabs.kt @@ -134,43 +134,14 @@ fun PostOnboardingTabs(viewModel: MainViewModel, modifier: Modifier = Modifier) @Composable private fun ScreenTabScreen(viewModel: MainViewModel) { val isConnected by viewModel.isConnected.collectAsState() - val isNodeConnected by viewModel.isNodeConnected.collectAsState() - val canvasUrl by viewModel.canvasCurrentUrl.collectAsState() - val canvasA2uiHydrated by viewModel.canvasA2uiHydrated.collectAsState() - val canvasRehydratePending by viewModel.canvasRehydratePending.collectAsState() - val canvasRehydrateErrorText by viewModel.canvasRehydrateErrorText.collectAsState() - val isA2uiUrl = canvasUrl?.contains("/__openclaw__/a2ui/") == true - val showRestoreCta = isConnected && isNodeConnected && (canvasUrl.isNullOrBlank() || (isA2uiUrl && !canvasA2uiHydrated)) - val restoreCtaText = - when { - canvasRehydratePending -> "Restore requested. Waiting for agent…" - !canvasRehydrateErrorText.isNullOrBlank() -> canvasRehydrateErrorText!! - else -> "Canvas reset. Tap to restore dashboard." + LaunchedEffect(isConnected) { + if (isConnected) { + viewModel.refreshHomeCanvasOverviewIfConnected() } + } Box(modifier = Modifier.fillMaxSize()) { CanvasScreen(viewModel = viewModel, modifier = Modifier.fillMaxSize()) - - if (showRestoreCta) { - Surface( - onClick = { - if (canvasRehydratePending) return@Surface - viewModel.requestCanvasRehydrate(source = "screen_tab_cta") - }, - modifier = Modifier.align(Alignment.TopCenter).padding(horizontal = 16.dp, vertical = 16.dp), - shape = RoundedCornerShape(12.dp), - color = mobileSurface.copy(alpha = 0.9f), - border = BorderStroke(1.dp, mobileBorder), - shadowElevation = 4.dp, - ) { - Text( - text = restoreCtaText, - modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp), - style = mobileCallout.copy(fontWeight = FontWeight.Medium), - color = mobileText, - ) - } - } } } @@ -188,28 +159,28 @@ private fun TopStatusBar( mobileSuccessSoft, mobileSuccess, mobileSuccess, - Color(0xFFCFEBD8), + LocalMobileColors.current.chipBorderConnected, ) StatusVisual.Connecting -> listOf( mobileAccentSoft, mobileAccent, mobileAccent, - Color(0xFFD5E2FA), + LocalMobileColors.current.chipBorderConnecting, ) StatusVisual.Warning -> listOf( mobileWarningSoft, mobileWarning, mobileWarning, - Color(0xFFEED8B8), + LocalMobileColors.current.chipBorderWarning, ) StatusVisual.Error -> listOf( mobileDangerSoft, mobileDanger, mobileDanger, - Color(0xFFF3C8C8), + LocalMobileColors.current.chipBorderError, ) StatusVisual.Offline -> listOf( @@ -278,7 +249,7 @@ private fun BottomTabBar( ) { Surface( modifier = Modifier.fillMaxWidth(), - color = Color.White.copy(alpha = 0.97f), + color = mobileCardSurface.copy(alpha = 0.97f), shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp), border = BorderStroke(1.dp, mobileBorder), shadowElevation = 6.dp, @@ -299,7 +270,7 @@ private fun BottomTabBar( modifier = Modifier.weight(1f).heightIn(min = 58.dp), shape = RoundedCornerShape(16.dp), color = if (active) mobileAccentSoft else Color.Transparent, - border = if (active) BorderStroke(1.dp, Color(0xFFD5E2FA)) else null, + border = if (active) BorderStroke(1.dp, LocalMobileColors.current.chipBorderConnecting) else null, shadowElevation = 0.dp, ) { Column( diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt index a3f7868fa90..f78e4535bcb 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt @@ -218,6 +218,18 @@ fun SettingsSheet(viewModel: MainViewModel) { calendarPermissionGranted = readOk && writeOk } + var callLogPermissionGranted by + remember { + mutableStateOf( + ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALL_LOG) == + PackageManager.PERMISSION_GRANTED, + ) + } + val callLogPermissionLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> + callLogPermissionGranted = granted + } + var motionPermissionGranted by remember { mutableStateOf( @@ -235,12 +247,16 @@ fun SettingsSheet(viewModel: MainViewModel) { remember { mutableStateOf( ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) == + PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED, ) } val smsPermissionLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> - smsPermissionGranted = granted + rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { perms -> + val sendOk = perms[Manifest.permission.SEND_SMS] == true + val readOk = perms[Manifest.permission.READ_SMS] == true + smsPermissionGranted = sendOk && readOk viewModel.refreshGatewayConnection() } @@ -266,12 +282,17 @@ fun SettingsSheet(viewModel: MainViewModel) { PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED + callLogPermissionGranted = + ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALL_LOG) == + PackageManager.PERMISSION_GRANTED motionPermissionGranted = !motionPermissionRequired || ContextCompat.checkSelfPermission(context, Manifest.permission.ACTIVITY_RECOGNITION) == PackageManager.PERMISSION_GRANTED smsPermissionGranted = ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) == + PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED } } @@ -345,179 +366,90 @@ fun SettingsSheet(viewModel: MainViewModel) { contentPadding = PaddingValues(horizontal = 20.dp, vertical = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - item { - Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { - Text( - "SETTINGS", - style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp), - color = mobileAccent, - ) - Text("Device Configuration", style = mobileTitle2, color = mobileText) - Text( - "Manage capabilities, permissions, and diagnostics.", - style = mobileCallout, - color = mobileTextSecondary, - ) - } - } - item { HorizontalDivider(color = mobileBorder) } - - // Order parity: Node → Voice → Camera → Messaging → Location → Screen. + // ── Node ── item { Text( - "NODE", - style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp), - color = mobileAccent, - ) - } - item { - OutlinedTextField( - value = displayName, - onValueChange = viewModel::setDisplayName, - label = { Text("Name", style = mobileCaption1, color = mobileTextSecondary) }, - modifier = Modifier.fillMaxWidth(), - textStyle = mobileBody.copy(color = mobileText), - colors = settingsTextFieldColors(), - ) - } - item { Text("Instance ID: $instanceId", style = mobileCallout.copy(fontFamily = FontFamily.Monospace), color = mobileTextSecondary) } - item { Text("Device: $deviceModel", style = mobileCallout, color = mobileTextSecondary) } - item { Text("Version: $appVersion", style = mobileCallout, color = mobileTextSecondary) } - - item { HorizontalDivider(color = mobileBorder) } - - // Voice - item { - Text( - "VOICE", + "DEVICE", style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp), color = mobileAccent, ) } item { - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Microphone permission", style = mobileHeadline) }, - supportingContent = { + Column(modifier = Modifier.settingsRowModifier()) { + OutlinedTextField( + value = displayName, + onValueChange = viewModel::setDisplayName, + label = { Text("Name", style = mobileCaption1, color = mobileTextSecondary) }, + modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 10.dp), + textStyle = mobileBody.copy(color = mobileText), + colors = settingsTextFieldColors(), + ) + HorizontalDivider(color = mobileBorder) + Column( + modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Text("$deviceModel · $appVersion", style = mobileCallout, color = mobileTextSecondary) Text( - if (micPermissionGranted) { - "Granted. Use the Voice tab mic button to capture transcript while the app is open." - } else { - "Required for foreground Voice tab transcription." - }, - style = mobileCallout, + instanceId.take(8) + "…", + style = mobileCaption1.copy(fontFamily = FontFamily.Monospace), + color = mobileTextTertiary, ) - }, - trailingContent = { - Button( - onClick = { - if (micPermissionGranted) { - openAppSettings(context) - } else { - audioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) - } - }, - colors = settingsPrimaryButtonColors(), - shape = RoundedCornerShape(14.dp), - ) { - Text( - if (micPermissionGranted) "Manage" else "Grant", - style = mobileCallout.copy(fontWeight = FontWeight.Bold), - ) - } - }, - ) - } - item { - Text( - "Voice wake and talk modes were removed. Voice now uses one mic on/off flow in the Voice tab while the app is open.", - style = mobileCallout, - color = mobileTextSecondary, - ) - } - - item { HorizontalDivider(color = mobileBorder) } - - // Camera - item { - Text( - "CAMERA", - style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp), - color = mobileAccent, - ) - } - item { - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Allow Camera", style = mobileHeadline) }, - supportingContent = { Text("Allows the gateway to request photos or short video clips (foreground only).", style = mobileCallout) }, - trailingContent = { Switch(checked = cameraEnabled, onCheckedChange = ::setCameraEnabledChecked) }, - ) - } - item { - Text( - "Tip: grant Microphone permission for video clips with audio.", - style = mobileCallout, - color = mobileTextSecondary, - ) - } - - item { HorizontalDivider(color = mobileBorder) } - - // Messaging - item { - Text( - "MESSAGING", - style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp), - color = mobileAccent, - ) - } - item { - val buttonLabel = - when { - !smsPermissionAvailable -> "Unavailable" - smsPermissionGranted -> "Manage" - else -> "Grant" + } } - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("SMS Permission", style = mobileHeadline) }, - supportingContent = { - Text( - if (smsPermissionAvailable) { - "Allow the gateway to send SMS from this device." - } else { - "SMS requires a device with telephony hardware." + } + + // ── Media ── + item { + Text( + "MEDIA", + style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp), + color = mobileAccent, + ) + } + item { + Column(modifier = Modifier.settingsRowModifier()) { + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Microphone", style = mobileHeadline) }, + supportingContent = { + Text( + if (micPermissionGranted) "Granted" else "Required for voice transcription.", + style = mobileCallout, + ) }, - style = mobileCallout, - ) - }, - trailingContent = { - Button( - onClick = { - if (!smsPermissionAvailable) return@Button - if (smsPermissionGranted) { - openAppSettings(context) - } else { - smsPermissionLauncher.launch(Manifest.permission.SEND_SMS) + trailingContent = { + Button( + onClick = { + if (micPermissionGranted) { + openAppSettings(context) + } else { + audioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) + } + }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text( + if (micPermissionGranted) "Manage" else "Grant", + style = mobileCallout.copy(fontWeight = FontWeight.Bold), + ) } }, - enabled = smsPermissionAvailable, - colors = settingsPrimaryButtonColors(), - shape = RoundedCornerShape(14.dp), - ) { - Text(buttonLabel, style = mobileCallout.copy(fontWeight = FontWeight.Bold)) - } - }, - ) - } + ) + HorizontalDivider(color = mobileBorder) + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Camera", style = mobileHeadline) }, + supportingContent = { Text("Photos and video clips (foreground only).", style = mobileCallout) }, + trailingContent = { Switch(checked = cameraEnabled, onCheckedChange = ::setCameraEnabledChecked) }, + ) + } + } - item { HorizontalDivider(color = mobileBorder) } - - // Notifications + // ── Notifications & Messaging ── item { Text( "NOTIFICATIONS", @@ -526,67 +458,87 @@ fun SettingsSheet(viewModel: MainViewModel) { ) } item { - val buttonLabel = - if (notificationsPermissionGranted) { - "Manage" - } else { - "Grant" - } - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("System Notifications", style = mobileHeadline) }, - supportingContent = { - Text( - "Required for `system.notify` and Android foreground service alerts.", - style = mobileCallout, - ) - }, - trailingContent = { - Button( - onClick = { - if (notificationsPermissionGranted || Build.VERSION.SDK_INT < 33) { - openAppSettings(context) - } else { - notificationsPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + Column(modifier = Modifier.settingsRowModifier()) { + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("System Notifications", style = mobileHeadline) }, + supportingContent = { + Text("Alerts and foreground service.", style = mobileCallout) + }, + trailingContent = { + Button( + onClick = { + if (notificationsPermissionGranted || Build.VERSION.SDK_INT < 33) { + openAppSettings(context) + } else { + notificationsPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text( + if (notificationsPermissionGranted) "Manage" else "Grant", + style = mobileCallout.copy(fontWeight = FontWeight.Bold), + ) + } + }, + ) + HorizontalDivider(color = mobileBorder) + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Notification Listener", style = mobileHeadline) }, + supportingContent = { + Text("Read and interact with notifications.", style = mobileCallout) + }, + trailingContent = { + Button( + onClick = { openNotificationListenerSettings(context) }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text( + if (notificationListenerEnabled) "Manage" else "Enable", + style = mobileCallout.copy(fontWeight = FontWeight.Bold), + ) + } + }, + ) + if (smsPermissionAvailable) { + HorizontalDivider(color = mobileBorder) + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("SMS", style = mobileHeadline) }, + supportingContent = { + Text("Send and search SMS from this device.", style = mobileCallout) + }, + trailingContent = { + Button( + onClick = { + if (smsPermissionGranted) { + openAppSettings(context) + } else { + smsPermissionLauncher.launch(arrayOf(Manifest.permission.SEND_SMS, Manifest.permission.READ_SMS)) + } + }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text( + if (smsPermissionGranted) "Manage" else "Grant", + style = mobileCallout.copy(fontWeight = FontWeight.Bold), + ) } }, - colors = settingsPrimaryButtonColors(), - shape = RoundedCornerShape(14.dp), - ) { - Text(buttonLabel, style = mobileCallout.copy(fontWeight = FontWeight.Bold)) - } - }, - ) - } - item { - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Notification Listener Access", style = mobileHeadline) }, - supportingContent = { - Text( - "Required for `notifications.list` and `notifications.actions`.", - style = mobileCallout, ) - }, - trailingContent = { - Button( - onClick = { openNotificationListenerSettings(context) }, - colors = settingsPrimaryButtonColors(), - shape = RoundedCornerShape(14.dp), - ) { - Text( - if (notificationListenerEnabled) "Manage" else "Enable", - style = mobileCallout.copy(fontWeight = FontWeight.Bold), - ) - } - }, - ) + } + } } - item { HorizontalDivider(color = mobileBorder) } - // Data access + // ── Data Access ── item { Text( "DATA ACCESS", @@ -595,142 +547,140 @@ fun SettingsSheet(viewModel: MainViewModel) { ) } item { - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Photos Permission", style = mobileHeadline) }, - supportingContent = { - Text( - "Required for `photos.latest`.", - style = mobileCallout, - ) - }, - trailingContent = { - Button( - onClick = { - if (photosPermissionGranted) { - openAppSettings(context) - } else { - photosPermissionLauncher.launch(photosPermission) + Column(modifier = Modifier.settingsRowModifier()) { + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Photos", style = mobileHeadline) }, + supportingContent = { Text("Access recent photos.", style = mobileCallout) }, + trailingContent = { + Button( + onClick = { + if (photosPermissionGranted) { + openAppSettings(context) + } else { + photosPermissionLauncher.launch(photosPermission) + } + }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text( + if (photosPermissionGranted) "Manage" else "Grant", + style = mobileCallout.copy(fontWeight = FontWeight.Bold), + ) + } + }, + ) + HorizontalDivider(color = mobileBorder) + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Contacts", style = mobileHeadline) }, + supportingContent = { Text("Search and add contacts.", style = mobileCallout) }, + trailingContent = { + Button( + onClick = { + if (contactsPermissionGranted) { + openAppSettings(context) + } else { + contactsPermissionLauncher.launch(arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) + } + }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text( + if (contactsPermissionGranted) "Manage" else "Grant", + style = mobileCallout.copy(fontWeight = FontWeight.Bold), + ) + } + }, + ) + HorizontalDivider(color = mobileBorder) + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Calendar", style = mobileHeadline) }, + supportingContent = { Text("Read and create events.", style = mobileCallout) }, + trailingContent = { + Button( + onClick = { + if (calendarPermissionGranted) { + openAppSettings(context) + } else { + calendarPermissionLauncher.launch(arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR)) + } + }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text( + if (calendarPermissionGranted) "Manage" else "Grant", + style = mobileCallout.copy(fontWeight = FontWeight.Bold), + ) + } + }, + ) + HorizontalDivider(color = mobileBorder) + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Call Log", style = mobileHeadline) }, + supportingContent = { Text("Search recent call history.", style = mobileCallout) }, + trailingContent = { + Button( + onClick = { + if (callLogPermissionGranted) { + openAppSettings(context) + } else { + callLogPermissionLauncher.launch(Manifest.permission.READ_CALL_LOG) + } + }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text( + if (callLogPermissionGranted) "Manage" else "Grant", + style = mobileCallout.copy(fontWeight = FontWeight.Bold), + ) + } + }, + ) + if (motionAvailable) { + HorizontalDivider(color = mobileBorder) + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Motion", style = mobileHeadline) }, + supportingContent = { Text("Track steps and activity.", style = mobileCallout) }, + trailingContent = { + val motionButtonLabel = + when { + !motionPermissionRequired -> "Manage" + motionPermissionGranted -> "Manage" + else -> "Grant" + } + Button( + onClick = { + if (!motionPermissionRequired || motionPermissionGranted) { + openAppSettings(context) + } else { + motionPermissionLauncher.launch(Manifest.permission.ACTIVITY_RECOGNITION) + } + }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text(motionButtonLabel, style = mobileCallout.copy(fontWeight = FontWeight.Bold)) } }, - colors = settingsPrimaryButtonColors(), - shape = RoundedCornerShape(14.dp), - ) { - Text( - if (photosPermissionGranted) "Manage" else "Grant", - style = mobileCallout.copy(fontWeight = FontWeight.Bold), - ) - } - }, - ) - } - item { - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Contacts Permission", style = mobileHeadline) }, - supportingContent = { - Text( - "Required for `contacts.search` and `contacts.add`.", - style = mobileCallout, ) - }, - trailingContent = { - Button( - onClick = { - if (contactsPermissionGranted) { - openAppSettings(context) - } else { - contactsPermissionLauncher.launch(arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) - } - }, - colors = settingsPrimaryButtonColors(), - shape = RoundedCornerShape(14.dp), - ) { - Text( - if (contactsPermissionGranted) "Manage" else "Grant", - style = mobileCallout.copy(fontWeight = FontWeight.Bold), - ) - } - }, - ) - } - item { - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Calendar Permission", style = mobileHeadline) }, - supportingContent = { - Text( - "Required for `calendar.events` and `calendar.add`.", - style = mobileCallout, - ) - }, - trailingContent = { - Button( - onClick = { - if (calendarPermissionGranted) { - openAppSettings(context) - } else { - calendarPermissionLauncher.launch(arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR)) - } - }, - colors = settingsPrimaryButtonColors(), - shape = RoundedCornerShape(14.dp), - ) { - Text( - if (calendarPermissionGranted) "Manage" else "Grant", - style = mobileCallout.copy(fontWeight = FontWeight.Bold), - ) - } - }, - ) - } - item { - val motionButtonLabel = - when { - !motionAvailable -> "Unavailable" - !motionPermissionRequired -> "Manage" - motionPermissionGranted -> "Manage" - else -> "Grant" } - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Motion Permission", style = mobileHeadline) }, - supportingContent = { - Text( - if (!motionAvailable) { - "This device does not expose accelerometer or step-counter motion sensors." - } else { - "Required for `motion.activity` and `motion.pedometer`." - }, - style = mobileCallout, - ) - }, - trailingContent = { - Button( - onClick = { - if (!motionAvailable) return@Button - if (!motionPermissionRequired || motionPermissionGranted) { - openAppSettings(context) - } else { - motionPermissionLauncher.launch(Manifest.permission.ACTIVITY_RECOGNITION) - } - }, - enabled = motionAvailable, - colors = settingsPrimaryButtonColors(), - shape = RoundedCornerShape(14.dp), - ) { - Text(motionButtonLabel, style = mobileCallout.copy(fontWeight = FontWeight.Bold)) - } - }, - ) + } } - item { HorizontalDivider(color = mobileBorder) } - // Location + // ── Location ── item { Text( "LOCATION", @@ -739,7 +689,7 @@ fun SettingsSheet(viewModel: MainViewModel) { ) } item { - Column(modifier = Modifier.settingsRowModifier(), verticalArrangement = Arrangement.spacedBy(0.dp)) { + Column(modifier = Modifier.settingsRowModifier()) { ListItem( modifier = Modifier.fillMaxWidth(), colors = listItemColors, @@ -781,50 +731,39 @@ fun SettingsSheet(viewModel: MainViewModel) { ) } } - item { HorizontalDivider(color = mobileBorder) } - // Screen + // ── Preferences ── item { Text( - "SCREEN", + "PREFERENCES", style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp), color = mobileAccent, ) } - item { - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Prevent Sleep", style = mobileHeadline) }, - supportingContent = { Text("Keeps the screen awake while OpenClaw is open.", style = mobileCallout) }, - trailingContent = { Switch(checked = preventSleep, onCheckedChange = viewModel::setPreventSleep) }, - ) - } - - item { HorizontalDivider(color = mobileBorder) } - - // Debug item { - Text( - "DEBUG", - style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp), - color = mobileAccent, - ) - } - item { - ListItem( - modifier = Modifier.settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Debug Canvas Status", style = mobileHeadline) }, - supportingContent = { Text("Show status text in the canvas when debug is enabled.", style = mobileCallout) }, - trailingContent = { - Switch( - checked = canvasDebugStatusEnabled, - onCheckedChange = viewModel::setCanvasDebugStatusEnabled, + Column(modifier = Modifier.settingsRowModifier()) { + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Prevent Sleep", style = mobileHeadline) }, + supportingContent = { Text("Keep screen awake while open.", style = mobileCallout) }, + trailingContent = { Switch(checked = preventSleep, onCheckedChange = viewModel::setPreventSleep) }, ) - }, - ) - } + HorizontalDivider(color = mobileBorder) + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Debug Canvas", style = mobileHeadline) }, + supportingContent = { Text("Show status overlay on canvas.", style = mobileCallout) }, + trailingContent = { + Switch( + checked = canvasDebugStatusEnabled, + onCheckedChange = viewModel::setCanvasDebugStatusEnabled, + ) + }, + ) + } + } item { Spacer(modifier = Modifier.height(24.dp)) } } @@ -843,11 +782,12 @@ private fun settingsTextFieldColors() = cursorColor = mobileAccent, ) +@Composable private fun Modifier.settingsRowModifier() = this .fillMaxWidth() .border(width = 1.dp, color = mobileBorder, shape = RoundedCornerShape(14.dp)) - .background(Color.White, RoundedCornerShape(14.dp)) + .background(mobileCardSurface, RoundedCornerShape(14.dp)) @Composable private fun settingsPrimaryButtonColors() = @@ -888,7 +828,7 @@ private fun openNotificationListenerSettings(context: Context) { private fun hasNotificationsPermission(context: Context): Boolean { if (Build.VERSION.SDK_INT < 33) return true return ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == - PackageManager.PERMISSION_GRANTED + PackageManager.PERMISSION_GRANTED } private fun isNotificationListenerEnabled(context: Context): Boolean { @@ -898,5 +838,5 @@ private fun isNotificationListenerEnabled(context: Context): Boolean { private fun hasMotionCapabilities(context: Context): Boolean { val sensorManager = context.getSystemService(SensorManager::class.java) ?: return false return sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null || - sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) != null + sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) != null } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt index be66f42bef3..76fc2c4f0c9 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt @@ -17,10 +17,12 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding @@ -212,19 +214,26 @@ fun VoiceTabScreen(viewModel: MainViewModel) { verticalAlignment = Alignment.CenterVertically, ) { // Speaker toggle - IconButton( - onClick = { viewModel.setSpeakerEnabled(!speakerEnabled) }, - modifier = Modifier.size(48.dp), - colors = - IconButtonDefaults.iconButtonColors( - containerColor = if (speakerEnabled) mobileSurface else mobileDangerSoft, - ), - ) { - Icon( - imageVector = if (speakerEnabled) Icons.AutoMirrored.Filled.VolumeUp else Icons.AutoMirrored.Filled.VolumeOff, - contentDescription = if (speakerEnabled) "Mute speaker" else "Unmute speaker", - modifier = Modifier.size(22.dp), - tint = if (speakerEnabled) mobileTextSecondary else mobileDanger, + Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp)) { + IconButton( + onClick = { viewModel.setSpeakerEnabled(!speakerEnabled) }, + modifier = Modifier.size(48.dp), + colors = + IconButtonDefaults.iconButtonColors( + containerColor = if (speakerEnabled) mobileSurface else mobileDangerSoft, + ), + ) { + Icon( + imageVector = if (speakerEnabled) Icons.AutoMirrored.Filled.VolumeUp else Icons.AutoMirrored.Filled.VolumeOff, + contentDescription = if (speakerEnabled) "Mute speaker" else "Unmute speaker", + modifier = Modifier.size(22.dp), + tint = if (speakerEnabled) mobileTextSecondary else mobileDanger, + ) + } + Text( + if (speakerEnabled) "Speaker" else "Muted", + style = mobileCaption2, + color = if (speakerEnabled) mobileTextTertiary else mobileDanger, ) } @@ -278,8 +287,12 @@ fun VoiceTabScreen(viewModel: MainViewModel) { } } - // Invisible spacer to balance the row (same size as speaker button) - Box(modifier = Modifier.size(48.dp)) + // Invisible spacer to balance the row (matches speaker column width) + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Box(modifier = Modifier.size(48.dp)) + Spacer(modifier = Modifier.height(4.dp)) + Text("", style = mobileCaption2) + } } // Status + labels @@ -292,11 +305,24 @@ fun VoiceTabScreen(viewModel: MainViewModel) { micEnabled -> "Listening" else -> "Mic off" } - Text( - "$gatewayStatus · $stateText", - style = mobileCaption1, - color = mobileTextSecondary, - ) + val stateColor = + when { + micEnabled -> mobileSuccess + micIsSending -> mobileAccent + else -> mobileTextSecondary + } + Surface( + shape = RoundedCornerShape(999.dp), + color = if (micEnabled) mobileSuccessSoft else mobileSurface, + border = BorderStroke(1.dp, if (micEnabled) mobileSuccess.copy(alpha = 0.3f) else mobileBorder), + ) { + Text( + "$gatewayStatus · $stateText", + style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), + color = stateColor, + modifier = Modifier.padding(horizontal = 14.dp, vertical = 6.dp), + ) + } if (!hasMicPermission) { val showRationale = @@ -337,7 +363,7 @@ private fun VoiceTurnBubble(entry: VoiceConversationEntry) { Surface( modifier = Modifier.fillMaxWidth(0.90f), shape = RoundedCornerShape(12.dp), - color = if (isUser) mobileAccentSoft else Color.White, + color = if (isUser) mobileAccentSoft else mobileCardSurface, border = BorderStroke(1.dp, if (isUser) mobileAccent else mobileBorderStrong), ) { Column( @@ -365,7 +391,7 @@ private fun VoiceThinkingBubble() { Surface( modifier = Modifier.fillMaxWidth(0.68f), shape = RoundedCornerShape(12.dp), - color = Color.White, + color = mobileCardSurface, border = BorderStroke(1.dp, mobileBorderStrong), ) { Row( diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/Base64ImageState.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/Base64ImageState.kt index b2b540bdb7a..8180d24bbed 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/Base64ImageState.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/Base64ImageState.kt @@ -1,7 +1,5 @@ package ai.openclaw.app.ui.chat -import android.graphics.BitmapFactory -import android.util.Base64 import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -28,8 +26,7 @@ internal fun rememberBase64ImageState(base64: String): Base64ImageState { image = withContext(Dispatchers.Default) { try { - val bytes = Base64.decode(base64, Base64.DEFAULT) - val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return@withContext null + val bitmap = decodeBase64Bitmap(base64) ?: return@withContext null bitmap.asImageBitmap() } catch (_: Throwable) { null diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatComposer.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatComposer.kt index 9601febfa31..1adcc34c2d6 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatComposer.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatComposer.kt @@ -26,7 +26,6 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField @@ -47,11 +46,13 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import ai.openclaw.app.ui.mobileAccent +import ai.openclaw.app.ui.mobileAccentBorderStrong import ai.openclaw.app.ui.mobileAccentSoft import ai.openclaw.app.ui.mobileBorder import ai.openclaw.app.ui.mobileBorderStrong import ai.openclaw.app.ui.mobileCallout import ai.openclaw.app.ui.mobileCaption1 +import ai.openclaw.app.ui.mobileCardSurface import ai.openclaw.app.ui.mobileHeadline import ai.openclaw.app.ui.mobileSurface import ai.openclaw.app.ui.mobileText @@ -78,65 +79,15 @@ fun ChatComposer( val sendBusy = pendingRunCount > 0 Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Box(modifier = Modifier.weight(1f)) { - Surface( - onClick = { showThinkingMenu = true }, - shape = RoundedCornerShape(14.dp), - color = mobileAccentSoft, - border = BorderStroke(1.dp, mobileBorderStrong), - ) { - Row( - modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Text( - text = "Thinking: ${thinkingLabel(thinkingLevel)}", - style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), - color = mobileText, - ) - Icon(Icons.Default.ArrowDropDown, contentDescription = "Select thinking level", tint = mobileTextSecondary) - } - } - - DropdownMenu(expanded = showThinkingMenu, onDismissRequest = { showThinkingMenu = false }) { - ThinkingMenuItem("off", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false } - ThinkingMenuItem("low", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false } - ThinkingMenuItem("medium", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false } - ThinkingMenuItem("high", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false } - } - } - - SecondaryActionButton( - label = "Attach", - icon = Icons.Default.AttachFile, - enabled = true, - onClick = onPickImages, - ) - } - if (attachments.isNotEmpty()) { AttachmentsStrip(attachments = attachments, onRemoveAttachment = onRemoveAttachment) } - HorizontalDivider(color = mobileBorder) - - Text( - text = "MESSAGE", - style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 0.9.sp), - color = mobileTextSecondary, - ) - OutlinedTextField( value = input, onValueChange = { input = it }, - modifier = Modifier.fillMaxWidth().height(92.dp), - placeholder = { Text("Type a message", style = mobileBodyStyle(), color = mobileTextTertiary) }, + modifier = Modifier.fillMaxWidth(), + placeholder = { Text("Type a message…", style = mobileBodyStyle(), color = mobileTextTertiary) }, minLines = 2, maxLines = 5, textStyle = mobileBodyStyle().copy(color = mobileText), @@ -155,26 +106,70 @@ fun ChatComposer( Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - SecondaryActionButton( - label = "Refresh", - icon = Icons.Default.Refresh, - enabled = true, - compact = true, - onClick = onRefresh, - ) + Box { + Surface( + onClick = { showThinkingMenu = true }, + shape = RoundedCornerShape(14.dp), + color = mobileCardSurface, + border = BorderStroke(1.dp, mobileBorderStrong), + ) { + Row( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = thinkingLabel(thinkingLevel), + style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), + color = mobileTextSecondary, + ) + Icon(Icons.Default.ArrowDropDown, contentDescription = "Select thinking level", modifier = Modifier.size(18.dp), tint = mobileTextTertiary) + } + } - SecondaryActionButton( - label = "Abort", - icon = Icons.Default.Stop, - enabled = pendingRunCount > 0, - compact = true, - onClick = onAbort, - ) + DropdownMenu( + expanded = showThinkingMenu, + onDismissRequest = { showThinkingMenu = false }, + shape = RoundedCornerShape(16.dp), + containerColor = mobileCardSurface, + tonalElevation = 0.dp, + shadowElevation = 8.dp, + border = BorderStroke(1.dp, mobileBorder), + ) { + ThinkingMenuItem("off", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false } + ThinkingMenuItem("low", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false } + ThinkingMenuItem("medium", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false } + ThinkingMenuItem("high", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false } + } } + SecondaryActionButton( + label = "Attach", + icon = Icons.Default.AttachFile, + enabled = true, + compact = true, + onClick = onPickImages, + ) + + SecondaryActionButton( + label = "Refresh", + icon = Icons.Default.Refresh, + enabled = true, + compact = true, + onClick = onRefresh, + ) + + SecondaryActionButton( + label = "Abort", + icon = Icons.Default.Stop, + enabled = pendingRunCount > 0, + compact = true, + onClick = onAbort, + ) + + Spacer(modifier = Modifier.weight(1f)) + Button( onClick = { val text = input @@ -182,8 +177,9 @@ fun ChatComposer( onSend(text) }, enabled = canSend, - modifier = Modifier.weight(1f).height(48.dp), + modifier = Modifier.height(44.dp), shape = RoundedCornerShape(14.dp), + contentPadding = PaddingValues(horizontal = 20.dp), colors = ButtonDefaults.buttonColors( containerColor = mobileAccent, @@ -191,14 +187,14 @@ fun ChatComposer( disabledContainerColor = mobileBorderStrong, disabledContentColor = mobileTextTertiary, ), - border = BorderStroke(1.dp, if (canSend) Color(0xFF154CAD) else mobileBorderStrong), + border = BorderStroke(1.dp, if (canSend) mobileAccentBorderStrong else mobileBorderStrong), ) { if (sendBusy) { CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = Color.White) } else { Icon(Icons.AutoMirrored.Filled.Send, contentDescription = null, modifier = Modifier.size(16.dp)) } - Spacer(modifier = Modifier.width(8.dp)) + Spacer(modifier = Modifier.width(6.dp)) Text( text = "Send", style = mobileHeadline.copy(fontWeight = FontWeight.Bold), @@ -225,9 +221,9 @@ private fun SecondaryActionButton( shape = RoundedCornerShape(14.dp), colors = ButtonDefaults.buttonColors( - containerColor = Color.White, + containerColor = mobileCardSurface, contentColor = mobileTextSecondary, - disabledContainerColor = Color.White, + disabledContainerColor = mobileCardSurface, disabledContentColor = mobileTextTertiary, ), border = BorderStroke(1.dp, mobileBorderStrong), @@ -317,7 +313,7 @@ private fun AttachmentChip(fileName: String, onRemove: () -> Unit) { Surface( onClick = onRemove, shape = RoundedCornerShape(999.dp), - color = Color.White, + color = mobileCardSurface, border = BorderStroke(1.dp, mobileBorderStrong), ) { Text( diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatImageCodec.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatImageCodec.kt new file mode 100644 index 00000000000..6574fa8678d --- /dev/null +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatImageCodec.kt @@ -0,0 +1,150 @@ +package ai.openclaw.app.ui.chat + +import android.content.ContentResolver +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.util.Base64 +import android.util.LruCache +import androidx.core.graphics.scale +import ai.openclaw.app.node.JpegSizeLimiter +import java.io.ByteArrayOutputStream +import kotlin.math.max +import kotlin.math.roundToInt + +private const val CHAT_ATTACHMENT_MAX_WIDTH = 1600 +private const val CHAT_ATTACHMENT_MAX_BASE64_CHARS = 300 * 1024 +private const val CHAT_ATTACHMENT_START_QUALITY = 85 +private const val CHAT_DECODE_MAX_DIMENSION = 1600 +private const val CHAT_IMAGE_CACHE_BYTES = 16 * 1024 * 1024 + +private val decodedBitmapCache = + object : LruCache(CHAT_IMAGE_CACHE_BYTES) { + override fun sizeOf(key: String, value: Bitmap): Int = value.byteCount.coerceAtLeast(1) + } + +internal fun loadSizedImageAttachment(resolver: ContentResolver, uri: Uri): PendingImageAttachment { + val fileName = normalizeAttachmentFileName((uri.lastPathSegment ?: "image").substringAfterLast('/')) + val bitmap = decodeScaledBitmap(resolver, uri, maxDimension = CHAT_ATTACHMENT_MAX_WIDTH) + if (bitmap == null) { + throw IllegalStateException("unsupported attachment") + } + val maxBytes = (CHAT_ATTACHMENT_MAX_BASE64_CHARS / 4) * 3 + val encoded = + JpegSizeLimiter.compressToLimit( + initialWidth = bitmap.width, + initialHeight = bitmap.height, + startQuality = CHAT_ATTACHMENT_START_QUALITY, + maxBytes = maxBytes, + minSize = 240, + encode = { width, height, quality -> + val working = + if (width == bitmap.width && height == bitmap.height) { + bitmap + } else { + bitmap.scale(width, height, true) + } + try { + val out = ByteArrayOutputStream() + if (!working.compress(Bitmap.CompressFormat.JPEG, quality, out)) { + throw IllegalStateException("attachment encode failed") + } + out.toByteArray() + } finally { + if (working !== bitmap) { + working.recycle() + } + } + }, + ) + val base64 = Base64.encodeToString(encoded.bytes, Base64.NO_WRAP) + return PendingImageAttachment( + id = uri.toString() + "#" + System.currentTimeMillis().toString(), + fileName = fileName, + mimeType = "image/jpeg", + base64 = base64, + ) +} + +internal fun decodeBase64Bitmap(base64: String, maxDimension: Int = CHAT_DECODE_MAX_DIMENSION): Bitmap? { + val cacheKey = "$maxDimension:${base64.length}:${base64.hashCode()}" + decodedBitmapCache.get(cacheKey)?.let { return it } + + val bytes = Base64.decode(base64, Base64.DEFAULT) + if (bytes.isEmpty()) return null + + val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeByteArray(bytes, 0, bytes.size, bounds) + if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return null + + val bitmap = + BitmapFactory.decodeByteArray( + bytes, + 0, + bytes.size, + BitmapFactory.Options().apply { + inSampleSize = computeInSampleSize(bounds.outWidth, bounds.outHeight, maxDimension) + inPreferredConfig = Bitmap.Config.RGB_565 + }, + ) ?: return null + + decodedBitmapCache.put(cacheKey, bitmap) + return bitmap +} + +internal fun computeInSampleSize(width: Int, height: Int, maxDimension: Int): Int { + if (width <= 0 || height <= 0 || maxDimension <= 0) return 1 + + var sample = 1 + var longestEdge = max(width, height) + while (longestEdge > maxDimension && sample < 64) { + sample *= 2 + longestEdge = max(width / sample, height / sample) + } + return sample.coerceAtLeast(1) +} + +internal fun normalizeAttachmentFileName(raw: String): String { + val trimmed = raw.trim() + if (trimmed.isEmpty()) return "image.jpg" + val stem = trimmed.substringBeforeLast('.', missingDelimiterValue = trimmed).ifEmpty { "image" } + return "$stem.jpg" +} + +private fun decodeScaledBitmap( + resolver: ContentResolver, + uri: Uri, + maxDimension: Int, +): Bitmap? { + val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true } + resolver.openInputStream(uri).use { input -> + if (input == null) return null + BitmapFactory.decodeStream(input, null, bounds) + } + if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return null + + val decoded = + resolver.openInputStream(uri).use { input -> + if (input == null) return null + BitmapFactory.decodeStream( + input, + null, + BitmapFactory.Options().apply { + inSampleSize = computeInSampleSize(bounds.outWidth, bounds.outHeight, maxDimension) + inPreferredConfig = Bitmap.Config.ARGB_8888 + }, + ) + } ?: return null + + val longestEdge = max(decoded.width, decoded.height) + if (longestEdge <= maxDimension) return decoded + + val scale = maxDimension.toDouble() / longestEdge.toDouble() + val targetWidth = max(1, (decoded.width * scale).roundToInt()) + val targetHeight = max(1, (decoded.height * scale).roundToInt()) + val scaled = decoded.scale(targetWidth, targetHeight, true) + if (scaled !== decoded) { + decoded.recycle() + } + return scaled +} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMarkdown.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMarkdown.kt index a8f932d8607..0d49ec4278f 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMarkdown.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMarkdown.kt @@ -94,7 +94,7 @@ private val markdownParser: Parser by lazy { @Composable fun ChatMarkdown(text: String, textColor: Color) { val document = remember(text) { markdownParser.parse(text) as Document } - val inlineStyles = InlineStyles(inlineCodeBg = mobileCodeBg, inlineCodeColor = mobileCodeText) + val inlineStyles = InlineStyles(inlineCodeBg = mobileCodeBg, inlineCodeColor = mobileCodeText, linkColor = mobileAccent, baseCallout = mobileCallout) Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { RenderMarkdownBlocks( @@ -124,7 +124,7 @@ private fun RenderMarkdownBlocks( val headingText = remember(current) { buildInlineMarkdown(current.firstChild, inlineStyles) } Text( text = headingText, - style = headingStyle(current.level), + style = headingStyle(current.level, inlineStyles.baseCallout), color = textColor, ) } @@ -231,7 +231,7 @@ private fun RenderParagraph( Text( text = annotated, - style = mobileCallout, + style = inlineStyles.baseCallout, color = textColor, ) } @@ -315,7 +315,7 @@ private fun RenderListItem( ) { Text( text = marker, - style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), + style = inlineStyles.baseCallout.copy(fontWeight = FontWeight.SemiBold), color = textColor, modifier = Modifier.width(24.dp), ) @@ -360,7 +360,7 @@ private fun RenderTableBlock( val cell = row.cells.getOrNull(index) ?: AnnotatedString("") Text( text = cell, - style = if (row.isHeader) mobileCaption1.copy(fontWeight = FontWeight.SemiBold) else mobileCallout, + style = if (row.isHeader) mobileCaption1.copy(fontWeight = FontWeight.SemiBold) else inlineStyles.baseCallout, color = textColor, modifier = Modifier .border(1.dp, mobileTextSecondary.copy(alpha = 0.22f)) @@ -417,6 +417,7 @@ private fun buildInlineMarkdown(start: Node?, inlineStyles: InlineStyles): Annot node = start, inlineCodeBg = inlineStyles.inlineCodeBg, inlineCodeColor = inlineStyles.inlineCodeColor, + linkColor = inlineStyles.linkColor, ) } } @@ -425,6 +426,7 @@ private fun AnnotatedString.Builder.appendInlineNode( node: Node?, inlineCodeBg: Color, inlineCodeColor: Color, + linkColor: Color, ) { var current = node while (current != null) { @@ -445,27 +447,27 @@ private fun AnnotatedString.Builder.appendInlineNode( } is Emphasis -> { withStyle(SpanStyle(fontStyle = FontStyle.Italic)) { - appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor) + appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor) } } is StrongEmphasis -> { withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) { - appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor) + appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor) } } is Strikethrough -> { withStyle(SpanStyle(textDecoration = TextDecoration.LineThrough)) { - appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor) + appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor) } } is Link -> { withStyle( SpanStyle( - color = mobileAccent, + color = linkColor, textDecoration = TextDecoration.Underline, ), ) { - appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor) + appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor) } } is MarkdownImage -> { @@ -482,7 +484,7 @@ private fun AnnotatedString.Builder.appendInlineNode( } } else -> { - appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor) + appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor) } } current = current.next @@ -519,19 +521,21 @@ private fun parseDataImageDestination(destination: String?): ParsedDataImage? { return ParsedDataImage(mimeType = "image/$subtype", base64 = base64) } -private fun headingStyle(level: Int): TextStyle { +private fun headingStyle(level: Int, baseCallout: TextStyle): TextStyle { return when (level.coerceIn(1, 6)) { - 1 -> mobileCallout.copy(fontSize = 22.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold) - 2 -> mobileCallout.copy(fontSize = 20.sp, lineHeight = 26.sp, fontWeight = FontWeight.Bold) - 3 -> mobileCallout.copy(fontSize = 18.sp, lineHeight = 24.sp, fontWeight = FontWeight.SemiBold) - 4 -> mobileCallout.copy(fontSize = 16.sp, lineHeight = 22.sp, fontWeight = FontWeight.SemiBold) - else -> mobileCallout.copy(fontWeight = FontWeight.SemiBold) + 1 -> baseCallout.copy(fontSize = 22.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold) + 2 -> baseCallout.copy(fontSize = 20.sp, lineHeight = 26.sp, fontWeight = FontWeight.Bold) + 3 -> baseCallout.copy(fontSize = 18.sp, lineHeight = 24.sp, fontWeight = FontWeight.SemiBold) + 4 -> baseCallout.copy(fontSize = 16.sp, lineHeight = 22.sp, fontWeight = FontWeight.SemiBold) + else -> baseCallout.copy(fontWeight = FontWeight.SemiBold) } } private data class InlineStyles( val inlineCodeBg: Color, val inlineCodeColor: Color, + val linkColor: Color, + val baseCallout: TextStyle, ) private data class TableRenderRow( diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageListCard.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageListCard.kt index 0c34ff0d763..96d5e7cf7f6 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageListCard.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageListCard.kt @@ -6,12 +6,14 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -19,6 +21,7 @@ import ai.openclaw.app.chat.ChatMessage import ai.openclaw.app.chat.ChatPendingToolCall import ai.openclaw.app.ui.mobileBorder import ai.openclaw.app.ui.mobileCallout +import ai.openclaw.app.ui.mobileCardSurface import ai.openclaw.app.ui.mobileHeadline import ai.openclaw.app.ui.mobileText import ai.openclaw.app.ui.mobileTextSecondary @@ -33,11 +36,19 @@ fun ChatMessageListCard( modifier: Modifier = Modifier, ) { val listState = rememberLazyListState() + val displayMessages = remember(messages) { messages.asReversed() } + val stream = streamingAssistantText?.trim() - // With reverseLayout the newest item is at index 0 (bottom of screen). - LaunchedEffect(messages.size, pendingRunCount, pendingToolCalls.size, streamingAssistantText) { + // New list items/tool rows should animate into view, but token streaming should not restart + // that animation on every delta. + LaunchedEffect(messages.size, pendingRunCount, pendingToolCalls.size) { listState.animateScrollToItem(index = 0) } + LaunchedEffect(stream) { + if (!stream.isNullOrEmpty()) { + listState.scrollToItem(index = 0) + } + } Box(modifier = modifier.fillMaxWidth()) { LazyColumn( @@ -49,8 +60,6 @@ fun ChatMessageListCard( ) { // With reverseLayout = true, index 0 renders at the BOTTOM. // So we emit newest items first: streaming → tools → typing → messages (newest→oldest). - - val stream = streamingAssistantText?.trim() if (!stream.isNullOrEmpty()) { item(key = "stream") { ChatStreamingAssistantBubble(text = stream) @@ -69,8 +78,8 @@ fun ChatMessageListCard( } } - items(count = messages.size, key = { idx -> messages[messages.size - 1 - idx].id }) { idx -> - ChatMessageBubble(message = messages[messages.size - 1 - idx]) + items(items = displayMessages, key = { it.id }) { message -> + ChatMessageBubble(message = message) } } @@ -85,7 +94,7 @@ private fun EmptyChatHint(modifier: Modifier = Modifier, healthOk: Boolean) { Surface( modifier = modifier.fillMaxWidth(), shape = RoundedCornerShape(14.dp), - color = androidx.compose.ui.graphics.Color.White.copy(alpha = 0.9f), + color = mobileCardSurface.copy(alpha = 0.9f), border = androidx.compose.foundation.BorderStroke(1.dp, mobileBorder), ) { androidx.compose.foundation.layout.Column( diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt index 9d08352a3f0..5d09d37a43f 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt @@ -36,7 +36,9 @@ import ai.openclaw.app.ui.mobileBorderStrong import ai.openclaw.app.ui.mobileCallout import ai.openclaw.app.ui.mobileCaption1 import ai.openclaw.app.ui.mobileCaption2 +import ai.openclaw.app.ui.mobileCardSurface import ai.openclaw.app.ui.mobileCodeBg +import ai.openclaw.app.ui.mobileCodeBorder import ai.openclaw.app.ui.mobileCodeText import ai.openclaw.app.ui.mobileHeadline import ai.openclaw.app.ui.mobileText @@ -151,7 +153,7 @@ fun ChatPendingToolsBubble(toolCalls: List) { ChatBubbleContainer( style = bubbleStyle("assistant"), - roleLabel = "TOOLS", + roleLabel = "Tools", ) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { Text("Running tools...", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) @@ -188,12 +190,13 @@ fun ChatPendingToolsBubble(toolCalls: List) { fun ChatStreamingAssistantBubble(text: String) { ChatBubbleContainer( style = bubbleStyle("assistant").copy(borderColor = mobileAccent), - roleLabel = "ASSISTANT · LIVE", + roleLabel = "OpenClaw · Live", ) { ChatMarkdown(text = text, textColor = mobileText) } } +@Composable private fun bubbleStyle(role: String): ChatBubbleStyle { return when (role) { "user" -> @@ -215,7 +218,7 @@ private fun bubbleStyle(role: String): ChatBubbleStyle { else -> ChatBubbleStyle( alignEnd = false, - containerColor = Color.White, + containerColor = mobileCardSurface, borderColor = mobileBorderStrong, roleColor = mobileTextSecondary, ) @@ -224,9 +227,9 @@ private fun bubbleStyle(role: String): ChatBubbleStyle { private fun roleLabel(role: String): String { return when (role) { - "user" -> "USER" - "system" -> "SYSTEM" - else -> "ASSISTANT" + "user" -> "You" + "system" -> "System" + else -> "OpenClaw" } } @@ -239,7 +242,7 @@ private fun ChatBase64Image(base64: String, mimeType: String?) { Surface( shape = RoundedCornerShape(10.dp), border = BorderStroke(1.dp, mobileBorder), - color = Color.White, + color = mobileCardSurface, modifier = Modifier.fillMaxWidth(), ) { Image( @@ -277,7 +280,7 @@ fun ChatCodeBlock(code: String, language: String?) { Surface( shape = RoundedCornerShape(8.dp), color = mobileCodeBg, - border = BorderStroke(1.dp, Color(0xFF2B2E35)), + border = BorderStroke(1.dp, mobileCodeBorder), modifier = Modifier.fillMaxWidth(), ) { Column(modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatSheetContent.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatSheetContent.kt index 2c09f4488b0..2d8fb255baa 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatSheetContent.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatSheetContent.kt @@ -1,8 +1,5 @@ package ai.openclaw.app.ui.chat -import android.content.ContentResolver -import android.net.Uri -import android.util.Base64 import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.BorderStroke @@ -36,19 +33,17 @@ import ai.openclaw.app.MainViewModel import ai.openclaw.app.chat.ChatSessionEntry import ai.openclaw.app.chat.OutgoingAttachment import ai.openclaw.app.ui.mobileAccent +import ai.openclaw.app.ui.mobileAccentBorderStrong import ai.openclaw.app.ui.mobileBorder import ai.openclaw.app.ui.mobileBorderStrong import ai.openclaw.app.ui.mobileCallout +import ai.openclaw.app.ui.mobileCardSurface import ai.openclaw.app.ui.mobileCaption1 import ai.openclaw.app.ui.mobileCaption2 import ai.openclaw.app.ui.mobileDanger -import ai.openclaw.app.ui.mobileSuccess -import ai.openclaw.app.ui.mobileSuccessSoft +import ai.openclaw.app.ui.mobileDangerSoft import ai.openclaw.app.ui.mobileText import ai.openclaw.app.ui.mobileTextSecondary -import ai.openclaw.app.ui.mobileWarning -import ai.openclaw.app.ui.mobileWarningSoft -import java.io.ByteArrayOutputStream import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -84,7 +79,7 @@ fun ChatSheetContent(viewModel: MainViewModel) { val next = uris.take(8).mapNotNull { uri -> try { - loadImageAttachment(resolver, uri) + loadSizedImageAttachment(resolver, uri) } catch (_: Throwable) { null } @@ -106,7 +101,6 @@ fun ChatSheetContent(viewModel: MainViewModel) { sessionKey = sessionKey, sessions = sessions, mainSessionKey = mainSessionKey, - healthOk = healthOk, onSelectSession = { key -> viewModel.switchChatSession(key) }, ) @@ -160,77 +154,37 @@ private fun ChatThreadSelector( sessionKey: String, sessions: List, mainSessionKey: String, - healthOk: Boolean, onSelectSession: (String) -> Unit, ) { - val sessionOptions = resolveSessionChoices(sessionKey, sessions, mainSessionKey = mainSessionKey) - val currentSessionLabel = - friendlySessionName(sessionOptions.firstOrNull { it.key == sessionKey }?.displayName ?: sessionKey) + val sessionOptions = + remember(sessionKey, sessions, mainSessionKey) { + resolveSessionChoices(sessionKey, sessions, mainSessionKey = mainSessionKey) + } - Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, - ) { - Text( - text = "SESSION", - style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 0.8.sp), - color = mobileTextSecondary, - ) - Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + Row( + modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + for (entry in sessionOptions) { + val active = entry.key == sessionKey + Surface( + onClick = { onSelectSession(entry.key) }, + shape = RoundedCornerShape(14.dp), + color = if (active) mobileAccent else mobileCardSurface, + border = BorderStroke(1.dp, if (active) mobileAccentBorderStrong else mobileBorderStrong), + tonalElevation = 0.dp, + shadowElevation = 0.dp, + ) { Text( - text = currentSessionLabel, - style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), - color = mobileText, + text = friendlySessionName(entry.displayName ?: entry.key), + style = mobileCaption1.copy(fontWeight = if (active) FontWeight.Bold else FontWeight.SemiBold), + color = if (active) Color.White else mobileText, maxLines = 1, overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), ) - ChatConnectionPill(healthOk = healthOk) } } - - Row( - modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - for (entry in sessionOptions) { - val active = entry.key == sessionKey - Surface( - onClick = { onSelectSession(entry.key) }, - shape = RoundedCornerShape(14.dp), - color = if (active) mobileAccent else Color.White, - border = BorderStroke(1.dp, if (active) Color(0xFF154CAD) else mobileBorderStrong), - tonalElevation = 0.dp, - shadowElevation = 0.dp, - ) { - Text( - text = friendlySessionName(entry.displayName ?: entry.key), - style = mobileCaption1.copy(fontWeight = if (active) FontWeight.Bold else FontWeight.SemiBold), - color = if (active) Color.White else mobileText, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), - ) - } - } - } - } -} - -@Composable -private fun ChatConnectionPill(healthOk: Boolean) { - Surface( - shape = RoundedCornerShape(999.dp), - color = if (healthOk) mobileSuccessSoft else mobileWarningSoft, - border = BorderStroke(1.dp, if (healthOk) mobileSuccess.copy(alpha = 0.35f) else mobileWarning.copy(alpha = 0.35f)), - ) { - Text( - text = if (healthOk) "Connected" else "Offline", - style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), - color = if (healthOk) mobileSuccess else mobileWarning, - modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp), - ) } } @@ -238,7 +192,7 @@ private fun ChatConnectionPill(healthOk: Boolean) { private fun ChatErrorRail(errorText: String) { Surface( modifier = Modifier.fillMaxWidth(), - color = androidx.compose.ui.graphics.Color.White, + color = mobileDangerSoft, shape = RoundedCornerShape(12.dp), border = androidx.compose.foundation.BorderStroke(1.dp, mobileDanger), ) { @@ -259,24 +213,3 @@ data class PendingImageAttachment( val mimeType: String, val base64: String, ) - -private suspend fun loadImageAttachment(resolver: ContentResolver, uri: Uri): PendingImageAttachment { - val mimeType = resolver.getType(uri) ?: "image/*" - val fileName = (uri.lastPathSegment ?: "image").substringAfterLast('/') - val bytes = - withContext(Dispatchers.IO) { - resolver.openInputStream(uri)?.use { input -> - val out = ByteArrayOutputStream() - input.copyTo(out) - out.toByteArray() - } ?: ByteArray(0) - } - if (bytes.isEmpty()) throw IllegalStateException("empty attachment") - val base64 = Base64.encodeToString(bytes, Base64.NO_WRAP) - return PendingImageAttachment( - id = uri.toString() + "#" + System.currentTimeMillis().toString(), - fileName = fileName, - mimeType = mimeType, - base64 = base64, - ) -} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/voice/TalkModeVoiceResolver.kt b/apps/android/app/src/main/java/ai/openclaw/app/voice/TalkModeVoiceResolver.kt index eff52017624..7ada19e166b 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/voice/TalkModeVoiceResolver.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/voice/TalkModeVoiceResolver.kt @@ -79,26 +79,30 @@ internal object TalkModeVoiceResolver { return withContext(Dispatchers.IO) { val url = URL("https://api.elevenlabs.io/v1/voices") val conn = url.openConnection() as HttpURLConnection - conn.requestMethod = "GET" - conn.connectTimeout = 15_000 - conn.readTimeout = 15_000 - conn.setRequestProperty("xi-api-key", apiKey) + try { + conn.requestMethod = "GET" + conn.connectTimeout = 15_000 + conn.readTimeout = 15_000 + conn.setRequestProperty("xi-api-key", apiKey) - val code = conn.responseCode - val stream = if (code >= 400) conn.errorStream else conn.inputStream - val data = stream.readBytes() - if (code >= 400) { - val message = data.toString(Charsets.UTF_8) - throw IllegalStateException("ElevenLabs voices failed: $code $message") - } + val code = conn.responseCode + val stream = if (code >= 400) conn.errorStream else conn.inputStream + val data = stream?.use { it.readBytes() } ?: byteArrayOf() + if (code >= 400) { + val message = data.toString(Charsets.UTF_8) + throw IllegalStateException("ElevenLabs voices failed: $code $message") + } - val root = json.parseToJsonElement(data.toString(Charsets.UTF_8)).asObjectOrNull() - val voices = (root?.get("voices") as? JsonArray) ?: JsonArray(emptyList()) - voices.mapNotNull { entry -> - val obj = entry.asObjectOrNull() ?: return@mapNotNull null - val voiceId = obj["voice_id"].asStringOrNull() ?: return@mapNotNull null - val name = obj["name"].asStringOrNull() - ElevenLabsVoice(voiceId, name) + val root = json.parseToJsonElement(data.toString(Charsets.UTF_8)).asObjectOrNull() + val voices = (root?.get("voices") as? JsonArray) ?: JsonArray(emptyList()) + voices.mapNotNull { entry -> + val obj = entry.asObjectOrNull() ?: return@mapNotNull null + val voiceId = obj["voice_id"].asStringOrNull() ?: return@mapNotNull null + val name = obj["name"].asStringOrNull() + ElevenLabsVoice(voiceId, name) + } + } finally { + conn.disconnect() } } } diff --git a/apps/android/app/src/main/res/values-night/themes.xml b/apps/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000000..4f55d0b8cfc --- /dev/null +++ b/apps/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,8 @@ + + + + diff --git a/apps/android/app/src/test/java/ai/openclaw/app/SecurePrefsTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/SecurePrefsTest.kt index cd72bf75dff..1ef860e29b4 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/SecurePrefsTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/SecurePrefsTest.kt @@ -20,4 +20,19 @@ class SecurePrefsTest { assertEquals(LocationMode.WhileUsing, prefs.locationMode.value) assertEquals("whileUsing", plainPrefs.getString("location.enabledMode", null)) } + + @Test + fun saveGatewayBootstrapToken_persistsSeparatelyFromSharedToken() { + val context = RuntimeEnvironment.getApplication() + val securePrefs = context.getSharedPreferences("openclaw.node.secure.test", Context.MODE_PRIVATE) + securePrefs.edit().clear().commit() + val prefs = SecurePrefs(context, securePrefsOverride = securePrefs) + + prefs.setGatewayToken("shared-token") + prefs.setGatewayBootstrapToken("bootstrap-token") + + assertEquals("shared-token", prefs.loadGatewayToken()) + assertEquals("bootstrap-token", prefs.loadGatewayBootstrapToken()) + assertEquals("bootstrap-token", prefs.gatewayBootstrapToken.value) + } } diff --git a/apps/android/app/src/test/java/ai/openclaw/app/chat/ChatControllerMessageIdentityTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/chat/ChatControllerMessageIdentityTest.kt new file mode 100644 index 00000000000..936bd526eb8 --- /dev/null +++ b/apps/android/app/src/test/java/ai/openclaw/app/chat/ChatControllerMessageIdentityTest.kt @@ -0,0 +1,81 @@ +package ai.openclaw.app.chat + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Test + +class ChatControllerMessageIdentityTest { + @Test + fun reconcileMessageIdsReusesMatchingIdsAcrossHistoryReload() { + val previous = + listOf( + ChatMessage( + id = "msg-1", + role = "assistant", + content = listOf(ChatMessageContent(type = "text", text = "hello")), + timestampMs = 1000L, + ), + ChatMessage( + id = "msg-2", + role = "user", + content = listOf(ChatMessageContent(type = "text", text = "hi")), + timestampMs = 2000L, + ), + ) + + val incoming = + listOf( + ChatMessage( + id = "new-1", + role = "assistant", + content = listOf(ChatMessageContent(type = "text", text = "hello")), + timestampMs = 1000L, + ), + ChatMessage( + id = "new-2", + role = "user", + content = listOf(ChatMessageContent(type = "text", text = "hi")), + timestampMs = 2000L, + ), + ) + + val reconciled = reconcileMessageIds(previous = previous, incoming = incoming) + + assertEquals(listOf("msg-1", "msg-2"), reconciled.map { it.id }) + } + + @Test + fun reconcileMessageIdsLeavesNewMessagesUntouched() { + val previous = + listOf( + ChatMessage( + id = "msg-1", + role = "assistant", + content = listOf(ChatMessageContent(type = "text", text = "hello")), + timestampMs = 1000L, + ), + ) + + val incoming = + listOf( + ChatMessage( + id = "new-1", + role = "assistant", + content = listOf(ChatMessageContent(type = "text", text = "hello")), + timestampMs = 1000L, + ), + ChatMessage( + id = "new-2", + role = "assistant", + content = listOf(ChatMessageContent(type = "text", text = "new reply")), + timestampMs = 3000L, + ), + ) + + val reconciled = reconcileMessageIds(previous = previous, incoming = incoming) + + assertEquals("msg-1", reconciled[0].id) + assertEquals("new-2", reconciled[1].id) + assertNotEquals(reconciled[0].id, reconciled[1].id) + } +} diff --git a/apps/android/app/src/test/java/ai/openclaw/app/gateway/GatewaySessionInvokeTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/gateway/GatewaySessionInvokeTest.kt index a3f301498c8..2cfa1be4866 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/gateway/GatewaySessionInvokeTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/gateway/GatewaySessionInvokeTest.kt @@ -27,6 +27,7 @@ import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config +import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference private const val TEST_TIMEOUT_MS = 8_000L @@ -41,11 +42,16 @@ private class InMemoryDeviceAuthStore : DeviceAuthTokenStore { override fun saveToken(deviceId: String, role: String, token: String) { tokens["${deviceId.trim()}|${role.trim()}"] = token.trim() } + + override fun clearToken(deviceId: String, role: String) { + tokens.remove("${deviceId.trim()}|${role.trim()}") + } } private data class NodeHarness( val session: GatewaySession, val sessionJob: Job, + val deviceAuthStore: InMemoryDeviceAuthStore, ) private data class InvokeScenarioResult( @@ -56,6 +62,157 @@ private data class InvokeScenarioResult( @RunWith(RobolectricTestRunner::class) @Config(sdk = [34]) class GatewaySessionInvokeTest { + @Test + fun connect_usesBootstrapTokenWhenSharedAndDeviceTokensAreAbsent() = runBlocking { + val json = testJson() + val connected = CompletableDeferred() + val connectAuth = CompletableDeferred() + val lastDisconnect = AtomicReference("") + val server = + startGatewayServer(json) { webSocket, id, method, frame -> + when (method) { + "connect" -> { + if (!connectAuth.isCompleted) { + connectAuth.complete(frame["params"]?.jsonObject?.get("auth")?.jsonObject) + } + webSocket.send(connectResponseFrame(id)) + webSocket.close(1000, "done") + } + } + } + + val harness = + createNodeHarness( + connected = connected, + lastDisconnect = lastDisconnect, + ) { GatewaySession.InvokeResult.ok("""{"handled":true}""") } + + try { + connectNodeSession( + session = harness.session, + port = server.port, + token = null, + bootstrapToken = "bootstrap-token", + ) + awaitConnectedOrThrow(connected, lastDisconnect, server) + + val auth = withTimeout(TEST_TIMEOUT_MS) { connectAuth.await() } + assertEquals("bootstrap-token", auth?.get("bootstrapToken")?.jsonPrimitive?.content) + assertNull(auth?.get("token")) + } finally { + shutdownHarness(harness, server) + } + } + + @Test + fun connect_prefersStoredDeviceTokenOverBootstrapToken() = runBlocking { + val json = testJson() + val connected = CompletableDeferred() + val connectAuth = CompletableDeferred() + val lastDisconnect = AtomicReference("") + val server = + startGatewayServer(json) { webSocket, id, method, frame -> + when (method) { + "connect" -> { + if (!connectAuth.isCompleted) { + connectAuth.complete(frame["params"]?.jsonObject?.get("auth")?.jsonObject) + } + webSocket.send(connectResponseFrame(id)) + webSocket.close(1000, "done") + } + } + } + + val harness = + createNodeHarness( + connected = connected, + lastDisconnect = lastDisconnect, + ) { GatewaySession.InvokeResult.ok("""{"handled":true}""") } + + try { + val deviceId = DeviceIdentityStore(RuntimeEnvironment.getApplication()).loadOrCreate().deviceId + harness.deviceAuthStore.saveToken(deviceId, "node", "device-token") + + connectNodeSession( + session = harness.session, + port = server.port, + token = null, + bootstrapToken = "bootstrap-token", + ) + awaitConnectedOrThrow(connected, lastDisconnect, server) + + val auth = withTimeout(TEST_TIMEOUT_MS) { connectAuth.await() } + assertEquals("device-token", auth?.get("token")?.jsonPrimitive?.content) + assertNull(auth?.get("bootstrapToken")) + } finally { + shutdownHarness(harness, server) + } + } + + @Test + fun connect_retriesWithStoredDeviceTokenAfterSharedTokenMismatch() = runBlocking { + val json = testJson() + val connected = CompletableDeferred() + val firstConnectAuth = CompletableDeferred() + val secondConnectAuth = CompletableDeferred() + val connectAttempts = AtomicInteger(0) + val lastDisconnect = AtomicReference("") + val server = + startGatewayServer(json) { webSocket, id, method, frame -> + when (method) { + "connect" -> { + val auth = frame["params"]?.jsonObject?.get("auth")?.jsonObject + when (connectAttempts.incrementAndGet()) { + 1 -> { + if (!firstConnectAuth.isCompleted) { + firstConnectAuth.complete(auth) + } + webSocket.send( + """{"type":"res","id":"$id","ok":false,"error":{"code":"INVALID_REQUEST","message":"unauthorized","details":{"code":"AUTH_TOKEN_MISMATCH","canRetryWithDeviceToken":true,"recommendedNextStep":"retry_with_device_token"}}}""", + ) + webSocket.close(1000, "retry") + } + else -> { + if (!secondConnectAuth.isCompleted) { + secondConnectAuth.complete(auth) + } + webSocket.send(connectResponseFrame(id)) + webSocket.close(1000, "done") + } + } + } + } + } + + val harness = + createNodeHarness( + connected = connected, + lastDisconnect = lastDisconnect, + ) { GatewaySession.InvokeResult.ok("""{"handled":true}""") } + + try { + val deviceId = DeviceIdentityStore(RuntimeEnvironment.getApplication()).loadOrCreate().deviceId + harness.deviceAuthStore.saveToken(deviceId, "node", "stored-device-token") + + connectNodeSession( + session = harness.session, + port = server.port, + token = "shared-auth-token", + bootstrapToken = null, + ) + awaitConnectedOrThrow(connected, lastDisconnect, server) + + val firstAuth = withTimeout(TEST_TIMEOUT_MS) { firstConnectAuth.await() } + val secondAuth = withTimeout(TEST_TIMEOUT_MS) { secondConnectAuth.await() } + assertEquals("shared-auth-token", firstAuth?.get("token")?.jsonPrimitive?.content) + assertNull(firstAuth?.get("deviceToken")) + assertEquals("shared-auth-token", secondAuth?.get("token")?.jsonPrimitive?.content) + assertEquals("stored-device-token", secondAuth?.get("deviceToken")?.jsonPrimitive?.content) + } finally { + shutdownHarness(harness, server) + } + } + @Test fun nodeInvokeRequest_roundTripsInvokeResult() = runBlocking { val handshakeOrigin = AtomicReference(null) @@ -182,11 +339,12 @@ class GatewaySessionInvokeTest { ): NodeHarness { val app = RuntimeEnvironment.getApplication() val sessionJob = SupervisorJob() + val deviceAuthStore = InMemoryDeviceAuthStore() val session = GatewaySession( scope = CoroutineScope(sessionJob + Dispatchers.Default), identityStore = DeviceIdentityStore(app), - deviceAuthStore = InMemoryDeviceAuthStore(), + deviceAuthStore = deviceAuthStore, onConnected = { _, _, _ -> if (!connected.isCompleted) connected.complete(Unit) }, @@ -197,10 +355,15 @@ class GatewaySessionInvokeTest { onInvoke = onInvoke, ) - return NodeHarness(session = session, sessionJob = sessionJob) + return NodeHarness(session = session, sessionJob = sessionJob, deviceAuthStore = deviceAuthStore) } - private suspend fun connectNodeSession(session: GatewaySession, port: Int) { + private suspend fun connectNodeSession( + session: GatewaySession, + port: Int, + token: String? = "test-token", + bootstrapToken: String? = null, + ) { session.connect( endpoint = GatewayEndpoint( @@ -210,7 +373,8 @@ class GatewaySessionInvokeTest { port = port, tlsEnabled = false, ), - token = "test-token", + token = token, + bootstrapToken = bootstrapToken, password = null, options = GatewayConnectOptions( diff --git a/apps/android/app/src/test/java/ai/openclaw/app/node/CallLogHandlerTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/node/CallLogHandlerTest.kt new file mode 100644 index 00000000000..21f4f7dd82a --- /dev/null +++ b/apps/android/app/src/test/java/ai/openclaw/app/node/CallLogHandlerTest.kt @@ -0,0 +1,193 @@ +package ai.openclaw.app.node + +import android.content.Context +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class CallLogHandlerTest : NodeHandlerRobolectricTest() { + @Test + fun handleCallLogSearch_requiresPermission() { + val handler = CallLogHandler.forTesting(appContext(), FakeCallLogDataSource(canRead = false)) + + val result = handler.handleCallLogSearch(null) + + assertFalse(result.ok) + assertEquals("CALL_LOG_PERMISSION_REQUIRED", result.error?.code) + } + + @Test + fun handleCallLogSearch_rejectsInvalidJson() { + val handler = CallLogHandler.forTesting(appContext(), FakeCallLogDataSource(canRead = true)) + + val result = handler.handleCallLogSearch("invalid json") + + assertFalse(result.ok) + assertEquals("INVALID_REQUEST", result.error?.code) + } + + @Test + fun handleCallLogSearch_returnsCallLogs() { + val callLog = + CallLogRecord( + number = "+123456", + cachedName = "lixuankai", + date = 1709280000000L, + duration = 60L, + type = 1, + ) + val handler = + CallLogHandler.forTesting( + appContext(), + FakeCallLogDataSource(canRead = true, searchResults = listOf(callLog)), + ) + + val result = handler.handleCallLogSearch("""{"limit":1}""") + + assertTrue(result.ok) + val payload = Json.parseToJsonElement(result.payloadJson ?: error("missing payload")).jsonObject + val callLogs = payload.getValue("callLogs").jsonArray + assertEquals(1, callLogs.size) + assertEquals("+123456", callLogs.first().jsonObject.getValue("number").jsonPrimitive.content) + assertEquals("lixuankai", callLogs.first().jsonObject.getValue("cachedName").jsonPrimitive.content) + assertEquals(1709280000000L, callLogs.first().jsonObject.getValue("date").jsonPrimitive.content.toLong()) + assertEquals(60L, callLogs.first().jsonObject.getValue("duration").jsonPrimitive.content.toLong()) + assertEquals(1, callLogs.first().jsonObject.getValue("type").jsonPrimitive.content.toInt()) + } + + @Test + fun handleCallLogSearch_withFilters() { + val callLog = + CallLogRecord( + number = "+123456", + cachedName = "lixuankai", + date = 1709280000000L, + duration = 120L, + type = 2, + ) + val handler = + CallLogHandler.forTesting( + appContext(), + FakeCallLogDataSource(canRead = true, searchResults = listOf(callLog)), + ) + + val result = handler.handleCallLogSearch( + """{"number":"123456","cachedName":"lixuankai","dateStart":1709270000000,"dateEnd":1709290000000,"duration":120,"type":2}""" + ) + + assertTrue(result.ok) + val payload = Json.parseToJsonElement(result.payloadJson ?: error("missing payload")).jsonObject + val callLogs = payload.getValue("callLogs").jsonArray + assertEquals(1, callLogs.size) + assertEquals("lixuankai", callLogs.first().jsonObject.getValue("cachedName").jsonPrimitive.content) + } + + @Test + fun handleCallLogSearch_withPagination() { + val callLogs = + listOf( + CallLogRecord( + number = "+123456", + cachedName = "lixuankai", + date = 1709280000000L, + duration = 60L, + type = 1, + ), + CallLogRecord( + number = "+654321", + cachedName = "lixuankai2", + date = 1709280001000L, + duration = 120L, + type = 2, + ), + ) + val handler = + CallLogHandler.forTesting( + appContext(), + FakeCallLogDataSource(canRead = true, searchResults = callLogs), + ) + + val result = handler.handleCallLogSearch("""{"limit":1,"offset":1}""") + + assertTrue(result.ok) + val payload = Json.parseToJsonElement(result.payloadJson ?: error("missing payload")).jsonObject + val callLogsResult = payload.getValue("callLogs").jsonArray + assertEquals(1, callLogsResult.size) + assertEquals("lixuankai2", callLogsResult.first().jsonObject.getValue("cachedName").jsonPrimitive.content) + } + + @Test + fun handleCallLogSearch_withDefaultParams() { + val callLog = + CallLogRecord( + number = "+123456", + cachedName = "lixuankai", + date = 1709280000000L, + duration = 60L, + type = 1, + ) + val handler = + CallLogHandler.forTesting( + appContext(), + FakeCallLogDataSource(canRead = true, searchResults = listOf(callLog)), + ) + + val result = handler.handleCallLogSearch(null) + + assertTrue(result.ok) + val payload = Json.parseToJsonElement(result.payloadJson ?: error("missing payload")).jsonObject + val callLogs = payload.getValue("callLogs").jsonArray + assertEquals(1, callLogs.size) + assertEquals("+123456", callLogs.first().jsonObject.getValue("number").jsonPrimitive.content) + } + + @Test + fun handleCallLogSearch_withNullFields() { + val callLog = + CallLogRecord( + number = null, + cachedName = null, + date = 1709280000000L, + duration = 60L, + type = 1, + ) + val handler = + CallLogHandler.forTesting( + appContext(), + FakeCallLogDataSource(canRead = true, searchResults = listOf(callLog)), + ) + + val result = handler.handleCallLogSearch("""{"limit":1}""") + + assertTrue(result.ok) + val payload = Json.parseToJsonElement(result.payloadJson ?: error("missing payload")).jsonObject + val callLogs = payload.getValue("callLogs").jsonArray + assertEquals(1, callLogs.size) + // Verify null values are properly serialized + val callLogObj = callLogs.first().jsonObject + assertTrue(callLogObj.containsKey("number")) + assertTrue(callLogObj.containsKey("cachedName")) + } +} + +private class FakeCallLogDataSource( + private val canRead: Boolean, + private val searchResults: List = emptyList(), +) : CallLogDataSource { + override fun hasReadPermission(context: Context): Boolean = canRead + + override fun search(context: Context, request: CallLogSearchRequest): List { + val startIndex = request.offset.coerceAtLeast(0) + val endIndex = (startIndex + request.limit).coerceAtMost(searchResults.size) + return if (startIndex < searchResults.size) { + searchResults.subList(startIndex, endIndex) + } else { + emptyList() + } + } +} diff --git a/apps/android/app/src/test/java/ai/openclaw/app/node/DeviceHandlerTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/node/DeviceHandlerTest.kt index e40e2b164ae..1bce95748e0 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/node/DeviceHandlerTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/node/DeviceHandlerTest.kt @@ -93,6 +93,7 @@ class DeviceHandlerTest { "photos", "contacts", "calendar", + "callLog", "motion", ) for (key in expected) { diff --git a/apps/android/app/src/test/java/ai/openclaw/app/node/InvokeCommandRegistryTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/node/InvokeCommandRegistryTest.kt index d3825a5720e..29decd2f76d 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/node/InvokeCommandRegistryTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/node/InvokeCommandRegistryTest.kt @@ -2,6 +2,7 @@ package ai.openclaw.app.node import ai.openclaw.app.protocol.OpenClawCalendarCommand import ai.openclaw.app.protocol.OpenClawCameraCommand +import ai.openclaw.app.protocol.OpenClawCallLogCommand import ai.openclaw.app.protocol.OpenClawCapability import ai.openclaw.app.protocol.OpenClawContactsCommand import ai.openclaw.app.protocol.OpenClawDeviceCommand @@ -25,6 +26,7 @@ class InvokeCommandRegistryTest { OpenClawCapability.Photos.rawValue, OpenClawCapability.Contacts.rawValue, OpenClawCapability.Calendar.rawValue, + OpenClawCapability.CallLog.rawValue, ) private val optionalCapabilities = @@ -50,6 +52,7 @@ class InvokeCommandRegistryTest { OpenClawContactsCommand.Add.rawValue, OpenClawCalendarCommand.Events.rawValue, OpenClawCalendarCommand.Add.rawValue, + OpenClawCallLogCommand.Search.rawValue, ) private val optionalCommands = @@ -61,6 +64,7 @@ class InvokeCommandRegistryTest { OpenClawMotionCommand.Activity.rawValue, OpenClawMotionCommand.Pedometer.rawValue, OpenClawSmsCommand.Send.rawValue, + OpenClawSmsCommand.Search.rawValue, ) private val debugCommands = setOf("debug.logs", "debug.ed25519") @@ -80,7 +84,8 @@ class InvokeCommandRegistryTest { defaultFlags( cameraEnabled = true, locationEnabled = true, - smsAvailable = true, + sendSmsAvailable = true, + readSmsAvailable = true, voiceWakeEnabled = true, motionActivityAvailable = true, motionPedometerAvailable = true, @@ -105,7 +110,8 @@ class InvokeCommandRegistryTest { defaultFlags( cameraEnabled = true, locationEnabled = true, - smsAvailable = true, + sendSmsAvailable = true, + readSmsAvailable = true, motionActivityAvailable = true, motionPedometerAvailable = true, debugBuild = true, @@ -122,7 +128,8 @@ class InvokeCommandRegistryTest { NodeRuntimeFlags( cameraEnabled = false, locationEnabled = false, - smsAvailable = false, + sendSmsAvailable = false, + readSmsAvailable = false, voiceWakeEnabled = false, motionActivityAvailable = true, motionPedometerAvailable = false, @@ -134,10 +141,43 @@ class InvokeCommandRegistryTest { assertFalse(commands.contains(OpenClawMotionCommand.Pedometer.rawValue)) } + @Test + fun advertisedCommands_splitsSmsSendAndSearchAvailability() { + val readOnlyCommands = + InvokeCommandRegistry.advertisedCommands( + defaultFlags(readSmsAvailable = true), + ) + val sendOnlyCommands = + InvokeCommandRegistry.advertisedCommands( + defaultFlags(sendSmsAvailable = true), + ) + + assertTrue(readOnlyCommands.contains(OpenClawSmsCommand.Search.rawValue)) + assertFalse(readOnlyCommands.contains(OpenClawSmsCommand.Send.rawValue)) + assertTrue(sendOnlyCommands.contains(OpenClawSmsCommand.Send.rawValue)) + assertFalse(sendOnlyCommands.contains(OpenClawSmsCommand.Search.rawValue)) + } + + @Test + fun advertisedCapabilities_includeSmsWhenEitherSmsPathIsAvailable() { + val readOnlyCapabilities = + InvokeCommandRegistry.advertisedCapabilities( + defaultFlags(readSmsAvailable = true), + ) + val sendOnlyCapabilities = + InvokeCommandRegistry.advertisedCapabilities( + defaultFlags(sendSmsAvailable = true), + ) + + assertTrue(readOnlyCapabilities.contains(OpenClawCapability.Sms.rawValue)) + assertTrue(sendOnlyCapabilities.contains(OpenClawCapability.Sms.rawValue)) + } + private fun defaultFlags( cameraEnabled: Boolean = false, locationEnabled: Boolean = false, - smsAvailable: Boolean = false, + sendSmsAvailable: Boolean = false, + readSmsAvailable: Boolean = false, voiceWakeEnabled: Boolean = false, motionActivityAvailable: Boolean = false, motionPedometerAvailable: Boolean = false, @@ -146,7 +186,8 @@ class InvokeCommandRegistryTest { NodeRuntimeFlags( cameraEnabled = cameraEnabled, locationEnabled = locationEnabled, - smsAvailable = smsAvailable, + sendSmsAvailable = sendSmsAvailable, + readSmsAvailable = readSmsAvailable, voiceWakeEnabled = voiceWakeEnabled, motionActivityAvailable = motionActivityAvailable, motionPedometerAvailable = motionPedometerAvailable, diff --git a/apps/android/app/src/test/java/ai/openclaw/app/node/SmsManagerTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/node/SmsManagerTest.kt index c1b98908f08..88c75a40a9a 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/node/SmsManagerTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/node/SmsManagerTest.kt @@ -88,4 +88,95 @@ class SmsManagerTest { assertFalse(plan.useMultipart) assertEquals(listOf("hello"), plan.parts) } + + @Test + fun parseQueryParamsAcceptsEmptyPayload() { + val result = SmsManager.parseQueryParams(null, json) + assertTrue(result is SmsManager.QueryParseResult.Ok) + val ok = result as SmsManager.QueryParseResult.Ok + assertEquals(25, ok.params.limit) + assertEquals(0, ok.params.offset) + } + + @Test + fun parseQueryParamsRejectsInvalidJson() { + val result = SmsManager.parseQueryParams("not-json", json) + assertTrue(result is SmsManager.QueryParseResult.Error) + val error = result as SmsManager.QueryParseResult.Error + assertEquals("INVALID_REQUEST: expected JSON object", error.error) + } + + @Test + fun parseQueryParamsRejectsNonObjectJson() { + val result = SmsManager.parseQueryParams("[]", json) + assertTrue(result is SmsManager.QueryParseResult.Error) + val error = result as SmsManager.QueryParseResult.Error + assertEquals("INVALID_REQUEST: expected JSON object", error.error) + } + + @Test + fun parseQueryParamsParsesLimitAndOffset() { + val result = SmsManager.parseQueryParams("{\"limit\":10,\"offset\":5}", json) + assertTrue(result is SmsManager.QueryParseResult.Ok) + val ok = result as SmsManager.QueryParseResult.Ok + assertEquals(10, ok.params.limit) + assertEquals(5, ok.params.offset) + } + + @Test + fun parseQueryParamsClampsLimitRange() { + val result = SmsManager.parseQueryParams("{\"limit\":300}", json) + assertTrue(result is SmsManager.QueryParseResult.Ok) + val ok = result as SmsManager.QueryParseResult.Ok + assertEquals(200, ok.params.limit) + } + + @Test + fun parseQueryParamsParsesPhoneNumber() { + val result = SmsManager.parseQueryParams("{\"phoneNumber\":\"+1234567890\"}", json) + assertTrue(result is SmsManager.QueryParseResult.Ok) + val ok = result as SmsManager.QueryParseResult.Ok + assertEquals("+1234567890", ok.params.phoneNumber) + } + + @Test + fun parseQueryParamsParsesContactName() { + val result = SmsManager.parseQueryParams("{\"contactName\":\"lixuankai\"}", json) + assertTrue(result is SmsManager.QueryParseResult.Ok) + val ok = result as SmsManager.QueryParseResult.Ok + assertEquals("lixuankai", ok.params.contactName) + } + + @Test + fun parseQueryParamsParsesKeyword() { + val result = SmsManager.parseQueryParams("{\"keyword\":\"test\"}", json) + assertTrue(result is SmsManager.QueryParseResult.Ok) + val ok = result as SmsManager.QueryParseResult.Ok + assertEquals("test", ok.params.keyword) + } + + @Test + fun parseQueryParamsParsesTimeRange() { + val result = SmsManager.parseQueryParams("{\"startTime\":1000,\"endTime\":2000}", json) + assertTrue(result is SmsManager.QueryParseResult.Ok) + val ok = result as SmsManager.QueryParseResult.Ok + assertEquals(1000L, ok.params.startTime) + assertEquals(2000L, ok.params.endTime) + } + + @Test + fun parseQueryParamsParsesType() { + val result = SmsManager.parseQueryParams("{\"type\":1}", json) + assertTrue(result is SmsManager.QueryParseResult.Ok) + val ok = result as SmsManager.QueryParseResult.Ok + assertEquals(1, ok.params.type) + } + + @Test + fun parseQueryParamsParsesReadStatus() { + val result = SmsManager.parseQueryParams("{\"isRead\":true}", json) + assertTrue(result is SmsManager.QueryParseResult.Ok) + val ok = result as SmsManager.QueryParseResult.Ok + assertEquals(true, ok.params.isRead) + } } diff --git a/apps/android/app/src/test/java/ai/openclaw/app/protocol/OpenClawProtocolConstantsTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/protocol/OpenClawProtocolConstantsTest.kt index 8dd844dee83..b30edb80e6f 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/protocol/OpenClawProtocolConstantsTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/protocol/OpenClawProtocolConstantsTest.kt @@ -34,6 +34,7 @@ class OpenClawProtocolConstantsTest { assertEquals("contacts", OpenClawCapability.Contacts.rawValue) assertEquals("calendar", OpenClawCapability.Calendar.rawValue) assertEquals("motion", OpenClawCapability.Motion.rawValue) + assertEquals("callLog", OpenClawCapability.CallLog.rawValue) } @Test @@ -84,4 +85,14 @@ class OpenClawProtocolConstantsTest { assertEquals("motion.activity", OpenClawMotionCommand.Activity.rawValue) assertEquals("motion.pedometer", OpenClawMotionCommand.Pedometer.rawValue) } + + @Test + fun callLogCommandsUseStableStrings() { + assertEquals("callLog.search", OpenClawCallLogCommand.Search.rawValue) + } + + @Test + fun smsCommandsUseStableStrings() { + assertEquals("sms.search", OpenClawSmsCommand.Search.rawValue) + } } diff --git a/apps/android/app/src/test/java/ai/openclaw/app/ui/GatewayConfigResolverTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/ui/GatewayConfigResolverTest.kt index 72738843ff0..5c24631cf0b 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/ui/GatewayConfigResolverTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/ui/GatewayConfigResolverTest.kt @@ -8,7 +8,8 @@ import org.junit.Test class GatewayConfigResolverTest { @Test fun resolveScannedSetupCodeAcceptsRawSetupCode() { - val setupCode = encodeSetupCode("""{"url":"wss://gateway.example:18789","token":"token-1"}""") + val setupCode = + encodeSetupCode("""{"url":"wss://gateway.example:18789","bootstrapToken":"bootstrap-1"}""") val resolved = resolveScannedSetupCode(setupCode) @@ -17,7 +18,8 @@ class GatewayConfigResolverTest { @Test fun resolveScannedSetupCodeAcceptsQrJsonPayload() { - val setupCode = encodeSetupCode("""{"url":"wss://gateway.example:18789","password":"pw-1"}""") + val setupCode = + encodeSetupCode("""{"url":"wss://gateway.example:18789","bootstrapToken":"bootstrap-1"}""") val qrJson = """ { @@ -53,6 +55,67 @@ class GatewayConfigResolverTest { assertNull(resolved) } + @Test + fun decodeGatewaySetupCodeParsesBootstrapToken() { + val setupCode = + encodeSetupCode("""{"url":"wss://gateway.example:18789","bootstrapToken":"bootstrap-1"}""") + + val decoded = decodeGatewaySetupCode(setupCode) + + assertEquals("wss://gateway.example:18789", decoded?.url) + assertEquals("bootstrap-1", decoded?.bootstrapToken) + assertNull(decoded?.token) + assertNull(decoded?.password) + } + + @Test + fun resolveGatewayConnectConfigPrefersBootstrapTokenFromSetupCode() { + val setupCode = + encodeSetupCode("""{"url":"wss://gateway.example:18789","bootstrapToken":"bootstrap-1"}""") + + val resolved = + resolveGatewayConnectConfig( + useSetupCode = true, + setupCode = setupCode, + manualHost = "", + manualPort = "", + manualTls = true, + fallbackToken = "shared-token", + fallbackPassword = "shared-password", + ) + + assertEquals("gateway.example", resolved?.host) + assertEquals(18789, resolved?.port) + assertEquals(true, resolved?.tls) + assertEquals("bootstrap-1", resolved?.bootstrapToken) + assertNull(resolved?.token?.takeIf { it.isNotEmpty() }) + assertNull(resolved?.password?.takeIf { it.isNotEmpty() }) + } + + @Test + fun resolveGatewayConnectConfigDefaultsPortlessWssSetupCodeTo443() { + val setupCode = + encodeSetupCode("""{"url":"wss://gateway.example","bootstrapToken":"bootstrap-1"}""") + + val resolved = + resolveGatewayConnectConfig( + useSetupCode = true, + setupCode = setupCode, + manualHost = "", + manualPort = "", + manualTls = true, + fallbackToken = "shared-token", + fallbackPassword = "shared-password", + ) + + assertEquals("gateway.example", resolved?.host) + assertEquals(443, resolved?.port) + assertEquals(true, resolved?.tls) + assertEquals("bootstrap-1", resolved?.bootstrapToken) + assertNull(resolved?.token?.takeIf { it.isNotEmpty() }) + assertNull(resolved?.password?.takeIf { it.isNotEmpty() }) + } + private fun encodeSetupCode(payloadJson: String): String { return Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.toByteArray(Charsets.UTF_8)) } diff --git a/apps/android/app/src/test/java/ai/openclaw/app/ui/chat/ChatImageCodecTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/ui/chat/ChatImageCodecTest.kt new file mode 100644 index 00000000000..c3d55e80494 --- /dev/null +++ b/apps/android/app/src/test/java/ai/openclaw/app/ui/chat/ChatImageCodecTest.kt @@ -0,0 +1,18 @@ +package ai.openclaw.app.ui.chat + +import org.junit.Assert.assertEquals +import org.junit.Test + +class ChatImageCodecTest { + @Test + fun computeInSampleSizeCapsLongestEdge() { + assertEquals(4, computeInSampleSize(width = 4032, height = 3024, maxDimension = 1600)) + assertEquals(1, computeInSampleSize(width = 800, height = 600, maxDimension = 1600)) + } + + @Test + fun normalizeAttachmentFileNameForcesJpegExtension() { + assertEquals("photo.jpg", normalizeAttachmentFileName("photo.png")) + assertEquals("image.jpg", normalizeAttachmentFileName("")) + } +} diff --git a/apps/android/scripts/build-release-aab.ts b/apps/android/scripts/build-release-aab.ts new file mode 100644 index 00000000000..30e4bb0390b --- /dev/null +++ b/apps/android/scripts/build-release-aab.ts @@ -0,0 +1,125 @@ +#!/usr/bin/env bun + +import { $ } from "bun"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const androidDir = join(scriptDir, ".."); +const buildGradlePath = join(androidDir, "app", "build.gradle.kts"); +const bundlePath = join(androidDir, "app", "build", "outputs", "bundle", "release", "app-release.aab"); + +type VersionState = { + versionName: string; + versionCode: number; +}; + +type ParsedVersionMatches = { + versionNameMatch: RegExpMatchArray; + versionCodeMatch: RegExpMatchArray; +}; + +function formatVersionName(date: Date): string { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + return `${year}.${month}.${day}`; +} + +function formatVersionCodePrefix(date: Date): string { + const year = date.getFullYear().toString(); + const month = (date.getMonth() + 1).toString().padStart(2, "0"); + const day = date.getDate().toString().padStart(2, "0"); + return `${year}${month}${day}`; +} + +function parseVersionMatches(buildGradleText: string): ParsedVersionMatches { + const versionCodeMatch = buildGradleText.match(/versionCode = (\d+)/); + const versionNameMatch = buildGradleText.match(/versionName = "([^"]+)"/); + if (!versionCodeMatch || !versionNameMatch) { + throw new Error(`Couldn't parse versionName/versionCode from ${buildGradlePath}`); + } + return { versionCodeMatch, versionNameMatch }; +} + +function resolveNextVersionCode(currentVersionCode: number, todayPrefix: string): number { + const currentRaw = currentVersionCode.toString(); + let nextSuffix = 0; + + if (currentRaw.startsWith(todayPrefix)) { + const suffixRaw = currentRaw.slice(todayPrefix.length); + nextSuffix = (suffixRaw ? Number.parseInt(suffixRaw, 10) : 0) + 1; + } + + if (!Number.isInteger(nextSuffix) || nextSuffix < 0 || nextSuffix > 99) { + throw new Error( + `Can't auto-bump Android versionCode for ${todayPrefix}: next suffix ${nextSuffix} is invalid`, + ); + } + + return Number.parseInt(`${todayPrefix}${nextSuffix.toString().padStart(2, "0")}`, 10); +} + +function resolveNextVersion(buildGradleText: string, date: Date): VersionState { + const { versionCodeMatch } = parseVersionMatches(buildGradleText); + const currentVersionCode = Number.parseInt(versionCodeMatch[1] ?? "", 10); + if (!Number.isInteger(currentVersionCode)) { + throw new Error(`Invalid Android versionCode in ${buildGradlePath}`); + } + + const versionName = formatVersionName(date); + const versionCode = resolveNextVersionCode(currentVersionCode, formatVersionCodePrefix(date)); + return { versionName, versionCode }; +} + +function updateBuildGradleVersions(buildGradleText: string, nextVersion: VersionState): string { + return buildGradleText + .replace(/versionCode = \d+/, `versionCode = ${nextVersion.versionCode}`) + .replace(/versionName = "[^"]+"/, `versionName = "${nextVersion.versionName}"`); +} + +async function sha256Hex(path: string): Promise { + const buffer = await Bun.file(path).arrayBuffer(); + const digest = await crypto.subtle.digest("SHA-256", buffer); + return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +async function verifyBundleSignature(path: string): Promise { + await $`jarsigner -verify ${path}`.quiet(); +} + +async function main() { + const buildGradleFile = Bun.file(buildGradlePath); + const originalText = await buildGradleFile.text(); + const nextVersion = resolveNextVersion(originalText, new Date()); + const updatedText = updateBuildGradleVersions(originalText, nextVersion); + + if (updatedText === originalText) { + throw new Error("Android version bump produced no change"); + } + + console.log(`Android versionName -> ${nextVersion.versionName}`); + console.log(`Android versionCode -> ${nextVersion.versionCode}`); + + await Bun.write(buildGradlePath, updatedText); + + try { + await $`./gradlew :app:bundleRelease`.cwd(androidDir); + } catch (error) { + await Bun.write(buildGradlePath, originalText); + throw error; + } + + const bundleFile = Bun.file(bundlePath); + if (!(await bundleFile.exists())) { + throw new Error(`Signed bundle missing at ${bundlePath}`); + } + + await verifyBundleSignature(bundlePath); + const hash = await sha256Hex(bundlePath); + + console.log(`Signed AAB: ${bundlePath}`); + console.log(`SHA-256: ${hash}`); +} + +await main(); diff --git a/apps/ios/ActivityWidget/Info.plist b/apps/ios/ActivityWidget/Info.plist index 4c2d89e1566..4c965121bf9 100644 --- a/apps/ios/ActivityWidget/Info.plist +++ b/apps/ios/ActivityWidget/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 2026.3.9 + $(OPENCLAW_MARKETING_VERSION) CFBundleVersion - 20260308 + $(OPENCLAW_BUILD_VERSION) NSExtension NSExtensionPointIdentifier diff --git a/apps/ios/ActivityWidget/OpenClawLiveActivity.swift b/apps/ios/ActivityWidget/OpenClawLiveActivity.swift index 836803f403f..497fbd45a08 100644 --- a/apps/ios/ActivityWidget/OpenClawLiveActivity.swift +++ b/apps/ios/ActivityWidget/OpenClawLiveActivity.swift @@ -47,6 +47,7 @@ struct OpenClawLiveActivity: Widget { Spacer() trailingView(state: context.state) } + .padding(.horizontal, 12) .padding(.vertical, 4) } diff --git a/apps/ios/Config/Signing.xcconfig b/apps/ios/Config/Signing.xcconfig index 1285d2a38a4..4fef287a09d 100644 --- a/apps/ios/Config/Signing.xcconfig +++ b/apps/ios/Config/Signing.xcconfig @@ -1,10 +1,12 @@ // Shared iOS signing defaults for local development + CI. +#include "Version.xcconfig" + OPENCLAW_IOS_DEFAULT_TEAM = Y5PE65HELJ OPENCLAW_IOS_SELECTED_TEAM = $(OPENCLAW_IOS_DEFAULT_TEAM) -OPENCLAW_APP_BUNDLE_ID = ai.openclaw.ios -OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclaw.ios.watchkitapp -OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclaw.ios.watchkitapp.extension -OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclaw.ios.activitywidget +OPENCLAW_APP_BUNDLE_ID = ai.openclaw.client +OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclaw.client.watchkitapp +OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclaw.client.watchkitapp.extension +OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclaw.client.activitywidget // Local contributors can override this by running scripts/ios-configure-signing.sh. // Keep include after defaults: xcconfig is evaluated top-to-bottom. diff --git a/apps/ios/Config/Version.xcconfig b/apps/ios/Config/Version.xcconfig new file mode 100644 index 00000000000..4297bc8ff57 --- /dev/null +++ b/apps/ios/Config/Version.xcconfig @@ -0,0 +1,8 @@ +// Shared iOS version defaults. +// Generated overrides live in build/Version.xcconfig (git-ignored). + +OPENCLAW_GATEWAY_VERSION = 2026.3.14 +OPENCLAW_MARKETING_VERSION = 2026.3.14 +OPENCLAW_BUILD_VERSION = 202603140 + +#include? "../build/Version.xcconfig" diff --git a/apps/ios/README.md b/apps/ios/README.md index c7c501fcbff..8e591839bd0 100644 --- a/apps/ios/README.md +++ b/apps/ios/README.md @@ -1,15 +1,12 @@ # OpenClaw iOS (Super Alpha) -NO TEST FLIGHT AVAILABLE AT THIS POINT - This iPhone app is super-alpha and internal-use only. It connects to an OpenClaw Gateway as a `role: node`. ## Distribution Status -NO TEST FLIGHT AVAILABLE AT THIS POINT - -- Current distribution: local/manual deploy from source via Xcode. -- App Store flow is not part of the current internal development path. +- Public distribution: not available. +- Internal beta distribution: local archive + TestFlight upload via Fastlane. +- Local/manual deploy from source via Xcode remains the default development path. ## Super-Alpha Disclaimer @@ -50,14 +47,93 @@ Shortcut command (same flow + open project): pnpm ios:open ``` +## Local Beta Release Flow + +Prereqs: + +- Xcode 16+ +- `pnpm` +- `xcodegen` +- `fastlane` +- Apple account signed into Xcode for automatic signing/provisioning +- App Store Connect API key set up in Keychain via `scripts/ios-asc-keychain-setup.sh` when auto-resolving a beta build number or uploading to TestFlight + +Release behavior: + +- Local development keeps using unique per-developer bundle IDs from `scripts/ios-configure-signing.sh`. +- Beta release uses canonical `ai.openclaw.client*` bundle IDs through a temporary generated xcconfig in `apps/ios/build/BetaRelease.xcconfig`. +- Beta release also switches the app to `OpenClawPushTransport=relay`, `OpenClawPushDistribution=official`, and `OpenClawPushAPNsEnvironment=production`. +- The beta flow does not modify `apps/ios/.local-signing.xcconfig` or `apps/ios/LocalSigning.xcconfig`. +- Root `package.json.version` is the only version source for iOS. +- A root version like `2026.3.13-beta.1` becomes: + - `CFBundleShortVersionString = 2026.3.13` + - `CFBundleVersion = next TestFlight build number for 2026.3.13` + +Required env for beta builds: + +- `OPENCLAW_PUSH_RELAY_BASE_URL=https://relay.example.com` + This must be a plain `https://host[:port][/path]` base URL without whitespace, query params, fragments, or xcconfig metacharacters. + +Archive without upload: + +```bash +pnpm ios:beta:archive +``` + +Archive and upload to TestFlight: + +```bash +pnpm ios:beta +``` + +If you need to force a specific build number: + +```bash +pnpm ios:beta -- --build-number 7 +``` + ## APNs Expectations For Local/Manual Builds - The app calls `registerForRemoteNotifications()` at launch. - `apps/ios/Sources/OpenClaw.entitlements` sets `aps-environment` to `development`. - APNs token registration to gateway happens only after gateway connection (`push.apns.register`). +- Local/manual builds default to `OpenClawPushTransport=direct` and `OpenClawPushDistribution=local`. - Your selected team/profile must support Push Notifications for the app bundle ID you are signing. - If push capability or provisioning is wrong, APNs registration fails at runtime (check Xcode logs for `APNs registration failed`). -- Debug builds register as APNs sandbox; Release builds use production. +- Debug builds default to `OpenClawPushAPNsEnvironment=sandbox`; Release builds default to `production`. + +## APNs Expectations For Official Builds + +- Official/TestFlight builds register with the external push relay before they publish `push.apns.register` to the gateway. +- The gateway registration for relay mode contains an opaque relay handle, a registration-scoped send grant, relay origin metadata, and installation metadata instead of the raw APNs token. +- The relay registration is bound to the gateway identity fetched from `gateway.identity.get`, so another gateway cannot reuse that stored registration. +- The app persists the relay handle metadata locally so reconnects can republish the gateway registration without re-registering on every connect. +- If the relay base URL changes in a later build, the app refreshes the relay registration instead of reusing the old relay origin. +- Relay mode requires a reachable relay base URL and uses App Attest plus the app receipt during registration. +- Gateway-side relay sending is configured through `gateway.push.apns.relay.baseUrl` in `openclaw.json`. `OPENCLAW_APNS_RELAY_BASE_URL` remains a temporary env override only. + +## Official Build Relay Trust Model + +- `iOS -> gateway` + - The app must pair with the gateway and establish both node and operator sessions. + - The operator session is used to fetch `gateway.identity.get`. +- `iOS -> relay` + - The app registers with the relay over HTTPS using App Attest plus the app receipt. + - The relay requires the official production/TestFlight distribution path, which is why local + Xcode/dev installs cannot use the hosted relay. +- `gateway delegation` + - The app includes the gateway identity in relay registration. + - The relay returns a relay handle and registration-scoped send grant delegated to that gateway. +- `gateway -> relay` + - The gateway signs relay send requests with its own device identity. + - The relay verifies both the delegated send grant and the gateway signature before it sends to + APNs. +- `relay -> APNs` + - Production APNs credentials and raw official-build APNs tokens stay in the relay deployment, + not on the gateway. + +This exists to keep the hosted relay limited to genuine OpenClaw official builds and to ensure a +gateway can only send pushes for iOS devices that paired with that gateway. ## What Works Now (Concrete) diff --git a/apps/ios/ShareExtension/Info.plist b/apps/ios/ShareExtension/Info.plist index 90a7e09e0fc..9469daa08a8 100644 --- a/apps/ios/ShareExtension/Info.plist +++ b/apps/ios/ShareExtension/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 2026.3.9 + $(OPENCLAW_MARKETING_VERSION) CFBundleVersion - 20260308 + $(OPENCLAW_BUILD_VERSION) NSExtension NSExtensionAttributes diff --git a/apps/ios/ShareExtension/ShareViewController.swift b/apps/ios/ShareExtension/ShareViewController.swift index 1181641e330..00f1b06f9dc 100644 --- a/apps/ios/ShareExtension/ShareViewController.swift +++ b/apps/ios/ShareExtension/ShareViewController.swift @@ -189,6 +189,7 @@ final class ShareViewController: UIViewController { try await gateway.connect( url: url, token: config.token, + bootstrapToken: nil, password: config.password, connectOptions: makeOptions("openclaw-ios"), sessionBox: nil, @@ -208,6 +209,7 @@ final class ShareViewController: UIViewController { try await gateway.connect( url: url, token: config.token, + bootstrapToken: nil, password: config.password, connectOptions: makeOptions("moltbot-ios"), sessionBox: nil, diff --git a/apps/ios/Signing.xcconfig b/apps/ios/Signing.xcconfig index 5966d6e2c2f..d6acc35dee8 100644 --- a/apps/ios/Signing.xcconfig +++ b/apps/ios/Signing.xcconfig @@ -2,6 +2,8 @@ // Auto-selected local team overrides live in .local-signing.xcconfig (git-ignored). // Manual local overrides can go in LocalSigning.xcconfig (git-ignored). +#include "Config/Version.xcconfig" + OPENCLAW_CODE_SIGN_STYLE = Manual OPENCLAW_DEVELOPMENT_TEAM = Y5PE65HELJ diff --git a/apps/ios/Sources/Chat/IOSGatewayChatTransport.swift b/apps/ios/Sources/Chat/IOSGatewayChatTransport.swift index 67f01138803..297811d3ee7 100644 --- a/apps/ios/Sources/Chat/IOSGatewayChatTransport.swift +++ b/apps/ios/Sources/Chat/IOSGatewayChatTransport.swift @@ -39,6 +39,13 @@ struct IOSGatewayChatTransport: OpenClawChatTransport, Sendable { // (chat.subscribe is a node event, not an operator RPC method.) } + func resetSession(sessionKey: String) async throws { + struct Params: Codable { var key: String } + let data = try JSONEncoder().encode(Params(key: sessionKey)) + let json = String(data: data, encoding: .utf8) + _ = try await self.gateway.request(method: "sessions.reset", paramsJSON: json, timeoutSeconds: 10) + } + func requestHistory(sessionKey: String) async throws -> OpenClawChatHistoryPayload { struct Params: Codable { var sessionKey: String } let data = try JSONEncoder().encode(Params(sessionKey: sessionKey)) diff --git a/apps/ios/Sources/Gateway/GatewayConnectConfig.swift b/apps/ios/Sources/Gateway/GatewayConnectConfig.swift index 7f4e93380b0..0abea0e312c 100644 --- a/apps/ios/Sources/Gateway/GatewayConnectConfig.swift +++ b/apps/ios/Sources/Gateway/GatewayConnectConfig.swift @@ -14,6 +14,7 @@ struct GatewayConnectConfig: Sendable { let stableID: String let tls: GatewayTLSParams? let token: String? + let bootstrapToken: String? let password: String? let nodeOptions: GatewayConnectOptions diff --git a/apps/ios/Sources/Gateway/GatewayConnectionController.swift b/apps/ios/Sources/Gateway/GatewayConnectionController.swift index 259768a4df1..dc94f3d0797 100644 --- a/apps/ios/Sources/Gateway/GatewayConnectionController.swift +++ b/apps/ios/Sources/Gateway/GatewayConnectionController.swift @@ -101,6 +101,7 @@ final class GatewayConnectionController { return "Missing instanceId (node.instanceId). Try restarting the app." } let token = GatewaySettingsStore.loadGatewayToken(instanceId: instanceId) + let bootstrapToken = GatewaySettingsStore.loadGatewayBootstrapToken(instanceId: instanceId) let password = GatewaySettingsStore.loadGatewayPassword(instanceId: instanceId) // Resolve the service endpoint (SRV/A/AAAA). TXT is unauthenticated; do not route via TXT. @@ -151,6 +152,7 @@ final class GatewayConnectionController { gatewayStableID: stableID, tls: tlsParams, token: token, + bootstrapToken: bootstrapToken, password: password) return nil } @@ -163,6 +165,7 @@ final class GatewayConnectionController { let instanceId = UserDefaults.standard.string(forKey: "node.instanceId")? .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let token = GatewaySettingsStore.loadGatewayToken(instanceId: instanceId) + let bootstrapToken = GatewaySettingsStore.loadGatewayBootstrapToken(instanceId: instanceId) let password = GatewaySettingsStore.loadGatewayPassword(instanceId: instanceId) let resolvedUseTLS = self.resolveManualUseTLS(host: host, useTLS: useTLS) guard let resolvedPort = self.resolveManualPort(host: host, port: port, useTLS: resolvedUseTLS) @@ -203,6 +206,7 @@ final class GatewayConnectionController { gatewayStableID: stableID, tls: tlsParams, token: token, + bootstrapToken: bootstrapToken, password: password) } @@ -229,6 +233,7 @@ final class GatewayConnectionController { stableID: cfg.stableID, tls: cfg.tls, token: cfg.token, + bootstrapToken: cfg.bootstrapToken, password: cfg.password, nodeOptions: self.makeConnectOptions(stableID: cfg.stableID)) appModel.applyGatewayConnectConfig(refreshedConfig) @@ -261,6 +266,7 @@ final class GatewayConnectionController { let instanceId = UserDefaults.standard.string(forKey: "node.instanceId")? .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let token = GatewaySettingsStore.loadGatewayToken(instanceId: instanceId) + let bootstrapToken = GatewaySettingsStore.loadGatewayBootstrapToken(instanceId: instanceId) let password = GatewaySettingsStore.loadGatewayPassword(instanceId: instanceId) let tlsParams = GatewayTLSParams( required: true, @@ -274,6 +280,7 @@ final class GatewayConnectionController { gatewayStableID: pending.stableID, tls: tlsParams, token: token, + bootstrapToken: bootstrapToken, password: password) } @@ -319,6 +326,7 @@ final class GatewayConnectionController { guard !instanceId.isEmpty else { return } let token = GatewaySettingsStore.loadGatewayToken(instanceId: instanceId) + let bootstrapToken = GatewaySettingsStore.loadGatewayBootstrapToken(instanceId: instanceId) let password = GatewaySettingsStore.loadGatewayPassword(instanceId: instanceId) if manualEnabled { @@ -353,6 +361,7 @@ final class GatewayConnectionController { gatewayStableID: stableID, tls: tlsParams, token: token, + bootstrapToken: bootstrapToken, password: password) return } @@ -379,6 +388,7 @@ final class GatewayConnectionController { gatewayStableID: stableID, tls: tlsParams, token: token, + bootstrapToken: bootstrapToken, password: password) return } @@ -448,6 +458,7 @@ final class GatewayConnectionController { gatewayStableID: String, tls: GatewayTLSParams?, token: String?, + bootstrapToken: String?, password: String?) { guard let appModel else { return } @@ -463,6 +474,7 @@ final class GatewayConnectionController { stableID: gatewayStableID, tls: tls, token: token, + bootstrapToken: bootstrapToken, password: password, nodeOptions: connectOptions) appModel.applyGatewayConnectConfig(cfg) diff --git a/apps/ios/Sources/Gateway/GatewaySettingsStore.swift b/apps/ios/Sources/Gateway/GatewaySettingsStore.swift index 37c039d69d1..92dc71259e5 100644 --- a/apps/ios/Sources/Gateway/GatewaySettingsStore.swift +++ b/apps/ios/Sources/Gateway/GatewaySettingsStore.swift @@ -104,6 +104,21 @@ enum GatewaySettingsStore { account: self.gatewayTokenAccount(instanceId: instanceId)) } + static func loadGatewayBootstrapToken(instanceId: String) -> String? { + let account = self.gatewayBootstrapTokenAccount(instanceId: instanceId) + let token = KeychainStore.loadString(service: self.gatewayService, account: account)? + .trimmingCharacters(in: .whitespacesAndNewlines) + if token?.isEmpty == false { return token } + return nil + } + + static func saveGatewayBootstrapToken(_ token: String, instanceId: String) { + _ = KeychainStore.saveString( + token, + service: self.gatewayService, + account: self.gatewayBootstrapTokenAccount(instanceId: instanceId)) + } + static func loadGatewayPassword(instanceId: String) -> String? { KeychainStore.loadString( service: self.gatewayService, @@ -278,6 +293,9 @@ enum GatewaySettingsStore { _ = KeychainStore.delete( service: self.gatewayService, account: self.gatewayTokenAccount(instanceId: trimmed)) + _ = KeychainStore.delete( + service: self.gatewayService, + account: self.gatewayBootstrapTokenAccount(instanceId: trimmed)) _ = KeychainStore.delete( service: self.gatewayService, account: self.gatewayPasswordAccount(instanceId: trimmed)) @@ -331,6 +349,10 @@ enum GatewaySettingsStore { "gateway-token.\(instanceId)" } + private static func gatewayBootstrapTokenAccount(instanceId: String) -> String { + "gateway-bootstrap-token.\(instanceId)" + } + private static func gatewayPasswordAccount(instanceId: String) -> String { "gateway-password.\(instanceId)" } diff --git a/apps/ios/Sources/Gateway/GatewaySetupCode.swift b/apps/ios/Sources/Gateway/GatewaySetupCode.swift index 8ccbab42da7..d52ca023563 100644 --- a/apps/ios/Sources/Gateway/GatewaySetupCode.swift +++ b/apps/ios/Sources/Gateway/GatewaySetupCode.swift @@ -5,6 +5,7 @@ struct GatewaySetupPayload: Codable { var host: String? var port: Int? var tls: Bool? + var bootstrapToken: String? var token: String? var password: String? } @@ -39,4 +40,3 @@ enum GatewaySetupCode { return String(data: data, encoding: .utf8) } } - diff --git a/apps/ios/Sources/Info.plist b/apps/ios/Sources/Info.plist index 2f1f03d24a1..5908021fad3 100644 --- a/apps/ios/Sources/Info.plist +++ b/apps/ios/Sources/Info.plist @@ -23,7 +23,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2026.3.9 + $(OPENCLAW_MARKETING_VERSION) CFBundleURLTypes @@ -36,7 +36,7 @@ CFBundleVersion - 20260308 + $(OPENCLAW_BUILD_VERSION) ITSAppUsesNonExemptEncryption NSAppTransportSecurity @@ -66,6 +66,14 @@ OpenClaw uses on-device speech recognition for voice wake. NSSupportsLiveActivities + OpenClawPushAPNsEnvironment + $(OPENCLAW_PUSH_APNS_ENVIRONMENT) + OpenClawPushDistribution + $(OPENCLAW_PUSH_DISTRIBUTION) + OpenClawPushRelayBaseURL + $(OPENCLAW_PUSH_RELAY_BASE_URL) + OpenClawPushTransport + $(OPENCLAW_PUSH_TRANSPORT) UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/apps/ios/Sources/Model/NodeAppModel.swift b/apps/ios/Sources/Model/NodeAppModel.swift index babb6b449da..4c0ab81f1a1 100644 --- a/apps/ios/Sources/Model/NodeAppModel.swift +++ b/apps/ios/Sources/Model/NodeAppModel.swift @@ -12,6 +12,12 @@ import UserNotifications private struct NotificationCallError: Error, Sendable { let message: String } + +private struct GatewayRelayIdentityResponse: Decodable { + let deviceId: String + let publicKey: String +} + // Ensures notification requests return promptly even if the system prompt blocks. private final class NotificationInvokeLatch: @unchecked Sendable { private let lock = NSLock() @@ -140,6 +146,7 @@ final class NodeAppModel { private var shareDeliveryTo: String? private var apnsDeviceTokenHex: String? private var apnsLastRegisteredTokenHex: String? + @ObservationIgnored private let pushRegistrationManager = PushRegistrationManager() var gatewaySession: GatewayNodeSession { self.nodeGateway } var operatorSession: GatewayNodeSession { self.operatorGateway } private(set) var activeGatewayConnectConfig: GatewayConnectConfig? @@ -528,13 +535,6 @@ final class NodeAppModel { private static let apnsDeviceTokenUserDefaultsKey = "push.apns.deviceTokenHex" private static let deepLinkKeyUserDefaultsKey = "deeplink.agent.key" private static let canvasUnattendedDeepLinkKey: String = NodeAppModel.generateDeepLinkKey() - private static var apnsEnvironment: String { -#if DEBUG - "sandbox" -#else - "production" -#endif - } private func refreshBrandingFromGateway() async { do { @@ -1189,7 +1189,15 @@ final class NodeAppModel { _ = try await notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) } - return await self.notificationAuthorizationStatus() + let updatedStatus = await self.notificationAuthorizationStatus() + if Self.isNotificationAuthorizationAllowed(updatedStatus) { + // Refresh APNs registration immediately after the first permission grant so the + // gateway can receive a push registration without requiring an app relaunch. + await MainActor.run { + UIApplication.shared.registerForRemoteNotifications() + } + } + return updatedStatus } private func notificationAuthorizationStatus() async -> NotificationAuthorizationStatus { @@ -1204,6 +1212,17 @@ final class NodeAppModel { } } + private static func isNotificationAuthorizationAllowed( + _ status: NotificationAuthorizationStatus + ) -> Bool { + switch status { + case .authorized, .provisional, .ephemeral: + true + case .denied, .notDetermined: + false + } + } + private func runNotificationCall( timeoutSeconds: Double, operation: @escaping @Sendable () async throws -> T @@ -1661,6 +1680,7 @@ extension NodeAppModel { gatewayStableID: String, tls: GatewayTLSParams?, token: String?, + bootstrapToken: String?, password: String?, connectOptions: GatewayConnectOptions) { @@ -1673,6 +1693,7 @@ extension NodeAppModel { stableID: stableID, tls: tls, token: token, + bootstrapToken: bootstrapToken, password: password, nodeOptions: connectOptions) self.prepareForGatewayConnect(url: url, stableID: effectiveStableID) @@ -1680,6 +1701,7 @@ extension NodeAppModel { url: url, stableID: effectiveStableID, token: token, + bootstrapToken: bootstrapToken, password: password, nodeOptions: connectOptions, sessionBox: sessionBox) @@ -1687,6 +1709,7 @@ extension NodeAppModel { url: url, stableID: effectiveStableID, token: token, + bootstrapToken: bootstrapToken, password: password, nodeOptions: connectOptions, sessionBox: sessionBox) @@ -1702,6 +1725,7 @@ extension NodeAppModel { gatewayStableID: cfg.stableID, tls: cfg.tls, token: cfg.token, + bootstrapToken: cfg.bootstrapToken, password: cfg.password, connectOptions: cfg.nodeOptions) } @@ -1782,6 +1806,7 @@ private extension NodeAppModel { url: URL, stableID: String, token: String?, + bootstrapToken: String?, password: String?, nodeOptions: GatewayConnectOptions, sessionBox: WebSocketSessionBox?) @@ -1819,6 +1844,7 @@ private extension NodeAppModel { try await self.operatorGateway.connect( url: url, token: token, + bootstrapToken: bootstrapToken, password: password, connectOptions: operatorOptions, sessionBox: sessionBox, @@ -1834,6 +1860,7 @@ private extension NodeAppModel { await self.refreshBrandingFromGateway() await self.refreshAgentsFromGateway() await self.refreshShareRouteFromGateway() + await self.registerAPNsTokenIfNeeded() await self.startVoiceWakeSync() await MainActor.run { LiveActivityManager.shared.handleReconnect() } await MainActor.run { self.startGatewayHealthMonitor() } @@ -1876,6 +1903,7 @@ private extension NodeAppModel { url: URL, stableID: String, token: String?, + bootstrapToken: String?, password: String?, nodeOptions: GatewayConnectOptions, sessionBox: WebSocketSessionBox?) @@ -1924,6 +1952,7 @@ private extension NodeAppModel { try await self.nodeGateway.connect( url: url, token: token, + bootstrapToken: bootstrapToken, password: password, connectOptions: currentOptions, sessionBox: sessionBox, @@ -2255,8 +2284,7 @@ extension NodeAppModel { from: payload) guard !decoded.actions.isEmpty else { return } self.pendingActionLogger.info( - "Pending actions pulled trigger=\(trigger, privacy: .public) " - + "count=\(decoded.actions.count, privacy: .public)") + "Pending actions pulled trigger=\(trigger, privacy: .public) count=\(decoded.actions.count, privacy: .public)") await self.applyPendingForegroundNodeActions(decoded.actions, trigger: trigger) } catch { // Best-effort only. @@ -2279,9 +2307,7 @@ extension NodeAppModel { paramsJSON: action.paramsJSON) let result = await self.handleInvoke(req) self.pendingActionLogger.info( - "Pending action replay trigger=\(trigger, privacy: .public) " - + "id=\(action.id, privacy: .public) command=\(action.command, privacy: .public) " - + "ok=\(result.ok, privacy: .public)") + "Pending action replay trigger=\(trigger, privacy: .public) id=\(action.id, privacy: .public) command=\(action.command, privacy: .public) ok=\(result.ok, privacy: .public)") guard result.ok else { return } let acked = await self.ackPendingForegroundNodeAction( id: action.id, @@ -2306,9 +2332,7 @@ extension NodeAppModel { return true } catch { self.pendingActionLogger.error( - "Pending action ack failed trigger=\(trigger, privacy: .public) " - + "id=\(id, privacy: .public) command=\(command, privacy: .public) " - + "error=\(String(describing: error), privacy: .public)") + "Pending action ack failed trigger=\(trigger, privacy: .public) id=\(id, privacy: .public) command=\(command, privacy: .public) error=\(String(describing: error), privacy: .public)") return false } } @@ -2484,7 +2508,8 @@ extension NodeAppModel { else { return } - if token == self.apnsLastRegisteredTokenHex { + let usesRelayTransport = await self.pushRegistrationManager.usesRelayTransport + if !usesRelayTransport && token == self.apnsLastRegisteredTokenHex { return } guard let topic = Bundle.main.bundleIdentifier?.trimmingCharacters(in: .whitespacesAndNewlines), @@ -2493,25 +2518,40 @@ extension NodeAppModel { return } - struct PushRegistrationPayload: Codable { - var token: String - var topic: String - var environment: String - } - - let payload = PushRegistrationPayload( - token: token, - topic: topic, - environment: Self.apnsEnvironment) do { - let json = try Self.encodePayload(payload) - await self.nodeGateway.sendEvent(event: "push.apns.register", payloadJSON: json) + let gatewayIdentity: PushRelayGatewayIdentity? + if usesRelayTransport { + guard self.operatorConnected else { return } + gatewayIdentity = try await self.fetchPushRelayGatewayIdentity() + } else { + gatewayIdentity = nil + } + let payloadJSON = try await self.pushRegistrationManager.makeGatewayRegistrationPayload( + apnsTokenHex: token, + topic: topic, + gatewayIdentity: gatewayIdentity) + await self.nodeGateway.sendEvent(event: "push.apns.register", payloadJSON: payloadJSON) self.apnsLastRegisteredTokenHex = token } catch { - // Best-effort only. + self.pushWakeLogger.error( + "APNs registration publish failed: \(error.localizedDescription, privacy: .public)") } } + private func fetchPushRelayGatewayIdentity() async throws -> PushRelayGatewayIdentity { + let response = try await self.operatorGateway.request( + method: "gateway.identity.get", + paramsJSON: "{}", + timeoutSeconds: 8) + let decoded = try JSONDecoder().decode(GatewayRelayIdentityResponse.self, from: response) + let deviceId = decoded.deviceId.trimmingCharacters(in: .whitespacesAndNewlines) + let publicKey = decoded.publicKey.trimmingCharacters(in: .whitespacesAndNewlines) + guard !deviceId.isEmpty, !publicKey.isEmpty else { + throw PushRelayError.relayMisconfigured("Gateway identity response missing required fields") + } + return PushRelayGatewayIdentity(deviceId: deviceId, publicKey: publicKey) + } + private static func isSilentPushPayload(_ userInfo: [AnyHashable: Any]) -> Bool { guard let apsAny = userInfo["aps"] else { return false } if let aps = apsAny as? [AnyHashable: Any] { diff --git a/apps/ios/Sources/Onboarding/GatewayOnboardingView.swift b/apps/ios/Sources/Onboarding/GatewayOnboardingView.swift index b8b6e267755..f160b37d798 100644 --- a/apps/ios/Sources/Onboarding/GatewayOnboardingView.swift +++ b/apps/ios/Sources/Onboarding/GatewayOnboardingView.swift @@ -275,9 +275,21 @@ private struct ManualEntryStep: View { if let token = payload.token, !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { self.manualToken = token.trimmingCharacters(in: .whitespacesAndNewlines) + } else if payload.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false { + self.manualToken = "" } if let password = payload.password, !password.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { self.manualPassword = password.trimmingCharacters(in: .whitespacesAndNewlines) + } else if payload.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false { + self.manualPassword = "" + } + + let trimmedInstanceId = UserDefaults.standard.string(forKey: "node.instanceId")? + .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + if !trimmedInstanceId.isEmpty { + let trimmedBootstrapToken = + payload.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + GatewaySettingsStore.saveGatewayBootstrapToken(trimmedBootstrapToken, instanceId: trimmedInstanceId) } self.setupStatusText = "Setup code applied." diff --git a/apps/ios/Sources/Onboarding/OnboardingStateStore.swift b/apps/ios/Sources/Onboarding/OnboardingStateStore.swift index 9822ac1706f..dc2859d86d9 100644 --- a/apps/ios/Sources/Onboarding/OnboardingStateStore.swift +++ b/apps/ios/Sources/Onboarding/OnboardingStateStore.swift @@ -19,6 +19,7 @@ enum OnboardingConnectionMode: String, CaseIterable { enum OnboardingStateStore { private static let completedDefaultsKey = "onboarding.completed" + private static let firstRunIntroSeenDefaultsKey = "onboarding.first_run_intro_seen" private static let lastModeDefaultsKey = "onboarding.last_mode" private static let lastSuccessTimeDefaultsKey = "onboarding.last_success_time" @@ -39,10 +40,23 @@ enum OnboardingStateStore { defaults.set(Int(Date().timeIntervalSince1970), forKey: Self.lastSuccessTimeDefaultsKey) } + static func shouldPresentFirstRunIntro(defaults: UserDefaults = .standard) -> Bool { + !defaults.bool(forKey: Self.firstRunIntroSeenDefaultsKey) + } + + static func markFirstRunIntroSeen(defaults: UserDefaults = .standard) { + defaults.set(true, forKey: Self.firstRunIntroSeenDefaultsKey) + } + static func markIncomplete(defaults: UserDefaults = .standard) { defaults.set(false, forKey: Self.completedDefaultsKey) } + static func reset(defaults: UserDefaults = .standard) { + defaults.set(false, forKey: Self.completedDefaultsKey) + defaults.set(false, forKey: Self.firstRunIntroSeenDefaultsKey) + } + static func lastMode(defaults: UserDefaults = .standard) -> OnboardingConnectionMode? { let raw = defaults.string(forKey: Self.lastModeDefaultsKey)? .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" diff --git a/apps/ios/Sources/Onboarding/OnboardingWizardView.swift b/apps/ios/Sources/Onboarding/OnboardingWizardView.swift index 4cefeb77e74..516e7b373eb 100644 --- a/apps/ios/Sources/Onboarding/OnboardingWizardView.swift +++ b/apps/ios/Sources/Onboarding/OnboardingWizardView.swift @@ -6,6 +6,7 @@ import SwiftUI import UIKit private enum OnboardingStep: Int, CaseIterable { + case intro case welcome case mode case connect @@ -29,7 +30,8 @@ private enum OnboardingStep: Int, CaseIterable { var title: String { switch self { - case .welcome: "Welcome" + case .intro: "Welcome" + case .welcome: "Connect Gateway" case .mode: "Connection Mode" case .connect: "Connect" case .auth: "Authentication" @@ -38,7 +40,7 @@ private enum OnboardingStep: Int, CaseIterable { } var canGoBack: Bool { - self != .welcome && self != .success + self != .intro && self != .welcome && self != .success } } @@ -49,7 +51,7 @@ struct OnboardingWizardView: View { @AppStorage("node.instanceId") private var instanceId: String = UUID().uuidString @AppStorage("gateway.discovery.domain") private var discoveryDomain: String = "" @AppStorage("onboarding.developerMode") private var developerModeEnabled: Bool = false - @State private var step: OnboardingStep = .welcome + @State private var step: OnboardingStep @State private var selectedMode: OnboardingConnectionMode? @State private var manualHost: String = "" @State private var manualPort: Int = 18789 @@ -58,11 +60,10 @@ struct OnboardingWizardView: View { @State private var gatewayToken: String = "" @State private var gatewayPassword: String = "" @State private var connectMessage: String? - @State private var statusLine: String = "Scan the QR code from your gateway to connect." + @State private var statusLine: String = "In your OpenClaw chat, run /pair qr, then scan the code here." @State private var connectingGatewayID: String? @State private var issue: GatewayConnectionIssue = .none @State private var didMarkCompleted = false - @State private var didAutoPresentQR = false @State private var pairingRequestId: String? @State private var discoveryRestartTask: Task? @State private var showQRScanner: Bool = false @@ -74,14 +75,23 @@ struct OnboardingWizardView: View { let allowSkip: Bool let onClose: () -> Void + init(allowSkip: Bool, onClose: @escaping () -> Void) { + self.allowSkip = allowSkip + self.onClose = onClose + _step = State( + initialValue: OnboardingStateStore.shouldPresentFirstRunIntro() ? .intro : .welcome) + } + private var isFullScreenStep: Bool { - self.step == .welcome || self.step == .success + self.step == .intro || self.step == .welcome || self.step == .success } var body: some View { NavigationStack { Group { switch self.step { + case .intro: + self.introStep case .welcome: self.welcomeStep case .success: @@ -293,6 +303,83 @@ struct OnboardingWizardView: View { } } + @ViewBuilder + private var introStep: some View { + VStack(spacing: 0) { + Spacer() + + Image(systemName: "iphone.gen3") + .font(.system(size: 60, weight: .semibold)) + .foregroundStyle(.tint) + .padding(.bottom, 18) + + Text("Welcome to OpenClaw") + .font(.largeTitle.weight(.bold)) + .multilineTextAlignment(.center) + .padding(.bottom, 10) + + Text("Turn this iPhone into a secure OpenClaw node for chat, voice, camera, and device tools.") + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 32) + .padding(.bottom, 24) + + VStack(alignment: .leading, spacing: 14) { + Label("Connect to your gateway", systemImage: "link") + Label("Choose device permissions", systemImage: "hand.raised") + Label("Use OpenClaw from your phone", systemImage: "message.fill") + } + .font(.subheadline.weight(.semibold)) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(18) + .background { + RoundedRectangle(cornerRadius: 20, style: .continuous) + .fill(Color(uiColor: .secondarySystemBackground)) + } + .padding(.horizontal, 24) + .padding(.bottom, 16) + + HStack(alignment: .top, spacing: 12) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.title3.weight(.semibold)) + .foregroundStyle(.orange) + .frame(width: 24) + .padding(.top, 2) + + VStack(alignment: .leading, spacing: 6) { + Text("Security notice") + .font(.headline) + Text( + "The connected OpenClaw agent can use device capabilities you enable, such as camera, microphone, photos, contacts, calendar, and location. Continue only if you trust the gateway and agent you connect to.") + .font(.footnote) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(18) + .background { + RoundedRectangle(cornerRadius: 20, style: .continuous) + .fill(Color(uiColor: .secondarySystemBackground)) + } + .padding(.horizontal, 24) + + Spacer() + + Button { + self.advanceFromIntro() + } label: { + Text("Continue") + .frame(maxWidth: .infinity) + } + .buttonStyle(.borderedProminent) + .controlSize(.large) + .padding(.horizontal, 24) + .padding(.bottom, 48) + } + } + @ViewBuilder private var welcomeStep: some View { VStack(spacing: 0) { @@ -303,16 +390,37 @@ struct OnboardingWizardView: View { .foregroundStyle(.tint) .padding(.bottom, 20) - Text("Welcome") + Text("Connect Gateway") .font(.largeTitle.weight(.bold)) .padding(.bottom, 8) - Text("Connect to your OpenClaw gateway") + Text("Scan a QR code from your OpenClaw gateway or continue with manual setup.") .font(.subheadline) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 32) + VStack(alignment: .leading, spacing: 8) { + Text("How to pair") + .font(.headline) + Text("In your OpenClaw chat, run") + .font(.footnote) + .foregroundStyle(.secondary) + Text("/pair qr") + .font(.system(.footnote, design: .monospaced).weight(.semibold)) + Text("Then scan the QR code here to connect this iPhone.") + .font(.footnote) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(16) + .background { + RoundedRectangle(cornerRadius: 18, style: .continuous) + .fill(Color(uiColor: .secondarySystemBackground)) + } + .padding(.horizontal, 24) + .padding(.top, 20) + Spacer() VStack(spacing: 12) { @@ -342,8 +450,7 @@ struct OnboardingWizardView: View { .foregroundStyle(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 24) - .padding(.horizontal, 24) - .padding(.bottom, 48) + .padding(.bottom, 48) } } @@ -642,11 +749,17 @@ struct OnboardingWizardView: View { self.manualHost = link.host self.manualPort = link.port self.manualTLS = link.tls - if let token = link.token { + let trimmedBootstrapToken = link.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines) + self.saveGatewayBootstrapToken(trimmedBootstrapToken) + if let token = link.token?.trimmingCharacters(in: .whitespacesAndNewlines), !token.isEmpty { self.gatewayToken = token + } else if trimmedBootstrapToken?.isEmpty == false { + self.gatewayToken = "" } - if let password = link.password { + if let password = link.password?.trimmingCharacters(in: .whitespacesAndNewlines), !password.isEmpty { self.gatewayPassword = password + } else if trimmedBootstrapToken?.isEmpty == false { + self.gatewayPassword = "" } self.saveGatewayCredentials(token: self.gatewayToken, password: self.gatewayPassword) self.showQRScanner = false @@ -721,6 +834,12 @@ struct OnboardingWizardView: View { return nil } + private func advanceFromIntro() { + OnboardingStateStore.markFirstRunIntroSeen() + self.statusLine = "In your OpenClaw chat, run /pair qr, then scan the code here." + self.step = .welcome + } + private func navigateBack() { guard let target = self.step.previous else { return } self.connectingGatewayID = nil @@ -769,10 +888,8 @@ struct OnboardingWizardView: View { let hasSavedGateway = GatewaySettingsStore.loadLastGatewayConnection() != nil let hasToken = !self.gatewayToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty let hasPassword = !self.gatewayPassword.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty - if !self.didAutoPresentQR, !hasSavedGateway, !hasToken, !hasPassword { - self.didAutoPresentQR = true - self.statusLine = "No saved pairing found. Scan QR code to connect." - self.showQRScanner = true + if !hasSavedGateway, !hasToken, !hasPassword { + self.statusLine = "No saved pairing found. In your OpenClaw chat, run /pair qr, then scan the code here." } } @@ -794,6 +911,13 @@ struct OnboardingWizardView: View { GatewaySettingsStore.saveGatewayPassword(trimmedPassword, instanceId: trimmedInstanceId) } + private func saveGatewayBootstrapToken(_ token: String?) { + let trimmedInstanceId = self.instanceId.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmedInstanceId.isEmpty else { return } + let trimmedToken = token?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + GatewaySettingsStore.saveGatewayBootstrapToken(trimmedToken, instanceId: trimmedInstanceId) + } + private func connectDiscoveredGateway(_ gateway: GatewayDiscoveryModel.DiscoveredGateway) async { self.connectingGatewayID = gateway.id self.issue = .none diff --git a/apps/ios/Sources/OpenClawApp.swift b/apps/ios/Sources/OpenClawApp.swift index c94b1209f8d..ae980b0216a 100644 --- a/apps/ios/Sources/OpenClawApp.swift +++ b/apps/ios/Sources/OpenClawApp.swift @@ -407,6 +407,13 @@ enum WatchPromptNotificationBridge { let granted = (try? await center.requestAuthorization(options: [.alert, .sound, .badge])) ?? false if !granted { return false } let updatedStatus = await self.notificationAuthorizationStatus(center: center) + if self.isAuthorizationStatusAllowed(updatedStatus) { + // Refresh APNs registration immediately after the first permission grant so the + // gateway can receive a push registration without requiring an app relaunch. + await MainActor.run { + UIApplication.shared.registerForRemoteNotifications() + } + } return self.isAuthorizationStatusAllowed(updatedStatus) case .denied: return false diff --git a/apps/ios/Sources/Push/PushBuildConfig.swift b/apps/ios/Sources/Push/PushBuildConfig.swift new file mode 100644 index 00000000000..d1665921552 --- /dev/null +++ b/apps/ios/Sources/Push/PushBuildConfig.swift @@ -0,0 +1,75 @@ +import Foundation + +enum PushTransportMode: String { + case direct + case relay +} + +enum PushDistributionMode: String { + case local + case official +} + +enum PushAPNsEnvironment: String { + case sandbox + case production +} + +struct PushBuildConfig { + let transport: PushTransportMode + let distribution: PushDistributionMode + let relayBaseURL: URL? + let apnsEnvironment: PushAPNsEnvironment + + static let current = PushBuildConfig() + + init(bundle: Bundle = .main) { + self.transport = Self.readEnum( + bundle: bundle, + key: "OpenClawPushTransport", + fallback: .direct) + self.distribution = Self.readEnum( + bundle: bundle, + key: "OpenClawPushDistribution", + fallback: .local) + self.apnsEnvironment = Self.readEnum( + bundle: bundle, + key: "OpenClawPushAPNsEnvironment", + fallback: Self.defaultAPNsEnvironment) + self.relayBaseURL = Self.readURL(bundle: bundle, key: "OpenClawPushRelayBaseURL") + } + + var usesRelay: Bool { + self.transport == .relay + } + + private static func readURL(bundle: Bundle, key: String) -> URL? { + guard let raw = bundle.object(forInfoDictionaryKey: key) as? String else { return nil } + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return nil } + guard let components = URLComponents(string: trimmed), + components.scheme?.lowercased() == "https", + let host = components.host, + !host.isEmpty, + components.user == nil, + components.password == nil, + components.query == nil, + components.fragment == nil + else { + return nil + } + return components.url + } + + private static func readEnum( + bundle: Bundle, + key: String, + fallback: T) + -> T where T.RawValue == String { + guard let raw = bundle.object(forInfoDictionaryKey: key) as? String else { return fallback } + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + return T(rawValue: trimmed) ?? fallback + } + + private static let defaultAPNsEnvironment: PushAPNsEnvironment = .sandbox +} diff --git a/apps/ios/Sources/Push/PushRegistrationManager.swift b/apps/ios/Sources/Push/PushRegistrationManager.swift new file mode 100644 index 00000000000..77f54f8d108 --- /dev/null +++ b/apps/ios/Sources/Push/PushRegistrationManager.swift @@ -0,0 +1,169 @@ +import CryptoKit +import Foundation + +private struct DirectGatewayPushRegistrationPayload: Encodable { + var transport: String = PushTransportMode.direct.rawValue + var token: String + var topic: String + var environment: String +} + +private struct RelayGatewayPushRegistrationPayload: Encodable { + var transport: String = PushTransportMode.relay.rawValue + var relayHandle: String + var sendGrant: String + var gatewayDeviceId: String + var installationId: String + var topic: String + var environment: String + var distribution: String + var tokenDebugSuffix: String? +} + +struct PushRelayGatewayIdentity: Codable { + var deviceId: String + var publicKey: String +} + +actor PushRegistrationManager { + private let buildConfig: PushBuildConfig + private let relayClient: PushRelayClient? + + var usesRelayTransport: Bool { + self.buildConfig.transport == .relay + } + + init(buildConfig: PushBuildConfig = .current) { + self.buildConfig = buildConfig + self.relayClient = buildConfig.relayBaseURL.map { PushRelayClient(baseURL: $0) } + } + + func makeGatewayRegistrationPayload( + apnsTokenHex: String, + topic: String, + gatewayIdentity: PushRelayGatewayIdentity?) + async throws -> String { + switch self.buildConfig.transport { + case .direct: + return try Self.encodePayload( + DirectGatewayPushRegistrationPayload( + token: apnsTokenHex, + topic: topic, + environment: self.buildConfig.apnsEnvironment.rawValue)) + case .relay: + guard let gatewayIdentity else { + throw PushRelayError.relayMisconfigured("Missing gateway identity for relay registration") + } + return try await self.makeRelayPayload( + apnsTokenHex: apnsTokenHex, + topic: topic, + gatewayIdentity: gatewayIdentity) + } + } + + private func makeRelayPayload( + apnsTokenHex: String, + topic: String, + gatewayIdentity: PushRelayGatewayIdentity) + async throws -> String { + guard self.buildConfig.distribution == .official else { + throw PushRelayError.relayMisconfigured( + "Relay transport requires OpenClawPushDistribution=official") + } + guard self.buildConfig.apnsEnvironment == .production else { + throw PushRelayError.relayMisconfigured( + "Relay transport requires OpenClawPushAPNsEnvironment=production") + } + guard let relayClient = self.relayClient else { + throw PushRelayError.relayBaseURLMissing + } + guard let bundleId = Bundle.main.bundleIdentifier?.trimmingCharacters(in: .whitespacesAndNewlines), + !bundleId.isEmpty + else { + throw PushRelayError.relayMisconfigured("Missing bundle identifier for relay registration") + } + guard let installationId = GatewaySettingsStore.loadStableInstanceID()? + .trimmingCharacters(in: .whitespacesAndNewlines), + !installationId.isEmpty + else { + throw PushRelayError.relayMisconfigured("Missing stable installation ID for relay registration") + } + + let tokenHashHex = Self.sha256Hex(apnsTokenHex) + let relayOrigin = relayClient.normalizedBaseURLString + if let stored = PushRelayRegistrationStore.loadRegistrationState(), + stored.installationId == installationId, + stored.gatewayDeviceId == gatewayIdentity.deviceId, + stored.relayOrigin == relayOrigin, + stored.lastAPNsTokenHashHex == tokenHashHex, + !Self.isExpired(stored.relayHandleExpiresAtMs) + { + return try Self.encodePayload( + RelayGatewayPushRegistrationPayload( + relayHandle: stored.relayHandle, + sendGrant: stored.sendGrant, + gatewayDeviceId: gatewayIdentity.deviceId, + installationId: installationId, + topic: topic, + environment: self.buildConfig.apnsEnvironment.rawValue, + distribution: self.buildConfig.distribution.rawValue, + tokenDebugSuffix: stored.tokenDebugSuffix)) + } + + let response = try await relayClient.register( + installationId: installationId, + bundleId: bundleId, + appVersion: DeviceInfoHelper.appVersion(), + environment: self.buildConfig.apnsEnvironment, + distribution: self.buildConfig.distribution, + apnsTokenHex: apnsTokenHex, + gatewayIdentity: gatewayIdentity) + let registrationState = PushRelayRegistrationStore.RegistrationState( + relayHandle: response.relayHandle, + sendGrant: response.sendGrant, + relayOrigin: relayOrigin, + gatewayDeviceId: gatewayIdentity.deviceId, + relayHandleExpiresAtMs: response.expiresAtMs, + tokenDebugSuffix: Self.normalizeTokenSuffix(response.tokenSuffix), + lastAPNsTokenHashHex: tokenHashHex, + installationId: installationId, + lastTransport: self.buildConfig.transport.rawValue) + _ = PushRelayRegistrationStore.saveRegistrationState(registrationState) + return try Self.encodePayload( + RelayGatewayPushRegistrationPayload( + relayHandle: response.relayHandle, + sendGrant: response.sendGrant, + gatewayDeviceId: gatewayIdentity.deviceId, + installationId: installationId, + topic: topic, + environment: self.buildConfig.apnsEnvironment.rawValue, + distribution: self.buildConfig.distribution.rawValue, + tokenDebugSuffix: registrationState.tokenDebugSuffix)) + } + + private static func isExpired(_ expiresAtMs: Int64?) -> Bool { + guard let expiresAtMs else { return true } + let nowMs = Int64(Date().timeIntervalSince1970 * 1000) + // Refresh shortly before expiry so reconnect-path republishes a live handle. + return expiresAtMs <= nowMs + 60_000 + } + + private static func sha256Hex(_ value: String) -> String { + let digest = SHA256.hash(data: Data(value.utf8)) + return digest.map { String(format: "%02x", $0) }.joined() + } + + private static func normalizeTokenSuffix(_ value: String?) -> String? { + guard let value else { return nil } + let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + return trimmed.isEmpty ? nil : trimmed + } + + private static func encodePayload(_ payload: some Encodable) throws -> String { + let data = try JSONEncoder().encode(payload) + guard let json = String(data: data, encoding: .utf8) else { + throw PushRelayError.relayMisconfigured("Failed to encode push registration payload as UTF-8") + } + return json + } +} diff --git a/apps/ios/Sources/Push/PushRelayClient.swift b/apps/ios/Sources/Push/PushRelayClient.swift new file mode 100644 index 00000000000..07bb5caa3b7 --- /dev/null +++ b/apps/ios/Sources/Push/PushRelayClient.swift @@ -0,0 +1,349 @@ +import CryptoKit +import DeviceCheck +import Foundation +import StoreKit + +enum PushRelayError: LocalizedError { + case relayBaseURLMissing + case relayMisconfigured(String) + case invalidResponse(String) + case requestFailed(status: Int, message: String) + case unsupportedAppAttest + case missingReceipt + + var errorDescription: String? { + switch self { + case .relayBaseURLMissing: + "Push relay base URL missing" + case let .relayMisconfigured(message): + message + case let .invalidResponse(message): + message + case let .requestFailed(status, message): + "Push relay request failed (\(status)): \(message)" + case .unsupportedAppAttest: + "App Attest unavailable on this device" + case .missingReceipt: + "App Store receipt missing after refresh" + } + } +} + +private struct PushRelayChallengeResponse: Decodable { + var challengeId: String + var challenge: String + var expiresAtMs: Int64 +} + +private struct PushRelayRegisterSignedPayload: Encodable { + var challengeId: String + var installationId: String + var bundleId: String + var environment: String + var distribution: String + var gateway: PushRelayGatewayIdentity + var appVersion: String + var apnsToken: String +} + +private struct PushRelayAppAttestPayload: Encodable { + var keyId: String + var attestationObject: String? + var assertion: String + var clientDataHash: String + var signedPayloadBase64: String +} + +private struct PushRelayReceiptPayload: Encodable { + var base64: String +} + +private struct PushRelayRegisterRequest: Encodable { + var challengeId: String + var installationId: String + var bundleId: String + var environment: String + var distribution: String + var gateway: PushRelayGatewayIdentity + var appVersion: String + var apnsToken: String + var appAttest: PushRelayAppAttestPayload + var receipt: PushRelayReceiptPayload +} + +struct PushRelayRegisterResponse: Decodable { + var relayHandle: String + var sendGrant: String + var expiresAtMs: Int64? + var tokenSuffix: String? + var status: String +} + +private struct RelayErrorResponse: Decodable { + var error: String? + var message: String? + var reason: String? +} + +private final class PushRelayReceiptRefreshCoordinator: NSObject, SKRequestDelegate { + private var continuation: CheckedContinuation? + private var activeRequest: SKReceiptRefreshRequest? + + func refresh() async throws { + try await withCheckedThrowingContinuation { continuation in + self.continuation = continuation + let request = SKReceiptRefreshRequest() + self.activeRequest = request + request.delegate = self + request.start() + } + } + + func requestDidFinish(_ request: SKRequest) { + self.continuation?.resume(returning: ()) + self.continuation = nil + self.activeRequest = nil + } + + func request(_ request: SKRequest, didFailWithError error: Error) { + self.continuation?.resume(throwing: error) + self.continuation = nil + self.activeRequest = nil + } +} + +private struct PushRelayAppAttestProof { + var keyId: String + var attestationObject: String? + var assertion: String + var clientDataHash: String + var signedPayloadBase64: String +} + +private final class PushRelayAppAttestService { + func createProof(challenge: String, signedPayload: Data) async throws -> PushRelayAppAttestProof { + let service = DCAppAttestService.shared + guard service.isSupported else { + throw PushRelayError.unsupportedAppAttest + } + + let keyID = try await self.loadOrCreateKeyID(using: service) + let attestationObject = try await self.attestKeyIfNeeded( + service: service, + keyID: keyID, + challenge: challenge) + let signedPayloadHash = Data(SHA256.hash(data: signedPayload)) + let assertion = try await self.generateAssertion( + service: service, + keyID: keyID, + signedPayloadHash: signedPayloadHash) + + return PushRelayAppAttestProof( + keyId: keyID, + attestationObject: attestationObject, + assertion: assertion.base64EncodedString(), + clientDataHash: Self.base64URL(signedPayloadHash), + signedPayloadBase64: signedPayload.base64EncodedString()) + } + + private func loadOrCreateKeyID(using service: DCAppAttestService) async throws -> String { + if let existing = PushRelayRegistrationStore.loadAppAttestKeyID(), !existing.isEmpty { + return existing + } + let keyID = try await service.generateKey() + _ = PushRelayRegistrationStore.saveAppAttestKeyID(keyID) + return keyID + } + + private func attestKeyIfNeeded( + service: DCAppAttestService, + keyID: String, + challenge: String) + async throws -> String? { + if PushRelayRegistrationStore.loadAttestedKeyID() == keyID { + return nil + } + let challengeData = Data(challenge.utf8) + let clientDataHash = Data(SHA256.hash(data: challengeData)) + let attestation = try await service.attestKey(keyID, clientDataHash: clientDataHash) + // Apple treats App Attest key attestation as a one-time operation. Save the + // attested marker immediately so later receipt/network failures do not cause a + // permanently broken re-attestation loop on the same key. + _ = PushRelayRegistrationStore.saveAttestedKeyID(keyID) + return attestation.base64EncodedString() + } + + private func generateAssertion( + service: DCAppAttestService, + keyID: String, + signedPayloadHash: Data) + async throws -> Data { + do { + return try await service.generateAssertion(keyID, clientDataHash: signedPayloadHash) + } catch { + _ = PushRelayRegistrationStore.clearAppAttestKeyID() + _ = PushRelayRegistrationStore.clearAttestedKeyID() + throw error + } + } + + private static func base64URL(_ data: Data) -> String { + data.base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") + } +} + +private final class PushRelayReceiptProvider { + func loadReceiptBase64() async throws -> String { + if let receipt = self.readReceiptData() { + return receipt.base64EncodedString() + } + let refreshCoordinator = PushRelayReceiptRefreshCoordinator() + try await refreshCoordinator.refresh() + if let refreshed = self.readReceiptData() { + return refreshed.base64EncodedString() + } + throw PushRelayError.missingReceipt + } + + private func readReceiptData() -> Data? { + guard let url = Bundle.main.appStoreReceiptURL else { return nil } + guard let data = try? Data(contentsOf: url), !data.isEmpty else { return nil } + return data + } +} + +// The client is constructed once and used behind PushRegistrationManager actor isolation. +final class PushRelayClient: @unchecked Sendable { + private let baseURL: URL + private let session: URLSession + private let jsonDecoder = JSONDecoder() + private let jsonEncoder = JSONEncoder() + private let appAttest = PushRelayAppAttestService() + private let receiptProvider = PushRelayReceiptProvider() + + init(baseURL: URL, session: URLSession = .shared) { + self.baseURL = baseURL + self.session = session + } + + var normalizedBaseURLString: String { + Self.normalizeBaseURLString(self.baseURL) + } + + func register( + installationId: String, + bundleId: String, + appVersion: String, + environment: PushAPNsEnvironment, + distribution: PushDistributionMode, + apnsTokenHex: String, + gatewayIdentity: PushRelayGatewayIdentity) + async throws -> PushRelayRegisterResponse { + let challenge = try await self.fetchChallenge() + let signedPayload = PushRelayRegisterSignedPayload( + challengeId: challenge.challengeId, + installationId: installationId, + bundleId: bundleId, + environment: environment.rawValue, + distribution: distribution.rawValue, + gateway: gatewayIdentity, + appVersion: appVersion, + apnsToken: apnsTokenHex) + let signedPayloadData = try self.jsonEncoder.encode(signedPayload) + let appAttest = try await self.appAttest.createProof( + challenge: challenge.challenge, + signedPayload: signedPayloadData) + let receiptBase64 = try await self.receiptProvider.loadReceiptBase64() + let requestBody = PushRelayRegisterRequest( + challengeId: signedPayload.challengeId, + installationId: signedPayload.installationId, + bundleId: signedPayload.bundleId, + environment: signedPayload.environment, + distribution: signedPayload.distribution, + gateway: signedPayload.gateway, + appVersion: signedPayload.appVersion, + apnsToken: signedPayload.apnsToken, + appAttest: PushRelayAppAttestPayload( + keyId: appAttest.keyId, + attestationObject: appAttest.attestationObject, + assertion: appAttest.assertion, + clientDataHash: appAttest.clientDataHash, + signedPayloadBase64: appAttest.signedPayloadBase64), + receipt: PushRelayReceiptPayload(base64: receiptBase64)) + + let endpoint = self.baseURL.appending(path: "v1/push/register") + var request = URLRequest(url: endpoint) + request.httpMethod = "POST" + request.timeoutInterval = 20 + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try self.jsonEncoder.encode(requestBody) + + let (data, response) = try await self.session.data(for: request) + let status = Self.statusCode(from: response) + guard (200..<300).contains(status) else { + if status == 401 { + // If the relay rejects registration, drop local App Attest state so the next + // attempt re-attests instead of getting stuck without an attestation object. + _ = PushRelayRegistrationStore.clearAppAttestKeyID() + _ = PushRelayRegistrationStore.clearAttestedKeyID() + } + throw PushRelayError.requestFailed( + status: status, + message: Self.decodeErrorMessage(data: data)) + } + let decoded = try self.decode(PushRelayRegisterResponse.self, from: data) + return decoded + } + + private func fetchChallenge() async throws -> PushRelayChallengeResponse { + let endpoint = self.baseURL.appending(path: "v1/push/challenge") + var request = URLRequest(url: endpoint) + request.httpMethod = "POST" + request.timeoutInterval = 10 + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = Data("{}".utf8) + + let (data, response) = try await self.session.data(for: request) + let status = Self.statusCode(from: response) + guard (200..<300).contains(status) else { + throw PushRelayError.requestFailed( + status: status, + message: Self.decodeErrorMessage(data: data)) + } + return try self.decode(PushRelayChallengeResponse.self, from: data) + } + + private func decode(_ type: T.Type, from data: Data) throws -> T { + do { + return try self.jsonDecoder.decode(type, from: data) + } catch { + throw PushRelayError.invalidResponse(error.localizedDescription) + } + } + + private static func statusCode(from response: URLResponse) -> Int { + (response as? HTTPURLResponse)?.statusCode ?? 0 + } + + private static func normalizeBaseURLString(_ url: URL) -> String { + var absolute = url.absoluteString + while absolute.hasSuffix("/") { + absolute.removeLast() + } + return absolute + } + + private static func decodeErrorMessage(data: Data) -> String { + if let decoded = try? JSONDecoder().decode(RelayErrorResponse.self, from: data) { + let message = decoded.message ?? decoded.reason ?? decoded.error ?? "" + if !message.isEmpty { + return message + } + } + let raw = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + return raw.isEmpty ? "unknown relay error" : raw + } +} diff --git a/apps/ios/Sources/Push/PushRelayKeychainStore.swift b/apps/ios/Sources/Push/PushRelayKeychainStore.swift new file mode 100644 index 00000000000..4d7df09cd14 --- /dev/null +++ b/apps/ios/Sources/Push/PushRelayKeychainStore.swift @@ -0,0 +1,112 @@ +import Foundation + +private struct StoredPushRelayRegistrationState: Codable { + var relayHandle: String + var sendGrant: String + var relayOrigin: String? + var gatewayDeviceId: String + var relayHandleExpiresAtMs: Int64? + var tokenDebugSuffix: String? + var lastAPNsTokenHashHex: String + var installationId: String + var lastTransport: String +} + +enum PushRelayRegistrationStore { + private static let service = "ai.openclaw.pushrelay" + private static let registrationStateAccount = "registration-state" + private static let appAttestKeyIDAccount = "app-attest-key-id" + private static let appAttestedKeyIDAccount = "app-attested-key-id" + + struct RegistrationState: Codable { + var relayHandle: String + var sendGrant: String + var relayOrigin: String? + var gatewayDeviceId: String + var relayHandleExpiresAtMs: Int64? + var tokenDebugSuffix: String? + var lastAPNsTokenHashHex: String + var installationId: String + var lastTransport: String + } + + static func loadRegistrationState() -> RegistrationState? { + guard let raw = KeychainStore.loadString( + service: self.service, + account: self.registrationStateAccount), + let data = raw.data(using: .utf8), + let decoded = try? JSONDecoder().decode(StoredPushRelayRegistrationState.self, from: data) + else { + return nil + } + return RegistrationState( + relayHandle: decoded.relayHandle, + sendGrant: decoded.sendGrant, + relayOrigin: decoded.relayOrigin, + gatewayDeviceId: decoded.gatewayDeviceId, + relayHandleExpiresAtMs: decoded.relayHandleExpiresAtMs, + tokenDebugSuffix: decoded.tokenDebugSuffix, + lastAPNsTokenHashHex: decoded.lastAPNsTokenHashHex, + installationId: decoded.installationId, + lastTransport: decoded.lastTransport) + } + + @discardableResult + static func saveRegistrationState(_ state: RegistrationState) -> Bool { + let stored = StoredPushRelayRegistrationState( + relayHandle: state.relayHandle, + sendGrant: state.sendGrant, + relayOrigin: state.relayOrigin, + gatewayDeviceId: state.gatewayDeviceId, + relayHandleExpiresAtMs: state.relayHandleExpiresAtMs, + tokenDebugSuffix: state.tokenDebugSuffix, + lastAPNsTokenHashHex: state.lastAPNsTokenHashHex, + installationId: state.installationId, + lastTransport: state.lastTransport) + guard let data = try? JSONEncoder().encode(stored), + let raw = String(data: data, encoding: .utf8) + else { + return false + } + return KeychainStore.saveString(raw, service: self.service, account: self.registrationStateAccount) + } + + @discardableResult + static func clearRegistrationState() -> Bool { + KeychainStore.delete(service: self.service, account: self.registrationStateAccount) + } + + static func loadAppAttestKeyID() -> String? { + let value = KeychainStore.loadString(service: self.service, account: self.appAttestKeyIDAccount)? + .trimmingCharacters(in: .whitespacesAndNewlines) + if value?.isEmpty == false { return value } + return nil + } + + @discardableResult + static func saveAppAttestKeyID(_ keyID: String) -> Bool { + KeychainStore.saveString(keyID, service: self.service, account: self.appAttestKeyIDAccount) + } + + @discardableResult + static func clearAppAttestKeyID() -> Bool { + KeychainStore.delete(service: self.service, account: self.appAttestKeyIDAccount) + } + + static func loadAttestedKeyID() -> String? { + let value = KeychainStore.loadString(service: self.service, account: self.appAttestedKeyIDAccount)? + .trimmingCharacters(in: .whitespacesAndNewlines) + if value?.isEmpty == false { return value } + return nil + } + + @discardableResult + static func saveAttestedKeyID(_ keyID: String) -> Bool { + KeychainStore.saveString(keyID, service: self.service, account: self.appAttestedKeyIDAccount) + } + + @discardableResult + static func clearAttestedKeyID() -> Bool { + KeychainStore.delete(service: self.service, account: self.appAttestedKeyIDAccount) + } +} diff --git a/apps/ios/Sources/Settings/SettingsTab.swift b/apps/ios/Sources/Settings/SettingsTab.swift index 7aa79fa24ca..6df8c1ec510 100644 --- a/apps/ios/Sources/Settings/SettingsTab.swift +++ b/apps/ios/Sources/Settings/SettingsTab.swift @@ -767,12 +767,22 @@ struct SettingsTab: View { } let trimmedInstanceId = self.instanceId.trimmingCharacters(in: .whitespacesAndNewlines) + let trimmedBootstrapToken = + payload.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + if !trimmedInstanceId.isEmpty { + GatewaySettingsStore.saveGatewayBootstrapToken(trimmedBootstrapToken, instanceId: trimmedInstanceId) + } if let token = payload.token, !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { let trimmedToken = token.trimmingCharacters(in: .whitespacesAndNewlines) self.gatewayToken = trimmedToken if !trimmedInstanceId.isEmpty { GatewaySettingsStore.saveGatewayToken(trimmedToken, instanceId: trimmedInstanceId) } + } else if !trimmedBootstrapToken.isEmpty { + self.gatewayToken = "" + if !trimmedInstanceId.isEmpty { + GatewaySettingsStore.saveGatewayToken("", instanceId: trimmedInstanceId) + } } if let password = payload.password, !password.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { let trimmedPassword = password.trimmingCharacters(in: .whitespacesAndNewlines) @@ -780,6 +790,11 @@ struct SettingsTab: View { if !trimmedInstanceId.isEmpty { GatewaySettingsStore.saveGatewayPassword(trimmedPassword, instanceId: trimmedInstanceId) } + } else if !trimmedBootstrapToken.isEmpty { + self.gatewayPassword = "" + if !trimmedInstanceId.isEmpty { + GatewaySettingsStore.saveGatewayPassword("", instanceId: trimmedInstanceId) + } } return true @@ -993,6 +1008,7 @@ struct SettingsTab: View { // Reset onboarding state + clear saved gateway connection (the two things RootCanvas checks). GatewaySettingsStore.clearLastGatewayConnection() + OnboardingStateStore.reset() // RootCanvas also short-circuits onboarding when these are true. self.onboardingComplete = false diff --git a/apps/ios/Tests/DeepLinkParserTests.swift b/apps/ios/Tests/DeepLinkParserTests.swift index 7f24aa3e34e..bac3288add1 100644 --- a/apps/ios/Tests/DeepLinkParserTests.swift +++ b/apps/ios/Tests/DeepLinkParserTests.swift @@ -86,7 +86,13 @@ private func agentAction( string: "openclaw://gateway?host=openclaw.local&port=18789&tls=1&token=abc&password=def")! #expect( DeepLinkParser.parse(url) == .gateway( - .init(host: "openclaw.local", port: 18789, tls: true, token: "abc", password: "def"))) + .init( + host: "openclaw.local", + port: 18789, + tls: true, + bootstrapToken: nil, + token: "abc", + password: "def"))) } @Test func parseGatewayLinkRejectsInsecureNonLoopbackWs() { @@ -102,14 +108,15 @@ private func agentAction( } @Test func parseGatewaySetupCodeParsesBase64UrlPayload() { - let payload = #"{"url":"wss://gateway.example.com:443","token":"tok","password":"pw"}"# + let payload = #"{"url":"wss://gateway.example.com:443","bootstrapToken":"tok","password":"pw"}"# let link = GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) #expect(link == .init( host: "gateway.example.com", port: 443, tls: true, - token: "tok", + bootstrapToken: "tok", + token: nil, password: "pw")) } @@ -118,38 +125,40 @@ private func agentAction( } @Test func parseGatewaySetupCodeDefaultsTo443ForWssWithoutPort() { - let payload = #"{"url":"wss://gateway.example.com","token":"tok"}"# + let payload = #"{"url":"wss://gateway.example.com","bootstrapToken":"tok"}"# let link = GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) #expect(link == .init( host: "gateway.example.com", port: 443, tls: true, - token: "tok", + bootstrapToken: "tok", + token: nil, password: nil)) } @Test func parseGatewaySetupCodeRejectsInsecureNonLoopbackWs() { - let payload = #"{"url":"ws://attacker.example:18789","token":"tok"}"# + let payload = #"{"url":"ws://attacker.example:18789","bootstrapToken":"tok"}"# let link = GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) #expect(link == nil) } @Test func parseGatewaySetupCodeRejectsInsecurePrefixBypassHost() { - let payload = #"{"url":"ws://127.attacker.example:18789","token":"tok"}"# + let payload = #"{"url":"ws://127.attacker.example:18789","bootstrapToken":"tok"}"# let link = GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) #expect(link == nil) } @Test func parseGatewaySetupCodeAllowsLoopbackWs() { - let payload = #"{"url":"ws://127.0.0.1:18789","token":"tok"}"# + let payload = #"{"url":"ws://127.0.0.1:18789","bootstrapToken":"tok"}"# let link = GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) #expect(link == .init( host: "127.0.0.1", port: 18789, tls: false, - token: "tok", + bootstrapToken: "tok", + token: nil, password: nil)) } } diff --git a/apps/ios/Tests/IOSGatewayChatTransportTests.swift b/apps/ios/Tests/IOSGatewayChatTransportTests.swift index f49f242ff24..42526dd21c4 100644 --- a/apps/ios/Tests/IOSGatewayChatTransportTests.swift +++ b/apps/ios/Tests/IOSGatewayChatTransportTests.swift @@ -26,5 +26,10 @@ import Testing _ = try await transport.requestHealth(timeoutMs: 250) Issue.record("Expected requestHealth to throw when gateway not connected") } catch {} + + do { + try await transport.resetSession(sessionKey: "node-test") + Issue.record("Expected resetSession to throw when gateway not connected") + } catch {} } } diff --git a/apps/ios/Tests/Info.plist b/apps/ios/Tests/Info.plist index 46e3fb97eb1..5bcf88ff5ad 100644 --- a/apps/ios/Tests/Info.plist +++ b/apps/ios/Tests/Info.plist @@ -17,8 +17,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2026.3.9 + $(OPENCLAW_MARKETING_VERSION) CFBundleVersion - 20260308 + $(OPENCLAW_BUILD_VERSION) diff --git a/apps/ios/Tests/OnboardingStateStoreTests.swift b/apps/ios/Tests/OnboardingStateStoreTests.swift index 30c014647b6..06a6a0f3ec2 100644 --- a/apps/ios/Tests/OnboardingStateStoreTests.swift +++ b/apps/ios/Tests/OnboardingStateStoreTests.swift @@ -39,6 +39,35 @@ import Testing #expect(OnboardingStateStore.shouldPresentOnLaunch(appModel: appModel, defaults: defaults)) } + @Test func firstRunIntroDefaultsToVisibleThenPersists() { + let testDefaults = self.makeDefaults() + let defaults = testDefaults.defaults + defer { self.reset(testDefaults) } + + #expect(OnboardingStateStore.shouldPresentFirstRunIntro(defaults: defaults)) + + OnboardingStateStore.markFirstRunIntroSeen(defaults: defaults) + #expect(!OnboardingStateStore.shouldPresentFirstRunIntro(defaults: defaults)) + } + + @Test @MainActor func resetClearsCompletionAndIntroSeen() { + let testDefaults = self.makeDefaults() + let defaults = testDefaults.defaults + defer { self.reset(testDefaults) } + + OnboardingStateStore.markCompleted(mode: .homeNetwork, defaults: defaults) + OnboardingStateStore.markFirstRunIntroSeen(defaults: defaults) + + OnboardingStateStore.reset(defaults: defaults) + + let appModel = NodeAppModel() + appModel.gatewayServerName = nil + + #expect(OnboardingStateStore.shouldPresentOnLaunch(appModel: appModel, defaults: defaults)) + #expect(OnboardingStateStore.shouldPresentFirstRunIntro(defaults: defaults)) + #expect(OnboardingStateStore.lastMode(defaults: defaults) == .homeNetwork) + } + private struct TestDefaults { var suiteName: String var defaults: UserDefaults diff --git a/apps/ios/WatchApp/Info.plist b/apps/ios/WatchApp/Info.plist index fa45d719b9c..3eea1e6ff09 100644 --- a/apps/ios/WatchApp/Info.plist +++ b/apps/ios/WatchApp/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2026.3.9 + $(OPENCLAW_MARKETING_VERSION) CFBundleVersion - 20260308 + $(OPENCLAW_BUILD_VERSION) WKCompanionAppBundleIdentifier $(OPENCLAW_APP_BUNDLE_ID) WKWatchKitApp diff --git a/apps/ios/WatchExtension/Info.plist b/apps/ios/WatchExtension/Info.plist index 1d898d43757..87313064945 100644 --- a/apps/ios/WatchExtension/Info.plist +++ b/apps/ios/WatchExtension/Info.plist @@ -15,9 +15,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 2026.3.9 + $(OPENCLAW_MARKETING_VERSION) CFBundleVersion - 20260308 + $(OPENCLAW_BUILD_VERSION) NSExtension NSExtensionAttributes diff --git a/apps/ios/fastlane/Fastfile b/apps/ios/fastlane/Fastfile index 33e6bfa8adb..74cbcec4b68 100644 --- a/apps/ios/fastlane/Fastfile +++ b/apps/ios/fastlane/Fastfile @@ -1,8 +1,11 @@ require "shellwords" require "open3" +require "json" default_platform(:ios) +BETA_APP_IDENTIFIER = "ai.openclaw.client" + def load_env_file(path) return unless File.exist?(path) @@ -84,6 +87,111 @@ def read_asc_key_content_from_keychain end end +def repo_root + File.expand_path("../../..", __dir__) +end + +def ios_root + File.expand_path("..", __dir__) +end + +def normalize_release_version(raw_value) + version = raw_value.to_s.strip.sub(/\Av/, "") + UI.user_error!("Missing root package.json version.") unless env_present?(version) + unless version.match?(/\A\d+\.\d+\.\d+(?:[.-]?beta[.-]\d+)?\z/i) + UI.user_error!("Invalid package.json version '#{raw_value}'. Expected 2026.3.13 or 2026.3.13-beta.1.") + end + + version +end + +def read_root_package_version + package_json_path = File.join(repo_root, "package.json") + UI.user_error!("Missing package.json at #{package_json_path}.") unless File.exist?(package_json_path) + + parsed = JSON.parse(File.read(package_json_path)) + normalize_release_version(parsed["version"]) +rescue JSON::ParserError => e + UI.user_error!("Invalid package.json at #{package_json_path}: #{e.message}") +end + +def short_release_version(version) + normalize_release_version(version).sub(/([.-]?beta[.-]\d+)\z/i, "") +end + +def shell_join(parts) + Shellwords.join(parts.compact) +end + +def resolve_beta_build_number(api_key:, version:) + explicit = ENV["IOS_BETA_BUILD_NUMBER"] + if env_present?(explicit) + UI.user_error!("Invalid IOS_BETA_BUILD_NUMBER '#{explicit}'. Expected digits only.") unless explicit.match?(/\A\d+\z/) + UI.message("Using explicit iOS beta build number #{explicit}.") + return explicit + end + + short_version = short_release_version(version) + latest_build = latest_testflight_build_number( + api_key: api_key, + app_identifier: BETA_APP_IDENTIFIER, + version: short_version, + initial_build_number: 0 + ) + next_build = latest_build.to_i + 1 + UI.message("Resolved iOS beta build number #{next_build} for #{short_version} (latest TestFlight build: #{latest_build}).") + next_build.to_s +end + +def beta_build_number_needs_asc_auth? + explicit = ENV["IOS_BETA_BUILD_NUMBER"] + !env_present?(explicit) +end + +def prepare_beta_release!(version:, build_number:) + script_path = File.join(repo_root, "scripts", "ios-beta-prepare.sh") + UI.message("Preparing iOS beta release #{version} (build #{build_number}).") + sh(shell_join(["bash", script_path, "--build-number", build_number])) + + beta_xcconfig = File.join(ios_root, "build", "BetaRelease.xcconfig") + UI.user_error!("Missing beta xcconfig at #{beta_xcconfig}.") unless File.exist?(beta_xcconfig) + + ENV["XCODE_XCCONFIG_FILE"] = beta_xcconfig + beta_xcconfig +end + +def build_beta_release(context) + version = context[:version] + output_directory = File.join("build", "beta") + archive_path = File.join(output_directory, "OpenClaw-#{version}.xcarchive") + + build_app( + project: "OpenClaw.xcodeproj", + scheme: "OpenClaw", + configuration: "Release", + export_method: "app-store", + clean: true, + skip_profile_detection: true, + build_path: "build", + archive_path: archive_path, + output_directory: output_directory, + output_name: "OpenClaw-#{version}.ipa", + xcargs: "-allowProvisioningUpdates", + export_xcargs: "-allowProvisioningUpdates", + export_options: { + signingStyle: "automatic" + } + ) + + { + archive_path: archive_path, + build_number: context[:build_number], + ipa_path: lane_context[SharedValues::IPA_OUTPUT_PATH], + short_version: context[:short_version], + version: version + } +end + platform :ios do private_lane :asc_api_key do load_env_file(File.join(__dir__, ".env")) @@ -132,38 +240,48 @@ platform :ios do api_key end - desc "Build + upload to TestFlight" + private_lane :prepare_beta_context do |options| + require_api_key = options[:require_api_key] == true + needs_api_key = require_api_key || beta_build_number_needs_asc_auth? + api_key = needs_api_key ? asc_api_key : nil + version = read_root_package_version + build_number = resolve_beta_build_number(api_key: api_key, version: version) + beta_xcconfig = prepare_beta_release!(version: version, build_number: build_number) + + { + api_key: api_key, + beta_xcconfig: beta_xcconfig, + build_number: build_number, + short_version: short_release_version(version), + version: version + } + end + + desc "Build a beta archive locally without uploading" + lane :beta_archive do + context = prepare_beta_context(require_api_key: false) + build = build_beta_release(context) + UI.success("Built iOS beta archive: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}") + build + ensure + ENV.delete("XCODE_XCCONFIG_FILE") + end + + desc "Build + upload a beta to TestFlight" lane :beta do - api_key = asc_api_key - - team_id = ENV["IOS_DEVELOPMENT_TEAM"] - if team_id.nil? || team_id.strip.empty? - helper_path = File.expand_path("../../../scripts/ios-team-id.sh", __dir__) - if File.exist?(helper_path) - # Keep CI/local compatibility where teams are present in keychain but not Xcode account metadata. - team_id = sh("IOS_ALLOW_KEYCHAIN_TEAM_FALLBACK=1 bash #{helper_path.shellescape}").strip - end - end - UI.user_error!("Missing IOS_DEVELOPMENT_TEAM (Apple Team ID). Add it to fastlane/.env or export it in your shell.") if team_id.nil? || team_id.strip.empty? - - build_app( - project: "OpenClaw.xcodeproj", - scheme: "OpenClaw", - export_method: "app-store", - clean: true, - skip_profile_detection: true, - xcargs: "DEVELOPMENT_TEAM=#{team_id} -allowProvisioningUpdates", - export_xcargs: "-allowProvisioningUpdates", - export_options: { - signingStyle: "automatic" - } - ) + context = prepare_beta_context(require_api_key: true) + build = build_beta_release(context) upload_to_testflight( - api_key: api_key, + api_key: context[:api_key], + ipa: build[:ipa_path], skip_waiting_for_build_processing: true, uses_non_exempt_encryption: false ) + + UI.success("Uploaded iOS beta: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}") + ensure + ENV.delete("XCODE_XCCONFIG_FILE") end desc "Upload App Store metadata (and optionally screenshots)" diff --git a/apps/ios/fastlane/SETUP.md b/apps/ios/fastlane/SETUP.md index 8dccf264b41..67d4fcc843a 100644 --- a/apps/ios/fastlane/SETUP.md +++ b/apps/ios/fastlane/SETUP.md @@ -32,9 +32,9 @@ ASC_KEYCHAIN_ACCOUNT=YOUR_MAC_USERNAME Optional app targeting variables (helpful if Fastlane cannot auto-resolve app by bundle): ```bash -ASC_APP_IDENTIFIER=ai.openclaw.ios +ASC_APP_IDENTIFIER=ai.openclaw.client # or -ASC_APP_ID=6760218713 +ASC_APP_ID=YOUR_APP_STORE_CONNECT_APP_ID ``` File-based fallback (CI/non-macOS): @@ -60,9 +60,37 @@ cd apps/ios fastlane ios auth_check ``` -Run: +ASC auth is only required when: + +- uploading to TestFlight +- auto-resolving the next build number from App Store Connect + +If you pass `--build-number` to `pnpm ios:beta:archive`, the local archive path does not need ASC auth. + +Archive locally without upload: + +```bash +pnpm ios:beta:archive +``` + +Upload to TestFlight: + +```bash +pnpm ios:beta +``` + +Direct Fastlane entry point: ```bash cd apps/ios -fastlane beta +fastlane ios beta ``` + +Versioning rules: + +- Root `package.json.version` is the single source of truth for iOS +- Use `YYYY.M.D` for stable versions and `YYYY.M.D-beta.N` for beta versions +- Fastlane stamps `CFBundleShortVersionString` to `YYYY.M.D` +- Fastlane resolves `CFBundleVersion` as the next integer TestFlight build number for that short version +- The beta flow regenerates `apps/ios/OpenClaw.xcodeproj` from `apps/ios/project.yml` before archiving +- Local beta signing uses a temporary generated xcconfig and leaves local development signing overrides untouched diff --git a/apps/ios/fastlane/metadata/README.md b/apps/ios/fastlane/metadata/README.md index 74eb7df87d3..07e7824311f 100644 --- a/apps/ios/fastlane/metadata/README.md +++ b/apps/ios/fastlane/metadata/README.md @@ -6,7 +6,7 @@ This directory is used by `fastlane deliver` for App Store Connect text metadata ```bash cd apps/ios -ASC_APP_ID=6760218713 \ +ASC_APP_ID=YOUR_APP_STORE_CONNECT_APP_ID \ DELIVER_METADATA=1 fastlane ios metadata ``` diff --git a/apps/ios/project.yml b/apps/ios/project.yml index 0664db9c6be..53e6489a25b 100644 --- a/apps/ios/project.yml +++ b/apps/ios/project.yml @@ -98,6 +98,17 @@ targets: SUPPORTS_LIVE_ACTIVITIES: YES ENABLE_APPINTENTS_METADATA: NO ENABLE_APP_INTENTS_METADATA_GENERATION: NO + configs: + Debug: + OPENCLAW_PUSH_TRANSPORT: direct + OPENCLAW_PUSH_DISTRIBUTION: local + OPENCLAW_PUSH_RELAY_BASE_URL: "" + OPENCLAW_PUSH_APNS_ENVIRONMENT: sandbox + Release: + OPENCLAW_PUSH_TRANSPORT: direct + OPENCLAW_PUSH_DISTRIBUTION: local + OPENCLAW_PUSH_RELAY_BASE_URL: "" + OPENCLAW_PUSH_APNS_ENVIRONMENT: production info: path: Sources/Info.plist properties: @@ -107,8 +118,8 @@ targets: - CFBundleURLName: ai.openclaw.ios CFBundleURLSchemes: - openclaw - CFBundleShortVersionString: "2026.3.9" - CFBundleVersion: "20260308" + CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)" + CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)" UILaunchScreen: {} UIApplicationSceneManifest: UIApplicationSupportsMultipleScenes: false @@ -131,6 +142,10 @@ targets: NSSpeechRecognitionUsageDescription: OpenClaw uses on-device speech recognition for voice wake. NSSupportsLiveActivities: true ITSAppUsesNonExemptEncryption: false + OpenClawPushTransport: "$(OPENCLAW_PUSH_TRANSPORT)" + OpenClawPushDistribution: "$(OPENCLAW_PUSH_DISTRIBUTION)" + OpenClawPushRelayBaseURL: "$(OPENCLAW_PUSH_RELAY_BASE_URL)" + OpenClawPushAPNsEnvironment: "$(OPENCLAW_PUSH_APNS_ENVIRONMENT)" UISupportedInterfaceOrientations: - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown @@ -168,8 +183,8 @@ targets: path: ShareExtension/Info.plist properties: CFBundleDisplayName: OpenClaw Share - CFBundleShortVersionString: "2026.3.9" - CFBundleVersion: "20260308" + CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)" + CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)" NSExtension: NSExtensionPointIdentifier: com.apple.share-services NSExtensionPrincipalClass: "$(PRODUCT_MODULE_NAME).ShareViewController" @@ -205,8 +220,8 @@ targets: path: ActivityWidget/Info.plist properties: CFBundleDisplayName: OpenClaw Activity - CFBundleShortVersionString: "2026.3.9" - CFBundleVersion: "20260308" + CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)" + CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)" NSSupportsLiveActivities: true NSExtension: NSExtensionPointIdentifier: com.apple.widgetkit-extension @@ -224,6 +239,7 @@ targets: Release: Config/Signing.xcconfig settings: base: + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon ENABLE_APPINTENTS_METADATA: NO ENABLE_APP_INTENTS_METADATA_GENERATION: NO PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)" @@ -231,8 +247,8 @@ targets: path: WatchApp/Info.plist properties: CFBundleDisplayName: OpenClaw - CFBundleShortVersionString: "2026.3.9" - CFBundleVersion: "20260308" + CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)" + CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)" WKCompanionAppBundleIdentifier: "$(OPENCLAW_APP_BUNDLE_ID)" WKWatchKitApp: true @@ -256,8 +272,8 @@ targets: path: WatchExtension/Info.plist properties: CFBundleDisplayName: OpenClaw - CFBundleShortVersionString: "2026.3.9" - CFBundleVersion: "20260308" + CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)" + CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)" NSExtension: NSExtensionAttributes: WKAppBundleIdentifier: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)" @@ -293,8 +309,8 @@ targets: path: Tests/Info.plist properties: CFBundleDisplayName: OpenClawTests - CFBundleShortVersionString: "2026.3.9" - CFBundleVersion: "20260308" + CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)" + CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)" OpenClawLogicTests: type: bundle.unit-test @@ -319,5 +335,5 @@ targets: path: Tests/Info.plist properties: CFBundleDisplayName: OpenClawLogicTests - CFBundleShortVersionString: "2026.3.9" - CFBundleVersion: "20260308" + CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)" + CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)" diff --git a/apps/macos/Sources/OpenClaw/AppState.swift b/apps/macos/Sources/OpenClaw/AppState.swift index 5e8238ebe92..d503686ba57 100644 --- a/apps/macos/Sources/OpenClaw/AppState.swift +++ b/apps/macos/Sources/OpenClaw/AppState.swift @@ -600,30 +600,29 @@ final class AppState { private func syncGatewayConfigIfNeeded() { guard !self.isPreview, !self.isInitializing else { return } - let connectionMode = self.connectionMode - let remoteTarget = self.remoteTarget - let remoteIdentity = self.remoteIdentity - let remoteTransport = self.remoteTransport - let remoteUrl = self.remoteUrl - let remoteToken = self.remoteToken - let remoteTokenDirty = self.remoteTokenDirty - Task { @MainActor in - // Keep app-only connection settings local to avoid overwriting remote gateway config. - let synced = Self.syncedGatewayRoot( - currentRoot: OpenClawConfigFile.loadDict(), - connectionMode: connectionMode, - remoteTransport: remoteTransport, - remoteTarget: remoteTarget, - remoteIdentity: remoteIdentity, - remoteUrl: remoteUrl, - remoteToken: remoteToken, - remoteTokenDirty: remoteTokenDirty) - guard synced.changed else { return } - OpenClawConfigFile.saveDict(synced.root) + self.syncGatewayConfigNow() } } + @MainActor + func syncGatewayConfigNow() { + guard !self.isPreview, !self.isInitializing else { return } + + // Keep app-only connection settings local to avoid overwriting remote gateway config. + let synced = Self.syncedGatewayRoot( + currentRoot: OpenClawConfigFile.loadDict(), + connectionMode: self.connectionMode, + remoteTransport: self.remoteTransport, + remoteTarget: self.remoteTarget, + remoteIdentity: self.remoteIdentity, + remoteUrl: self.remoteUrl, + remoteToken: self.remoteToken, + remoteTokenDirty: self.remoteTokenDirty) + guard synced.changed else { return } + OpenClawConfigFile.saveDict(synced.root) + } + func triggerVoiceEars(ttl: TimeInterval? = 5) { self.earBoostTask?.cancel() self.earBoostActive = true diff --git a/apps/macos/Sources/OpenClaw/CanvasA2UIActionMessageHandler.swift b/apps/macos/Sources/OpenClaw/CanvasA2UIActionMessageHandler.swift index 4f47ea835df..0599f4ab3a6 100644 --- a/apps/macos/Sources/OpenClaw/CanvasA2UIActionMessageHandler.swift +++ b/apps/macos/Sources/OpenClaw/CanvasA2UIActionMessageHandler.swift @@ -8,6 +8,24 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler { static let messageName = "openclawCanvasA2UIAction" static let allMessageNames = [messageName] + // Compatibility helper for debug/test shims. Runtime dispatch remains + // limited to in-app canvas schemes in `didReceive`. + static func isLocalNetworkCanvasURL(_ url: URL) -> Bool { + guard let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" else { + return false + } + guard let host = url.host?.lowercased(), !host.isEmpty else { + return false + } + if host == "localhost" { + return true + } + guard let ip = Self.parseIPv4(host) else { + return false + } + return Self.isLocalNetworkIPv4(ip) + } + private let sessionKey: String init(sessionKey: String) { @@ -18,13 +36,10 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler { func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) { guard Self.allMessageNames.contains(message.name) else { return } - // Only accept actions from local Canvas content (not arbitrary web pages). + // Only accept actions from the in-app canvas scheme. Local-network HTTP + // pages are regular web content and must not get direct agent dispatch. guard let webView = message.webView, let url = webView.url else { return } - if let scheme = url.scheme, CanvasScheme.allSchemes.contains(scheme) { - // ok - } else if Self.isLocalNetworkCanvasURL(url) { - // ok - } else { + guard let scheme = url.scheme, CanvasScheme.allSchemes.contains(scheme) else { return } @@ -108,9 +123,23 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler { } } - static func isLocalNetworkCanvasURL(_ url: URL) -> Bool { - LocalNetworkURLSupport.isLocalNetworkHTTPURL(url) + private static func parseIPv4(_ host: String) -> (UInt8, UInt8, UInt8, UInt8)? { + let parts = host.split(separator: ".", omittingEmptySubsequences: false) + guard parts.count == 4 else { return nil } + let bytes = parts.compactMap { UInt8($0) } + guard bytes.count == 4 else { return nil } + return (bytes[0], bytes[1], bytes[2], bytes[3]) } + private static func isLocalNetworkIPv4(_ ip: (UInt8, UInt8, UInt8, UInt8)) -> Bool { + let (a, b, _, _) = ip + if a == 10 { return true } + if a == 172, (16...31).contains(Int(b)) { return true } + if a == 192, b == 168 { return true } + if a == 127 { return true } + if a == 169, b == 254 { return true } + if a == 100, (64...127).contains(Int(b)) { return true } + return false + } // Formatting helpers live in OpenClawKit (`OpenClawCanvasA2UIAction`). } diff --git a/apps/macos/Sources/OpenClaw/CanvasSchemeHandler.swift b/apps/macos/Sources/OpenClaw/CanvasSchemeHandler.swift index 6905af50014..9b4c8e5ebad 100644 --- a/apps/macos/Sources/OpenClaw/CanvasSchemeHandler.swift +++ b/apps/macos/Sources/OpenClaw/CanvasSchemeHandler.swift @@ -81,22 +81,23 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler { return self.html("Not Found", title: "Canvas: 404") } - // Directory traversal guard: served files must live under the session root. - let standardizedRoot = sessionRoot.standardizedFileURL - let standardizedFile = fileURL.standardizedFileURL - guard standardizedFile.path.hasPrefix(standardizedRoot.path) else { + // Resolve symlinks before enforcing the session-root boundary so links inside + // the canvas tree cannot escape to arbitrary host files. + let resolvedRoot = sessionRoot.resolvingSymlinksInPath().standardizedFileURL + let resolvedFile = fileURL.resolvingSymlinksInPath().standardizedFileURL + guard self.isFileURL(resolvedFile, withinDirectory: resolvedRoot) else { return self.html("Forbidden", title: "Canvas: 403") } do { - let data = try Data(contentsOf: standardizedFile) - let mime = CanvasScheme.mimeType(forExtension: standardizedFile.pathExtension) - let servedPath = standardizedFile.path + let data = try Data(contentsOf: resolvedFile) + let mime = CanvasScheme.mimeType(forExtension: resolvedFile.pathExtension) + let servedPath = resolvedFile.path canvasLogger.debug( "served \(session, privacy: .public)/\(path, privacy: .public) -> \(servedPath, privacy: .public)") return CanvasResponse(mime: mime, data: data) } catch { - let failedPath = standardizedFile.path + let failedPath = resolvedFile.path let errorText = error.localizedDescription canvasLogger .error( @@ -145,6 +146,11 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler { return nil } + private func isFileURL(_ fileURL: URL, withinDirectory rootURL: URL) -> Bool { + let rootPath = rootURL.path.hasSuffix("/") ? rootURL.path : rootURL.path + "/" + return fileURL.path == rootURL.path || fileURL.path.hasPrefix(rootPath) + } + private func html(_ body: String, title: String = "Canvas") -> CanvasResponse { let html = """ diff --git a/apps/macos/Sources/OpenClaw/CanvasWindowController.swift b/apps/macos/Sources/OpenClaw/CanvasWindowController.swift index 8017304087e..0032bfff0fa 100644 --- a/apps/macos/Sources/OpenClaw/CanvasWindowController.swift +++ b/apps/macos/Sources/OpenClaw/CanvasWindowController.swift @@ -50,21 +50,24 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS // Bridge A2UI "a2uiaction" DOM events back into the native agent loop. // - // Prefer WKScriptMessageHandler when WebKit exposes it, otherwise fall back to an unattended deep link - // (includes the app-generated key so it won't prompt). + // Keep the bridge on the trusted in-app canvas scheme only, and do not + // expose unattended deep-link credentials to page JavaScript. canvasWindowLogger.debug("CanvasWindowController init building A2UI bridge script") - let deepLinkKey = DeepLinkHandler.currentCanvasKey() let injectedSessionKey = sessionKey.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "main" + let allowedSchemesJSON = ( + try? String( + data: JSONSerialization.data(withJSONObject: CanvasScheme.allSchemes), + encoding: .utf8) + ) ?? "[]" let bridgeScript = """ (() => { try { - const allowedSchemes = \(String(describing: CanvasScheme.allSchemes)); + const allowedSchemes = \(allowedSchemesJSON); const protocol = location.protocol.replace(':', ''); if (!allowedSchemes.includes(protocol)) return; if (globalThis.__openclawA2UIBridgeInstalled) return; globalThis.__openclawA2UIBridgeInstalled = true; - const deepLinkKey = \(Self.jsStringLiteral(deepLinkKey)); const sessionKey = \(Self.jsStringLiteral(injectedSessionKey)); const machineName = \(Self.jsStringLiteral(InstanceIdentity.displayName)); const instanceId = \(Self.jsStringLiteral(InstanceIdentity.instanceId)); @@ -104,24 +107,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS return; } - const ctx = userAction.context ? (' ctx=' + JSON.stringify(userAction.context)) : ''; - const message = - 'CANVAS_A2UI action=' + userAction.name + - ' session=' + sessionKey + - ' surface=' + userAction.surfaceId + - ' component=' + (userAction.sourceComponentId || '-') + - ' host=' + machineName.replace(/\\s+/g, '_') + - ' instance=' + instanceId + - ctx + - ' default=update_canvas'; - const params = new URLSearchParams(); - params.set('message', message); - params.set('sessionKey', sessionKey); - params.set('thinking', 'low'); - params.set('deliver', 'false'); - params.set('channel', 'last'); - params.set('key', deepLinkKey); - location.href = 'openclaw://agent?' + params.toString(); + // Without the native handler, fail closed instead of exposing an + // unattended deep-link credential to page JavaScript. } catch {} }, true); } catch {} diff --git a/apps/macos/Sources/OpenClaw/ControlChannel.swift b/apps/macos/Sources/OpenClaw/ControlChannel.swift index aecf9539ef5..607aab47940 100644 --- a/apps/macos/Sources/OpenClaw/ControlChannel.swift +++ b/apps/macos/Sources/OpenClaw/ControlChannel.swift @@ -188,6 +188,10 @@ final class ControlChannel { return desc } + if let authIssue = RemoteGatewayAuthIssue(error: error) { + return authIssue.statusMessage + } + // If the gateway explicitly rejects the hello (e.g., auth/token mismatch), surface it. if let urlErr = error as? URLError, urlErr.code == .dataNotAllowed // used for WS close 1008 auth failures @@ -320,6 +324,8 @@ final class ControlChannel { switch source { case .deviceToken: return "Auth: device token (paired device)" + case .bootstrapToken: + return "Auth: bootstrap token (setup code)" case .sharedToken: return "Auth: shared token (\(isRemote ? "gateway.remote.token" : "gateway.auth.token"))" case .password: diff --git a/apps/macos/Sources/OpenClaw/CronJobEditor+Helpers.swift b/apps/macos/Sources/OpenClaw/CronJobEditor+Helpers.swift index 26b64ea7c65..41b98111b4e 100644 --- a/apps/macos/Sources/OpenClaw/CronJobEditor+Helpers.swift +++ b/apps/macos/Sources/OpenClaw/CronJobEditor+Helpers.swift @@ -16,7 +16,14 @@ extension CronJobEditor { self.agentId = job.agentId ?? "" self.enabled = job.enabled self.deleteAfterRun = job.deleteAfterRun ?? false - self.sessionTarget = job.sessionTarget + switch job.parsedSessionTarget { + case .predefined(let target): + self.sessionTarget = target + self.preservedSessionTargetRaw = nil + case .session(let id): + self.sessionTarget = .isolated + self.preservedSessionTargetRaw = "session:\(id)" + } self.wakeMode = job.wakeMode switch job.schedule { @@ -51,7 +58,7 @@ extension CronJobEditor { self.channel = trimmed.isEmpty ? "last" : trimmed self.to = delivery.to ?? "" self.bestEffortDeliver = delivery.bestEffort ?? false - } else if self.sessionTarget == .isolated { + } else if self.isIsolatedLikeSessionTarget { self.deliveryMode = .announce } } @@ -80,7 +87,7 @@ extension CronJobEditor { "name": name, "enabled": self.enabled, "schedule": schedule, - "sessionTarget": self.sessionTarget.rawValue, + "sessionTarget": self.effectiveSessionTargetRaw, "wakeMode": self.wakeMode.rawValue, "payload": payload, ] @@ -92,7 +99,7 @@ extension CronJobEditor { root["agentId"] = NSNull() } - if self.sessionTarget == .isolated { + if self.isIsolatedLikeSessionTarget { root["delivery"] = self.buildDelivery() } @@ -160,7 +167,7 @@ extension CronJobEditor { } func buildSelectedPayload() throws -> [String: Any] { - if self.sessionTarget == .isolated { return self.buildAgentTurnPayload() } + if self.isIsolatedLikeSessionTarget { return self.buildAgentTurnPayload() } switch self.payloadKind { case .systemEvent: let text = self.trimmed(self.systemEventText) @@ -171,7 +178,7 @@ extension CronJobEditor { } func validateSessionTarget(_ payload: [String: Any]) throws { - if self.sessionTarget == .main, payload["kind"] as? String == "agentTurn" { + if self.effectiveSessionTargetRaw == "main", payload["kind"] as? String == "agentTurn" { throw NSError( domain: "Cron", code: 0, @@ -181,7 +188,7 @@ extension CronJobEditor { ]) } - if self.sessionTarget == .isolated, payload["kind"] as? String == "systemEvent" { + if self.effectiveSessionTargetRaw != "main", payload["kind"] as? String == "systemEvent" { throw NSError( domain: "Cron", code: 0, @@ -257,6 +264,17 @@ extension CronJobEditor { return Int(floor(n * factor)) } + var effectiveSessionTargetRaw: String { + if self.sessionTarget == .isolated, let preserved = self.preservedSessionTargetRaw?.trimmingCharacters(in: .whitespacesAndNewlines), !preserved.isEmpty { + return preserved + } + return self.sessionTarget.rawValue + } + + var isIsolatedLikeSessionTarget: Bool { + self.effectiveSessionTargetRaw != "main" + } + func formatDuration(ms: Int) -> String { DurationFormattingSupport.conciseDuration(ms: ms) } diff --git a/apps/macos/Sources/OpenClaw/CronJobEditor.swift b/apps/macos/Sources/OpenClaw/CronJobEditor.swift index a7d88a4f2fb..292f3a63284 100644 --- a/apps/macos/Sources/OpenClaw/CronJobEditor.swift +++ b/apps/macos/Sources/OpenClaw/CronJobEditor.swift @@ -16,7 +16,7 @@ struct CronJobEditor: View { + "Use an isolated session for agent turns so your main chat stays clean." static let sessionTargetNote = "Main jobs post a system event into the current main session. " - + "Isolated jobs run OpenClaw in a dedicated session and can announce results to a channel." + + "Current and isolated-style jobs run agent turns and can announce results to a channel." static let scheduleKindNote = "“At” runs once, “Every” repeats with a duration, “Cron” uses a 5-field Unix expression." static let isolatedPayloadNote = @@ -29,6 +29,7 @@ struct CronJobEditor: View { @State var agentId: String = "" @State var enabled: Bool = true @State var sessionTarget: CronSessionTarget = .main + @State var preservedSessionTargetRaw: String? @State var wakeMode: CronWakeMode = .now @State var deleteAfterRun: Bool = false @@ -117,6 +118,7 @@ struct CronJobEditor: View { Picker("", selection: self.$sessionTarget) { Text("main").tag(CronSessionTarget.main) Text("isolated").tag(CronSessionTarget.isolated) + Text("current").tag(CronSessionTarget.current) } .labelsHidden() .pickerStyle(.segmented) @@ -209,7 +211,7 @@ struct CronJobEditor: View { GroupBox("Payload") { VStack(alignment: .leading, spacing: 10) { - if self.sessionTarget == .isolated { + if self.isIsolatedLikeSessionTarget { Text(Self.isolatedPayloadNote) .font(.footnote) .foregroundStyle(.secondary) @@ -289,8 +291,11 @@ struct CronJobEditor: View { self.sessionTarget = .isolated } } - .onChange(of: self.sessionTarget) { _, newValue in - if newValue == .isolated { + .onChange(of: self.sessionTarget) { oldValue, newValue in + if oldValue != newValue { + self.preservedSessionTargetRaw = nil + } + if newValue != .main { self.payloadKind = .agentTurn } else if newValue == .main, self.payloadKind == .agentTurn { self.payloadKind = .systemEvent diff --git a/apps/macos/Sources/OpenClaw/CronModels.swift b/apps/macos/Sources/OpenClaw/CronModels.swift index e0ce46c13da..78016ff9f88 100644 --- a/apps/macos/Sources/OpenClaw/CronModels.swift +++ b/apps/macos/Sources/OpenClaw/CronModels.swift @@ -3,12 +3,39 @@ import Foundation enum CronSessionTarget: String, CaseIterable, Identifiable, Codable { case main case isolated + case current var id: String { self.rawValue } } +enum CronCustomSessionTarget: Codable, Equatable { + case predefined(CronSessionTarget) + case session(id: String) + + var rawValue: String { + switch self { + case .predefined(let target): + return target.rawValue + case .session(let id): + return "session:\(id)" + } + } + + static func from(_ value: String) -> CronCustomSessionTarget { + if let predefined = CronSessionTarget(rawValue: value) { + return .predefined(predefined) + } + if value.hasPrefix("session:") { + let sessionId = String(value.dropFirst(8)) + return .session(id: sessionId) + } + // Fallback to isolated for unknown values + return .predefined(.isolated) + } +} + enum CronWakeMode: String, CaseIterable, Identifiable, Codable { case now case nextHeartbeat = "next-heartbeat" @@ -204,12 +231,134 @@ struct CronJob: Identifiable, Codable, Equatable { let createdAtMs: Int let updatedAtMs: Int let schedule: CronSchedule - let sessionTarget: CronSessionTarget + private let sessionTargetRaw: String let wakeMode: CronWakeMode let payload: CronPayload let delivery: CronDelivery? let state: CronJobState + enum CodingKeys: String, CodingKey { + case id + case agentId + case name + case description + case enabled + case deleteAfterRun + case createdAtMs + case updatedAtMs + case schedule + case sessionTargetRaw = "sessionTarget" + case wakeMode + case payload + case delivery + case state + } + + init( + id: String, + agentId: String?, + name: String, + description: String?, + enabled: Bool, + deleteAfterRun: Bool?, + createdAtMs: Int, + updatedAtMs: Int, + schedule: CronSchedule, + sessionTarget: CronSessionTarget, + wakeMode: CronWakeMode, + payload: CronPayload, + delivery: CronDelivery?, + state: CronJobState) + { + self.init( + id: id, + agentId: agentId, + name: name, + description: description, + enabled: enabled, + deleteAfterRun: deleteAfterRun, + createdAtMs: createdAtMs, + updatedAtMs: updatedAtMs, + schedule: schedule, + sessionTarget: .predefined(sessionTarget), + wakeMode: wakeMode, + payload: payload, + delivery: delivery, + state: state) + } + + init( + id: String, + agentId: String?, + name: String, + description: String?, + enabled: Bool, + deleteAfterRun: Bool?, + createdAtMs: Int, + updatedAtMs: Int, + schedule: CronSchedule, + sessionTarget: CronCustomSessionTarget, + wakeMode: CronWakeMode, + payload: CronPayload, + delivery: CronDelivery?, + state: CronJobState) + { + self.id = id + self.agentId = agentId + self.name = name + self.description = description + self.enabled = enabled + self.deleteAfterRun = deleteAfterRun + self.createdAtMs = createdAtMs + self.updatedAtMs = updatedAtMs + self.schedule = schedule + self.sessionTargetRaw = sessionTarget.rawValue + self.wakeMode = wakeMode + self.payload = payload + self.delivery = delivery + self.state = state + } + + /// Parsed session target (predefined or custom session ID) + var parsedSessionTarget: CronCustomSessionTarget { + CronCustomSessionTarget.from(self.sessionTargetRaw) + } + + /// Compatibility shim for existing editor/UI code paths that still use the + /// predefined enum. + var sessionTarget: CronSessionTarget { + switch self.parsedSessionTarget { + case .predefined(let target): + return target + case .session: + return .isolated + } + } + + var sessionTargetDisplayValue: String { + self.parsedSessionTarget.rawValue + } + + var transcriptSessionKey: String? { + switch self.parsedSessionTarget { + case .predefined(.main): + return nil + case .predefined(.isolated), .predefined(.current): + return "cron:\(self.id)" + case .session(let id): + return id + } + } + + var supportsAnnounceDelivery: Bool { + switch self.parsedSessionTarget { + case .predefined(.main): + return false + case .predefined(.isolated), .predefined(.current), .session: + return true + } + } + var displayName: String { let trimmed = self.name.trimmingCharacters(in: .whitespacesAndNewlines) return trimmed.isEmpty ? "Untitled job" : trimmed diff --git a/apps/macos/Sources/OpenClaw/CronSettings+Rows.swift b/apps/macos/Sources/OpenClaw/CronSettings+Rows.swift index 69655bdc302..85e45928853 100644 --- a/apps/macos/Sources/OpenClaw/CronSettings+Rows.swift +++ b/apps/macos/Sources/OpenClaw/CronSettings+Rows.swift @@ -18,7 +18,7 @@ extension CronSettings { } } HStack(spacing: 6) { - StatusPill(text: job.sessionTarget.rawValue, tint: .secondary) + StatusPill(text: job.sessionTargetDisplayValue, tint: .secondary) StatusPill(text: job.wakeMode.rawValue, tint: .secondary) if let agentId = job.agentId, !agentId.isEmpty { StatusPill(text: "agent \(agentId)", tint: .secondary) @@ -34,9 +34,9 @@ extension CronSettings { @ViewBuilder func jobContextMenu(_ job: CronJob) -> some View { Button("Run now") { Task { await self.store.runJob(id: job.id, force: true) } } - if job.sessionTarget == .isolated { + if let transcriptSessionKey = job.transcriptSessionKey { Button("Open transcript") { - WebChatManager.shared.show(sessionKey: "cron:\(job.id)") + WebChatManager.shared.show(sessionKey: transcriptSessionKey) } } Divider() @@ -75,9 +75,9 @@ extension CronSettings { .labelsHidden() Button("Run") { Task { await self.store.runJob(id: job.id, force: true) } } .buttonStyle(.borderedProminent) - if job.sessionTarget == .isolated { + if let transcriptSessionKey = job.transcriptSessionKey { Button("Transcript") { - WebChatManager.shared.show(sessionKey: "cron:\(job.id)") + WebChatManager.shared.show(sessionKey: transcriptSessionKey) } .buttonStyle(.bordered) } @@ -103,7 +103,7 @@ extension CronSettings { if let agentId = job.agentId, !agentId.isEmpty { LabeledContent("Agent") { Text(agentId) } } - LabeledContent("Session") { Text(job.sessionTarget.rawValue) } + LabeledContent("Session") { Text(job.sessionTargetDisplayValue) } LabeledContent("Wake") { Text(job.wakeMode.rawValue) } LabeledContent("Next run") { if let date = job.nextRunDate { @@ -224,7 +224,7 @@ extension CronSettings { HStack(spacing: 8) { if let thinking, !thinking.isEmpty { StatusPill(text: "think \(thinking)", tint: .secondary) } if let timeoutSeconds { StatusPill(text: "\(timeoutSeconds)s", tint: .secondary) } - if job.sessionTarget == .isolated { + if job.supportsAnnounceDelivery { let delivery = job.delivery if let delivery { if delivery.mode == .announce { diff --git a/apps/macos/Sources/OpenClaw/ExecApprovalEvaluation.swift b/apps/macos/Sources/OpenClaw/ExecApprovalEvaluation.swift index c7d9d0928e1..a36e58db1d8 100644 --- a/apps/macos/Sources/OpenClaw/ExecApprovalEvaluation.swift +++ b/apps/macos/Sources/OpenClaw/ExecApprovalEvaluation.swift @@ -45,8 +45,8 @@ enum ExecApprovalEvaluator { let skillAllow: Bool if approvals.agent.autoAllowSkills, !allowlistResolutions.isEmpty { - let bins = await SkillBinsCache.shared.currentBins() - skillAllow = allowlistResolutions.allSatisfy { bins.contains($0.executableName) } + let bins = await SkillBinsCache.shared.currentTrust() + skillAllow = self.isSkillAutoAllowed(allowlistResolutions, trustedBinsByName: bins) } else { skillAllow = false } @@ -65,4 +65,26 @@ enum ExecApprovalEvaluator { allowlistMatch: allowlistSatisfied ? allowlistMatches.first : nil, skillAllow: skillAllow) } + + static func isSkillAutoAllowed( + _ resolutions: [ExecCommandResolution], + trustedBinsByName: [String: Set]) -> Bool + { + guard !resolutions.isEmpty, !trustedBinsByName.isEmpty else { return false } + return resolutions.allSatisfy { resolution in + guard let executableName = SkillBinsCache.normalizeSkillBinName(resolution.executableName), + let resolvedPath = SkillBinsCache.normalizeResolvedPath(resolution.resolvedPath) + else { + return false + } + return trustedBinsByName[executableName]?.contains(resolvedPath) == true + } + } + + static func _testIsSkillAutoAllowed( + _ resolutions: [ExecCommandResolution], + trustedBinsByName: [String: Set]) -> Bool + { + self.isSkillAutoAllowed(resolutions, trustedBinsByName: trustedBinsByName) + } } diff --git a/apps/macos/Sources/OpenClaw/ExecApprovals.swift b/apps/macos/Sources/OpenClaw/ExecApprovals.swift index ba49b37cd9f..141da33ad48 100644 --- a/apps/macos/Sources/OpenClaw/ExecApprovals.swift +++ b/apps/macos/Sources/OpenClaw/ExecApprovals.swift @@ -370,6 +370,17 @@ enum ExecApprovalsStore { static func resolve(agentId: String?) -> ExecApprovalsResolved { let file = self.ensureFile() + return self.resolveFromFile(file, agentId: agentId) + } + + /// Read-only resolve: loads file without writing (no ensureFile side effects). + /// Safe to call from background threads / off MainActor. + static func resolveReadOnly(agentId: String?) -> ExecApprovalsResolved { + let file = self.loadFile() + return self.resolveFromFile(file, agentId: agentId) + } + + private static func resolveFromFile(_ file: ExecApprovalsFile, agentId: String?) -> ExecApprovalsResolved { let defaults = file.defaults ?? ExecApprovalsDefaults() let resolvedDefaults = ExecApprovalsResolvedDefaults( security: defaults.security ?? self.defaultSecurity, @@ -777,6 +788,7 @@ actor SkillBinsCache { static let shared = SkillBinsCache() private var bins: Set = [] + private var trustByName: [String: Set] = [:] private var lastRefresh: Date? private let refreshInterval: TimeInterval = 90 @@ -787,27 +799,90 @@ actor SkillBinsCache { return self.bins } + func currentTrust(force: Bool = false) async -> [String: Set] { + if force || self.isStale() { + await self.refresh() + } + return self.trustByName + } + func refresh() async { do { let report = try await GatewayConnection.shared.skillsStatus() - var next = Set() - for skill in report.skills { - for bin in skill.requirements.bins { - let trimmed = bin.trimmingCharacters(in: .whitespacesAndNewlines) - if !trimmed.isEmpty { next.insert(trimmed) } - } - } - self.bins = next + let trust = Self.buildTrustIndex(report: report, searchPaths: CommandResolver.preferredPaths()) + self.bins = trust.names + self.trustByName = trust.pathsByName self.lastRefresh = Date() } catch { if self.lastRefresh == nil { self.bins = [] + self.trustByName = [:] } } } + static func normalizeSkillBinName(_ value: String) -> String? { + let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + return trimmed.isEmpty ? nil : trimmed + } + + static func normalizeResolvedPath(_ value: String?) -> String? { + let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + guard !trimmed.isEmpty else { return nil } + return URL(fileURLWithPath: trimmed).standardizedFileURL.path + } + + static func buildTrustIndex( + report: SkillsStatusReport, + searchPaths: [String]) -> SkillBinTrustIndex + { + var names = Set() + var pathsByName: [String: Set] = [:] + + for skill in report.skills { + for bin in skill.requirements.bins { + let trimmed = bin.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { continue } + names.insert(trimmed) + + guard let name = self.normalizeSkillBinName(trimmed), + let resolvedPath = self.resolveSkillBinPath(trimmed, searchPaths: searchPaths), + let normalizedPath = self.normalizeResolvedPath(resolvedPath) + else { + continue + } + + var paths = pathsByName[name] ?? Set() + paths.insert(normalizedPath) + pathsByName[name] = paths + } + } + + return SkillBinTrustIndex(names: names, pathsByName: pathsByName) + } + + private static func resolveSkillBinPath(_ bin: String, searchPaths: [String]) -> String? { + let expanded = bin.hasPrefix("~") ? (bin as NSString).expandingTildeInPath : bin + if expanded.contains("/") || expanded.contains("\\") { + return FileManager().isExecutableFile(atPath: expanded) ? expanded : nil + } + return CommandResolver.findExecutable(named: expanded, searchPaths: searchPaths) + } + private func isStale() -> Bool { guard let lastRefresh else { return true } return Date().timeIntervalSince(lastRefresh) > self.refreshInterval } + + static func _testBuildTrustIndex( + report: SkillsStatusReport, + searchPaths: [String]) -> SkillBinTrustIndex + { + self.buildTrustIndex(report: report, searchPaths: searchPaths) + } +} + +struct SkillBinTrustIndex { + let names: Set + let pathsByName: [String: Set] } diff --git a/apps/macos/Sources/OpenClaw/ExecApprovalsGatewayPrompter.swift b/apps/macos/Sources/OpenClaw/ExecApprovalsGatewayPrompter.swift index 379e8c0f559..08e60b84d2b 100644 --- a/apps/macos/Sources/OpenClaw/ExecApprovalsGatewayPrompter.swift +++ b/apps/macos/Sources/OpenClaw/ExecApprovalsGatewayPrompter.swift @@ -43,7 +43,33 @@ final class ExecApprovalsGatewayPrompter { do { let data = try JSONEncoder().encode(payload) let request = try JSONDecoder().decode(GatewayApprovalRequest.self, from: data) - guard self.shouldPresent(request: request) else { return } + let presentation = self.shouldPresent(request: request) + guard presentation.shouldAsk else { + // Ask policy says no prompt needed – resolve based on security policy + let decision: ExecApprovalDecision = presentation.security == .full ? .allowOnce : .deny + try await GatewayConnection.shared.requestVoid( + method: .execApprovalResolve, + params: [ + "id": AnyCodable(request.id), + "decision": AnyCodable(decision.rawValue), + ], + timeoutMs: 10000) + return + } + guard presentation.canPresent else { + let decision = Self.fallbackDecision( + request: request.request, + askFallback: presentation.askFallback, + allowlist: presentation.allowlist) + try await GatewayConnection.shared.requestVoid( + method: .execApprovalResolve, + params: [ + "id": AnyCodable(request.id), + "decision": AnyCodable(decision.rawValue), + ], + timeoutMs: 10000) + return + } let decision = ExecApprovalsPromptPresenter.prompt(request.request) try await GatewayConnection.shared.requestVoid( method: .execApprovalResolve, @@ -57,16 +83,89 @@ final class ExecApprovalsGatewayPrompter { } } - private func shouldPresent(request: GatewayApprovalRequest) -> Bool { + /// Whether the ask policy requires prompting the user. + /// Note: this only determines if a prompt is shown, not whether the action is allowed. + /// The security policy (full/deny/allowlist) decides the actual outcome. + private static func shouldAsk(security: ExecSecurity, ask: ExecAsk) -> Bool { + switch ask { + case .always: + return true + case .onMiss: + return security == .allowlist + case .off: + return false + } + } + + struct PresentationDecision { + /// Whether the ask policy requires prompting the user (not whether the action is allowed). + var shouldAsk: Bool + /// Whether the prompt can actually be shown (session match, recent activity, etc.). + var canPresent: Bool + /// The resolved security policy, used to determine allow/deny when no prompt is shown. + var security: ExecSecurity + /// Fallback security policy when a prompt is needed but can't be presented. + var askFallback: ExecSecurity + var allowlist: [ExecAllowlistEntry] + } + + private func shouldPresent(request: GatewayApprovalRequest) -> PresentationDecision { let mode = AppStateStore.shared.connectionMode let activeSession = WebChatManager.shared.activeSessionKey?.trimmingCharacters(in: .whitespacesAndNewlines) let requestSession = request.request.sessionKey?.trimmingCharacters(in: .whitespacesAndNewlines) - return Self.shouldPresent( + + // Read-only resolve to avoid disk writes on the MainActor + let approvals = ExecApprovalsStore.resolveReadOnly(agentId: request.request.agentId) + let security = approvals.agent.security + let ask = approvals.agent.ask + + let shouldAsk = Self.shouldAsk(security: security, ask: ask) + + let canPresent = shouldAsk && Self.shouldPresent( mode: mode, activeSession: activeSession, requestSession: requestSession, lastInputSeconds: Self.lastInputSeconds(), thresholdSeconds: 120) + + return PresentationDecision( + shouldAsk: shouldAsk, + canPresent: canPresent, + security: security, + askFallback: approvals.agent.askFallback, + allowlist: approvals.allowlist) + } + + private static func fallbackDecision( + request: ExecApprovalPromptRequest, + askFallback: ExecSecurity, + allowlist: [ExecAllowlistEntry]) -> ExecApprovalDecision + { + guard askFallback == .allowlist else { + return askFallback == .full ? .allowOnce : .deny + } + let resolution = self.fallbackResolution(for: request) + let match = ExecAllowlistMatcher.match(entries: allowlist, resolution: resolution) + return match == nil ? .deny : .allowOnce + } + + private static func fallbackResolution(for request: ExecApprovalPromptRequest) -> ExecCommandResolution? { + let resolvedPath = request.resolvedPath?.trimmingCharacters(in: .whitespacesAndNewlines) + let trimmedResolvedPath = (resolvedPath?.isEmpty == false) ? resolvedPath : nil + let rawExecutable = self.firstToken(from: request.command) ?? trimmedResolvedPath ?? "" + guard !rawExecutable.isEmpty || trimmedResolvedPath != nil else { return nil } + let executableName = trimmedResolvedPath.map { URL(fileURLWithPath: $0).lastPathComponent } ?? rawExecutable + return ExecCommandResolution( + rawExecutable: rawExecutable, + resolvedPath: trimmedResolvedPath, + executableName: executableName, + cwd: request.cwd) + } + + private static func firstToken(from command: String) -> String? { + let trimmed = command.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return nil } + return trimmed.split(whereSeparator: { $0.isWhitespace }).first.map(String.init) } private static func shouldPresent( @@ -117,5 +216,29 @@ extension ExecApprovalsGatewayPrompter { lastInputSeconds: lastInputSeconds, thresholdSeconds: thresholdSeconds) } + + static func _testShouldAsk(security: ExecSecurity, ask: ExecAsk) -> Bool { + self.shouldAsk(security: security, ask: ask) + } + + static func _testFallbackDecision( + command: String, + resolvedPath: String?, + askFallback: ExecSecurity, + allowlistPatterns: [String]) -> ExecApprovalDecision + { + self.fallbackDecision( + request: ExecApprovalPromptRequest( + command: command, + cwd: nil, + host: nil, + security: nil, + ask: nil, + agentId: nil, + resolvedPath: resolvedPath, + sessionKey: nil), + askFallback: askFallback, + allowlist: allowlistPatterns.map { ExecAllowlistEntry(pattern: $0) }) + } } #endif diff --git a/apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift b/apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift index a2cc9d53390..19336f4f7b1 100644 --- a/apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift +++ b/apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift @@ -89,6 +89,20 @@ private func readLineFromHandle(_ handle: FileHandle, maxBytes: Int) throws -> S return String(data: lineData, encoding: .utf8) } +func timingSafeHexStringEquals(_ lhs: String, _ rhs: String) -> Bool { + let lhsBytes = Array(lhs.utf8) + let rhsBytes = Array(rhs.utf8) + guard lhsBytes.count == rhsBytes.count else { + return false + } + + var diff: UInt8 = 0 + for index in lhsBytes.indices { + diff |= lhsBytes[index] ^ rhsBytes[index] + } + return diff == 0 +} + enum ExecApprovalsSocketClient { private struct TimeoutError: LocalizedError { var message: String @@ -854,7 +868,7 @@ private final class ExecApprovalsSocketServer: @unchecked Sendable { error: ExecHostError(code: "INVALID_REQUEST", message: "expired request", reason: "ttl")) } let expected = self.hmacHex(nonce: request.nonce, ts: request.ts, requestJson: request.requestJson) - if expected != request.hmac { + if !timingSafeHexStringEquals(expected, request.hmac) { return ExecHostResponse( type: "exec-res", id: request.id, diff --git a/apps/macos/Sources/OpenClaw/ExecCommandResolution.swift b/apps/macos/Sources/OpenClaw/ExecCommandResolution.swift index 91a22153f3c..f89293a81aa 100644 --- a/apps/macos/Sources/OpenClaw/ExecCommandResolution.swift +++ b/apps/macos/Sources/OpenClaw/ExecCommandResolution.swift @@ -37,8 +37,7 @@ struct ExecCommandResolution { var resolutions: [ExecCommandResolution] = [] resolutions.reserveCapacity(segments.count) for segment in segments { - guard let token = self.parseFirstToken(segment), - let resolution = self.resolveExecutable(rawExecutable: token, cwd: cwd, env: env) + guard let resolution = self.resolveShellSegmentExecutable(segment, cwd: cwd, env: env) else { return [] } @@ -88,6 +87,20 @@ struct ExecCommandResolution { cwd: cwd) } + private static func resolveShellSegmentExecutable( + _ segment: String, + cwd: String?, + env: [String: String]?) -> ExecCommandResolution? + { + let tokens = self.tokenizeShellWords(segment) + guard !tokens.isEmpty else { return nil } + let effective = ExecEnvInvocationUnwrapper.unwrapDispatchWrappersForResolution(tokens) + guard let raw = effective.first?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { + return nil + } + return self.resolveExecutable(rawExecutable: raw, cwd: cwd, env: env) + } + private static func parseFirstToken(_ command: String) -> String? { let trimmed = command.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return nil } @@ -102,6 +115,59 @@ struct ExecCommandResolution { return trimmed.split(whereSeparator: { $0.isWhitespace }).first.map(String.init) } + private static func tokenizeShellWords(_ command: String) -> [String] { + let trimmed = command.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return [] } + + var tokens: [String] = [] + var current = "" + var inSingle = false + var inDouble = false + var escaped = false + + func appendCurrent() { + guard !current.isEmpty else { return } + tokens.append(current) + current.removeAll(keepingCapacity: true) + } + + for ch in trimmed { + if escaped { + current.append(ch) + escaped = false + continue + } + + if ch == "\\", !inSingle { + escaped = true + continue + } + + if ch == "'", !inDouble { + inSingle.toggle() + continue + } + + if ch == "\"", !inSingle { + inDouble.toggle() + continue + } + + if ch.isWhitespace, !inSingle, !inDouble { + appendCurrent() + continue + } + + current.append(ch) + } + + if escaped { + current.append("\\") + } + appendCurrent() + return tokens + } + private enum ShellTokenContext { case unquoted case doubleQuoted @@ -148,8 +214,14 @@ struct ExecCommandResolution { while idx < chars.count { let ch = chars[idx] let next: Character? = idx + 1 < chars.count ? chars[idx + 1] : nil + let lookahead = self.nextShellSignificantCharacter(chars: chars, after: idx, inSingle: inSingle) if escaped { + if ch == "\n" { + escaped = false + idx += 1 + continue + } current.append(ch) escaped = false idx += 1 @@ -157,6 +229,10 @@ struct ExecCommandResolution { } if ch == "\\", !inSingle { + if next == "\n" { + idx += 2 + continue + } current.append(ch) escaped = true idx += 1 @@ -177,7 +253,7 @@ struct ExecCommandResolution { continue } - if !inSingle, self.shouldFailClosedForShell(ch: ch, next: next, inDouble: inDouble) { + if !inSingle, self.shouldFailClosedForShell(ch: ch, next: lookahead, inDouble: inDouble) { // Fail closed on command/process substitution in allowlist mode, // including command substitution inside double-quoted shell strings. return nil @@ -201,6 +277,25 @@ struct ExecCommandResolution { return segments } + private static func nextShellSignificantCharacter( + chars: [Character], + after idx: Int, + inSingle: Bool) -> Character? + { + guard !inSingle else { + return idx + 1 < chars.count ? chars[idx + 1] : nil + } + var cursor = idx + 1 + while cursor < chars.count { + if chars[cursor] == "\\", cursor + 1 < chars.count, chars[cursor + 1] == "\n" { + cursor += 2 + continue + } + return chars[cursor] + } + return nil + } + private static func shouldFailClosedForShell(ch: Character, next: Character?, inDouble: Bool) -> Bool { let context: ShellTokenContext = inDouble ? .doubleQuoted : .unquoted guard let rules = self.shellFailClosedRules[context] else { diff --git a/apps/macos/Sources/OpenClaw/GeneralSettings.swift b/apps/macos/Sources/OpenClaw/GeneralSettings.swift index b55ed439489..633879367ea 100644 --- a/apps/macos/Sources/OpenClaw/GeneralSettings.swift +++ b/apps/macos/Sources/OpenClaw/GeneralSettings.swift @@ -348,10 +348,18 @@ struct GeneralSettings: View { Text("Testing…") .font(.caption) .foregroundStyle(.secondary) - case .ok: - Label("Ready", systemImage: "checkmark.circle.fill") - .font(.caption) - .foregroundStyle(.green) + case let .ok(success): + VStack(alignment: .leading, spacing: 2) { + Label(success.title, systemImage: "checkmark.circle.fill") + .font(.caption) + .foregroundStyle(.green) + if let detail = success.detail { + Text(detail) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + } case let .failed(message): Text(message) .font(.caption) @@ -518,7 +526,7 @@ struct GeneralSettings: View { private enum RemoteStatus: Equatable { case idle case checking - case ok + case ok(RemoteGatewayProbeSuccess) case failed(String) } @@ -558,114 +566,14 @@ extension GeneralSettings { @MainActor func testRemote() async { self.remoteStatus = .checking - let settings = CommandResolver.connectionSettings() - if self.state.remoteTransport == .direct { - let trimmedUrl = self.state.remoteUrl.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmedUrl.isEmpty else { - self.remoteStatus = .failed("Set a gateway URL first") - return - } - guard Self.isValidWsUrl(trimmedUrl) else { - self.remoteStatus = .failed( - "Gateway URL must use wss:// for remote hosts (ws:// only for localhost)") - return - } - } else { - guard !settings.target.isEmpty else { - self.remoteStatus = .failed("Set an SSH target first") - return - } - - // Step 1: basic SSH reachability check - guard let sshCommand = Self.sshCheckCommand( - target: settings.target, - identity: settings.identity) - else { - self.remoteStatus = .failed("SSH target is invalid") - return - } - let sshResult = await ShellExecutor.run( - command: sshCommand, - cwd: nil, - env: nil, - timeout: 8) - - guard sshResult.ok else { - self.remoteStatus = .failed(self.formatSSHFailure(sshResult, target: settings.target)) - return - } + switch await RemoteGatewayProbe.run() { + case let .ready(success): + self.remoteStatus = .ok(success) + case let .authIssue(issue): + self.remoteStatus = .failed(issue.statusMessage) + case let .failed(message): + self.remoteStatus = .failed(message) } - - // Step 2: control channel health check - let originalMode = AppStateStore.shared.connectionMode - do { - try await ControlChannel.shared.configure(mode: .remote( - target: settings.target, - identity: settings.identity)) - let data = try await ControlChannel.shared.health(timeout: 10) - if decodeHealthSnapshot(from: data) != nil { - self.remoteStatus = .ok - } else { - self.remoteStatus = .failed("Control channel returned invalid health JSON") - } - } catch { - self.remoteStatus = .failed(error.localizedDescription) - } - - // Restore original mode if we temporarily switched - switch originalMode { - case .remote: - break - case .local: - try? await ControlChannel.shared.configure(mode: .local) - case .unconfigured: - await ControlChannel.shared.disconnect() - } - } - - private static func isValidWsUrl(_ raw: String) -> Bool { - GatewayRemoteConfig.normalizeGatewayUrl(raw) != nil - } - - private static func sshCheckCommand(target: String, identity: String) -> [String]? { - guard let parsed = CommandResolver.parseSSHTarget(target) else { return nil } - let options = [ - "-o", "BatchMode=yes", - "-o", "ConnectTimeout=5", - "-o", "StrictHostKeyChecking=accept-new", - "-o", "UpdateHostKeys=yes", - ] - let args = CommandResolver.sshArguments( - target: parsed, - identity: identity, - options: options, - remoteCommand: ["echo", "ok"]) - return ["/usr/bin/ssh"] + args - } - - private func formatSSHFailure(_ response: Response, target: String) -> String { - let payload = response.payload.flatMap { String(data: $0, encoding: .utf8) } - let trimmed = payload? - .trimmingCharacters(in: .whitespacesAndNewlines) - .split(whereSeparator: \.isNewline) - .joined(separator: " ") - if let trimmed, - trimmed.localizedCaseInsensitiveContains("host key verification failed") - { - let host = CommandResolver.parseSSHTarget(target)?.host ?? target - return "SSH check failed: Host key verification failed. Remove the old key with " + - "`ssh-keygen -R \(host)` and try again." - } - if let trimmed, !trimmed.isEmpty { - if let message = response.message, message.hasPrefix("exit ") { - return "SSH check failed: \(trimmed) (\(message))" - } - return "SSH check failed: \(trimmed)" - } - if let message = response.message { - return "SSH check failed (\(message))" - } - return "SSH check failed" } private func revealLogs() { diff --git a/apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift b/apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift index 2981a60bbf7..40db384b226 100644 --- a/apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift +++ b/apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift @@ -17,16 +17,29 @@ enum HostEnvSecurityPolicy { "BASH_ENV", "ENV", "GIT_EXTERNAL_DIFF", + "GIT_EXEC_PATH", "SHELL", "SHELLOPTS", "PS4", "GCONV_PATH", "IFS", - "SSLKEYLOGFILE" + "SSLKEYLOGFILE", + "JAVA_TOOL_OPTIONS", + "_JAVA_OPTIONS", + "JDK_JAVA_OPTIONS", + "PYTHONBREAKPOINT", + "DOTNET_STARTUP_HOOKS", + "DOTNET_ADDITIONAL_DEPS", + "GLIBC_TUNABLES", + "MAVEN_OPTS", + "SBT_OPTS", + "GRADLE_OPTS", + "ANT_OPTS" ] static let blockedOverrideKeys: Set = [ "HOME", + "GRADLE_USER_HOME", "ZDOTDIR", "GIT_SSH_COMMAND", "GIT_SSH", diff --git a/apps/macos/Sources/OpenClaw/LaunchAgentManager.swift b/apps/macos/Sources/OpenClaw/LaunchAgentManager.swift index af318b330d4..004d575d5d5 100644 --- a/apps/macos/Sources/OpenClaw/LaunchAgentManager.swift +++ b/apps/macos/Sources/OpenClaw/LaunchAgentManager.swift @@ -26,7 +26,12 @@ enum LaunchAgentManager { } private static func writePlist(bundlePath: String) { - let plist = """ + let plist = self.plistContents(bundlePath: bundlePath) + try? plist.write(to: self.plistURL, atomically: true, encoding: .utf8) + } + + static func plistContents(bundlePath: String) -> String { + """ @@ -41,8 +46,6 @@ enum LaunchAgentManager { \(FileManager().homeDirectoryForCurrentUser.path) RunAtLoad - KeepAlive - EnvironmentVariables PATH @@ -55,7 +58,6 @@ enum LaunchAgentManager { """ - try? plist.write(to: self.plistURL, atomically: true, encoding: .utf8) } @discardableResult diff --git a/apps/macos/Sources/OpenClaw/MenuSessionsInjector.swift b/apps/macos/Sources/OpenClaw/MenuSessionsInjector.swift index eb6271d0a8c..9f667cc6239 100644 --- a/apps/macos/Sources/OpenClaw/MenuSessionsInjector.swift +++ b/apps/macos/Sources/OpenClaw/MenuSessionsInjector.swift @@ -1099,38 +1099,33 @@ extension MenuSessionsInjector { // MARK: - Width + placement private func findInsertIndex(in menu: NSMenu) -> Int? { - // Insert right before the separator above "Send Heartbeats". - if let idx = menu.items.firstIndex(where: { $0.title == "Send Heartbeats" }) { - if let sepIdx = menu.items[..= 1 { return 1 } - return menu.items.count + self.findDynamicSectionInsertIndex(in: menu) } private func findNodesInsertIndex(in menu: NSMenu) -> Int? { - if let idx = menu.items.firstIndex(where: { $0.title == "Send Heartbeats" }) { - if let sepIdx = menu.items[.. Int? { + // Keep controls and action buttons visible by inserting dynamic rows at the + // built-in footer boundary, not by matching localized menu item titles. + if let footerSeparatorIndex = menu.items.lastIndex(where: { item in + item.isSeparatorItem && !self.isInjectedItem(item) + }) { + return footerSeparatorIndex } - if let sepIdx = menu.items.firstIndex(where: { $0.isSeparatorItem }) { - return sepIdx + if let firstBaseItemIndex = menu.items.firstIndex(where: { !self.isInjectedItem($0) }) { + return min(firstBaseItemIndex + 1, menu.items.count) } - if menu.items.count >= 1 { return 1 } return menu.items.count } + private func isInjectedItem(_ item: NSMenuItem) -> Bool { + item.tag == self.tag || item.tag == self.nodesTag + } + private func initialWidth(for menu: NSMenu) -> CGFloat { if let openWidth = self.menuOpenWidth { return max(300, openWidth) @@ -1236,5 +1231,13 @@ extension MenuSessionsInjector { func injectForTesting(into menu: NSMenu) { self.inject(into: menu) } + + func testingFindInsertIndex(in menu: NSMenu) -> Int? { + self.findInsertIndex(in: menu) + } + + func testingFindNodesInsertIndex(in menu: NSMenu) -> Int? { + self.findNodesInsertIndex(in: menu) + } } #endif diff --git a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeBrowserProxy.swift b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeBrowserProxy.swift index 0da6510f608..367907f9fb7 100644 --- a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeBrowserProxy.swift +++ b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeBrowserProxy.swift @@ -146,8 +146,8 @@ actor MacNodeBrowserProxy { request.setValue(password, forHTTPHeaderField: "x-openclaw-password") } - if method != "GET", let body = params.body?.value { - request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [.fragmentsAllowed]) + if method != "GET", let body = params.body { + request.httpBody = try JSONSerialization.data(withJSONObject: body.foundationValue, options: [.fragmentsAllowed]) request.setValue("application/json", forHTTPHeaderField: "Content-Type") } diff --git a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift index fa216d09c5f..5e093c49e24 100644 --- a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift +++ b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift @@ -77,6 +77,7 @@ final class MacNodeModeCoordinator { try await self.session.connect( url: config.url, token: config.token, + bootstrapToken: nil, password: config.password, connectOptions: connectOptions, sessionBox: sessionBox, diff --git a/apps/macos/Sources/OpenClaw/NodeServiceManager.swift b/apps/macos/Sources/OpenClaw/NodeServiceManager.swift index 7a9da5925f8..18f500bd359 100644 --- a/apps/macos/Sources/OpenClaw/NodeServiceManager.swift +++ b/apps/macos/Sources/OpenClaw/NodeServiceManager.swift @@ -6,7 +6,7 @@ enum NodeServiceManager { static func start() async -> String? { let result = await self.runServiceCommandResult( - ["node", "start"], + ["start"], timeout: 20, quiet: false) if let error = self.errorMessage(from: result, treatNotLoadedAsError: true) { @@ -18,7 +18,7 @@ enum NodeServiceManager { static func stop() async -> String? { let result = await self.runServiceCommandResult( - ["node", "stop"], + ["stop"], timeout: 15, quiet: false) if let error = self.errorMessage(from: result, treatNotLoadedAsError: false) { @@ -30,6 +30,14 @@ enum NodeServiceManager { } extension NodeServiceManager { + private static func serviceCommand(_ args: [String]) -> [String] { + CommandResolver.openclawCommand( + subcommand: "node", + extraArgs: self.withJsonFlag(args), + // Service management must always run locally, even if remote mode is configured. + configRoot: ["gateway": ["mode": "local"]]) + } + private struct CommandResult { let success: Bool let payload: Data? @@ -52,11 +60,7 @@ extension NodeServiceManager { timeout: Double, quiet: Bool) async -> CommandResult { - let command = CommandResolver.openclawCommand( - subcommand: "service", - extraArgs: self.withJsonFlag(args), - // Service management must always run locally, even if remote mode is configured. - configRoot: ["gateway": ["mode": "local"]]) + let command = self.serviceCommand(args) var env = ProcessInfo.processInfo.environment env["PATH"] = CommandResolver.preferredPaths().joined(separator: ":") let response = await ShellExecutor.runDetailed(command: command, cwd: nil, env: env, timeout: timeout) @@ -136,3 +140,11 @@ extension NodeServiceManager { TextSummarySupport.summarizeLastLine(text) } } + +#if DEBUG +extension NodeServiceManager { + static func _testServiceCommand(_ args: [String]) -> [String] { + self.serviceCommand(args) + } +} +#endif diff --git a/apps/macos/Sources/OpenClaw/Onboarding.swift b/apps/macos/Sources/OpenClaw/Onboarding.swift index 4eae7e092b0..ca183d35311 100644 --- a/apps/macos/Sources/OpenClaw/Onboarding.swift +++ b/apps/macos/Sources/OpenClaw/Onboarding.swift @@ -9,6 +9,13 @@ enum UIStrings { static let welcomeTitle = "Welcome to OpenClaw" } +enum RemoteOnboardingProbeState: Equatable { + case idle + case checking + case ok(RemoteGatewayProbeSuccess) + case failed(String) +} + @MainActor final class OnboardingController { static let shared = OnboardingController() @@ -72,6 +79,9 @@ struct OnboardingView: View { @State var didAutoKickoff = false @State var showAdvancedConnection = false @State var preferredGatewayID: String? + @State var remoteProbeState: RemoteOnboardingProbeState = .idle + @State var remoteAuthIssue: RemoteGatewayAuthIssue? + @State var suppressRemoteProbeReset = false @State var gatewayDiscovery: GatewayDiscoveryModel @State var onboardingChatModel: OpenClawChatViewModel @State var onboardingSkillsModel = SkillsSettingsModel() diff --git a/apps/macos/Sources/OpenClaw/OnboardingView+Pages.swift b/apps/macos/Sources/OpenClaw/OnboardingView+Pages.swift index 8f4d16420bc..f35e4e4c4ec 100644 --- a/apps/macos/Sources/OpenClaw/OnboardingView+Pages.swift +++ b/apps/macos/Sources/OpenClaw/OnboardingView+Pages.swift @@ -2,6 +2,7 @@ import AppKit import OpenClawChatUI import OpenClawDiscovery import OpenClawIPC +import OpenClawKit import SwiftUI extension OnboardingView { @@ -97,6 +98,11 @@ extension OnboardingView { self.gatewayDiscoverySection() + if self.shouldShowRemoteConnectionSection { + Divider().padding(.vertical, 4) + self.remoteConnectionSection() + } + self.connectionChoiceButton( title: "Configure later", subtitle: "Don’t start the Gateway yet.", @@ -109,6 +115,22 @@ extension OnboardingView { } } } + .onChange(of: self.state.connectionMode) { _, newValue in + guard Self.shouldResetRemoteProbeFeedback( + for: newValue, + suppressReset: self.suppressRemoteProbeReset) + else { return } + self.resetRemoteProbeFeedback() + } + .onChange(of: self.state.remoteTransport) { _, _ in + self.resetRemoteProbeFeedback() + } + .onChange(of: self.state.remoteTarget) { _, _ in + self.resetRemoteProbeFeedback() + } + .onChange(of: self.state.remoteUrl) { _, _ in + self.resetRemoteProbeFeedback() + } } private var localGatewaySubtitle: String { @@ -199,25 +221,6 @@ extension OnboardingView { .pickerStyle(.segmented) .frame(width: fieldWidth) } - GridRow { - Text("Gateway token") - .font(.callout.weight(.semibold)) - .frame(width: labelWidth, alignment: .leading) - SecureField("remote gateway auth token (gateway.remote.token)", text: self.$state.remoteToken) - .textFieldStyle(.roundedBorder) - .frame(width: fieldWidth) - } - if self.state.remoteTokenUnsupported { - GridRow { - Text("") - .frame(width: labelWidth, alignment: .leading) - Text( - "The current gateway.remote.token value is not plain text. OpenClaw for macOS cannot use it directly; enter a plaintext token here to replace it.") - .font(.caption) - .foregroundStyle(.orange) - .frame(width: fieldWidth, alignment: .leading) - } - } if self.state.remoteTransport == .direct { GridRow { Text("Gateway URL") @@ -289,6 +292,250 @@ extension OnboardingView { } } + private var shouldShowRemoteConnectionSection: Bool { + self.state.connectionMode == .remote || + self.showAdvancedConnection || + self.remoteProbeState != .idle || + self.remoteAuthIssue != nil || + Self.shouldShowRemoteTokenField( + showAdvancedConnection: self.showAdvancedConnection, + remoteToken: self.state.remoteToken, + remoteTokenUnsupported: self.state.remoteTokenUnsupported, + authIssue: self.remoteAuthIssue) + } + + private var shouldShowRemoteTokenField: Bool { + guard self.shouldShowRemoteConnectionSection else { return false } + return Self.shouldShowRemoteTokenField( + showAdvancedConnection: self.showAdvancedConnection, + remoteToken: self.state.remoteToken, + remoteTokenUnsupported: self.state.remoteTokenUnsupported, + authIssue: self.remoteAuthIssue) + } + + private var remoteProbePreflightMessage: String? { + switch self.state.remoteTransport { + case .direct: + let trimmedUrl = self.state.remoteUrl.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmedUrl.isEmpty { + return "Select a nearby gateway or open Advanced to enter a gateway URL." + } + if GatewayRemoteConfig.normalizeGatewayUrl(trimmedUrl) == nil { + return "Gateway URL must use wss:// for remote hosts (ws:// only for localhost)." + } + return nil + case .ssh: + let trimmedTarget = self.state.remoteTarget.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmedTarget.isEmpty { + return "Select a nearby gateway or open Advanced to enter an SSH target." + } + return CommandResolver.sshTargetValidationMessage(trimmedTarget) + } + } + + private var canProbeRemoteConnection: Bool { + self.remoteProbePreflightMessage == nil && self.remoteProbeState != .checking + } + + @ViewBuilder + private func remoteConnectionSection() -> some View { + VStack(alignment: .leading, spacing: 10) { + HStack(alignment: .top, spacing: 12) { + VStack(alignment: .leading, spacing: 2) { + Text("Remote connection") + .font(.callout.weight(.semibold)) + Text("Checks the real remote websocket and auth handshake.") + .font(.caption) + .foregroundStyle(.secondary) + } + Spacer(minLength: 0) + Button { + Task { await self.probeRemoteConnection() } + } label: { + if self.remoteProbeState == .checking { + ProgressView() + .controlSize(.small) + .frame(minWidth: 120) + } else { + Text("Check connection") + .frame(minWidth: 120) + } + } + .buttonStyle(.borderedProminent) + .disabled(!self.canProbeRemoteConnection) + } + + if self.shouldShowRemoteTokenField { + self.remoteTokenField() + } + + if let message = self.remoteProbePreflightMessage, self.remoteProbeState != .checking { + Text(message) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + + self.remoteProbeStatusView() + + if let issue = self.remoteAuthIssue { + self.remoteAuthPromptView(issue: issue) + } + } + } + + private func remoteTokenField() -> some View { + VStack(alignment: .leading, spacing: 6) { + HStack(alignment: .center, spacing: 12) { + Text("Gateway token") + .font(.callout.weight(.semibold)) + .frame(width: 110, alignment: .leading) + SecureField("remote gateway auth token (gateway.remote.token)", text: self.$state.remoteToken) + .textFieldStyle(.roundedBorder) + .frame(maxWidth: 320) + } + Text("Used when the remote gateway requires token auth.") + .font(.caption) + .foregroundStyle(.secondary) + if self.state.remoteTokenUnsupported { + Text( + "The current gateway.remote.token value is not plain text. OpenClaw for macOS cannot use it directly; enter a plaintext token here to replace it.") + .font(.caption) + .foregroundStyle(.orange) + .fixedSize(horizontal: false, vertical: true) + } + } + } + + @ViewBuilder + private func remoteProbeStatusView() -> some View { + switch self.remoteProbeState { + case .idle: + EmptyView() + case .checking: + Text("Checking remote gateway…") + .font(.caption) + .foregroundStyle(.secondary) + case let .ok(success): + VStack(alignment: .leading, spacing: 2) { + Label(success.title, systemImage: "checkmark.circle.fill") + .font(.caption) + .foregroundStyle(.green) + if let detail = success.detail { + Text(detail) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + } + case let .failed(message): + if self.remoteAuthIssue == nil { + Text(message) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + } + } + + private func remoteAuthPromptView(issue: RemoteGatewayAuthIssue) -> some View { + let promptStyle = Self.remoteAuthPromptStyle(for: issue) + return HStack(alignment: .top, spacing: 10) { + Image(systemName: promptStyle.systemImage) + .font(.caption.weight(.semibold)) + .foregroundStyle(promptStyle.tint) + .frame(width: 16, alignment: .center) + .padding(.top, 1) + VStack(alignment: .leading, spacing: 4) { + Text(issue.title) + .font(.caption.weight(.semibold)) + Text(.init(issue.body)) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + if let footnote = issue.footnote { + Text(.init(footnote)) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + } + } + } + + @MainActor + private func probeRemoteConnection() async { + let originalMode = self.state.connectionMode + let shouldRestoreMode = originalMode != .remote + if shouldRestoreMode { + // Reuse the shared remote endpoint stack for probing without committing the user's mode choice. + self.state.connectionMode = .remote + } + self.remoteProbeState = .checking + self.remoteAuthIssue = nil + defer { + if shouldRestoreMode { + self.suppressRemoteProbeReset = true + self.state.connectionMode = originalMode + self.suppressRemoteProbeReset = false + } + } + + switch await RemoteGatewayProbe.run() { + case let .ready(success): + self.remoteProbeState = .ok(success) + case let .authIssue(issue): + self.remoteAuthIssue = issue + self.remoteProbeState = .failed(issue.statusMessage) + case let .failed(message): + self.remoteProbeState = .failed(message) + } + } + + private func resetRemoteProbeFeedback() { + self.remoteProbeState = .idle + self.remoteAuthIssue = nil + } + + static func remoteAuthPromptStyle( + for issue: RemoteGatewayAuthIssue) + -> (systemImage: String, tint: Color) + { + switch issue { + case .tokenRequired: + return ("key.fill", .orange) + case .tokenMismatch: + return ("exclamationmark.triangle.fill", .orange) + case .gatewayTokenNotConfigured: + return ("wrench.and.screwdriver.fill", .orange) + case .setupCodeExpired: + return ("qrcode.viewfinder", .orange) + case .passwordRequired: + return ("lock.slash.fill", .orange) + case .pairingRequired: + return ("link.badge.plus", .orange) + } + } + + static func shouldShowRemoteTokenField( + showAdvancedConnection: Bool, + remoteToken: String, + remoteTokenUnsupported: Bool, + authIssue: RemoteGatewayAuthIssue?) -> Bool + { + showAdvancedConnection || + remoteTokenUnsupported || + !remoteToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || + authIssue?.showsTokenField == true + } + + static func shouldResetRemoteProbeFeedback( + for connectionMode: AppState.ConnectionMode, + suppressReset: Bool) -> Bool + { + !suppressReset && connectionMode != .remote + } + func gatewaySubtitle(for gateway: GatewayDiscoveryModel.DiscoveredGateway) -> String? { if self.state.remoteTransport == .direct { return GatewayDiscoveryHelpers.directUrl(for: gateway) ?? "Gateway pairing only" diff --git a/apps/macos/Sources/OpenClaw/PortGuardian.swift b/apps/macos/Sources/OpenClaw/PortGuardian.swift index dfae5c3bcaa..7d8837415ff 100644 --- a/apps/macos/Sources/OpenClaw/PortGuardian.swift +++ b/apps/macos/Sources/OpenClaw/PortGuardian.swift @@ -47,7 +47,7 @@ actor PortGuardian { let listeners = await self.listeners(on: port) guard !listeners.isEmpty else { continue } for listener in listeners { - if self.isExpected(listener, port: port, mode: mode) { + if Self.isExpected(listener, port: port, mode: mode) { let message = """ port \(port) already served by expected \(listener.command) (pid \(listener.pid)) — keeping @@ -55,6 +55,14 @@ actor PortGuardian { self.logger.info("\(message, privacy: .public)") continue } + if mode == .remote { + let message = """ + port \(port) held by \(listener.command) + (pid \(listener.pid)) in remote mode — not killing + """ + self.logger.warning(message) + continue + } let killed = await self.kill(listener.pid) if killed { let message = """ @@ -271,8 +279,8 @@ actor PortGuardian { switch mode { case .remote: - expectedDesc = "SSH tunnel to remote gateway" - okPredicate = { $0.command.lowercased().contains("ssh") } + expectedDesc = "Remote gateway (SSH tunnel, Docker, or direct)" + okPredicate = { _ in true } case .local: expectedDesc = "Gateway websocket (node/tsx)" okPredicate = { listener in @@ -352,13 +360,12 @@ actor PortGuardian { return sigkill.ok } - private func isExpected(_ listener: Listener, port: Int, mode: AppState.ConnectionMode) -> Bool { + private static func isExpected(_ listener: Listener, port: Int, mode: AppState.ConnectionMode) -> Bool { let cmd = listener.command.lowercased() let full = listener.fullCommand.lowercased() switch mode { case .remote: - // Remote mode expects an SSH tunnel for the gateway WebSocket port. - if port == GatewayEnvironment.gatewayPort() { return cmd.contains("ssh") } + if port == GatewayEnvironment.gatewayPort() { return true } return false case .local: // The gateway daemon may listen as `openclaw` or as its runtime (`node`, `bun`, etc). @@ -406,6 +413,16 @@ extension PortGuardian { self.parseListeners(from: text).map { ($0.pid, $0.command, $0.fullCommand, $0.user) } } + static func _testIsExpected( + command: String, + fullCommand: String, + port: Int, + mode: AppState.ConnectionMode) -> Bool + { + let listener = Listener(pid: 0, command: command, fullCommand: fullCommand, user: nil) + return Self.isExpected(listener, port: port, mode: mode) + } + static func _testBuildReport( port: Int, mode: AppState.ConnectionMode, diff --git a/apps/macos/Sources/OpenClaw/RemoteGatewayProbe.swift b/apps/macos/Sources/OpenClaw/RemoteGatewayProbe.swift new file mode 100644 index 00000000000..7073ad81de7 --- /dev/null +++ b/apps/macos/Sources/OpenClaw/RemoteGatewayProbe.swift @@ -0,0 +1,237 @@ +import Foundation +import OpenClawIPC +import OpenClawKit + +enum RemoteGatewayAuthIssue: Equatable { + case tokenRequired + case tokenMismatch + case gatewayTokenNotConfigured + case setupCodeExpired + case passwordRequired + case pairingRequired + + init?(error: Error) { + guard let authError = error as? GatewayConnectAuthError else { + return nil + } + switch authError.detail { + case .authTokenMissing: + self = .tokenRequired + case .authTokenMismatch: + self = .tokenMismatch + case .authTokenNotConfigured: + self = .gatewayTokenNotConfigured + case .authBootstrapTokenInvalid: + self = .setupCodeExpired + case .authPasswordMissing, .authPasswordMismatch, .authPasswordNotConfigured: + self = .passwordRequired + case .pairingRequired: + self = .pairingRequired + default: + return nil + } + } + + var showsTokenField: Bool { + switch self { + case .tokenRequired, .tokenMismatch: + true + case .gatewayTokenNotConfigured, .setupCodeExpired, .passwordRequired, .pairingRequired: + false + } + } + + var title: String { + switch self { + case .tokenRequired: + "This gateway requires an auth token" + case .tokenMismatch: + "That token did not match the gateway" + case .gatewayTokenNotConfigured: + "This gateway host needs token setup" + case .setupCodeExpired: + "This setup code is no longer valid" + case .passwordRequired: + "This gateway is using unsupported auth" + case .pairingRequired: + "This device needs pairing approval" + } + } + + var body: String { + switch self { + case .tokenRequired: + "Paste the token configured on the gateway host. On the gateway host, run `openclaw config get gateway.auth.token`. If the gateway uses an environment variable instead, use `OPENCLAW_GATEWAY_TOKEN`." + case .tokenMismatch: + "Check `gateway.auth.token` or `OPENCLAW_GATEWAY_TOKEN` on the gateway host and try again." + case .gatewayTokenNotConfigured: + "This gateway is set to token auth, but no `gateway.auth.token` is configured on the gateway host. If the gateway uses an environment variable instead, set `OPENCLAW_GATEWAY_TOKEN` before starting the gateway." + case .setupCodeExpired: + "Scan or paste a fresh setup code from an already-paired OpenClaw client, then try again." + case .passwordRequired: + "This onboarding flow does not support password auth yet. Reconfigure the gateway to use token auth, then retry." + case .pairingRequired: + "Approve this device from an already-paired OpenClaw client. In your OpenClaw chat, run `/pair approve`, then click **Check connection** again." + } + } + + var footnote: String? { + switch self { + case .tokenRequired, .gatewayTokenNotConfigured: + "No token yet? Generate one on the gateway host with `openclaw doctor --generate-gateway-token`, then set it as `gateway.auth.token`." + case .setupCodeExpired: + nil + case .pairingRequired: + "If you do not have another paired OpenClaw client yet, approve the pending request on the gateway host with `openclaw devices approve`." + case .tokenMismatch, .passwordRequired: + nil + } + } + + var statusMessage: String { + switch self { + case .tokenRequired: + "This gateway requires an auth token from the gateway host." + case .tokenMismatch: + "Gateway token mismatch. Check gateway.auth.token or OPENCLAW_GATEWAY_TOKEN on the gateway host." + case .gatewayTokenNotConfigured: + "This gateway has token auth enabled, but no gateway.auth.token is configured on the host." + case .setupCodeExpired: + "Setup code expired or already used. Scan a fresh setup code, then try again." + case .passwordRequired: + "This gateway uses password auth. Remote onboarding on macOS cannot collect gateway passwords yet." + case .pairingRequired: + "Pairing required. In an already-paired OpenClaw client, run /pair approve, then check the connection again." + } + } +} + +enum RemoteGatewayProbeResult: Equatable { + case ready(RemoteGatewayProbeSuccess) + case authIssue(RemoteGatewayAuthIssue) + case failed(String) +} + +struct RemoteGatewayProbeSuccess: Equatable { + let authSource: GatewayAuthSource? + + var title: String { + switch self.authSource { + case .some(.deviceToken): + "Connected via paired device" + case .some(.bootstrapToken): + "Connected with setup code" + case .some(.sharedToken): + "Connected with gateway token" + case .some(.password): + "Connected with password" + case .some(GatewayAuthSource.none), nil: + "Remote gateway ready" + } + } + + var detail: String? { + switch self.authSource { + case .some(.deviceToken): + "This Mac used a stored device token. New or unpaired devices may still need the gateway token." + case .some(.bootstrapToken): + "This Mac is still using the temporary setup code. Approve pairing to finish provisioning device-scoped auth." + case .some(.sharedToken), .some(.password), .some(GatewayAuthSource.none), nil: + nil + } + } +} + +enum RemoteGatewayProbe { + @MainActor + static func run() async -> RemoteGatewayProbeResult { + AppStateStore.shared.syncGatewayConfigNow() + let settings = CommandResolver.connectionSettings() + let transport = AppStateStore.shared.remoteTransport + + if transport == .direct { + let trimmedUrl = AppStateStore.shared.remoteUrl.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmedUrl.isEmpty else { + return .failed("Set a gateway URL first") + } + guard self.isValidWsUrl(trimmedUrl) else { + return .failed("Gateway URL must use wss:// for remote hosts (ws:// only for localhost)") + } + } else { + let trimmedTarget = settings.target.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmedTarget.isEmpty else { + return .failed("Set an SSH target first") + } + if let validationMessage = CommandResolver.sshTargetValidationMessage(trimmedTarget) { + return .failed(validationMessage) + } + guard let sshCommand = self.sshCheckCommand(target: settings.target, identity: settings.identity) else { + return .failed("SSH target is invalid") + } + + let sshResult = await ShellExecutor.run( + command: sshCommand, + cwd: nil, + env: nil, + timeout: 8) + guard sshResult.ok else { + return .failed(self.formatSSHFailure(sshResult, target: settings.target)) + } + } + + do { + _ = try await GatewayConnection.shared.healthSnapshot(timeoutMs: 10_000) + let authSource = await GatewayConnection.shared.authSource() + return .ready(RemoteGatewayProbeSuccess(authSource: authSource)) + } catch { + if let authIssue = RemoteGatewayAuthIssue(error: error) { + return .authIssue(authIssue) + } + return .failed(error.localizedDescription) + } + } + + private static func isValidWsUrl(_ raw: String) -> Bool { + GatewayRemoteConfig.normalizeGatewayUrl(raw) != nil + } + + private static func sshCheckCommand(target: String, identity: String) -> [String]? { + guard let parsed = CommandResolver.parseSSHTarget(target) else { return nil } + let options = [ + "-o", "BatchMode=yes", + "-o", "ConnectTimeout=5", + "-o", "StrictHostKeyChecking=accept-new", + "-o", "UpdateHostKeys=yes", + ] + let args = CommandResolver.sshArguments( + target: parsed, + identity: identity, + options: options, + remoteCommand: ["echo", "ok"]) + return ["/usr/bin/ssh"] + args + } + + private static func formatSSHFailure(_ response: Response, target: String) -> String { + let payload = response.payload.flatMap { String(data: $0, encoding: .utf8) } + let trimmed = payload? + .trimmingCharacters(in: .whitespacesAndNewlines) + .split(whereSeparator: \.isNewline) + .joined(separator: " ") + if let trimmed, + trimmed.localizedCaseInsensitiveContains("host key verification failed") + { + let host = CommandResolver.parseSSHTarget(target)?.host ?? target + return "SSH check failed: Host key verification failed. Remove the old key with ssh-keygen -R \(host) and try again." + } + if let trimmed, !trimmed.isEmpty { + if let message = response.message, message.hasPrefix("exit ") { + return "SSH check failed: \(trimmed) (\(message))" + } + return "SSH check failed: \(trimmed)" + } + if let message = response.message { + return "SSH check failed (\(message))" + } + return "SSH check failed" + } +} diff --git a/apps/macos/Sources/OpenClaw/Resources/Info.plist b/apps/macos/Sources/OpenClaw/Resources/Info.plist index 706fe7029c4..89ebf70beb4 100644 --- a/apps/macos/Sources/OpenClaw/Resources/Info.plist +++ b/apps/macos/Sources/OpenClaw/Resources/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2026.3.9 + 2026.3.14 CFBundleVersion - 202603080 + 202603140 CFBundleIconFile OpenClaw CFBundleURLTypes @@ -59,6 +59,8 @@ OpenClaw uses speech recognition to detect your Voice Wake trigger phrase. NSAppleEventsUsageDescription OpenClaw needs Automation (AppleScript) permission to drive Terminal and other apps for agent actions. + NSRemindersUsageDescription + OpenClaw can access Reminders when requested by the agent for the apple-reminders skill. NSAppTransportSecurity diff --git a/apps/macos/Sources/OpenClaw/RuntimeLocator.swift b/apps/macos/Sources/OpenClaw/RuntimeLocator.swift index 3112f57879b..6f1ef2b723d 100644 --- a/apps/macos/Sources/OpenClaw/RuntimeLocator.swift +++ b/apps/macos/Sources/OpenClaw/RuntimeLocator.swift @@ -54,7 +54,7 @@ enum RuntimeResolutionError: Error { enum RuntimeLocator { private static let logger = Logger(subsystem: "ai.openclaw", category: "runtime") - private static let minNode = RuntimeVersion(major: 22, minor: 0, patch: 0) + private static let minNode = RuntimeVersion(major: 22, minor: 16, patch: 0) static func resolve( searchPaths: [String] = CommandResolver.preferredPaths()) -> Result @@ -91,7 +91,7 @@ enum RuntimeLocator { switch error { case let .notFound(searchPaths): [ - "openclaw needs Node >=22.0.0 but found no runtime.", + "openclaw needs Node >=22.16.0 but found no runtime.", "PATH searched: \(searchPaths.joined(separator: ":"))", "Install Node: https://nodejs.org/en/download", ].joined(separator: "\n") @@ -105,7 +105,7 @@ enum RuntimeLocator { [ "Could not parse \(kind.rawValue) version output \"\(raw)\" from \(path).", "PATH searched: \(searchPaths.joined(separator: ":"))", - "Try reinstalling or pinning a supported version (Node >=22.0.0).", + "Try reinstalling or pinning a supported version (Node >=22.16.0).", ].joined(separator: "\n") } } diff --git a/apps/macos/Sources/OpenClaw/WebChatSwiftUI.swift b/apps/macos/Sources/OpenClaw/WebChatSwiftUI.swift index 9110ce59faf..86c225f9ef0 100644 --- a/apps/macos/Sources/OpenClaw/WebChatSwiftUI.swift +++ b/apps/macos/Sources/OpenClaw/WebChatSwiftUI.swift @@ -59,7 +59,23 @@ struct MacGatewayChatTransport: OpenClawChatTransport { method: "sessions.list", params: params, timeoutMs: 15000) - return try JSONDecoder().decode(OpenClawChatSessionsListResponse.self, from: data) + let decoded = try JSONDecoder().decode(OpenClawChatSessionsListResponse.self, from: data) + let mainSessionKey = await GatewayConnection.shared.cachedMainSessionKey() + let defaults = decoded.defaults.map { + OpenClawChatSessionsDefaults( + model: $0.model, + contextTokens: $0.contextTokens, + mainSessionKey: mainSessionKey) + } ?? OpenClawChatSessionsDefaults( + model: nil, + contextTokens: nil, + mainSessionKey: mainSessionKey) + return OpenClawChatSessionsListResponse( + ts: decoded.ts, + path: decoded.path, + count: decoded.count, + defaults: defaults, + sessions: decoded.sessions) } func setSessionModel(sessionKey: String, model: String?) async throws { @@ -103,6 +119,13 @@ struct MacGatewayChatTransport: OpenClawChatTransport { try await GatewayConnection.shared.healthOK(timeoutMs: timeoutMs) } + func resetSession(sessionKey: String) async throws { + _ = try await GatewayConnection.shared.request( + method: "sessions.reset", + params: ["key": AnyCodable(sessionKey)], + timeoutMs: 10000) + } + func events() -> AsyncStream { AsyncStream { continuation in let task = Task { diff --git a/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift b/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift index ea85e6c1511..6f97c9bf9f1 100644 --- a/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift @@ -515,6 +515,8 @@ public struct PollParams: Codable, Sendable { public struct AgentParams: Codable, Sendable { public let message: String public let agentid: String? + public let provider: String? + public let model: String? public let to: String? public let replyto: String? public let sessionid: String? @@ -538,12 +540,12 @@ public struct AgentParams: Codable, Sendable { public let inputprovenance: [String: AnyCodable]? public let idempotencykey: String public let label: String? - public let spawnedby: String? - public let workspacedir: String? public init( message: String, agentid: String?, + provider: String?, + model: String?, to: String?, replyto: String?, sessionid: String?, @@ -566,12 +568,12 @@ public struct AgentParams: Codable, Sendable { internalevents: [[String: AnyCodable]]?, inputprovenance: [String: AnyCodable]?, idempotencykey: String, - label: String?, - spawnedby: String?, - workspacedir: String?) + label: String?) { self.message = message self.agentid = agentid + self.provider = provider + self.model = model self.to = to self.replyto = replyto self.sessionid = sessionid @@ -595,13 +597,13 @@ public struct AgentParams: Codable, Sendable { self.inputprovenance = inputprovenance self.idempotencykey = idempotencykey self.label = label - self.spawnedby = spawnedby - self.workspacedir = workspacedir } private enum CodingKeys: String, CodingKey { case message case agentid = "agentId" + case provider + case model case to case replyto = "replyTo" case sessionid = "sessionId" @@ -625,8 +627,6 @@ public struct AgentParams: Codable, Sendable { case inputprovenance = "inputProvenance" case idempotencykey = "idempotencyKey" case label - case spawnedby = "spawnedBy" - case workspacedir = "workspaceDir" } } @@ -1114,6 +1114,7 @@ public struct PushTestResult: Codable, Sendable { public let tokensuffix: String public let topic: String public let environment: String + public let transport: String public init( ok: Bool, @@ -1122,7 +1123,8 @@ public struct PushTestResult: Codable, Sendable { reason: String?, tokensuffix: String, topic: String, - environment: String) + environment: String, + transport: String) { self.ok = ok self.status = status @@ -1131,6 +1133,7 @@ public struct PushTestResult: Codable, Sendable { self.tokensuffix = tokensuffix self.topic = topic self.environment = environment + self.transport = transport } private enum CodingKeys: String, CodingKey { @@ -1141,6 +1144,7 @@ public struct PushTestResult: Codable, Sendable { case tokensuffix = "tokenSuffix" case topic case environment + case transport } } @@ -1322,10 +1326,129 @@ public struct SessionsResolveParams: Codable, Sendable { } } +public struct SessionsCreateParams: Codable, Sendable { + public let key: String? + public let agentid: String? + public let label: String? + public let model: String? + public let parentsessionkey: String? + public let task: String? + public let message: String? + + public init( + key: String?, + agentid: String?, + label: String?, + model: String?, + parentsessionkey: String?, + task: String?, + message: String?) + { + self.key = key + self.agentid = agentid + self.label = label + self.model = model + self.parentsessionkey = parentsessionkey + self.task = task + self.message = message + } + + private enum CodingKeys: String, CodingKey { + case key + case agentid = "agentId" + case label + case model + case parentsessionkey = "parentSessionKey" + case task + case message + } +} + +public struct SessionsSendParams: Codable, Sendable { + public let key: String + public let message: String + public let thinking: String? + public let attachments: [AnyCodable]? + public let timeoutms: Int? + public let idempotencykey: String? + + public init( + key: String, + message: String, + thinking: String?, + attachments: [AnyCodable]?, + timeoutms: Int?, + idempotencykey: String?) + { + self.key = key + self.message = message + self.thinking = thinking + self.attachments = attachments + self.timeoutms = timeoutms + self.idempotencykey = idempotencykey + } + + private enum CodingKeys: String, CodingKey { + case key + case message + case thinking + case attachments + case timeoutms = "timeoutMs" + case idempotencykey = "idempotencyKey" + } +} + +public struct SessionsMessagesSubscribeParams: Codable, Sendable { + public let key: String + + public init( + key: String) + { + self.key = key + } + + private enum CodingKeys: String, CodingKey { + case key + } +} + +public struct SessionsMessagesUnsubscribeParams: Codable, Sendable { + public let key: String + + public init( + key: String) + { + self.key = key + } + + private enum CodingKeys: String, CodingKey { + case key + } +} + +public struct SessionsAbortParams: Codable, Sendable { + public let key: String + public let runid: String? + + public init( + key: String, + runid: String?) + { + self.key = key + self.runid = runid + } + + private enum CodingKeys: String, CodingKey { + case key + case runid = "runId" + } +} + public struct SessionsPatchParams: Codable, Sendable { public let key: String public let label: AnyCodable? public let thinkinglevel: AnyCodable? + public let fastmode: AnyCodable? public let verboselevel: AnyCodable? public let reasoninglevel: AnyCodable? public let responseusage: AnyCodable? @@ -1336,6 +1459,7 @@ public struct SessionsPatchParams: Codable, Sendable { public let execnode: AnyCodable? public let model: AnyCodable? public let spawnedby: AnyCodable? + public let spawnedworkspacedir: AnyCodable? public let spawndepth: AnyCodable? public let subagentrole: AnyCodable? public let subagentcontrolscope: AnyCodable? @@ -1346,6 +1470,7 @@ public struct SessionsPatchParams: Codable, Sendable { key: String, label: AnyCodable?, thinkinglevel: AnyCodable?, + fastmode: AnyCodable?, verboselevel: AnyCodable?, reasoninglevel: AnyCodable?, responseusage: AnyCodable?, @@ -1356,6 +1481,7 @@ public struct SessionsPatchParams: Codable, Sendable { execnode: AnyCodable?, model: AnyCodable?, spawnedby: AnyCodable?, + spawnedworkspacedir: AnyCodable?, spawndepth: AnyCodable?, subagentrole: AnyCodable?, subagentcontrolscope: AnyCodable?, @@ -1365,6 +1491,7 @@ public struct SessionsPatchParams: Codable, Sendable { self.key = key self.label = label self.thinkinglevel = thinkinglevel + self.fastmode = fastmode self.verboselevel = verboselevel self.reasoninglevel = reasoninglevel self.responseusage = responseusage @@ -1375,6 +1502,7 @@ public struct SessionsPatchParams: Codable, Sendable { self.execnode = execnode self.model = model self.spawnedby = spawnedby + self.spawnedworkspacedir = spawnedworkspacedir self.spawndepth = spawndepth self.subagentrole = subagentrole self.subagentcontrolscope = subagentcontrolscope @@ -1386,6 +1514,7 @@ public struct SessionsPatchParams: Codable, Sendable { case key case label case thinkinglevel = "thinkingLevel" + case fastmode = "fastMode" case verboselevel = "verboseLevel" case reasoninglevel = "reasoningLevel" case responseusage = "responseUsage" @@ -1396,6 +1525,7 @@ public struct SessionsPatchParams: Codable, Sendable { case execnode = "execNode" case model case spawnedby = "spawnedBy" + case spawnedworkspacedir = "spawnedWorkspaceDir" case spawndepth = "spawnDepth" case subagentrole = "subagentRole" case subagentcontrolscope = "subagentControlScope" diff --git a/apps/macos/Tests/OpenClawIPCTests/ExecAllowlistTests.swift b/apps/macos/Tests/OpenClawIPCTests/ExecAllowlistTests.swift index f12b8f717dc..fa92cc81ef5 100644 --- a/apps/macos/Tests/OpenClawIPCTests/ExecAllowlistTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/ExecAllowlistTests.swift @@ -141,6 +141,26 @@ struct ExecAllowlistTests { #expect(resolutions.isEmpty) } + @Test func `resolve for allowlist fails closed on line-continued command substitution`() { + let command = ["/bin/sh", "-lc", "echo $\\\n(/usr/bin/touch /tmp/openclaw-allowlist-test-line-cont-subst)"] + let resolutions = ExecCommandResolution.resolveForAllowlist( + command: command, + rawCommand: "echo $\\\n(/usr/bin/touch /tmp/openclaw-allowlist-test-line-cont-subst)", + cwd: nil, + env: ["PATH": "/usr/bin:/bin"]) + #expect(resolutions.isEmpty) + } + + @Test func `resolve for allowlist fails closed on chained line-continued command substitution`() { + let command = ["/bin/sh", "-lc", "echo ok && $\\\n(/usr/bin/touch /tmp/openclaw-allowlist-test-chained-line-cont-subst)"] + let resolutions = ExecCommandResolution.resolveForAllowlist( + command: command, + rawCommand: "echo ok && $\\\n(/usr/bin/touch /tmp/openclaw-allowlist-test-chained-line-cont-subst)", + cwd: nil, + env: ["PATH": "/usr/bin:/bin"]) + #expect(resolutions.isEmpty) + } + @Test func `resolve for allowlist fails closed on quoted backticks`() { let command = ["/bin/sh", "-lc", "echo \"ok `/usr/bin/id`\""] let resolutions = ExecCommandResolution.resolveForAllowlist( @@ -208,6 +228,30 @@ struct ExecAllowlistTests { #expect(resolutions[1].executableName == "touch") } + @Test func `resolve for allowlist unwraps env dispatch wrappers inside shell segments`() { + let command = ["/bin/sh", "-lc", "env /usr/bin/touch /tmp/openclaw-allowlist-test"] + let resolutions = ExecCommandResolution.resolveForAllowlist( + command: command, + rawCommand: "env /usr/bin/touch /tmp/openclaw-allowlist-test", + cwd: nil, + env: ["PATH": "/usr/bin:/bin"]) + #expect(resolutions.count == 1) + #expect(resolutions[0].resolvedPath == "/usr/bin/touch") + #expect(resolutions[0].executableName == "touch") + } + + @Test func `resolve for allowlist unwraps env assignments inside shell segments`() { + let command = ["/bin/sh", "-lc", "env FOO=bar /usr/bin/touch /tmp/openclaw-allowlist-test"] + let resolutions = ExecCommandResolution.resolveForAllowlist( + command: command, + rawCommand: "env FOO=bar /usr/bin/touch /tmp/openclaw-allowlist-test", + cwd: nil, + env: ["PATH": "/usr/bin:/bin"]) + #expect(resolutions.count == 1) + #expect(resolutions[0].resolvedPath == "/usr/bin/touch") + #expect(resolutions[0].executableName == "touch") + } + @Test func `resolve for allowlist unwraps env to effective direct executable`() { let command = ["/usr/bin/env", "FOO=bar", "/usr/bin/printf", "ok"] let resolutions = ExecCommandResolution.resolveForAllowlist( diff --git a/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsGatewayPrompterTests.swift b/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsGatewayPrompterTests.swift index cd4e234ed66..03b17b42ab2 100644 --- a/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsGatewayPrompterTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsGatewayPrompterTests.swift @@ -52,4 +52,51 @@ struct ExecApprovalsGatewayPrompterTests { lastInputSeconds: 400) #expect(!remote) } + + // MARK: - shouldAsk + + @Test func askAlwaysPromptsRegardlessOfSecurity() { + #expect(ExecApprovalsGatewayPrompter._testShouldAsk(security: .deny, ask: .always)) + #expect(ExecApprovalsGatewayPrompter._testShouldAsk(security: .allowlist, ask: .always)) + #expect(ExecApprovalsGatewayPrompter._testShouldAsk(security: .full, ask: .always)) + } + + @Test func askOnMissPromptsOnlyForAllowlist() { + #expect(ExecApprovalsGatewayPrompter._testShouldAsk(security: .allowlist, ask: .onMiss)) + #expect(!ExecApprovalsGatewayPrompter._testShouldAsk(security: .deny, ask: .onMiss)) + #expect(!ExecApprovalsGatewayPrompter._testShouldAsk(security: .full, ask: .onMiss)) + } + + @Test func askOffNeverPrompts() { + #expect(!ExecApprovalsGatewayPrompter._testShouldAsk(security: .deny, ask: .off)) + #expect(!ExecApprovalsGatewayPrompter._testShouldAsk(security: .allowlist, ask: .off)) + #expect(!ExecApprovalsGatewayPrompter._testShouldAsk(security: .full, ask: .off)) + } + + @Test func fallbackAllowlistAllowsMatchingResolvedPath() { + let decision = ExecApprovalsGatewayPrompter._testFallbackDecision( + command: "git status", + resolvedPath: "/usr/bin/git", + askFallback: .allowlist, + allowlistPatterns: ["/usr/bin/git"]) + #expect(decision == .allowOnce) + } + + @Test func fallbackAllowlistDeniesAllowlistMiss() { + let decision = ExecApprovalsGatewayPrompter._testFallbackDecision( + command: "git status", + resolvedPath: "/usr/bin/git", + askFallback: .allowlist, + allowlistPatterns: ["/usr/bin/rg"]) + #expect(decision == .deny) + } + + @Test func fallbackFullAllowsWhenPromptCannotBeShown() { + let decision = ExecApprovalsGatewayPrompter._testFallbackDecision( + command: "git status", + resolvedPath: "/usr/bin/git", + askFallback: .full, + allowlistPatterns: []) + #expect(decision == .allowOnce) + } } diff --git a/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsSocketAuthTests.swift b/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsSocketAuthTests.swift new file mode 100644 index 00000000000..ee0ead1f902 --- /dev/null +++ b/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsSocketAuthTests.swift @@ -0,0 +1,21 @@ +import Testing +@testable import OpenClaw + +struct ExecApprovalsSocketAuthTests { + @Test + func `timing safe hex compare matches equal strings`() { + #expect(timingSafeHexStringEquals(String(repeating: "a", count: 64), String(repeating: "a", count: 64))) + } + + @Test + func `timing safe hex compare rejects mismatched strings`() { + let expected = String(repeating: "a", count: 63) + "b" + let provided = String(repeating: "a", count: 63) + "c" + #expect(!timingSafeHexStringEquals(expected, provided)) + } + + @Test + func `timing safe hex compare rejects different length strings`() { + #expect(!timingSafeHexStringEquals(String(repeating: "a", count: 64), "deadbeef")) + } +} diff --git a/apps/macos/Tests/OpenClawIPCTests/ExecSkillBinTrustTests.swift b/apps/macos/Tests/OpenClawIPCTests/ExecSkillBinTrustTests.swift new file mode 100644 index 00000000000..779b59a3499 --- /dev/null +++ b/apps/macos/Tests/OpenClawIPCTests/ExecSkillBinTrustTests.swift @@ -0,0 +1,90 @@ +import Foundation +import Testing +@testable import OpenClaw + +struct ExecSkillBinTrustTests { + @Test func `build trust index resolves skill bin paths`() throws { + let fixture = try Self.makeExecutable(named: "jq") + defer { try? FileManager.default.removeItem(at: fixture.root) } + + let trust = SkillBinsCache._testBuildTrustIndex( + report: Self.makeReport(bins: ["jq"]), + searchPaths: [fixture.root.path]) + + #expect(trust.names == ["jq"]) + #expect(trust.pathsByName["jq"] == [fixture.path]) + } + + @Test func `skill auto allow accepts trusted resolved skill bin path`() throws { + let fixture = try Self.makeExecutable(named: "jq") + defer { try? FileManager.default.removeItem(at: fixture.root) } + + let trust = SkillBinsCache._testBuildTrustIndex( + report: Self.makeReport(bins: ["jq"]), + searchPaths: [fixture.root.path]) + let resolution = ExecCommandResolution( + rawExecutable: "jq", + resolvedPath: fixture.path, + executableName: "jq", + cwd: nil) + + #expect(ExecApprovalEvaluator._testIsSkillAutoAllowed([resolution], trustedBinsByName: trust.pathsByName)) + } + + @Test func `skill auto allow rejects same basename at different path`() throws { + let trusted = try Self.makeExecutable(named: "jq") + let untrusted = try Self.makeExecutable(named: "jq") + defer { + try? FileManager.default.removeItem(at: trusted.root) + try? FileManager.default.removeItem(at: untrusted.root) + } + + let trust = SkillBinsCache._testBuildTrustIndex( + report: Self.makeReport(bins: ["jq"]), + searchPaths: [trusted.root.path]) + let resolution = ExecCommandResolution( + rawExecutable: "jq", + resolvedPath: untrusted.path, + executableName: "jq", + cwd: nil) + + #expect(!ExecApprovalEvaluator._testIsSkillAutoAllowed([resolution], trustedBinsByName: trust.pathsByName)) + } + + private static func makeExecutable(named name: String) throws -> (root: URL, path: String) { + let root = FileManager.default.temporaryDirectory + .appendingPathComponent("openclaw-skill-bin-\(UUID().uuidString)", isDirectory: true) + try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) + let file = root.appendingPathComponent(name) + try "#!/bin/sh\nexit 0\n".write(to: file, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes( + [.posixPermissions: NSNumber(value: Int16(0o755))], + ofItemAtPath: file.path) + return (root, file.path) + } + + private static func makeReport(bins: [String]) -> SkillsStatusReport { + SkillsStatusReport( + workspaceDir: "/tmp/workspace", + managedSkillsDir: "/tmp/skills", + skills: [ + SkillStatus( + name: "test-skill", + description: "test", + source: "local", + filePath: "/tmp/skills/test-skill/SKILL.md", + baseDir: "/tmp/skills/test-skill", + skillKey: "test-skill", + primaryEnv: nil, + emoji: nil, + homepage: nil, + always: false, + disabled: false, + eligible: true, + requirements: SkillRequirements(bins: bins, env: [], config: []), + missing: SkillMissing(bins: [], env: [], config: []), + configChecks: [], + install: []) + ]) + } +} diff --git a/apps/macos/Tests/OpenClawIPCTests/GatewayChannelConnectTests.swift b/apps/macos/Tests/OpenClawIPCTests/GatewayChannelConnectTests.swift index 8d37faa511e..9942f6e84ce 100644 --- a/apps/macos/Tests/OpenClawIPCTests/GatewayChannelConnectTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/GatewayChannelConnectTests.swift @@ -7,6 +7,11 @@ struct GatewayChannelConnectTests { private enum FakeResponse { case helloOk(delayMs: Int) case invalid(delayMs: Int) + case authFailed( + delayMs: Int, + detailCode: String, + canRetryWithDeviceToken: Bool, + recommendedNextStep: String?) } private func makeSession(response: FakeResponse) -> GatewayTestWebSocketSession { @@ -27,6 +32,14 @@ struct GatewayChannelConnectTests { case let .invalid(ms): delayMs = ms message = .string("not json") + case let .authFailed(ms, detailCode, canRetryWithDeviceToken, recommendedNextStep): + delayMs = ms + let id = task.snapshotConnectRequestID() ?? "connect" + message = .data(GatewayWebSocketTestSupport.connectAuthFailureData( + id: id, + detailCode: detailCode, + canRetryWithDeviceToken: canRetryWithDeviceToken, + recommendedNextStep: recommendedNextStep)) } try await Task.sleep(nanoseconds: UInt64(delayMs) * 1_000_000) return message @@ -71,4 +84,29 @@ struct GatewayChannelConnectTests { }()) #expect(session.snapshotMakeCount() == 1) } + + @Test func `connect surfaces structured auth failure`() async throws { + let session = self.makeSession(response: .authFailed( + delayMs: 0, + detailCode: GatewayConnectAuthDetailCode.authTokenMissing.rawValue, + canRetryWithDeviceToken: true, + recommendedNextStep: GatewayConnectRecoveryNextStep.updateAuthConfiguration.rawValue)) + let channel = try GatewayChannelActor( + url: #require(URL(string: "ws://example.invalid")), + token: nil, + session: WebSocketSessionBox(session: session)) + + do { + try await channel.connect() + Issue.record("expected GatewayConnectAuthError") + } catch let error as GatewayConnectAuthError { + #expect(error.detail == .authTokenMissing) + #expect(error.detailCode == GatewayConnectAuthDetailCode.authTokenMissing.rawValue) + #expect(error.canRetryWithDeviceToken) + #expect(error.recommendedNextStep == .updateAuthConfiguration) + #expect(error.recommendedNextStepCode == GatewayConnectRecoveryNextStep.updateAuthConfiguration.rawValue) + } catch { + Issue.record("unexpected error: \(error)") + } + } } diff --git a/apps/macos/Tests/OpenClawIPCTests/GatewayWebSocketTestSupport.swift b/apps/macos/Tests/OpenClawIPCTests/GatewayWebSocketTestSupport.swift index 8af4ccf6905..cf2b13de5ea 100644 --- a/apps/macos/Tests/OpenClawIPCTests/GatewayWebSocketTestSupport.swift +++ b/apps/macos/Tests/OpenClawIPCTests/GatewayWebSocketTestSupport.swift @@ -52,6 +52,40 @@ enum GatewayWebSocketTestSupport { return Data(json.utf8) } + static func connectAuthFailureData( + id: String, + detailCode: String, + message: String = "gateway auth rejected", + canRetryWithDeviceToken: Bool = false, + recommendedNextStep: String? = nil) -> Data + { + let recommendedNextStepJson: String + if let recommendedNextStep { + recommendedNextStepJson = """ + , + "recommendedNextStep": "\(recommendedNextStep)" + """ + } else { + recommendedNextStepJson = "" + } + let json = """ + { + "type": "res", + "id": "\(id)", + "ok": false, + "error": { + "message": "\(message)", + "details": { + "code": "\(detailCode)", + "canRetryWithDeviceToken": \(canRetryWithDeviceToken ? "true" : "false") + \(recommendedNextStepJson) + } + } + } + """ + return Data(json.utf8) + } + static func requestID(from message: URLSessionWebSocketTask.Message) -> String? { guard let obj = self.requestFrameObject(from: message) else { return nil } guard (obj["type"] as? String) == "req" else { diff --git a/apps/macos/Tests/OpenClawIPCTests/LaunchAgentManagerTests.swift b/apps/macos/Tests/OpenClawIPCTests/LaunchAgentManagerTests.swift new file mode 100644 index 00000000000..c9a17d57577 --- /dev/null +++ b/apps/macos/Tests/OpenClawIPCTests/LaunchAgentManagerTests.swift @@ -0,0 +1,19 @@ +import Foundation +import Testing +@testable import OpenClaw + +struct LaunchAgentManagerTests { + @Test func `launch at login plist does not keep app alive after manual quit`() throws { + let plist = LaunchAgentManager.plistContents(bundlePath: "/Applications/OpenClaw.app") + let data = try #require(plist.data(using: .utf8)) + let object = try #require( + PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any] + ) + + #expect(object["RunAtLoad"] as? Bool == true) + #expect(object["KeepAlive"] == nil) + + let args = try #require(object["ProgramArguments"] as? [String]) + #expect(args == ["/Applications/OpenClaw.app/Contents/MacOS/OpenClaw"]) + } +} diff --git a/apps/macos/Tests/OpenClawIPCTests/LowCoverageHelperTests.swift b/apps/macos/Tests/OpenClawIPCTests/LowCoverageHelperTests.swift index c8928978f74..b47dd70c3ff 100644 --- a/apps/macos/Tests/OpenClawIPCTests/LowCoverageHelperTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/LowCoverageHelperTests.swift @@ -139,6 +139,54 @@ struct LowCoverageHelperTests { #expect(emptyReport.summary.contains("Nothing is listening")) } + @Test func `port guardian remote mode does not kill docker`() { + #expect(PortGuardian._testIsExpected( + command: "com.docker.backend", + fullCommand: "com.docker.backend", + port: 18789, mode: .remote) == true) + + #expect(PortGuardian._testIsExpected( + command: "ssh", + fullCommand: "ssh -L 18789:localhost:18789 user@host", + port: 18789, mode: .remote) == true) + + #expect(PortGuardian._testIsExpected( + command: "podman", + fullCommand: "podman", + port: 18789, mode: .remote) == true) + } + + @Test func `port guardian local mode still rejects unexpected`() { + #expect(PortGuardian._testIsExpected( + command: "com.docker.backend", + fullCommand: "com.docker.backend", + port: 18789, mode: .local) == false) + + #expect(PortGuardian._testIsExpected( + command: "python", + fullCommand: "python server.py", + port: 18789, mode: .local) == false) + + #expect(PortGuardian._testIsExpected( + command: "node", + fullCommand: "node /path/to/gateway-daemon", + port: 18789, mode: .local) == true) + } + + @Test func `port guardian remote mode report accepts any listener`() { + let dockerReport = PortGuardian._testBuildReport( + port: 18789, mode: .remote, + listeners: [(pid: 99, command: "com.docker.backend", + fullCommand: "com.docker.backend", user: "me")]) + #expect(dockerReport.offenders.isEmpty) + + let localDockerReport = PortGuardian._testBuildReport( + port: 18789, mode: .local, + listeners: [(pid: 99, command: "com.docker.backend", + fullCommand: "com.docker.backend", user: "me")]) + #expect(!localDockerReport.offenders.isEmpty) + } + @Test @MainActor func `canvas scheme handler resolves files and errors`() throws { let root = FileManager().temporaryDirectory .appendingPathComponent("canvas-\(UUID().uuidString)", isDirectory: true) @@ -168,6 +216,32 @@ struct LowCoverageHelperTests { #expect(handler._testTextEncodingName(for: "application/octet-stream") == nil) } + @Test @MainActor func `canvas scheme handler blocks symlink escapes`() throws { + let root = FileManager().temporaryDirectory + .appendingPathComponent("canvas-\(UUID().uuidString)", isDirectory: true) + defer { try? FileManager().removeItem(at: root) } + try FileManager().createDirectory(at: root, withIntermediateDirectories: true) + + let session = root.appendingPathComponent("main", isDirectory: true) + try FileManager().createDirectory(at: session, withIntermediateDirectories: true) + + let outside = root.deletingLastPathComponent().appendingPathComponent("canvas-secret-\(UUID().uuidString).txt") + defer { try? FileManager().removeItem(at: outside) } + try "top-secret".write(to: outside, atomically: true, encoding: .utf8) + + let symlink = session.appendingPathComponent("index.html") + try FileManager().createSymbolicLink(at: symlink, withDestinationURL: outside) + + let handler = CanvasSchemeHandler(root: root) + let url = try #require(CanvasScheme.makeURL(session: "main", path: "index.html")) + let response = handler._testResponse(for: url) + let body = String(data: response.data, encoding: .utf8) ?? "" + + #expect(response.mime == "text/html") + #expect(body.contains("Forbidden")) + #expect(!body.contains("top-secret")) + } + @Test @MainActor func `menu context card injector inserts and finds index`() { let injector = MenuContextCardInjector() let menu = NSMenu() diff --git a/apps/macos/Tests/OpenClawIPCTests/MacNodeBrowserProxyTests.swift b/apps/macos/Tests/OpenClawIPCTests/MacNodeBrowserProxyTests.swift index c000f6d4241..b341263b21f 100644 --- a/apps/macos/Tests/OpenClawIPCTests/MacNodeBrowserProxyTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/MacNodeBrowserProxyTests.swift @@ -38,4 +38,49 @@ struct MacNodeBrowserProxyTests { #expect(tabs.count == 1) #expect(tabs[0]["id"] as? String == "tab-1") } + + // Regression test: nested POST bodies must serialize without __SwiftValue crashes. + @Test func postRequestSerializesNestedBodyWithoutCrash() async throws { + actor BodyCapture { + private var body: Data? + + func set(_ body: Data?) { + self.body = body + } + + func get() -> Data? { + self.body + } + } + + let capturedBody = BodyCapture() + let proxy = MacNodeBrowserProxy( + endpointProvider: { + MacNodeBrowserProxy.Endpoint( + baseURL: URL(string: "http://127.0.0.1:18791")!, + token: nil, + password: nil) + }, + performRequest: { request in + await capturedBody.set(request.httpBody) + let url = try #require(request.url) + let response = try #require( + HTTPURLResponse( + url: url, + statusCode: 200, + httpVersion: nil, + headerFields: nil)) + return (Data(#"{"ok":true}"#.utf8), response) + }) + + _ = try await proxy.request( + paramsJSON: #"{"method":"POST","path":"/action","body":{"nested":{"key":"val"},"arr":[1,2]}}"#) + + let bodyData = try #require(await capturedBody.get()) + let parsed = try #require(JSONSerialization.jsonObject(with: bodyData) as? [String: Any]) + let nested = try #require(parsed["nested"] as? [String: Any]) + #expect(nested["key"] as? String == "val") + let arr = try #require(parsed["arr"] as? [Any]) + #expect(arr.count == 2) + } } diff --git a/apps/macos/Tests/OpenClawIPCTests/MenuSessionsInjectorTests.swift b/apps/macos/Tests/OpenClawIPCTests/MenuSessionsInjectorTests.swift index 186675f1eea..b1d01b9650e 100644 --- a/apps/macos/Tests/OpenClawIPCTests/MenuSessionsInjectorTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/MenuSessionsInjectorTests.swift @@ -5,7 +5,26 @@ import Testing @Suite(.serialized) @MainActor struct MenuSessionsInjectorTests { - @Test func `injects disconnected message`() { + @Test func anchorsDynamicRowsBelowControlsAndActions() throws { + let injector = MenuSessionsInjector() + + let menu = NSMenu() + menu.addItem(NSMenuItem(title: "Header", action: nil, keyEquivalent: "")) + menu.addItem(.separator()) + menu.addItem(NSMenuItem(title: "Send Heartbeats", action: nil, keyEquivalent: "")) + menu.addItem(NSMenuItem(title: "Browser Control", action: nil, keyEquivalent: "")) + menu.addItem(.separator()) + menu.addItem(NSMenuItem(title: "Open Dashboard", action: nil, keyEquivalent: "")) + menu.addItem(NSMenuItem(title: "Open Chat", action: nil, keyEquivalent: "")) + menu.addItem(.separator()) + menu.addItem(NSMenuItem(title: "Settings…", action: nil, keyEquivalent: "")) + + let footerSeparatorIndex = try #require(menu.items.lastIndex(where: { $0.isSeparatorItem })) + #expect(injector.testingFindInsertIndex(in: menu) == footerSeparatorIndex) + #expect(injector.testingFindNodesInsertIndex(in: menu) == footerSeparatorIndex) + } + + @Test func injectsDisconnectedMessage() { let injector = MenuSessionsInjector() injector.setTestingControlChannelConnected(false) injector.setTestingSnapshot(nil, errorText: nil) @@ -19,7 +38,7 @@ struct MenuSessionsInjectorTests { #expect(menu.items.contains { $0.tag == 9_415_557 }) } - @Test func `injects session rows`() { + @Test func injectsSessionRows() throws { let injector = MenuSessionsInjector() injector.setTestingControlChannelConnected(true) @@ -88,10 +107,22 @@ struct MenuSessionsInjectorTests { menu.addItem(NSMenuItem(title: "Header", action: nil, keyEquivalent: "")) menu.addItem(.separator()) menu.addItem(NSMenuItem(title: "Send Heartbeats", action: nil, keyEquivalent: "")) + menu.addItem(NSMenuItem(title: "Browser Control", action: nil, keyEquivalent: "")) + menu.addItem(.separator()) + menu.addItem(NSMenuItem(title: "Open Dashboard", action: nil, keyEquivalent: "")) + menu.addItem(.separator()) + menu.addItem(NSMenuItem(title: "Settings…", action: nil, keyEquivalent: "")) injector.injectForTesting(into: menu) #expect(menu.items.contains { $0.tag == 9_415_557 }) #expect(menu.items.contains { $0.tag == 9_415_557 && $0.isSeparatorItem }) + let sendHeartbeatsIndex = try #require(menu.items.firstIndex(where: { $0.title == "Send Heartbeats" })) + let openDashboardIndex = try #require(menu.items.firstIndex(where: { $0.title == "Open Dashboard" })) + let firstInjectedIndex = try #require(menu.items.firstIndex(where: { $0.tag == 9_415_557 })) + let settingsIndex = try #require(menu.items.firstIndex(where: { $0.title == "Settings…" })) + #expect(sendHeartbeatsIndex < firstInjectedIndex) + #expect(openDashboardIndex < firstInjectedIndex) + #expect(firstInjectedIndex < settingsIndex) } @Test func `cost usage submenu does not use injector delegate`() { diff --git a/apps/macos/Tests/OpenClawIPCTests/NodeServiceManagerTests.swift b/apps/macos/Tests/OpenClawIPCTests/NodeServiceManagerTests.swift new file mode 100644 index 00000000000..df49a82e223 --- /dev/null +++ b/apps/macos/Tests/OpenClawIPCTests/NodeServiceManagerTests.swift @@ -0,0 +1,19 @@ +import Foundation +import Testing +@testable import OpenClaw + +@Suite(.serialized) struct NodeServiceManagerTests { + @Test func `builds node service commands with current CLI shape`() throws { + let tmp = try makeTempDirForTests() + CommandResolver.setProjectRoot(tmp.path) + + let openclawPath = tmp.appendingPathComponent("node_modules/.bin/openclaw") + try makeExecutableForTests(at: openclawPath) + + let start = NodeServiceManager._testServiceCommand(["start"]) + #expect(start == [openclawPath.path, "node", "start", "--json"]) + + let stop = NodeServiceManager._testServiceCommand(["stop"]) + #expect(stop == [openclawPath.path, "node", "stop", "--json"]) + } +} diff --git a/apps/macos/Tests/OpenClawIPCTests/OnboardingRemoteAuthPromptTests.swift b/apps/macos/Tests/OpenClawIPCTests/OnboardingRemoteAuthPromptTests.swift new file mode 100644 index 00000000000..00f3e704708 --- /dev/null +++ b/apps/macos/Tests/OpenClawIPCTests/OnboardingRemoteAuthPromptTests.swift @@ -0,0 +1,139 @@ +import OpenClawKit +import Testing +@testable import OpenClaw + +@MainActor +struct OnboardingRemoteAuthPromptTests { + @Test func `auth detail codes map to remote auth issues`() { + let tokenMissing = GatewayConnectAuthError( + message: "token missing", + detailCode: GatewayConnectAuthDetailCode.authTokenMissing.rawValue, + canRetryWithDeviceToken: false) + let tokenMismatch = GatewayConnectAuthError( + message: "token mismatch", + detailCode: GatewayConnectAuthDetailCode.authTokenMismatch.rawValue, + canRetryWithDeviceToken: false) + let tokenNotConfigured = GatewayConnectAuthError( + message: "token not configured", + detailCode: GatewayConnectAuthDetailCode.authTokenNotConfigured.rawValue, + canRetryWithDeviceToken: false) + let bootstrapInvalid = GatewayConnectAuthError( + message: "setup code expired", + detailCode: GatewayConnectAuthDetailCode.authBootstrapTokenInvalid.rawValue, + canRetryWithDeviceToken: false) + let passwordMissing = GatewayConnectAuthError( + message: "password missing", + detailCode: GatewayConnectAuthDetailCode.authPasswordMissing.rawValue, + canRetryWithDeviceToken: false) + let pairingRequired = GatewayConnectAuthError( + message: "pairing required", + detailCode: GatewayConnectAuthDetailCode.pairingRequired.rawValue, + canRetryWithDeviceToken: false) + let unknown = GatewayConnectAuthError( + message: "other", + detailCode: "SOMETHING_ELSE", + canRetryWithDeviceToken: false) + + #expect(RemoteGatewayAuthIssue(error: tokenMissing) == .tokenRequired) + #expect(RemoteGatewayAuthIssue(error: tokenMismatch) == .tokenMismatch) + #expect(RemoteGatewayAuthIssue(error: tokenNotConfigured) == .gatewayTokenNotConfigured) + #expect(RemoteGatewayAuthIssue(error: bootstrapInvalid) == .setupCodeExpired) + #expect(RemoteGatewayAuthIssue(error: passwordMissing) == .passwordRequired) + #expect(RemoteGatewayAuthIssue(error: pairingRequired) == .pairingRequired) + #expect(RemoteGatewayAuthIssue(error: unknown) == nil) + } + + @Test func `password detail family maps to password required issue`() { + let mismatch = GatewayConnectAuthError( + message: "password mismatch", + detailCode: GatewayConnectAuthDetailCode.authPasswordMismatch.rawValue, + canRetryWithDeviceToken: false) + let notConfigured = GatewayConnectAuthError( + message: "password not configured", + detailCode: GatewayConnectAuthDetailCode.authPasswordNotConfigured.rawValue, + canRetryWithDeviceToken: false) + + #expect(RemoteGatewayAuthIssue(error: mismatch) == .passwordRequired) + #expect(RemoteGatewayAuthIssue(error: notConfigured) == .passwordRequired) + } + + @Test func `token field visibility follows onboarding rules`() { + #expect(OnboardingView.shouldShowRemoteTokenField( + showAdvancedConnection: false, + remoteToken: "", + remoteTokenUnsupported: false, + authIssue: nil) == false) + #expect(OnboardingView.shouldShowRemoteTokenField( + showAdvancedConnection: true, + remoteToken: "", + remoteTokenUnsupported: false, + authIssue: nil)) + #expect(OnboardingView.shouldShowRemoteTokenField( + showAdvancedConnection: false, + remoteToken: "secret", + remoteTokenUnsupported: false, + authIssue: nil)) + #expect(OnboardingView.shouldShowRemoteTokenField( + showAdvancedConnection: false, + remoteToken: "", + remoteTokenUnsupported: true, + authIssue: nil)) + #expect(OnboardingView.shouldShowRemoteTokenField( + showAdvancedConnection: false, + remoteToken: "", + remoteTokenUnsupported: false, + authIssue: .tokenRequired)) + #expect(OnboardingView.shouldShowRemoteTokenField( + showAdvancedConnection: false, + remoteToken: "", + remoteTokenUnsupported: false, + authIssue: .tokenMismatch)) + #expect(OnboardingView.shouldShowRemoteTokenField( + showAdvancedConnection: false, + remoteToken: "", + remoteTokenUnsupported: false, + authIssue: .gatewayTokenNotConfigured) == false) + #expect(OnboardingView.shouldShowRemoteTokenField( + showAdvancedConnection: false, + remoteToken: "", + remoteTokenUnsupported: false, + authIssue: .setupCodeExpired) == false) + #expect(OnboardingView.shouldShowRemoteTokenField( + showAdvancedConnection: false, + remoteToken: "", + remoteTokenUnsupported: false, + authIssue: .pairingRequired) == false) + } + + @Test func `pairing required copy points users to pair approve`() { + let issue = RemoteGatewayAuthIssue.pairingRequired + + #expect(issue.title == "This device needs pairing approval") + #expect(issue.body.contains("`/pair approve`")) + #expect(issue.statusMessage.contains("/pair approve")) + #expect(issue.footnote?.contains("`openclaw devices approve`") == true) + } + + @Test func `paired device success copy explains auth source`() { + let pairedDevice = RemoteGatewayProbeSuccess(authSource: .deviceToken) + let bootstrap = RemoteGatewayProbeSuccess(authSource: .bootstrapToken) + let sharedToken = RemoteGatewayProbeSuccess(authSource: .sharedToken) + let noAuth = RemoteGatewayProbeSuccess(authSource: GatewayAuthSource.none) + + #expect(pairedDevice.title == "Connected via paired device") + #expect(pairedDevice.detail == "This Mac used a stored device token. New or unpaired devices may still need the gateway token.") + #expect(bootstrap.title == "Connected with setup code") + #expect(bootstrap.detail == "This Mac is still using the temporary setup code. Approve pairing to finish provisioning device-scoped auth.") + #expect(sharedToken.title == "Connected with gateway token") + #expect(sharedToken.detail == nil) + #expect(noAuth.title == "Remote gateway ready") + #expect(noAuth.detail == nil) + } + + @Test func `transient probe mode restore does not clear probe feedback`() { + #expect(OnboardingView.shouldResetRemoteProbeFeedback(for: .local, suppressReset: false)) + #expect(OnboardingView.shouldResetRemoteProbeFeedback(for: .unconfigured, suppressReset: false)) + #expect(OnboardingView.shouldResetRemoteProbeFeedback(for: .remote, suppressReset: false) == false) + #expect(OnboardingView.shouldResetRemoteProbeFeedback(for: .local, suppressReset: true) == false) + } +} diff --git a/apps/macos/Tests/OpenClawIPCTests/RuntimeLocatorTests.swift b/apps/macos/Tests/OpenClawIPCTests/RuntimeLocatorTests.swift index 990c033445f..782dbd77212 100644 --- a/apps/macos/Tests/OpenClawIPCTests/RuntimeLocatorTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/RuntimeLocatorTests.swift @@ -16,7 +16,7 @@ struct RuntimeLocatorTests { @Test func `resolve succeeds with valid node`() throws { let script = """ #!/bin/sh - echo v22.5.0 + echo v22.16.0 """ let node = try self.makeTempExecutable(contents: script) let result = RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path]) @@ -25,7 +25,23 @@ struct RuntimeLocatorTests { return } #expect(res.path == node.path) - #expect(res.version == RuntimeVersion(major: 22, minor: 5, patch: 0)) + #expect(res.version == RuntimeVersion(major: 22, minor: 16, patch: 0)) + } + + @Test func `resolve fails on boundary below minimum`() throws { + let script = """ + #!/bin/sh + echo v22.15.9 + """ + let node = try self.makeTempExecutable(contents: script) + let result = RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path]) + guard case let .failure(.unsupported(_, found, required, path, _)) = result else { + Issue.record("Expected unsupported error, got \(result)") + return + } + #expect(found == RuntimeVersion(major: 22, minor: 15, patch: 9)) + #expect(required == RuntimeVersion(major: 22, minor: 16, patch: 0)) + #expect(path == node.path) } @Test func `resolve fails when too old`() throws { @@ -60,7 +76,17 @@ struct RuntimeLocatorTests { @Test func `describe failure includes paths`() { let msg = RuntimeLocator.describeFailure(.notFound(searchPaths: ["/tmp/a", "/tmp/b"])) + #expect(msg.contains("Node >=22.16.0")) #expect(msg.contains("PATH searched: /tmp/a:/tmp/b")) + + let parseMsg = RuntimeLocator.describeFailure( + .versionParse( + kind: .node, + raw: "garbage", + path: "/usr/local/bin/node", + searchPaths: ["/usr/local/bin"], + )) + #expect(parseMsg.contains("Node >=22.16.0")) } @Test func `runtime version parses with leading V and metadata`() { diff --git a/apps/macos/Tests/OpenClawIPCTests/VoiceWakeRuntimeTests.swift b/apps/macos/Tests/OpenClawIPCTests/VoiceWakeRuntimeTests.swift index eac7ceea37d..fcf3f3b1158 100644 --- a/apps/macos/Tests/OpenClawIPCTests/VoiceWakeRuntimeTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/VoiceWakeRuntimeTests.swift @@ -74,4 +74,22 @@ struct VoiceWakeRuntimeTests { let config = WakeWordGateConfig(triggers: ["openclaw"], minPostTriggerGap: 0.3) #expect(WakeWordGate.match(transcript: transcript, segments: segments, config: config)?.command == "do thing") } + + @Test func `gate command text handles foreign string ranges`() { + let transcript = "hey openclaw do thing" + let other = "do thing" + let foreignRange = other.range(of: "do") + let segments = [ + WakeWordSegment(text: "hey", start: 0.0, duration: 0.1, range: transcript.range(of: "hey")), + WakeWordSegment(text: "openclaw", start: 0.2, duration: 0.1, range: transcript.range(of: "openclaw")), + WakeWordSegment(text: "do", start: 0.9, duration: 0.1, range: foreignRange), + WakeWordSegment(text: "thing", start: 1.1, duration: 0.1, range: nil), + ] + + #expect( + WakeWordGate.commandText( + transcript: transcript, + segments: segments, + triggerEndTime: 0.3) == "do thing") + } } diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatSessions.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatSessions.swift index 48f01e09c6a..c5a74c9a9aa 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatSessions.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatSessions.swift @@ -34,6 +34,13 @@ public struct OpenClawChatModelChoice: Identifiable, Codable, Sendable, Hashable public struct OpenClawChatSessionsDefaults: Codable, Sendable { public let model: String? public let contextTokens: Int? + public let mainSessionKey: String? + + public init(model: String?, contextTokens: Int?, mainSessionKey: String? = nil) { + self.model = model + self.contextTokens = contextTokens + self.mainSessionKey = mainSessionKey + } } public struct OpenClawChatSessionEntry: Codable, Identifiable, Sendable, Hashable { @@ -69,4 +76,18 @@ public struct OpenClawChatSessionsListResponse: Codable, Sendable { public let count: Int? public let defaults: OpenClawChatSessionsDefaults? public let sessions: [OpenClawChatSessionEntry] + + public init( + ts: Double?, + path: String?, + count: Int?, + defaults: OpenClawChatSessionsDefaults?, + sessions: [OpenClawChatSessionEntry]) + { + self.ts = ts + self.path = path + self.count = count + self.defaults = defaults + self.sessions = sessions + } } diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTransport.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTransport.swift index bfbd33bfda3..49bd91db372 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTransport.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTransport.swift @@ -27,11 +27,19 @@ public protocol OpenClawChatTransport: Sendable { func events() -> AsyncStream func setActiveSessionKey(_ sessionKey: String) async throws + func resetSession(sessionKey: String) async throws } extension OpenClawChatTransport { public func setActiveSessionKey(_: String) async throws {} + public func resetSession(sessionKey _: String) async throws { + throw NSError( + domain: "OpenClawChatTransport", + code: 0, + userInfo: [NSLocalizedDescriptionKey: "sessions.reset not supported by this transport"]) + } + public func abortRun(sessionKey _: String, runId _: String) async throws { throw NSError( domain: "OpenClawChatTransport", diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift index a136469fbd8..92413aefe64 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift @@ -138,21 +138,23 @@ public final class OpenClawChatViewModel { let now = Date().timeIntervalSince1970 * 1000 let cutoff = now - (24 * 60 * 60 * 1000) let sorted = self.sessions.sorted { ($0.updatedAt ?? 0) > ($1.updatedAt ?? 0) } + let mainSessionKey = self.resolvedMainSessionKey var result: [OpenClawChatSessionEntry] = [] var included = Set() - // Always show the main session first, even if it hasn't been updated recently. - if let main = sorted.first(where: { $0.key == "main" }) { + // Always show the resolved main session first, even if it hasn't been updated recently. + if let main = sorted.first(where: { $0.key == mainSessionKey }) { result.append(main) included.insert(main.key) } else { - result.append(self.placeholderSession(key: "main")) - included.insert("main") + result.append(self.placeholderSession(key: mainSessionKey)) + included.insert(mainSessionKey) } for entry in sorted { guard !included.contains(entry.key) else { continue } + guard entry.key == self.sessionKey || !Self.isHiddenInternalSession(entry.key) else { continue } guard (entry.updatedAt ?? 0) >= cutoff else { continue } result.append(entry) included.insert(entry.key) @@ -169,6 +171,18 @@ public final class OpenClawChatViewModel { return result } + private var resolvedMainSessionKey: String { + let trimmed = self.sessionDefaults?.mainSessionKey? + .trimmingCharacters(in: .whitespacesAndNewlines) + return (trimmed?.isEmpty == false ? trimmed : nil) ?? "main" + } + + private static func isHiddenInternalSession(_ key: String) -> Bool { + let trimmed = key.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return false } + return trimmed == "onboarding" || trimmed.hasSuffix(":onboarding") + } + public var showsModelPicker: Bool { !self.modelChoices.isEmpty } @@ -365,10 +379,19 @@ public final class OpenClawChatViewModel { return "\(message.role)|\(timestamp)|\(text)" } + private static let resetTriggers: Set = ["/new", "/reset", "/clear"] + private func performSend() async { guard !self.isSending else { return } let trimmed = self.input.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty || !self.attachments.isEmpty else { return } + + if Self.resetTriggers.contains(trimmed.lowercased()) { + self.input = "" + await self.performReset() + return + } + let sessionKey = self.sessionKey guard self.healthOK else { @@ -499,6 +522,22 @@ public final class OpenClawChatViewModel { await self.bootstrap() } + private func performReset() async { + self.isLoading = true + self.errorText = nil + defer { self.isLoading = false } + + do { + try await self.transport.resetSession(sessionKey: self.sessionKey) + } catch { + self.errorText = error.localizedDescription + chatUILogger.error("session reset failed \(error.localizedDescription, privacy: .public)") + return + } + + await self.bootstrap() + } + private func performSelectThinkingLevel(_ level: String) async { let next = Self.normalizedThinkingLevel(level) ?? "off" guard next != self.thinkingLevel else { return } @@ -549,7 +588,9 @@ public final class OpenClawChatViewModel { sessionKey: sessionKey, model: nextModelRef) guard requestID == self.latestModelSelectionRequestIDsBySession[sessionKey] else { - self.applySuccessfulModelSelection(next, sessionKey: sessionKey, syncSelection: false) + // Keep older successful patches as rollback state, but do not replay + // stale UI/session state over a newer in-flight or completed selection. + self.lastSuccessfulModelSelectionIDsBySession[sessionKey] = next return } self.applySuccessfulModelSelection(next, sessionKey: sessionKey, syncSelection: true) diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/DeepLinks.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/DeepLinks.swift index 20b3761668b..5f1440ccb1a 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/DeepLinks.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/DeepLinks.swift @@ -9,13 +9,15 @@ public struct GatewayConnectDeepLink: Codable, Sendable, Equatable { public let host: String public let port: Int public let tls: Bool + public let bootstrapToken: String? public let token: String? public let password: String? - public init(host: String, port: Int, tls: Bool, token: String?, password: String?) { + public init(host: String, port: Int, tls: Bool, bootstrapToken: String?, token: String?, password: String?) { self.host = host self.port = port self.tls = tls + self.bootstrapToken = bootstrapToken self.token = token self.password = password } @@ -25,7 +27,7 @@ public struct GatewayConnectDeepLink: Codable, Sendable, Equatable { return URL(string: "\(scheme)://\(self.host):\(self.port)") } - /// Parse a device-pair setup code (base64url-encoded JSON: `{url, token?, password?}`). + /// Parse a device-pair setup code (base64url-encoded JSON: `{url, bootstrapToken?, token?, password?}`). public static func fromSetupCode(_ code: String) -> GatewayConnectDeepLink? { guard let data = Self.decodeBase64Url(code) else { return nil } guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil } @@ -41,9 +43,16 @@ public struct GatewayConnectDeepLink: Codable, Sendable, Equatable { return nil } let port = parsed.port ?? (tls ? 443 : 18789) + let bootstrapToken = json["bootstrapToken"] as? String let token = json["token"] as? String let password = json["password"] as? String - return GatewayConnectDeepLink(host: hostname, port: port, tls: tls, token: token, password: password) + return GatewayConnectDeepLink( + host: hostname, + port: port, + tls: tls, + bootstrapToken: bootstrapToken, + token: token, + password: password) } private static func decodeBase64Url(_ input: String) -> Data? { @@ -140,6 +149,7 @@ public enum DeepLinkParser { host: hostParam, port: port, tls: tls, + bootstrapToken: nil, token: query["token"], password: query["password"])) diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift index f822e32044e..2c3da84af68 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift @@ -112,6 +112,7 @@ public struct GatewayConnectOptions: Sendable { public enum GatewayAuthSource: String, Sendable { case deviceToken = "device-token" case sharedToken = "shared-token" + case bootstrapToken = "bootstrap-token" case password = "password" case none = "none" } @@ -131,39 +132,34 @@ private let defaultOperatorConnectScopes: [String] = [ "operator.pairing", ] -private enum GatewayConnectErrorCodes { - static let authTokenMismatch = "AUTH_TOKEN_MISMATCH" - static let authDeviceTokenMismatch = "AUTH_DEVICE_TOKEN_MISMATCH" - static let authTokenMissing = "AUTH_TOKEN_MISSING" - static let authPasswordMissing = "AUTH_PASSWORD_MISSING" - static let authPasswordMismatch = "AUTH_PASSWORD_MISMATCH" - static let authRateLimited = "AUTH_RATE_LIMITED" - static let pairingRequired = "PAIRING_REQUIRED" - static let controlUiDeviceIdentityRequired = "CONTROL_UI_DEVICE_IDENTITY_REQUIRED" - static let deviceIdentityRequired = "DEVICE_IDENTITY_REQUIRED" +private extension String { + var nilIfEmpty: String? { + self.isEmpty ? nil : self + } } -private struct GatewayConnectAuthError: LocalizedError { - let message: String - let detailCode: String? - let canRetryWithDeviceToken: Bool +private struct SelectedConnectAuth: Sendable { + let authToken: String? + let authBootstrapToken: String? + let authDeviceToken: String? + let authPassword: String? + let signatureToken: String? + let storedToken: String? + let authSource: GatewayAuthSource +} - var errorDescription: String? { self.message } - - var isNonRecoverable: Bool { - switch self.detailCode { - case GatewayConnectErrorCodes.authTokenMissing, - GatewayConnectErrorCodes.authPasswordMissing, - GatewayConnectErrorCodes.authPasswordMismatch, - GatewayConnectErrorCodes.authRateLimited, - GatewayConnectErrorCodes.pairingRequired, - GatewayConnectErrorCodes.controlUiDeviceIdentityRequired, - GatewayConnectErrorCodes.deviceIdentityRequired: - return true - default: - return false - } - } +private enum GatewayConnectErrorCodes { + static let authTokenMismatch = GatewayConnectAuthDetailCode.authTokenMismatch.rawValue + static let authDeviceTokenMismatch = GatewayConnectAuthDetailCode.authDeviceTokenMismatch.rawValue + static let authTokenMissing = GatewayConnectAuthDetailCode.authTokenMissing.rawValue + static let authTokenNotConfigured = GatewayConnectAuthDetailCode.authTokenNotConfigured.rawValue + static let authPasswordMissing = GatewayConnectAuthDetailCode.authPasswordMissing.rawValue + static let authPasswordMismatch = GatewayConnectAuthDetailCode.authPasswordMismatch.rawValue + static let authPasswordNotConfigured = GatewayConnectAuthDetailCode.authPasswordNotConfigured.rawValue + static let authRateLimited = GatewayConnectAuthDetailCode.authRateLimited.rawValue + static let pairingRequired = GatewayConnectAuthDetailCode.pairingRequired.rawValue + static let controlUiDeviceIdentityRequired = GatewayConnectAuthDetailCode.controlUiDeviceIdentityRequired.rawValue + static let deviceIdentityRequired = GatewayConnectAuthDetailCode.deviceIdentityRequired.rawValue } public actor GatewayChannelActor { @@ -175,6 +171,7 @@ public actor GatewayChannelActor { private var connectWaiters: [CheckedContinuation] = [] private var url: URL private var token: String? + private var bootstrapToken: String? private var password: String? private let session: WebSocketSessioning private var backoffMs: Double = 500 @@ -206,6 +203,7 @@ public actor GatewayChannelActor { public init( url: URL, token: String?, + bootstrapToken: String? = nil, password: String? = nil, session: WebSocketSessionBox? = nil, pushHandler: (@Sendable (GatewayPush) async -> Void)? = nil, @@ -214,6 +212,7 @@ public actor GatewayChannelActor { { self.url = url self.token = token + self.bootstrapToken = bootstrapToken self.password = password self.session = session?.session ?? URLSession(configuration: .default) self.pushHandler = pushHandler @@ -278,8 +277,7 @@ public actor GatewayChannelActor { if self.shouldPauseReconnectAfterAuthFailure(error) { self.reconnectPausedForAuthFailure = true self.logger.error( - "gateway watchdog reconnect paused for non-recoverable auth failure " + - "\(error.localizedDescription, privacy: .public)" + "gateway watchdog reconnect paused for non-recoverable auth failure \(error.localizedDescription, privacy: .public)" ) continue } @@ -420,39 +418,24 @@ public actor GatewayChannelActor { } let includeDeviceIdentity = options.includeDeviceIdentity let identity = includeDeviceIdentity ? DeviceIdentityStore.loadOrCreate() : nil - let storedToken = - (includeDeviceIdentity && identity != nil) - ? DeviceAuthStore.loadToken(deviceId: identity!.deviceId, role: role)?.token - : nil - let shouldUseDeviceRetryToken = - includeDeviceIdentity && self.pendingDeviceTokenRetry && - storedToken != nil && self.token != nil && self.isTrustedDeviceRetryEndpoint() - if shouldUseDeviceRetryToken { + let selectedAuth = self.selectConnectAuth( + role: role, + includeDeviceIdentity: includeDeviceIdentity, + deviceId: identity?.deviceId) + if selectedAuth.authDeviceToken != nil && self.pendingDeviceTokenRetry { self.pendingDeviceTokenRetry = false } - // Keep shared credentials explicit when provided. Device token retry is attached - // only on a bounded second attempt after token mismatch. - let authToken = self.token ?? (includeDeviceIdentity ? storedToken : nil) - let authDeviceToken = shouldUseDeviceRetryToken ? storedToken : nil - let authSource: GatewayAuthSource - if authDeviceToken != nil || (self.token == nil && storedToken != nil) { - authSource = .deviceToken - } else if authToken != nil { - authSource = .sharedToken - } else if self.password != nil { - authSource = .password - } else { - authSource = .none - } - self.lastAuthSource = authSource - self.logger.info("gateway connect auth=\(authSource.rawValue, privacy: .public)") - if let authToken { + self.lastAuthSource = selectedAuth.authSource + self.logger.info("gateway connect auth=\(selectedAuth.authSource.rawValue, privacy: .public)") + if let authToken = selectedAuth.authToken { var auth: [String: ProtoAnyCodable] = ["token": ProtoAnyCodable(authToken)] - if let authDeviceToken { + if let authDeviceToken = selectedAuth.authDeviceToken { auth["deviceToken"] = ProtoAnyCodable(authDeviceToken) } params["auth"] = ProtoAnyCodable(auth) - } else if let password = self.password { + } else if let authBootstrapToken = selectedAuth.authBootstrapToken { + params["auth"] = ProtoAnyCodable(["bootstrapToken": ProtoAnyCodable(authBootstrapToken)]) + } else if let password = selectedAuth.authPassword { params["auth"] = ProtoAnyCodable(["password": ProtoAnyCodable(password)]) } let signedAtMs = Int(Date().timeIntervalSince1970 * 1000) @@ -465,7 +448,7 @@ public actor GatewayChannelActor { role: role, scopes: scopes, signedAtMs: signedAtMs, - token: authToken, + token: selectedAuth.signatureToken, nonce: connectNonce, platform: platform, deviceFamily: InstanceIdentity.deviceFamily) @@ -494,14 +477,14 @@ public actor GatewayChannelActor { } catch { let shouldRetryWithDeviceToken = self.shouldRetryWithStoredDeviceToken( error: error, - explicitGatewayToken: self.token, - storedToken: storedToken, - attemptedDeviceTokenRetry: authDeviceToken != nil) + explicitGatewayToken: self.token?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty, + storedToken: selectedAuth.storedToken, + attemptedDeviceTokenRetry: selectedAuth.authDeviceToken != nil) if shouldRetryWithDeviceToken { self.pendingDeviceTokenRetry = true self.deviceTokenRetryBudgetUsed = true self.backoffMs = min(self.backoffMs, 250) - } else if authDeviceToken != nil, + } else if selectedAuth.authDeviceToken != nil, let identity, self.shouldClearStoredDeviceTokenAfterRetry(error) { @@ -512,6 +495,50 @@ public actor GatewayChannelActor { } } + private func selectConnectAuth( + role: String, + includeDeviceIdentity: Bool, + deviceId: String? + ) -> SelectedConnectAuth { + let explicitToken = self.token?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty + let explicitBootstrapToken = + self.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty + let explicitPassword = self.password?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty + let storedToken = + (includeDeviceIdentity && deviceId != nil) + ? DeviceAuthStore.loadToken(deviceId: deviceId!, role: role)?.token + : nil + let shouldUseDeviceRetryToken = + includeDeviceIdentity && self.pendingDeviceTokenRetry && + storedToken != nil && explicitToken != nil && self.isTrustedDeviceRetryEndpoint() + let authToken = + explicitToken ?? + (includeDeviceIdentity && explicitPassword == nil && + (explicitBootstrapToken == nil || storedToken != nil) ? storedToken : nil) + let authBootstrapToken = authToken == nil ? explicitBootstrapToken : nil + let authDeviceToken = shouldUseDeviceRetryToken ? storedToken : nil + let authSource: GatewayAuthSource + if authDeviceToken != nil || (explicitToken == nil && authToken != nil) { + authSource = .deviceToken + } else if authToken != nil { + authSource = .sharedToken + } else if authBootstrapToken != nil { + authSource = .bootstrapToken + } else if explicitPassword != nil { + authSource = .password + } else { + authSource = .none + } + return SelectedConnectAuth( + authToken: authToken, + authBootstrapToken: authBootstrapToken, + authDeviceToken: authDeviceToken, + authPassword: explicitPassword, + signatureToken: authToken ?? authBootstrapToken, + storedToken: storedToken, + authSource: authSource) + } + private func handleConnectResponse( _ res: ResponseFrame, identity: DeviceIdentity?, @@ -522,10 +549,12 @@ public actor GatewayChannelActor { let details = res.error?["details"]?.value as? [String: ProtoAnyCodable] let detailCode = details?["code"]?.value as? String let canRetryWithDeviceToken = details?["canRetryWithDeviceToken"]?.value as? Bool ?? false + let recommendedNextStep = details?["recommendedNextStep"]?.value as? String throw GatewayConnectAuthError( message: msg, - detailCode: detailCode, - canRetryWithDeviceToken: canRetryWithDeviceToken) + detailCodeRaw: detailCode, + canRetryWithDeviceToken: canRetryWithDeviceToken, + recommendedNextStepRaw: recommendedNextStep) } guard let payload = res.payload else { throw NSError( @@ -710,8 +739,7 @@ public actor GatewayChannelActor { if self.shouldPauseReconnectAfterAuthFailure(error) { self.reconnectPausedForAuthFailure = true self.logger.error( - "gateway reconnect paused for non-recoverable auth failure " + - "\(error.localizedDescription, privacy: .public)" + "gateway reconnect paused for non-recoverable auth failure \(error.localizedDescription, privacy: .public)" ) return } @@ -743,7 +771,7 @@ public actor GatewayChannelActor { return false } return authError.canRetryWithDeviceToken || - authError.detailCode == GatewayConnectErrorCodes.authTokenMismatch + authError.detail == .authTokenMismatch } private func shouldPauseReconnectAfterAuthFailure(_ error: Error) -> Bool { @@ -753,7 +781,7 @@ public actor GatewayChannelActor { if authError.isNonRecoverable { return true } - if authError.detailCode == GatewayConnectErrorCodes.authTokenMismatch && + if authError.detail == .authTokenMismatch && self.deviceTokenRetryBudgetUsed && !self.pendingDeviceTokenRetry { return true @@ -765,7 +793,7 @@ public actor GatewayChannelActor { guard let authError = error as? GatewayConnectAuthError else { return false } - return authError.detailCode == GatewayConnectErrorCodes.authDeviceTokenMismatch + return authError.detail == .authDeviceTokenMismatch } private func isTrustedDeviceRetryEndpoint() -> Bool { @@ -867,6 +895,9 @@ public actor GatewayChannelActor { // Wrap low-level URLSession/WebSocket errors with context so UI can surface them. private func wrap(_ error: Error, context: String) -> Error { + if error is GatewayConnectAuthError || error is GatewayResponseError || error is GatewayDecodingError { + return error + } if let urlError = error as? URLError { let desc = urlError.localizedDescription.isEmpty ? "cancelled" : urlError.localizedDescription return NSError( @@ -910,8 +941,8 @@ public actor GatewayChannelActor { return (id: id, data: data) } catch { self.logger.error( - "gateway \(kind) encode failed \(method, privacy: .public) " + - "error=\(error.localizedDescription, privacy: .public)") + "gateway \(kind) encode failed \(method, privacy: .public) error=\(error.localizedDescription, privacy: .public)" + ) throw error } } diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayErrors.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayErrors.swift index 6ca81dec445..7ef7f466476 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayErrors.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayErrors.swift @@ -1,6 +1,114 @@ import OpenClawProtocol import Foundation +public enum GatewayConnectAuthDetailCode: String, Sendable { + case authRequired = "AUTH_REQUIRED" + case authUnauthorized = "AUTH_UNAUTHORIZED" + case authTokenMismatch = "AUTH_TOKEN_MISMATCH" + case authBootstrapTokenInvalid = "AUTH_BOOTSTRAP_TOKEN_INVALID" + case authDeviceTokenMismatch = "AUTH_DEVICE_TOKEN_MISMATCH" + case authTokenMissing = "AUTH_TOKEN_MISSING" + case authTokenNotConfigured = "AUTH_TOKEN_NOT_CONFIGURED" + case authPasswordMissing = "AUTH_PASSWORD_MISSING" + case authPasswordMismatch = "AUTH_PASSWORD_MISMATCH" + case authPasswordNotConfigured = "AUTH_PASSWORD_NOT_CONFIGURED" + case authRateLimited = "AUTH_RATE_LIMITED" + case authTailscaleIdentityMissing = "AUTH_TAILSCALE_IDENTITY_MISSING" + case authTailscaleProxyMissing = "AUTH_TAILSCALE_PROXY_MISSING" + case authTailscaleWhoisFailed = "AUTH_TAILSCALE_WHOIS_FAILED" + case authTailscaleIdentityMismatch = "AUTH_TAILSCALE_IDENTITY_MISMATCH" + case pairingRequired = "PAIRING_REQUIRED" + case controlUiDeviceIdentityRequired = "CONTROL_UI_DEVICE_IDENTITY_REQUIRED" + case deviceIdentityRequired = "DEVICE_IDENTITY_REQUIRED" + case deviceAuthInvalid = "DEVICE_AUTH_INVALID" + case deviceAuthDeviceIdMismatch = "DEVICE_AUTH_DEVICE_ID_MISMATCH" + case deviceAuthSignatureExpired = "DEVICE_AUTH_SIGNATURE_EXPIRED" + case deviceAuthNonceRequired = "DEVICE_AUTH_NONCE_REQUIRED" + case deviceAuthNonceMismatch = "DEVICE_AUTH_NONCE_MISMATCH" + case deviceAuthSignatureInvalid = "DEVICE_AUTH_SIGNATURE_INVALID" + case deviceAuthPublicKeyInvalid = "DEVICE_AUTH_PUBLIC_KEY_INVALID" +} + +public enum GatewayConnectRecoveryNextStep: String, Sendable { + case retryWithDeviceToken = "retry_with_device_token" + case updateAuthConfiguration = "update_auth_configuration" + case updateAuthCredentials = "update_auth_credentials" + case waitThenRetry = "wait_then_retry" + case reviewAuthConfiguration = "review_auth_configuration" +} + +/// Structured websocket connect-auth rejection surfaced before the channel is usable. +public struct GatewayConnectAuthError: LocalizedError, Sendable { + public let message: String + public let detailCodeRaw: String? + public let recommendedNextStepRaw: String? + public let canRetryWithDeviceToken: Bool + + public init( + message: String, + detailCodeRaw: String?, + canRetryWithDeviceToken: Bool, + recommendedNextStepRaw: String? = nil) + { + let trimmedMessage = message.trimmingCharacters(in: .whitespacesAndNewlines) + let trimmedDetailCode = detailCodeRaw?.trimmingCharacters(in: .whitespacesAndNewlines) + let trimmedRecommendedNextStep = + recommendedNextStepRaw?.trimmingCharacters(in: .whitespacesAndNewlines) + self.message = trimmedMessage.isEmpty ? "gateway connect failed" : trimmedMessage + self.detailCodeRaw = trimmedDetailCode?.isEmpty == false ? trimmedDetailCode : nil + self.canRetryWithDeviceToken = canRetryWithDeviceToken + self.recommendedNextStepRaw = + trimmedRecommendedNextStep?.isEmpty == false ? trimmedRecommendedNextStep : nil + } + + public init( + message: String, + detailCode: String?, + canRetryWithDeviceToken: Bool, + recommendedNextStep: String? = nil) + { + self.init( + message: message, + detailCodeRaw: detailCode, + canRetryWithDeviceToken: canRetryWithDeviceToken, + recommendedNextStepRaw: recommendedNextStep) + } + + public var detailCode: String? { self.detailCodeRaw } + + public var recommendedNextStepCode: String? { self.recommendedNextStepRaw } + + public var detail: GatewayConnectAuthDetailCode? { + guard let detailCodeRaw else { return nil } + return GatewayConnectAuthDetailCode(rawValue: detailCodeRaw) + } + + public var recommendedNextStep: GatewayConnectRecoveryNextStep? { + guard let recommendedNextStepRaw else { return nil } + return GatewayConnectRecoveryNextStep(rawValue: recommendedNextStepRaw) + } + + public var errorDescription: String? { self.message } + + public var isNonRecoverable: Bool { + switch self.detail { + case .authTokenMissing, + .authBootstrapTokenInvalid, + .authTokenNotConfigured, + .authPasswordMissing, + .authPasswordMismatch, + .authPasswordNotConfigured, + .authRateLimited, + .pairingRequired, + .controlUiDeviceIdentityRequired, + .deviceIdentityRequired: + return true + default: + return false + } + } +} + /// Structured error surfaced when the gateway responds with `{ ok: false }`. public struct GatewayResponseError: LocalizedError, @unchecked Sendable { public let method: String diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayNodeSession.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayNodeSession.swift index 378ad10e365..945e482bbbf 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayNodeSession.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayNodeSession.swift @@ -64,6 +64,7 @@ public actor GatewayNodeSession { private var channel: GatewayChannelActor? private var activeURL: URL? private var activeToken: String? + private var activeBootstrapToken: String? private var activePassword: String? private var activeConnectOptionsKey: String? private var connectOptions: GatewayConnectOptions? @@ -194,6 +195,7 @@ public actor GatewayNodeSession { public func connect( url: URL, token: String?, + bootstrapToken: String?, password: String?, connectOptions: GatewayConnectOptions, sessionBox: WebSocketSessionBox?, @@ -204,6 +206,7 @@ public actor GatewayNodeSession { let nextOptionsKey = self.connectOptionsKey(connectOptions) let shouldReconnect = self.activeURL != url || self.activeToken != token || + self.activeBootstrapToken != bootstrapToken || self.activePassword != password || self.activeConnectOptionsKey != nextOptionsKey || self.channel == nil @@ -221,6 +224,7 @@ public actor GatewayNodeSession { let channel = GatewayChannelActor( url: url, token: token, + bootstrapToken: bootstrapToken, password: password, session: sessionBox, pushHandler: { [weak self] push in @@ -233,6 +237,7 @@ public actor GatewayNodeSession { self.channel = channel self.activeURL = url self.activeToken = token + self.activeBootstrapToken = bootstrapToken self.activePassword = password self.activeConnectOptionsKey = nextOptionsKey } @@ -257,6 +262,7 @@ public actor GatewayNodeSession { self.channel = nil self.activeURL = nil self.activeToken = nil + self.activeBootstrapToken = nil self.activePassword = nil self.activeConnectOptionsKey = nil self.hasEverConnected = false diff --git a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift index ea85e6c1511..6f97c9bf9f1 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift @@ -515,6 +515,8 @@ public struct PollParams: Codable, Sendable { public struct AgentParams: Codable, Sendable { public let message: String public let agentid: String? + public let provider: String? + public let model: String? public let to: String? public let replyto: String? public let sessionid: String? @@ -538,12 +540,12 @@ public struct AgentParams: Codable, Sendable { public let inputprovenance: [String: AnyCodable]? public let idempotencykey: String public let label: String? - public let spawnedby: String? - public let workspacedir: String? public init( message: String, agentid: String?, + provider: String?, + model: String?, to: String?, replyto: String?, sessionid: String?, @@ -566,12 +568,12 @@ public struct AgentParams: Codable, Sendable { internalevents: [[String: AnyCodable]]?, inputprovenance: [String: AnyCodable]?, idempotencykey: String, - label: String?, - spawnedby: String?, - workspacedir: String?) + label: String?) { self.message = message self.agentid = agentid + self.provider = provider + self.model = model self.to = to self.replyto = replyto self.sessionid = sessionid @@ -595,13 +597,13 @@ public struct AgentParams: Codable, Sendable { self.inputprovenance = inputprovenance self.idempotencykey = idempotencykey self.label = label - self.spawnedby = spawnedby - self.workspacedir = workspacedir } private enum CodingKeys: String, CodingKey { case message case agentid = "agentId" + case provider + case model case to case replyto = "replyTo" case sessionid = "sessionId" @@ -625,8 +627,6 @@ public struct AgentParams: Codable, Sendable { case inputprovenance = "inputProvenance" case idempotencykey = "idempotencyKey" case label - case spawnedby = "spawnedBy" - case workspacedir = "workspaceDir" } } @@ -1114,6 +1114,7 @@ public struct PushTestResult: Codable, Sendable { public let tokensuffix: String public let topic: String public let environment: String + public let transport: String public init( ok: Bool, @@ -1122,7 +1123,8 @@ public struct PushTestResult: Codable, Sendable { reason: String?, tokensuffix: String, topic: String, - environment: String) + environment: String, + transport: String) { self.ok = ok self.status = status @@ -1131,6 +1133,7 @@ public struct PushTestResult: Codable, Sendable { self.tokensuffix = tokensuffix self.topic = topic self.environment = environment + self.transport = transport } private enum CodingKeys: String, CodingKey { @@ -1141,6 +1144,7 @@ public struct PushTestResult: Codable, Sendable { case tokensuffix = "tokenSuffix" case topic case environment + case transport } } @@ -1322,10 +1326,129 @@ public struct SessionsResolveParams: Codable, Sendable { } } +public struct SessionsCreateParams: Codable, Sendable { + public let key: String? + public let agentid: String? + public let label: String? + public let model: String? + public let parentsessionkey: String? + public let task: String? + public let message: String? + + public init( + key: String?, + agentid: String?, + label: String?, + model: String?, + parentsessionkey: String?, + task: String?, + message: String?) + { + self.key = key + self.agentid = agentid + self.label = label + self.model = model + self.parentsessionkey = parentsessionkey + self.task = task + self.message = message + } + + private enum CodingKeys: String, CodingKey { + case key + case agentid = "agentId" + case label + case model + case parentsessionkey = "parentSessionKey" + case task + case message + } +} + +public struct SessionsSendParams: Codable, Sendable { + public let key: String + public let message: String + public let thinking: String? + public let attachments: [AnyCodable]? + public let timeoutms: Int? + public let idempotencykey: String? + + public init( + key: String, + message: String, + thinking: String?, + attachments: [AnyCodable]?, + timeoutms: Int?, + idempotencykey: String?) + { + self.key = key + self.message = message + self.thinking = thinking + self.attachments = attachments + self.timeoutms = timeoutms + self.idempotencykey = idempotencykey + } + + private enum CodingKeys: String, CodingKey { + case key + case message + case thinking + case attachments + case timeoutms = "timeoutMs" + case idempotencykey = "idempotencyKey" + } +} + +public struct SessionsMessagesSubscribeParams: Codable, Sendable { + public let key: String + + public init( + key: String) + { + self.key = key + } + + private enum CodingKeys: String, CodingKey { + case key + } +} + +public struct SessionsMessagesUnsubscribeParams: Codable, Sendable { + public let key: String + + public init( + key: String) + { + self.key = key + } + + private enum CodingKeys: String, CodingKey { + case key + } +} + +public struct SessionsAbortParams: Codable, Sendable { + public let key: String + public let runid: String? + + public init( + key: String, + runid: String?) + { + self.key = key + self.runid = runid + } + + private enum CodingKeys: String, CodingKey { + case key + case runid = "runId" + } +} + public struct SessionsPatchParams: Codable, Sendable { public let key: String public let label: AnyCodable? public let thinkinglevel: AnyCodable? + public let fastmode: AnyCodable? public let verboselevel: AnyCodable? public let reasoninglevel: AnyCodable? public let responseusage: AnyCodable? @@ -1336,6 +1459,7 @@ public struct SessionsPatchParams: Codable, Sendable { public let execnode: AnyCodable? public let model: AnyCodable? public let spawnedby: AnyCodable? + public let spawnedworkspacedir: AnyCodable? public let spawndepth: AnyCodable? public let subagentrole: AnyCodable? public let subagentcontrolscope: AnyCodable? @@ -1346,6 +1470,7 @@ public struct SessionsPatchParams: Codable, Sendable { key: String, label: AnyCodable?, thinkinglevel: AnyCodable?, + fastmode: AnyCodable?, verboselevel: AnyCodable?, reasoninglevel: AnyCodable?, responseusage: AnyCodable?, @@ -1356,6 +1481,7 @@ public struct SessionsPatchParams: Codable, Sendable { execnode: AnyCodable?, model: AnyCodable?, spawnedby: AnyCodable?, + spawnedworkspacedir: AnyCodable?, spawndepth: AnyCodable?, subagentrole: AnyCodable?, subagentcontrolscope: AnyCodable?, @@ -1365,6 +1491,7 @@ public struct SessionsPatchParams: Codable, Sendable { self.key = key self.label = label self.thinkinglevel = thinkinglevel + self.fastmode = fastmode self.verboselevel = verboselevel self.reasoninglevel = reasoninglevel self.responseusage = responseusage @@ -1375,6 +1502,7 @@ public struct SessionsPatchParams: Codable, Sendable { self.execnode = execnode self.model = model self.spawnedby = spawnedby + self.spawnedworkspacedir = spawnedworkspacedir self.spawndepth = spawndepth self.subagentrole = subagentrole self.subagentcontrolscope = subagentcontrolscope @@ -1386,6 +1514,7 @@ public struct SessionsPatchParams: Codable, Sendable { case key case label case thinkinglevel = "thinkingLevel" + case fastmode = "fastMode" case verboselevel = "verboseLevel" case reasoninglevel = "reasoningLevel" case responseusage = "responseUsage" @@ -1396,6 +1525,7 @@ public struct SessionsPatchParams: Codable, Sendable { case execnode = "execNode" case model case spawnedby = "spawnedBy" + case spawnedworkspacedir = "spawnedWorkspaceDir" case spawndepth = "spawnDepth" case subagentrole = "subagentRole" case subagentcontrolscope = "subagentControlScope" diff --git a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift index abfd267a66c..6d1fa88e569 100644 --- a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift +++ b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift @@ -83,6 +83,7 @@ private func makeViewModel( historyResponses: [OpenClawChatHistoryPayload], sessionsResponses: [OpenClawChatSessionsListResponse] = [], modelResponses: [[OpenClawChatModelChoice]] = [], + resetSessionHook: (@Sendable (String) async throws -> Void)? = nil, setSessionModelHook: (@Sendable (String?) async throws -> Void)? = nil, setSessionThinkingHook: (@Sendable (String) async throws -> Void)? = nil, initialThinkingLevel: String? = nil, @@ -93,6 +94,7 @@ private func makeViewModel( historyResponses: historyResponses, sessionsResponses: sessionsResponses, modelResponses: modelResponses, + resetSessionHook: resetSessionHook, setSessionModelHook: setSessionModelHook, setSessionThinkingHook: setSessionThinkingHook) let vm = await MainActor.run { @@ -199,6 +201,7 @@ private actor TestChatTransportState { var historyCallCount: Int = 0 var sessionsCallCount: Int = 0 var modelsCallCount: Int = 0 + var resetSessionKeys: [String] = [] var sentRunIds: [String] = [] var sentThinkingLevels: [String] = [] var abortedRunIds: [String] = [] @@ -211,6 +214,7 @@ private final class TestChatTransport: @unchecked Sendable, OpenClawChatTranspor private let historyResponses: [OpenClawChatHistoryPayload] private let sessionsResponses: [OpenClawChatSessionsListResponse] private let modelResponses: [[OpenClawChatModelChoice]] + private let resetSessionHook: (@Sendable (String) async throws -> Void)? private let setSessionModelHook: (@Sendable (String?) async throws -> Void)? private let setSessionThinkingHook: (@Sendable (String) async throws -> Void)? @@ -221,12 +225,14 @@ private final class TestChatTransport: @unchecked Sendable, OpenClawChatTranspor historyResponses: [OpenClawChatHistoryPayload], sessionsResponses: [OpenClawChatSessionsListResponse] = [], modelResponses: [[OpenClawChatModelChoice]] = [], + resetSessionHook: (@Sendable (String) async throws -> Void)? = nil, setSessionModelHook: (@Sendable (String?) async throws -> Void)? = nil, setSessionThinkingHook: (@Sendable (String) async throws -> Void)? = nil) { self.historyResponses = historyResponses self.sessionsResponses = sessionsResponses self.modelResponses = modelResponses + self.resetSessionHook = resetSessionHook self.setSessionModelHook = setSessionModelHook self.setSessionThinkingHook = setSessionThinkingHook var cont: AsyncStream.Continuation! @@ -301,6 +307,13 @@ private final class TestChatTransport: @unchecked Sendable, OpenClawChatTranspor } } + func resetSession(sessionKey: String) async throws { + await self.state.resetSessionKeysAppend(sessionKey) + if let resetSessionHook = self.resetSessionHook { + try await resetSessionHook(sessionKey) + } + } + func setSessionThinking(sessionKey _: String, thinkingLevel: String) async throws { await self.state.patchedThinkingLevelsAppend(thinkingLevel) if let setSessionThinkingHook = self.setSessionThinkingHook { @@ -336,6 +349,10 @@ private final class TestChatTransport: @unchecked Sendable, OpenClawChatTranspor func patchedThinkingLevels() async -> [String] { await self.state.patchedThinkingLevels } + + func resetSessionKeys() async -> [String] { + await self.state.resetSessionKeys + } } extension TestChatTransportState { @@ -370,6 +387,10 @@ extension TestChatTransportState { fileprivate func patchedThinkingLevelsAppend(_ v: String) { self.patchedThinkingLevels.append(v) } + + fileprivate func resetSessionKeysAppend(_ v: String) { + self.resetSessionKeys.append(v) + } } @Suite struct ChatViewModelTests { @@ -592,6 +613,151 @@ extension TestChatTransportState { #expect(keys == ["main", "custom"]) } + @Test func sessionChoicesUseResolvedMainSessionKeyInsteadOfLiteralMain() async throws { + let now = Date().timeIntervalSince1970 * 1000 + let recent = now - (30 * 60 * 1000) + let recentOlder = now - (90 * 60 * 1000) + let history = historyPayload(sessionKey: "Luke’s MacBook Pro", sessionId: "sess-main") + let sessions = OpenClawChatSessionsListResponse( + ts: now, + path: nil, + count: 2, + defaults: OpenClawChatSessionsDefaults( + model: nil, + contextTokens: nil, + mainSessionKey: "Luke’s MacBook Pro"), + sessions: [ + OpenClawChatSessionEntry( + key: "Luke’s MacBook Pro", + kind: nil, + displayName: "Luke’s MacBook Pro", + surface: nil, + subject: nil, + room: nil, + space: nil, + updatedAt: recent, + sessionId: nil, + systemSent: nil, + abortedLastRun: nil, + thinkingLevel: nil, + verboseLevel: nil, + inputTokens: nil, + outputTokens: nil, + totalTokens: nil, + modelProvider: nil, + model: nil, + contextTokens: nil), + sessionEntry(key: "recent-1", updatedAt: recentOlder), + ]) + + let (_, vm) = await makeViewModel( + sessionKey: "Luke’s MacBook Pro", + historyResponses: [history], + sessionsResponses: [sessions]) + await MainActor.run { vm.load() } + try await waitUntil("sessions loaded") { await MainActor.run { !vm.sessions.isEmpty } } + + let keys = await MainActor.run { vm.sessionChoices.map(\.key) } + #expect(keys == ["Luke’s MacBook Pro", "recent-1"]) + } + + @Test func sessionChoicesHideInternalOnboardingSession() async throws { + let now = Date().timeIntervalSince1970 * 1000 + let recent = now - (2 * 60 * 1000) + let recentOlder = now - (5 * 60 * 1000) + let history = historyPayload(sessionKey: "agent:main:main", sessionId: "sess-main") + let sessions = OpenClawChatSessionsListResponse( + ts: now, + path: nil, + count: 2, + defaults: OpenClawChatSessionsDefaults( + model: nil, + contextTokens: nil, + mainSessionKey: "agent:main:main"), + sessions: [ + OpenClawChatSessionEntry( + key: "agent:main:onboarding", + kind: nil, + displayName: "Luke’s MacBook Pro", + surface: nil, + subject: nil, + room: nil, + space: nil, + updatedAt: recent, + sessionId: nil, + systemSent: nil, + abortedLastRun: nil, + thinkingLevel: nil, + verboseLevel: nil, + inputTokens: nil, + outputTokens: nil, + totalTokens: nil, + modelProvider: nil, + model: nil, + contextTokens: nil), + OpenClawChatSessionEntry( + key: "agent:main:main", + kind: nil, + displayName: "Luke’s MacBook Pro", + surface: nil, + subject: nil, + room: nil, + space: nil, + updatedAt: recentOlder, + sessionId: nil, + systemSent: nil, + abortedLastRun: nil, + thinkingLevel: nil, + verboseLevel: nil, + inputTokens: nil, + outputTokens: nil, + totalTokens: nil, + modelProvider: nil, + model: nil, + contextTokens: nil), + ]) + + let (_, vm) = await makeViewModel( + sessionKey: "agent:main:main", + historyResponses: [history], + sessionsResponses: [sessions]) + await MainActor.run { vm.load() } + try await waitUntil("sessions loaded") { await MainActor.run { !vm.sessions.isEmpty } } + + let keys = await MainActor.run { vm.sessionChoices.map(\.key) } + #expect(keys == ["agent:main:main"]) + } + + @Test func resetTriggerResetsSessionAndReloadsHistory() async throws { + let before = historyPayload( + messages: [ + chatTextMessage(role: "assistant", text: "before reset", timestamp: 1), + ]) + let after = historyPayload( + messages: [ + chatTextMessage(role: "assistant", text: "after reset", timestamp: 2), + ]) + + let (transport, vm) = await makeViewModel(historyResponses: [before, after]) + try await loadAndWaitBootstrap(vm: vm) + try await waitUntil("initial history loaded") { + await MainActor.run { vm.messages.first?.content.first?.text == "before reset" } + } + + await MainActor.run { + vm.input = "/new" + vm.send() + } + + try await waitUntil("reset called") { + await transport.resetSessionKeys() == ["main"] + } + try await waitUntil("history reloaded") { + await MainActor.run { vm.messages.first?.content.first?.text == "after reset" } + } + #expect(await transport.lastSentRunId() == nil) + } + @Test func bootstrapsModelSelectionFromSessionAndDefaults() async throws { let now = Date().timeIntervalSince1970 * 1000 let history = historyPayload() @@ -758,7 +924,8 @@ extension TestChatTransportState { } #expect(await MainActor.run { vm.modelSelectionID } == "openai/gpt-5.4-pro") - #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.model } == "openai/gpt-5.4-pro") + #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.model } == "gpt-5.4-pro") + #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.modelProvider } == "openai") } @Test func sendWaitsForInFlightModelPatchToFinish() async throws { @@ -852,11 +1019,15 @@ extension TestChatTransportState { } try await waitUntil("older model completion wins after latest failure") { - await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.model == "openai/gpt-5.4" } + await MainActor.run { + vm.sessions.first(where: { $0.key == "main" })?.model == "gpt-5.4" && + vm.sessions.first(where: { $0.key == "main" })?.modelProvider == "openai" + } } #expect(await MainActor.run { vm.modelSelectionID } == "openai/gpt-5.4") - #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.model } == "openai/gpt-5.4") + #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.model } == "gpt-5.4") + #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.modelProvider } == "openai") #expect(await transport.patchedModels() == ["openai/gpt-5.4", "openai/gpt-5.4-pro"]) } @@ -1012,12 +1183,17 @@ extension TestChatTransportState { } try await waitUntil("late model completion updates only the original session") { - await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.model == "openai/gpt-5.4" } + await MainActor.run { + vm.sessions.first(where: { $0.key == "main" })?.model == "gpt-5.4" && + vm.sessions.first(where: { $0.key == "main" })?.modelProvider == "openai" + } } #expect(await MainActor.run { vm.modelSelectionID } == "openai/gpt-5.4") - #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.model } == "openai/gpt-5.4") + #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.model } == "gpt-5.4") + #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.modelProvider } == "openai") #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "other" })?.model } == "openai/gpt-5.4-pro") + #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "other" })?.modelProvider } == nil) #expect(await transport.patchedModels() == ["openai/gpt-5.4", "openai/gpt-5.4-pro"]) } diff --git a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/DeepLinksSecurityTests.swift b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/DeepLinksSecurityTests.swift index 8bbf4f8a650..79613b310ff 100644 --- a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/DeepLinksSecurityTests.swift +++ b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/DeepLinksSecurityTests.swift @@ -20,11 +20,17 @@ import Testing string: "openclaw://gateway?host=127.0.0.1&port=18789&tls=0&token=abc")! #expect( DeepLinkParser.parse(url) == .gateway( - .init(host: "127.0.0.1", port: 18789, tls: false, token: "abc", password: nil))) + .init( + host: "127.0.0.1", + port: 18789, + tls: false, + bootstrapToken: nil, + token: "abc", + password: nil))) } @Test func setupCodeRejectsInsecureNonLoopbackWs() { - let payload = #"{"url":"ws://attacker.example:18789","token":"tok"}"# + let payload = #"{"url":"ws://attacker.example:18789","bootstrapToken":"tok"}"# let encoded = Data(payload.utf8) .base64EncodedString() .replacingOccurrences(of: "+", with: "-") @@ -34,7 +40,7 @@ import Testing } @Test func setupCodeRejectsInsecurePrefixBypassHost() { - let payload = #"{"url":"ws://127.attacker.example:18789","token":"tok"}"# + let payload = #"{"url":"ws://127.attacker.example:18789","bootstrapToken":"tok"}"# let encoded = Data(payload.utf8) .base64EncodedString() .replacingOccurrences(of: "+", with: "-") @@ -44,7 +50,7 @@ import Testing } @Test func setupCodeAllowsLoopbackWs() { - let payload = #"{"url":"ws://127.0.0.1:18789","token":"tok"}"# + let payload = #"{"url":"ws://127.0.0.1:18789","bootstrapToken":"tok"}"# let encoded = Data(payload.utf8) .base64EncodedString() .replacingOccurrences(of: "+", with: "-") @@ -55,7 +61,8 @@ import Testing host: "127.0.0.1", port: 18789, tls: false, - token: "tok", + bootstrapToken: "tok", + token: nil, password: nil)) } } diff --git a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayErrorsTests.swift b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayErrorsTests.swift new file mode 100644 index 00000000000..92d3e1292de --- /dev/null +++ b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayErrorsTests.swift @@ -0,0 +1,14 @@ +import OpenClawKit +import Testing + +@Suite struct GatewayErrorsTests { + @Test func bootstrapTokenInvalidIsNonRecoverable() { + let error = GatewayConnectAuthError( + message: "setup code expired", + detailCode: GatewayConnectAuthDetailCode.authBootstrapTokenInvalid.rawValue, + canRetryWithDeviceToken: false) + + #expect(error.isNonRecoverable) + #expect(error.detail == .authBootstrapTokenInvalid) + } +} diff --git a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayNodeSessionTests.swift b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayNodeSessionTests.swift index a48015e1100..183fc385d8c 100644 --- a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayNodeSessionTests.swift +++ b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayNodeSessionTests.swift @@ -266,6 +266,7 @@ struct GatewayNodeSessionTests { try await gateway.connect( url: URL(string: "ws://example.invalid")!, token: nil, + bootstrapToken: nil, password: nil, connectOptions: options, sessionBox: WebSocketSessionBox(session: session), diff --git a/assets/chrome-extension/README.md b/assets/chrome-extension/README.md deleted file mode 100644 index 4ee072c1f2b..00000000000 --- a/assets/chrome-extension/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# OpenClaw Chrome Extension (Browser Relay) - -Purpose: attach OpenClaw to an existing Chrome tab so the Gateway can automate it (via the local CDP relay server). - -## Dev / load unpacked - -1. Build/run OpenClaw Gateway with browser control enabled. -2. Ensure the relay server is reachable at `http://127.0.0.1:18792/` (default). -3. Install the extension to a stable path: - - ```bash - openclaw browser extension install - openclaw browser extension path - ``` - -4. Chrome → `chrome://extensions` → enable “Developer mode”. -5. “Load unpacked” → select the path printed above. -6. Pin the extension. Click the icon on a tab to attach/detach. - -## Options - -- `Relay port`: defaults to `18792`. -- `Gateway token`: required. Set this to `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`). diff --git a/assets/chrome-extension/background-utils.js b/assets/chrome-extension/background-utils.js deleted file mode 100644 index 82d43359c0a..00000000000 --- a/assets/chrome-extension/background-utils.js +++ /dev/null @@ -1,64 +0,0 @@ -export function reconnectDelayMs( - attempt, - opts = { baseMs: 1000, maxMs: 30000, jitterMs: 1000, random: Math.random }, -) { - const baseMs = Number.isFinite(opts.baseMs) ? opts.baseMs : 1000; - const maxMs = Number.isFinite(opts.maxMs) ? opts.maxMs : 30000; - const jitterMs = Number.isFinite(opts.jitterMs) ? opts.jitterMs : 1000; - const random = typeof opts.random === "function" ? opts.random : Math.random; - const safeAttempt = Math.max(0, Number.isFinite(attempt) ? attempt : 0); - const backoff = Math.min(baseMs * 2 ** safeAttempt, maxMs); - return backoff + Math.max(0, jitterMs) * random(); -} - -export async function deriveRelayToken(gatewayToken, port) { - const enc = new TextEncoder(); - const key = await crypto.subtle.importKey( - "raw", - enc.encode(gatewayToken), - { name: "HMAC", hash: "SHA-256" }, - false, - ["sign"], - ); - const sig = await crypto.subtle.sign( - "HMAC", - key, - enc.encode(`openclaw-extension-relay-v1:${port}`), - ); - return [...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, "0")).join(""); -} - -export async function buildRelayWsUrl(port, gatewayToken) { - const token = String(gatewayToken || "").trim(); - if (!token) { - throw new Error( - "Missing gatewayToken in extension settings (chrome.storage.local.gatewayToken)", - ); - } - const relayToken = await deriveRelayToken(token, port); - return `ws://127.0.0.1:${port}/extension?token=${encodeURIComponent(relayToken)}`; -} - -export function isRetryableReconnectError(err) { - const message = err instanceof Error ? err.message : String(err || ""); - if (message.includes("Missing gatewayToken")) { - return false; - } - return true; -} - -export function isMissingTabError(err) { - const message = (err instanceof Error ? err.message : String(err || "")).toLowerCase(); - return ( - message.includes("no tab with id") || - message.includes("no tab with given id") || - message.includes("tab not found") - ); -} - -export function isLastRemainingTab(allTabs, tabIdToClose) { - if (!Array.isArray(allTabs)) { - return true; - } - return allTabs.filter((tab) => tab && tab.id !== tabIdToClose).length === 0; -} diff --git a/assets/chrome-extension/background.js b/assets/chrome-extension/background.js deleted file mode 100644 index 9031a156489..00000000000 --- a/assets/chrome-extension/background.js +++ /dev/null @@ -1,1025 +0,0 @@ -import { - buildRelayWsUrl, - isLastRemainingTab, - isMissingTabError, - isRetryableReconnectError, - reconnectDelayMs, -} from './background-utils.js' - -const DEFAULT_PORT = 18792 - -const BADGE = { - on: { text: 'ON', color: '#FF5A36' }, - off: { text: '', color: '#000000' }, - connecting: { text: '…', color: '#F59E0B' }, - error: { text: '!', color: '#B91C1C' }, -} - -/** @type {WebSocket|null} */ -let relayWs = null -/** @type {Promise|null} */ -let relayConnectPromise = null -let relayGatewayToken = '' -/** @type {string|null} */ -let relayConnectRequestId = null - -let nextSession = 1 - -/** @type {Map} */ -const tabs = new Map() -/** @type {Map} */ -const tabBySession = new Map() -/** @type {Map} */ -const childSessionToTab = new Map() - -/** @type {Mapvoid, reject:(e:Error)=>void}>} */ -const pending = new Map() - -// Per-tab operation locks prevent double-attach races. -/** @type {Set} */ -const tabOperationLocks = new Set() - -// Tabs currently in a detach/re-attach cycle after navigation. -/** @type {Set} */ -const reattachPending = new Set() - -// Reconnect state for exponential backoff. -let reconnectAttempt = 0 -let reconnectTimer = null - -const TAB_VALIDATION_ATTEMPTS = 2 -const TAB_VALIDATION_RETRY_DELAY_MS = 1000 - -function nowStack() { - try { - return new Error().stack || '' - } catch { - return '' - } -} - -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)) -} - -async function validateAttachedTab(tabId) { - try { - await chrome.tabs.get(tabId) - } catch { - return false - } - - for (let attempt = 0; attempt < TAB_VALIDATION_ATTEMPTS; attempt++) { - try { - await chrome.debugger.sendCommand({ tabId }, 'Runtime.evaluate', { - expression: '1', - returnByValue: true, - }) - return true - } catch (err) { - if (isMissingTabError(err)) { - return false - } - if (attempt < TAB_VALIDATION_ATTEMPTS - 1) { - await sleep(TAB_VALIDATION_RETRY_DELAY_MS) - } - } - } - - return false -} - -async function getRelayPort() { - const stored = await chrome.storage.local.get(['relayPort']) - const raw = stored.relayPort - const n = Number.parseInt(String(raw || ''), 10) - if (!Number.isFinite(n) || n <= 0 || n > 65535) return DEFAULT_PORT - return n -} - -async function getGatewayToken() { - const stored = await chrome.storage.local.get(['gatewayToken']) - const token = String(stored.gatewayToken || '').trim() - return token || '' -} - -function setBadge(tabId, kind) { - const cfg = BADGE[kind] - void chrome.action.setBadgeText({ tabId, text: cfg.text }) - void chrome.action.setBadgeBackgroundColor({ tabId, color: cfg.color }) - void chrome.action.setBadgeTextColor({ tabId, color: '#FFFFFF' }).catch(() => {}) -} - -// Persist attached tab state to survive MV3 service worker restarts. -async function persistState() { - try { - const tabEntries = [] - for (const [tabId, tab] of tabs.entries()) { - if (tab.state === 'connected' && tab.sessionId && tab.targetId) { - tabEntries.push({ tabId, sessionId: tab.sessionId, targetId: tab.targetId, attachOrder: tab.attachOrder }) - } - } - await chrome.storage.session.set({ - persistedTabs: tabEntries, - nextSession, - }) - } catch { - // chrome.storage.session may not be available in all contexts. - } -} - -// Rehydrate tab state on service worker startup. Fast path — just restores -// maps and badges. Relay reconnect happens separately in background. -async function rehydrateState() { - try { - const stored = await chrome.storage.session.get(['persistedTabs', 'nextSession']) - if (stored.nextSession) { - nextSession = Math.max(nextSession, stored.nextSession) - } - const entries = stored.persistedTabs || [] - // Phase 1: optimistically restore state and badges. - for (const entry of entries) { - tabs.set(entry.tabId, { - state: 'connected', - sessionId: entry.sessionId, - targetId: entry.targetId, - attachOrder: entry.attachOrder, - }) - tabBySession.set(entry.sessionId, entry.tabId) - setBadge(entry.tabId, 'on') - } - // Retry once so transient busy/navigation states do not permanently drop - // a still-attached tab after a service worker restart. - for (const entry of entries) { - const valid = await validateAttachedTab(entry.tabId) - if (!valid) { - tabs.delete(entry.tabId) - tabBySession.delete(entry.sessionId) - setBadge(entry.tabId, 'off') - } - } - } catch { - // Ignore rehydration errors. - } -} - -async function ensureRelayConnection() { - if (relayWs && relayWs.readyState === WebSocket.OPEN) return - if (relayConnectPromise) return await relayConnectPromise - - relayConnectPromise = (async () => { - const port = await getRelayPort() - const gatewayToken = await getGatewayToken() - const httpBase = `http://127.0.0.1:${port}` - const wsUrl = await buildRelayWsUrl(port, gatewayToken) - - // Fast preflight: is the relay server up? - try { - await fetch(`${httpBase}/`, { method: 'HEAD', signal: AbortSignal.timeout(2000) }) - } catch (err) { - throw new Error(`Relay server not reachable at ${httpBase} (${String(err)})`) - } - - const ws = new WebSocket(wsUrl) - relayWs = ws - relayGatewayToken = gatewayToken - // Bind message handler before open so an immediate first frame (for example - // gateway connect.challenge) cannot be missed. - ws.onmessage = (event) => { - if (ws !== relayWs) return - void whenReady(() => onRelayMessage(String(event.data || ''))) - } - - await new Promise((resolve, reject) => { - const t = setTimeout(() => reject(new Error('WebSocket connect timeout')), 5000) - ws.onopen = () => { - clearTimeout(t) - resolve() - } - ws.onerror = () => { - clearTimeout(t) - reject(new Error('WebSocket connect failed')) - } - ws.onclose = (ev) => { - clearTimeout(t) - reject(new Error(`WebSocket closed (${ev.code} ${ev.reason || 'no reason'})`)) - } - }) - - // Bind permanent handlers. Guard against stale socket: if this WS was - // replaced before its close fires, the handler is a no-op. - ws.onclose = () => { - if (ws !== relayWs) return - onRelayClosed('closed') - } - ws.onerror = () => { - if (ws !== relayWs) return - onRelayClosed('error') - } - })() - - try { - await relayConnectPromise - reconnectAttempt = 0 - } finally { - relayConnectPromise = null - } -} - -// Relay closed — update badges, reject pending requests, auto-reconnect. -// Debugger sessions are kept alive so they survive transient WS drops. -function onRelayClosed(reason) { - relayWs = null - relayGatewayToken = '' - relayConnectRequestId = null - - for (const [id, p] of pending.entries()) { - pending.delete(id) - p.reject(new Error(`Relay disconnected (${reason})`)) - } - - reattachPending.clear() - - for (const [tabId, tab] of tabs.entries()) { - if (tab.state === 'connected') { - setBadge(tabId, 'connecting') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay: relay reconnecting…', - }) - } - } - - scheduleReconnect() -} - -function scheduleReconnect() { - if (reconnectTimer) { - clearTimeout(reconnectTimer) - reconnectTimer = null - } - - const delay = reconnectDelayMs(reconnectAttempt) - reconnectAttempt++ - - console.log(`Scheduling reconnect attempt ${reconnectAttempt} in ${Math.round(delay)}ms`) - - reconnectTimer = setTimeout(async () => { - reconnectTimer = null - try { - await ensureRelayConnection() - reconnectAttempt = 0 - console.log('Reconnected successfully') - await reannounceAttachedTabs() - } catch (err) { - const message = err instanceof Error ? err.message : String(err) - console.warn(`Reconnect attempt ${reconnectAttempt} failed: ${message}`) - if (!isRetryableReconnectError(err)) { - return - } - scheduleReconnect() - } - }, delay) -} - -function cancelReconnect() { - if (reconnectTimer) { - clearTimeout(reconnectTimer) - reconnectTimer = null - } - reconnectAttempt = 0 -} - -// Re-announce all attached tabs to the relay after reconnect. -async function reannounceAttachedTabs() { - for (const [tabId, tab] of tabs.entries()) { - if (tab.state !== 'connected' || !tab.sessionId || !tab.targetId) continue - - // Retry once here as well; reconnect races can briefly make an otherwise - // healthy tab look unavailable. - const valid = await validateAttachedTab(tabId) - if (!valid) { - tabs.delete(tabId) - if (tab.sessionId) tabBySession.delete(tab.sessionId) - setBadge(tabId, 'off') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay (click to attach/detach)', - }) - continue - } - - // Send fresh attach event to relay. - // Split into two try-catch blocks so debugger failures and relay send - // failures are handled independently. Previously, a relay send failure - // would fall into the outer catch and set the badge to 'on' even though - // the relay had no record of the tab — causing every subsequent browser - // tool call to fail with "no tab connected" until the next reconnect cycle. - let targetInfo - try { - const info = /** @type {any} */ ( - await chrome.debugger.sendCommand({ tabId }, 'Target.getTargetInfo') - ) - targetInfo = info?.targetInfo - } catch { - // Target.getTargetInfo failed. Preserve at least targetId from - // cached tab state so relay receives a stable identifier. - targetInfo = tab.targetId ? { targetId: tab.targetId } : undefined - } - - try { - sendToRelay({ - method: 'forwardCDPEvent', - params: { - method: 'Target.attachedToTarget', - params: { - sessionId: tab.sessionId, - targetInfo: { ...targetInfo, attached: true }, - waitingForDebugger: false, - }, - }, - }) - - setBadge(tabId, 'on') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay: attached (click to detach)', - }) - } catch { - // Relay send failed (e.g. WS closed in the gap between ensureRelayConnection - // resolving and this loop executing). The tab is still valid — leave badge - // as 'connecting' so the reconnect/keepalive cycle will retry rather than - // showing a false-positive 'on' that hides the broken state from the user. - setBadge(tabId, 'connecting') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay: relay reconnecting…', - }) - } - } - - await persistState() -} - -function sendToRelay(payload) { - const ws = relayWs - if (!ws || ws.readyState !== WebSocket.OPEN) { - throw new Error('Relay not connected') - } - ws.send(JSON.stringify(payload)) -} - -function ensureGatewayHandshakeStarted(payload) { - if (relayConnectRequestId) return - const nonce = typeof payload?.nonce === 'string' ? payload.nonce.trim() : '' - relayConnectRequestId = `ext-connect-${Date.now()}-${Math.random().toString(16).slice(2, 8)}` - sendToRelay({ - type: 'req', - id: relayConnectRequestId, - method: 'connect', - params: { - minProtocol: 3, - maxProtocol: 3, - client: { - id: 'chrome-relay-extension', - version: '1.0.0', - platform: 'chrome-extension', - mode: 'webchat', - }, - role: 'operator', - scopes: ['operator.read', 'operator.write'], - caps: [], - commands: [], - nonce: nonce || undefined, - auth: relayGatewayToken ? { token: relayGatewayToken } : undefined, - }, - }) -} - -async function maybeOpenHelpOnce() { - try { - const stored = await chrome.storage.local.get(['helpOnErrorShown']) - if (stored.helpOnErrorShown === true) return - await chrome.storage.local.set({ helpOnErrorShown: true }) - await chrome.runtime.openOptionsPage() - } catch { - // ignore - } -} - -function requestFromRelay(command) { - const id = command.id - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - pending.delete(id) - reject(new Error('Relay request timeout (30s)')) - }, 30000) - pending.set(id, { - resolve: (v) => { clearTimeout(timer); resolve(v) }, - reject: (e) => { clearTimeout(timer); reject(e) }, - }) - try { - sendToRelay(command) - } catch (err) { - clearTimeout(timer) - pending.delete(id) - reject(err instanceof Error ? err : new Error(String(err))) - } - }) -} - -async function onRelayMessage(text) { - /** @type {any} */ - let msg - try { - msg = JSON.parse(text) - } catch { - return - } - - if (msg && msg.type === 'event' && msg.event === 'connect.challenge') { - try { - ensureGatewayHandshakeStarted(msg.payload) - } catch (err) { - console.warn('gateway connect handshake start failed', err instanceof Error ? err.message : String(err)) - relayConnectRequestId = null - const ws = relayWs - if (ws && ws.readyState === WebSocket.OPEN) { - ws.close(1008, 'gateway connect failed') - } - } - return - } - - if (msg && msg.type === 'res' && relayConnectRequestId && msg.id === relayConnectRequestId) { - relayConnectRequestId = null - if (!msg.ok) { - const detail = msg?.error?.message || msg?.error || 'gateway connect failed' - console.warn('gateway connect handshake rejected', String(detail)) - const ws = relayWs - if (ws && ws.readyState === WebSocket.OPEN) { - ws.close(1008, 'gateway connect failed') - } - } - return - } - - if (msg && msg.method === 'ping') { - try { - sendToRelay({ method: 'pong' }) - } catch { - // ignore - } - return - } - - if (msg && typeof msg.id === 'number' && (msg.result !== undefined || msg.error !== undefined)) { - const p = pending.get(msg.id) - if (!p) return - pending.delete(msg.id) - if (msg.error) p.reject(new Error(String(msg.error))) - else p.resolve(msg.result) - return - } - - if (msg && typeof msg.id === 'number' && msg.method === 'forwardCDPCommand') { - try { - const result = await handleForwardCdpCommand(msg) - sendToRelay({ id: msg.id, result }) - } catch (err) { - sendToRelay({ id: msg.id, error: err instanceof Error ? err.message : String(err) }) - } - } -} - -function getTabBySessionId(sessionId) { - const direct = tabBySession.get(sessionId) - if (direct) return { tabId: direct, kind: 'main' } - const child = childSessionToTab.get(sessionId) - if (child) return { tabId: child, kind: 'child' } - return null -} - -function getTabByTargetId(targetId) { - for (const [tabId, tab] of tabs.entries()) { - if (tab.targetId === targetId) return tabId - } - return null -} - -async function attachTab(tabId, opts = {}) { - const debuggee = { tabId } - await chrome.debugger.attach(debuggee, '1.3') - await chrome.debugger.sendCommand(debuggee, 'Page.enable').catch(() => {}) - - const info = /** @type {any} */ (await chrome.debugger.sendCommand(debuggee, 'Target.getTargetInfo')) - const targetInfo = info?.targetInfo - const targetId = String(targetInfo?.targetId || '').trim() - if (!targetId) { - throw new Error('Target.getTargetInfo returned no targetId') - } - - const sid = nextSession++ - const sessionId = `cb-tab-${sid}` - const attachOrder = sid - - tabs.set(tabId, { state: 'connected', sessionId, targetId, attachOrder }) - tabBySession.set(sessionId, tabId) - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay: attached (click to detach)', - }) - - if (!opts.skipAttachedEvent) { - sendToRelay({ - method: 'forwardCDPEvent', - params: { - method: 'Target.attachedToTarget', - params: { - sessionId, - targetInfo: { ...targetInfo, attached: true }, - waitingForDebugger: false, - }, - }, - }) - } - - setBadge(tabId, 'on') - await persistState() - - return { sessionId, targetId } -} - -async function detachTab(tabId, reason) { - const tab = tabs.get(tabId) - - // Send detach events for child sessions first. - for (const [childSessionId, parentTabId] of childSessionToTab.entries()) { - if (parentTabId === tabId) { - try { - sendToRelay({ - method: 'forwardCDPEvent', - params: { - method: 'Target.detachedFromTarget', - params: { sessionId: childSessionId, reason: 'parent_detached' }, - }, - }) - } catch { - // Relay may be down. - } - childSessionToTab.delete(childSessionId) - } - } - - // Send detach event for main session. - if (tab?.sessionId && tab?.targetId) { - try { - sendToRelay({ - method: 'forwardCDPEvent', - params: { - method: 'Target.detachedFromTarget', - params: { sessionId: tab.sessionId, targetId: tab.targetId, reason }, - }, - }) - } catch { - // Relay may be down. - } - } - - if (tab?.sessionId) tabBySession.delete(tab.sessionId) - tabs.delete(tabId) - - try { - await chrome.debugger.detach({ tabId }) - } catch { - // May already be detached. - } - - setBadge(tabId, 'off') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay (click to attach/detach)', - }) - - await persistState() -} - -async function connectOrToggleForActiveTab() { - const [active] = await chrome.tabs.query({ active: true, currentWindow: true }) - const tabId = active?.id - if (!tabId) return - - // Prevent concurrent operations on the same tab. - if (tabOperationLocks.has(tabId)) return - tabOperationLocks.add(tabId) - - try { - if (reattachPending.has(tabId)) { - reattachPending.delete(tabId) - setBadge(tabId, 'off') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay (click to attach/detach)', - }) - return - } - - const existing = tabs.get(tabId) - if (existing?.state === 'connected') { - await detachTab(tabId, 'toggle') - return - } - - // User is manually connecting — cancel any pending reconnect. - cancelReconnect() - - tabs.set(tabId, { state: 'connecting' }) - setBadge(tabId, 'connecting') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay: connecting to local relay…', - }) - - try { - await ensureRelayConnection() - await attachTab(tabId) - } catch (err) { - tabs.delete(tabId) - setBadge(tabId, 'error') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay: relay not running (open options for setup)', - }) - void maybeOpenHelpOnce() - const message = err instanceof Error ? err.message : String(err) - console.warn('attach failed', message, nowStack()) - } - } finally { - tabOperationLocks.delete(tabId) - } -} - -async function handleForwardCdpCommand(msg) { - const method = String(msg?.params?.method || '').trim() - const params = msg?.params?.params || undefined - const sessionId = typeof msg?.params?.sessionId === 'string' ? msg.params.sessionId : undefined - - const bySession = sessionId ? getTabBySessionId(sessionId) : null - const targetId = typeof params?.targetId === 'string' ? params.targetId : undefined - const tabId = - bySession?.tabId || - (targetId ? getTabByTargetId(targetId) : null) || - (() => { - for (const [id, tab] of tabs.entries()) { - if (tab.state === 'connected') return id - } - return null - })() - - if (!tabId) throw new Error(`No attached tab for method ${method}`) - - /** @type {chrome.debugger.DebuggerSession} */ - const debuggee = { tabId } - - if (method === 'Runtime.enable') { - try { - await chrome.debugger.sendCommand(debuggee, 'Runtime.disable') - await new Promise((r) => setTimeout(r, 50)) - } catch { - // ignore - } - return await chrome.debugger.sendCommand(debuggee, 'Runtime.enable', params) - } - - if (method === 'Target.createTarget') { - const url = typeof params?.url === 'string' ? params.url : 'about:blank' - const tab = await chrome.tabs.create({ url, active: false }) - if (!tab.id) throw new Error('Failed to create tab') - await new Promise((r) => setTimeout(r, 100)) - const attached = await attachTab(tab.id) - return { targetId: attached.targetId } - } - - if (method === 'Target.closeTarget') { - const target = typeof params?.targetId === 'string' ? params.targetId : '' - const toClose = target ? getTabByTargetId(target) : tabId - if (!toClose) return { success: false } - try { - const allTabs = await chrome.tabs.query({}) - if (isLastRemainingTab(allTabs, toClose)) { - console.warn('Refusing to close the last tab: this would kill the browser process') - return { success: false, error: 'Cannot close the last tab' } - } - await chrome.tabs.remove(toClose) - } catch { - return { success: false } - } - return { success: true } - } - - if (method === 'Target.activateTarget') { - const target = typeof params?.targetId === 'string' ? params.targetId : '' - const toActivate = target ? getTabByTargetId(target) : tabId - if (!toActivate) return {} - const tab = await chrome.tabs.get(toActivate).catch(() => null) - if (!tab) return {} - if (tab.windowId) { - await chrome.windows.update(tab.windowId, { focused: true }).catch(() => {}) - } - await chrome.tabs.update(toActivate, { active: true }).catch(() => {}) - return {} - } - - const tabState = tabs.get(tabId) - const mainSessionId = tabState?.sessionId - const debuggerSession = - sessionId && mainSessionId && sessionId !== mainSessionId - ? { ...debuggee, sessionId } - : debuggee - - return await chrome.debugger.sendCommand(debuggerSession, method, params) -} - -function onDebuggerEvent(source, method, params) { - const tabId = source.tabId - if (!tabId) return - const tab = tabs.get(tabId) - if (!tab?.sessionId) return - - if (method === 'Target.attachedToTarget' && params?.sessionId) { - childSessionToTab.set(String(params.sessionId), tabId) - } - - if (method === 'Target.detachedFromTarget' && params?.sessionId) { - childSessionToTab.delete(String(params.sessionId)) - } - - try { - sendToRelay({ - method: 'forwardCDPEvent', - params: { - sessionId: source.sessionId || tab.sessionId, - method, - params, - }, - }) - } catch { - // Relay may be down. - } -} - -async function onDebuggerDetach(source, reason) { - const tabId = source.tabId - if (!tabId) return - if (!tabs.has(tabId)) return - - // User explicitly cancelled or DevTools replaced the connection — respect their intent - if (reason === 'canceled_by_user' || reason === 'replaced_with_devtools') { - void detachTab(tabId, reason) - return - } - - // Check if tab still exists — distinguishes navigation from tab close - let tabInfo - try { - tabInfo = await chrome.tabs.get(tabId) - } catch { - // Tab is gone (closed) — normal cleanup - void detachTab(tabId, reason) - return - } - - if (tabInfo.url?.startsWith('chrome://') || tabInfo.url?.startsWith('chrome-extension://')) { - void detachTab(tabId, reason) - return - } - - if (reattachPending.has(tabId)) return - - const oldTab = tabs.get(tabId) - const oldSessionId = oldTab?.sessionId - const oldTargetId = oldTab?.targetId - - if (oldSessionId) tabBySession.delete(oldSessionId) - tabs.delete(tabId) - for (const [childSessionId, parentTabId] of childSessionToTab.entries()) { - if (parentTabId === tabId) childSessionToTab.delete(childSessionId) - } - - if (oldSessionId && oldTargetId) { - try { - sendToRelay({ - method: 'forwardCDPEvent', - params: { - method: 'Target.detachedFromTarget', - params: { sessionId: oldSessionId, targetId: oldTargetId, reason: 'navigation-reattach' }, - }, - }) - } catch { - // Relay may be down. - } - } - - reattachPending.add(tabId) - setBadge(tabId, 'connecting') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay: re-attaching after navigation…', - }) - - // Extend re-attach window from 2.5 s to ~7.7 s (5 attempts). - // SPAs and pages with heavy JS can take >2.5 s before the Chrome debugger - // is attachable, causing all three original attempts to fail and leaving - // the badge permanently off after every navigation. - const delays = [200, 500, 1000, 2000, 4000] - for (let attempt = 0; attempt < delays.length; attempt++) { - await new Promise((r) => setTimeout(r, delays[attempt])) - - if (!reattachPending.has(tabId)) return - - try { - await chrome.tabs.get(tabId) - } catch { - reattachPending.delete(tabId) - setBadge(tabId, 'off') - return - } - - const relayUp = relayWs && relayWs.readyState === WebSocket.OPEN - - try { - // When relay is down, still attach the debugger but skip sending the - // relay event. reannounceAttachedTabs() will notify the relay once it - // reconnects, so the tab stays tracked across transient relay drops. - await attachTab(tabId, { skipAttachedEvent: !relayUp }) - reattachPending.delete(tabId) - if (!relayUp) { - setBadge(tabId, 'connecting') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay: attached, waiting for relay reconnect…', - }) - } - return - } catch { - // continue retries - } - } - - reattachPending.delete(tabId) - setBadge(tabId, 'off') - void chrome.action.setTitle({ - tabId, - title: 'OpenClaw Browser Relay: re-attach failed (click to retry)', - }) -} - -// Tab lifecycle listeners — clean up stale entries. -chrome.tabs.onRemoved.addListener((tabId) => void whenReady(() => { - reattachPending.delete(tabId) - if (!tabs.has(tabId)) return - const tab = tabs.get(tabId) - if (tab?.sessionId) tabBySession.delete(tab.sessionId) - tabs.delete(tabId) - for (const [childSessionId, parentTabId] of childSessionToTab.entries()) { - if (parentTabId === tabId) childSessionToTab.delete(childSessionId) - } - if (tab?.sessionId && tab?.targetId) { - try { - sendToRelay({ - method: 'forwardCDPEvent', - params: { - method: 'Target.detachedFromTarget', - params: { sessionId: tab.sessionId, targetId: tab.targetId, reason: 'tab_closed' }, - }, - }) - } catch { - // Relay may be down. - } - } - void persistState() -})) - -chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) => void whenReady(() => { - const tab = tabs.get(removedTabId) - if (!tab) return - tabs.delete(removedTabId) - tabs.set(addedTabId, tab) - if (tab.sessionId) { - tabBySession.set(tab.sessionId, addedTabId) - } - for (const [childSessionId, parentTabId] of childSessionToTab.entries()) { - if (parentTabId === removedTabId) { - childSessionToTab.set(childSessionId, addedTabId) - } - } - setBadge(addedTabId, 'on') - void persistState() -})) - -// Register debugger listeners at module scope so detach/event handling works -// even when the relay WebSocket is down. -chrome.debugger.onEvent.addListener((...args) => void whenReady(() => onDebuggerEvent(...args))) -chrome.debugger.onDetach.addListener((...args) => void whenReady(() => onDebuggerDetach(...args))) - -chrome.action.onClicked.addListener(() => void whenReady(() => connectOrToggleForActiveTab())) - -// Refresh badge after navigation completes — service worker may have restarted -// during navigation, losing ephemeral badge state. -chrome.webNavigation.onCompleted.addListener(({ tabId, frameId }) => void whenReady(() => { - if (frameId !== 0) return - const tab = tabs.get(tabId) - if (tab?.state === 'connected') { - setBadge(tabId, relayWs && relayWs.readyState === WebSocket.OPEN ? 'on' : 'connecting') - } -})) - -// Refresh badge when user switches to an attached tab. -chrome.tabs.onActivated.addListener(({ tabId }) => void whenReady(() => { - const tab = tabs.get(tabId) - if (tab?.state === 'connected') { - setBadge(tabId, relayWs && relayWs.readyState === WebSocket.OPEN ? 'on' : 'connecting') - } -})) - -chrome.runtime.onInstalled.addListener(() => { - void chrome.runtime.openOptionsPage() -}) - -// MV3 keepalive via chrome.alarms — more reliable than setInterval across -// service worker restarts. Checks relay health and refreshes badges. -chrome.alarms.create('relay-keepalive', { periodInMinutes: 0.5 }) - -chrome.alarms.onAlarm.addListener(async (alarm) => { - if (alarm.name !== 'relay-keepalive') return - await initPromise - - if (tabs.size === 0) return - - // Refresh badges (ephemeral in MV3). - for (const [tabId, tab] of tabs.entries()) { - if (tab.state === 'connected') { - setBadge(tabId, relayWs && relayWs.readyState === WebSocket.OPEN ? 'on' : 'connecting') - } - } - - // If relay is down and no reconnect is in progress, trigger one. - if (!relayWs || relayWs.readyState !== WebSocket.OPEN) { - if (!relayConnectPromise && !reconnectTimer) { - console.log('Keepalive: WebSocket unhealthy, triggering reconnect') - await ensureRelayConnection().catch(() => { - // ensureRelayConnection may throw without triggering onRelayClosed - // (e.g. preflight fetch fails before WS is created), so ensure - // reconnect is always scheduled on failure. - if (!reconnectTimer) { - scheduleReconnect() - } - }) - } - } -}) - -// Rehydrate state on service worker startup. Split: rehydration is the gate -// (fast), relay reconnect runs in background (slow, non-blocking). -const initPromise = rehydrateState() - -initPromise.then(() => { - if (tabs.size > 0) { - ensureRelayConnection().then(() => { - reconnectAttempt = 0 - return reannounceAttachedTabs() - }).catch(() => { - scheduleReconnect() - }) - } -}) - -// Shared gate: all state-dependent handlers await this before accessing maps. -async function whenReady(fn) { - await initPromise - return fn() -} - -// Relay check handler for the options page. The service worker has -// host_permissions and bypasses CORS preflight, so the options page -// delegates token-validation requests here. -chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => { - if (msg?.type !== 'relayCheck') return false - const { url, token } = msg - const headers = token ? { 'x-openclaw-relay-token': token } : {} - fetch(url, { method: 'GET', headers, signal: AbortSignal.timeout(2000) }) - .then(async (res) => { - const contentType = String(res.headers.get('content-type') || '') - let json = null - if (contentType.includes('application/json')) { - try { - json = await res.json() - } catch { - json = null - } - } - sendResponse({ status: res.status, ok: res.ok, contentType, json }) - }) - .catch((err) => sendResponse({ status: 0, ok: false, error: String(err) })) - return true -}) diff --git a/assets/chrome-extension/manifest.json b/assets/chrome-extension/manifest.json deleted file mode 100644 index 62038276cd7..00000000000 --- a/assets/chrome-extension/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "manifest_version": 3, - "name": "OpenClaw Browser Relay", - "version": "0.1.0", - "description": "Attach OpenClaw to your existing Chrome tab via a local CDP relay server.", - "icons": { - "16": "icons/icon16.png", - "32": "icons/icon32.png", - "48": "icons/icon48.png", - "128": "icons/icon128.png" - }, - "permissions": ["debugger", "tabs", "activeTab", "storage", "alarms", "webNavigation"], - "host_permissions": ["http://127.0.0.1/*", "http://localhost/*"], - "background": { "service_worker": "background.js", "type": "module" }, - "action": { - "default_title": "OpenClaw Browser Relay (click to attach/detach)", - "default_icon": { - "16": "icons/icon16.png", - "32": "icons/icon32.png", - "48": "icons/icon48.png", - "128": "icons/icon128.png" - } - }, - "options_ui": { "page": "options.html", "open_in_tab": true } -} diff --git a/assets/chrome-extension/options-validation.js b/assets/chrome-extension/options-validation.js deleted file mode 100644 index 53e2cd55014..00000000000 --- a/assets/chrome-extension/options-validation.js +++ /dev/null @@ -1,57 +0,0 @@ -const PORT_GUIDANCE = 'Use gateway port + 3 (for gateway 18789, relay is 18792).' - -function hasCdpVersionShape(data) { - return !!data && typeof data === 'object' && 'Browser' in data && 'Protocol-Version' in data -} - -export function classifyRelayCheckResponse(res, port) { - if (!res) { - return { action: 'throw', error: 'No response from service worker' } - } - - if (res.status === 401) { - return { action: 'status', kind: 'error', message: 'Gateway token rejected. Check token and save again.' } - } - - if (res.error) { - return { action: 'throw', error: res.error } - } - - if (!res.ok) { - return { action: 'throw', error: `HTTP ${res.status}` } - } - - const contentType = String(res.contentType || '') - if (!contentType.includes('application/json')) { - return { - action: 'status', - kind: 'error', - message: `Wrong port: this is likely the gateway, not the relay. ${PORT_GUIDANCE}`, - } - } - - if (!hasCdpVersionShape(res.json)) { - return { - action: 'status', - kind: 'error', - message: `Wrong port: expected relay /json/version response. ${PORT_GUIDANCE}`, - } - } - - return { action: 'status', kind: 'ok', message: `Relay reachable and authenticated at http://127.0.0.1:${port}/` } -} - -export function classifyRelayCheckException(err, port) { - const message = String(err || '').toLowerCase() - if (message.includes('json') || message.includes('syntax')) { - return { - kind: 'error', - message: `Wrong port: this is not a relay endpoint. ${PORT_GUIDANCE}`, - } - } - - return { - kind: 'error', - message: `Relay not reachable/authenticated at http://127.0.0.1:${port}/. Start OpenClaw browser relay and verify token.`, - } -} diff --git a/assets/chrome-extension/options.html b/assets/chrome-extension/options.html deleted file mode 100644 index 17fc6a79eed..00000000000 --- a/assets/chrome-extension/options.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - OpenClaw Browser Relay - - - -
-
- -
-

OpenClaw Browser Relay

-

Click the toolbar button on a tab to attach / detach.

-
-
- -
-
-

Getting started

-

- If you see a red ! badge on the extension icon, the relay server is not reachable. - Start OpenClaw’s browser relay on this machine (Gateway or node host), then click the toolbar button again. -

-

- Full guide (install, remote Gateway, security): docs.openclaw.ai/tools/chrome-extension -

-
- -
-

Relay connection

- -
- -
- -
- - -
-
- Default port: 18792. Extension connects to: http://127.0.0.1:<port>/. - Gateway token must match gateway.auth.token (or OPENCLAW_GATEWAY_TOKEN). -
-
-
-
- - -
- - diff --git a/assets/chrome-extension/options.js b/assets/chrome-extension/options.js deleted file mode 100644 index aa6fcc4901f..00000000000 --- a/assets/chrome-extension/options.js +++ /dev/null @@ -1,74 +0,0 @@ -import { deriveRelayToken } from './background-utils.js' -import { classifyRelayCheckException, classifyRelayCheckResponse } from './options-validation.js' - -const DEFAULT_PORT = 18792 - -function clampPort(value) { - const n = Number.parseInt(String(value || ''), 10) - if (!Number.isFinite(n)) return DEFAULT_PORT - if (n <= 0 || n > 65535) return DEFAULT_PORT - return n -} - -function updateRelayUrl(port) { - const el = document.getElementById('relay-url') - if (!el) return - el.textContent = `http://127.0.0.1:${port}/` -} - -function setStatus(kind, message) { - const status = document.getElementById('status') - if (!status) return - status.dataset.kind = kind || '' - status.textContent = message || '' -} - -async function checkRelayReachable(port, token) { - const url = `http://127.0.0.1:${port}/json/version` - const trimmedToken = String(token || '').trim() - if (!trimmedToken) { - setStatus('error', 'Gateway token required. Save your gateway token to connect.') - return - } - try { - const relayToken = await deriveRelayToken(trimmedToken, port) - // Delegate the fetch to the background service worker to bypass - // CORS preflight on the custom x-openclaw-relay-token header. - const res = await chrome.runtime.sendMessage({ - type: 'relayCheck', - url, - token: relayToken, - }) - const result = classifyRelayCheckResponse(res, port) - if (result.action === 'throw') throw new Error(result.error) - setStatus(result.kind, result.message) - } catch (err) { - const result = classifyRelayCheckException(err, port) - setStatus(result.kind, result.message) - } -} - -async function load() { - const stored = await chrome.storage.local.get(['relayPort', 'gatewayToken']) - const port = clampPort(stored.relayPort) - const token = String(stored.gatewayToken || '').trim() - document.getElementById('port').value = String(port) - document.getElementById('token').value = token - updateRelayUrl(port) - await checkRelayReachable(port, token) -} - -async function save() { - const portInput = document.getElementById('port') - const tokenInput = document.getElementById('token') - const port = clampPort(portInput.value) - const token = String(tokenInput.value || '').trim() - await chrome.storage.local.set({ relayPort: port, gatewayToken: token }) - portInput.value = String(port) - tokenInput.value = token - updateRelayUrl(port) - await checkRelayReachable(port, token) -} - -document.getElementById('save').addEventListener('click', () => void save()) -void load() diff --git a/changelog/fragments/session-history-live-events-followups.md b/changelog/fragments/session-history-live-events-followups.md new file mode 100644 index 00000000000..59b98f364e2 --- /dev/null +++ b/changelog/fragments/session-history-live-events-followups.md @@ -0,0 +1,3 @@ +### Fixes + +- Gateway/session history: return `404` for unknown session history lookups, unsubscribe session lifecycle listeners during shutdown, add coverage for the new transcript and lifecycle helpers, and tighten session history plus live transcript tests so the Control UI session surfaces stay stable under restart and follow mode. diff --git a/docker-compose.yml b/docker-compose.yml index cc7169d3a88..c0bffc64458 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-} CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-} CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-} + TZ: ${OPENCLAW_TZ:-UTC} volumes: - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace @@ -65,6 +66,7 @@ services: CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-} CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-} CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-} + TZ: ${OPENCLAW_TZ:-UTC} volumes: - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace diff --git a/docker-setup.sh b/docker-setup.sh index 450c2025ffa..19e5461765b 100755 --- a/docker-setup.sh +++ b/docker-setup.sh @@ -10,6 +10,7 @@ HOME_VOLUME_NAME="${OPENCLAW_HOME_VOLUME:-}" RAW_SANDBOX_SETTING="${OPENCLAW_SANDBOX:-}" SANDBOX_ENABLED="" DOCKER_SOCKET_PATH="${OPENCLAW_DOCKER_SOCKET:-}" +TIMEZONE="${OPENCLAW_TZ:-}" fail() { echo "ERROR: $*" >&2 @@ -135,6 +136,11 @@ contains_disallowed_chars() { [[ "$value" == *$'\n'* || "$value" == *$'\r'* || "$value" == *$'\t'* ]] } +is_valid_timezone() { + local value="$1" + [[ -e "/usr/share/zoneinfo/$value" && ! -d "/usr/share/zoneinfo/$value" ]] +} + validate_mount_path_value() { local label="$1" local value="$2" @@ -202,6 +208,17 @@ fi if [[ -n "$SANDBOX_ENABLED" ]]; then validate_mount_path_value "OPENCLAW_DOCKER_SOCKET" "$DOCKER_SOCKET_PATH" fi +if [[ -n "$TIMEZONE" ]]; then + if contains_disallowed_chars "$TIMEZONE"; then + fail "OPENCLAW_TZ contains unsupported control characters." + fi + if [[ ! "$TIMEZONE" =~ ^[A-Za-z0-9/_+\-]+$ ]]; then + fail "OPENCLAW_TZ must be a valid IANA timezone string (e.g. Asia/Shanghai)." + fi + if ! is_valid_timezone "$TIMEZONE"; then + fail "OPENCLAW_TZ must match a timezone in /usr/share/zoneinfo (e.g. Asia/Shanghai)." + fi +fi mkdir -p "$OPENCLAW_CONFIG_DIR" mkdir -p "$OPENCLAW_WORKSPACE_DIR" @@ -224,6 +241,7 @@ export OPENCLAW_HOME_VOLUME="$HOME_VOLUME_NAME" export OPENCLAW_ALLOW_INSECURE_PRIVATE_WS="${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}" export OPENCLAW_SANDBOX="$SANDBOX_ENABLED" export OPENCLAW_DOCKER_SOCKET="$DOCKER_SOCKET_PATH" +export OPENCLAW_TZ="$TIMEZONE" # Detect Docker socket GID for sandbox group_add. DOCKER_GID="" @@ -408,7 +426,8 @@ upsert_env "$ENV_FILE" \ OPENCLAW_DOCKER_SOCKET \ DOCKER_GID \ OPENCLAW_INSTALL_DOCKER_CLI \ - OPENCLAW_ALLOW_INSECURE_PRIVATE_WS + OPENCLAW_ALLOW_INSECURE_PRIVATE_WS \ + OPENCLAW_TZ if [[ "$IMAGE_NAME" == "openclaw:local" ]]; then echo "==> Building Docker image: $IMAGE_NAME" diff --git a/docs/.generated/README.md b/docs/.generated/README.md new file mode 100644 index 00000000000..a2218ab3855 --- /dev/null +++ b/docs/.generated/README.md @@ -0,0 +1,8 @@ +# Generated Docs Artifacts + +These baseline artifacts are generated from the repo-owned OpenClaw config schema and bundled channel/plugin metadata. + +- Do not edit `config-baseline.json` by hand. +- Do not edit `config-baseline.jsonl` by hand. +- Regenerate it with `pnpm config:docs:gen`. +- Validate it in CI or locally with `pnpm config:docs:check`. diff --git a/docs/.generated/config-baseline.json b/docs/.generated/config-baseline.json new file mode 100644 index 00000000000..ec8c22e0627 --- /dev/null +++ b/docs/.generated/config-baseline.json @@ -0,0 +1,64209 @@ +{ + "generatedBy": "scripts/generate-config-doc-baseline.ts", + "entries": [ + { + "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 + }, + { + "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 + }, + { + "path": "acp.allowedAgents.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "acp.backend", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Backend", + "help": "Default ACP runtime backend id (for example: acpx). Must match a registered ACP runtime plugin backend.", + "hasChildren": false + }, + { + "path": "acp.defaultAgent", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Default Agent", + "help": "Fallback ACP target agent id used when ACP spawns do not specify an explicit target.", + "hasChildren": false + }, + { + "path": "acp.dispatch", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "acp.dispatch.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Dispatch Enabled", + "help": "Independent dispatch gate for ACP session turns (default: true). Set false to keep ACP commands available while blocking ACP turn execution.", + "hasChildren": false + }, + { + "path": "acp.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Enabled", + "help": "Global ACP feature gate. Keep disabled unless ACP runtime + policy are configured.", + "hasChildren": false + }, + { + "path": "acp.maxConcurrentSessions", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "ACP Max Concurrent Sessions", + "help": "Maximum concurrently active ACP sessions across this gateway process.", + "hasChildren": false + }, + { + "path": "acp.runtime", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "acp.runtime.installCommand", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Runtime Install Command", + "help": "Optional operator install/setup command shown by `/acp install` and `/acp doctor` when ACP backend wiring is missing.", + "hasChildren": false + }, + { + "path": "acp.runtime.ttlMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Runtime TTL (minutes)", + "help": "Idle runtime TTL in minutes for ACP session workers before eligible cleanup.", + "hasChildren": false + }, + { + "path": "acp.stream", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Stream", + "help": "ACP streaming projection controls for chunk sizing, metadata visibility, and deduped delivery behavior.", + "hasChildren": true + }, + { + "path": "acp.stream.coalesceIdleMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Stream Coalesce Idle (ms)", + "help": "Coalescer idle flush window in milliseconds for ACP streamed text before block replies are emitted.", + "hasChildren": false + }, + { + "path": "acp.stream.deliveryMode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Stream Delivery Mode", + "help": "ACP delivery style: live streams projected output incrementally, final_only buffers all projected ACP output until terminal turn events.", + "hasChildren": false + }, + { + "path": "acp.stream.hiddenBoundarySeparator", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Stream Hidden Boundary Separator", + "help": "Separator inserted before next visible assistant text when hidden ACP tool lifecycle events occurred (none|space|newline|paragraph). Default: paragraph.", + "hasChildren": false + }, + { + "path": "acp.stream.maxChunkChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "ACP Stream Max Chunk Chars", + "help": "Maximum chunk size for ACP streamed block projection before splitting into multiple block replies.", + "hasChildren": false + }, + { + "path": "acp.stream.maxOutputChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "ACP Stream Max Output Chars", + "help": "Maximum assistant output characters projected per ACP turn before truncation notice is emitted.", + "hasChildren": false + }, + { + "path": "acp.stream.maxSessionUpdateChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "ACP Stream Max Session Update Chars", + "help": "Maximum characters for projected ACP session/update lines (tool/status updates).", + "hasChildren": false + }, + { + "path": "acp.stream.repeatSuppression", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Stream Repeat Suppression", + "help": "When true (default), suppress repeated ACP status/tool projection lines in a turn while keeping raw ACP events unchanged.", + "hasChildren": false + }, + { + "path": "acp.stream.tagVisibility", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Stream Tag Visibility", + "help": "Per-sessionUpdate visibility overrides for ACP projection (for example usage_update, available_commands_update).", + "hasChildren": true + }, + { + "path": "acp.stream.tagVisibility.*", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agents", + "help": "Agent runtime configuration root covering defaults and explicit agent entries used for routing and execution context. Keep this section explicit so model/tool behavior stays predictable across multi-agent workflows.", + "hasChildren": true + }, + { + "path": "agents.defaults", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent Defaults", + "help": "Shared default settings inherited by agents unless overridden per entry in agents.list. Use defaults to enforce consistent baseline behavior and reduce duplicated per-agent configuration.", + "hasChildren": true + }, + { + "path": "agents.defaults.blockStreamingBreak", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.blockStreamingChunk", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.blockStreamingChunk.breakPreference", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.blockStreamingChunk.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.blockStreamingChunk.minChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.blockStreamingCoalesce", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.blockStreamingCoalesce.idleMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.blockStreamingCoalesce.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.blockStreamingCoalesce.minChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.blockStreamingDefault", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.bootstrapMaxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Bootstrap Max Chars", + "help": "Max characters of each workspace bootstrap file injected into the system prompt before truncation (default: 20000).", + "hasChildren": false + }, + { + "path": "agents.defaults.bootstrapPromptTruncationWarning", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Bootstrap Prompt Truncation Warning", + "help": "Inject agent-visible warning text when bootstrap files are truncated: \"off\", \"once\" (default), or \"always\".", + "hasChildren": false + }, + { + "path": "agents.defaults.bootstrapTotalMaxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Bootstrap Total Max Chars", + "help": "Max total characters across all injected workspace bootstrap files (default: 150000).", + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "CLI Backends", + "help": "Optional CLI backends for text-only fallback (claude-cli, etc.).", + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.args", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.args.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.clearEnv", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.clearEnv.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.command", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.env", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.env.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.imageArg", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.imageMode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.input", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.maxPromptArgChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.modelAliases", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.modelAliases.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.modelArg", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.output", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.reliability", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog.fresh", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog.fresh.maxMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog.fresh.minMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog.fresh.noOutputTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog.fresh.noOutputTimeoutRatio", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog.resume", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog.resume.maxMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog.resume.minMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog.resume.noOutputTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.reliability.watchdog.resume.noOutputTimeoutRatio", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.resumeArgs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.resumeArgs.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.resumeOutput", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.serialize", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.sessionArg", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.sessionArgs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.sessionArgs.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.sessionIdFields", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.cliBackends.*.sessionIdFields.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.sessionMode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.systemPromptArg", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.systemPromptMode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.cliBackends.*.systemPromptWhen", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.compaction", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction", + "help": "Compaction tuning for when context nears token limits, including history share, reserve headroom, and pre-compaction memory flush behavior. Use this when long-running sessions need stable continuity under tight context windows.", + "hasChildren": true + }, + { + "path": "agents.defaults.compaction.customInstructions", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.identifierInstructions", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Identifier Instructions", + "help": "Custom identifier-preservation instruction text used when identifierPolicy=\"custom\". Keep this explicit and safety-focused so compaction summaries do not rewrite opaque IDs, URLs, hosts, or ports.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.identifierPolicy", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Compaction Identifier Policy", + "help": "Identifier-preservation policy for compaction summaries: \"strict\" prepends built-in opaque-identifier retention guidance (default), \"off\" disables this prefix, and \"custom\" uses identifierInstructions. Keep \"strict\" unless you have a specific compatibility need.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.keepRecentTokens", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "auth", + "security" + ], + "label": "Compaction Keep Recent Tokens", + "help": "Minimum token budget preserved from the most recent conversation window during compaction. Use higher values to protect immediate context continuity and lower values to keep more long-tail history.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.maxHistoryShare", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Compaction Max History Share", + "help": "Maximum fraction of total context budget allowed for retained history after compaction (range 0.1-0.9). Use lower shares for more generation headroom or higher shares for deeper historical continuity.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.memoryFlush", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Memory Flush", + "help": "Pre-compaction memory flush settings that run an agentic memory write before heavy compaction. Keep enabled for long sessions so salient context is persisted before aggressive trimming.", + "hasChildren": true + }, + { + "path": "agents.defaults.compaction.memoryFlush.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Memory Flush Enabled", + "help": "Enables pre-compaction memory flush before the runtime performs stronger history reduction near token limits. Keep enabled unless you intentionally disable memory side effects in constrained environments.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.memoryFlush.forceFlushTranscriptBytes", + "kind": "core", + "type": [ + "integer", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Memory Flush Transcript Size Threshold", + "help": "Forces pre-compaction memory flush when transcript file size reaches this threshold (bytes or strings like \"2mb\"). Use this to prevent long-session hangs even when token counters are stale; set to 0 to disable.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.memoryFlush.prompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Memory Flush Prompt", + "help": "User-prompt template used for the pre-compaction memory flush turn when generating memory candidates. Use this only when you need custom extraction instructions beyond the default memory flush behavior.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.memoryFlush.softThresholdTokens", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "auth", + "security" + ], + "label": "Compaction Memory Flush Soft Threshold", + "help": "Threshold distance to compaction (in tokens) that triggers pre-compaction memory flush execution. Use earlier thresholds for safer persistence, or tighter thresholds for lower flush frequency.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.memoryFlush.systemPrompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Memory Flush System Prompt", + "help": "System-prompt override for the pre-compaction memory flush turn to control extraction style and safety constraints. Use carefully so custom instructions do not reduce memory quality or leak sensitive context.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Mode", + "help": "Compaction strategy mode: \"default\" uses baseline behavior, while \"safeguard\" applies stricter guardrails to preserve recent context. Keep \"default\" unless you observe aggressive history loss near limit boundaries.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Compaction Model Override", + "help": "Optional provider/model override used only for compaction summarization. Set this when you want compaction to run on a different model than the session default, and leave it unset to keep using the primary agent model.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.postCompactionSections", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Post-Compaction Context Sections", + "help": "AGENTS.md H2/H3 section names re-injected after compaction so the agent reruns critical startup guidance. Leave unset to use \"Session Startup\"/\"Red Lines\" with legacy fallback to \"Every Session\"/\"Safety\"; set to [] to disable reinjection entirely.", + "hasChildren": true + }, + { + "path": "agents.defaults.compaction.postCompactionSections.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.postIndexSync", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "off", + "async", + "await" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Post-Index Sync", + "help": "Controls post-compaction session memory reindex mode: \"off\", \"async\", or \"await\" (default: \"async\"). Use \"await\" for strongest freshness, \"async\" for lower compaction latency, and \"off\" only when session-memory sync is handled elsewhere.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.qualityGuard", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Quality Guard", + "help": "Optional quality-audit retry settings for safeguard compaction summaries. Leave this disabled unless you explicitly want summary audits and one-shot regeneration on failed checks.", + "hasChildren": true + }, + { + "path": "agents.defaults.compaction.qualityGuard.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Quality Guard Enabled", + "help": "Enables summary quality audits and regeneration retries for safeguard compaction. Default: false, so safeguard mode alone does not turn on retry behavior.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.qualityGuard.maxRetries", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Compaction Quality Guard Max Retries", + "help": "Maximum number of regeneration retries after a failed safeguard summary quality audit. Use small values to bound extra latency and token cost.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.recentTurnsPreserve", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Compaction Preserve Recent Turns", + "help": "Number of most recent user/assistant turns kept verbatim outside safeguard summarization (default: 3). Raise this to preserve exact recent dialogue context, or lower it to maximize compaction savings.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.reserveTokens", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "auth", + "security" + ], + "label": "Compaction Reserve Tokens", + "help": "Token headroom reserved for reply generation and tool output after compaction runs. Use higher reserves for verbose/tool-heavy sessions, and lower reserves when maximizing retained history matters more.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.reserveTokensFloor", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "auth", + "security" + ], + "label": "Compaction Reserve Token Floor", + "help": "Minimum floor enforced for reserveTokens in Pi compaction paths (0 disables the floor guard). Use a non-zero floor to avoid over-aggressive compression under fluctuating token estimates.", + "hasChildren": false + }, + { + "path": "agents.defaults.compaction.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Compaction Timeout (Seconds)", + "help": "Maximum time in seconds allowed for a single compaction operation before it is aborted (default: 900). Increase this for very large sessions that need more time to summarize, or decrease it to fail faster on unresponsive models.", + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.contextPruning.hardClear", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.contextPruning.hardClear.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.hardClear.placeholder", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.hardClearRatio", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.keepLastAssistants", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.minPrunableToolChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.softTrim", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.contextPruning.softTrim.headChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.softTrim.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.softTrim.tailChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.softTrimRatio", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.tools", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.contextPruning.tools.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.contextPruning.tools.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.tools.deny", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.contextPruning.tools.deny.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextPruning.ttl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.contextTokens", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.elevatedDefault", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.embeddedPi", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Embedded Pi", + "help": "Embedded Pi runner hardening controls for how workspace-local Pi settings are trusted and applied in OpenClaw sessions.", + "hasChildren": true + }, + { + "path": "agents.defaults.embeddedPi.projectSettingsPolicy", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Embedded Pi Project Settings Policy", + "help": "How embedded Pi handles workspace-local `.pi/config/settings.json`: \"sanitize\" (default) strips shellPath/shellCommandPrefix, \"ignore\" disables project settings entirely, and \"trusted\" applies project settings as-is.", + "hasChildren": false + }, + { + "path": "agents.defaults.envelopeElapsed", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Envelope Elapsed", + "help": "Include elapsed time in message envelopes (\"on\" or \"off\").", + "hasChildren": false + }, + { + "path": "agents.defaults.envelopeTimestamp", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Envelope Timestamp", + "help": "Include absolute timestamps in message envelopes (\"on\" or \"off\").", + "hasChildren": false + }, + { + "path": "agents.defaults.envelopeTimezone", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Envelope Timezone", + "help": "Timezone for message envelopes (\"utc\", \"local\", \"user\", or an IANA timezone string).", + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.heartbeat.accountId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.ackMaxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.activeHours", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.heartbeat.activeHours.end", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.activeHours.start", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.activeHours.timezone", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.directPolicy", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "automation", + "storage" + ], + "label": "Heartbeat Direct Policy", + "help": "Controls whether heartbeat delivery may target direct/DM chats: \"allow\" (default) permits DM delivery and \"block\" suppresses direct-target sends.", + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.every", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.includeReasoning", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.isolatedSession", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.lightContext", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.prompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.session", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.suppressToolErrorWarnings", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "label": "Heartbeat Suppress Tool Error Warnings", + "help": "Suppress tool error warning payloads during heartbeat runs.", + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.target", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "help": "Delivery target (\"last\", \"none\", or a channel id). Known channels: telegram, whatsapp, discord, irc, googlechat, slack, signal, imessage, line, bluebubbles, feishu, matrix, mattermost, msteams, nextcloud-talk, nostr, synology-chat, tlon, twitch, zalo, zalouser.", + "hasChildren": false + }, + { + "path": "agents.defaults.heartbeat.to", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.humanDelay", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.humanDelay.maxMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Human Delay Max (ms)", + "help": "Maximum delay in ms for custom humanDelay (default: 2500).", + "hasChildren": false + }, + { + "path": "agents.defaults.humanDelay.minMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Human Delay Min (ms)", + "help": "Minimum delay in ms for custom humanDelay (default: 800).", + "hasChildren": false + }, + { + "path": "agents.defaults.humanDelay.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Human Delay Mode", + "help": "Delay style for block replies (\"off\", \"natural\", \"custom\").", + "hasChildren": false + }, + { + "path": "agents.defaults.imageGenerationModel", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.imageGenerationModel.fallbacks", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "reliability" + ], + "label": "Image Generation Model Fallbacks", + "help": "Ordered fallback image-generation models (provider/model).", + "hasChildren": true + }, + { + "path": "agents.defaults.imageGenerationModel.fallbacks.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.imageGenerationModel.primary", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Image Generation Model", + "help": "Optional image-generation model (provider/model) used by the shared image generation capability.", + "hasChildren": false + }, + { + "path": "agents.defaults.imageMaxDimensionPx", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance" + ], + "label": "Image Max Dimension (px)", + "help": "Max image side length in pixels when sanitizing transcript/tool-result image payloads (default: 1200).", + "hasChildren": false + }, + { + "path": "agents.defaults.imageModel", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "path": "agents.defaults.imageModel.fallbacks.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "agents.defaults.maxConcurrent", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.mediaMaxMb", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "agents.defaults.memorySearch.cache", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.cache.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Memory Search Embedding Cache", + "help": "Caches computed chunk embeddings in SQLite so reindexing and incremental updates run faster (default: true). Keep this enabled unless investigating cache correctness or minimizing disk usage.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.cache.maxEntries", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "Memory Search Embedding Cache Max Entries", + "help": "Sets a best-effort upper bound on cached embeddings kept in SQLite for memory search. Use this when controlling disk growth matters more than peak reindex speed.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.chunking", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.chunking.overlap", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Chunk Overlap Tokens", + "help": "Token overlap between adjacent memory chunks to preserve context continuity near split boundaries. Use modest overlap to reduce boundary misses without inflating index size too aggressively.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.chunking.tokens", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "auth", + "security" + ], + "label": "Memory Chunk Tokens", + "help": "Chunk size in tokens used when splitting memory sources before embedding/indexing. Increase for broader context per chunk, or lower to improve precision on pinpoint lookups.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable Memory Search", + "help": "Master toggle for memory search indexing and retrieval behavior on this agent profile. Keep enabled for semantic recall, and disable when you want fully stateless responses.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.experimental", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.experimental.sessionMemory", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced", + "security", + "storage" + ], + "label": "Memory Search Session Index (Experimental)", + "help": "Indexes session transcripts into memory search so responses can reference prior chat turns. Keep this off unless transcript recall is needed, because indexing cost and storage usage both increase.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.extraPaths", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Extra Memory Paths", + "help": "Adds extra directories or .md files to the memory index beyond default memory files. Use this when key reference docs live elsewhere in your repo; when multimodal memory is enabled, matching image/audio files under these paths are also eligible for indexing.", + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.extraPaths.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.fallback", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "reliability" + ], + "label": "Memory Search Fallback", + "help": "Backup provider used when primary embeddings fail: \"openai\", \"gemini\", \"voyage\", \"mistral\", \"ollama\", \"local\", or \"none\". Set a real fallback for production reliability; use \"none\" only if you prefer explicit failures.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.local", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.local.modelCacheDir", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.local.modelPath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Local Embedding Model Path", + "help": "Specifies the local embedding model source for local memory search, such as a GGUF file path or `hf:` URI. Use this only when provider is `local`, and verify model compatibility before large index rebuilds.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Memory Search Model", + "help": "Embedding model override used by the selected memory provider when a non-default model is required. Set this only when you need explicit recall quality/cost tuning beyond provider defaults.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.multimodal", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Multimodal", + "help": "Optional multimodal memory settings for indexing image and audio files from configured extra paths. Keep this off unless your embedding model explicitly supports cross-modal embeddings, and set `memorySearch.fallback` to \"none\" while it is enabled. Matching files are uploaded to the configured remote embedding provider during indexing.", + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.multimodal.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable Memory Search Multimodal", + "help": "Enables image/audio memory indexing from extraPaths. This currently requires Gemini embedding-2, keeps the default memory roots Markdown-only, disables memory-search fallback providers, and uploads matching binary content to the configured remote embedding provider.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.multimodal.maxFileBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "Memory Search Multimodal Max File Bytes", + "help": "Sets the maximum bytes allowed per multimodal file before it is skipped during memory indexing. Use this to cap upload cost and indexing latency, or raise it for short high-quality audio clips.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.multimodal.modalities", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Multimodal Modalities", + "help": "Selects which multimodal file types are indexed from extraPaths: \"image\", \"audio\", or \"all\". Keep this narrow to avoid indexing large binary corpora unintentionally.", + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.multimodal.modalities.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.outputDimensionality", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Output Dimensionality", + "help": "Gemini embedding-2 only: chooses the output vector size for memory embeddings. Use 768, 1536, or 3072 (default), and expect a full reindex when you change it because stored vector dimensions must stay consistent.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.provider", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Provider", + "help": "Selects the embedding backend used to build/query memory vectors: \"openai\", \"gemini\", \"voyage\", \"mistral\", \"ollama\", or \"local\". Keep your most reliable provider here and configure fallback for resilience.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.query", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.query.hybrid", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.query.hybrid.candidateMultiplier", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Hybrid Candidate Multiplier", + "help": "Expands the candidate pool before reranking (default: 4). Raise this for better recall on noisy corpora, but expect more compute and slightly slower searches.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.query.hybrid.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Hybrid", + "help": "Combines BM25 keyword matching with vector similarity for better recall on mixed exact + semantic queries. Keep enabled unless you are isolating ranking behavior for troubleshooting.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.query.hybrid.mmr", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.query.hybrid.mmr.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search MMR Re-ranking", + "help": "Adds MMR reranking to diversify results and reduce near-duplicate snippets in a single answer window. Enable when recall looks repetitive; keep off for strict score ordering.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.query.hybrid.mmr.lambda", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search MMR Lambda", + "help": "Sets MMR relevance-vs-diversity balance (0 = most diverse, 1 = most relevant, default: 0.7). Lower values reduce repetition; higher values keep tightly relevant but may duplicate.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.query.hybrid.temporalDecay", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.query.hybrid.temporalDecay.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Temporal Decay", + "help": "Applies recency decay so newer memory can outrank older memory when scores are close. Enable when timeliness matters; keep off for timeless reference knowledge.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.query.hybrid.temporalDecay.halfLifeDays", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Temporal Decay Half-life (Days)", + "help": "Controls how fast older memory loses rank when temporal decay is enabled (half-life in days, default: 30). Lower values prioritize recent context more aggressively.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.query.hybrid.textWeight", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Text Weight", + "help": "Controls how strongly BM25 keyword relevance influences hybrid ranking (0-1). Increase for exact-term matching; decrease when semantic matches should rank higher.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.query.hybrid.vectorWeight", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Vector Weight", + "help": "Controls how strongly semantic similarity influences hybrid ranking (0-1). Increase when paraphrase matching matters more than exact terms; decrease for stricter keyword emphasis.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.query.maxResults", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Memory Search Max Results", + "help": "Maximum number of memory hits returned from search before downstream reranking and prompt injection. Raise for broader recall, or lower for tighter prompts and faster responses.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.query.minScore", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Min Score", + "help": "Minimum relevance score threshold for including memory results in final recall output. Increase to reduce weak/noisy matches, or lower when you need more permissive retrieval.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.remote", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.remote.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security" + ], + "label": "Remote Embedding API Key", + "help": "Supplies a dedicated API key for remote embedding calls used by memory indexing and query-time embeddings. Use this when memory embeddings should use different credentials than global defaults or environment variables.", + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.remote.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.remote.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.remote.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "agents.defaults.memorySearch.remote.batch", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "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 + }, + { + "path": "agents.defaults.memorySearch.remote.batch.pollIntervalMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Remote Batch Poll Interval (ms)", + "help": "Controls how often the system polls provider APIs for batch job status in milliseconds (default: 2000). Use longer intervals to reduce API chatter, or shorter intervals for faster completion detection.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.remote.batch.timeoutMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Remote Batch Timeout (min)", + "help": "Sets the maximum wait time for a full embedding batch operation in minutes (default: 60). Increase for very large corpora or slower providers, and lower it to fail fast in automation-heavy flows.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.remote.batch.wait", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Remote Batch Wait for Completion", + "help": "Waits for batch embedding jobs to fully finish before the indexing operation completes. Keep this enabled for deterministic indexing state; disable only if you accept delayed consistency.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.remote.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Remote Embedding Headers", + "help": "Adds custom HTTP headers to remote embedding requests, merged with provider defaults. Use this for proxy auth and tenant routing headers, and keep values minimal to avoid leaking sensitive metadata.", + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.remote.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.sources", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Search Sources", + "help": "Chooses which sources are indexed: \"memory\" reads MEMORY.md + memory files, and \"sessions\" includes transcript history. Keep [\"memory\"] unless you need recall from prior chat transcripts.", + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.sources.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.store", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.store.driver", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.store.path", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Memory Search Index Path", + "help": "Sets where the SQLite memory index is stored on disk for each agent. Keep the default `~/.openclaw/memory/{agentId}.sqlite` unless you need custom storage placement or backup policy alignment.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.store.vector", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.store.vector.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Memory Search Vector Index", + "help": "Enables the sqlite-vec extension used for vector similarity queries in memory search (default: true). Keep this enabled for normal semantic recall; disable only for debugging or fallback-only operation.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.store.vector.extensionPath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Memory Search Vector Extension Path", + "help": "Overrides the auto-discovered sqlite-vec extension library path (`.dylib`, `.so`, or `.dll`). Use this when your runtime cannot find sqlite-vec automatically or you pin a known-good build.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.sync", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.sync.intervalMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.sync.onSearch", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Index on Search (Lazy)", + "help": "Uses lazy sync by scheduling reindex on search after content changes are detected. Keep enabled for lower idle overhead, or disable if you require pre-synced indexes before any query.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.sync.onSessionStart", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "storage" + ], + "label": "Index on Session Start", + "help": "Triggers a memory index sync when a session starts so early turns see fresh memory content. Keep enabled when startup freshness matters more than initial turn latency.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.sync.sessions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.memorySearch.sync.sessions.deltaBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Delta Bytes", + "help": "Requires at least this many newly appended bytes before session transcript changes trigger reindex (default: 100000). Increase to reduce frequent small reindexes, or lower for faster transcript freshness.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.sync.sessions.deltaMessages", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Delta Messages", + "help": "Requires at least this many appended transcript messages before reindex is triggered (default: 50). Lower this for near-real-time transcript recall, or raise it to reduce indexing churn.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.sync.sessions.postCompactionForce", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Force Reindex After Compaction", + "help": "Forces a session memory-search reindex after compaction-triggered transcript updates (default: true). Keep enabled when compacted summaries must be immediately searchable, or disable to reduce write-time indexing pressure.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.sync.watch", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Watch Memory Files", + "help": "Watches memory files and schedules index updates from file-change events (chokidar). Enable for near-real-time freshness; disable on very large workspaces if watch churn is too noisy.", + "hasChildren": false + }, + { + "path": "agents.defaults.memorySearch.sync.watchDebounceMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "performance" + ], + "label": "Memory Watch Debounce (ms)", + "help": "Debounce window in milliseconds for coalescing rapid file-watch events before reindex runs. Increase to reduce churn on frequently-written files, or lower for faster freshness.", + "hasChildren": false + }, + { + "path": "agents.defaults.model", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.model.fallbacks", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models", + "reliability" + ], + "label": "Model Fallbacks", + "help": "Ordered fallback models (provider/model). Used when the primary model fails.", + "hasChildren": true + }, + { + "path": "agents.defaults.model.fallbacks.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.model.primary", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Primary Model", + "help": "Primary model (provider/model).", + "hasChildren": false + }, + { + "path": "agents.defaults.models", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Models", + "help": "Configured model catalog (keys are full provider/model IDs).", + "hasChildren": true + }, + { + "path": "agents.defaults.models.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.models.*.alias", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.models.*.params", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.models.*.params.*", + "kind": "core", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.models.*.streaming", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.pdfMaxBytesMb", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "PDF Max Size (MB)", + "help": "Maximum PDF file size in megabytes for the PDF tool (default: 10).", + "hasChildren": false + }, + { + "path": "agents.defaults.pdfMaxPages", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "PDF Max Pages", + "help": "Maximum number of PDF pages to process for the PDF tool (default: 20).", + "hasChildren": false + }, + { + "path": "agents.defaults.pdfModel", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.pdfModel.fallbacks", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "reliability" + ], + "label": "PDF Model Fallbacks", + "help": "Ordered fallback PDF models (provider/model).", + "hasChildren": true + }, + { + "path": "agents.defaults.pdfModel.fallbacks.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.pdfModel.primary", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "PDF Model", + "help": "Optional PDF model (provider/model) for the PDF analysis tool. Defaults to imageModel, then session model.", + "hasChildren": false + }, + { + "path": "agents.defaults.repoRoot", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Repo Root", + "help": "Optional repository root shown in the system prompt runtime line (overrides auto-detect).", + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.backend", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.browser.allowHostControl", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.autoStart", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.autoStartTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.binds", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.browser.binds.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.cdpPort", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.cdpSourceRange", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Sandbox Browser CDP Source Port Range", + "help": "Optional CIDR allowlist for container-edge CDP ingress (for example 172.21.0.1/32).", + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.containerPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.enableNoVnc", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.headless", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.image", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.network", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Sandbox Browser Network", + "help": "Docker network for sandbox browser containers (default: openclaw-sandbox-browser). Avoid bridge if you need stricter isolation.", + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.noVncPort", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.browser.vncPort", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.docker.apparmorProfile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.binds", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.docker.binds.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.capDrop", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.docker.capDrop.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.containerPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.cpus", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "advanced", + "security", + "storage" + ], + "label": "Sandbox Docker Allow Container Namespace Join", + "help": "DANGEROUS break-glass override that allows sandbox Docker network mode container:. This joins another container namespace and weakens sandbox isolation.", + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.dangerouslyAllowExternalBindSources", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.dangerouslyAllowReservedContainerTargets", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.dns", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.docker.dns.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.env", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.docker.env.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.extraHosts", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.docker.extraHosts.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.image", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.memory", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.memorySwap", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.network", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.pidsLimit", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.readOnlyRoot", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.seccompProfile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.setupCommand", + "kind": "core", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.tmpfs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.docker.tmpfs.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.ulimits", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.docker.ulimits.*", + "kind": "core", + "type": [ + "number", + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.docker.ulimits.*.hard", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.ulimits.*.soft", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.user", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.docker.workdir", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.perSession", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.prune", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.prune.idleHours", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.prune.maxAgeDays", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.scope", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.sessionToolsVisibility", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.ssh.certificateData", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "security", + "storage" + ], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.ssh.certificateData.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.certificateData.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.certificateData.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.certificateFile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.command", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.identityData", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "security", + "storage" + ], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.ssh.identityData.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.identityData.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.identityData.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.identityFile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.knownHostsData", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "security", + "storage" + ], + "hasChildren": true + }, + { + "path": "agents.defaults.sandbox.ssh.knownHostsData.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.knownHostsData.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.knownHostsData.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.knownHostsFile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.strictHostKeyChecking", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.target", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.updateHostKeys", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.ssh.workspaceRoot", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.workspaceAccess", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.sandbox.workspaceRoot", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.skipBootstrap", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.subagents", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.subagents.announceTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.subagents.archiveAfterMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.subagents.maxChildrenPerAgent", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.subagents.maxConcurrent", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.subagents.maxSpawnDepth", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.subagents.model", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.subagents.model.fallbacks", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.defaults.subagents.model.fallbacks.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.subagents.model.primary", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.subagents.runTimeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.subagents.thinking", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.thinkingDefault", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.timeFormat", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.typingIntervalSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.typingMode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.userTimezone", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.verboseDefault", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.defaults.workspace", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Workspace", + "help": "Default workspace path exposed to agent runtime tools for filesystem context and repo-aware behavior. Set this explicitly when running from wrappers so path resolution stays deterministic.", + "hasChildren": false + }, + { + "path": "agents.list", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent List", + "help": "Explicit list of configured agents with IDs and optional overrides for model, tools, identity, and workspace. Keep IDs stable over time so bindings, approvals, and session routing remain deterministic.", + "hasChildren": true + }, + { + "path": "agents.list.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.agentDir", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.default", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.groupChat", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.groupChat.historyLimit", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.groupChat.mentionPatterns", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.groupChat.mentionPatterns.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.heartbeat.accountId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.ackMaxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.activeHours", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.heartbeat.activeHours.end", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.activeHours.start", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.activeHours.timezone", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.directPolicy", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "automation", + "storage" + ], + "label": "Heartbeat Direct Policy", + "help": "Per-agent override for heartbeat direct/DM delivery policy; use \"block\" for agents that should only send heartbeat alerts to non-DM destinations.", + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.every", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.includeReasoning", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.isolatedSession", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.lightContext", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.prompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.session", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.suppressToolErrorWarnings", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "label": "Agent Heartbeat Suppress Tool Error Warnings", + "help": "Suppress tool error warning payloads during heartbeat runs.", + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.target", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "help": "Delivery target (\"last\", \"none\", or a channel id). Known channels: telegram, whatsapp, discord, irc, googlechat, slack, signal, imessage, line, bluebubbles, feishu, matrix, mattermost, msteams, nextcloud-talk, nostr, synology-chat, tlon, twitch, zalo, zalouser.", + "hasChildren": false + }, + { + "path": "agents.list.*.heartbeat.to", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.humanDelay", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.humanDelay.maxMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.humanDelay.minMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.humanDelay.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.identity", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.identity.avatar", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Identity Avatar", + "help": "Agent avatar (workspace-relative path, http(s) URL, or data URI).", + "hasChildren": false + }, + { + "path": "agents.list.*.identity.emoji", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.identity.name", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.identity.theme", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.cache", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.cache.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.cache.maxEntries", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.chunking", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.chunking.overlap", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.chunking.tokens", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.experimental", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.experimental.sessionMemory", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.extraPaths", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.extraPaths.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.fallback", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.local", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.local.modelCacheDir", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.local.modelPath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.multimodal", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.multimodal.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.multimodal.maxFileBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.multimodal.modalities", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.multimodal.modalities.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.outputDimensionality", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.provider", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.query", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.query.hybrid", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.query.hybrid.candidateMultiplier", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.query.hybrid.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.query.hybrid.mmr", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.query.hybrid.mmr.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.query.hybrid.mmr.lambda", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.query.hybrid.temporalDecay", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.query.hybrid.temporalDecay.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.query.hybrid.temporalDecay.halfLifeDays", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.query.hybrid.textWeight", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.query.hybrid.vectorWeight", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.query.maxResults", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.query.minScore", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.remote", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.remote.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security" + ], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.remote.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.remote.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.remote.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.remote.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.remote.batch", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.remote.batch.concurrency", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.remote.batch.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.remote.batch.pollIntervalMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.remote.batch.timeoutMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.remote.batch.wait", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.remote.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.remote.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.sources", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.sources.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.store", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.store.driver", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.store.path", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.store.vector", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.store.vector.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.store.vector.extensionPath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.sync", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.sync.intervalMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.sync.onSearch", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.sync.onSessionStart", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.sync.sessions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.memorySearch.sync.sessions.deltaBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.sync.sessions.deltaMessages", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.sync.sessions.postCompactionForce", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.sync.watch", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.memorySearch.sync.watchDebounceMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.model", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.model.fallbacks", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.model.fallbacks.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.model.primary", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.name", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.params", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.params.*", + "kind": "core", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.runtime", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent Runtime", + "help": "Optional runtime descriptor for this agent. Use embedded for default OpenClaw execution or acp for external ACP harness defaults.", + "hasChildren": true + }, + { + "path": "agents.list.*.runtime.acp", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent ACP Runtime", + "help": "ACP runtime defaults for this agent when runtime.type=acp. Binding-level ACP overrides still take precedence per conversation.", + "hasChildren": true + }, + { + "path": "agents.list.*.runtime.acp.agent", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent ACP Harness Agent", + "help": "Optional ACP harness agent id to use for this OpenClaw agent (for example codex, claude).", + "hasChildren": false + }, + { + "path": "agents.list.*.runtime.acp.backend", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent ACP Backend", + "help": "Optional ACP backend override for this agent's ACP sessions (falls back to global acp.backend).", + "hasChildren": false + }, + { + "path": "agents.list.*.runtime.acp.cwd", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent ACP Working Directory", + "help": "Optional default working directory for this agent's ACP sessions.", + "hasChildren": false + }, + { + "path": "agents.list.*.runtime.acp.mode", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "persistent", + "oneshot" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent ACP Mode", + "help": "Optional ACP session mode default for this agent (persistent or oneshot).", + "hasChildren": false + }, + { + "path": "agents.list.*.runtime.type", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent Runtime Type", + "help": "Runtime type for this agent: \"embedded\" (default OpenClaw runtime) or \"acp\" (ACP harness defaults).", + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.backend", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.browser.allowHostControl", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.autoStart", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.autoStartTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.binds", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.browser.binds.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.cdpPort", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.cdpSourceRange", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Agent Sandbox Browser CDP Source Port Range", + "help": "Per-agent override for CDP source CIDR allowlist.", + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.containerPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.enableNoVnc", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.headless", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.image", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.network", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Agent Sandbox Browser Network", + "help": "Per-agent override for sandbox browser Docker network.", + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.noVncPort", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.browser.vncPort", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.docker.apparmorProfile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.binds", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.docker.binds.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.capDrop", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.docker.capDrop.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.containerPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.cpus", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.dangerouslyAllowContainerNamespaceJoin", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "advanced", + "security", + "storage" + ], + "label": "Agent Sandbox Docker Allow Container Namespace Join", + "help": "Per-agent DANGEROUS override for container namespace joins in sandbox Docker network mode.", + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.dangerouslyAllowExternalBindSources", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.dangerouslyAllowReservedContainerTargets", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.dns", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.docker.dns.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.env", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.docker.env.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.extraHosts", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.docker.extraHosts.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.image", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.memory", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.memorySwap", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.network", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.pidsLimit", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.readOnlyRoot", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.seccompProfile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.setupCommand", + "kind": "core", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.tmpfs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.docker.tmpfs.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.ulimits", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.docker.ulimits.*", + "kind": "core", + "type": [ + "number", + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.docker.ulimits.*.hard", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.ulimits.*.soft", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.user", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.docker.workdir", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.perSession", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.prune", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.prune.idleHours", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.prune.maxAgeDays", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.scope", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.sessionToolsVisibility", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.ssh.certificateData", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "security", + "storage" + ], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.ssh.certificateData.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.certificateData.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.certificateData.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.certificateFile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.command", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.identityData", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "security", + "storage" + ], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.ssh.identityData.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.identityData.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.identityData.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.identityFile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.knownHostsData", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "security", + "storage" + ], + "hasChildren": true + }, + { + "path": "agents.list.*.sandbox.ssh.knownHostsData.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.knownHostsData.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.knownHostsData.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.knownHostsFile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.strictHostKeyChecking", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.target", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.updateHostKeys", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.ssh.workspaceRoot", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.workspaceAccess", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.sandbox.workspaceRoot", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.skills", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent Skill Filter", + "help": "Optional allowlist of skills for this agent (omit = all skills; empty = no skills).", + "hasChildren": true + }, + { + "path": "agents.list.*.skills.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.subagents", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.subagents.allowAgents", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.subagents.allowAgents.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.subagents.model", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.subagents.model.fallbacks", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.subagents.model.fallbacks.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.subagents.model.primary", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.subagents.thinking", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.alsoAllow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Agent Tool Allowlist Additions", + "help": "Per-agent additive allowlist for tools on top of global and profile policy. Keep narrow to avoid accidental privilege expansion on specialized agents.", + "hasChildren": true + }, + { + "path": "agents.list.*.tools.alsoAllow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.byProvider", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Agent Tool Policy by Provider", + "help": "Per-agent provider-specific tool policy overrides for channel-scoped capability control. Use this when a single agent needs tighter restrictions on one provider than others.", + "hasChildren": true + }, + { + "path": "agents.list.*.tools.byProvider.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.byProvider.*.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.byProvider.*.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.byProvider.*.alsoAllow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.byProvider.*.alsoAllow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.byProvider.*.deny", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.byProvider.*.deny.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.byProvider.*.profile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.deny", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.deny.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.elevated", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.elevated.allowFrom", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.elevated.allowFrom.*", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.elevated.allowFrom.*.*", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.elevated.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.exec.applyPatch", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.exec.applyPatch.allowModels", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.exec.applyPatch.allowModels.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.applyPatch.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.applyPatch.workspaceOnly", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.approvalRunningNoticeMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.ask", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "off", + "on-miss", + "always" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.backgroundMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.cleanupMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.host", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "sandbox", + "gateway", + "node" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.node", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.notifyOnExit", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.notifyOnExitEmptySuccess", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.pathPrepend", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.exec.pathPrepend.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.safeBinProfiles", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.exec.safeBinProfiles.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.exec.safeBinProfiles.*.allowedValueFlags", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.exec.safeBinProfiles.*.allowedValueFlags.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.safeBinProfiles.*.deniedFlags", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.exec.safeBinProfiles.*.deniedFlags.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.safeBinProfiles.*.maxPositional", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.safeBinProfiles.*.minPositional", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.safeBins", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.exec.safeBins.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.safeBinTrustedDirs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.exec.safeBinTrustedDirs.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.security", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "deny", + "allowlist", + "full" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.exec.timeoutSec", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.fs", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.fs.workspaceOnly", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.loopDetection", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.loopDetection.criticalThreshold", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.loopDetection.detectors", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.loopDetection.detectors.genericRepeat", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.loopDetection.detectors.knownPollNoProgress", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.loopDetection.detectors.pingPong", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.loopDetection.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.loopDetection.globalCircuitBreakerThreshold", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.loopDetection.historySize", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.loopDetection.warningThreshold", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.profile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Agent Tool Profile", + "help": "Per-agent override for tool profile selection when one agent needs a different capability baseline. Use this sparingly so policy differences across agents stay intentional and reviewable.", + "hasChildren": false + }, + { + "path": "agents.list.*.tools.sandbox", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.sandbox.tools", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.sandbox.tools.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.sandbox.tools.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.sandbox.tools.alsoAllow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.sandbox.tools.alsoAllow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.tools.sandbox.tools.deny", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "agents.list.*.tools.sandbox.tools.deny.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "agents.list.*.workspace", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "approvals", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Approvals", + "help": "Approval routing controls for forwarding exec approval requests to chat destinations outside the originating session. Keep this disabled unless operators need explicit out-of-band approval visibility.", + "hasChildren": true + }, + { + "path": "approvals.exec", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Exec Approval Forwarding", + "help": "Groups exec-approval forwarding behavior including enablement, routing mode, filters, and explicit targets. Configure here when approval prompts must reach operational channels instead of only the origin thread.", + "hasChildren": true + }, + { + "path": "approvals.exec.agentFilter", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Approval Agent Filter", + "help": "Optional allowlist of agent IDs eligible for forwarded approvals, for example `[\"primary\", \"ops-agent\"]`. Use this to limit forwarding blast radius and avoid notifying channels for unrelated agents.", + "hasChildren": true + }, + { + "path": "approvals.exec.agentFilter.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "approvals.exec.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Forward Exec Approvals", + "help": "Enables forwarding of exec approval requests to configured delivery destinations (default: false). Keep disabled in low-risk setups and enable only when human approval responders need channel-visible prompts.", + "hasChildren": false + }, + { + "path": "approvals.exec.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Approval Forwarding Mode", + "help": "Controls where approval prompts are sent: \"session\" uses origin chat, \"targets\" uses configured targets, and \"both\" sends to both paths. Use \"session\" as baseline and expand only when operational workflow requires redundancy.", + "hasChildren": false + }, + { + "path": "approvals.exec.sessionFilter", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Approval Session Filter", + "help": "Optional session-key filters matched as substring or regex-style patterns, for example `[\"discord:\", \"^agent:ops:\"]`. Use narrow patterns so only intended approval contexts are forwarded to shared destinations.", + "hasChildren": true + }, + { + "path": "approvals.exec.sessionFilter.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "approvals.exec.targets", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Approval Forwarding Targets", + "help": "Explicit delivery targets used when forwarding mode includes targets, each with channel and destination details. Keep target lists least-privilege and validate each destination before enabling broad forwarding.", + "hasChildren": true + }, + { + "path": "approvals.exec.targets.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "approvals.exec.targets.*.accountId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Approval Target Account ID", + "help": "Optional account selector for multi-account channel setups when approvals must route through a specific account context. Use this only when the target channel has multiple configured identities.", + "hasChildren": false + }, + { + "path": "approvals.exec.targets.*.channel", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Approval Target Channel", + "help": "Channel/provider ID used for forwarded approval delivery, such as discord, slack, or a plugin channel id. Use valid channel IDs only so approvals do not silently fail due to unknown routes.", + "hasChildren": false + }, + { + "path": "approvals.exec.targets.*.threadId", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Approval Target Thread ID", + "help": "Optional thread/topic target for channels that support threaded delivery of forwarded approvals. Use this to keep approval traffic contained in operational threads instead of main channels.", + "hasChildren": false + }, + { + "path": "approvals.exec.targets.*.to", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Approval Target Destination", + "help": "Destination identifier inside the target channel (channel ID, user ID, or thread root depending on provider). Verify semantics per provider because destination format differs across channel integrations.", + "hasChildren": false + }, + { + "path": "audio", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Audio", + "help": "Global audio ingestion settings used before higher-level tools process speech or media content. Configure this when you need deterministic transcription behavior for voice notes and clips.", + "hasChildren": true + }, + { + "path": "audio.transcription", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Audio Transcription", + "help": "Command-based transcription settings for converting audio files into text before agent handling. Keep a simple, deterministic command path here so failures are easy to diagnose in logs.", + "hasChildren": true + }, + { + "path": "audio.transcription.command", + "kind": "core", + "type": "array", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Audio Transcription Command", + "help": "Executable + args used to transcribe audio (first token must be a safe binary/path), for example `[\"whisper-cli\", \"--model\", \"small\", \"{input}\"]`. Prefer a pinned command so runtime environments behave consistently.", + "hasChildren": true + }, + { + "path": "audio.transcription.command.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "audio.transcription.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance" + ], + "label": "Audio Transcription Timeout (sec)", + "help": "Maximum time allowed for the transcription command to finish before it is aborted. Increase this for longer recordings, and keep it tight in latency-sensitive deployments.", + "hasChildren": false + }, + { + "path": "auth", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Auth", + "help": "Authentication profile root used for multi-profile provider credentials and cooldown-based failover ordering. Keep profiles minimal and explicit so automatic failover behavior stays auditable.", + "hasChildren": true + }, + { + "path": "auth.cooldowns", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "auth" + ], + "label": "Auth Cooldowns", + "help": "Cooldown/backoff controls for temporary profile suppression after billing-related failures and retry windows. Use these to prevent rapid re-selection of profiles that are still blocked.", + "hasChildren": true + }, + { + "path": "auth.cooldowns.billingBackoffHours", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "auth", + "reliability" + ], + "label": "Billing Backoff (hours)", + "help": "Base backoff (hours) when a profile fails due to billing/insufficient credits (default: 5).", + "hasChildren": false + }, + { + "path": "auth.cooldowns.billingBackoffHoursByProvider", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "auth", + "reliability" + ], + "label": "Billing Backoff Overrides", + "help": "Optional per-provider overrides for billing backoff (hours).", + "hasChildren": true + }, + { + "path": "auth.cooldowns.billingBackoffHoursByProvider.*", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "auth.cooldowns.billingMaxHours", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "auth", + "performance" + ], + "label": "Billing Backoff Cap (hours)", + "help": "Cap (hours) for billing backoff (default: 24).", + "hasChildren": false + }, + { + "path": "auth.cooldowns.failureWindowHours", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "auth" + ], + "label": "Failover Window (hours)", + "help": "Failure window (hours) for backoff counters (default: 24).", + "hasChildren": false + }, + { + "path": "auth.order", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "auth" + ], + "label": "Auth Profile Order", + "help": "Ordered auth profile IDs per provider (used for automatic failover).", + "hasChildren": true + }, + { + "path": "auth.order.*", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "auth.order.*.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "auth.profiles", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "auth", + "storage" + ], + "label": "Auth Profiles", + "help": "Named auth profiles (provider + mode + optional email).", + "hasChildren": true + }, + { + "path": "auth.profiles.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "auth.profiles.*.email", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "auth.profiles.*.mode", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "auth.profiles.*.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "bindings", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Bindings", + "help": "Top-level binding rules for routing and persistent ACP conversation ownership. Use type=route for normal routing and type=acp for persistent ACP harness bindings.", + "hasChildren": true + }, + { + "path": "bindings.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "bindings.*.acp", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Binding Overrides", + "help": "Optional per-binding ACP overrides for bindings[].type=acp. This layer overrides agents.list[].runtime.acp defaults for the matched conversation.", + "hasChildren": true + }, + { + "path": "bindings.*.acp.backend", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Binding Backend", + "help": "ACP backend override for this binding (falls back to agent runtime ACP backend, then global acp.backend).", + "hasChildren": false + }, + { + "path": "bindings.*.acp.cwd", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Binding Working Directory", + "help": "Working directory override for ACP sessions created from this binding.", + "hasChildren": false + }, + { + "path": "bindings.*.acp.label", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Binding Label", + "help": "Human-friendly label for ACP status/diagnostics in this bound conversation.", + "hasChildren": false + }, + { + "path": "bindings.*.acp.mode", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "persistent", + "oneshot" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACP Binding Mode", + "help": "ACP session mode override for this binding (persistent or oneshot).", + "hasChildren": false + }, + { + "path": "bindings.*.agentId", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Agent ID", + "help": "Target agent ID that receives traffic when the corresponding binding match rule is satisfied. Use valid configured agent IDs only so routing does not fail at runtime.", + "hasChildren": false + }, + { + "path": "bindings.*.comment", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "bindings.*.match", + "kind": "core", + "type": "object", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Match Rule", + "help": "Match rule object for deciding when a binding applies, including channel and optional account/peer constraints. Keep rules narrow to avoid accidental agent takeover across contexts.", + "hasChildren": true + }, + { + "path": "bindings.*.match.accountId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Account ID", + "help": "Optional account selector for multi-account channel setups so the binding applies only to one identity. Use this when account scoping is required for the route and leave unset otherwise.", + "hasChildren": false + }, + { + "path": "bindings.*.match.channel", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Channel", + "help": "Channel/provider identifier this binding applies to, such as `telegram`, `discord`, or a plugin channel ID. Use the configured channel key exactly so binding evaluation works reliably.", + "hasChildren": false + }, + { + "path": "bindings.*.match.guildId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Guild ID", + "help": "Optional Discord-style guild/server ID constraint for binding evaluation in multi-server deployments. Use this when the same peer identifiers can appear across different guilds.", + "hasChildren": false + }, + { + "path": "bindings.*.match.peer", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Peer Match", + "help": "Optional peer matcher for specific conversations including peer kind and peer id. Use this when only one direct/group/channel target should be pinned to an agent.", + "hasChildren": true + }, + { + "path": "bindings.*.match.peer.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Peer ID", + "help": "Conversation identifier used with peer matching, such as a chat ID, channel ID, or group ID from the provider. Keep this exact to avoid silent non-matches.", + "hasChildren": false + }, + { + "path": "bindings.*.match.peer.kind", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Peer Kind", + "help": "Peer conversation type: \"direct\", \"group\", \"channel\", or legacy \"dm\" (deprecated alias for direct). Prefer \"direct\" for new configs and keep kind aligned with channel semantics.", + "hasChildren": false + }, + { + "path": "bindings.*.match.roles", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Roles", + "help": "Optional role-based filter list used by providers that attach roles to chat context. Use this to route privileged or operational role traffic to specialized agents.", + "hasChildren": true + }, + { + "path": "bindings.*.match.roles.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "bindings.*.match.teamId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Team ID", + "help": "Optional team/workspace ID constraint used by providers that scope chats under teams. Add this when you need bindings isolated to one workspace context.", + "hasChildren": false + }, + { + "path": "bindings.*.type", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Binding Type", + "help": "Binding kind. Use \"route\" (or omit for legacy route entries) for normal routing, and \"acp\" for persistent ACP conversation bindings.", + "hasChildren": false + }, + { + "path": "broadcast", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Broadcast", + "help": "Broadcast routing map for sending the same outbound message to multiple peer IDs per source conversation. Keep this minimal and audited because one source can fan out to many destinations.", + "hasChildren": true + }, + { + "path": "broadcast.*", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Broadcast Destination List", + "help": "Per-source broadcast destination list where each key is a source peer ID and the value is an array of destination peer IDs. Keep lists intentional to avoid accidental message amplification.", + "hasChildren": true + }, + { + "path": "broadcast.*.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "broadcast.strategy", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "parallel", + "sequential" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Broadcast Strategy", + "help": "Delivery order for broadcast fan-out: \"parallel\" sends to all targets concurrently, while \"sequential\" sends one-by-one. Use \"parallel\" for speed and \"sequential\" for stricter ordering/backpressure control.", + "hasChildren": false + }, + { + "path": "browser", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Browser", + "help": "Browser runtime controls for local or remote CDP attachment, profile routing, and screenshot/snapshot behavior. Keep defaults unless your automation workflow requires custom browser transport settings.", + "hasChildren": true + }, + { + "path": "browser.attachOnly", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Browser Attach-only Mode", + "help": "Restricts browser mode to attach-only behavior without starting local browser processes. Use this when all browser sessions are externally managed by a remote CDP provider.", + "hasChildren": false + }, + { + "path": "browser.cdpPortRangeStart", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Browser CDP Port Range Start", + "help": "Starting local CDP port used for auto-allocated browser profile ports. Increase this when host-level port defaults conflict with other local services.", + "hasChildren": false + }, + { + "path": "browser.cdpUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Browser CDP URL", + "help": "Remote CDP websocket URL used to attach to an externally managed browser instance. Use this for centralized browser hosts and keep URL access restricted to trusted network paths.", + "hasChildren": false + }, + { + "path": "browser.color", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Browser Accent Color", + "help": "Default accent color used for browser profile/UI cues where colored identity hints are displayed. Use consistent colors to help operators identify active browser profile context quickly.", + "hasChildren": false + }, + { + "path": "browser.defaultProfile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Browser Default Profile", + "help": "Default browser profile name selected when callers do not explicitly choose a profile. Use a stable low-privilege profile as the default to reduce accidental cross-context state use.", + "hasChildren": false + }, + { + "path": "browser.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Browser Enabled", + "help": "Enables browser capability wiring in the gateway so browser tools and CDP-driven workflows can run. Disable when browser automation is not needed to reduce surface area and startup work.", + "hasChildren": false + }, + { + "path": "browser.evaluateEnabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Browser Evaluate Enabled", + "help": "Enables browser-side evaluate helpers for runtime script evaluation capabilities where supported. Keep disabled unless your workflows require evaluate semantics beyond snapshots/navigation.", + "hasChildren": false + }, + { + "path": "browser.executablePath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Browser Executable Path", + "help": "Explicit browser executable path when auto-discovery is insufficient for your host environment. Use absolute stable paths so launch behavior stays deterministic across restarts.", + "hasChildren": false + }, + { + "path": "browser.extraArgs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "browser.extraArgs.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "browser.headless", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Browser Headless Mode", + "help": "Forces browser launch in headless mode when the local launcher starts browser instances. Keep headless enabled for server environments and disable only when visible UI debugging is required.", + "hasChildren": false + }, + { + "path": "browser.noSandbox", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Browser No-Sandbox Mode", + "help": "Disables Chromium sandbox isolation flags for environments where sandboxing fails at runtime. Keep this off whenever possible because process isolation protections are reduced.", + "hasChildren": false + }, + { + "path": "browser.profiles", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Browser Profiles", + "help": "Named browser profile connection map used for explicit routing to CDP ports or URLs with optional metadata. Keep profile names consistent and avoid overlapping endpoint definitions.", + "hasChildren": true + }, + { + "path": "browser.profiles.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "browser.profiles.*.attachOnly", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Browser Profile Attach-only Mode", + "help": "Per-profile attach-only override that skips local browser launch and only attaches to an existing CDP endpoint. Useful when one profile is externally managed but others are locally launched.", + "hasChildren": false + }, + { + "path": "browser.profiles.*.cdpPort", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Browser Profile CDP Port", + "help": "Per-profile local CDP port used when connecting to browser instances by port instead of URL. Use unique ports per profile to avoid connection collisions.", + "hasChildren": false + }, + { + "path": "browser.profiles.*.cdpUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Browser Profile CDP URL", + "help": "Per-profile CDP websocket URL used for explicit remote browser routing by profile name. Use this when profile connections terminate on remote hosts or tunnels.", + "hasChildren": false + }, + { + "path": "browser.profiles.*.color", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Browser Profile Accent Color", + "help": "Per-profile accent color for visual differentiation in dashboards and browser-related UI hints. Use distinct colors for high-signal operator recognition of active profiles.", + "hasChildren": false + }, + { + "path": "browser.profiles.*.driver", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Browser Profile Driver", + "help": "Per-profile browser driver mode. Use \"openclaw\" (or legacy \"clawd\") for CDP-based profiles, or use \"existing-session\" for host-local Chrome DevTools MCP attachment.", + "hasChildren": false + }, + { + "path": "browser.profiles.*.userDataDir", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Browser Profile User Data Dir", + "help": "Per-profile Chromium user data directory for existing-session attachment through Chrome DevTools MCP. Use this for host-local Brave, Edge, Chromium, or non-default Chrome profiles when the built-in auto-connect path would pick the wrong browser data directory.", + "hasChildren": false + }, + { + "path": "browser.remoteCdpHandshakeTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Remote CDP Handshake Timeout (ms)", + "help": "Timeout in milliseconds for post-connect CDP handshake readiness checks against remote browser targets. Raise this for slow-start remote browsers and lower to fail fast in automation loops.", + "hasChildren": false + }, + { + "path": "browser.remoteCdpTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Remote CDP Timeout (ms)", + "help": "Timeout in milliseconds for connecting to a remote CDP endpoint before failing the browser attach attempt. Increase for high-latency tunnels, or lower for faster failure detection.", + "hasChildren": false + }, + { + "path": "browser.snapshotDefaults", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Browser Snapshot Defaults", + "help": "Default snapshot capture configuration used when callers do not provide explicit snapshot options. Tune this for consistent capture behavior across channels and automation paths.", + "hasChildren": true + }, + { + "path": "browser.snapshotDefaults.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Browser Snapshot Mode", + "help": "Default snapshot extraction mode controlling how page content is transformed for agent consumption. Choose the mode that balances readability, fidelity, and token footprint for your workflows.", + "hasChildren": false + }, + { + "path": "browser.ssrfPolicy", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Browser SSRF Policy", + "help": "Server-side request forgery guardrail settings for browser/network fetch paths that could reach internal hosts. Keep restrictive defaults in production and open only explicitly approved targets.", + "hasChildren": true + }, + { + "path": "browser.ssrfPolicy.allowedHostnames", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Browser Allowed Hostnames", + "help": "Explicit hostname allowlist exceptions for SSRF policy checks on browser/network requests. Keep this list minimal and review entries regularly to avoid stale broad access.", + "hasChildren": true + }, + { + "path": "browser.ssrfPolicy.allowedHostnames.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "browser.ssrfPolicy.allowPrivateNetwork", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Browser Allow Private Network", + "help": "Legacy alias for browser.ssrfPolicy.dangerouslyAllowPrivateNetwork. Prefer the dangerously-named key so risk intent is explicit.", + "hasChildren": false + }, + { + "path": "browser.ssrfPolicy.dangerouslyAllowPrivateNetwork", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "advanced", + "security" + ], + "label": "Browser Dangerously Allow Private Network", + "help": "Allows access to private-network address ranges from browser tooling. Default is enabled for trusted-network operator setups; disable to enforce strict public-only resolution checks.", + "hasChildren": false + }, + { + "path": "browser.ssrfPolicy.hostnameAllowlist", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Browser Hostname Allowlist", + "help": "Legacy/alternate hostname allowlist field used by SSRF policy consumers for explicit host exceptions. Use stable exact hostnames and avoid wildcard-like broad patterns.", + "hasChildren": true + }, + { + "path": "browser.ssrfPolicy.hostnameAllowlist.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "canvasHost", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Canvas Host", + "help": "Canvas host settings for serving canvas assets and local live-reload behavior used by canvas-enabled workflows. Keep disabled unless canvas-hosted assets are actively used.", + "hasChildren": true + }, + { + "path": "canvasHost.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Canvas Host Enabled", + "help": "Enables the canvas host server process and routes for serving canvas files. Keep disabled when canvas workflows are inactive to reduce exposed local services.", + "hasChildren": false + }, + { + "path": "canvasHost.liveReload", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "reliability" + ], + "label": "Canvas Host Live Reload", + "help": "Enables automatic live-reload behavior for canvas assets during development workflows. Keep disabled in production-like environments where deterministic output is preferred.", + "hasChildren": false + }, + { + "path": "canvasHost.port", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Canvas Host Port", + "help": "TCP port used by the canvas host HTTP server when canvas hosting is enabled. Choose a non-conflicting port and align firewall/proxy policy accordingly.", + "hasChildren": false + }, + { + "path": "canvasHost.root", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Canvas Host Root Directory", + "help": "Filesystem root directory served by canvas host for canvas content and static assets. Use a dedicated directory and avoid broad repo roots for least-privilege file exposure.", + "hasChildren": false + }, + { + "path": "channels", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Channels", + "help": "Channel provider configurations plus shared defaults that control access policies, heartbeat visibility, and per-surface behavior. Keep defaults centralized and override per provider only where required.", + "hasChildren": true + }, + { + "path": "channels.bluebubbles", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "BlueBubbles", + "help": "iMessage via the BlueBubbles mac app + REST API.", + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.allowPrivateNetwork", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.mediaLocalRoots", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.mediaLocalRoots.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.password", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.accounts.*.password.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.password.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.password.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.sendReadReceipts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.serverUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.accounts.*.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.actions.addParticipant", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions.edit", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions.leaveGroup", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions.removeParticipant", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions.renameGroup", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions.reply", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions.sendAttachment", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions.sendWithEffect", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions.setGroupIcon", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.actions.unsend", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.allowPrivateNetwork", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "BlueBubbles DM Policy", + "help": "Direct message access control (\"pairing\" recommended). \"open\" requires channels.bluebubbles.allowFrom=[\"*\"].", + "hasChildren": false + }, + { + "path": "channels.bluebubbles.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.mediaLocalRoots", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.mediaLocalRoots.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.mediaMaxMb", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.password", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.bluebubbles.password.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.password.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.password.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.sendReadReceipts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.serverUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.bluebubbles.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord", + "help": "very well supported right now.", + "hasChildren": true + }, + { + "path": "channels.discord.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.ackReaction", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.ackReactionScope", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "group-mentions", + "group-all", + "direct", + "all", + "off", + "none" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.actions.channelInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.channels", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.emojiUploads", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.events", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.memberInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.messages", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.moderation", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.permissions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.pins", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.polls", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.presence", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.roleInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.roles", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.search", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.stickers", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.stickerUploads", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.threads", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.actions.voiceStatus", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.activity", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.activityType", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.activityUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.agentComponents", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.agentComponents.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.allowBots", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.autoPresence", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.autoPresence.degradedText", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.autoPresence.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.autoPresence.exhaustedText", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.autoPresence.healthyText", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.autoPresence.intervalMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.autoPresence.minUpdateIntervalMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.commands", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.commands.native", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.commands.nativeSkills", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.dm", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.dm.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.dm.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.dm.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.dm.groupChannels", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.dm.groupChannels.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.dm.groupEnabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.dm.policy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.draftChunk", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.draftChunk.breakPreference", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.draftChunk.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.draftChunk.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.eventQueue", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.eventQueue.listenerTimeout", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.eventQueue.maxConcurrency", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.eventQueue.maxQueueSize", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.execApprovals", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.execApprovals.agentFilter", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.execApprovals.agentFilter.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.execApprovals.approvers", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.execApprovals.approvers.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.execApprovals.cleanupAfterResolve", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.execApprovals.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.execApprovals.sessionFilter", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.execApprovals.sessionFilter.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.execApprovals.target", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "dm", + "channel", + "both" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.allow", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.autoArchiveDuration", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "enumValues": [ + "60", + "1440", + "4320", + "10080" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.autoThread", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.ignoreOtherMentions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.includeThreadStarter", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.roles", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.roles.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.users", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.channels.*.users.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.ignoreOtherMentions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.reactionNotifications", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "own", + "all", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.roles", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.roles.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.slug", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.guilds.*.users", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.guilds.*.users.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.inboundWorker", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.inboundWorker.runTimeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.intents", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.intents.guildMembers", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.intents.presence", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.maxLinesPerMessage", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.pluralkit", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.pluralkit.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.pluralkit.token", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.pluralkit.token.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.pluralkit.token.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.pluralkit.token.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.proxy", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.retry", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.retry.attempts", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.retry.jitter", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.retry.maxDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.retry.minDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.slashCommand", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.slashCommand.ephemeral", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.status", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "online", + "dnd", + "idle", + "invisible" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.streaming", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "enumValues": [ + "off", + "partial", + "block", + "progress" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.streamMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "partial", + "block", + "off" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.threadBindings", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.threadBindings.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.threadBindings.idleHours", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.threadBindings.maxAgeHours", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.threadBindings.spawnAcpSessions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.threadBindings.spawnSubagentSessions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.token", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.token.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.token.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.token.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.ui", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.ui.components", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.ui.components.accentColor", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.autoJoin", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.autoJoin.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.autoJoin.*.channelId", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.autoJoin.*.guildId", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.daveEncryption", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.decryptionFailureTolerance", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.tts.auto", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "always", + "inbound", + "tagged" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge.lang", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge.outputFormat", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge.pitch", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge.proxy", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge.rate", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge.saveSubtitles", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge.timeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge.voice", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.edge.volume", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.apiKey", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "media", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.apiKey.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.apiKey.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.apiKey.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.applyTextNormalization", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "auto", + "on", + "off" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.baseUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.languageCode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.modelId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.seed", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.voiceId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings.similarityBoost", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings.speed", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings.stability", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings.style", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings.useSpeakerBoost", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.maxTextLength", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft.lang", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft.outputFormat", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft.pitch", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft.proxy", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft.rate", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft.saveSubtitles", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft.timeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft.voice", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.microsoft.volume", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.mode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "final", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.modelOverrides", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.tts.modelOverrides.allowModelId", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.modelOverrides.allowNormalization", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.modelOverrides.allowProvider", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.modelOverrides.allowSeed", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.modelOverrides.allowText", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.modelOverrides.allowVoice", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.modelOverrides.allowVoiceSettings", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.modelOverrides.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.openai", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.tts.openai.apiKey", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "media", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.discord.accounts.*.voice.tts.openai.apiKey.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.openai.apiKey.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.openai.apiKey.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.openai.baseUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.openai.instructions", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.openai.model", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.openai.speed", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.openai.voice", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.prefsPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.provider", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.summaryModel", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.accounts.*.voice.tts.timeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.ackReaction", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.ackReactionScope", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "group-mentions", + "group-all", + "direct", + "all", + "off", + "none" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.actions.channelInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.channels", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.emojiUploads", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.events", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.memberInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.messages", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.moderation", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.permissions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.pins", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.polls", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.presence", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.roleInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.roles", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.search", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.stickers", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.stickerUploads", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.threads", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.actions.voiceStatus", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.activity", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Presence Activity", + "help": "Discord presence activity text (defaults to custom status).", + "hasChildren": false + }, + { + "path": "channels.discord.activityType", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Presence Activity Type", + "help": "Discord presence activity type (0=Playing,1=Streaming,2=Listening,3=Watching,4=Custom,5=Competing).", + "hasChildren": false + }, + { + "path": "channels.discord.activityUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Presence Activity URL", + "help": "Discord presence streaming URL (required for activityType=1).", + "hasChildren": false + }, + { + "path": "channels.discord.agentComponents", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.agentComponents.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.allowBots", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "Discord Allow Bot Messages", + "help": "Allow bot-authored messages to trigger Discord replies (default: false). Set \"mentions\" to only accept bot messages that mention the bot.", + "hasChildren": false + }, + { + "path": "channels.discord.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.autoPresence", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.autoPresence.degradedText", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Auto Presence Degraded Text", + "help": "Optional custom status text while runtime/model availability is degraded or unknown (idle).", + "hasChildren": false + }, + { + "path": "channels.discord.autoPresence.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Auto Presence Enabled", + "help": "Enable automatic Discord bot presence updates based on runtime/model availability signals. When enabled: healthy=>online, degraded/unknown=>idle, exhausted/unavailable=>dnd.", + "hasChildren": false + }, + { + "path": "channels.discord.autoPresence.exhaustedText", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Auto Presence Exhausted Text", + "help": "Optional custom status text while runtime detects exhausted/unavailable model quota (dnd). Supports {reason} template placeholder.", + "hasChildren": false + }, + { + "path": "channels.discord.autoPresence.healthyText", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "reliability" + ], + "label": "Discord Auto Presence Healthy Text", + "help": "Optional custom status text while runtime is healthy (online). If omitted, falls back to static channels.discord.activity when set.", + "hasChildren": false + }, + { + "path": "channels.discord.autoPresence.intervalMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "Discord Auto Presence Check Interval (ms)", + "help": "How often to evaluate Discord auto-presence state in milliseconds (default: 30000).", + "hasChildren": false + }, + { + "path": "channels.discord.autoPresence.minUpdateIntervalMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "Discord Auto Presence Min Update Interval (ms)", + "help": "Minimum time between actual Discord presence update calls in milliseconds (default: 15000). Prevents status spam on noisy state changes.", + "hasChildren": false + }, + { + "path": "channels.discord.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.commands", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.commands.native", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Native Commands", + "help": "Override native commands for Discord (bool or \"auto\").", + "hasChildren": false + }, + { + "path": "channels.discord.commands.nativeSkills", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Native Skill Commands", + "help": "Override native skill commands for Discord (bool or \"auto\").", + "hasChildren": false + }, + { + "path": "channels.discord.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Config Writes", + "help": "Allow Discord to write config in response to channel events/commands (default: true).", + "hasChildren": false + }, + { + "path": "channels.discord.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.dm", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.dm.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.dm.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.dm.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.dm.groupChannels", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.dm.groupChannels.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.dm.groupEnabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.dm.policy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "Discord DM Policy", + "help": "Direct message access control (\"pairing\" recommended). \"open\" requires channels.discord.allowFrom=[\"*\"] (legacy: channels.discord.dm.allowFrom).", + "hasChildren": false + }, + { + "path": "channels.discord.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "Discord DM Policy", + "help": "Direct message access control (\"pairing\" recommended). \"open\" requires channels.discord.allowFrom=[\"*\"].", + "hasChildren": false + }, + { + "path": "channels.discord.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.draftChunk", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.draftChunk.breakPreference", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Draft Chunk Break Preference", + "help": "Preferred breakpoints for Discord draft chunks (paragraph | newline | sentence). Default: paragraph.", + "hasChildren": false + }, + { + "path": "channels.discord.draftChunk.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "Discord Draft Chunk Max Chars", + "help": "Target max size for a Discord stream preview chunk when channels.discord.streaming=\"block\" (default: 800; clamped to channels.discord.textChunkLimit).", + "hasChildren": false + }, + { + "path": "channels.discord.draftChunk.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Draft Chunk Min Chars", + "help": "Minimum chars before emitting a Discord stream preview update when channels.discord.streaming=\"block\" (default: 200).", + "hasChildren": false + }, + { + "path": "channels.discord.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.eventQueue", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.eventQueue.listenerTimeout", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "Discord EventQueue Listener Timeout (ms)", + "help": "Canonical Discord listener timeout control in ms for gateway normalization/enqueue handlers. Default is 120000 in OpenClaw; set per account via channels.discord.accounts..eventQueue.listenerTimeout.", + "hasChildren": false + }, + { + "path": "channels.discord.eventQueue.maxConcurrency", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "Discord EventQueue Max Concurrency", + "help": "Optional Discord EventQueue concurrency override (max concurrent handler executions). Set per account via channels.discord.accounts..eventQueue.maxConcurrency.", + "hasChildren": false + }, + { + "path": "channels.discord.eventQueue.maxQueueSize", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "Discord EventQueue Max Queue Size", + "help": "Optional Discord EventQueue capacity override (max queued events before backpressure). Set per account via channels.discord.accounts..eventQueue.maxQueueSize.", + "hasChildren": false + }, + { + "path": "channels.discord.execApprovals", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.execApprovals.agentFilter", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.execApprovals.agentFilter.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.execApprovals.approvers", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.execApprovals.approvers.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.execApprovals.cleanupAfterResolve", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.execApprovals.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.execApprovals.sessionFilter", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.execApprovals.sessionFilter.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.execApprovals.target", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "dm", + "channel", + "both" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.allow", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.autoArchiveDuration", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "enumValues": [ + "60", + "1440", + "4320", + "10080" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.autoThread", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.ignoreOtherMentions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.includeThreadStarter", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.roles", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.roles.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.channels.*.users", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.channels.*.users.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.ignoreOtherMentions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.reactionNotifications", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "own", + "all", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.roles", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.roles.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.slug", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.guilds.*.users", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.guilds.*.users.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.inboundWorker", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.inboundWorker.runTimeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "Discord Inbound Worker Timeout (ms)", + "help": "Optional queued Discord inbound worker timeout in ms. This is separate from Carbon listener timeouts; defaults to 1800000 and can be disabled with 0. Set per account via channels.discord.accounts..inboundWorker.runTimeoutMs.", + "hasChildren": false + }, + { + "path": "channels.discord.intents", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.intents.guildMembers", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Guild Members Intent", + "help": "Enable the Guild Members privileged intent. Must also be enabled in the Discord Developer Portal. Default: false.", + "hasChildren": false + }, + { + "path": "channels.discord.intents.presence", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Presence Intent", + "help": "Enable the Guild Presences privileged intent. Must also be enabled in the Discord Developer Portal. Allows tracking user activities (e.g. Spotify). Default: false.", + "hasChildren": false + }, + { + "path": "channels.discord.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.maxLinesPerMessage", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "Discord Max Lines Per Message", + "help": "Soft max line count per Discord message (default: 17).", + "hasChildren": false + }, + { + "path": "channels.discord.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.pluralkit", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.pluralkit.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord PluralKit Enabled", + "help": "Resolve PluralKit proxied messages and treat system members as distinct senders.", + "hasChildren": false + }, + { + "path": "channels.discord.pluralkit.token", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "label": "Discord PluralKit Token", + "help": "Optional PluralKit token for resolving private systems or members.", + "hasChildren": true + }, + { + "path": "channels.discord.pluralkit.token.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.pluralkit.token.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.pluralkit.token.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.proxy", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Proxy URL", + "help": "Proxy URL for Discord gateway + API requests (app-id lookup and allowlist resolution). Set per account via channels.discord.accounts..proxy.", + "hasChildren": false + }, + { + "path": "channels.discord.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.retry", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.retry.attempts", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "reliability" + ], + "label": "Discord Retry Attempts", + "help": "Max retry attempts for outbound Discord API calls (default: 3).", + "hasChildren": false + }, + { + "path": "channels.discord.retry.jitter", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "reliability" + ], + "label": "Discord Retry Jitter", + "help": "Jitter factor (0-1) applied to Discord retry delays.", + "hasChildren": false + }, + { + "path": "channels.discord.retry.maxDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance", + "reliability" + ], + "label": "Discord Retry Max Delay (ms)", + "help": "Maximum retry delay cap in ms for Discord outbound calls.", + "hasChildren": false + }, + { + "path": "channels.discord.retry.minDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "reliability" + ], + "label": "Discord Retry Min Delay (ms)", + "help": "Minimum retry delay in ms for Discord outbound calls.", + "hasChildren": false + }, + { + "path": "channels.discord.slashCommand", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.slashCommand.ephemeral", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.status", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "online", + "dnd", + "idle", + "invisible" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Presence Status", + "help": "Discord presence status (online, dnd, idle, invisible).", + "hasChildren": false + }, + { + "path": "channels.discord.streaming", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "enumValues": [ + "off", + "partial", + "block", + "progress" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Streaming Mode", + "help": "Unified Discord stream preview mode: \"off\" | \"partial\" | \"block\" | \"progress\". \"progress\" maps to \"partial\" on Discord. Legacy boolean/streamMode keys are auto-mapped.", + "hasChildren": false + }, + { + "path": "channels.discord.streamMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "partial", + "block", + "off" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Stream Mode (Legacy)", + "help": "Legacy Discord preview mode alias (off | partial | block); auto-migrated to channels.discord.streaming.", + "hasChildren": false + }, + { + "path": "channels.discord.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.threadBindings", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.threadBindings.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "storage" + ], + "label": "Discord Thread Binding Enabled", + "help": "Enable Discord thread binding features (/focus, bound-thread routing/delivery, and thread-bound subagent sessions). Overrides session.threadBindings.enabled when set.", + "hasChildren": false + }, + { + "path": "channels.discord.threadBindings.idleHours", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "storage" + ], + "label": "Discord Thread Binding Idle Timeout (hours)", + "help": "Inactivity window in hours for Discord thread-bound sessions (/focus and spawned thread sessions). Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set.", + "hasChildren": false + }, + { + "path": "channels.discord.threadBindings.maxAgeHours", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance", + "storage" + ], + "label": "Discord Thread Binding Max Age (hours)", + "help": "Optional hard max age in hours for Discord thread-bound sessions. Set 0 to disable hard cap (default: 0). Overrides session.threadBindings.maxAgeHours when set.", + "hasChildren": false + }, + { + "path": "channels.discord.threadBindings.spawnAcpSessions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "storage" + ], + "label": "Discord Thread-Bound ACP Spawn", + "help": "Allow /acp spawn to auto-create and bind Discord threads for ACP sessions (default: false; opt-in). Set true to enable thread-bound ACP spawns for this account/channel.", + "hasChildren": false + }, + { + "path": "channels.discord.threadBindings.spawnSubagentSessions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "storage" + ], + "label": "Discord Thread-Bound Subagent Spawn", + "help": "Allow subagent spawns with thread=true to auto-create and bind Discord threads (default: false; opt-in). Set true to enable thread-bound subagent spawns for this account/channel.", + "hasChildren": false + }, + { + "path": "channels.discord.token", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "label": "Discord Bot Token", + "help": "Discord bot token used for gateway and REST API authentication for this provider account. Keep this secret out of committed config and rotate immediately after any leak.", + "hasChildren": true + }, + { + "path": "channels.discord.token.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.token.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.token.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.ui", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.ui.components", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.ui.components.accentColor", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Component Accent Color", + "help": "Accent color for Discord component containers (hex). Set per account via channels.discord.accounts..ui.components.accentColor.", + "hasChildren": false + }, + { + "path": "channels.discord.voice", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.voice.autoJoin", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Voice Auto-Join", + "help": "Voice channels to auto-join on startup (list of guildId/channelId entries).", + "hasChildren": true + }, + { + "path": "channels.discord.voice.autoJoin.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.voice.autoJoin.*.channelId", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.autoJoin.*.guildId", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.daveEncryption", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Voice DAVE Encryption", + "help": "Toggle DAVE end-to-end encryption for Discord voice joins (default: true in @discordjs/voice; Discord may require this).", + "hasChildren": false + }, + { + "path": "channels.discord.voice.decryptionFailureTolerance", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Voice Decrypt Failure Tolerance", + "help": "Consecutive decrypt failures before DAVE attempts session recovery (passed to @discordjs/voice; default: 24).", + "hasChildren": false + }, + { + "path": "channels.discord.voice.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Discord Voice Enabled", + "help": "Enable Discord voice channel conversations (default: true). Omit channels.discord.voice to keep voice support disabled for the account.", + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "media", + "network" + ], + "label": "Discord Voice Text-to-Speech", + "help": "Optional TTS overrides for Discord voice playback (merged with messages.tts).", + "hasChildren": true + }, + { + "path": "channels.discord.voice.tts.auto", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "always", + "inbound", + "tagged" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.edge", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.voice.tts.edge.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.edge.lang", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.edge.outputFormat", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.edge.pitch", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.edge.proxy", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.edge.rate", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.edge.saveSubtitles", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.edge.timeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.edge.voice", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.edge.volume", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.voice.tts.elevenlabs.apiKey", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "media", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.discord.voice.tts.elevenlabs.apiKey.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.apiKey.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.apiKey.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.applyTextNormalization", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "auto", + "on", + "off" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.baseUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.languageCode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.modelId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.seed", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.voiceId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.voiceSettings", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.voice.tts.elevenlabs.voiceSettings.similarityBoost", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.voiceSettings.speed", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.voiceSettings.stability", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.voiceSettings.style", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.elevenlabs.voiceSettings.useSpeakerBoost", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.maxTextLength", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.microsoft", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.voice.tts.microsoft.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.microsoft.lang", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.microsoft.outputFormat", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.microsoft.pitch", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.microsoft.proxy", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.microsoft.rate", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.microsoft.saveSubtitles", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.microsoft.timeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.microsoft.voice", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.microsoft.volume", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.mode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "final", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.modelOverrides", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.voice.tts.modelOverrides.allowModelId", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.modelOverrides.allowNormalization", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.modelOverrides.allowProvider", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.modelOverrides.allowSeed", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.modelOverrides.allowText", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.modelOverrides.allowVoice", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.modelOverrides.allowVoiceSettings", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.modelOverrides.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.openai", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.discord.voice.tts.openai.apiKey", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "media", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.discord.voice.tts.openai.apiKey.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.openai.apiKey.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.openai.apiKey.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.openai.baseUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.openai.instructions", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.openai.model", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.openai.speed", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.openai.voice", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.prefsPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.provider", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.summaryModel", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.discord.voice.tts.timeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Feishu", + "help": "飞书/Lark enterprise messaging with doc/wiki/drive tools.", + "hasChildren": true + }, + { + "path": "channels.feishu.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.appId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.appSecret", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.appSecret.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.appSecret.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.appSecret.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.blockStreamingCoalesce.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.blockStreamingCoalesce.maxDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.blockStreamingCoalesce.minDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.connectionMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "websocket", + "webhook" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "pairing", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.dms.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.dms.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.domain", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "feishu", + "lark" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.encryptKey", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.encryptKey.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.encryptKey.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.encryptKey.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "allowlist", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.groups.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.groups.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groups.*.groupSessionScope", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "group", + "group_sender", + "group_topic", + "group_topic_sender" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groups.*.replyInThread", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "disabled", + "enabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groups.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.groups.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groups.*.topicSessionMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "disabled", + "enabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groupSenderAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.groupSenderAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.groupSessionScope", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "group", + "group_sender", + "group_topic", + "group_topic_sender" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.heartbeat.intervalMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.heartbeat.visibility", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "visible", + "hidden" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.httpTimeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.markdown.mode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "native", + "escape", + "strip" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.markdown.tableMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "native", + "ascii", + "simple" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.reactionNotifications", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "own", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.renderMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "auto", + "raw", + "card" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.replyInThread", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "disabled", + "enabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.resolveSenderNames", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.streaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.tools.chat", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.tools.doc", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.tools.drive", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.tools.perm", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.tools.scopes", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.tools.wiki", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.topicSessionMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "disabled", + "enabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.typingIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.verificationToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.accounts.*.verificationToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.verificationToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.verificationToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.webhookHost", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.accounts.*.webhookPort", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.appId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.appSecret", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.appSecret.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.appSecret.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.appSecret.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.blockStreamingCoalesce.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.blockStreamingCoalesce.maxDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.blockStreamingCoalesce.minDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.connectionMode", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "websocket", + "webhook" + ], + "defaultValue": "websocket", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "pairing", + "allowlist" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.dms.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.dms.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.domain", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "feishu", + "lark" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.dynamicAgentCreation", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.dynamicAgentCreation.agentDirTemplate", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.dynamicAgentCreation.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.dynamicAgentCreation.maxAgents", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.dynamicAgentCreation.workspaceTemplate", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.encryptKey", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.encryptKey.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.encryptKey.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.encryptKey.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "allowlist", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.groups.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.groups.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groups.*.groupSessionScope", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "group", + "group_sender", + "group_topic", + "group_topic_sender" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groups.*.replyInThread", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "disabled", + "enabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groups.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.groups.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groups.*.topicSessionMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "disabled", + "enabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groupSenderAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.groupSenderAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.groupSessionScope", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "group", + "group_sender", + "group_topic", + "group_topic_sender" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.heartbeat.intervalMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.heartbeat.visibility", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "visible", + "hidden" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.httpTimeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.markdown.mode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "native", + "escape", + "strip" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.markdown.tableMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "native", + "ascii", + "simple" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.reactionNotifications", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "off", + "own", + "all" + ], + "defaultValue": "own", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.renderMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "auto", + "raw", + "card" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.replyInThread", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "disabled", + "enabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.requireMention", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.resolveSenderNames", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.streaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.tools.chat", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.tools.doc", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.tools.drive", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.tools.perm", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.tools.scopes", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.tools.wiki", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.topicSessionMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "disabled", + "enabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.typingIndicator", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.verificationToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.feishu.verificationToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.verificationToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.verificationToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.webhookHost", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.webhookPath", + "kind": "channel", + "type": "string", + "required": true, + "defaultValue": "/feishu/events", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.feishu.webhookPort", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Google Chat", + "help": "Google Workspace Chat app via HTTP webhooks.", + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.allowBots", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.appPrincipal", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.audience", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.audienceType", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "app-url", + "project-number" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.botUser", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.dm", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.dm.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.dm.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.dm.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.dm.policy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.groups.*.allow", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.groups.*.users", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.groups.*.users.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.serviceAccount", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.serviceAccount.*", + "kind": "channel", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.serviceAccount.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.serviceAccount.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.serviceAccount.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.serviceAccountFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.serviceAccountRef", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.googlechat.accounts.*.serviceAccountRef.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.serviceAccountRef.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.serviceAccountRef.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.streamMode", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "replace", + "status_final", + "append" + ], + "defaultValue": "replace", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.typingIndicator", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "none", + "message", + "reaction" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.accounts.*.webhookUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.allowBots", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.appPrincipal", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.audience", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.audienceType", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "app-url", + "project-number" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.botUser", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.dm", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.dm.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.dm.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.dm.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.dm.policy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.groups.*.allow", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.groups.*.users", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.groups.*.users.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.googlechat.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.serviceAccount", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.googlechat.serviceAccount.*", + "kind": "channel", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.serviceAccount.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.serviceAccount.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.serviceAccount.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.serviceAccountFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.serviceAccountRef", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.googlechat.serviceAccountRef.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.serviceAccountRef.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.serviceAccountRef.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.streamMode", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "replace", + "status_final", + "append" + ], + "defaultValue": "replace", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.typingIndicator", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "none", + "message", + "reaction" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.googlechat.webhookUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "iMessage", + "help": "this is still a work in progress.", + "hasChildren": true + }, + { + "path": "channels.imessage.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.attachmentRoots", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.attachmentRoots.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.cliPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.dbPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.groups.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.groups.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.groups.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.groups.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.includeAttachments", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.region", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.remoteAttachmentRoots", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.accounts.*.remoteAttachmentRoots.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.remoteHost", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.service", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.attachmentRoots", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.attachmentRoots.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.cliPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "storage" + ], + "label": "iMessage CLI Path", + "help": "Filesystem path to the iMessage bridge CLI binary used for send/receive operations. Set explicitly when the binary is not on PATH in service runtime environments.", + "hasChildren": false + }, + { + "path": "channels.imessage.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "iMessage Config Writes", + "help": "Allow iMessage to write config in response to channel events/commands (default: true).", + "hasChildren": false + }, + { + "path": "channels.imessage.dbPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "iMessage DM Policy", + "help": "Direct message access control (\"pairing\" recommended). \"open\" requires channels.imessage.allowFrom=[\"*\"].", + "hasChildren": false + }, + { + "path": "channels.imessage.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.groups.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.groups.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.groups.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.groups.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.includeAttachments", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.mediaMaxMb", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.region", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.remoteAttachmentRoots", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.imessage.remoteAttachmentRoots.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.remoteHost", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.service", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.imessage.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "IRC", + "help": "classic IRC networks with DM/channel routing and pairing controls.", + "hasChildren": true + }, + { + "path": "channels.irc.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.channels", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.channels.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.groups.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.groups.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.host", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.mentionPatterns", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.mentionPatterns.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.nick", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.nickserv", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.accounts.*.nickserv.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.nickserv.password", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.nickserv.passwordFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.nickserv.register", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.nickserv.registerEmail", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.nickserv.service", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.password", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.passwordFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.port", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.realname", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.tls", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.accounts.*.username", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.channels", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.channels.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "IRC DM Policy", + "help": "Direct message access control (\"pairing\" recommended). \"open\" requires channels.irc.allowFrom=[\"*\"].", + "hasChildren": false + }, + { + "path": "channels.irc.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.groups.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.groups.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.host", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.mentionPatterns", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.mentionPatterns.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.nick", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.nickserv", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.irc.nickserv.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "IRC NickServ Enabled", + "help": "Enable NickServ identify/register after connect (defaults to enabled when password is configured).", + "hasChildren": false + }, + { + "path": "channels.irc.nickserv.password", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "label": "IRC NickServ Password", + "help": "NickServ password used for IDENTIFY/REGISTER (sensitive).", + "hasChildren": false + }, + { + "path": "channels.irc.nickserv.passwordFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "auth", + "channels", + "network", + "security", + "storage" + ], + "label": "IRC NickServ Password File", + "help": "Optional file path containing NickServ password.", + "hasChildren": false + }, + { + "path": "channels.irc.nickserv.register", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "IRC NickServ Register", + "help": "If true, send NickServ REGISTER on every connect. Use once for initial registration, then disable.", + "hasChildren": false + }, + { + "path": "channels.irc.nickserv.registerEmail", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "IRC NickServ Register Email", + "help": "Email used with NickServ REGISTER (required when register=true).", + "hasChildren": false + }, + { + "path": "channels.irc.nickserv.service", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "IRC NickServ Service", + "help": "NickServ service nick (default: NickServ).", + "hasChildren": false + }, + { + "path": "channels.irc.password", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": false + }, + { + "path": "channels.irc.passwordFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.port", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.realname", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.tls", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.irc.username", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "LINE", + "help": "LINE Messaging API bot for Japan/Taiwan/Thailand markets.", + "hasChildren": true + }, + { + "path": "channels.line.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.channelAccessToken", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.channelSecret", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "allowlist", + "pairing", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "allowlist", + "disabled" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.accounts.*.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.accounts.*.groups.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.accounts.*.groups.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.groups.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.accounts.*.groups.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.secretFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.tokenFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.accounts.*.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.channelAccessToken", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.channelSecret", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "allowlist", + "pairing", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "allowlist", + "disabled" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.groups.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.groups.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.groups.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.line.groups.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.secretFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.tokenFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.line.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Matrix", + "help": "open protocol; install the plugin to enable.", + "hasChildren": true + }, + { + "path": "channels.matrix.accessToken", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.accounts.*", + "kind": "channel", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.actions.channelInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.actions.memberInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.actions.messages", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.actions.pins", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.allowlistOnly", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.autoJoin", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "always", + "allowlist", + "off" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.autoJoinAllowlist", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.autoJoinAllowlist.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.deviceName", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.dm", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.dm.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.dm.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.dm.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.dm.policy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.encryption", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.groups.*.allow", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groups.*.autoReply", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groups.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.groups.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.groups.*.users", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.groups.*.users.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.homeserver", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.initialSyncLimit", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.password", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.password.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.password.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.password.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "first", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.rooms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.rooms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.rooms.*.allow", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.rooms.*.autoReply", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.rooms.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.rooms.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.rooms.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.rooms.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.rooms.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.rooms.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.rooms.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.rooms.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.rooms.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.rooms.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.rooms.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.rooms.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.rooms.*.users", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.matrix.rooms.*.users.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.textChunkLimit", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.threadReplies", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "inbound", + "always" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.matrix.userId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Mattermost", + "help": "self-hosted Slack-style chat; install the plugin to enable.", + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.baseUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.botToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.botToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.botToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.botToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.chatmode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "oncall", + "onmessage", + "onchar" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.commands", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.commands.callbackPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.commands.callbackUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.commands.native", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.commands.nativeSkills", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.dmChannelRetry", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.dmChannelRetry.initialDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.dmChannelRetry.maxDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.dmChannelRetry.maxRetries", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.dmChannelRetry.timeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.interactions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.interactions.allowedSourceIps", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.interactions.allowedSourceIps.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.interactions.callbackBaseUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.oncharPrefixes", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.accounts.*.oncharPrefixes.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "first", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.baseUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Mattermost Base URL", + "help": "Base URL for your Mattermost server (e.g., https://chat.example.com).", + "hasChildren": false + }, + { + "path": "channels.mattermost.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.botToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "label": "Mattermost Bot Token", + "help": "Bot token from Mattermost System Console -> Integrations -> Bot Accounts.", + "hasChildren": true + }, + { + "path": "channels.mattermost.botToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.botToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.botToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.chatmode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "oncall", + "onmessage", + "onchar" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Mattermost Chat Mode", + "help": "Reply to channel messages on mention (\"oncall\"), on trigger chars (\">\" or \"!\") (\"onchar\"), or on every message (\"onmessage\").", + "hasChildren": false + }, + { + "path": "channels.mattermost.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.commands", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.commands.callbackPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.commands.callbackUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.commands.native", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.commands.nativeSkills", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Mattermost Config Writes", + "help": "Allow Mattermost to write config in response to channel events/commands (default: true).", + "hasChildren": false + }, + { + "path": "channels.mattermost.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.dmChannelRetry", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.dmChannelRetry.initialDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.dmChannelRetry.maxDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.dmChannelRetry.maxRetries", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.dmChannelRetry.timeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.interactions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.interactions.allowedSourceIps", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.interactions.allowedSourceIps.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.interactions.callbackBaseUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.mattermost.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.oncharPrefixes", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Mattermost Onchar Prefixes", + "help": "Trigger prefixes for onchar mode (default: [\">\", \"!\"]).", + "hasChildren": true + }, + { + "path": "channels.mattermost.oncharPrefixes.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "first", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Mattermost Require Mention", + "help": "Require @mention in channels before responding (default: true).", + "hasChildren": false + }, + { + "path": "channels.mattermost.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.mattermost.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Microsoft Teams", + "help": "Bot Framework; enterprise support.", + "hasChildren": true + }, + { + "path": "channels.msteams.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.allowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.appId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.appPassword", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.msteams.appPassword.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.appPassword.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.appPassword.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "MS Teams Config Writes", + "help": "Allow Microsoft Teams to write config in response to channel events/commands (default: true).", + "hasChildren": false + }, + { + "path": "channels.msteams.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.groupAllowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.mediaAllowHosts", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.mediaAllowHosts.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.mediaAuthAllowHosts", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.mediaAuthAllowHosts.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.replyStyle", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "thread", + "top-level" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.sharePointSiteId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*.replyStyle", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "thread", + "top-level" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.channels.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.channels.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.channels.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.channels.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.channels.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.channels.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.channels.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.channels.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.replyStyle", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "thread", + "top-level" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.teams.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.teams.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.tenantId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.webhook", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.msteams.webhook.path", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.msteams.webhook.port", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Nextcloud Talk", + "help": "Self-hosted chat via Nextcloud Talk webhook bots.", + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.allowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.apiPassword", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.apiPassword.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.apiPassword.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.apiPassword.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.apiPasswordFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.apiUser", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.baseUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.botSecret", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.botSecret.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.botSecret.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.botSecret.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.botSecretFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.allowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.accounts.*.rooms.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.webhookHost", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.webhookPort", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.accounts.*.webhookPublicUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.allowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.apiPassword", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.apiPassword.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.apiPassword.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.apiPassword.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.apiPasswordFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.apiUser", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.baseUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.botSecret", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.botSecret.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.botSecret.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.botSecret.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.botSecretFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.groupAllowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.rooms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.rooms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.rooms.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.rooms.*.allowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.rooms.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.rooms.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.rooms.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.rooms.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.rooms.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.rooms.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.rooms.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.rooms.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.rooms.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.rooms.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.rooms.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nextcloud-talk.rooms.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.webhookHost", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.webhookPort", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nextcloud-talk.webhookPublicUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Nostr", + "help": "Decentralized protocol; encrypted DMs via NIP-04.", + "hasChildren": true + }, + { + "path": "channels.nostr.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nostr.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nostr.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.privateKey", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.profile", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nostr.profile.about", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.profile.banner", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.profile.displayName", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.profile.lud16", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.profile.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.profile.nip05", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.profile.picture", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.profile.website", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.nostr.relays", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.nostr.relays.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Signal", + "help": "signal-cli linked device; more setup (David Reagans: \"Hop on Discord.\").", + "hasChildren": true + }, + { + "path": "channels.signal.account", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Signal Account", + "help": "Signal account identifier (phone/number handle) used to bind this channel config to a specific Signal identity. Keep this aligned with your linked device/session state.", + "hasChildren": false + }, + { + "path": "channels.signal.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.account", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.accountUuid", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.autoStart", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.cliPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.groups.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.groups.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.groups.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.groups.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.httpHost", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.httpPort", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.httpUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.ignoreAttachments", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.ignoreStories", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.reactionAllowlist", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.accounts.*.reactionAllowlist.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.reactionLevel", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "ack", + "minimal", + "extensive" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.reactionNotifications", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "own", + "all", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.receiveMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.sendReadReceipts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.startupTimeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.accountUuid", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.autoStart", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.cliPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Signal Config Writes", + "help": "Allow Signal to write config in response to channel events/commands (default: true).", + "hasChildren": false + }, + { + "path": "channels.signal.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "Signal DM Policy", + "help": "Direct message access control (\"pairing\" recommended). \"open\" requires channels.signal.allowFrom=[\"*\"].", + "hasChildren": false + }, + { + "path": "channels.signal.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.groups.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.groups.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.groups.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.groups.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.httpHost", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.httpPort", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.httpUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.ignoreAttachments", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.ignoreStories", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.mediaMaxMb", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.reactionAllowlist", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.signal.reactionAllowlist.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.reactionLevel", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "ack", + "minimal", + "extensive" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.reactionNotifications", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "own", + "all", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.receiveMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.sendReadReceipts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.startupTimeoutMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.signal.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Slack", + "help": "supported (Socket Mode).", + "hasChildren": true + }, + { + "path": "channels.slack.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.ackReaction", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.actions.channelInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.actions.emojiList", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.actions.memberInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.actions.messages", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.actions.permissions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.actions.pins", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.actions.search", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.allowBots", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.appToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.appToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.appToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.appToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.botToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.botToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.botToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.botToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.capabilities", + "kind": "channel", + "type": [ + "array", + "object" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.capabilities.interactiveReplies", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.allow", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.allowBots", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.channels.*.users", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.channels.*.users.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.commands", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.commands.native", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.commands.nativeSkills", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.dm", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.dm.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.dm.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.dm.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.dm.groupChannels", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.dm.groupChannels.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.dm.groupEnabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.dm.policy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.dm.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.mode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "socket", + "http" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.nativeStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.reactionAllowlist", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.reactionAllowlist.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.reactionNotifications", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "own", + "all", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.replyToModeByChatType", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.replyToModeByChatType.channel", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.replyToModeByChatType.direct", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.replyToModeByChatType.group", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.signingSecret", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.signingSecret.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.signingSecret.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.signingSecret.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.slashCommand", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.slashCommand.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.slashCommand.ephemeral", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.slashCommand.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.slashCommand.sessionPrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.streaming", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "enumValues": [ + "off", + "partial", + "block", + "progress" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.streamMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "replace", + "status_final", + "append" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.thread", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.thread.historyScope", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "thread", + "channel" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.thread.inheritParent", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.thread.initialHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.typingReaction", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.userToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.slack.accounts.*.userToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.userToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.userToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.userTokenReadOnly", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.accounts.*.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.ackReaction", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.actions.channelInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.actions.emojiList", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.actions.memberInfo", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.actions.messages", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.actions.permissions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.actions.pins", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.actions.search", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.allowBots", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "Slack Allow Bot Messages", + "help": "Allow bot-authored messages to trigger Slack replies (default: false).", + "hasChildren": false + }, + { + "path": "channels.slack.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.appToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "label": "Slack App Token", + "help": "Slack app-level token used for Socket Mode connections and event transport when enabled. Use least-privilege app scopes and store this token as a secret.", + "hasChildren": true + }, + { + "path": "channels.slack.appToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.appToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.appToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.botToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "label": "Slack Bot Token", + "help": "Slack bot token used for standard chat actions in the configured workspace. Keep this credential scoped and rotate if workspace app permissions change.", + "hasChildren": true + }, + { + "path": "channels.slack.botToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.botToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.botToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.capabilities", + "kind": "channel", + "type": [ + "array", + "object" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.capabilities.interactiveReplies", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Slack Interactive Replies", + "help": "Enable agent-authored Slack interactive reply directives (`[[slack_buttons: ...]]`, `[[slack_select: ...]]`). Default: false.", + "hasChildren": false + }, + { + "path": "channels.slack.channels", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.allow", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.allowBots", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.channels.*.users", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.channels.*.users.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.commands", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.commands.native", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Slack Native Commands", + "help": "Override native commands for Slack (bool or \"auto\").", + "hasChildren": false + }, + { + "path": "channels.slack.commands.nativeSkills", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Slack Native Skill Commands", + "help": "Override native skill commands for Slack (bool or \"auto\").", + "hasChildren": false + }, + { + "path": "channels.slack.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Slack Config Writes", + "help": "Allow Slack to write config in response to channel events/commands (default: true).", + "hasChildren": false + }, + { + "path": "channels.slack.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.dm", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.dm.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.dm.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.dm.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.dm.groupChannels", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.dm.groupChannels.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.dm.groupEnabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.dm.policy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "Slack DM Policy", + "help": "Direct message access control (\"pairing\" recommended). \"open\" requires channels.slack.allowFrom=[\"*\"] (legacy: channels.slack.dm.allowFrom).", + "hasChildren": false + }, + { + "path": "channels.slack.dm.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "Slack DM Policy", + "help": "Direct message access control (\"pairing\" recommended). \"open\" requires channels.slack.allowFrom=[\"*\"].", + "hasChildren": false + }, + { + "path": "channels.slack.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.mode", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "socket", + "http" + ], + "defaultValue": "socket", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.nativeStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Slack Native Streaming", + "help": "Enable native Slack text streaming (chat.startStream/chat.appendStream/chat.stopStream) when channels.slack.streaming is partial (default: true).", + "hasChildren": false + }, + { + "path": "channels.slack.reactionAllowlist", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.reactionAllowlist.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.reactionNotifications", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "own", + "all", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.replyToModeByChatType", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.replyToModeByChatType.channel", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.replyToModeByChatType.direct", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.replyToModeByChatType.group", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.signingSecret", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.slack.signingSecret.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.signingSecret.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.signingSecret.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.slashCommand", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.slashCommand.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.slashCommand.ephemeral", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.slashCommand.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.slashCommand.sessionPrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.streaming", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "enumValues": [ + "off", + "partial", + "block", + "progress" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Slack Streaming Mode", + "help": "Unified Slack stream preview mode: \"off\" | \"partial\" | \"block\" | \"progress\". Legacy boolean/streamMode keys are auto-mapped.", + "hasChildren": false + }, + { + "path": "channels.slack.streamMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "replace", + "status_final", + "append" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Slack Stream Mode (Legacy)", + "help": "Legacy Slack preview mode alias (replace | status_final | append); auto-migrated to channels.slack.streaming.", + "hasChildren": false + }, + { + "path": "channels.slack.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.thread", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.slack.thread.historyScope", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "thread", + "channel" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Slack Thread History Scope", + "help": "Scope for Slack thread history context (\"thread\" isolates per thread; \"channel\" reuses channel history).", + "hasChildren": false + }, + { + "path": "channels.slack.thread.inheritParent", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Slack Thread Parent Inheritance", + "help": "If true, Slack thread sessions inherit the parent channel transcript (default: false).", + "hasChildren": false + }, + { + "path": "channels.slack.thread.initialHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "Slack Thread Initial History Limit", + "help": "Maximum number of existing Slack thread messages to fetch when starting a new thread session (default: 20, set to 0 to disable).", + "hasChildren": false + }, + { + "path": "channels.slack.typingReaction", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.userToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "label": "Slack User Token", + "help": "Optional Slack user token for workflows requiring user-context API access beyond bot permissions. Use sparingly and audit scopes because this token can carry broader authority.", + "hasChildren": true + }, + { + "path": "channels.slack.userToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.userToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.userToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.slack.userTokenReadOnly", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "label": "Slack User Token Read Only", + "help": "When true, treat configured Slack user token usage as read-only helper behavior where possible. Keep enabled if you only need supplemental reads without user-context writes.", + "hasChildren": false + }, + { + "path": "channels.slack.webhookPath", + "kind": "channel", + "type": "string", + "required": true, + "defaultValue": "/slack/events", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.synology-chat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Synology Chat", + "help": "Connect your Synology NAS Chat to OpenClaw with full agent capabilities.", + "hasChildren": true + }, + { + "path": "channels.synology-chat.*", + "kind": "channel", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram", + "help": "simplest way to get started — register a bot with @BotFather and get going.", + "hasChildren": true + }, + { + "path": "channels.telegram.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.ackReaction", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.actions.createForumTopic", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.actions.deleteMessage", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.actions.editForumTopic", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.actions.editMessage", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.actions.poll", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.actions.sendMessage", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.actions.sticker", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.botToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.botToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.botToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.botToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.capabilities", + "kind": "channel", + "type": [ + "array", + "object" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.capabilities.inlineButtons", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "dm", + "group", + "all", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.commands", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.commands.native", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.commands.nativeSkills", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.customCommands", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.customCommands.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.customCommands.*.command", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.customCommands.*.description", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.defaultTo", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.requireTopic", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*.agentId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*.disableAudioPreflight", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.direct.*.topics.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.draftChunk", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.draftChunk.breakPreference", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.draftChunk.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.draftChunk.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.execApprovals", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.execApprovals.agentFilter", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.execApprovals.agentFilter.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.execApprovals.approvers", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.execApprovals.approvers.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.execApprovals.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.execApprovals.sessionFilter", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.execApprovals.sessionFilter.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.execApprovals.target", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "dm", + "channel", + "both" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.disableAudioPreflight", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*.agentId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*.disableAudioPreflight", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.groups.*.topics.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.linkPreview", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.network", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.network.autoSelectFamily", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.network.dnsResultOrder", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "ipv4first", + "verbatim" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.proxy", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.reactionLevel", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "ack", + "minimal", + "extensive" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.reactionNotifications", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "own", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.retry", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.retry.attempts", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.retry.jitter", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.retry.maxDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.retry.minDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.silentErrorReplies", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.streaming", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "enumValues": [ + "off", + "partial", + "block", + "progress" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.streamMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "partial", + "block" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.threadBindings", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.threadBindings.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.threadBindings.idleHours", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.threadBindings.maxAgeHours", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.threadBindings.spawnAcpSessions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.threadBindings.spawnSubagentSessions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.timeoutSeconds", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.tokenFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.webhookCertPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.webhookHost", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.webhookPort", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.webhookSecret", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.telegram.accounts.*.webhookSecret.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.webhookSecret.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.webhookSecret.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.accounts.*.webhookUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.ackReaction", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.actions.createForumTopic", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.actions.deleteMessage", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.actions.editForumTopic", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.actions.editMessage", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.actions.poll", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.actions.sendMessage", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.actions.sticker", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.botToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "label": "Telegram Bot Token", + "help": "Telegram bot token used to authenticate Bot API requests for this account/provider config. Use secret/env substitution and rotate tokens if exposure is suspected.", + "hasChildren": true + }, + { + "path": "channels.telegram.botToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.botToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.botToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.capabilities", + "kind": "channel", + "type": [ + "array", + "object" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.capabilities.inlineButtons", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "dm", + "group", + "all", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Inline Buttons", + "help": "Enable Telegram inline button components for supported command and interaction surfaces. Disable if your deployment needs plain-text-only compatibility behavior.", + "hasChildren": false + }, + { + "path": "channels.telegram.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.commands", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.commands.native", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Native Commands", + "help": "Override native commands for Telegram (bool or \"auto\").", + "hasChildren": false + }, + { + "path": "channels.telegram.commands.nativeSkills", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Native Skill Commands", + "help": "Override native skill commands for Telegram (bool or \"auto\").", + "hasChildren": false + }, + { + "path": "channels.telegram.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Config Writes", + "help": "Allow Telegram to write config in response to channel events/commands (default: true).", + "hasChildren": false + }, + { + "path": "channels.telegram.customCommands", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Custom Commands", + "help": "Additional Telegram bot menu commands (merged with native; conflicts ignored).", + "hasChildren": true + }, + { + "path": "channels.telegram.customCommands.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.customCommands.*.command", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.customCommands.*.description", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.defaultTo", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.requireTopic", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.topics", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.topics.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.topics.*.agentId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.topics.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.topics.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.topics.*.disableAudioPreflight", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.topics.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.topics.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.topics.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.topics.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.direct.*.topics.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.direct.*.topics.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "Telegram DM Policy", + "help": "Direct message access control (\"pairing\" recommended). \"open\" requires channels.telegram.allowFrom=[\"*\"].", + "hasChildren": false + }, + { + "path": "channels.telegram.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.draftChunk", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.draftChunk.breakPreference", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.draftChunk.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.draftChunk.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.execApprovals", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Exec Approvals", + "help": "Telegram-native exec approval routing and approver authorization. Enable this only when Telegram should act as an explicit exec-approval client for the selected bot account.", + "hasChildren": true + }, + { + "path": "channels.telegram.execApprovals.agentFilter", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Exec Approval Agent Filter", + "help": "Optional allowlist of agent IDs eligible for Telegram exec approvals, for example `[\"main\", \"ops-agent\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Telegram.", + "hasChildren": true + }, + { + "path": "channels.telegram.execApprovals.agentFilter.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.execApprovals.approvers", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Exec Approval Approvers", + "help": "Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs; prompts are only delivered to these approvers when target includes dm.", + "hasChildren": true + }, + { + "path": "channels.telegram.execApprovals.approvers.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.execApprovals.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Exec Approvals Enabled", + "help": "Enable Telegram exec approvals for this account. When false or unset, Telegram messages/buttons cannot approve exec requests.", + "hasChildren": false + }, + { + "path": "channels.telegram.execApprovals.sessionFilter", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "storage" + ], + "label": "Telegram Exec Approval Session Filter", + "help": "Optional session-key filters matched as substring or regex-style patterns before Telegram approval routing is used. Use narrow patterns so Telegram approvals only appear for intended sessions.", + "hasChildren": true + }, + { + "path": "channels.telegram.execApprovals.sessionFilter.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.execApprovals.target", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "dm", + "channel", + "both" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Exec Approval Target", + "help": "Controls where Telegram approval prompts are sent: \"dm\" sends to approver DMs (default), \"channel\" sends to the originating Telegram chat/topic, and \"both\" sends to both. Channel delivery exposes the command text to the chat, so only use it in trusted groups/topics.", + "hasChildren": false + }, + { + "path": "channels.telegram.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.disableAudioPreflight", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.topics", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.topics.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.topics.*.agentId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.topics.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.topics.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.topics.*.disableAudioPreflight", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.topics.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.topics.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.topics.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.topics.*.skills", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.groups.*.topics.*.skills.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.groups.*.topics.*.systemPrompt", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.linkPreview", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.network", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.network.autoSelectFamily", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram autoSelectFamily", + "help": "Override Node autoSelectFamily for Telegram (true=enable, false=disable).", + "hasChildren": false + }, + { + "path": "channels.telegram.network.dnsResultOrder", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "ipv4first", + "verbatim" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.proxy", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.reactionLevel", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "ack", + "minimal", + "extensive" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.reactionNotifications", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "own", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.replyToMode", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.retry", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.retry.attempts", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "reliability" + ], + "label": "Telegram Retry Attempts", + "help": "Max retry attempts for outbound Telegram API calls (default: 3).", + "hasChildren": false + }, + { + "path": "channels.telegram.retry.jitter", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "reliability" + ], + "label": "Telegram Retry Jitter", + "help": "Jitter factor (0-1) applied to Telegram retry delays.", + "hasChildren": false + }, + { + "path": "channels.telegram.retry.maxDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance", + "reliability" + ], + "label": "Telegram Retry Max Delay (ms)", + "help": "Maximum retry delay cap in ms for Telegram outbound calls.", + "hasChildren": false + }, + { + "path": "channels.telegram.retry.minDelayMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "reliability" + ], + "label": "Telegram Retry Min Delay (ms)", + "help": "Minimum retry delay in ms for Telegram outbound calls.", + "hasChildren": false + }, + { + "path": "channels.telegram.silentErrorReplies", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Silent Error Replies", + "help": "When true, Telegram bot replies marked as errors are sent silently (no notification sound). Default: false.", + "hasChildren": false + }, + { + "path": "channels.telegram.streaming", + "kind": "channel", + "type": [ + "boolean", + "string" + ], + "required": false, + "enumValues": [ + "off", + "partial", + "block", + "progress" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Telegram Streaming Mode", + "help": "Unified Telegram stream preview mode: \"off\" | \"partial\" | \"block\" | \"progress\" (default: \"partial\"). \"progress\" maps to \"partial\" on Telegram. Legacy boolean/streamMode keys are auto-mapped.", + "hasChildren": false + }, + { + "path": "channels.telegram.streamMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "partial", + "block" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.threadBindings", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.telegram.threadBindings.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "storage" + ], + "label": "Telegram Thread Binding Enabled", + "help": "Enable Telegram conversation binding features (/focus, /unfocus, /agents, and /session idle|max-age). Overrides session.threadBindings.enabled when set.", + "hasChildren": false + }, + { + "path": "channels.telegram.threadBindings.idleHours", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "storage" + ], + "label": "Telegram Thread Binding Idle Timeout (hours)", + "help": "Inactivity window in hours for Telegram bound sessions. Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set.", + "hasChildren": false + }, + { + "path": "channels.telegram.threadBindings.maxAgeHours", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance", + "storage" + ], + "label": "Telegram Thread Binding Max Age (hours)", + "help": "Optional hard max age in hours for Telegram bound sessions. Set 0 to disable hard cap (default: 0). Overrides session.threadBindings.maxAgeHours when set.", + "hasChildren": false + }, + { + "path": "channels.telegram.threadBindings.spawnAcpSessions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "storage" + ], + "label": "Telegram Thread-Bound ACP Spawn", + "help": "Allow ACP spawns with thread=true to auto-bind Telegram current conversations when supported.", + "hasChildren": false + }, + { + "path": "channels.telegram.threadBindings.spawnSubagentSessions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "storage" + ], + "label": "Telegram Thread-Bound Subagent Spawn", + "help": "Allow subagent spawns with thread=true to auto-bind Telegram current conversations when supported.", + "hasChildren": false + }, + { + "path": "channels.telegram.timeoutSeconds", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "Telegram API Timeout (seconds)", + "help": "Max seconds before Telegram API requests are aborted (default: 500 per grammY).", + "hasChildren": false + }, + { + "path": "channels.telegram.tokenFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.webhookCertPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.webhookHost", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.webhookPort", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.webhookSecret", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "channels", + "network", + "security" + ], + "hasChildren": true + }, + { + "path": "channels.telegram.webhookSecret.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.webhookSecret.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.webhookSecret.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.telegram.webhookUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Tlon", + "help": "decentralized messaging on Urbit; install the plugin to enable.", + "hasChildren": true + }, + { + "path": "channels.tlon.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.accounts.*.allowPrivateNetwork", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.autoAcceptDmInvites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.autoAcceptGroupInvites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.autoDiscoverChannels", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.code", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.dmAllowlist", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.accounts.*.dmAllowlist.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.groupChannels", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.accounts.*.groupChannels.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.ownerShip", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.ship", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.showModelSignature", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.accounts.*.url", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.allowPrivateNetwork", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.authorization", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.authorization.channelRules", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.authorization.channelRules.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.authorization.channelRules.*.allowedShips", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.authorization.channelRules.*.allowedShips.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.authorization.channelRules.*.mode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "restricted", + "open" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.autoAcceptDmInvites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.autoAcceptGroupInvites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.autoDiscoverChannels", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.code", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.defaultAuthorizedShips", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.defaultAuthorizedShips.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.dmAllowlist", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.dmAllowlist.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.groupChannels", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.tlon.groupChannels.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.ownerShip", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.ship", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.showModelSignature", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.tlon.url", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Twitch", + "help": "Twitch chat integration", + "hasChildren": true + }, + { + "path": "channels.twitch.accessToken", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts", + "kind": "channel", + "type": "object", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.twitch.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.twitch.accounts.*.accessToken", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.allowedRoles", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.twitch.accounts.*.allowedRoles.*", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "moderator", + "owner", + "vip", + "subscriber", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.twitch.accounts.*.allowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.channel", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.clientId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.clientSecret", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.expiresIn", + "kind": "channel", + "type": [ + "null", + "number" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.obtainmentTimestamp", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.refreshToken", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.accounts.*.username", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.allowedRoles", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.twitch.allowedRoles.*", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "moderator", + "owner", + "vip", + "subscriber", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.twitch.allowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.channel", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.clientId", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.clientSecret", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.expiresIn", + "kind": "channel", + "type": [ + "null", + "number" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.twitch.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "bullets", + "code", + "off" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.obtainmentTimestamp", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.refreshToken", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.twitch.username", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "WhatsApp", + "help": "works with your own number; recommend a separate phone + eSIM.", + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.ackReaction", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.ackReaction.direct", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.ackReaction.emoji", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.ackReaction.group", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "always", + "mentions", + "never" + ], + "defaultValue": "mentions", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.allowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.authDir", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.debounceMs", + "kind": "channel", + "type": "integer", + "required": true, + "defaultValue": 0, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.groups.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.messagePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.selfChatMode", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.sendReadReceipts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.accounts.*.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.ackReaction", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.ackReaction.direct", + "kind": "channel", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.ackReaction.emoji", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.ackReaction.group", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "always", + "mentions", + "never" + ], + "defaultValue": "mentions", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.actions", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.actions.polls", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.actions.reactions", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.actions.sendMessage", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.allowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.blockStreaming", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.blockStreamingCoalesce", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.blockStreamingCoalesce.idleMs", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.blockStreamingCoalesce.maxChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.blockStreamingCoalesce.minChars", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.capabilities", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.capabilities.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.chunkMode", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "length", + "newline" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.configWrites", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "WhatsApp Config Writes", + "help": "Allow WhatsApp to write config in response to channel events/commands (default: true).", + "hasChildren": false + }, + { + "path": "channels.whatsapp.debounceMs", + "kind": "channel", + "type": "integer", + "required": true, + "defaultValue": 0, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network", + "performance" + ], + "label": "WhatsApp Message Debounce (ms)", + "help": "Debounce window (ms) for batching rapid consecutive messages from the same sender (0 to disable).", + "hasChildren": false + }, + { + "path": "channels.whatsapp.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.defaultTo", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.dmHistoryLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.dmPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "defaultValue": "pairing", + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "channels", + "network" + ], + "label": "WhatsApp DM Policy", + "help": "Direct message access control (\"pairing\" recommended). \"open\" requires channels.whatsapp.allowFrom=[\"*\"].", + "hasChildren": false + }, + { + "path": "channels.whatsapp.dms", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.dms.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.dms.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groupAllowFrom.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.groups.*.toolsBySender", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*.toolsBySender.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*.toolsBySender.*.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*.toolsBySender.*.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.groups.*.toolsBySender.*.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*.toolsBySender.*.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.groups.*.toolsBySender.*.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.groups.*.toolsBySender.*.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.healthMonitor", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.healthMonitor.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.heartbeat", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.heartbeat.showAlerts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.heartbeat.showOk", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.heartbeat.useIndicator", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.whatsapp.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.mediaMaxMb", + "kind": "channel", + "type": "integer", + "required": true, + "defaultValue": 50, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.messagePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.selfChatMode", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "WhatsApp Self-Phone Mode", + "help": "Same-phone setup (bot uses your personal WhatsApp number).", + "hasChildren": false + }, + { + "path": "channels.whatsapp.sendReadReceipts", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.whatsapp.textChunkLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Zalo", + "help": "Vietnam-focused messaging platform with Bot API.", + "hasChildren": true + }, + { + "path": "channels.zalo.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.botToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.accounts.*.botToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.botToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.botToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.proxy", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.tokenFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.webhookSecret", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.accounts.*.webhookSecret.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.webhookSecret.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.webhookSecret.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.accounts.*.webhookUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.botToken", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.botToken.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.botToken.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.botToken.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.groupPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.mediaMaxMb", + "kind": "channel", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.proxy", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.tokenFile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.webhookPath", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.webhookSecret", + "kind": "channel", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalo.webhookSecret.id", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.webhookSecret.provider", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.webhookSecret.source", + "kind": "channel", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalo.webhookUrl", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "channels", + "network" + ], + "label": "Zalo Personal", + "help": "Zalo personal account via QR code login.", + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*.groups.*.allow", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.accounts.*.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.messagePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.profile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.accounts.*.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.allowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.allowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.dangerouslyAllowNameMatching", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.defaultAccount", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.dmPolicy", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "pairing", + "allowlist", + "open", + "disabled" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.groupAllowFrom", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.groupAllowFrom.*", + "kind": "channel", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.groupPolicy", + "kind": "channel", + "type": "string", + "required": true, + "enumValues": [ + "open", + "disabled", + "allowlist" + ], + "defaultValue": "allowlist", + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.groups", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.groups.*", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.groups.*.allow", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.groups.*.enabled", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.groups.*.requireMention", + "kind": "channel", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.groups.*.tools", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.groups.*.tools.allow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.groups.*.tools.allow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.groups.*.tools.alsoAllow", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.groups.*.tools.alsoAllow.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.groups.*.tools.deny", + "kind": "channel", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.groups.*.tools.deny.*", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.historyLimit", + "kind": "channel", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.markdown", + "kind": "channel", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "channels.zalouser.markdown.tables", + "kind": "channel", + "type": "string", + "required": false, + "enumValues": [ + "off", + "bullets", + "code" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.messagePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.name", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.profile", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "channels.zalouser.responsePrefix", + "kind": "channel", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cli", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "CLI", + "help": "CLI presentation controls for local command output behavior such as banner and tagline style. Use this section to keep startup output aligned with operator preference without changing runtime behavior.", + "hasChildren": true + }, + { + "path": "cli.banner", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "CLI Banner", + "help": "CLI startup banner controls for title/version line and tagline style behavior. Keep banner enabled for fast version/context checks, then tune tagline mode to your preferred noise level.", + "hasChildren": true + }, + { + "path": "cli.banner.taglineMode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "CLI Banner Tagline Mode", + "help": "Controls tagline style in the CLI startup banner: \"random\" (default) picks from the rotating tagline pool, \"default\" always shows the neutral default tagline, and \"off\" hides tagline text while keeping the banner version line.", + "hasChildren": false + }, + { + "path": "commands", + "kind": "core", + "type": "object", + "required": true, + "defaultValue": { + "native": "auto", + "nativeSkills": "auto", + "ownerDisplay": "raw", + "restart": true + }, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Commands", + "help": "Controls chat command surfaces, owner gating, and elevated command access behavior across providers. Keep defaults unless you need stricter operator controls or broader command availability.", + "hasChildren": true + }, + { + "path": "commands.allowFrom", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Command Elevated Access Rules", + "help": "Defines elevated command allow rules by channel and sender for owner-level command surfaces. Use narrow provider-specific identities so privileged commands are not exposed to broad chat audiences.", + "hasChildren": true + }, + { + "path": "commands.allowFrom.*", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "commands.allowFrom.*.*", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "commands.bash", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Allow Bash Chat Command", + "help": "Allow bash chat command (`!`; `/bash` alias) to run host shell commands (default: false; requires tools.elevated).", + "hasChildren": false + }, + { + "path": "commands.bashForegroundMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Bash Foreground Window (ms)", + "help": "How long bash waits before backgrounding (default: 2000; 0 backgrounds immediately).", + "hasChildren": false + }, + { + "path": "commands.config", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Allow /config", + "help": "Allow /config chat command to read/write config on disk (default: false).", + "hasChildren": false + }, + { + "path": "commands.debug", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Allow /debug", + "help": "Allow /debug chat command for runtime-only overrides (default: false).", + "hasChildren": false + }, + { + "path": "commands.mcp", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Allow /mcp", + "help": "Allow /mcp chat command to manage OpenClaw MCP server config under mcp.servers (default: false).", + "hasChildren": false + }, + { + "path": "commands.native", + "kind": "core", + "type": [ + "boolean", + "string" + ], + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Native Commands", + "help": "Registers native slash/menu commands with channels that support command registration (Discord, Slack, Telegram). Keep enabled for discoverability unless you intentionally run text-only command workflows.", + "hasChildren": false + }, + { + "path": "commands.nativeSkills", + "kind": "core", + "type": [ + "boolean", + "string" + ], + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Native Skill Commands", + "help": "Registers native skill commands so users can invoke skills directly from provider command menus where supported. Keep aligned with your skill policy so exposed commands match what operators expect.", + "hasChildren": false + }, + { + "path": "commands.ownerAllowFrom", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Command Owners", + "help": "Explicit owner allowlist for owner-only tools/commands. Use channel-native IDs (optionally prefixed like \"whatsapp:+15551234567\"). '*' is ignored.", + "hasChildren": true + }, + { + "path": "commands.ownerAllowFrom.*", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "commands.ownerDisplay", + "kind": "core", + "type": "string", + "required": true, + "enumValues": [ + "raw", + "hash" + ], + "defaultValue": "raw", + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Owner ID Display", + "help": "Controls how owner IDs are rendered in the system prompt. Allowed values: raw, hash. Default: raw.", + "hasChildren": false + }, + { + "path": "commands.ownerDisplaySecret", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "access", + "auth", + "security" + ], + "label": "Owner ID Hash Secret", + "help": "Optional secret used to HMAC hash owner IDs when ownerDisplay=hash. Prefer env substitution.", + "hasChildren": false + }, + { + "path": "commands.plugins", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Allow /plugins", + "help": "Allow /plugins chat command to list discovered plugins and toggle plugin enablement in config (default: false).", + "hasChildren": false + }, + { + "path": "commands.restart", + "kind": "core", + "type": "boolean", + "required": true, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Allow Restart", + "help": "Allow /restart and gateway restart tool actions (default: true).", + "hasChildren": false + }, + { + "path": "commands.text", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Text Commands", + "help": "Enables text-command parsing in chat input in addition to native command surfaces where available. Keep this enabled for compatibility across channels that do not support native command registration.", + "hasChildren": false + }, + { + "path": "commands.useAccessGroups", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Use Access Groups", + "help": "Enforce access-group allowlists/policies for commands.", + "hasChildren": false + }, + { + "path": "cron", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "label": "Cron", + "help": "Global scheduler settings for stored cron jobs, run concurrency, delivery fallback, and run-session retention. Keep defaults unless you are scaling job volume or integrating external webhook receivers.", + "hasChildren": true + }, + { + "path": "cron.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "label": "Cron Enabled", + "help": "Enables cron job execution for stored schedules managed by the gateway. Keep enabled for normal reminder/automation flows, and disable only to pause all cron execution without deleting jobs.", + "hasChildren": false + }, + { + "path": "cron.failureAlert", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "cron.failureAlert.accountId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.failureAlert.after", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.failureAlert.cooldownMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.failureAlert.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.failureAlert.mode", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "announce", + "webhook" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.failureDestination", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "cron.failureDestination.accountId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.failureDestination.channel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.failureDestination.mode", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "announce", + "webhook" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.failureDestination.to", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.maxConcurrentRuns", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "performance" + ], + "label": "Cron Max Concurrent Runs", + "help": "Limits how many cron jobs can execute at the same time when multiple schedules fire together. Use lower values to protect CPU/memory under heavy automation load, or raise carefully for higher throughput.", + "hasChildren": false + }, + { + "path": "cron.retry", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "reliability" + ], + "label": "Cron Retry Policy", + "help": "Overrides the default retry policy for one-shot jobs when they fail with transient errors (rate limit, overloaded, network, server_error). Omit to use defaults: maxAttempts 3, backoffMs [30000, 60000, 300000], retry all transient types.", + "hasChildren": true + }, + { + "path": "cron.retry.backoffMs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "reliability" + ], + "label": "Cron Retry Backoff (ms)", + "help": "Backoff delays in ms for each retry attempt (default: [30000, 60000, 300000]). Use shorter values for faster retries.", + "hasChildren": true + }, + { + "path": "cron.retry.backoffMs.*", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.retry.maxAttempts", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "performance", + "reliability" + ], + "label": "Cron Retry Max Attempts", + "help": "Max retries for one-shot jobs on transient errors before permanent disable (default: 3).", + "hasChildren": false + }, + { + "path": "cron.retry.retryOn", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "reliability" + ], + "label": "Cron Retry Error Types", + "help": "Error types to retry: rate_limit, overloaded, network, timeout, server_error. Use to restrict which errors trigger retries; omit to retry all transient types.", + "hasChildren": true + }, + { + "path": "cron.retry.retryOn.*", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "rate_limit", + "overloaded", + "network", + "timeout", + "server_error" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.runLog", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "label": "Cron Run Log Pruning", + "help": "Pruning controls for per-job cron run history files under `cron/runs/.jsonl`, including size and line retention.", + "hasChildren": true + }, + { + "path": "cron.runLog.keepLines", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "label": "Cron Run Log Keep Lines", + "help": "How many trailing run-log lines to retain when a file exceeds maxBytes (default `2000`). Increase for longer forensic history or lower for smaller disks.", + "hasChildren": false + }, + { + "path": "cron.runLog.maxBytes", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "performance" + ], + "label": "Cron Run Log Max Bytes", + "help": "Maximum bytes per cron run-log file before pruning rewrites to the last keepLines entries (for example `2mb`, default `2000000`).", + "hasChildren": false + }, + { + "path": "cron.sessionRetention", + "kind": "core", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "storage" + ], + "label": "Cron Session Retention", + "help": "Controls how long completed cron run sessions are kept before pruning (`24h`, `7d`, `1h30m`, or `false` to disable pruning; default: `24h`). Use shorter retention to reduce storage growth on high-frequency schedules.", + "hasChildren": false + }, + { + "path": "cron.store", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "storage" + ], + "label": "Cron Store Path", + "help": "Path to the cron job store file used to persist scheduled jobs across restarts. Set an explicit path only when you need custom storage layout, backups, or mounted volumes.", + "hasChildren": false + }, + { + "path": "cron.webhook", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "label": "Cron Legacy Webhook (Deprecated)", + "help": "Deprecated legacy fallback webhook URL used only for old jobs with `notify=true`. Migrate to per-job delivery using `delivery.mode=\"webhook\"` plus `delivery.to`, and avoid relying on this global field.", + "hasChildren": false + }, + { + "path": "cron.webhookToken", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "automation", + "security" + ], + "label": "Cron Webhook Bearer Token", + "help": "Bearer token attached to cron webhook POST deliveries when webhook mode is used. Prefer secret/env substitution and rotate this token regularly if shared webhook endpoints are internet-reachable.", + "hasChildren": true + }, + { + "path": "cron.webhookToken.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.webhookToken.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "cron.webhookToken.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "diagnostics", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "Diagnostics", + "help": "Diagnostics controls for targeted tracing, telemetry export, and cache inspection during debugging. Keep baseline diagnostics minimal in production and enable deeper signals only when investigating issues.", + "hasChildren": true + }, + { + "path": "diagnostics.cacheTrace", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "storage" + ], + "label": "Cache Trace", + "help": "Cache-trace logging settings for observing cache decisions and payload context in embedded runs. Enable this temporarily for debugging and disable afterward to reduce sensitive log footprint.", + "hasChildren": true + }, + { + "path": "diagnostics.cacheTrace.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "storage" + ], + "label": "Cache Trace Enabled", + "help": "Log cache trace snapshots for embedded agent runs (default: false).", + "hasChildren": false + }, + { + "path": "diagnostics.cacheTrace.filePath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "storage" + ], + "label": "Cache Trace File Path", + "help": "JSONL output path for cache trace logs (default: $OPENCLAW_STATE_DIR/logs/cache-trace.jsonl).", + "hasChildren": false + }, + { + "path": "diagnostics.cacheTrace.includeMessages", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "storage" + ], + "label": "Cache Trace Include Messages", + "help": "Include full message payloads in trace output (default: true).", + "hasChildren": false + }, + { + "path": "diagnostics.cacheTrace.includePrompt", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "storage" + ], + "label": "Cache Trace Include Prompt", + "help": "Include prompt text in trace output (default: true).", + "hasChildren": false + }, + { + "path": "diagnostics.cacheTrace.includeSystem", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "storage" + ], + "label": "Cache Trace Include System", + "help": "Include system prompt in trace output (default: true).", + "hasChildren": false + }, + { + "path": "diagnostics.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "Diagnostics Enabled", + "help": "Master toggle for diagnostics instrumentation output in logs and telemetry wiring paths. Keep enabled for normal observability, and disable only in tightly constrained environments.", + "hasChildren": false + }, + { + "path": "diagnostics.flags", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "Diagnostics Flags", + "help": "Enable targeted diagnostics logs by flag (e.g. [\"telegram.http\"]). Supports wildcards like \"telegram.*\" or \"*\".", + "hasChildren": true + }, + { + "path": "diagnostics.flags.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "diagnostics.otel", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "OpenTelemetry", + "help": "OpenTelemetry export settings for traces, metrics, and logs emitted by gateway components. Use this when integrating with centralized observability backends and distributed tracing pipelines.", + "hasChildren": true + }, + { + "path": "diagnostics.otel.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "OpenTelemetry Enabled", + "help": "Enables OpenTelemetry export pipeline for traces, metrics, and logs based on configured endpoint/protocol settings. Keep disabled unless your collector endpoint and auth are fully configured.", + "hasChildren": false + }, + { + "path": "diagnostics.otel.endpoint", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "OpenTelemetry Endpoint", + "help": "Collector endpoint URL used for OpenTelemetry export transport, including scheme and port. Use a reachable, trusted collector endpoint and monitor ingestion errors after rollout.", + "hasChildren": false + }, + { + "path": "diagnostics.otel.flushIntervalMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "performance" + ], + "label": "OpenTelemetry Flush Interval (ms)", + "help": "Interval in milliseconds for periodic telemetry flush from buffers to the collector. Increase to reduce export chatter, or lower for faster visibility during active incident response.", + "hasChildren": false + }, + { + "path": "diagnostics.otel.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "OpenTelemetry Headers", + "help": "Additional HTTP/gRPC metadata headers sent with OpenTelemetry export requests, often used for tenant auth or routing. Keep secrets in env-backed values and avoid unnecessary header sprawl.", + "hasChildren": true + }, + { + "path": "diagnostics.otel.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "diagnostics.otel.logs", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "OpenTelemetry Logs Enabled", + "help": "Enable log signal export through OpenTelemetry in addition to local logging sinks. Use this when centralized log correlation is required across services and agents.", + "hasChildren": false + }, + { + "path": "diagnostics.otel.metrics", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "OpenTelemetry Metrics Enabled", + "help": "Enable metrics signal export to the configured OpenTelemetry collector endpoint. Keep enabled for runtime health dashboards, and disable only if metric volume must be minimized.", + "hasChildren": false + }, + { + "path": "diagnostics.otel.protocol", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "OpenTelemetry Protocol", + "help": "OTel transport protocol for telemetry export: \"http/protobuf\" or \"grpc\" depending on collector support. Use the protocol your observability backend expects to avoid dropped telemetry payloads.", + "hasChildren": false + }, + { + "path": "diagnostics.otel.sampleRate", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "OpenTelemetry Trace Sample Rate", + "help": "Trace sampling rate (0-1) controlling how much trace traffic is exported to observability backends. Lower rates reduce overhead/cost, while higher rates improve debugging fidelity.", + "hasChildren": false + }, + { + "path": "diagnostics.otel.serviceName", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "OpenTelemetry Service Name", + "help": "Service name reported in telemetry resource attributes to identify this gateway instance in observability backends. Use stable names so dashboards and alerts remain consistent over deployments.", + "hasChildren": false + }, + { + "path": "diagnostics.otel.traces", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "OpenTelemetry Traces Enabled", + "help": "Enable trace signal export to the configured OpenTelemetry collector endpoint. Keep enabled when latency/debug tracing is needed, and disable if you only want metrics/logs.", + "hasChildren": false + }, + { + "path": "diagnostics.stuckSessionWarnMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "storage" + ], + "label": "Stuck Session Warning Threshold (ms)", + "help": "Age threshold in milliseconds for emitting stuck-session warnings while a session remains in processing state. Increase for long multi-tool turns to reduce false positives; decrease for faster hang detection.", + "hasChildren": false + }, + { + "path": "discovery", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Discovery", + "help": "Service discovery settings for local mDNS advertisement and optional wide-area presence signaling. Keep discovery scoped to expected networks to avoid leaking service metadata.", + "hasChildren": true + }, + { + "path": "discovery.mdns", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "mDNS Discovery", + "help": "mDNS discovery configuration group for local network advertisement and discovery behavior tuning. Keep minimal mode for routine LAN discovery unless extra metadata is required.", + "hasChildren": true + }, + { + "path": "discovery.mdns.mode", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "off", + "minimal", + "full" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "mDNS Discovery Mode", + "help": "mDNS broadcast mode (\"minimal\" default, \"full\" includes cliPath/sshPort, \"off\" disables mDNS).", + "hasChildren": false + }, + { + "path": "discovery.wideArea", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Wide-area Discovery", + "help": "Wide-area discovery configuration group for exposing discovery signals beyond local-link scopes. Enable only in deployments that intentionally aggregate gateway presence across sites.", + "hasChildren": true + }, + { + "path": "discovery.wideArea.domain", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Wide-area Discovery Domain", + "help": "Optional unicast DNS-SD domain for wide-area discovery, such as openclaw.internal. Use this when you intentionally publish gateway discovery beyond local mDNS scopes.", + "hasChildren": false + }, + { + "path": "discovery.wideArea.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Wide-area Discovery Enabled", + "help": "Enables wide-area discovery signaling when your environment needs non-local gateway discovery. Keep disabled unless cross-network discovery is operationally required.", + "hasChildren": false + }, + { + "path": "env", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Environment", + "help": "Environment import and override settings used to supply runtime variables to the gateway process. Use this section to control shell-env loading and explicit variable injection behavior.", + "hasChildren": true + }, + { + "path": "env.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "env.shellEnv", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Shell Environment Import", + "help": "Shell environment import controls for loading variables from your login shell during startup. Keep this enabled when you depend on profile-defined secrets or PATH customizations.", + "hasChildren": true + }, + { + "path": "env.shellEnv.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Shell Environment Import Enabled", + "help": "Enables loading environment variables from the user shell profile during startup initialization. Keep enabled for developer machines, or disable in locked-down service environments with explicit env management.", + "hasChildren": false + }, + { + "path": "env.shellEnv.timeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Shell Environment Import Timeout (ms)", + "help": "Maximum time in milliseconds allowed for shell environment resolution before fallback behavior applies. Use tighter timeouts for faster startup, or increase when shell initialization is heavy.", + "hasChildren": false + }, + { + "path": "env.vars", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Environment Variable Overrides", + "help": "Explicit key/value environment variable overrides merged into runtime process environment for OpenClaw. Use this for deterministic env configuration instead of relying only on shell profile side effects.", + "hasChildren": true + }, + { + "path": "env.vars.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gateway", + "help": "Gateway runtime surface for bind mode, auth, control UI, remote transport, and operational safety controls. Keep conservative defaults unless you intentionally expose the gateway beyond trusted local interfaces.", + "hasChildren": true + }, + { + "path": "gateway.allowRealIpFallback", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "network", + "reliability" + ], + "label": "Gateway Allow x-real-ip Fallback", + "help": "Enables x-real-ip fallback when x-forwarded-for is missing in proxy scenarios. Keep disabled unless your ingress stack requires this compatibility behavior.", + "hasChildren": false + }, + { + "path": "gateway.auth", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Auth", + "help": "Authentication policy for gateway HTTP/WebSocket access including mode, credentials, trusted-proxy behavior, and rate limiting. Keep auth enabled for every non-loopback deployment.", + "hasChildren": true + }, + { + "path": "gateway.auth.allowTailscale", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "network" + ], + "label": "Gateway Auth Allow Tailscale Identity", + "help": "Allows trusted Tailscale identity paths to satisfy gateway auth checks when configured. Use this only when your tailnet identity posture is strong and operator workflows depend on it.", + "hasChildren": false + }, + { + "path": "gateway.auth.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Auth Mode", + "help": "Gateway auth mode: \"none\", \"token\", \"password\", or \"trusted-proxy\" depending on your edge architecture. Use token/password for direct exposure, and trusted-proxy only behind hardened identity-aware proxies.", + "hasChildren": false + }, + { + "path": "gateway.auth.password", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "access", + "auth", + "network", + "security" + ], + "label": "Gateway Password", + "help": "Required for Tailscale funnel.", + "hasChildren": true + }, + { + "path": "gateway.auth.password.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.password.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.password.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.rateLimit", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network", + "performance" + ], + "label": "Gateway Auth Rate Limit", + "help": "Login/auth attempt throttling controls to reduce credential brute-force risk at the gateway boundary. Keep enabled in exposed environments and tune thresholds to your traffic baseline.", + "hasChildren": true + }, + { + "path": "gateway.auth.rateLimit.exemptLoopback", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.rateLimit.lockoutMs", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.rateLimit.maxAttempts", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.rateLimit.windowMs", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.token", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "access", + "auth", + "network", + "security" + ], + "label": "Gateway Token", + "help": "Required by default for gateway access (unless using Tailscale Serve identity); required for non-loopback binds.", + "hasChildren": true + }, + { + "path": "gateway.auth.token.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.token.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.token.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.trustedProxy", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Trusted Proxy Auth", + "help": "Trusted-proxy auth header mapping for upstream identity providers that inject user claims. Use only with known proxy CIDRs and strict header allowlists to prevent spoofed identity headers.", + "hasChildren": true + }, + { + "path": "gateway.auth.trustedProxy.allowUsers", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.auth.trustedProxy.allowUsers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.trustedProxy.requiredHeaders", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.auth.trustedProxy.requiredHeaders.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.auth.trustedProxy.userHeader", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.bind", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Bind Mode", + "help": "Network bind profile: \"auto\", \"lan\", \"loopback\", \"custom\", or \"tailnet\" to control interface exposure. Keep \"loopback\" or \"auto\" for safest local operation unless external clients must connect.", + "hasChildren": false + }, + { + "path": "gateway.channelHealthCheckMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network", + "reliability" + ], + "label": "Gateway Channel Health Check Interval (min)", + "help": "Interval in minutes for automatic channel health probing and status updates. Use lower intervals for faster detection, or higher intervals to reduce periodic probe noise.", + "hasChildren": false + }, + { + "path": "gateway.channelMaxRestartsPerHour", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network", + "performance" + ], + "label": "Gateway Channel Max Restarts Per Hour", + "help": "Maximum number of health-monitor-initiated channel restarts allowed within a rolling one-hour window. Once hit, further restarts are skipped until the window expires. Default: 10.", + "hasChildren": false + }, + { + "path": "gateway.channelStaleEventThresholdMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Channel Stale Event Threshold (min)", + "help": "How many minutes a connected channel can go without receiving any event before the health monitor treats it as a stale socket and triggers a restart. Default: 30.", + "hasChildren": false + }, + { + "path": "gateway.controlUi", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Control UI", + "help": "Control UI hosting settings including enablement, pathing, and browser-origin/auth hardening behavior. Keep UI exposure minimal and pair with strong auth controls before internet-facing deployments.", + "hasChildren": true + }, + { + "path": "gateway.controlUi.allowedOrigins", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "network" + ], + "label": "Control UI Allowed Origins", + "help": "Allowed browser origins for Control UI/WebChat websocket connections (full origins only, e.g. https://control.example.com). Required for non-loopback Control UI deployments unless dangerous Host-header fallback is explicitly enabled. Setting [\"*\"] means allow any browser origin and should be avoided outside tightly controlled local testing.", + "hasChildren": true + }, + { + "path": "gateway.controlUi.allowedOrigins.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.controlUi.allowInsecureAuth", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "advanced", + "network", + "security" + ], + "label": "Insecure Control UI Auth Toggle", + "help": "Loosens strict browser auth checks for Control UI when you must run a non-standard setup. Keep this off unless you trust your network and proxy path, because impersonation risk is higher.", + "hasChildren": false + }, + { + "path": "gateway.controlUi.basePath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network", + "storage" + ], + "label": "Control UI Base Path", + "help": "Optional URL prefix where the Control UI is served (e.g. /openclaw).", + "hasChildren": false + }, + { + "path": "gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "advanced", + "network", + "security" + ], + "label": "Dangerously Allow Host-Header Origin Fallback", + "help": "DANGEROUS toggle that enables Host-header based origin fallback for Control UI/WebChat websocket checks. This mode is supported when your deployment intentionally relies on Host-header origin policy; explicit gateway.controlUi.allowedOrigins remains the recommended hardened default.", + "hasChildren": false + }, + { + "path": "gateway.controlUi.dangerouslyDisableDeviceAuth", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "advanced", + "network", + "security" + ], + "label": "Dangerously Disable Control UI Device Auth", + "help": "Disables Control UI device identity checks and relies on token/password only. Use only for short-lived debugging on trusted networks, then turn it off immediately.", + "hasChildren": false + }, + { + "path": "gateway.controlUi.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Control UI Enabled", + "help": "Enables serving the gateway Control UI from the gateway HTTP process when true. Keep enabled for local administration, and disable when an external control surface replaces it.", + "hasChildren": false + }, + { + "path": "gateway.controlUi.root", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Control UI Assets Root", + "help": "Optional filesystem root for Control UI assets (defaults to dist/control-ui).", + "hasChildren": false + }, + { + "path": "gateway.customBindHost", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Custom Bind Host", + "help": "Explicit bind host/IP used when gateway.bind is set to custom for manual interface targeting. Use a precise address and avoid wildcard binds unless external exposure is required.", + "hasChildren": false + }, + { + "path": "gateway.http", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway HTTP API", + "help": "Gateway HTTP API configuration grouping endpoint toggles and transport-facing API exposure controls. Keep only required endpoints enabled to reduce attack surface.", + "hasChildren": true + }, + { + "path": "gateway.http.endpoints", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway HTTP Endpoints", + "help": "HTTP endpoint feature toggles under the gateway API surface for compatibility routes and optional integrations. Enable endpoints intentionally and monitor access patterns after rollout.", + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.chatCompletions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.chatCompletions.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "OpenAI Chat Completions Endpoint", + "help": "Enable the OpenAI-compatible `POST /v1/chat/completions` endpoint (default: false).", + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.chatCompletions.images", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "network" + ], + "label": "OpenAI Chat Completions Image Limits", + "help": "Image fetch/validation controls for OpenAI-compatible `image_url` parts.", + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.chatCompletions.images.allowedMimes", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "media", + "network" + ], + "label": "OpenAI Chat Completions Image MIME Allowlist", + "help": "Allowed MIME types for `image_url` parts (case-insensitive list).", + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.chatCompletions.images.allowedMimes.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.chatCompletions.images.allowUrl", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "media", + "network" + ], + "label": "OpenAI Chat Completions Allow Image URLs", + "help": "Allow server-side URL fetches for `image_url` parts (default: false; data URIs remain supported). Set this to `false` to disable URL fetching entirely.", + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.chatCompletions.images.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "network", + "performance" + ], + "label": "OpenAI Chat Completions Image Max Bytes", + "help": "Max bytes per fetched/decoded `image_url` image (default: 10MB).", + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.chatCompletions.images.maxRedirects", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "network", + "performance", + "storage" + ], + "label": "OpenAI Chat Completions Image Max Redirects", + "help": "Max HTTP redirects allowed when fetching `image_url` URLs (default: 3).", + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.chatCompletions.images.timeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "network", + "performance" + ], + "label": "OpenAI Chat Completions Image Timeout (ms)", + "help": "Timeout in milliseconds for `image_url` URL fetches (default: 10000).", + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.chatCompletions.images.urlAllowlist", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "media", + "network" + ], + "label": "OpenAI Chat Completions Image URL Allowlist", + "help": "Optional hostname allowlist for `image_url` URL fetches; supports exact hosts and `*.example.com` wildcards. Empty or omitted lists mean no hostname allowlist restriction.", + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.chatCompletions.images.urlAllowlist.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.chatCompletions.maxBodyBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network", + "performance" + ], + "label": "OpenAI Chat Completions Max Body Bytes", + "help": "Max request body size in bytes for `/v1/chat/completions` (default: 20MB).", + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.chatCompletions.maxImageParts", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "network", + "performance" + ], + "label": "OpenAI Chat Completions Max Image Parts", + "help": "Max number of `image_url` parts accepted from the latest user message (default: 8).", + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.chatCompletions.maxTotalImageBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "network", + "performance" + ], + "label": "OpenAI Chat Completions Max Total Image Bytes", + "help": "Max cumulative decoded bytes across all `image_url` parts in one request (default: 20MB).", + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.responses.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.files", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.responses.files.allowedMimes", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.responses.files.allowedMimes.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.files.allowUrl", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.files.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.files.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.files.maxRedirects", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.files.pdf", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.responses.files.pdf.maxPages", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.files.pdf.maxPixels", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.files.pdf.minTextChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.files.timeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.files.urlAllowlist", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.responses.files.urlAllowlist.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.images", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.responses.images.allowedMimes", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.responses.images.allowedMimes.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.images.allowUrl", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.images.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.images.maxRedirects", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.images.timeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.images.urlAllowlist", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.http.endpoints.responses.images.urlAllowlist.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.maxBodyBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.endpoints.responses.maxUrlParts", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.http.securityHeaders", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway HTTP Security Headers", + "help": "Optional HTTP response security headers applied by the gateway process itself. Prefer setting these at your reverse proxy when TLS terminates there.", + "hasChildren": true + }, + { + "path": "gateway.http.securityHeaders.strictTransportSecurity", + "kind": "core", + "type": [ + "boolean", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Strict Transport Security Header", + "help": "Value for the Strict-Transport-Security response header. Set only on HTTPS origins that you fully control; use false to explicitly disable.", + "hasChildren": false + }, + { + "path": "gateway.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Mode", + "help": "Gateway operation mode: \"local\" runs channels and agent runtime on this host, while \"remote\" connects through remote transport. Keep \"local\" unless you intentionally run a split remote gateway topology.", + "hasChildren": false + }, + { + "path": "gateway.nodes", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.nodes.allowCommands", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "network" + ], + "label": "Gateway Node Allowlist (Extra Commands)", + "help": "Extra node.invoke commands to allow beyond the gateway defaults (array of command strings). Enabling dangerous commands here is a security-sensitive override and is flagged by `openclaw security audit`.", + "hasChildren": true + }, + { + "path": "gateway.nodes.allowCommands.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.nodes.browser", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "gateway.nodes.browser.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Node Browser Mode", + "help": "Node browser routing (\"auto\" = pick single connected browser node, \"manual\" = require node param, \"off\" = disable).", + "hasChildren": false + }, + { + "path": "gateway.nodes.browser.node", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Node Browser Pin", + "help": "Pin browser routing to a specific node id or name (optional).", + "hasChildren": false + }, + { + "path": "gateway.nodes.denyCommands", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "network" + ], + "label": "Gateway Node Denylist", + "help": "Node command names to block even if present in node claims or default allowlist (exact command-name matching only, e.g. `system.run`; does not inspect shell text inside that command).", + "hasChildren": true + }, + { + "path": "gateway.nodes.denyCommands.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.port", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Port", + "help": "TCP port used by the gateway listener for API, control UI, and channel-facing ingress paths. Use a dedicated port and avoid collisions with reverse proxies or local developer services.", + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "gateway.reload.deferralTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network", + "performance", + "reliability" + ], + "label": "Restart Deferral Timeout (ms)", + "help": "Maximum time (ms) to wait for in-flight operations to complete before forcing a SIGUSR1 restart. Default: 300000 (5 minutes). Lower values risk aborting active subagent LLM calls.", + "hasChildren": false + }, + { + "path": "gateway.reload.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network", + "reliability" + ], + "label": "Config Reload Mode", + "help": "Controls how config edits are applied: \"off\" ignores live edits, \"restart\" always restarts, \"hot\" applies in-process, and \"hybrid\" tries hot then restarts if required. Keep \"hybrid\" for safest routine updates.", + "hasChildren": false + }, + { + "path": "gateway.remote", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Remote Gateway", + "help": "Remote gateway connection settings for direct or SSH transport when this instance proxies to another runtime host. Use remote mode only when split-host operation is intentionally configured.", + "hasChildren": true + }, + { + "path": "gateway.remote.password", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "network", + "security" + ], + "label": "Remote Gateway Password", + "help": "Password credential used for remote gateway authentication when password mode is enabled. Keep this secret managed externally and avoid plaintext values in committed config.", + "hasChildren": true + }, + { + "path": "gateway.remote.password.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.remote.password.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.remote.password.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.remote.sshIdentity", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Remote Gateway SSH Identity", + "help": "Optional SSH identity file path (passed to ssh -i).", + "hasChildren": false + }, + { + "path": "gateway.remote.sshTarget", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Remote Gateway SSH Target", + "help": "Remote gateway over SSH (tunnels the gateway port to localhost). Format: user@host or user@host:port.", + "hasChildren": false + }, + { + "path": "gateway.remote.tlsFingerprint", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "auth", + "network", + "security" + ], + "label": "Remote Gateway TLS Fingerprint", + "help": "Expected sha256 TLS fingerprint for the remote gateway (pin to avoid MITM).", + "hasChildren": false + }, + { + "path": "gateway.remote.token", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "network", + "security" + ], + "label": "Remote Gateway Token", + "help": "Bearer token used to authenticate this client to a remote gateway in token-auth deployments. Store via secret/env substitution and rotate alongside remote gateway auth changes.", + "hasChildren": true + }, + { + "path": "gateway.remote.token.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.remote.token.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.remote.token.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.remote.transport", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Remote Gateway Transport", + "help": "Remote connection transport: \"direct\" uses configured URL connectivity, while \"ssh\" tunnels through SSH. Use SSH when you need encrypted tunnel semantics without exposing remote ports.", + "hasChildren": false + }, + { + "path": "gateway.remote.url", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Remote Gateway URL", + "help": "Remote Gateway WebSocket URL (ws:// or wss://).", + "hasChildren": false + }, + { + "path": "gateway.tailscale", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Tailscale", + "help": "Tailscale integration settings for Serve/Funnel exposure and lifecycle handling on gateway start/exit. Keep off unless your deployment intentionally relies on Tailscale ingress.", + "hasChildren": true + }, + { + "path": "gateway.tailscale.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Tailscale Mode", + "help": "Tailscale publish mode: \"off\", \"serve\", or \"funnel\" for private or public exposure paths. Use \"serve\" for tailnet-only access and \"funnel\" only when public internet reachability is required.", + "hasChildren": false + }, + { + "path": "gateway.tailscale.resetOnExit", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Tailscale Reset on Exit", + "help": "Resets Tailscale Serve/Funnel state on gateway exit to avoid stale published routes after shutdown. Keep enabled unless another controller manages publish lifecycle outside the gateway.", + "hasChildren": false + }, + { + "path": "gateway.tls", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway TLS", + "help": "TLS certificate and key settings for terminating HTTPS directly in the gateway process. Use explicit certificates in production and avoid plaintext exposure on untrusted networks.", + "hasChildren": true + }, + { + "path": "gateway.tls.autoGenerate", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway TLS Auto-Generate Cert", + "help": "Auto-generates a local TLS certificate/key pair when explicit files are not configured. Use only for local/dev setups and replace with real certificates for production traffic.", + "hasChildren": false + }, + { + "path": "gateway.tls.caPath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network", + "storage" + ], + "label": "Gateway TLS CA Path", + "help": "Optional CA bundle path for client verification or custom trust-chain requirements at the gateway edge. Use this when private PKI or custom certificate chains are part of deployment.", + "hasChildren": false + }, + { + "path": "gateway.tls.certPath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network", + "storage" + ], + "label": "Gateway TLS Certificate Path", + "help": "Filesystem path to the TLS certificate file used by the gateway when TLS is enabled. Use managed certificate paths and keep renewal automation aligned with this location.", + "hasChildren": false + }, + { + "path": "gateway.tls.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway TLS Enabled", + "help": "Enables TLS termination at the gateway listener so clients connect over HTTPS/WSS directly. Keep enabled for direct internet exposure or any untrusted network boundary.", + "hasChildren": false + }, + { + "path": "gateway.tls.keyPath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network", + "storage" + ], + "label": "Gateway TLS Key Path", + "help": "Filesystem path to the TLS private key file used by the gateway when TLS is enabled. Keep this key file permission-restricted and rotate per your security policy.", + "hasChildren": false + }, + { + "path": "gateway.tools", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Tool Exposure Policy", + "help": "Gateway-level tool exposure allow/deny policy that can restrict runtime tool availability independent of agent/tool profiles. Use this for coarse emergency controls and production hardening.", + "hasChildren": true + }, + { + "path": "gateway.tools.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "network" + ], + "label": "Gateway Tool Allowlist", + "help": "Explicit gateway-level tool allowlist when you want a narrow set of tools available at runtime. Use this for locked-down environments where tool scope must be tightly controlled.", + "hasChildren": true + }, + { + "path": "gateway.tools.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.tools.deny", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "network" + ], + "label": "Gateway Tool Denylist", + "help": "Explicit gateway-level tool denylist to block risky tools even if lower-level policies allow them. Use deny rules for emergency response and defense-in-depth hardening.", + "hasChildren": true + }, + { + "path": "gateway.tools.deny.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "gateway.trustedProxies", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Gateway Trusted Proxy CIDRs", + "help": "CIDR/IP allowlist of upstream proxies permitted to provide forwarded client identity headers. Keep this list narrow so untrusted hops cannot impersonate users.", + "hasChildren": true + }, + { + "path": "gateway.trustedProxies.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hooks", + "help": "Inbound webhook automation surface for mapping external events into wake or agent actions in OpenClaw. Keep this locked down with explicit token/session/agent controls before exposing it beyond trusted networks.", + "hasChildren": true + }, + { + "path": "hooks.allowedAgentIds", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Hooks Allowed Agent IDs", + "help": "Allowlist of agent IDs that hook mappings are allowed to target when selecting execution agents. Use this to constrain automation events to dedicated service agents and reduce blast radius if a hook token is exposed.", + "hasChildren": true + }, + { + "path": "hooks.allowedAgentIds.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.allowedSessionKeyPrefixes", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Hooks Allowed Session Key Prefixes", + "help": "Allowlist of accepted session-key prefixes for inbound hook requests when caller-provided keys are enabled. Use narrow prefixes to prevent arbitrary session-key injection.", + "hasChildren": true + }, + { + "path": "hooks.allowedSessionKeyPrefixes.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.allowRequestSessionKey", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Hooks Allow Request Session Key", + "help": "Allows callers to supply a session key in hook requests when true, enabling caller-controlled routing. Keep false unless trusted integrators explicitly need custom session threading.", + "hasChildren": false + }, + { + "path": "hooks.defaultSessionKey", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Hooks Default Session Key", + "help": "Fallback session key used for hook deliveries when a request does not provide one through allowed channels. Use a stable but scoped key to avoid mixing unrelated automation conversations.", + "hasChildren": false + }, + { + "path": "hooks.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hooks Enabled", + "help": "Enables the hooks endpoint and mapping execution pipeline for inbound webhook requests. Keep disabled unless you are actively routing external events into the gateway.", + "hasChildren": false + }, + { + "path": "hooks.gmail", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook", + "help": "Gmail push integration settings used for Pub/Sub notifications and optional local callback serving. Keep this scoped to dedicated Gmail automation accounts where possible.", + "hasChildren": true + }, + { + "path": "hooks.gmail.account", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Account", + "help": "Google account identifier used for Gmail watch/subscription operations in this hook integration. Use a dedicated automation mailbox account to isolate operational permissions.", + "hasChildren": false + }, + { + "path": "hooks.gmail.allowUnsafeExternalContent", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Gmail Hook Allow Unsafe External Content", + "help": "Allows less-sanitized external Gmail content to pass into processing when enabled. Keep disabled for safer defaults, and enable only for trusted mail streams with controlled transforms.", + "hasChildren": false + }, + { + "path": "hooks.gmail.hookUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Callback URL", + "help": "Public callback URL Gmail or intermediaries invoke to deliver notifications into this hook pipeline. Keep this URL protected with token validation and restricted network exposure.", + "hasChildren": false + }, + { + "path": "hooks.gmail.includeBody", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Include Body", + "help": "When true, fetch and include email body content for downstream mapping/agent processing. Keep false unless body text is required, because this increases payload size and sensitivity.", + "hasChildren": false + }, + { + "path": "hooks.gmail.label", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Label", + "help": "Optional Gmail label filter limiting which labeled messages trigger hook events. Keep filters narrow to avoid flooding automations with unrelated inbox traffic.", + "hasChildren": false + }, + { + "path": "hooks.gmail.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Gmail Hook Max Body Bytes", + "help": "Maximum Gmail payload bytes processed per event when includeBody is enabled. Keep conservative limits to reduce oversized message processing cost and risk.", + "hasChildren": false + }, + { + "path": "hooks.gmail.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Gmail Hook Model Override", + "help": "Optional model override for Gmail-triggered runs when mailbox automations should use dedicated model behavior. Keep unset to inherit agent defaults unless mailbox tasks need specialization.", + "hasChildren": false + }, + { + "path": "hooks.gmail.pushToken", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security" + ], + "label": "Gmail Hook Push Token", + "help": "Shared secret token required on Gmail push hook callbacks before processing notifications. Use env substitution and rotate if callback endpoints are exposed externally.", + "hasChildren": false + }, + { + "path": "hooks.gmail.renewEveryMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Renew Interval (min)", + "help": "Renewal cadence in minutes for Gmail watch subscriptions to prevent expiration. Set below provider expiration windows and monitor renew failures in logs.", + "hasChildren": false + }, + { + "path": "hooks.gmail.serve", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Local Server", + "help": "Local callback server settings block for directly receiving Gmail notifications without a separate ingress layer. Enable only when this process should terminate webhook traffic itself.", + "hasChildren": true + }, + { + "path": "hooks.gmail.serve.bind", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Server Bind Address", + "help": "Bind address for the local Gmail callback HTTP server used when serving hooks directly. Keep loopback-only unless external ingress is intentionally required.", + "hasChildren": false + }, + { + "path": "hooks.gmail.serve.path", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Gmail Hook Server Path", + "help": "HTTP path on the local Gmail callback server where push notifications are accepted. Keep this consistent with subscription configuration to avoid dropped events.", + "hasChildren": false + }, + { + "path": "hooks.gmail.serve.port", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Server Port", + "help": "Port for the local Gmail callback HTTP server when serve mode is enabled. Use a dedicated port to avoid collisions with gateway/control interfaces.", + "hasChildren": false + }, + { + "path": "hooks.gmail.subscription", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Subscription", + "help": "Pub/Sub subscription consumed by the gateway to receive Gmail change notifications from the configured topic. Keep subscription ownership clear so multiple consumers do not race unexpectedly.", + "hasChildren": false + }, + { + "path": "hooks.gmail.tailscale", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Tailscale", + "help": "Tailscale exposure configuration block for publishing Gmail callbacks through Serve/Funnel routes. Use private tailnet modes before enabling any public ingress path.", + "hasChildren": true + }, + { + "path": "hooks.gmail.tailscale.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Tailscale Mode", + "help": "Tailscale exposure mode for Gmail callbacks: \"off\", \"serve\", or \"funnel\". Use \"serve\" for private tailnet delivery and \"funnel\" only when public internet ingress is required.", + "hasChildren": false + }, + { + "path": "hooks.gmail.tailscale.path", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Gmail Hook Tailscale Path", + "help": "Path published by Tailscale Serve/Funnel for Gmail callback forwarding when enabled. Keep it aligned with Gmail webhook config so requests reach the expected handler.", + "hasChildren": false + }, + { + "path": "hooks.gmail.tailscale.target", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Tailscale Target", + "help": "Local service target forwarded by Tailscale Serve/Funnel (for example http://127.0.0.1:8787). Use explicit loopback targets to avoid ambiguous routing.", + "hasChildren": false + }, + { + "path": "hooks.gmail.thinking", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Thinking Override", + "help": "Thinking effort override for Gmail-driven agent runs: \"off\", \"minimal\", \"low\", \"medium\", or \"high\". Keep modest defaults for routine inbox automations to control cost and latency.", + "hasChildren": false + }, + { + "path": "hooks.gmail.topic", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gmail Hook Pub/Sub Topic", + "help": "Google Pub/Sub topic name used by Gmail watch to publish change notifications for this account. Ensure the topic IAM grants Gmail publish access before enabling watches.", + "hasChildren": false + }, + { + "path": "hooks.internal", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Internal Hooks", + "help": "Internal hook runtime settings for bundled/custom event handlers loaded from module paths. Use this for trusted in-process automations and keep handler loading tightly scoped.", + "hasChildren": true + }, + { + "path": "hooks.internal.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Internal Hooks Enabled", + "help": "Enables processing for internal hook handlers and configured entries in the internal hook runtime. Keep disabled unless internal hook handlers are intentionally configured.", + "hasChildren": false + }, + { + "path": "hooks.internal.entries", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Internal Hook Entries", + "help": "Configured internal hook entry records used to register concrete runtime handlers and metadata. Keep entries explicit and versioned so production behavior is auditable.", + "hasChildren": true + }, + { + "path": "hooks.internal.entries.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "hooks.internal.entries.*.*", + "kind": "core", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.entries.*.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.entries.*.env", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "hooks.internal.entries.*.env.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.handlers", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Internal Hook Handlers", + "help": "List of internal event handlers mapping event names to modules and optional exports. Keep handler definitions explicit so event-to-code routing is auditable.", + "hasChildren": true + }, + { + "path": "hooks.internal.handlers.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "hooks.internal.handlers.*.event", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Internal Hook Event", + "help": "Internal event name that triggers this handler module when emitted by the runtime. Use stable event naming conventions to avoid accidental overlap across handlers.", + "hasChildren": false + }, + { + "path": "hooks.internal.handlers.*.export", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Internal Hook Export", + "help": "Optional named export for the internal hook handler function when module default export is not used. Set this when one module ships multiple handler entrypoints.", + "hasChildren": false + }, + { + "path": "hooks.internal.handlers.*.module", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Internal Hook Module", + "help": "Safe relative module path for the internal hook handler implementation loaded at runtime. Keep module files in reviewed directories and avoid dynamic path composition.", + "hasChildren": false + }, + { + "path": "hooks.internal.installs", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Internal Hook Install Records", + "help": "Install metadata for internal hook modules, including source and resolved artifacts for repeatable deployments. Use this as operational provenance and avoid manual drift edits.", + "hasChildren": true + }, + { + "path": "hooks.internal.installs.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "hooks.internal.installs.*.hooks", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "hooks.internal.installs.*.hooks.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.installedAt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.installPath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.integrity", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.resolvedAt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.resolvedName", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.resolvedSpec", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.resolvedVersion", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.shasum", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.sourcePath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.spec", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.installs.*.version", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.internal.load", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Internal Hook Loader", + "help": "Internal hook loader settings controlling where handler modules are discovered at startup. Use constrained load roots to reduce accidental module conflicts or shadowing.", + "hasChildren": true + }, + { + "path": "hooks.internal.load.extraDirs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Internal Hook Extra Directories", + "help": "Additional directories searched for internal hook modules beyond default load paths. Keep this minimal and controlled to reduce accidental module shadowing.", + "hasChildren": true + }, + { + "path": "hooks.internal.load.extraDirs.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.mappings", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mappings", + "help": "Ordered mapping rules that match inbound hook requests and choose wake or agent actions with optional delivery routing. Use specific mappings first to avoid broad pattern rules capturing everything.", + "hasChildren": true + }, + { + "path": "hooks.mappings.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "hooks.mappings.*.action", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Action", + "help": "Mapping action type: \"wake\" triggers agent wake flow, while \"agent\" sends directly to agent handling. Use \"agent\" for immediate execution and \"wake\" when heartbeat-driven processing is preferred.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.agentId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Agent ID", + "help": "Target agent ID for mapping execution when action routing should not use defaults. Use dedicated automation agents to isolate webhook behavior from interactive operator sessions.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.allowUnsafeExternalContent", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Hook Mapping Allow Unsafe External Content", + "help": "When true, mapping content may include less-sanitized external payload data in generated messages. Keep false by default and enable only for trusted sources with reviewed transform logic.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.channel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Delivery Channel", + "help": "Delivery channel override for mapping outputs (for example \"last\", \"telegram\", \"discord\", \"slack\", \"signal\", \"imessage\", or \"msteams\"). Keep channel overrides explicit to avoid accidental cross-channel sends.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.deliver", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Deliver Reply", + "help": "Controls whether mapping execution results are delivered back to a channel destination versus being processed silently. Disable delivery for background automations that should not post user-facing output.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.id", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping ID", + "help": "Optional stable identifier for a hook mapping entry used for auditing, troubleshooting, and targeted updates. Use unique IDs so logs and config diffs can reference mappings unambiguously.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.match", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Match", + "help": "Grouping object for mapping match predicates such as path and source before action routing is applied. Keep match criteria specific so unrelated webhook traffic does not trigger automations.", + "hasChildren": true + }, + { + "path": "hooks.mappings.*.match.path", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Hook Mapping Match Path", + "help": "Path match condition for a hook mapping, usually compared against the inbound request path. Use this to split automation behavior by webhook endpoint path families.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.match.source", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Match Source", + "help": "Source match condition for a hook mapping, typically set by trusted upstream metadata or adapter logic. Use stable source identifiers so routing remains deterministic across retries.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.messageTemplate", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Message Template", + "help": "Template for synthesizing structured mapping input into the final message content sent to the target action path. Keep templates deterministic so downstream parsing and behavior remain stable.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Hook Mapping Model Override", + "help": "Optional model override for mapping-triggered runs when automation should use a different model than agent defaults. Use this sparingly so behavior remains predictable across mapping executions.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.name", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Name", + "help": "Human-readable mapping display name used in diagnostics and operator-facing config UIs. Keep names concise and descriptive so routing intent is obvious during incident review.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.sessionKey", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "security", + "storage" + ], + "label": "Hook Mapping Session Key", + "help": "Explicit session key override for mapping-delivered messages to control thread continuity. Use stable scoped keys so repeated events correlate without leaking into unrelated conversations.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.textTemplate", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Text Template", + "help": "Text-only fallback template used when rich payload rendering is not desired or not supported. Use this to provide a concise, consistent summary string for chat delivery surfaces.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.thinking", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Thinking Override", + "help": "Optional thinking-effort override for mapping-triggered runs to tune latency versus reasoning depth. Keep low or minimal for high-volume hooks unless deeper reasoning is clearly required.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Hook Mapping Timeout (sec)", + "help": "Maximum runtime allowed for mapping action execution before timeout handling applies. Use tighter limits for high-volume webhook sources to prevent queue pileups.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.to", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Delivery Destination", + "help": "Destination identifier inside the selected channel when mapping replies should route to a fixed target. Verify provider-specific destination formats before enabling production mappings.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.transform", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Transform", + "help": "Transform configuration block defining module/export preprocessing before mapping action handling. Use transforms only from reviewed code paths and keep behavior deterministic for repeatable automation.", + "hasChildren": true + }, + { + "path": "hooks.mappings.*.transform.export", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Transform Export", + "help": "Named export to invoke from the transform module; defaults to module default export when omitted. Set this when one file hosts multiple transform handlers.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.transform.module", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Transform Module", + "help": "Relative transform module path loaded from hooks.transformsDir to rewrite incoming payloads before delivery. Keep modules local, reviewed, and free of path traversal patterns.", + "hasChildren": false + }, + { + "path": "hooks.mappings.*.wakeMode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hook Mapping Wake Mode", + "help": "Wake scheduling mode: \"now\" wakes immediately, while \"next-heartbeat\" defers until the next heartbeat cycle. Use deferred mode for lower-priority automations that can tolerate slight delay.", + "hasChildren": false + }, + { + "path": "hooks.maxBodyBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Hooks Max Body Bytes", + "help": "Maximum accepted webhook payload size in bytes before the request is rejected. Keep this bounded to reduce abuse risk and protect memory usage under bursty integrations.", + "hasChildren": false + }, + { + "path": "hooks.path", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Hooks Endpoint Path", + "help": "HTTP path used by the hooks endpoint (for example `/hooks`) on the gateway control server. Use a non-guessable path and combine it with token validation for defense in depth.", + "hasChildren": false + }, + { + "path": "hooks.presets", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Hooks Presets", + "help": "Named hook preset bundles applied at load time to seed standard mappings and behavior defaults. Keep preset usage explicit so operators can audit which automations are active.", + "hasChildren": true + }, + { + "path": "hooks.presets.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "hooks.token", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security" + ], + "label": "Hooks Auth Token", + "help": "Shared bearer token checked by hooks ingress for request authentication before mappings run. Treat holders as full-trust callers for the hook ingress surface, not as a separate non-owner role. Use environment substitution and rotate regularly when webhook endpoints are internet-accessible.", + "hasChildren": false + }, + { + "path": "hooks.transformsDir", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Hooks Transforms Directory", + "help": "Base directory for hook transform modules referenced by mapping transform.module paths. Use a controlled repo directory so dynamic imports remain reviewable and predictable.", + "hasChildren": false + }, + { + "path": "logging", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Logging", + "help": "Logging behavior controls for severity, output destinations, formatting, and sensitive-data redaction. Keep levels and redaction strict enough for production while preserving useful diagnostics.", + "hasChildren": true + }, + { + "path": "logging.consoleLevel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "Console Log Level", + "help": "Console-specific log threshold: \"silent\", \"fatal\", \"error\", \"warn\", \"info\", \"debug\", or \"trace\" for terminal output control. Use this to keep local console quieter while retaining richer file logging if needed.", + "hasChildren": false + }, + { + "path": "logging.consoleStyle", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "Console Log Style", + "help": "Console output format style: \"pretty\", \"compact\", or \"json\" based on operator and ingestion needs. Use json for machine parsing pipelines and pretty/compact for human-first terminal workflows.", + "hasChildren": false + }, + { + "path": "logging.file", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "storage" + ], + "label": "Log File Path", + "help": "Optional file path for persisted log output in addition to or instead of console logging. Use a managed writable path and align retention/rotation with your operational policy.", + "hasChildren": false + }, + { + "path": "logging.level", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "Log Level", + "help": "Primary log level threshold for runtime logger output: \"silent\", \"fatal\", \"error\", \"warn\", \"info\", \"debug\", or \"trace\". Keep \"info\" or \"warn\" for production, and use debug/trace only during investigation.", + "hasChildren": false + }, + { + "path": "logging.maxFileBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "logging.redactPatterns", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "privacy" + ], + "label": "Custom Redaction Patterns", + "help": "Additional custom redact regex patterns applied to log output before emission/storage. Use this to mask org-specific tokens and identifiers not covered by built-in redaction rules.", + "hasChildren": true + }, + { + "path": "logging.redactPatterns.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "logging.redactSensitive", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability", + "privacy" + ], + "label": "Sensitive Data Redaction Mode", + "help": "Sensitive redaction mode: \"off\" disables built-in masking, while \"tools\" redacts sensitive tool/config payload fields. Keep \"tools\" in shared logs unless you have isolated secure log sinks.", + "hasChildren": false + }, + { + "path": "mcp", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "MCP", + "help": "Global MCP server definitions managed by OpenClaw. Embedded Pi and other runtime adapters can consume these servers without storing them inside Pi-owned project settings.", + "hasChildren": true + }, + { + "path": "mcp.servers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "MCP Servers", + "help": "Named MCP server definitions. OpenClaw stores them in its own config and runtime adapters decide which transports are supported at execution time.", + "hasChildren": true + }, + { + "path": "mcp.servers.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "mcp.servers.*.*", + "kind": "core", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "mcp.servers.*.args", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "mcp.servers.*.args.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "mcp.servers.*.command", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "mcp.servers.*.cwd", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "mcp.servers.*.env", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "mcp.servers.*.env.*", + "kind": "core", + "type": [ + "boolean", + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "mcp.servers.*.url", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "mcp.servers.*.workingDirectory", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "path": "media.ttlHours", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Media Retention TTL (hours)", + "help": "Optional retention window in hours for persisted inbound media cleanup across the full media tree. Leave unset to preserve legacy behavior, or set values like 24 (1 day) or 168 (7 days) when you want automatic cleanup.", + "hasChildren": false + }, + { + "path": "memory", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory", + "help": "Memory backend configuration (global).", + "hasChildren": true + }, + { + "path": "memory.backend", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Memory Backend", + "help": "Selects the global memory engine: \"builtin\" uses OpenClaw memory internals, while \"qmd\" uses the QMD sidecar pipeline. Keep \"builtin\" unless you intentionally operate QMD.", + "hasChildren": false + }, + { + "path": "memory.citations", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Memory Citations Mode", + "help": "Controls citation visibility in replies: \"auto\" shows citations when useful, \"on\" always shows them, and \"off\" hides them. Keep \"auto\" for a balanced signal-to-noise default.", + "hasChildren": false + }, + { + "path": "memory.qmd", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "memory.qmd.command", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD Binary", + "help": "Sets the executable path for the `qmd` binary used by the QMD backend (default: resolved from PATH). Use an explicit absolute path when multiple qmd installs exist or PATH differs across environments.", + "hasChildren": false + }, + { + "path": "memory.qmd.includeDefaultMemory", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD Include Default Memory", + "help": "Automatically indexes default memory files (MEMORY.md and memory/**/*.md) into QMD collections. Keep enabled unless you want indexing controlled only through explicit custom paths.", + "hasChildren": false + }, + { + "path": "memory.qmd.limits", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "memory.qmd.limits.maxInjectedChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "QMD Max Injected Chars", + "help": "Caps how much QMD text can be injected into one turn across all hits. Use lower values to control prompt bloat and latency; raise only when context is consistently truncated.", + "hasChildren": false + }, + { + "path": "memory.qmd.limits.maxResults", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "QMD Max Results", + "help": "Limits how many QMD hits are returned into the agent loop for each recall request (default: 6). Increase for broader recall context, or lower to keep prompts tighter and faster.", + "hasChildren": false + }, + { + "path": "memory.qmd.limits.maxSnippetChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "QMD Max Snippet Chars", + "help": "Caps per-result snippet length extracted from QMD hits in characters (default: 700). Lower this when prompts bloat quickly, and raise only if answers consistently miss key details.", + "hasChildren": false + }, + { + "path": "memory.qmd.limits.timeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "QMD Search Timeout (ms)", + "help": "Sets per-query QMD search timeout in milliseconds (default: 4000). Increase for larger indexes or slower environments, and lower to keep request latency bounded.", + "hasChildren": false + }, + { + "path": "memory.qmd.mcporter", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD MCPorter", + "help": "Routes QMD work through mcporter (MCP runtime) instead of spawning `qmd` for each call. Use this when cold starts are expensive on large models; keep direct process mode for simpler local setups.", + "hasChildren": true + }, + { + "path": "memory.qmd.mcporter.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD MCPorter Enabled", + "help": "Routes QMD through an mcporter daemon instead of spawning qmd per request, reducing cold-start overhead for larger models. Keep disabled unless mcporter is installed and configured.", + "hasChildren": false + }, + { + "path": "memory.qmd.mcporter.serverName", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD MCPorter Server Name", + "help": "Names the mcporter server target used for QMD calls (default: qmd). Change only when your mcporter setup uses a custom server name for qmd mcp keep-alive.", + "hasChildren": false + }, + { + "path": "memory.qmd.mcporter.startDaemon", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD MCPorter Start Daemon", + "help": "Automatically starts the mcporter daemon when mcporter-backed QMD mode is enabled (default: true). Keep enabled unless process lifecycle is managed externally by your service supervisor.", + "hasChildren": false + }, + { + "path": "memory.qmd.paths", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD Extra Paths", + "help": "Adds custom directories or files to include in QMD indexing, each with an optional name and glob pattern. Use this for project-specific knowledge locations that are outside default memory paths.", + "hasChildren": true + }, + { + "path": "memory.qmd.paths.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "memory.qmd.paths.*.name", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "memory.qmd.paths.*.path", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "memory.qmd.paths.*.pattern", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "memory.qmd.scope", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD Surface Scope", + "help": "Defines which sessions/channels are eligible for QMD recall using session.sendPolicy-style rules. Keep default direct-only scope unless you intentionally want cross-chat memory sharing.", + "hasChildren": true + }, + { + "path": "memory.qmd.scope.default", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "memory.qmd.scope.rules", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "memory.qmd.scope.rules.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "memory.qmd.scope.rules.*.action", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "memory.qmd.scope.rules.*.match", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "memory.qmd.scope.rules.*.match.channel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "memory.qmd.scope.rules.*.match.chatType", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "memory.qmd.scope.rules.*.match.keyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "memory.qmd.scope.rules.*.match.rawKeyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "memory.qmd.sessions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "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 + }, + { + "path": "memory.qmd.sessions.retentionDays", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD Session Retention (days)", + "help": "Defines how long exported session files are kept before automatic pruning, in days (default: unlimited). Set a finite value for storage hygiene or compliance retention policies.", + "hasChildren": false + }, + { + "path": "memory.qmd.update", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "memory.qmd.update.commandTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "QMD Command Timeout (ms)", + "help": "Sets timeout for QMD maintenance commands such as collection list/add in milliseconds (default: 30000). Increase when running on slower disks or remote filesystems that delay command completion.", + "hasChildren": false + }, + { + "path": "memory.qmd.update.debounceMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "QMD Update Debounce (ms)", + "help": "Sets the minimum delay between consecutive QMD refresh attempts in milliseconds (default: 15000). Increase this if frequent file changes cause update thrash or unnecessary background load.", + "hasChildren": false + }, + { + "path": "memory.qmd.update.embedInterval", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "QMD Embed Interval", + "help": "Sets how often QMD recomputes embeddings (duration string, default: 60m; set 0 to disable periodic embeds). Lower intervals improve freshness but increase embedding workload and cost.", + "hasChildren": false + }, + { + "path": "memory.qmd.update.embedTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "QMD Embed Timeout (ms)", + "help": "Sets maximum runtime for each `qmd embed` cycle in milliseconds (default: 120000). Increase for heavier embedding workloads or slower hardware, and lower to fail fast under tight SLAs.", + "hasChildren": false + }, + { + "path": "memory.qmd.update.interval", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "QMD Update Interval", + "help": "Sets how often QMD refreshes indexes from source content (duration string, default: 5m). Shorter intervals improve freshness but increase background CPU and I/O.", + "hasChildren": false + }, + { + "path": "memory.qmd.update.onBoot", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD Update on Startup", + "help": "Runs an initial QMD update once during gateway startup (default: true). Keep enabled so recall starts from a fresh baseline; disable only when startup speed is more important than immediate freshness.", + "hasChildren": false + }, + { + "path": "memory.qmd.update.updateTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "QMD Update Timeout (ms)", + "help": "Sets maximum runtime for each `qmd update` cycle in milliseconds (default: 120000). Raise this for larger collections; lower it when you want quicker failure detection in automation.", + "hasChildren": false + }, + { + "path": "memory.qmd.update.waitForBootSync", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "QMD Wait for Boot Sync", + "help": "Blocks startup completion until the initial boot-time QMD sync finishes (default: false). Enable when you need fully up-to-date recall before serving traffic, and keep off for faster boot.", + "hasChildren": false + }, + { + "path": "messages", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Messages", + "help": "Message formatting, acknowledgment, queueing, debounce, and status reaction behavior for inbound/outbound chat flows. Use this section when channel responsiveness or message UX needs adjustment.", + "hasChildren": true + }, + { + "path": "messages.ackReaction", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Ack Reaction Emoji", + "help": "Emoji reaction used to acknowledge inbound messages (empty disables).", + "hasChildren": false + }, + { + "path": "messages.ackReactionScope", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "group-mentions", + "group-all", + "direct", + "all", + "off", + "none" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Ack Reaction Scope", + "help": "When to send ack reactions (\"group-mentions\", \"group-all\", \"direct\", \"all\", \"off\", \"none\"). \"off\"/\"none\" disables ack reactions entirely.", + "hasChildren": false + }, + { + "path": "messages.groupChat", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Group Chat Rules", + "help": "Group-message handling controls including mention triggers and history window sizing. Keep mention patterns narrow so group channels do not trigger on every message.", + "hasChildren": true + }, + { + "path": "messages.groupChat.historyLimit", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Group History Limit", + "help": "Maximum number of prior group messages loaded as context per turn for group sessions. Use higher values for richer continuity, or lower values for faster and cheaper responses.", + "hasChildren": false + }, + { + "path": "messages.groupChat.mentionPatterns", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Group Mention Patterns", + "help": "Safe case-insensitive regex patterns used to detect explicit mentions/trigger phrases in group chats. Use precise patterns to reduce false positives in high-volume channels; invalid or unsafe nested-repetition patterns are ignored.", + "hasChildren": true + }, + { + "path": "messages.groupChat.mentionPatterns.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.inbound", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Inbound Debounce", + "help": "Direct inbound debounce settings used before queue/turn processing starts. Configure this for provider-specific rapid message bursts from the same sender.", + "hasChildren": true + }, + { + "path": "messages.inbound.byChannel", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Inbound Debounce by Channel (ms)", + "help": "Per-channel inbound debounce overrides keyed by provider id in milliseconds. Use this where some providers send message fragments more aggressively than others.", + "hasChildren": true + }, + { + "path": "messages.inbound.byChannel.*", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.inbound.debounceMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Inbound Message Debounce (ms)", + "help": "Debounce window (ms) for batching rapid inbound messages from the same sender (0 to disable).", + "hasChildren": false + }, + { + "path": "messages.messagePrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Inbound Message Prefix", + "help": "Prefix text prepended to inbound user messages before they are handed to the agent runtime. Use this sparingly for channel context markers and keep it stable across sessions.", + "hasChildren": false + }, + { + "path": "messages.queue", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Inbound Queue", + "help": "Inbound message queue strategy used to buffer bursts before processing turns. Tune this for busy channels where sequential processing or batching behavior matters.", + "hasChildren": true + }, + { + "path": "messages.queue.byChannel", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Queue Mode by Channel", + "help": "Per-channel queue mode overrides keyed by provider id (for example telegram, discord, slack). Use this when one channel’s traffic pattern needs different queue behavior than global defaults.", + "hasChildren": true + }, + { + "path": "messages.queue.byChannel.discord", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.byChannel.imessage", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.byChannel.irc", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.byChannel.mattermost", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.byChannel.msteams", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.byChannel.signal", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.byChannel.slack", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.byChannel.telegram", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.byChannel.webchat", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.byChannel.whatsapp", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.cap", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Queue Capacity", + "help": "Maximum number of queued inbound items retained before drop policy applies. Keep caps bounded in noisy channels so memory usage remains predictable.", + "hasChildren": false + }, + { + "path": "messages.queue.debounceMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Queue Debounce (ms)", + "help": "Global queue debounce window in milliseconds before processing buffered inbound messages. Use higher values to coalesce rapid bursts, or lower values for reduced response latency.", + "hasChildren": false + }, + { + "path": "messages.queue.debounceMsByChannel", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Queue Debounce by Channel (ms)", + "help": "Per-channel debounce overrides for queue behavior keyed by provider id. Use this to tune burst handling independently for chat surfaces with different pacing.", + "hasChildren": true + }, + { + "path": "messages.queue.debounceMsByChannel.*", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.queue.drop", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Queue Drop Strategy", + "help": "Drop strategy when queue cap is exceeded: \"old\", \"new\", or \"summarize\". Use summarize when preserving intent matters, or old/new when deterministic dropping is preferred.", + "hasChildren": false + }, + { + "path": "messages.queue.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Queue Mode", + "help": "Queue behavior mode: \"steer\", \"followup\", \"collect\", \"steer-backlog\", \"steer+backlog\", \"queue\", or \"interrupt\". Keep conservative modes unless you intentionally need aggressive interruption/backlog semantics.", + "hasChildren": false + }, + { + "path": "messages.removeAckAfterReply", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Remove Ack Reaction After Reply", + "help": "Removes the acknowledgment reaction after final reply delivery when enabled. Keep enabled for cleaner UX in channels where persistent ack reactions create clutter.", + "hasChildren": false + }, + { + "path": "messages.responsePrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Outbound Response Prefix", + "help": "Prefix text prepended to outbound assistant replies before sending to channels. Use for lightweight branding/context tags and avoid long prefixes that reduce content density.", + "hasChildren": false + }, + { + "path": "messages.statusReactions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Status Reactions", + "help": "Lifecycle status reactions that update the emoji on the trigger message as the agent progresses (queued → thinking → tool → done/error).", + "hasChildren": true + }, + { + "path": "messages.statusReactions.emojis", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Status Reaction Emojis", + "help": "Override default status reaction emojis. Keys: thinking, compacting, tool, coding, web, done, error, stallSoft, stallHard. Must be valid Telegram reaction emojis.", + "hasChildren": true + }, + { + "path": "messages.statusReactions.emojis.coding", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.emojis.compacting", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.emojis.done", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.emojis.error", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.emojis.stallHard", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.emojis.stallSoft", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.emojis.thinking", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.emojis.tool", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.emojis.web", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable Status Reactions", + "help": "Enable lifecycle status reactions for Telegram. When enabled, the ack reaction becomes the initial 'queued' state and progresses through thinking, tool, done/error automatically. Default: false.", + "hasChildren": false + }, + { + "path": "messages.statusReactions.timing", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Status Reaction Timing", + "help": "Override default timing. Keys: debounceMs (700), stallSoftMs (25000), stallHardMs (60000), doneHoldMs (1500), errorHoldMs (2500).", + "hasChildren": true + }, + { + "path": "messages.statusReactions.timing.debounceMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.timing.doneHoldMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.timing.errorHoldMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.timing.stallHardMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.statusReactions.timing.stallSoftMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.suppressToolErrors", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Suppress Tool Error Warnings", + "help": "When true, suppress ⚠️ tool-error warnings from being shown to the user. The agent already sees errors in context and can retry. Default: false.", + "hasChildren": false + }, + { + "path": "messages.tts", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Message Text-to-Speech", + "help": "Text-to-speech policy for reading agent replies aloud on supported voice or audio surfaces. Keep disabled unless voice playback is part of your operator/user workflow.", + "hasChildren": true + }, + { + "path": "messages.tts.auto", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "off", + "always", + "inbound", + "tagged" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.edge", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "messages.tts.edge.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.edge.lang", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.edge.outputFormat", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.edge.pitch", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.edge.proxy", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.edge.rate", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.edge.saveSubtitles", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.edge.timeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.edge.voice", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.edge.volume", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "messages.tts.elevenlabs.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "media", + "security" + ], + "hasChildren": true + }, + { + "path": "messages.tts.elevenlabs.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.applyTextNormalization", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "auto", + "on", + "off" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.languageCode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.modelId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.seed", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.voiceId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.voiceSettings", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "messages.tts.elevenlabs.voiceSettings.similarityBoost", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.voiceSettings.speed", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.voiceSettings.stability", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.voiceSettings.style", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.elevenlabs.voiceSettings.useSpeakerBoost", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.maxTextLength", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.microsoft", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "messages.tts.microsoft.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.microsoft.lang", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.microsoft.outputFormat", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.microsoft.pitch", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.microsoft.proxy", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.microsoft.rate", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.microsoft.saveSubtitles", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.microsoft.timeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.microsoft.voice", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.microsoft.volume", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.mode", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "final", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.modelOverrides", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "messages.tts.modelOverrides.allowModelId", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.modelOverrides.allowNormalization", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.modelOverrides.allowProvider", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.modelOverrides.allowSeed", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.modelOverrides.allowText", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.modelOverrides.allowVoice", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.modelOverrides.allowVoiceSettings", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.modelOverrides.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.openai", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "messages.tts.openai.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "media", + "security" + ], + "hasChildren": true + }, + { + "path": "messages.tts.openai.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.openai.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.openai.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.openai.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.openai.instructions", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.openai.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.openai.speed", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.openai.voice", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.prefsPath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.provider", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.summaryModel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "messages.tts.timeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "meta", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Metadata", + "help": "Metadata fields automatically maintained by OpenClaw to record write/version history for this config file. Keep these values system-managed and avoid manual edits unless debugging migration history.", + "hasChildren": true + }, + { + "path": "meta.lastTouchedAt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Config Last Touched At", + "help": "ISO timestamp of the last config write (auto-set).", + "hasChildren": false + }, + { + "path": "meta.lastTouchedVersion", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Config Last Touched Version", + "help": "Auto-set when OpenClaw writes the config.", + "hasChildren": false + }, + { + "path": "models", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Models", + "help": "Model catalog root for provider definitions, merge/replace behavior, and optional Bedrock discovery integration. Keep provider definitions explicit and validated before relying on production failover paths.", + "hasChildren": true + }, + { + "path": "models.bedrockDiscovery", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Bedrock Model Discovery", + "help": "Automatic AWS Bedrock model discovery settings used to synthesize provider model entries from account visibility. Keep discovery scoped and refresh intervals conservative to reduce API churn.", + "hasChildren": true + }, + { + "path": "models.bedrockDiscovery.defaultContextWindow", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Bedrock Default Context Window", + "help": "Fallback context-window value applied to discovered models when provider metadata lacks explicit limits. Use realistic defaults to avoid oversized prompts that exceed true provider constraints.", + "hasChildren": false + }, + { + "path": "models.bedrockDiscovery.defaultMaxTokens", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "auth", + "models", + "performance", + "security" + ], + "label": "Bedrock Default Max Tokens", + "help": "Fallback max-token value applied to discovered models without explicit output token limits. Use conservative defaults to reduce truncation surprises and unexpected token spend.", + "hasChildren": false + }, + { + "path": "models.bedrockDiscovery.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Bedrock Discovery Enabled", + "help": "Enables periodic Bedrock model discovery and catalog refresh for Bedrock-backed providers. Keep disabled unless Bedrock is actively used and IAM permissions are correctly configured.", + "hasChildren": false + }, + { + "path": "models.bedrockDiscovery.providerFilter", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Bedrock Discovery Provider Filter", + "help": "Optional provider allowlist filter for Bedrock discovery so only selected providers are refreshed. Use this to limit discovery scope in multi-provider environments.", + "hasChildren": true + }, + { + "path": "models.bedrockDiscovery.providerFilter.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.bedrockDiscovery.refreshInterval", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models", + "performance" + ], + "label": "Bedrock Discovery Refresh Interval (s)", + "help": "Refresh cadence for Bedrock discovery polling in seconds to detect newly available models over time. Use longer intervals in production to reduce API cost and control-plane noise.", + "hasChildren": false + }, + { + "path": "models.bedrockDiscovery.region", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Bedrock Discovery Region", + "help": "AWS region used for Bedrock discovery calls when discovery is enabled for your deployment. Use the region where your Bedrock models are provisioned to avoid empty discovery results.", + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "path": "models.providers.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "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 + }, + { + "path": "models.providers.*.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "models.providers.*.headers.*", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "models", + "security" + ], + "hasChildren": true + }, + { + "path": "models.providers.*.headers.*.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.headers.*.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.headers.*.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "path": "models.providers.*.models.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "path": "models.providers.*.models.*.compat", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "models.providers.*.models.*.compat.maxTokensField", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.nativeWebSearchTool", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.requiresAssistantAfterToolResult", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.requiresMistralToolIds", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.requiresOpenAiAnthropicToolPayload", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.requiresThinkingAsText", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.requiresToolResultName", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.supportsDeveloperRole", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.supportsReasoningEffort", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.supportsStore", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.supportsStrictMode", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.supportsTools", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.supportsUsageInStreaming", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.thinkingFormat", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.toolCallArgumentsEncoding", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.compat.toolSchemaProfile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.contextWindow", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.cost", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "models.providers.*.models.*.cost.cacheRead", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.cost.cacheWrite", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.cost.input", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.cost.output", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "models.providers.*.models.*.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.input", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "models.providers.*.models.*.input.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.maxTokens", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.name", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "models.providers.*.models.*.reasoning", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "nodeHost", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Node Host", + "help": "Node host controls for features exposed from this gateway node to other nodes or clients. Keep defaults unless you intentionally proxy local capabilities across your node network.", + "hasChildren": true + }, + { + "path": "nodeHost.browserProxy", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Node Browser Proxy", + "help": "Groups browser-proxy settings for exposing local browser control through node routing. Enable only when remote node workflows need your local browser profiles.", + "hasChildren": true + }, + { + "path": "nodeHost.browserProxy.allowProfiles", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "network", + "storage" + ], + "label": "Node Browser Proxy Allowed Profiles", + "help": "Optional allowlist of browser profile names exposed through node proxy routing. Leave empty to expose all configured profiles, or use a tight list to enforce least-privilege profile access.", + "hasChildren": true + }, + { + "path": "nodeHost.browserProxy.allowProfiles.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "nodeHost.browserProxy.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "network" + ], + "label": "Node Browser Proxy Enabled", + "help": "Expose the local browser control server through node proxy routing so remote clients can use this host's browser capabilities. Keep disabled unless remote automation explicitly depends on it.", + "hasChildren": false + }, + { + "path": "plugins", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugins", + "help": "Plugin system controls for enabling extensions, constraining load scope, configuring entries, and tracking installs. Keep plugin policy explicit and least-privilege in production environments.", + "hasChildren": true + }, + { + "path": "plugins.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Allowlist", + "help": "Optional allowlist of plugin IDs; when set, only listed plugins are eligible to load. Use this to enforce approved extension inventories in controlled environments.", + "hasChildren": true + }, + { + "path": "plugins.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.deny", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Denylist", + "help": "Optional denylist of plugin IDs that are blocked even if allowlists or paths include them. Use deny rules for emergency rollback and hard blocks on risky plugins.", + "hasChildren": true + }, + { + "path": "plugins.deny.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable Plugins", + "help": "Enable or disable plugin/extension loading globally during startup and config reload (default: true). Keep enabled only when extension capabilities are required by your deployment.", + "hasChildren": false + }, + { + "path": "plugins.entries", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Entries", + "help": "Per-plugin settings keyed by plugin ID including enablement and plugin-specific runtime configuration payloads. Use this for scoped plugin tuning without changing global loader policy.", + "hasChildren": true + }, + { + "path": "plugins.entries.*", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.*.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Config", + "help": "Plugin-defined configuration payload interpreted by that plugin's own schema and validation rules. Use only documented fields from the plugin to prevent ignored or invalid settings.", + "hasChildren": true + }, + { + "path": "plugins.entries.*.config.*", + "kind": "plugin", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.*.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Enabled", + "help": "Per-plugin enablement override for a specific entry, applied on top of global plugin policy (restart required). Use this to stage plugin rollout gradually across environments.", + "hasChildren": false + }, + { + "path": "plugins.entries.*.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.*.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.*.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.*.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.*.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.*.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.acpx", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACPX Runtime", + "help": "ACP runtime backend powered by acpx with configurable command path and version policy. (plugin: acpx)", + "hasChildren": true + }, + { + "path": "plugins.entries.acpx.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ACPX Runtime Config", + "help": "Plugin-defined config payload for acpx.", + "hasChildren": true + }, + { + "path": "plugins.entries.acpx.config.command", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "acpx Command", + "help": "Optional path/command override for acpx (for example /home/user/repos/acpx/dist/cli.js). Leave unset to use plugin-local bundled acpx.", + "hasChildren": false + }, + { + "path": "plugins.entries.acpx.config.cwd", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Default Working Directory", + "help": "Default cwd for ACP session operations when not set per session.", + "hasChildren": false + }, + { + "path": "plugins.entries.acpx.config.expectedVersion", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Expected acpx Version", + "help": "Exact version to enforce (for example 0.1.16) or \"any\" to skip strict version matching.", + "hasChildren": false + }, + { + "path": "plugins.entries.acpx.config.mcpServers", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "MCP Servers", + "help": "Named MCP server definitions to inject into ACPX-backed session bootstrap. Each entry needs a command and can include args and env.", + "hasChildren": true + }, + { + "path": "plugins.entries.acpx.config.mcpServers.*", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.acpx.config.mcpServers.*.args", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.acpx.config.mcpServers.*.args.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.acpx.config.mcpServers.*.command", + "kind": "plugin", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.acpx.config.mcpServers.*.env", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.acpx.config.mcpServers.*.env.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "plugins.entries.acpx.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable ACPX Runtime", + "hasChildren": false + }, + { + "path": "plugins.entries.acpx.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.acpx.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.acpx.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.acpx.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.acpx.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.acpx.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.amazon-bedrock", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/amazon-bedrock-provider", + "help": "OpenClaw Amazon Bedrock provider plugin (plugin: amazon-bedrock)", + "hasChildren": true + }, + { + "path": "plugins.entries.amazon-bedrock.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/amazon-bedrock-provider Config", + "help": "Plugin-defined config payload for amazon-bedrock.", + "hasChildren": false + }, + { + "path": "plugins.entries.amazon-bedrock.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/amazon-bedrock-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.amazon-bedrock.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.amazon-bedrock.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.amazon-bedrock.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.amazon-bedrock.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.amazon-bedrock.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "plugins.entries.anthropic.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/anthropic-provider", + "hasChildren": false + }, + { + "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 + }, + { + "path": "plugins.entries.anthropic.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.anthropic.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.anthropic.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.anthropic.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.anthropic.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.bluebubbles", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/bluebubbles", + "help": "OpenClaw BlueBubbles channel plugin (plugin: bluebubbles)", + "hasChildren": true + }, + { + "path": "plugins.entries.bluebubbles.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/bluebubbles Config", + "help": "Plugin-defined config payload for bluebubbles.", + "hasChildren": false + }, + { + "path": "plugins.entries.bluebubbles.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/bluebubbles", + "hasChildren": false + }, + { + "path": "plugins.entries.bluebubbles.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.bluebubbles.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.bluebubbles.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.bluebubbles.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.bluebubbles.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.bluebubbles.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.brave", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/brave-plugin", + "help": "OpenClaw Brave plugin (plugin: brave)", + "hasChildren": true + }, + { + "path": "plugins.entries.brave.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/brave-plugin Config", + "help": "Plugin-defined config payload for brave.", + "hasChildren": true + }, + { + "path": "plugins.entries.brave.config.webSearch", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.brave.config.webSearch.apiKey", + "kind": "plugin", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security" + ], + "label": "Brave Search API Key", + "help": "Brave Search API key (fallback: BRAVE_API_KEY env var).", + "hasChildren": false + }, + { + "path": "plugins.entries.brave.config.webSearch.mode", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "web", + "llm-context" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Brave Search Mode", + "help": "Brave Search mode: web or llm-context.", + "hasChildren": false + }, + { + "path": "plugins.entries.brave.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/brave-plugin", + "hasChildren": false + }, + { + "path": "plugins.entries.brave.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.brave.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.brave.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.brave.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.brave.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.brave.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.byteplus", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/byteplus-provider", + "help": "OpenClaw BytePlus provider plugin (plugin: byteplus)", + "hasChildren": true + }, + { + "path": "plugins.entries.byteplus.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/byteplus-provider Config", + "help": "Plugin-defined config payload for byteplus.", + "hasChildren": false + }, + { + "path": "plugins.entries.byteplus.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/byteplus-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.byteplus.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.byteplus.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.byteplus.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.byteplus.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.byteplus.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.byteplus.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.chutes", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/chutes-provider", + "help": "OpenClaw Chutes.ai provider plugin (plugin: chutes)", + "hasChildren": true + }, + { + "path": "plugins.entries.chutes.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/chutes-provider Config", + "help": "Plugin-defined config payload for chutes.", + "hasChildren": false + }, + { + "path": "plugins.entries.chutes.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/chutes-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.chutes.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.chutes.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.chutes.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.chutes.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.chutes.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.chutes.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.cloudflare-ai-gateway", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/cloudflare-ai-gateway-provider", + "help": "OpenClaw Cloudflare AI Gateway provider plugin (plugin: cloudflare-ai-gateway)", + "hasChildren": true + }, + { + "path": "plugins.entries.cloudflare-ai-gateway.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/cloudflare-ai-gateway-provider Config", + "help": "Plugin-defined config payload for cloudflare-ai-gateway.", + "hasChildren": false + }, + { + "path": "plugins.entries.cloudflare-ai-gateway.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/cloudflare-ai-gateway-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.cloudflare-ai-gateway.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.cloudflare-ai-gateway.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.cloudflare-ai-gateway.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.cloudflare-ai-gateway.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.cloudflare-ai-gateway.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.cloudflare-ai-gateway.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.copilot-proxy", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/copilot-proxy", + "help": "OpenClaw Copilot Proxy provider plugin (plugin: copilot-proxy)", + "hasChildren": true + }, + { + "path": "plugins.entries.copilot-proxy.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/copilot-proxy Config", + "help": "Plugin-defined config payload for copilot-proxy.", + "hasChildren": false + }, + { + "path": "plugins.entries.copilot-proxy.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/copilot-proxy", + "hasChildren": false + }, + { + "path": "plugins.entries.copilot-proxy.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.copilot-proxy.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.copilot-proxy.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.copilot-proxy.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.copilot-proxy.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.copilot-proxy.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.device-pair", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Device Pairing", + "help": "Generate setup codes and approve device pairing requests. (plugin: device-pair)", + "hasChildren": true + }, + { + "path": "plugins.entries.device-pair.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Device Pairing Config", + "help": "Plugin-defined config payload for device-pair.", + "hasChildren": true + }, + { + "path": "plugins.entries.device-pair.config.publicUrl", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gateway URL", + "help": "Public WebSocket URL used for /pair setup codes (ws/wss or http/https).", + "hasChildren": false + }, + { + "path": "plugins.entries.device-pair.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable Device Pairing", + "hasChildren": false + }, + { + "path": "plugins.entries.device-pair.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.device-pair.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.device-pair.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.device-pair.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.device-pair.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.device-pair.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.diagnostics-otel", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "@openclaw/diagnostics-otel", + "help": "OpenClaw diagnostics OpenTelemetry exporter (plugin: diagnostics-otel)", + "hasChildren": true + }, + { + "path": "plugins.entries.diagnostics-otel.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "@openclaw/diagnostics-otel Config", + "help": "Plugin-defined config payload for diagnostics-otel.", + "hasChildren": false + }, + { + "path": "plugins.entries.diagnostics-otel.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "observability" + ], + "label": "Enable @openclaw/diagnostics-otel", + "hasChildren": false + }, + { + "path": "plugins.entries.diagnostics-otel.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.diagnostics-otel.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.diagnostics-otel.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.diagnostics-otel.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.diagnostics-otel.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.diagnostics-otel.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.diffs", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Diffs", + "help": "Read-only diff viewer and file renderer for agents. (plugin: diffs)", + "hasChildren": true + }, + { + "path": "plugins.entries.diffs.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Diffs Config", + "help": "Plugin-defined config payload for diffs.", + "hasChildren": true + }, + { + "path": "plugins.entries.diffs.config.defaults", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "plugins.entries.diffs.config.defaults.format", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "png", + "pdf" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.defaults.imageFormat", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "png", + "pdf" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.defaults.imageMaxWidth", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.defaults.imageQuality", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "standard", + "hq", + "print" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.defaults.imageScale", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.defaults.layout", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "unified", + "split" + ], + "defaultValue": "unified", + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Default Layout", + "help": "Initial diff layout shown in the viewer.", + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.defaults.lineSpacing", + "kind": "plugin", + "type": "number", + "required": false, + "defaultValue": 1.6, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Default Line Spacing", + "help": "Line-height multiplier applied to diff rows.", + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.defaults.mode", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "view", + "image", + "file", + "both" + ], + "defaultValue": "both", + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Default Output Mode", + "help": "Tool default when mode is omitted. Use view for canvas/gateway viewer, file for PNG/PDF, or both.", + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.defaults.showLineNumbers", + "kind": "plugin", + "type": "boolean", + "required": false, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Show Line Numbers", + "help": "Show line numbers by default.", + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.defaults.theme", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "light", + "dark" + ], + "defaultValue": "dark", + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Default Theme", + "help": "Initial viewer theme.", + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.defaults.wordWrap", + "kind": "plugin", + "type": "boolean", + "required": false, + "defaultValue": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Default Word Wrap", + "help": "Wrap long lines by default.", + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.config.security", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.diffs.config.security.allowRemoteViewer", + "kind": "plugin", + "type": "boolean", + "required": false, + "defaultValue": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Remote Viewer", + "help": "Allow non-loopback access to diff viewer URLs when the token path is known.", + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable Diffs", + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.diffs.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.diffs.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.diffs.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.diffs.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.discord", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/discord", + "help": "OpenClaw Discord channel plugin (plugin: discord)", + "hasChildren": true + }, + { + "path": "plugins.entries.discord.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/discord Config", + "help": "Plugin-defined config payload for discord.", + "hasChildren": false + }, + { + "path": "plugins.entries.discord.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/discord", + "hasChildren": false + }, + { + "path": "plugins.entries.discord.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.discord.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.discord.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.discord.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.discord.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.discord.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.elevenlabs", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/elevenlabs-speech", + "help": "OpenClaw ElevenLabs speech plugin (plugin: elevenlabs)", + "hasChildren": true + }, + { + "path": "plugins.entries.elevenlabs.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/elevenlabs-speech Config", + "help": "Plugin-defined config payload for elevenlabs.", + "hasChildren": false + }, + { + "path": "plugins.entries.elevenlabs.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/elevenlabs-speech", + "hasChildren": false + }, + { + "path": "plugins.entries.elevenlabs.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.elevenlabs.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.elevenlabs.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.elevenlabs.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.elevenlabs.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.elevenlabs.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.fal", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/fal-provider", + "help": "OpenClaw fal provider plugin (plugin: fal)", + "hasChildren": true + }, + { + "path": "plugins.entries.fal.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/fal-provider Config", + "help": "Plugin-defined config payload for fal.", + "hasChildren": false + }, + { + "path": "plugins.entries.fal.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/fal-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.fal.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.fal.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.fal.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.fal.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.fal.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.fal.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.feishu", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/feishu", + "help": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng) (plugin: feishu)", + "hasChildren": true + }, + { + "path": "plugins.entries.feishu.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/feishu Config", + "help": "Plugin-defined config payload for feishu.", + "hasChildren": false + }, + { + "path": "plugins.entries.feishu.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/feishu", + "hasChildren": false + }, + { + "path": "plugins.entries.feishu.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.feishu.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.feishu.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.feishu.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.feishu.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.feishu.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.firecrawl", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/firecrawl-plugin", + "help": "OpenClaw Firecrawl plugin (plugin: firecrawl)", + "hasChildren": true + }, + { + "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 + }, + { + "path": "plugins.entries.firecrawl.config.webSearch", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "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 + }, + { + "path": "plugins.entries.firecrawl.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/firecrawl-plugin", + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "path": "plugins.entries.firecrawl.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.firecrawl.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.firecrawl.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.firecrawl.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.github-copilot", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/github-copilot-provider", + "help": "OpenClaw GitHub Copilot provider plugin (plugin: github-copilot)", + "hasChildren": true + }, + { + "path": "plugins.entries.github-copilot.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/github-copilot-provider Config", + "help": "Plugin-defined config payload for github-copilot.", + "hasChildren": false + }, + { + "path": "plugins.entries.github-copilot.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/github-copilot-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.github-copilot.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.github-copilot.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.github-copilot.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.github-copilot.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.github-copilot.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.github-copilot.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.google", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/google-plugin", + "help": "OpenClaw Google plugin (plugin: google)", + "hasChildren": true + }, + { + "path": "plugins.entries.google.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/google-plugin Config", + "help": "Plugin-defined config payload for google.", + "hasChildren": true + }, + { + "path": "plugins.entries.google.config.webSearch", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.google.config.webSearch.apiKey", + "kind": "plugin", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security" + ], + "label": "Gemini Search API Key", + "help": "Gemini API key for Google Search grounding (fallback: GEMINI_API_KEY env var).", + "hasChildren": false + }, + { + "path": "plugins.entries.google.config.webSearch.model", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Gemini Search Model", + "help": "Gemini model override for web search grounding.", + "hasChildren": false + }, + { + "path": "plugins.entries.google.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/google-plugin", + "hasChildren": false + }, + { + "path": "plugins.entries.google.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.google.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.google.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.google.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.google.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.google.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.googlechat", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/googlechat", + "help": "OpenClaw Google Chat channel plugin (plugin: googlechat)", + "hasChildren": true + }, + { + "path": "plugins.entries.googlechat.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/googlechat Config", + "help": "Plugin-defined config payload for googlechat.", + "hasChildren": false + }, + { + "path": "plugins.entries.googlechat.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/googlechat", + "hasChildren": false + }, + { + "path": "plugins.entries.googlechat.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.googlechat.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.googlechat.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.googlechat.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.googlechat.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.googlechat.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.huggingface", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/huggingface-provider", + "help": "OpenClaw Hugging Face provider plugin (plugin: huggingface)", + "hasChildren": true + }, + { + "path": "plugins.entries.huggingface.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/huggingface-provider Config", + "help": "Plugin-defined config payload for huggingface.", + "hasChildren": false + }, + { + "path": "plugins.entries.huggingface.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/huggingface-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.huggingface.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.huggingface.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.huggingface.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.huggingface.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.huggingface.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.huggingface.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.imessage", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/imessage", + "help": "OpenClaw iMessage channel plugin (plugin: imessage)", + "hasChildren": true + }, + { + "path": "plugins.entries.imessage.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/imessage Config", + "help": "Plugin-defined config payload for imessage.", + "hasChildren": false + }, + { + "path": "plugins.entries.imessage.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/imessage", + "hasChildren": false + }, + { + "path": "plugins.entries.imessage.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.imessage.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.imessage.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.imessage.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.imessage.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.imessage.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.irc", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/irc", + "help": "OpenClaw IRC channel plugin (plugin: irc)", + "hasChildren": true + }, + { + "path": "plugins.entries.irc.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/irc Config", + "help": "Plugin-defined config payload for irc.", + "hasChildren": false + }, + { + "path": "plugins.entries.irc.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/irc", + "hasChildren": false + }, + { + "path": "plugins.entries.irc.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.irc.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.irc.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.irc.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.irc.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.irc.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.kilocode", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/kilocode-provider", + "help": "OpenClaw Kilo Gateway provider plugin (plugin: kilocode)", + "hasChildren": true + }, + { + "path": "plugins.entries.kilocode.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/kilocode-provider Config", + "help": "Plugin-defined config payload for kilocode.", + "hasChildren": false + }, + { + "path": "plugins.entries.kilocode.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/kilocode-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.kilocode.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.kilocode.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.kilocode.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.kilocode.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.kilocode.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.kilocode.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.kimi", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/kimi-provider", + "help": "OpenClaw Kimi provider plugin (plugin: kimi)", + "hasChildren": true + }, + { + "path": "plugins.entries.kimi.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/kimi-provider Config", + "help": "Plugin-defined config payload for kimi.", + "hasChildren": false + }, + { + "path": "plugins.entries.kimi.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/kimi-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.kimi.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.kimi.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.kimi.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.kimi.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.kimi.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.kimi.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.line", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/line", + "help": "OpenClaw LINE channel plugin (plugin: line)", + "hasChildren": true + }, + { + "path": "plugins.entries.line.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/line Config", + "help": "Plugin-defined config payload for line.", + "hasChildren": false + }, + { + "path": "plugins.entries.line.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/line", + "hasChildren": false + }, + { + "path": "plugins.entries.line.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.line.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.line.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.line.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.line.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.line.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.llm-task", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "LLM Task", + "help": "Generic JSON-only LLM tool for structured tasks callable from workflows. (plugin: llm-task)", + "hasChildren": true + }, + { + "path": "plugins.entries.llm-task.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "LLM Task Config", + "help": "Plugin-defined config payload for llm-task.", + "hasChildren": true + }, + { + "path": "plugins.entries.llm-task.config.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.llm-task.config.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.llm-task.config.defaultAuthProfileId", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.llm-task.config.defaultModel", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.llm-task.config.defaultProvider", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.llm-task.config.maxTokens", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.llm-task.config.timeoutMs", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.llm-task.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable LLM Task", + "hasChildren": false + }, + { + "path": "plugins.entries.llm-task.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.llm-task.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.llm-task.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.llm-task.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.llm-task.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.llm-task.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.lobster", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Lobster", + "help": "Typed workflow tool with resumable approvals. (plugin: lobster)", + "hasChildren": true + }, + { + "path": "plugins.entries.lobster.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Lobster Config", + "help": "Plugin-defined config payload for lobster.", + "hasChildren": false + }, + { + "path": "plugins.entries.lobster.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable Lobster", + "hasChildren": false + }, + { + "path": "plugins.entries.lobster.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.lobster.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.lobster.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.lobster.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.lobster.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.lobster.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.matrix", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/matrix", + "help": "OpenClaw Matrix channel plugin (plugin: matrix)", + "hasChildren": true + }, + { + "path": "plugins.entries.matrix.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/matrix Config", + "help": "Plugin-defined config payload for matrix.", + "hasChildren": false + }, + { + "path": "plugins.entries.matrix.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/matrix", + "hasChildren": false + }, + { + "path": "plugins.entries.matrix.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.matrix.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.matrix.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.matrix.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.matrix.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.matrix.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.mattermost", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/mattermost", + "help": "OpenClaw Mattermost channel plugin (plugin: mattermost)", + "hasChildren": true + }, + { + "path": "plugins.entries.mattermost.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/mattermost Config", + "help": "Plugin-defined config payload for mattermost.", + "hasChildren": false + }, + { + "path": "plugins.entries.mattermost.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/mattermost", + "hasChildren": false + }, + { + "path": "plugins.entries.mattermost.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.mattermost.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.mattermost.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.mattermost.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.mattermost.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.mattermost.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.memory-core", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/memory-core", + "help": "OpenClaw core memory search plugin (plugin: memory-core)", + "hasChildren": true + }, + { + "path": "plugins.entries.memory-core.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/memory-core Config", + "help": "Plugin-defined config payload for memory-core.", + "hasChildren": false + }, + { + "path": "plugins.entries.memory-core.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/memory-core", + "hasChildren": false + }, + { + "path": "plugins.entries.memory-core.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.memory-core.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.memory-core.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.memory-core.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.memory-core.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.memory-core.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.memory-lancedb", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "@openclaw/memory-lancedb", + "help": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall/capture (plugin: memory-lancedb)", + "hasChildren": true + }, + { + "path": "plugins.entries.memory-lancedb.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "@openclaw/memory-lancedb Config", + "help": "Plugin-defined config payload for memory-lancedb.", + "hasChildren": true + }, + { + "path": "plugins.entries.memory-lancedb.config.autoCapture", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Auto-Capture", + "help": "Automatically capture important information from conversations", + "hasChildren": false + }, + { + "path": "plugins.entries.memory-lancedb.config.autoRecall", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Auto-Recall", + "help": "Automatically inject relevant memories into context", + "hasChildren": false + }, + { + "path": "plugins.entries.memory-lancedb.config.captureMaxChars", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced", + "performance", + "storage" + ], + "label": "Capture Max Chars", + "help": "Maximum message length eligible for auto-capture", + "hasChildren": false + }, + { + "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 + }, + { + "path": "plugins.entries.memory-lancedb.config.embedding", + "kind": "plugin", + "type": "object", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "plugins.entries.memory-lancedb.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.memory-lancedb.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.memory-lancedb.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.memory-lancedb.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.memory-lancedb.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.memory-lancedb.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.microsoft", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/microsoft-speech", + "help": "OpenClaw Microsoft speech plugin (plugin: microsoft)", + "hasChildren": true + }, + { + "path": "plugins.entries.microsoft.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/microsoft-speech Config", + "help": "Plugin-defined config payload for microsoft.", + "hasChildren": false + }, + { + "path": "plugins.entries.microsoft.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/microsoft-speech", + "hasChildren": false + }, + { + "path": "plugins.entries.microsoft.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.microsoft.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.microsoft.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.microsoft.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.microsoft.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.microsoft.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.minimax", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "@openclaw/minimax-provider", + "help": "OpenClaw MiniMax provider and OAuth plugin (plugin: minimax)", + "hasChildren": true + }, + { + "path": "plugins.entries.minimax.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "@openclaw/minimax-provider Config", + "help": "Plugin-defined config payload for minimax.", + "hasChildren": false + }, + { + "path": "plugins.entries.minimax.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Enable @openclaw/minimax-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.minimax.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.minimax.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.minimax.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.minimax.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.minimax.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.minimax.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.mistral", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/mistral-provider", + "help": "OpenClaw Mistral provider plugin (plugin: mistral)", + "hasChildren": true + }, + { + "path": "plugins.entries.mistral.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/mistral-provider Config", + "help": "Plugin-defined config payload for mistral.", + "hasChildren": false + }, + { + "path": "plugins.entries.mistral.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/mistral-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.mistral.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.mistral.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.mistral.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.mistral.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.mistral.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.mistral.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.modelstudio", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/modelstudio-provider", + "help": "OpenClaw Model Studio provider plugin (plugin: modelstudio)", + "hasChildren": true + }, + { + "path": "plugins.entries.modelstudio.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/modelstudio-provider Config", + "help": "Plugin-defined config payload for modelstudio.", + "hasChildren": false + }, + { + "path": "plugins.entries.modelstudio.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/modelstudio-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.modelstudio.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.modelstudio.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.modelstudio.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.modelstudio.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.modelstudio.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.modelstudio.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.moonshot", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/moonshot-provider", + "help": "OpenClaw Moonshot provider plugin (plugin: moonshot)", + "hasChildren": true + }, + { + "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 + }, + { + "path": "plugins.entries.moonshot.config.webSearch", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "plugins.entries.moonshot.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/moonshot-provider", + "hasChildren": false + }, + { + "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 + }, + { + "path": "plugins.entries.moonshot.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.moonshot.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.moonshot.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.moonshot.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.moonshot.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.msteams", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/msteams", + "help": "OpenClaw Microsoft Teams channel plugin (plugin: msteams)", + "hasChildren": true + }, + { + "path": "plugins.entries.msteams.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/msteams Config", + "help": "Plugin-defined config payload for msteams.", + "hasChildren": false + }, + { + "path": "plugins.entries.msteams.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/msteams", + "hasChildren": false + }, + { + "path": "plugins.entries.msteams.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.msteams.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.msteams.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.msteams.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.msteams.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.msteams.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.nextcloud-talk", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/nextcloud-talk", + "help": "OpenClaw Nextcloud Talk channel plugin (plugin: nextcloud-talk)", + "hasChildren": true + }, + { + "path": "plugins.entries.nextcloud-talk.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/nextcloud-talk Config", + "help": "Plugin-defined config payload for nextcloud-talk.", + "hasChildren": false + }, + { + "path": "plugins.entries.nextcloud-talk.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/nextcloud-talk", + "hasChildren": false + }, + { + "path": "plugins.entries.nextcloud-talk.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.nextcloud-talk.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.nextcloud-talk.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.nextcloud-talk.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.nextcloud-talk.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.nextcloud-talk.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.nostr", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/nostr", + "help": "OpenClaw Nostr channel plugin for NIP-04 encrypted DMs (plugin: nostr)", + "hasChildren": true + }, + { + "path": "plugins.entries.nostr.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/nostr Config", + "help": "Plugin-defined config payload for nostr.", + "hasChildren": false + }, + { + "path": "plugins.entries.nostr.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/nostr", + "hasChildren": false + }, + { + "path": "plugins.entries.nostr.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.nostr.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.nostr.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.nostr.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.nostr.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.nostr.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.nvidia", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/nvidia-provider", + "help": "OpenClaw NVIDIA provider plugin (plugin: nvidia)", + "hasChildren": true + }, + { + "path": "plugins.entries.nvidia.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/nvidia-provider Config", + "help": "Plugin-defined config payload for nvidia.", + "hasChildren": false + }, + { + "path": "plugins.entries.nvidia.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/nvidia-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.nvidia.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.nvidia.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.nvidia.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.nvidia.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.nvidia.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.nvidia.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.ollama", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/ollama-provider", + "help": "OpenClaw Ollama provider plugin (plugin: ollama)", + "hasChildren": true + }, + { + "path": "plugins.entries.ollama.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/ollama-provider Config", + "help": "Plugin-defined config payload for ollama.", + "hasChildren": false + }, + { + "path": "plugins.entries.ollama.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/ollama-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.ollama.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.ollama.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.ollama.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.ollama.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.ollama.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.ollama.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.open-prose", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "OpenProse", + "help": "OpenProse VM skill pack with a /prose slash command. (plugin: open-prose)", + "hasChildren": true + }, + { + "path": "plugins.entries.open-prose.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "OpenProse Config", + "help": "Plugin-defined config payload for open-prose.", + "hasChildren": false + }, + { + "path": "plugins.entries.open-prose.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable OpenProse", + "hasChildren": false + }, + { + "path": "plugins.entries.open-prose.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.open-prose.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.open-prose.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.open-prose.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.open-prose.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.open-prose.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.openai", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/openai-provider", + "help": "OpenClaw OpenAI provider plugins (plugin: openai)", + "hasChildren": true + }, + { + "path": "plugins.entries.openai.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/openai-provider Config", + "help": "Plugin-defined config payload for openai.", + "hasChildren": false + }, + { + "path": "plugins.entries.openai.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/openai-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.openai.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.openai.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.openai.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.openai.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.openai.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.openai.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.opencode", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/opencode-provider", + "help": "OpenClaw OpenCode Zen provider plugin (plugin: opencode)", + "hasChildren": true + }, + { + "path": "plugins.entries.opencode-go", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/opencode-go-provider", + "help": "OpenClaw OpenCode Go provider plugin (plugin: opencode-go)", + "hasChildren": true + }, + { + "path": "plugins.entries.opencode-go.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/opencode-go-provider Config", + "help": "Plugin-defined config payload for opencode-go.", + "hasChildren": false + }, + { + "path": "plugins.entries.opencode-go.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/opencode-go-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.opencode-go.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.opencode-go.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.opencode-go.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.opencode-go.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.opencode-go.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.opencode-go.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.opencode.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/opencode-provider Config", + "help": "Plugin-defined config payload for opencode.", + "hasChildren": false + }, + { + "path": "plugins.entries.opencode.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/opencode-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.opencode.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.opencode.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.opencode.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.opencode.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.opencode.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.opencode.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.openrouter", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/openrouter-provider", + "help": "OpenClaw OpenRouter provider plugin (plugin: openrouter)", + "hasChildren": true + }, + { + "path": "plugins.entries.openrouter.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/openrouter-provider Config", + "help": "Plugin-defined config payload for openrouter.", + "hasChildren": false + }, + { + "path": "plugins.entries.openrouter.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/openrouter-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.openrouter.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.openrouter.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.openrouter.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.openrouter.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.openrouter.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.openrouter.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "OpenShell Sandbox", + "help": "Sandbox backend powered by OpenShell with mirrored local workspaces and SSH-based command execution. (plugin: openshell)", + "hasChildren": true + }, + { + "path": "plugins.entries.openshell.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "OpenShell Sandbox Config", + "help": "Plugin-defined config payload for openshell.", + "hasChildren": true + }, + { + "path": "plugins.entries.openshell.config.autoProviders", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Auto-create Providers", + "help": "When enabled, pass --auto-providers during sandbox create.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.config.command", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "OpenShell Command", + "help": "Path or command name for the openshell CLI.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.config.from", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Sandbox Source", + "help": "OpenShell sandbox source for first-time create. Defaults to openclaw.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.config.gateway", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gateway Name", + "help": "Optional OpenShell gateway name passed as --gateway.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.config.gatewayEndpoint", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Gateway Endpoint", + "help": "Optional OpenShell gateway endpoint passed as --gateway-endpoint.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.config.gpu", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "GPU", + "help": "Request GPU resources when creating the sandbox.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.config.policy", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Policy File", + "help": "Optional path to a custom OpenShell sandbox policy YAML.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.config.providers", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Providers", + "help": "Provider names to attach when a sandbox is created.", + "hasChildren": true + }, + { + "path": "plugins.entries.openshell.config.providers.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.config.remoteAgentWorkspaceDir", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced", + "storage" + ], + "label": "Remote Agent Dir", + "help": "Mirror path for the real agent workspace when workspaceAccess is read-only.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.config.remoteWorkspaceDir", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced", + "storage" + ], + "label": "Remote Workspace Dir", + "help": "Primary writable workspace inside the OpenShell sandbox.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.config.timeoutSeconds", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced", + "performance" + ], + "label": "Command Timeout Seconds", + "help": "Timeout for openshell CLI operations such as create/upload/download.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable OpenShell Sandbox", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.openshell.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.openshell.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.openshell.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.openshell.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.perplexity", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/perplexity-plugin", + "help": "OpenClaw Perplexity plugin (plugin: perplexity)", + "hasChildren": true + }, + { + "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 + }, + { + "path": "plugins.entries.perplexity.config.webSearch", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "plugins.entries.perplexity.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/perplexity-plugin", + "hasChildren": false + }, + { + "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 + }, + { + "path": "plugins.entries.perplexity.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.perplexity.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.perplexity.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.perplexity.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.perplexity.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.phone-control", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Phone Control", + "help": "Arm/disarm high-risk phone node commands (camera/screen/writes) with an optional auto-expiry. (plugin: phone-control)", + "hasChildren": true + }, + { + "path": "plugins.entries.phone-control.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Phone Control Config", + "help": "Plugin-defined config payload for phone-control.", + "hasChildren": false + }, + { + "path": "plugins.entries.phone-control.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable Phone Control", + "hasChildren": false + }, + { + "path": "plugins.entries.phone-control.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.phone-control.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.phone-control.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.phone-control.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.phone-control.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.phone-control.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.qianfan", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/qianfan-provider", + "help": "OpenClaw Qianfan provider plugin (plugin: qianfan)", + "hasChildren": true + }, + { + "path": "plugins.entries.qianfan.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/qianfan-provider Config", + "help": "Plugin-defined config payload for qianfan.", + "hasChildren": false + }, + { + "path": "plugins.entries.qianfan.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/qianfan-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.qianfan.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.qianfan.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.qianfan.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.qianfan.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.qianfan.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.qianfan.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.qwen-portal-auth", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "qwen-portal-auth", + "help": "Plugin entry for qwen-portal-auth.", + "hasChildren": true + }, + { + "path": "plugins.entries.qwen-portal-auth.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "qwen-portal-auth Config", + "help": "Plugin-defined config payload for qwen-portal-auth.", + "hasChildren": false + }, + { + "path": "plugins.entries.qwen-portal-auth.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable qwen-portal-auth", + "hasChildren": false + }, + { + "path": "plugins.entries.qwen-portal-auth.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.qwen-portal-auth.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.qwen-portal-auth.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.qwen-portal-auth.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.qwen-portal-auth.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.qwen-portal-auth.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.sglang", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/sglang-provider", + "help": "OpenClaw SGLang provider plugin (plugin: sglang)", + "hasChildren": true + }, + { + "path": "plugins.entries.sglang.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/sglang-provider Config", + "help": "Plugin-defined config payload for sglang.", + "hasChildren": false + }, + { + "path": "plugins.entries.sglang.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/sglang-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.sglang.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.sglang.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.sglang.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.sglang.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.sglang.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.sglang.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.signal", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/signal", + "help": "OpenClaw Signal channel plugin (plugin: signal)", + "hasChildren": true + }, + { + "path": "plugins.entries.signal.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/signal Config", + "help": "Plugin-defined config payload for signal.", + "hasChildren": false + }, + { + "path": "plugins.entries.signal.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/signal", + "hasChildren": false + }, + { + "path": "plugins.entries.signal.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.signal.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.signal.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.signal.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.signal.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.signal.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.slack", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/slack", + "help": "OpenClaw Slack channel plugin (plugin: slack)", + "hasChildren": true + }, + { + "path": "plugins.entries.slack.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/slack Config", + "help": "Plugin-defined config payload for slack.", + "hasChildren": false + }, + { + "path": "plugins.entries.slack.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/slack", + "hasChildren": false + }, + { + "path": "plugins.entries.slack.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.slack.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.slack.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.slack.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.slack.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.slack.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.synology-chat", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/synology-chat", + "help": "Synology Chat channel plugin for OpenClaw (plugin: synology-chat)", + "hasChildren": true + }, + { + "path": "plugins.entries.synology-chat.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/synology-chat Config", + "help": "Plugin-defined config payload for synology-chat.", + "hasChildren": false + }, + { + "path": "plugins.entries.synology-chat.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/synology-chat", + "hasChildren": false + }, + { + "path": "plugins.entries.synology-chat.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.synology-chat.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.synology-chat.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.synology-chat.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.synology-chat.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.synology-chat.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.synthetic", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/synthetic-provider", + "help": "OpenClaw Synthetic provider plugin (plugin: synthetic)", + "hasChildren": true + }, + { + "path": "plugins.entries.synthetic.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/synthetic-provider Config", + "help": "Plugin-defined config payload for synthetic.", + "hasChildren": false + }, + { + "path": "plugins.entries.synthetic.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/synthetic-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.synthetic.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.synthetic.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.synthetic.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.synthetic.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.synthetic.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.synthetic.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.talk-voice", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Talk Voice", + "help": "Manage Talk voice selection (list/set). (plugin: talk-voice)", + "hasChildren": true + }, + { + "path": "plugins.entries.talk-voice.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Talk Voice Config", + "help": "Plugin-defined config payload for talk-voice.", + "hasChildren": false + }, + { + "path": "plugins.entries.talk-voice.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable Talk Voice", + "hasChildren": false + }, + { + "path": "plugins.entries.talk-voice.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.talk-voice.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.talk-voice.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.talk-voice.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.talk-voice.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.talk-voice.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.telegram", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/telegram", + "help": "OpenClaw Telegram channel plugin (plugin: telegram)", + "hasChildren": true + }, + { + "path": "plugins.entries.telegram.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/telegram Config", + "help": "Plugin-defined config payload for telegram.", + "hasChildren": false + }, + { + "path": "plugins.entries.telegram.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/telegram", + "hasChildren": false + }, + { + "path": "plugins.entries.telegram.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.telegram.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.telegram.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.telegram.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.telegram.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.telegram.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.thread-ownership", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Thread Ownership", + "help": "Prevents multiple agents from responding in the same Slack thread. Uses HTTP calls to the slack-forwarder ownership API. (plugin: thread-ownership)", + "hasChildren": true + }, + { + "path": "plugins.entries.thread-ownership.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Thread Ownership Config", + "help": "Plugin-defined config payload for thread-ownership.", + "hasChildren": true + }, + { + "path": "plugins.entries.thread-ownership.config.abTestChannels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "A/B Test Channels", + "help": "Slack channel IDs where thread ownership is enforced", + "hasChildren": true + }, + { + "path": "plugins.entries.thread-ownership.config.abTestChannels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.thread-ownership.config.forwarderUrl", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Forwarder URL", + "help": "Base URL of the slack-forwarder ownership API (default: http://slack-forwarder:8750)", + "hasChildren": false + }, + { + "path": "plugins.entries.thread-ownership.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Enable Thread Ownership", + "hasChildren": false + }, + { + "path": "plugins.entries.thread-ownership.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.thread-ownership.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.thread-ownership.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.thread-ownership.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.thread-ownership.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.thread-ownership.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.tlon", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/tlon", + "help": "OpenClaw Tlon/Urbit channel plugin (plugin: tlon)", + "hasChildren": true + }, + { + "path": "plugins.entries.tlon.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/tlon Config", + "help": "Plugin-defined config payload for tlon.", + "hasChildren": false + }, + { + "path": "plugins.entries.tlon.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/tlon", + "hasChildren": false + }, + { + "path": "plugins.entries.tlon.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.tlon.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.tlon.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.tlon.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.tlon.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.tlon.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.together", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/together-provider", + "help": "OpenClaw Together provider plugin (plugin: together)", + "hasChildren": true + }, + { + "path": "plugins.entries.together.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/together-provider Config", + "help": "Plugin-defined config payload for together.", + "hasChildren": false + }, + { + "path": "plugins.entries.together.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/together-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.together.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.together.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.together.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.together.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.together.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.together.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.twitch", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/twitch", + "help": "OpenClaw Twitch channel plugin (plugin: twitch)", + "hasChildren": true + }, + { + "path": "plugins.entries.twitch.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/twitch Config", + "help": "Plugin-defined config payload for twitch.", + "hasChildren": false + }, + { + "path": "plugins.entries.twitch.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/twitch", + "hasChildren": false + }, + { + "path": "plugins.entries.twitch.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.twitch.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.twitch.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.twitch.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.twitch.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.twitch.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.venice", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/venice-provider", + "help": "OpenClaw Venice provider plugin (plugin: venice)", + "hasChildren": true + }, + { + "path": "plugins.entries.venice.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/venice-provider Config", + "help": "Plugin-defined config payload for venice.", + "hasChildren": false + }, + { + "path": "plugins.entries.venice.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/venice-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.venice.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.venice.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.venice.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.venice.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.venice.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.venice.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.vercel-ai-gateway", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/vercel-ai-gateway-provider", + "help": "OpenClaw Vercel AI Gateway provider plugin (plugin: vercel-ai-gateway)", + "hasChildren": true + }, + { + "path": "plugins.entries.vercel-ai-gateway.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/vercel-ai-gateway-provider Config", + "help": "Plugin-defined config payload for vercel-ai-gateway.", + "hasChildren": false + }, + { + "path": "plugins.entries.vercel-ai-gateway.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/vercel-ai-gateway-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.vercel-ai-gateway.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.vercel-ai-gateway.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.vercel-ai-gateway.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.vercel-ai-gateway.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.vercel-ai-gateway.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.vercel-ai-gateway.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.vllm", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/vllm-provider", + "help": "OpenClaw vLLM provider plugin (plugin: vllm)", + "hasChildren": true + }, + { + "path": "plugins.entries.vllm.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/vllm-provider Config", + "help": "Plugin-defined config payload for vllm.", + "hasChildren": false + }, + { + "path": "plugins.entries.vllm.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/vllm-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.vllm.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.vllm.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.vllm.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.vllm.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.vllm.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.vllm.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/voice-call", + "help": "OpenClaw voice-call plugin (plugin: voice-call)", + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/voice-call Config", + "help": "Plugin-defined config payload for voice-call.", + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.allowFrom", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Inbound Allowlist", + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.allowFrom.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.fromNumber", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "From Number", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.inboundGreeting", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Inbound Greeting", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.inboundPolicy", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "disabled", + "allowlist", + "pairing", + "open" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Inbound Policy", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.maxConcurrentCalls", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.maxDurationSeconds", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.outbound", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.outbound.defaultMode", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "notify", + "conversation" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Default Call Mode", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.outbound.notifyHangupDelaySec", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Notify Hangup Delay (sec)", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.plivo", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.plivo.authId", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.plivo.authToken", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.provider", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "telnyx", + "twilio", + "plivo", + "mock" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Provider", + "help": "Use twilio, telnyx, or mock for dev/no-network.", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.publicUrl", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Public Webhook URL", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.responseModel", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Response Model", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.responseSystemPrompt", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Response System Prompt", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.responseTimeoutMs", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced", + "performance" + ], + "label": "Response Timeout (ms)", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.ringTimeoutMs", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.serve", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.serve.bind", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Webhook Bind", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.serve.path", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Webhook Path", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.serve.port", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Webhook Port", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.silenceTimeoutMs", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.skipSignatureVerification", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Skip Signature Verification", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.staleCallReaperSeconds", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.store", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced", + "storage" + ], + "label": "Call Log Store Path", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.streaming.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable Streaming", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming.maxConnections", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming.maxPendingConnections", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming.maxPendingConnectionsPerIp", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming.openaiApiKey", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "advanced", + "auth", + "security" + ], + "label": "OpenAI Realtime API Key", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming.preStartTimeoutMs", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming.silenceDurationMs", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming.streamPath", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced", + "storage" + ], + "label": "Media Stream Path", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming.sttModel", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced", + "media" + ], + "label": "Realtime STT Model", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming.sttProvider", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "openai-realtime" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.streaming.vadThreshold", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.stt", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.stt.model", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.stt.provider", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "openai" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tailscale", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.tailscale.mode", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "off", + "serve", + "funnel" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Tailscale Mode", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tailscale.path", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced", + "storage" + ], + "label": "Tailscale Path", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.telnyx", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.telnyx.apiKey", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security" + ], + "label": "Telnyx API Key", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.telnyx.connectionId", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Telnyx Connection ID", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.telnyx.publicKey", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "security" + ], + "label": "Telnyx Public Key", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.toNumber", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Default To Number", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.transcriptTimeoutMs", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "path": "plugins.entries.voice-call.config.tts.edge", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.tts.edge.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.edge.lang", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.edge.outputFormat", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.edge.pitch", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.edge.proxy", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.edge.rate", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.edge.saveSubtitles", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.edge.timeoutMs", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.edge.voice", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.edge.volume", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.elevenlabs", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "plugins.entries.voice-call.config.tts.elevenlabs.languageCode", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "plugins.entries.voice-call.config.tts.elevenlabs.seed", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.similarityBoost", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.speed", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.stability", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.style", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.useSpeakerBoost", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.maxTextLength", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.mode", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "final", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.modelOverrides", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.tts.modelOverrides.allowModelId", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.modelOverrides.allowNormalization", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.modelOverrides.allowProvider", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.modelOverrides.allowSeed", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.modelOverrides.allowText", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.modelOverrides.allowVoice", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.modelOverrides.allowVoiceSettings", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.modelOverrides.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.openai", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "path": "plugins.entries.voice-call.config.tts.openai.baseUrl", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.openai.instructions", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "plugins.entries.voice-call.config.tts.openai.speed", + "kind": "plugin", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "plugins.entries.voice-call.config.tts.prefsPath", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "plugins.entries.voice-call.config.tts.summaryModel", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tts.timeoutMs", + "kind": "plugin", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tunnel", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.tunnel.allowNgrokFreeTierLoopbackBypass", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "advanced" + ], + "label": "Allow ngrok Free Tier (Loopback Bypass)", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tunnel.ngrokAuthToken", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "advanced", + "auth", + "security" + ], + "label": "ngrok Auth Token", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tunnel.ngrokDomain", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "ngrok Domain", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.tunnel.provider", + "kind": "plugin", + "type": "string", + "required": false, + "enumValues": [ + "none", + "ngrok", + "tailscale-serve", + "tailscale-funnel" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Tunnel Provider", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.twilio", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.twilio.accountSid", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Twilio Account SID", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.twilio.authToken", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security" + ], + "label": "Twilio Auth Token", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.webhookSecurity", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.webhookSecurity.allowedHosts", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.webhookSecurity.allowedHosts.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.webhookSecurity.trustedProxyIPs", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.config.webhookSecurity.trustedProxyIPs.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.config.webhookSecurity.trustForwardingHeaders", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/voice-call", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.voice-call.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.voice-call.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.volcengine", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/volcengine-provider", + "help": "OpenClaw Volcengine provider plugin (plugin: volcengine)", + "hasChildren": true + }, + { + "path": "plugins.entries.volcengine.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/volcengine-provider Config", + "help": "Plugin-defined config payload for volcengine.", + "hasChildren": false + }, + { + "path": "plugins.entries.volcengine.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/volcengine-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.volcengine.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.volcengine.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.volcengine.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.volcengine.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.volcengine.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.volcengine.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.whatsapp", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/whatsapp", + "help": "OpenClaw WhatsApp channel plugin (plugin: whatsapp)", + "hasChildren": true + }, + { + "path": "plugins.entries.whatsapp.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/whatsapp Config", + "help": "Plugin-defined config payload for whatsapp.", + "hasChildren": false + }, + { + "path": "plugins.entries.whatsapp.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/whatsapp", + "hasChildren": false + }, + { + "path": "plugins.entries.whatsapp.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.whatsapp.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.whatsapp.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.whatsapp.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.whatsapp.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.whatsapp.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.xai", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/xai-plugin", + "help": "OpenClaw xAI plugin (plugin: xai)", + "hasChildren": true + }, + { + "path": "plugins.entries.xai.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/xai-plugin Config", + "help": "Plugin-defined config payload for xai.", + "hasChildren": true + }, + { + "path": "plugins.entries.xai.config.webSearch", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "plugins.entries.xai.config.webSearch.apiKey", + "kind": "plugin", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security" + ], + "label": "Grok Search API Key", + "help": "xAI API key for Grok web search (fallback: XAI_API_KEY env var).", + "hasChildren": false + }, + { + "path": "plugins.entries.xai.config.webSearch.inlineCitations", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Inline Citations", + "help": "Include inline markdown citations in Grok responses.", + "hasChildren": false + }, + { + "path": "plugins.entries.xai.config.webSearch.model", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models" + ], + "label": "Grok Search Model", + "help": "Grok model override for web search.", + "hasChildren": false + }, + { + "path": "plugins.entries.xai.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/xai-plugin", + "hasChildren": false + }, + { + "path": "plugins.entries.xai.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.xai.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.xai.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.xai.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.xai.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.xai.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.xiaomi", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/xiaomi-provider", + "help": "OpenClaw Xiaomi provider plugin (plugin: xiaomi)", + "hasChildren": true + }, + { + "path": "plugins.entries.xiaomi.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/xiaomi-provider Config", + "help": "Plugin-defined config payload for xiaomi.", + "hasChildren": false + }, + { + "path": "plugins.entries.xiaomi.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/xiaomi-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.xiaomi.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.xiaomi.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.xiaomi.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.xiaomi.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.xiaomi.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.xiaomi.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.zai", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/zai-provider", + "help": "OpenClaw Z.AI provider plugin (plugin: zai)", + "hasChildren": true + }, + { + "path": "plugins.entries.zai.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/zai-provider Config", + "help": "Plugin-defined config payload for zai.", + "hasChildren": false + }, + { + "path": "plugins.entries.zai.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/zai-provider", + "hasChildren": false + }, + { + "path": "plugins.entries.zai.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.zai.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.zai.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.zai.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.zai.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.zai.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.zalo", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/zalo", + "help": "OpenClaw Zalo channel plugin (plugin: zalo)", + "hasChildren": true + }, + { + "path": "plugins.entries.zalo.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/zalo Config", + "help": "Plugin-defined config payload for zalo.", + "hasChildren": false + }, + { + "path": "plugins.entries.zalo.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/zalo", + "hasChildren": false + }, + { + "path": "plugins.entries.zalo.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.zalo.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.zalo.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.zalo.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.zalo.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.zalo.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.entries.zalouser", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/zalouser", + "help": "OpenClaw Zalo Personal Account plugin via native zca-js integration (plugin: zalouser)", + "hasChildren": true + }, + { + "path": "plugins.entries.zalouser.config", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "@openclaw/zalouser Config", + "help": "Plugin-defined config payload for zalouser.", + "hasChildren": false + }, + { + "path": "plugins.entries.zalouser.enabled", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Enable @openclaw/zalouser", + "hasChildren": false + }, + { + "path": "plugins.entries.zalouser.hooks", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Hook Policy", + "help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.", + "hasChildren": true + }, + { + "path": "plugins.entries.zalouser.hooks.allowPromptInjection", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Prompt Injection Hooks", + "help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.", + "hasChildren": false + }, + { + "path": "plugins.entries.zalouser.subagent", + "kind": "plugin", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Subagent Policy", + "help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.", + "hasChildren": true + }, + { + "path": "plugins.entries.zalouser.subagent.allowedModels", + "kind": "plugin", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Plugin Subagent Allowed Models", + "help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.", + "hasChildren": true + }, + { + "path": "plugins.entries.zalouser.subagent.allowedModels.*", + "kind": "plugin", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.entries.zalouser.subagent.allowModelOverride", + "kind": "plugin", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access" + ], + "label": "Allow Plugin Subagent Model Override", + "help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.", + "hasChildren": false + }, + { + "path": "plugins.installs", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Install Records", + "help": "CLI-managed install metadata (used by `openclaw plugins update` to locate install sources).", + "hasChildren": true + }, + { + "path": "plugins.installs.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "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 + }, + { + "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/).", + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "plugins.installs.*.marketplaceSource", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Marketplace Source", + "help": "Original marketplace source used to resolve the install (for example a repo path or Git URL).", + "hasChildren": false + }, + { + "path": "plugins.installs.*.resolvedAt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Resolution Time", + "help": "ISO timestamp when npm package metadata was last resolved for this install record.", + "hasChildren": false + }, + { + "path": "plugins.installs.*.resolvedName", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Resolved Package Name", + "help": "Resolved npm package name from the fetched artifact.", + "hasChildren": false + }, + { + "path": "plugins.installs.*.resolvedSpec", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Resolved Package Spec", + "help": "Resolved exact npm spec (@) from the fetched artifact.", + "hasChildren": false + }, + { + "path": "plugins.installs.*.resolvedVersion", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Resolved Package Version", + "help": "Resolved npm package version from the fetched artifact (useful for non-pinned specs).", + "hasChildren": false + }, + { + "path": "plugins.installs.*.shasum", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Resolved Shasum", + "help": "Resolved npm dist shasum for the fetched artifact (if reported by npm).", + "hasChildren": false + }, + { + "path": "plugins.installs.*.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Install Source", + "help": "Install source (\"npm\", \"archive\", or \"path\").", + "hasChildren": false + }, + { + "path": "plugins.installs.*.sourcePath", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Plugin Install Source Path", + "help": "Original archive/path used for install (if any).", + "hasChildren": false + }, + { + "path": "plugins.installs.*.spec", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Install Spec", + "help": "Original npm spec used for install (if source is npm).", + "hasChildren": false + }, + { + "path": "plugins.installs.*.version", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Install Version", + "help": "Version recorded at install time (if available).", + "hasChildren": false + }, + { + "path": "plugins.load", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Loader", + "help": "Plugin loader configuration group for specifying filesystem paths where plugins are discovered. Keep load paths explicit and reviewed to avoid accidental untrusted extension loading.", + "hasChildren": true + }, + { + "path": "plugins.load.paths", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Plugin Load Paths", + "help": "Additional plugin files or directories scanned by the loader beyond built-in defaults. Use dedicated extension directories and avoid broad paths with unrelated executable content.", + "hasChildren": true + }, + { + "path": "plugins.load.paths.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "plugins.slots", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Plugin Slots", + "help": "Selects which plugins own exclusive runtime slots such as memory so only one plugin provides that capability. Use explicit slot ownership to avoid overlapping providers with conflicting behavior.", + "hasChildren": true + }, + { + "path": "plugins.slots.contextEngine", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Context Engine Plugin", + "help": "Selects the active context engine plugin by id so one plugin provides context orchestration behavior.", + "hasChildren": false + }, + { + "path": "plugins.slots.memory", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Memory Plugin", + "help": "Select the active memory plugin by id, or \"none\" to disable memory plugins.", + "hasChildren": false + }, + { + "path": "secrets", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "secrets.defaults", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "secrets.defaults.env", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.defaults.exec", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.defaults.file", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "secrets.providers.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "secrets.providers.*.allowInsecurePath", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.allowlist", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "secrets.providers.*.allowlist.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.allowSymlinkCommand", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.args", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "secrets.providers.*.args.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.command", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.env", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "secrets.providers.*.env.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.jsonOnly", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.maxOutputBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.noOutputTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.passEnv", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "secrets.providers.*.passEnv.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.path", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.timeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.providers.*.trustedDirs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "secrets.providers.*.trustedDirs.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.resolution", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "secrets.resolution.maxBatchBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.resolution.maxProviderConcurrency", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "secrets.resolution.maxRefsPerProvider", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session", + "help": "Global session routing, reset, delivery policy, and maintenance controls for conversation history behavior. Keep defaults unless you need stricter isolation, retention, or delivery constraints.", + "hasChildren": true + }, + { + "path": "session.agentToAgent", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Agent-to-Agent", + "help": "Groups controls for inter-agent session exchanges, including loop prevention limits on reply chaining. Keep defaults unless you run advanced agent-to-agent automation with strict turn caps.", + "hasChildren": true + }, + { + "path": "session.agentToAgent.maxPingPongTurns", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "Agent-to-Agent Ping-Pong Turns", + "help": "Max reply-back turns between requester and target agents during agent-to-agent exchanges (0-5). Use lower values to hard-limit chatter loops and preserve predictable run completion.", + "hasChildren": false + }, + { + "path": "session.dmScope", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "DM Session Scope", + "help": "DM session scoping: \"main\" keeps continuity, while \"per-peer\", \"per-channel-peer\", and \"per-account-channel-peer\" increase isolation. Use isolated modes for shared inboxes or multi-account deployments.", + "hasChildren": false + }, + { + "path": "session.identityLinks", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Identity Links", + "help": "Maps canonical identities to provider-prefixed peer IDs so equivalent users resolve to one DM thread (example: telegram:123456). Use this when the same human appears across multiple channels or accounts.", + "hasChildren": true + }, + { + "path": "session.identityLinks.*", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "session.identityLinks.*.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.idleMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Idle Minutes", + "help": "Applies a legacy idle reset window in minutes for session reuse behavior across inactivity gaps. Use this only for compatibility and prefer structured reset policies under session.reset/session.resetByType.", + "hasChildren": false + }, + { + "path": "session.mainKey", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Main Key", + "help": "Overrides the canonical main session key used for continuity when dmScope or routing logic points to \"main\". Use a stable value only if you intentionally need custom session anchoring.", + "hasChildren": false + }, + { + "path": "session.maintenance", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Maintenance", + "help": "Automatic session-store maintenance controls for pruning age, entry caps, and file rotation behavior. Start in warn mode to observe impact, then enforce once thresholds are tuned.", + "hasChildren": true + }, + { + "path": "session.maintenance.highWaterBytes", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Disk High-water Target", + "help": "Target size after disk-budget cleanup (high-water mark). Defaults to 80% of maxDiskBytes; set explicitly for tighter reclaim behavior on constrained disks.", + "hasChildren": false + }, + { + "path": "session.maintenance.maxDiskBytes", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "Session Max Disk Budget", + "help": "Optional per-agent sessions-directory disk budget (for example `500mb`). Use this to cap session storage per agent; when exceeded, warn mode reports pressure and enforce mode performs oldest-first cleanup.", + "hasChildren": false + }, + { + "path": "session.maintenance.maxEntries", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "Session Max Entries", + "help": "Caps total session entry count retained in the store to prevent unbounded growth over time. Use lower limits for constrained environments, or higher limits when longer history is required.", + "hasChildren": false + }, + { + "path": "session.maintenance.mode", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "enforce", + "warn" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Maintenance Mode", + "help": "Determines whether maintenance policies are only reported (\"warn\") or actively applied (\"enforce\"). Keep \"warn\" during rollout and switch to \"enforce\" after validating safe thresholds.", + "hasChildren": false + }, + { + "path": "session.maintenance.pruneAfter", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Prune After", + "help": "Removes entries older than this duration (for example `30d` or `12h`) during maintenance passes. Use this as the primary age-retention control and align it with data retention policy.", + "hasChildren": false + }, + { + "path": "session.maintenance.pruneDays", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Prune Days (Deprecated)", + "help": "Deprecated age-retention field kept for compatibility with legacy configs using day counts. Use session.maintenance.pruneAfter instead so duration syntax and behavior are consistent.", + "hasChildren": false + }, + { + "path": "session.maintenance.resetArchiveRetention", + "kind": "core", + "type": [ + "boolean", + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset Archive Retention", + "help": "Retention for reset transcript archives (`*.reset.`). Accepts a duration (for example `30d`), or `false` to disable cleanup. Defaults to pruneAfter so reset artifacts do not grow forever.", + "hasChildren": false + }, + { + "path": "session.maintenance.rotateBytes", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Rotate Size", + "help": "Rotates the session store when file size exceeds a threshold such as `10mb` or `1gb`. Use this to bound single-file growth and keep backup/restore operations manageable.", + "hasChildren": false + }, + { + "path": "session.parentForkMaxTokens", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "auth", + "performance", + "security", + "storage" + ], + "label": "Session Parent Fork Max Tokens", + "help": "Maximum parent-session token count allowed for thread/session inheritance forking. If the parent exceeds this, OpenClaw starts a fresh thread session instead of forking; set 0 to disable this protection.", + "hasChildren": false + }, + { + "path": "session.reset", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset Policy", + "help": "Defines the default reset policy object used when no type-specific or channel-specific override applies. Set this first, then layer resetByType or resetByChannel only where behavior must differ.", + "hasChildren": true + }, + { + "path": "session.reset.atHour", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Daily Reset Hour", + "help": "Sets local-hour boundary (0-23) for daily reset mode so sessions roll over at predictable times. Use with mode=daily and align to operator timezone expectations for human-readable behavior.", + "hasChildren": false + }, + { + "path": "session.reset.idleMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset Idle Minutes", + "help": "Sets inactivity window before reset for idle mode and can also act as secondary guard with daily mode. Use larger values to preserve continuity or smaller values for fresher short-lived threads.", + "hasChildren": false + }, + { + "path": "session.reset.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset Mode", + "help": "Selects reset strategy: \"daily\" resets at a configured hour and \"idle\" resets after inactivity windows. Keep one clear mode per policy to avoid surprising context turnover patterns.", + "hasChildren": false + }, + { + "path": "session.resetByChannel", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset by Channel", + "help": "Provides channel-specific reset overrides keyed by provider/channel id for fine-grained behavior control. Use this only when one channel needs exceptional reset behavior beyond type-level policies.", + "hasChildren": true + }, + { + "path": "session.resetByChannel.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "session.resetByChannel.*.atHour", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByChannel.*.idleMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByChannel.*.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset by Chat Type", + "help": "Overrides reset behavior by chat type (direct, group, thread) when defaults are not sufficient. Use this when group/thread traffic needs different reset cadence than direct messages.", + "hasChildren": true + }, + { + "path": "session.resetByType.direct", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset (Direct)", + "help": "Defines reset policy for direct chats and supersedes the base session.reset configuration for that type. Use this as the canonical direct-message override instead of the legacy dm alias.", + "hasChildren": true + }, + { + "path": "session.resetByType.direct.atHour", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.direct.idleMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.direct.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.dm", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset (DM Deprecated Alias)", + "help": "Deprecated alias for direct reset behavior kept for backward compatibility with older configs. Use session.resetByType.direct instead so future tooling and validation remain consistent.", + "hasChildren": true + }, + { + "path": "session.resetByType.dm.atHour", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.dm.idleMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.dm.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.group", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset (Group)", + "help": "Defines reset policy for group chat sessions where continuity and noise patterns differ from DMs. Use shorter idle windows for busy groups if context drift becomes a problem.", + "hasChildren": true + }, + { + "path": "session.resetByType.group.atHour", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.group.idleMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.group.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.thread", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset (Thread)", + "help": "Defines reset policy for thread-scoped sessions, including focused channel thread workflows. Use this when thread sessions should expire faster or slower than other chat types.", + "hasChildren": true + }, + { + "path": "session.resetByType.thread.atHour", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.thread.idleMinutes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetByType.thread.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.resetTriggers", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Reset Triggers", + "help": "Lists message triggers that force a session reset when matched in inbound content. Use sparingly for explicit reset phrases so context is not dropped unexpectedly during normal conversation.", + "hasChildren": true + }, + { + "path": "session.resetTriggers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "session.scope", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Scope", + "help": "Sets base session grouping strategy: \"per-sender\" isolates by sender and \"global\" shares one session per channel context. Keep \"per-sender\" for safer multi-user behavior unless deliberate shared context is required.", + "hasChildren": false + }, + { + "path": "session.sendPolicy", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Session Send Policy", + "help": "Controls cross-session send permissions using allow/deny rules evaluated against channel, chatType, and key prefixes. Use this to fence where session tools can deliver messages in complex environments.", + "hasChildren": true + }, + { + "path": "session.sendPolicy.default", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Session Send Policy Default Action", + "help": "Sets fallback action when no sendPolicy rule matches: \"allow\" or \"deny\". Keep \"allow\" for simpler setups, or choose \"deny\" when you require explicit allow rules for every destination.", + "hasChildren": false + }, + { + "path": "session.sendPolicy.rules", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Session Send Policy Rules", + "help": "Ordered allow/deny rules evaluated before the default action, for example `{ action: \"deny\", match: { channel: \"discord\" } }`. Put most specific rules first so broad rules do not shadow exceptions.", + "hasChildren": true + }, + { + "path": "session.sendPolicy.rules.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "session.sendPolicy.rules.*.action", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Session Send Rule Action", + "help": "Defines rule decision as \"allow\" or \"deny\" when the corresponding match criteria are satisfied. Use deny-first ordering when enforcing strict boundaries with explicit allow exceptions.", + "hasChildren": false + }, + { + "path": "session.sendPolicy.rules.*.match", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Session Send Rule Match", + "help": "Defines optional rule match conditions that can combine channel, chatType, and key-prefix constraints. Keep matches narrow so policy intent stays readable and debugging remains straightforward.", + "hasChildren": true + }, + { + "path": "session.sendPolicy.rules.*.match.channel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Session Send Rule Channel", + "help": "Matches rule application to a specific channel/provider id (for example discord, telegram, slack). Use this when one channel should permit or deny delivery independently of others.", + "hasChildren": false + }, + { + "path": "session.sendPolicy.rules.*.match.chatType", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Session Send Rule Chat Type", + "help": "Matches rule application to chat type (direct, group, thread) so behavior varies by conversation form. Use this when DM and group destinations require different safety boundaries.", + "hasChildren": false + }, + { + "path": "session.sendPolicy.rules.*.match.keyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Session Send Rule Key Prefix", + "help": "Matches a normalized session-key prefix after internal key normalization steps in policy consumers. Use this for general prefix controls, and prefer rawKeyPrefix when exact full-key matching is required.", + "hasChildren": false + }, + { + "path": "session.sendPolicy.rules.*.match.rawKeyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "storage" + ], + "label": "Session Send Rule Raw Key Prefix", + "help": "Matches the raw, unnormalized session-key prefix for exact full-key policy targeting. Use this when normalized keyPrefix is too broad and you need agent-prefixed or transport-specific precision.", + "hasChildren": false + }, + { + "path": "session.store", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Store Path", + "help": "Sets the session storage file path used to persist session records across restarts. Use an explicit path only when you need custom disk layout, backup routing, or mounted-volume storage.", + "hasChildren": false + }, + { + "path": "session.threadBindings", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Thread Bindings", + "help": "Shared defaults for thread-bound session routing behavior across providers that support thread focus workflows. Configure global defaults here and override per channel only when behavior differs.", + "hasChildren": true + }, + { + "path": "session.threadBindings.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Thread Binding Enabled", + "help": "Global master switch for thread-bound session routing features and focused thread delivery behavior. Keep enabled for modern thread workflows unless you need to disable thread binding globally.", + "hasChildren": false + }, + { + "path": "session.threadBindings.idleHours", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Thread Binding Idle Timeout (hours)", + "help": "Default inactivity window in hours for thread-bound sessions across providers/channels (0 disables idle auto-unfocus). Default: 24.", + "hasChildren": false + }, + { + "path": "session.threadBindings.maxAgeHours", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "Thread Binding Max Age (hours)", + "help": "Optional hard max age in hours for thread-bound sessions across providers/channels (0 disables hard cap). Default: 0.", + "hasChildren": false + }, + { + "path": "session.typingIntervalSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage" + ], + "label": "Session Typing Interval (seconds)", + "help": "Controls interval for repeated typing indicators while replies are being prepared in typing-capable channels. Increase to reduce chatty updates or decrease for more active typing feedback.", + "hasChildren": false + }, + { + "path": "session.typingMode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage" + ], + "label": "Session Typing Mode", + "help": "Controls typing behavior timing: \"never\", \"instant\", \"thinking\", or \"message\" based emission points. Keep conservative modes in high-volume channels to avoid unnecessary typing noise.", + "hasChildren": false + }, + { + "path": "skills", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Skills", + "hasChildren": true + }, + { + "path": "skills.allowBundled", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "skills.allowBundled.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.entries", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "skills.entries.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "skills.entries.*.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security" + ], + "hasChildren": true + }, + { + "path": "skills.entries.*.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.entries.*.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.entries.*.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.entries.*.config", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "skills.entries.*.config.*", + "kind": "core", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.entries.*.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.entries.*.env", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "skills.entries.*.env.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.install", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "skills.install.nodeManager", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.install.preferBrew", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.limits", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "skills.limits.maxCandidatesPerRoot", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.limits.maxSkillFileBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.limits.maxSkillsInPrompt", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.limits.maxSkillsLoadedPerSource", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.limits.maxSkillsPromptChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.load", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "skills.load.extraDirs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "skills.load.extraDirs.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "skills.load.watch", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Watch Skills", + "help": "Enable filesystem watching for skill-definition changes so updates can be applied without full process restart. Keep enabled in development workflows and disable in immutable production images.", + "hasChildren": false + }, + { + "path": "skills.load.watchDebounceMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation", + "performance" + ], + "label": "Skills Watch Debounce (ms)", + "help": "Debounce window in milliseconds for coalescing rapid skill file changes before reload logic runs. Increase to reduce reload churn on frequent writes, or lower for faster edit feedback.", + "hasChildren": false + }, + { + "path": "talk", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Talk", + "help": "Talk-mode voice synthesis settings for voice identity, model selection, output format, and interruption behavior. Use this section to tune human-facing voice UX while controlling latency and cost.", + "hasChildren": true + }, + { + "path": "talk.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "media", + "security" + ], + "label": "Talk API Key", + "help": "Use this legacy ElevenLabs API key for Talk mode only during migration, and keep secrets in env-backed storage. Prefer talk.providers.elevenlabs.apiKey (fallback: ELEVENLABS_API_KEY).", + "hasChildren": true + }, + { + "path": "talk.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "talk.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "talk.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "talk.interruptOnSpeech", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Talk Interrupt on Speech", + "help": "If true (default), stop assistant speech when the user starts speaking in Talk mode. Keep enabled for conversational turn-taking.", + "hasChildren": false + }, + { + "path": "talk.modelId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "models" + ], + "label": "Talk Model ID", + "help": "Legacy ElevenLabs model ID for Talk mode (default: eleven_v3). Prefer talk.providers.elevenlabs.modelId.", + "hasChildren": false + }, + { + "path": "talk.outputFormat", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Talk Output Format", + "help": "Use this legacy ElevenLabs output format for Talk mode (for example pcm_44100 or mp3_44100_128) only during migration. Prefer talk.providers.elevenlabs.outputFormat.", + "hasChildren": false + }, + { + "path": "talk.provider", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Talk Active Provider", + "help": "Active Talk provider id (for example \"elevenlabs\").", + "hasChildren": false + }, + { + "path": "talk.providers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Talk Provider Settings", + "help": "Provider-specific Talk settings keyed by provider id. During migration, prefer this over legacy talk.* keys.", + "hasChildren": true + }, + { + "path": "talk.providers.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "talk.providers.*.*", + "kind": "core", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "talk.providers.*.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "media", + "security" + ], + "label": "Talk Provider API Key", + "help": "Provider API key for Talk mode.", + "hasChildren": true + }, + { + "path": "talk.providers.*.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "talk.providers.*.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "talk.providers.*.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "talk.providers.*.modelId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "models" + ], + "label": "Talk Provider Model ID", + "help": "Provider default model ID for Talk mode.", + "hasChildren": false + }, + { + "path": "talk.providers.*.outputFormat", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Talk Provider Output Format", + "help": "Provider default output format for Talk mode.", + "hasChildren": false + }, + { + "path": "talk.providers.*.voiceAliases", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Talk Provider Voice Aliases", + "help": "Optional provider voice alias map for Talk directives.", + "hasChildren": true + }, + { + "path": "talk.providers.*.voiceAliases.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "talk.providers.*.voiceId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Talk Provider Voice ID", + "help": "Provider default voice ID for Talk mode.", + "hasChildren": false + }, + { + "path": "talk.silenceTimeoutMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance" + ], + "label": "Talk Silence Timeout (ms)", + "help": "Milliseconds of user silence before Talk mode finalizes and sends the current transcript. Leave unset to keep the platform default pause window (700 ms on macOS and Android, 900 ms on iOS).", + "hasChildren": false + }, + { + "path": "talk.voiceAliases", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Talk Voice Aliases", + "help": "Use this legacy ElevenLabs voice alias map (for example {\"Clawd\":\"EXAVITQu4vr4xnSDxMaL\"}) only during migration. Prefer talk.providers.elevenlabs.voiceAliases.", + "hasChildren": true + }, + { + "path": "talk.voiceAliases.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "talk.voiceId", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media" + ], + "label": "Talk Voice ID", + "help": "Legacy ElevenLabs default voice ID for Talk mode. Prefer talk.providers.elevenlabs.voiceId.", + "hasChildren": false + }, + { + "path": "tools", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Tools", + "help": "Global tool access policy and capability configuration across web, exec, media, messaging, and elevated surfaces. Use this section to constrain risky capabilities before broad rollout.", + "hasChildren": true + }, + { + "path": "tools.agentToAgent", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Agent-to-Agent Tool Access", + "help": "Policy for allowing agent-to-agent tool calls and constraining which target agents can be reached. Keep disabled or tightly scoped unless cross-agent orchestration is intentionally enabled.", + "hasChildren": true + }, + { + "path": "tools.agentToAgent.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "tools" + ], + "label": "Agent-to-Agent Target Allowlist", + "help": "Allowlist of target agent IDs permitted for agent_to_agent calls when orchestration is enabled. Use explicit allowlists to avoid uncontrolled cross-agent call graphs.", + "hasChildren": true + }, + { + "path": "tools.agentToAgent.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.agentToAgent.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Enable Agent-to-Agent Tool", + "help": "Enables the agent_to_agent tool surface so one agent can invoke another agent at runtime. Keep off in simple deployments and enable only when orchestration value outweighs complexity.", + "hasChildren": false + }, + { + "path": "tools.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "tools" + ], + "label": "Tool Allowlist", + "help": "Absolute tool allowlist that replaces profile-derived defaults for strict environments. Use this only when you intentionally run a tightly curated subset of tool capabilities.", + "hasChildren": true + }, + { + "path": "tools.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.alsoAllow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "tools" + ], + "label": "Tool Allowlist Additions", + "help": "Extra tool allowlist entries merged on top of the selected tool profile and default policy. Keep this list small and explicit so audits can quickly identify intentional policy exceptions.", + "hasChildren": true + }, + { + "path": "tools.alsoAllow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.byProvider", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Tool Policy by Provider", + "help": "Per-provider tool allow/deny overrides keyed by channel/provider ID to tailor capabilities by surface. Use this when one provider needs stricter controls than global tool policy.", + "hasChildren": true + }, + { + "path": "tools.byProvider.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.byProvider.*.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.byProvider.*.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.byProvider.*.alsoAllow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.byProvider.*.alsoAllow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.byProvider.*.deny", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.byProvider.*.deny.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.byProvider.*.profile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.deny", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "tools" + ], + "label": "Tool Denylist", + "help": "Global tool denylist that blocks listed tools even when profile or provider rules would allow them. Use deny rules for emergency lockouts and long-term defense-in-depth.", + "hasChildren": true + }, + { + "path": "tools.deny.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.elevated", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Elevated Tool Access", + "help": "Elevated tool access controls for privileged command surfaces that should only be reachable from trusted senders. Keep disabled unless operator workflows explicitly require elevated actions.", + "hasChildren": true + }, + { + "path": "tools.elevated.allowFrom", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "tools" + ], + "label": "Elevated Tool Allow Rules", + "help": "Sender allow rules for elevated tools, usually keyed by channel/provider identity formats. Use narrow, explicit identities so elevated commands cannot be triggered by unintended users.", + "hasChildren": true + }, + { + "path": "tools.elevated.allowFrom.*", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.elevated.allowFrom.*.*", + "kind": "core", + "type": [ + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.elevated.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Enable Elevated Tool Access", + "help": "Enables elevated tool execution path when sender and policy checks pass. Keep disabled in public/shared channels and enable only for trusted owner-operated contexts.", + "hasChildren": false + }, + { + "path": "tools.exec", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Exec Tool", + "help": "Exec-tool policy grouping for shell execution host, security mode, approval behavior, and runtime bindings. Keep conservative defaults in production and tighten elevated execution paths.", + "hasChildren": true + }, + { + "path": "tools.exec.applyPatch", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.exec.applyPatch.allowModels", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "tools" + ], + "label": "apply_patch Model Allowlist", + "help": "Optional allowlist of model ids (e.g. \"gpt-5.2\" or \"openai/gpt-5.2\").", + "hasChildren": true + }, + { + "path": "tools.exec.applyPatch.allowModels.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.exec.applyPatch.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Enable apply_patch", + "help": "Experimental. Enables apply_patch for OpenAI models when allowed by tool policy.", + "hasChildren": false + }, + { + "path": "tools.exec.applyPatch.workspaceOnly", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "advanced", + "security", + "tools" + ], + "label": "apply_patch Workspace-Only", + "help": "Restrict apply_patch paths to the workspace directory (default: true). Set false to allow writing outside the workspace (dangerous).", + "hasChildren": false + }, + { + "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 + }, + { + "path": "tools.exec.backgroundMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.exec.cleanupMs", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "tools.exec.pathPrepend", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage", + "tools" + ], + "label": "Exec PATH Prepend", + "help": "Directories to prepend to PATH for exec runs (gateway/sandbox).", + "hasChildren": true + }, + { + "path": "tools.exec.pathPrepend.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.exec.safeBinProfiles", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage", + "tools" + ], + "label": "Exec Safe Bin Profiles", + "help": "Optional per-binary safe-bin profiles (positional limits + allowed/denied flags).", + "hasChildren": true + }, + { + "path": "tools.exec.safeBinProfiles.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.exec.safeBinProfiles.*.allowedValueFlags", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.exec.safeBinProfiles.*.allowedValueFlags.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.exec.safeBinProfiles.*.deniedFlags", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.exec.safeBinProfiles.*.deniedFlags.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.exec.safeBinProfiles.*.maxPositional", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.exec.safeBinProfiles.*.minPositional", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.exec.safeBins", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Exec Safe Bins", + "help": "Allow stdin-only safe binaries to run without explicit allowlist entries.", + "hasChildren": true + }, + { + "path": "tools.exec.safeBins.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.exec.safeBinTrustedDirs", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage", + "tools" + ], + "label": "Exec Safe Bin Trusted Dirs", + "help": "Additional explicit directories trusted for safe-bin path checks (PATH entries are never auto-trusted).", + "hasChildren": true + }, + { + "path": "tools.exec.safeBinTrustedDirs.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.exec.security", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "deny", + "allowlist", + "full" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Exec Security", + "help": "Execution security posture selector controlling sandbox/approval expectations for command execution. Keep strict security mode for untrusted prompts and relax only for trusted operator workflows.", + "hasChildren": false + }, + { + "path": "tools.exec.timeoutSec", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.fs", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.fs.workspaceOnly", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Workspace-only FS tools", + "help": "Restrict filesystem tools (read/write/edit/apply_patch) to the workspace directory (default: false).", + "hasChildren": false + }, + { + "path": "tools.links", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.links.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Enable Link Understanding", + "help": "Enable automatic link understanding pre-processing so URLs can be summarized before agent reasoning. Keep enabled for richer context, and disable when strict minimal processing is required.", + "hasChildren": false + }, + { + "path": "tools.links.maxLinks", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "tools" + ], + "label": "Link Understanding Max Links", + "help": "Maximum number of links expanded per turn during link understanding. Use lower values to control latency/cost in chatty threads and higher values when multi-link context is critical.", + "hasChildren": false + }, + { + "path": "tools.links.models", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "models", + "tools" + ], + "label": "Link Understanding Models", + "help": "Preferred model list for link understanding tasks, evaluated in order as fallbacks when supported. Use lightweight models first for routine summarization and heavier models only when needed.", + "hasChildren": true + }, + { + "path": "tools.links.models.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.links.models.*.args", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.links.models.*.args.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.links.models.*.command", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.links.models.*.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.links.models.*.type", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.links.scope", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Link Understanding Scope", + "help": "Controls when link understanding runs relative to conversation context and message type. Keep scope conservative to avoid unnecessary fetches on messages where links are not actionable.", + "hasChildren": true + }, + { + "path": "tools.links.scope.default", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.links.scope.rules", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.links.scope.rules.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.links.scope.rules.*.action", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.links.scope.rules.*.match", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.links.scope.rules.*.match.channel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.links.scope.rules.*.match.chatType", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.links.scope.rules.*.match.keyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.links.scope.rules.*.match.rawKeyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.links.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "tools" + ], + "label": "Link Understanding Timeout (sec)", + "help": "Per-link understanding timeout budget in seconds before unresolved links are skipped. Keep this bounded to avoid long stalls when external sites are slow or unreachable.", + "hasChildren": false + }, + { + "path": "tools.loopDetection", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.loopDetection.criticalThreshold", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Tool-loop Critical Threshold", + "help": "Critical threshold for repetitive patterns when detector is enabled (default: 20).", + "hasChildren": false + }, + { + "path": "tools.loopDetection.detectors", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.loopDetection.detectors.genericRepeat", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Tool-loop Generic Repeat Detection", + "help": "Enable generic repeated same-tool/same-params loop detection (default: true).", + "hasChildren": false + }, + { + "path": "tools.loopDetection.detectors.knownPollNoProgress", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Tool-loop Poll No-Progress Detection", + "help": "Enable known poll tool no-progress loop detection (default: true).", + "hasChildren": false + }, + { + "path": "tools.loopDetection.detectors.pingPong", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Tool-loop Ping-Pong Detection", + "help": "Enable ping-pong loop detection (default: true).", + "hasChildren": false + }, + { + "path": "tools.loopDetection.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Tool-loop Detection", + "help": "Enable repetitive tool-call loop detection and backoff safety checks (default: false).", + "hasChildren": false + }, + { + "path": "tools.loopDetection.globalCircuitBreakerThreshold", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "reliability", + "tools" + ], + "label": "Tool-loop Global Circuit Breaker Threshold", + "help": "Global no-progress breaker threshold (default: 30).", + "hasChildren": false + }, + { + "path": "tools.loopDetection.historySize", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Tool-loop History Size", + "help": "Tool history window size for loop detection (default: 30).", + "hasChildren": false + }, + { + "path": "tools.loopDetection.warningThreshold", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Tool-loop Warning Threshold", + "help": "Warning threshold for repetitive patterns when detector is enabled (default: 10).", + "hasChildren": false + }, + { + "path": "tools.media", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.attachments", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Audio Understanding Attachment Policy", + "help": "Attachment policy for audio inputs indicating which uploaded files are eligible for audio processing. Keep restrictive defaults in mixed-content channels to avoid unintended audio workloads.", + "hasChildren": true + }, + { + "path": "tools.media.audio.attachments.maxAttachments", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.attachments.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.attachments.prefer", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.deepgram", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.deepgram.detectLanguage", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.deepgram.punctuate", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.deepgram.smartFormat", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.echoFormat", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Transcript Echo Format", + "help": "Format string for the echoed transcript message. Use `{transcript}` as a placeholder for the transcribed text. Default: '📝 \"{transcript}\"'.", + "hasChildren": false + }, + { + "path": "tools.media.audio.echoTranscript", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Echo Transcript to Chat", + "help": "Echo the audio transcript back to the originating chat before agent processing. When enabled, users immediately see what was heard from their voice note, helping them verify transcription accuracy before the agent acts on it. Default: false.", + "hasChildren": false + }, + { + "path": "tools.media.audio.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Enable Audio Understanding", + "help": "Enable audio understanding so voice notes or audio clips can be transcribed/summarized for agent context. Disable when audio ingestion is outside policy or unnecessary for your workflows.", + "hasChildren": false + }, + { + "path": "tools.media.audio.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.language", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Audio Understanding Language", + "help": "Preferred language hint for audio understanding/transcription when provider support is available. Set this to improve recognition accuracy for known primary languages.", + "hasChildren": false + }, + { + "path": "tools.media.audio.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance", + "tools" + ], + "label": "Audio Understanding Max Bytes", + "help": "Maximum accepted audio payload size in bytes before processing is rejected or clipped by policy. Set this based on expected recording length and upstream provider limits.", + "hasChildren": false + }, + { + "path": "tools.media.audio.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance", + "tools" + ], + "label": "Audio Understanding Max Chars", + "help": "Maximum characters retained from audio understanding output to prevent oversized transcript injection. Increase for long-form dictation, or lower to keep conversational turns compact.", + "hasChildren": false + }, + { + "path": "tools.media.audio.models", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "models", + "tools" + ], + "label": "Audio Understanding Models", + "help": "Ordered model preferences specifically for audio understanding, used before shared media model fallback. Choose models optimized for transcription quality in your primary language/domain.", + "hasChildren": true + }, + { + "path": "tools.media.audio.models.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.models.*.args", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.models.*.args.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.capabilities", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.models.*.capabilities.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.command", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.deepgram", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.models.*.deepgram.detectLanguage", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.deepgram.punctuate", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.deepgram.smartFormat", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.models.*.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.language", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.preferredProfile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.profile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.prompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.provider", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.providerOptions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.models.*.providerOptions.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.models.*.providerOptions.*.*", + "kind": "core", + "type": [ + "boolean", + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.models.*.type", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.prompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Audio Understanding Prompt", + "help": "Instruction template guiding audio understanding output style, such as concise summary versus near-verbatim transcript. Keep wording consistent so downstream automations can rely on output format.", + "hasChildren": false + }, + { + "path": "tools.media.audio.providerOptions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.providerOptions.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.providerOptions.*.*", + "kind": "core", + "type": [ + "boolean", + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.scope", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Audio Understanding Scope", + "help": "Scope selector for when audio understanding runs across inbound messages and attachments. Keep focused scopes in high-volume channels to reduce cost and avoid accidental transcription.", + "hasChildren": true + }, + { + "path": "tools.media.audio.scope.default", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.scope.rules", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.scope.rules.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.scope.rules.*.action", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.scope.rules.*.match", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.audio.scope.rules.*.match.channel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.scope.rules.*.match.chatType", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.scope.rules.*.match.keyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.scope.rules.*.match.rawKeyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.audio.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance", + "tools" + ], + "label": "Audio Understanding Timeout (sec)", + "help": "Timeout in seconds for audio understanding execution before the operation is cancelled. Use longer timeouts for long recordings and tighter ones for interactive chat responsiveness.", + "hasChildren": false + }, + { + "path": "tools.media.concurrency", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance", + "tools" + ], + "label": "Media Understanding Concurrency", + "help": "Maximum number of concurrent media understanding operations per turn across image, audio, and video tasks. Lower this in resource-constrained deployments to prevent CPU/network saturation.", + "hasChildren": false + }, + { + "path": "tools.media.image", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.attachments", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Image Understanding Attachment Policy", + "help": "Attachment handling policy for image inputs, including which message attachments qualify for image analysis. Use restrictive settings in untrusted channels to reduce unexpected processing.", + "hasChildren": true + }, + { + "path": "tools.media.image.attachments.maxAttachments", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.attachments.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.attachments.prefer", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.deepgram", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.deepgram.detectLanguage", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.deepgram.punctuate", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.deepgram.smartFormat", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.echoFormat", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.echoTranscript", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Enable Image Understanding", + "help": "Enable image understanding so attached or referenced images can be interpreted into textual context. Disable if you need text-only operation or want to avoid image-processing cost.", + "hasChildren": false + }, + { + "path": "tools.media.image.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.language", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance", + "tools" + ], + "label": "Image Understanding Max Bytes", + "help": "Maximum accepted image payload size in bytes before the item is skipped or truncated by policy. Keep limits realistic for your provider caps and infrastructure bandwidth.", + "hasChildren": false + }, + { + "path": "tools.media.image.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance", + "tools" + ], + "label": "Image Understanding Max Chars", + "help": "Maximum characters returned from image understanding output after model response normalization. Use tighter limits to reduce prompt bloat and larger limits for detail-heavy OCR tasks.", + "hasChildren": false + }, + { + "path": "tools.media.image.models", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "models", + "tools" + ], + "label": "Image Understanding Models", + "help": "Ordered model preferences specifically for image understanding when you want to override shared media models. Put the most reliable multimodal model first to reduce fallback attempts.", + "hasChildren": true + }, + { + "path": "tools.media.image.models.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.models.*.args", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.models.*.args.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.capabilities", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.models.*.capabilities.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.command", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.deepgram", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.models.*.deepgram.detectLanguage", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.deepgram.punctuate", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.deepgram.smartFormat", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.models.*.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.language", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.preferredProfile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.profile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.prompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.provider", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.providerOptions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.models.*.providerOptions.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.models.*.providerOptions.*.*", + "kind": "core", + "type": [ + "boolean", + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.models.*.type", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.prompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Image Understanding Prompt", + "help": "Instruction template used for image understanding requests to shape extraction style and detail level. Keep prompts deterministic so outputs stay consistent across turns and channels.", + "hasChildren": false + }, + { + "path": "tools.media.image.providerOptions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.providerOptions.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.providerOptions.*.*", + "kind": "core", + "type": [ + "boolean", + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.scope", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Image Understanding Scope", + "help": "Scope selector for when image understanding is attempted (for example only explicit requests versus broader auto-detection). Keep narrow scope in busy channels to control token and API spend.", + "hasChildren": true + }, + { + "path": "tools.media.image.scope.default", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.scope.rules", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.scope.rules.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.scope.rules.*.action", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.scope.rules.*.match", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.image.scope.rules.*.match.channel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.scope.rules.*.match.chatType", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.scope.rules.*.match.keyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.scope.rules.*.match.rawKeyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.image.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance", + "tools" + ], + "label": "Image Understanding Timeout (sec)", + "help": "Timeout in seconds for each image understanding request before it is aborted. Increase for high-resolution analysis and lower it for latency-sensitive operator workflows.", + "hasChildren": false + }, + { + "path": "tools.media.models", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "models", + "tools" + ], + "label": "Media Understanding Shared Models", + "help": "Shared fallback model list used by media understanding tools when modality-specific model lists are not set. Keep this aligned with available multimodal providers to avoid runtime fallback churn.", + "hasChildren": true + }, + { + "path": "tools.media.models.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.models.*.args", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.models.*.args.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.capabilities", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.models.*.capabilities.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.command", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.deepgram", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.models.*.deepgram.detectLanguage", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.deepgram.punctuate", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.deepgram.smartFormat", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.models.*.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.language", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.preferredProfile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.profile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.prompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.provider", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.providerOptions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.models.*.providerOptions.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.models.*.providerOptions.*.*", + "kind": "core", + "type": [ + "boolean", + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.models.*.type", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.attachments", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Video Understanding Attachment Policy", + "help": "Attachment eligibility policy for video analysis, defining which message files can trigger video processing. Keep this explicit in shared channels to prevent accidental large media workloads.", + "hasChildren": true + }, + { + "path": "tools.media.video.attachments.maxAttachments", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.attachments.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.attachments.prefer", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.deepgram", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.deepgram.detectLanguage", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.deepgram.punctuate", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.deepgram.smartFormat", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.echoFormat", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.echoTranscript", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Enable Video Understanding", + "help": "Enable video understanding so clips can be summarized into text for downstream reasoning and responses. Disable when processing video is out of policy or too expensive for your deployment.", + "hasChildren": false + }, + { + "path": "tools.media.video.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.language", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance", + "tools" + ], + "label": "Video Understanding Max Bytes", + "help": "Maximum accepted video payload size in bytes before policy rejection or trimming occurs. Tune this to provider and infrastructure limits to avoid repeated timeout/failure loops.", + "hasChildren": false + }, + { + "path": "tools.media.video.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance", + "tools" + ], + "label": "Video Understanding Max Chars", + "help": "Maximum characters retained from video understanding output to control prompt growth. Raise for dense scene descriptions and lower when concise summaries are preferred.", + "hasChildren": false + }, + { + "path": "tools.media.video.models", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "models", + "tools" + ], + "label": "Video Understanding Models", + "help": "Ordered model preferences specifically for video understanding before shared media fallback applies. Prioritize models with strong multimodal video support to minimize degraded summaries.", + "hasChildren": true + }, + { + "path": "tools.media.video.models.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.models.*.args", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.models.*.args.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.capabilities", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.models.*.capabilities.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.command", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.deepgram", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.models.*.deepgram.detectLanguage", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.deepgram.punctuate", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.deepgram.smartFormat", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.headers", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.models.*.headers.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.language", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.maxBytes", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.preferredProfile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.profile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.prompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.provider", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.providerOptions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.models.*.providerOptions.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.models.*.providerOptions.*.*", + "kind": "core", + "type": [ + "boolean", + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.models.*.type", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.prompt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Video Understanding Prompt", + "help": "Instruction template for video understanding describing desired summary granularity and focus areas. Keep this stable so output quality remains predictable across model/provider fallbacks.", + "hasChildren": false + }, + { + "path": "tools.media.video.providerOptions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.providerOptions.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.providerOptions.*.*", + "kind": "core", + "type": [ + "boolean", + "number", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.scope", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "tools" + ], + "label": "Video Understanding Scope", + "help": "Scope selector controlling when video understanding is attempted across incoming events. Narrow scope in noisy channels, and broaden only where video interpretation is core to workflow.", + "hasChildren": true + }, + { + "path": "tools.media.video.scope.default", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.scope.rules", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.scope.rules.*", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.scope.rules.*.action", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.scope.rules.*.match", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.media.video.scope.rules.*.match.channel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.scope.rules.*.match.chatType", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.scope.rules.*.match.keyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.scope.rules.*.match.rawKeyPrefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.media.video.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "media", + "performance", + "tools" + ], + "label": "Video Understanding Timeout (sec)", + "help": "Timeout in seconds for each video understanding request before cancellation. Use conservative values in interactive channels and longer values for offline or batch-heavy processing.", + "hasChildren": false + }, + { + "path": "tools.message", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.message.allowCrossContextSend", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "tools" + ], + "label": "Allow Cross-Context Messaging", + "help": "Legacy override: allow cross-context sends across all providers.", + "hasChildren": false + }, + { + "path": "tools.message.broadcast", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.message.broadcast.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Enable Message Broadcast", + "help": "Enable broadcast action (default: true).", + "hasChildren": false + }, + { + "path": "tools.message.crossContext", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.message.crossContext.allowAcrossProviders", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "tools" + ], + "label": "Allow Cross-Context (Across Providers)", + "help": "Allow sends across different providers (default: false).", + "hasChildren": false + }, + { + "path": "tools.message.crossContext.allowWithinProvider", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "access", + "tools" + ], + "label": "Allow Cross-Context (Same Provider)", + "help": "Allow sends to other channels within the same provider (default: true).", + "hasChildren": false + }, + { + "path": "tools.message.crossContext.marker", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.message.crossContext.marker.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Cross-Context Marker", + "help": "Add a visible origin marker when sending cross-context (default: true).", + "hasChildren": false + }, + { + "path": "tools.message.crossContext.marker.prefix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Cross-Context Marker Prefix", + "help": "Text prefix for cross-context markers (supports \"{channel}\").", + "hasChildren": false + }, + { + "path": "tools.message.crossContext.marker.suffix", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Cross-Context Marker Suffix", + "help": "Text suffix for cross-context markers (supports \"{channel}\").", + "hasChildren": false + }, + { + "path": "tools.profile", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage", + "tools" + ], + "label": "Tool Profile", + "help": "Global tool profile name used to select a predefined tool policy baseline before applying allow/deny overrides. Use this for consistent environment posture across agents and keep profile names stable.", + "hasChildren": false + }, + { + "path": "tools.sandbox", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage", + "tools" + ], + "label": "Sandbox Tool Policy", + "help": "Tool policy wrapper for sandboxed agent executions so sandbox runs can have distinct capability boundaries. Use this to enforce stronger safety in sandbox contexts.", + "hasChildren": true + }, + { + "path": "tools.sandbox.tools", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "storage", + "tools" + ], + "label": "Sandbox Tool Allow/Deny Policy", + "help": "Allow/deny tool policy applied when agents run in sandboxed execution environments. Keep policies minimal so sandbox tasks cannot escalate into unnecessary external actions.", + "hasChildren": true + }, + { + "path": "tools.sandbox.tools.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.sandbox.tools.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.sandbox.tools.alsoAllow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.sandbox.tools.alsoAllow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.sandbox.tools.deny", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.sandbox.tools.deny.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.sessions", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.sessions_spawn", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.sessions_spawn.attachments", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.sessions_spawn.attachments.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.sessions_spawn.attachments.maxFileBytes", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.sessions_spawn.attachments.maxFiles", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.sessions_spawn.attachments.maxTotalBytes", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.sessions_spawn.attachments.retainOnSessionKeep", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.sessions.visibility", + "kind": "core", + "type": "string", + "required": false, + "enumValues": [ + "self", + "tree", + "agent", + "all" + ], + "deprecated": false, + "sensitive": false, + "tags": [ + "storage", + "tools" + ], + "label": "Session Tools Visibility", + "help": "Controls which sessions can be targeted by sessions_list/sessions_history/sessions_send. (\"tree\" default = current session + spawned subagent sessions; \"self\" = only current; \"agent\" = any session in the current agent id; \"all\" = any session; cross-agent still requires tools.agentToAgent).", + "hasChildren": false + }, + { + "path": "tools.subagents", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Subagent Tool Policy", + "help": "Tool policy wrapper for spawned subagents to restrict or expand tool availability compared to parent defaults. Use this to keep delegated agent capabilities scoped to task intent.", + "hasChildren": true + }, + { + "path": "tools.subagents.tools", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Subagent Tool Allow/Deny Policy", + "help": "Allow/deny tool policy applied to spawned subagent runtimes for per-subagent hardening. Keep this narrower than parent scope when subagents run semi-autonomous workflows.", + "hasChildren": true + }, + { + "path": "tools.subagents.tools.allow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.subagents.tools.allow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.subagents.tools.alsoAllow", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.subagents.tools.alsoAllow.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.subagents.tools.deny", + "kind": "core", + "type": "array", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.subagents.tools.deny.*", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Web Tools", + "help": "Web-tool policy grouping for search/fetch providers, limits, and fallback behavior tuning. Keep enabled settings aligned with API key availability and outbound networking policy.", + "hasChildren": true + }, + { + "path": "tools.web.fetch", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.web.fetch.cacheTtlMinutes", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage", + "tools" + ], + "label": "Web Fetch Cache TTL (min)", + "help": "Cache TTL in minutes for web_fetch results.", + "hasChildren": false + }, + { + "path": "tools.web.fetch.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Enable Web Fetch Tool", + "help": "Enable the web_fetch tool (lightweight HTTP fetch).", + "hasChildren": false + }, + { + "path": "tools.web.fetch.firecrawl", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.web.fetch.firecrawl.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security", + "tools" + ], + "label": "Firecrawl API Key", + "help": "Firecrawl API key (fallback: FIRECRAWL_API_KEY env var).", + "hasChildren": true + }, + { + "path": "tools.web.fetch.firecrawl.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.fetch.firecrawl.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.fetch.firecrawl.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "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 + }, + { + "path": "tools.web.fetch.firecrawl.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "tools" + ], + "label": "Firecrawl Timeout (sec)", + "help": "Timeout in seconds for Firecrawl requests.", + "hasChildren": false + }, + { + "path": "tools.web.fetch.maxChars", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "tools" + ], + "label": "Web Fetch Max Chars", + "help": "Max characters returned by web_fetch (truncated).", + "hasChildren": false + }, + { + "path": "tools.web.fetch.maxCharsCap", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "tools" + ], + "label": "Web Fetch Hard Max Chars", + "help": "Hard cap for web_fetch maxChars (applies to config and tool calls).", + "hasChildren": false + }, + { + "path": "tools.web.fetch.maxRedirects", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "storage", + "tools" + ], + "label": "Web Fetch Max Redirects", + "help": "Maximum redirects allowed for web_fetch (default: 3).", + "hasChildren": false + }, + { + "path": "tools.web.fetch.readability", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Web Fetch Readability Extraction", + "help": "Use Readability to extract main content from HTML (fallbacks to basic HTML cleanup).", + "hasChildren": false + }, + { + "path": "tools.web.fetch.timeoutSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance", + "tools" + ], + "label": "Web Fetch Timeout (sec)", + "help": "Timeout in seconds for web_fetch requests.", + "hasChildren": false + }, + { + "path": "tools.web.fetch.userAgent", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Web Fetch User-Agent", + "help": "Override User-Agent header for web_fetch requests.", + "hasChildren": false + }, + { + "path": "tools.web.search", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.web.search.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security", + "tools" + ], + "hasChildren": true + }, + { + "path": "tools.web.search.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.brave", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.web.search.brave.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security", + "tools" + ], + "hasChildren": true + }, + { + "path": "tools.web.search.brave.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.brave.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.brave.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.brave.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.brave.mode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.brave.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "tools.web.search.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "tools" + ], + "label": "Enable Web Search Tool", + "help": "Enable the web_search tool (requires a provider API key).", + "hasChildren": false + }, + { + "path": "tools.web.search.firecrawl", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.web.search.firecrawl.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security", + "tools" + ], + "hasChildren": true + }, + { + "path": "tools.web.search.firecrawl.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.firecrawl.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.firecrawl.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.firecrawl.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.firecrawl.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.gemini", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.web.search.gemini.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security", + "tools" + ], + "hasChildren": true + }, + { + "path": "tools.web.search.gemini.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.gemini.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.gemini.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.gemini.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.gemini.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.grok", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.web.search.grok.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security", + "tools" + ], + "hasChildren": true + }, + { + "path": "tools.web.search.grok.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.grok.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.grok.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.grok.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.grok.inlineCitations", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.grok.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.kimi", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.web.search.kimi.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security", + "tools" + ], + "hasChildren": true + }, + { + "path": "tools.web.search.kimi.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.kimi.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.kimi.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.kimi.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.kimi.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "path": "tools.web.search.perplexity", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "tools.web.search.perplexity.apiKey", + "kind": "core", + "type": [ + "object", + "string" + ], + "required": false, + "deprecated": false, + "sensitive": true, + "tags": [ + "auth", + "security", + "tools" + ], + "hasChildren": true + }, + { + "path": "tools.web.search.perplexity.apiKey.id", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.perplexity.apiKey.provider", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.perplexity.apiKey.source", + "kind": "core", + "type": "string", + "required": true, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.perplexity.baseUrl", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "path": "tools.web.search.perplexity.model", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, + { + "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 + }, + { + "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 + }, + { + "path": "ui", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "UI", + "help": "UI presentation settings for accenting and assistant identity shown in control surfaces. Use this for branding and readability customization without changing runtime behavior.", + "hasChildren": true + }, + { + "path": "ui.assistant", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Assistant Appearance", + "help": "Assistant display identity settings for name and avatar shown in UI surfaces. Keep these values aligned with your operator-facing persona and support expectations.", + "hasChildren": true + }, + { + "path": "ui.assistant.avatar", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Assistant Avatar", + "help": "Assistant avatar image source used in UI surfaces (URL, path, or data URI depending on runtime support). Use trusted assets and consistent branding dimensions for clean rendering.", + "hasChildren": false + }, + { + "path": "ui.assistant.name", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Assistant Name", + "help": "Display name shown for the assistant in UI views, chat chrome, and status contexts. Keep this stable so operators can reliably identify which assistant persona is active.", + "hasChildren": false + }, + { + "path": "ui.seamColor", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Accent Color", + "help": "Primary accent color used by UI surfaces for emphasis, badges, and visual identity cues. Use high-contrast values that remain readable across light/dark themes.", + "hasChildren": false + }, + { + "path": "update", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Updates", + "help": "Update-channel and startup-check behavior for keeping OpenClaw runtime versions current. Use conservative channels in production and more experimental channels only in controlled environments.", + "hasChildren": true + }, + { + "path": "update.auto", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": true + }, + { + "path": "update.auto.betaCheckIntervalHours", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Auto Update Beta Check Interval (hours)", + "help": "How often beta-channel checks run in hours (default: 1).", + "hasChildren": false + }, + { + "path": "update.auto.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Auto Update Enabled", + "help": "Enable background auto-update for package installs (default: false).", + "hasChildren": false + }, + { + "path": "update.auto.stableDelayHours", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Auto Update Stable Delay (hours)", + "help": "Minimum delay before stable-channel auto-apply starts (default: 6).", + "hasChildren": false + }, + { + "path": "update.auto.stableJitterHours", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Auto Update Stable Jitter (hours)", + "help": "Extra stable-channel rollout spread window in hours (default: 12).", + "hasChildren": false + }, + { + "path": "update.channel", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Update Channel", + "help": "Update channel for git + npm installs (\"stable\", \"beta\", or \"dev\").", + "hasChildren": false + }, + { + "path": "update.checkOnStart", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "label": "Update Check on Start", + "help": "Check for npm updates when the gateway starts (default: true).", + "hasChildren": false + }, + { + "path": "web", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Web Channel", + "help": "Web channel runtime settings for heartbeat and reconnect behavior when operating web-based chat surfaces. Use reconnect values tuned to your network reliability profile and expected uptime needs.", + "hasChildren": true + }, + { + "path": "web.enabled", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Web Channel Enabled", + "help": "Enables the web channel runtime and related websocket lifecycle behavior. Keep disabled when web chat is unused to reduce active connection management overhead.", + "hasChildren": false + }, + { + "path": "web.heartbeatSeconds", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "automation" + ], + "label": "Web Channel Heartbeat Interval (sec)", + "help": "Heartbeat interval in seconds for web channel connectivity and liveness maintenance. Use shorter intervals for faster detection, or longer intervals to reduce keepalive chatter.", + "hasChildren": false + }, + { + "path": "web.reconnect", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Web Channel Reconnect Policy", + "help": "Reconnect backoff policy for web channel reconnect attempts after transport failure. Keep bounded retries and jitter tuned to avoid thundering-herd reconnect behavior.", + "hasChildren": true + }, + { + "path": "web.reconnect.factor", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Web Reconnect Backoff Factor", + "help": "Exponential backoff multiplier used between reconnect attempts in web channel retry loops. Keep factor above 1 and tune with jitter for stable large-fleet reconnect behavior.", + "hasChildren": false + }, + { + "path": "web.reconnect.initialMs", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Web Reconnect Initial Delay (ms)", + "help": "Initial reconnect delay in milliseconds before the first retry after disconnection. Use modest delays to recover quickly without immediate retry storms.", + "hasChildren": false + }, + { + "path": "web.reconnect.jitter", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Web Reconnect Jitter", + "help": "Randomization factor (0-1) applied to reconnect delays to desynchronize clients after outage events. Keep non-zero jitter in multi-client deployments to reduce synchronized spikes.", + "hasChildren": false + }, + { + "path": "web.reconnect.maxAttempts", + "kind": "core", + "type": "integer", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Web Reconnect Max Attempts", + "help": "Maximum reconnect attempts before giving up for the current failure sequence (0 means no retries). Use finite caps for controlled failure handling in automation-sensitive environments.", + "hasChildren": false + }, + { + "path": "web.reconnect.maxMs", + "kind": "core", + "type": "number", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "performance" + ], + "label": "Web Reconnect Max Delay (ms)", + "help": "Maximum reconnect backoff cap in milliseconds to bound retry delay growth over repeated failures. Use a reasonable cap so recovery remains timely after prolonged outages.", + "hasChildren": false + }, + { + "path": "wizard", + "kind": "core", + "type": "object", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Setup Wizard State", + "help": "Setup wizard state tracking fields that record the most recent guided setup run details. Keep these fields for observability and troubleshooting of setup flows across upgrades.", + "hasChildren": true + }, + { + "path": "wizard.lastRunAt", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Wizard Last Run Timestamp", + "help": "ISO timestamp for when the setup wizard most recently completed on this host. Use this to confirm setup recency during support and operational audits.", + "hasChildren": false + }, + { + "path": "wizard.lastRunCommand", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Wizard Last Run Command", + "help": "Command invocation recorded for the latest wizard run to preserve execution context. Use this to reproduce setup steps when verifying setup regressions.", + "hasChildren": false + }, + { + "path": "wizard.lastRunCommit", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Wizard Last Run Commit", + "help": "Source commit identifier recorded for the last wizard execution in development builds. Use this to correlate setup behavior with exact source state during debugging.", + "hasChildren": false + }, + { + "path": "wizard.lastRunMode", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Wizard Last Run Mode", + "help": "Wizard execution mode recorded as \"local\" or \"remote\" for the most recent setup flow. Use this to understand whether setup targeted direct local runtime or remote gateway topology.", + "hasChildren": false + }, + { + "path": "wizard.lastRunVersion", + "kind": "core", + "type": "string", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [ + "advanced" + ], + "label": "Wizard Last Run Version", + "help": "OpenClaw version recorded at the time of the most recent wizard run on this config. Use this when diagnosing behavior differences across version-to-version setup changes.", + "hasChildren": false + } + ] +} diff --git a/docs/.generated/config-baseline.jsonl b/docs/.generated/config-baseline.jsonl new file mode 100644 index 00000000000..8c75f3c5177 --- /dev/null +++ b/docs/.generated/config-baseline.jsonl @@ -0,0 +1,5519 @@ +{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5518} +{"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} +{"recordType":"path","path":"acp.backend","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Backend","help":"Default ACP runtime backend id (for example: acpx). Must match a registered ACP runtime plugin backend.","hasChildren":false} +{"recordType":"path","path":"acp.defaultAgent","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Default Agent","help":"Fallback ACP target agent id used when ACP spawns do not specify an explicit target.","hasChildren":false} +{"recordType":"path","path":"acp.dispatch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"acp.dispatch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Dispatch Enabled","help":"Independent dispatch gate for ACP session turns (default: true). Set false to keep ACP commands available while blocking ACP turn execution.","hasChildren":false} +{"recordType":"path","path":"acp.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Enabled","help":"Global ACP feature gate. Keep disabled unless ACP runtime + policy are configured.","hasChildren":false} +{"recordType":"path","path":"acp.maxConcurrentSessions","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"ACP Max Concurrent Sessions","help":"Maximum concurrently active ACP sessions across this gateway process.","hasChildren":false} +{"recordType":"path","path":"acp.runtime","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"acp.runtime.installCommand","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Runtime Install Command","help":"Optional operator install/setup command shown by `/acp install` and `/acp doctor` when ACP backend wiring is missing.","hasChildren":false} +{"recordType":"path","path":"acp.runtime.ttlMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Runtime TTL (minutes)","help":"Idle runtime TTL in minutes for ACP session workers before eligible cleanup.","hasChildren":false} +{"recordType":"path","path":"acp.stream","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Stream","help":"ACP streaming projection controls for chunk sizing, metadata visibility, and deduped delivery behavior.","hasChildren":true} +{"recordType":"path","path":"acp.stream.coalesceIdleMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Stream Coalesce Idle (ms)","help":"Coalescer idle flush window in milliseconds for ACP streamed text before block replies are emitted.","hasChildren":false} +{"recordType":"path","path":"acp.stream.deliveryMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Stream Delivery Mode","help":"ACP delivery style: live streams projected output incrementally, final_only buffers all projected ACP output until terminal turn events.","hasChildren":false} +{"recordType":"path","path":"acp.stream.hiddenBoundarySeparator","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Stream Hidden Boundary Separator","help":"Separator inserted before next visible assistant text when hidden ACP tool lifecycle events occurred (none|space|newline|paragraph). Default: paragraph.","hasChildren":false} +{"recordType":"path","path":"acp.stream.maxChunkChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"ACP Stream Max Chunk Chars","help":"Maximum chunk size for ACP streamed block projection before splitting into multiple block replies.","hasChildren":false} +{"recordType":"path","path":"acp.stream.maxOutputChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"ACP Stream Max Output Chars","help":"Maximum assistant output characters projected per ACP turn before truncation notice is emitted.","hasChildren":false} +{"recordType":"path","path":"acp.stream.maxSessionUpdateChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"ACP Stream Max Session Update Chars","help":"Maximum characters for projected ACP session/update lines (tool/status updates).","hasChildren":false} +{"recordType":"path","path":"acp.stream.repeatSuppression","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Stream Repeat Suppression","help":"When true (default), suppress repeated ACP status/tool projection lines in a turn while keeping raw ACP events unchanged.","hasChildren":false} +{"recordType":"path","path":"acp.stream.tagVisibility","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Stream Tag Visibility","help":"Per-sessionUpdate visibility overrides for ACP projection (for example usage_update, available_commands_update).","hasChildren":true} +{"recordType":"path","path":"acp.stream.tagVisibility.*","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agents","help":"Agent runtime configuration root covering defaults and explicit agent entries used for routing and execution context. Keep this section explicit so model/tool behavior stays predictable across multi-agent workflows.","hasChildren":true} +{"recordType":"path","path":"agents.defaults","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Defaults","help":"Shared default settings inherited by agents unless overridden per entry in agents.list. Use defaults to enforce consistent baseline behavior and reduce duplicated per-agent configuration.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.blockStreamingBreak","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.blockStreamingChunk","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.blockStreamingChunk.breakPreference","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.blockStreamingChunk.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.blockStreamingChunk.minChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.blockStreamingCoalesce","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.blockStreamingCoalesce.idleMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.blockStreamingCoalesce.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.blockStreamingCoalesce.minChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.blockStreamingDefault","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.bootstrapMaxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Bootstrap Max Chars","help":"Max characters of each workspace bootstrap file injected into the system prompt before truncation (default: 20000).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.bootstrapPromptTruncationWarning","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Bootstrap Prompt Truncation Warning","help":"Inject agent-visible warning text when bootstrap files are truncated: \"off\", \"once\" (default), or \"always\".","hasChildren":false} +{"recordType":"path","path":"agents.defaults.bootstrapTotalMaxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Bootstrap Total Max Chars","help":"Max total characters across all injected workspace bootstrap files (default: 150000).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"CLI Backends","help":"Optional CLI backends for text-only fallback (claude-cli, etc.).","hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.clearEnv","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.clearEnv.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.command","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.env","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.env.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.imageArg","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.imageMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.input","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.maxPromptArgChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.modelAliases","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.modelAliases.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.modelArg","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.output","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog.fresh","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog.fresh.maxMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog.fresh.minMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog.fresh.noOutputTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog.fresh.noOutputTimeoutRatio","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog.resume","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog.resume.maxMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog.resume.minMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog.resume.noOutputTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.reliability.watchdog.resume.noOutputTimeoutRatio","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.resumeArgs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.resumeArgs.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.resumeOutput","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.serialize","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.sessionArg","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.sessionArgs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.sessionArgs.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.sessionIdFields","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.cliBackends.*.sessionIdFields.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.sessionMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.systemPromptArg","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.systemPromptMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.cliBackends.*.systemPromptWhen","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction","help":"Compaction tuning for when context nears token limits, including history share, reserve headroom, and pre-compaction memory flush behavior. Use this when long-running sessions need stable continuity under tight context windows.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.compaction.customInstructions","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.identifierInstructions","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Identifier Instructions","help":"Custom identifier-preservation instruction text used when identifierPolicy=\"custom\". Keep this explicit and safety-focused so compaction summaries do not rewrite opaque IDs, URLs, hosts, or ports.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.identifierPolicy","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Compaction Identifier Policy","help":"Identifier-preservation policy for compaction summaries: \"strict\" prepends built-in opaque-identifier retention guidance (default), \"off\" disables this prefix, and \"custom\" uses identifierInstructions. Keep \"strict\" unless you have a specific compatibility need.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.keepRecentTokens","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["auth","security"],"label":"Compaction Keep Recent Tokens","help":"Minimum token budget preserved from the most recent conversation window during compaction. Use higher values to protect immediate context continuity and lower values to keep more long-tail history.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.maxHistoryShare","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Compaction Max History Share","help":"Maximum fraction of total context budget allowed for retained history after compaction (range 0.1-0.9). Use lower shares for more generation headroom or higher shares for deeper historical continuity.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.memoryFlush","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Memory Flush","help":"Pre-compaction memory flush settings that run an agentic memory write before heavy compaction. Keep enabled for long sessions so salient context is persisted before aggressive trimming.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.compaction.memoryFlush.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Memory Flush Enabled","help":"Enables pre-compaction memory flush before the runtime performs stronger history reduction near token limits. Keep enabled unless you intentionally disable memory side effects in constrained environments.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.memoryFlush.forceFlushTranscriptBytes","kind":"core","type":["integer","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Memory Flush Transcript Size Threshold","help":"Forces pre-compaction memory flush when transcript file size reaches this threshold (bytes or strings like \"2mb\"). Use this to prevent long-session hangs even when token counters are stale; set to 0 to disable.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.memoryFlush.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Memory Flush Prompt","help":"User-prompt template used for the pre-compaction memory flush turn when generating memory candidates. Use this only when you need custom extraction instructions beyond the default memory flush behavior.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.memoryFlush.softThresholdTokens","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["auth","security"],"label":"Compaction Memory Flush Soft Threshold","help":"Threshold distance to compaction (in tokens) that triggers pre-compaction memory flush execution. Use earlier thresholds for safer persistence, or tighter thresholds for lower flush frequency.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.memoryFlush.systemPrompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Memory Flush System Prompt","help":"System-prompt override for the pre-compaction memory flush turn to control extraction style and safety constraints. Use carefully so custom instructions do not reduce memory quality or leak sensitive context.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Mode","help":"Compaction strategy mode: \"default\" uses baseline behavior, while \"safeguard\" applies stricter guardrails to preserve recent context. Keep \"default\" unless you observe aggressive history loss near limit boundaries.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Compaction Model Override","help":"Optional provider/model override used only for compaction summarization. Set this when you want compaction to run on a different model than the session default, and leave it unset to keep using the primary agent model.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.postCompactionSections","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Post-Compaction Context Sections","help":"AGENTS.md H2/H3 section names re-injected after compaction so the agent reruns critical startup guidance. Leave unset to use \"Session Startup\"/\"Red Lines\" with legacy fallback to \"Every Session\"/\"Safety\"; set to [] to disable reinjection entirely.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.compaction.postCompactionSections.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.postIndexSync","kind":"core","type":"string","required":false,"enumValues":["off","async","await"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Post-Index Sync","help":"Controls post-compaction session memory reindex mode: \"off\", \"async\", or \"await\" (default: \"async\"). Use \"await\" for strongest freshness, \"async\" for lower compaction latency, and \"off\" only when session-memory sync is handled elsewhere.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.qualityGuard","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Quality Guard","help":"Optional quality-audit retry settings for safeguard compaction summaries. Leave this disabled unless you explicitly want summary audits and one-shot regeneration on failed checks.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.compaction.qualityGuard.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Quality Guard Enabled","help":"Enables summary quality audits and regeneration retries for safeguard compaction. Default: false, so safeguard mode alone does not turn on retry behavior.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.qualityGuard.maxRetries","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Compaction Quality Guard Max Retries","help":"Maximum number of regeneration retries after a failed safeguard summary quality audit. Use small values to bound extra latency and token cost.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.recentTurnsPreserve","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Compaction Preserve Recent Turns","help":"Number of most recent user/assistant turns kept verbatim outside safeguard summarization (default: 3). Raise this to preserve exact recent dialogue context, or lower it to maximize compaction savings.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.reserveTokens","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["auth","security"],"label":"Compaction Reserve Tokens","help":"Token headroom reserved for reply generation and tool output after compaction runs. Use higher reserves for verbose/tool-heavy sessions, and lower reserves when maximizing retained history matters more.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.reserveTokensFloor","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["auth","security"],"label":"Compaction Reserve Token Floor","help":"Minimum floor enforced for reserveTokens in Pi compaction paths (0 disables the floor guard). Use a non-zero floor to avoid over-aggressive compression under fluctuating token estimates.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.compaction.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Compaction Timeout (Seconds)","help":"Maximum time in seconds allowed for a single compaction operation before it is aborted (default: 900). Increase this for very large sessions that need more time to summarize, or decrease it to fail faster on unresponsive models.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.contextPruning.hardClear","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.contextPruning.hardClear.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.hardClear.placeholder","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.hardClearRatio","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.keepLastAssistants","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.minPrunableToolChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.softTrim","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.contextPruning.softTrim.headChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.softTrim.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.softTrim.tailChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.softTrimRatio","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.tools","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.contextPruning.tools.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.contextPruning.tools.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.tools.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.contextPruning.tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextPruning.ttl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.contextTokens","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.elevatedDefault","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.embeddedPi","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Embedded Pi","help":"Embedded Pi runner hardening controls for how workspace-local Pi settings are trusted and applied in OpenClaw sessions.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.embeddedPi.projectSettingsPolicy","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Embedded Pi Project Settings Policy","help":"How embedded Pi handles workspace-local `.pi/config/settings.json`: \"sanitize\" (default) strips shellPath/shellCommandPrefix, \"ignore\" disables project settings entirely, and \"trusted\" applies project settings as-is.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.envelopeElapsed","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Envelope Elapsed","help":"Include elapsed time in message envelopes (\"on\" or \"off\").","hasChildren":false} +{"recordType":"path","path":"agents.defaults.envelopeTimestamp","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Envelope Timestamp","help":"Include absolute timestamps in message envelopes (\"on\" or \"off\").","hasChildren":false} +{"recordType":"path","path":"agents.defaults.envelopeTimezone","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Envelope Timezone","help":"Timezone for message envelopes (\"utc\", \"local\", \"user\", or an IANA timezone string).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.heartbeat.accountId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.ackMaxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.activeHours","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.heartbeat.activeHours.end","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.activeHours.start","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.activeHours.timezone","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.directPolicy","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access","automation","storage"],"label":"Heartbeat Direct Policy","help":"Controls whether heartbeat delivery may target direct/DM chats: \"allow\" (default) permits DM delivery and \"block\" suppresses direct-target sends.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.every","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.includeReasoning","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.isolatedSession","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.lightContext","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.session","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.suppressToolErrorWarnings","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Heartbeat Suppress Tool Error Warnings","help":"Suppress tool error warning payloads during heartbeat runs.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.target","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"help":"Delivery target (\"last\", \"none\", or a channel id). Known channels: telegram, whatsapp, discord, irc, googlechat, slack, signal, imessage, line, bluebubbles, feishu, matrix, mattermost, msteams, nextcloud-talk, nostr, synology-chat, tlon, twitch, zalo, zalouser.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.heartbeat.to","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.humanDelay","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.humanDelay.maxMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Human Delay Max (ms)","help":"Maximum delay in ms for custom humanDelay (default: 2500).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.humanDelay.minMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Human Delay Min (ms)","help":"Minimum delay in ms for custom humanDelay (default: 800).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.humanDelay.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Human Delay Mode","help":"Delay style for block replies (\"off\", \"natural\", \"custom\").","hasChildren":false} +{"recordType":"path","path":"agents.defaults.imageGenerationModel","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.imageGenerationModel.fallbacks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["media","reliability"],"label":"Image Generation Model Fallbacks","help":"Ordered fallback image-generation models (provider/model).","hasChildren":true} +{"recordType":"path","path":"agents.defaults.imageGenerationModel.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.imageGenerationModel.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Image Generation Model","help":"Optional image-generation model (provider/model) used by the shared image generation capability.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.imageMaxDimensionPx","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance"],"label":"Image Max Dimension (px)","help":"Max image side length in pixels when sanitizing transcript/tool-result image payloads (default: 1200).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.imageModel","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"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.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} +{"recordType":"path","path":"agents.defaults.memorySearch.cache","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.cache.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Memory Search Embedding Cache","help":"Caches computed chunk embeddings in SQLite so reindexing and incremental updates run faster (default: true). Keep this enabled unless investigating cache correctness or minimizing disk usage.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.cache.maxEntries","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Memory Search Embedding Cache Max Entries","help":"Sets a best-effort upper bound on cached embeddings kept in SQLite for memory search. Use this when controlling disk growth matters more than peak reindex speed.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.chunking","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.chunking.overlap","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Chunk Overlap Tokens","help":"Token overlap between adjacent memory chunks to preserve context continuity near split boundaries. Use modest overlap to reduce boundary misses without inflating index size too aggressively.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.chunking.tokens","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["auth","security"],"label":"Memory Chunk Tokens","help":"Chunk size in tokens used when splitting memory sources before embedding/indexing. Increase for broader context per chunk, or lower to improve precision on pinpoint lookups.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable Memory Search","help":"Master toggle for memory search indexing and retrieval behavior on this agent profile. Keep enabled for semantic recall, and disable when you want fully stateless responses.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.experimental","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.experimental.sessionMemory","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","security","storage"],"label":"Memory Search Session Index (Experimental)","help":"Indexes session transcripts into memory search so responses can reference prior chat turns. Keep this off unless transcript recall is needed, because indexing cost and storage usage both increase.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.extraPaths","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Extra Memory Paths","help":"Adds extra directories or .md files to the memory index beyond default memory files. Use this when key reference docs live elsewhere in your repo; when multimodal memory is enabled, matching image/audio files under these paths are also eligible for indexing.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.extraPaths.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.fallback","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["reliability"],"label":"Memory Search Fallback","help":"Backup provider used when primary embeddings fail: \"openai\", \"gemini\", \"voyage\", \"mistral\", \"ollama\", \"local\", or \"none\". Set a real fallback for production reliability; use \"none\" only if you prefer explicit failures.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.local","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.local.modelCacheDir","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.local.modelPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Local Embedding Model Path","help":"Specifies the local embedding model source for local memory search, such as a GGUF file path or `hf:` URI. Use this only when provider is `local`, and verify model compatibility before large index rebuilds.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Memory Search Model","help":"Embedding model override used by the selected memory provider when a non-default model is required. Set this only when you need explicit recall quality/cost tuning beyond provider defaults.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.multimodal","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Multimodal","help":"Optional multimodal memory settings for indexing image and audio files from configured extra paths. Keep this off unless your embedding model explicitly supports cross-modal embeddings, and set `memorySearch.fallback` to \"none\" while it is enabled. Matching files are uploaded to the configured remote embedding provider during indexing.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.multimodal.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable Memory Search Multimodal","help":"Enables image/audio memory indexing from extraPaths. This currently requires Gemini embedding-2, keeps the default memory roots Markdown-only, disables memory-search fallback providers, and uploads matching binary content to the configured remote embedding provider.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.multimodal.maxFileBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Memory Search Multimodal Max File Bytes","help":"Sets the maximum bytes allowed per multimodal file before it is skipped during memory indexing. Use this to cap upload cost and indexing latency, or raise it for short high-quality audio clips.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.multimodal.modalities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Multimodal Modalities","help":"Selects which multimodal file types are indexed from extraPaths: \"image\", \"audio\", or \"all\". Keep this narrow to avoid indexing large binary corpora unintentionally.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.multimodal.modalities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.outputDimensionality","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Output Dimensionality","help":"Gemini embedding-2 only: chooses the output vector size for memory embeddings. Use 768, 1536, or 3072 (default), and expect a full reindex when you change it because stored vector dimensions must stay consistent.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Provider","help":"Selects the embedding backend used to build/query memory vectors: \"openai\", \"gemini\", \"voyage\", \"mistral\", \"ollama\", or \"local\". Keep your most reliable provider here and configure fallback for resilience.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.query","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid.candidateMultiplier","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Hybrid Candidate Multiplier","help":"Expands the candidate pool before reranking (default: 4). Raise this for better recall on noisy corpora, but expect more compute and slightly slower searches.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Hybrid","help":"Combines BM25 keyword matching with vector similarity for better recall on mixed exact + semantic queries. Keep enabled unless you are isolating ranking behavior for troubleshooting.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid.mmr","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid.mmr.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search MMR Re-ranking","help":"Adds MMR reranking to diversify results and reduce near-duplicate snippets in a single answer window. Enable when recall looks repetitive; keep off for strict score ordering.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid.mmr.lambda","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search MMR Lambda","help":"Sets MMR relevance-vs-diversity balance (0 = most diverse, 1 = most relevant, default: 0.7). Lower values reduce repetition; higher values keep tightly relevant but may duplicate.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid.temporalDecay","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid.temporalDecay.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Temporal Decay","help":"Applies recency decay so newer memory can outrank older memory when scores are close. Enable when timeliness matters; keep off for timeless reference knowledge.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid.temporalDecay.halfLifeDays","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Temporal Decay Half-life (Days)","help":"Controls how fast older memory loses rank when temporal decay is enabled (half-life in days, default: 30). Lower values prioritize recent context more aggressively.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid.textWeight","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Text Weight","help":"Controls how strongly BM25 keyword relevance influences hybrid ranking (0-1). Increase for exact-term matching; decrease when semantic matches should rank higher.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.query.hybrid.vectorWeight","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Vector Weight","help":"Controls how strongly semantic similarity influences hybrid ranking (0-1). Increase when paraphrase matching matters more than exact terms; decrease for stricter keyword emphasis.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.query.maxResults","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Memory Search Max Results","help":"Maximum number of memory hits returned from search before downstream reranking and prompt injection. Raise for broader recall, or lower for tighter prompts and faster responses.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.query.minScore","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Min Score","help":"Minimum relevance score threshold for including memory results in final recall output. Increase to reduce weak/noisy matches, or lower when you need more permissive retrieval.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.remote","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.remote.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Remote Embedding API Key","help":"Supplies a dedicated API key for remote embedding calls used by memory indexing and query-time embeddings. Use this when memory embeddings should use different credentials than global defaults or environment variables.","hasChildren":true} +{"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"],"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} +{"recordType":"path","path":"agents.defaults.memorySearch.remote.batch.pollIntervalMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Remote Batch Poll Interval (ms)","help":"Controls how often the system polls provider APIs for batch job status in milliseconds (default: 2000). Use longer intervals to reduce API chatter, or shorter intervals for faster completion detection.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.remote.batch.timeoutMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Remote Batch Timeout (min)","help":"Sets the maximum wait time for a full embedding batch operation in minutes (default: 60). Increase for very large corpora or slower providers, and lower it to fail fast in automation-heavy flows.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.remote.batch.wait","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Remote Batch Wait for Completion","help":"Waits for batch embedding jobs to fully finish before the indexing operation completes. Keep this enabled for deterministic indexing state; disable only if you accept delayed consistency.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.remote.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Remote Embedding Headers","help":"Adds custom HTTP headers to remote embedding requests, merged with provider defaults. Use this for proxy auth and tenant routing headers, and keep values minimal to avoid leaking sensitive metadata.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.remote.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.sources","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Sources","help":"Chooses which sources are indexed: \"memory\" reads MEMORY.md + memory files, and \"sessions\" includes transcript history. Keep [\"memory\"] unless you need recall from prior chat transcripts.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.sources.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.store","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.store.driver","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.store.path","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Memory Search Index Path","help":"Sets where the SQLite memory index is stored on disk for each agent. Keep the default `~/.openclaw/memory/{agentId}.sqlite` unless you need custom storage placement or backup policy alignment.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.store.vector","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.store.vector.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Memory Search Vector Index","help":"Enables the sqlite-vec extension used for vector similarity queries in memory search (default: true). Keep this enabled for normal semantic recall; disable only for debugging or fallback-only operation.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.store.vector.extensionPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Memory Search Vector Extension Path","help":"Overrides the auto-discovered sqlite-vec extension library path (`.dylib`, `.so`, or `.dll`). Use this when your runtime cannot find sqlite-vec automatically or you pin a known-good build.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.sync","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.sync.intervalMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.sync.onSearch","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Index on Search (Lazy)","help":"Uses lazy sync by scheduling reindex on search after content changes are detected. Keep enabled for lower idle overhead, or disable if you require pre-synced indexes before any query.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.sync.onSessionStart","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["automation","storage"],"label":"Index on Session Start","help":"Triggers a memory index sync when a session starts so early turns see fresh memory content. Keep enabled when startup freshness matters more than initial turn latency.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.sync.sessions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.memorySearch.sync.sessions.deltaBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Delta Bytes","help":"Requires at least this many newly appended bytes before session transcript changes trigger reindex (default: 100000). Increase to reduce frequent small reindexes, or lower for faster transcript freshness.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.sync.sessions.deltaMessages","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Delta Messages","help":"Requires at least this many appended transcript messages before reindex is triggered (default: 50). Lower this for near-real-time transcript recall, or raise it to reduce indexing churn.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.sync.sessions.postCompactionForce","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Force Reindex After Compaction","help":"Forces a session memory-search reindex after compaction-triggered transcript updates (default: true). Keep enabled when compacted summaries must be immediately searchable, or disable to reduce write-time indexing pressure.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.sync.watch","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Watch Memory Files","help":"Watches memory files and schedules index updates from file-change events (chokidar). Enable for near-real-time freshness; disable on very large workspaces if watch churn is too noisy.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.sync.watchDebounceMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["automation","performance"],"label":"Memory Watch Debounce (ms)","help":"Debounce window in milliseconds for coalescing rapid file-watch events before reindex runs. Increase to reduce churn on frequently-written files, or lower for faster freshness.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.model","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.model.fallbacks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["models","reliability"],"label":"Model Fallbacks","help":"Ordered fallback models (provider/model). Used when the primary model fails.","hasChildren":true} +{"recordType":"path","path":"agents.defaults.model.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.model.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Primary Model","help":"Primary model (provider/model).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.models","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Models","help":"Configured model catalog (keys are full provider/model IDs).","hasChildren":true} +{"recordType":"path","path":"agents.defaults.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.models.*.alias","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.models.*.params","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.models.*.params.*","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.models.*.streaming","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.pdfMaxBytesMb","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"PDF Max Size (MB)","help":"Maximum PDF file size in megabytes for the PDF tool (default: 10).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.pdfMaxPages","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"PDF Max Pages","help":"Maximum number of PDF pages to process for the PDF tool (default: 20).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.pdfModel","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.pdfModel.fallbacks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["reliability"],"label":"PDF Model Fallbacks","help":"Ordered fallback PDF models (provider/model).","hasChildren":true} +{"recordType":"path","path":"agents.defaults.pdfModel.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.pdfModel.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"PDF Model","help":"Optional PDF model (provider/model) for the PDF analysis tool. Defaults to imageModel, then session model.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.repoRoot","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Repo Root","help":"Optional repository root shown in the system prompt runtime line (overrides auto-detect).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.backend","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.browser.allowHostControl","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.autoStart","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.autoStartTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.binds","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.browser.binds.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.cdpPort","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.cdpSourceRange","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Sandbox Browser CDP Source Port Range","help":"Optional CIDR allowlist for container-edge CDP ingress (for example 172.21.0.1/32).","hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.containerPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.enableNoVnc","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.headless","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.image","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.network","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Sandbox Browser Network","help":"Docker network for sandbox browser containers (default: openclaw-sandbox-browser). Avoid bridge if you need stricter isolation.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.noVncPort","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.browser.vncPort","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.docker.apparmorProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.binds","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.docker.binds.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.capDrop","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.docker.capDrop.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.containerPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.cpus","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced","security","storage"],"label":"Sandbox Docker Allow Container Namespace Join","help":"DANGEROUS break-glass override that allows sandbox Docker network mode container:. This joins another container namespace and weakens sandbox isolation.","hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.dangerouslyAllowExternalBindSources","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.dangerouslyAllowReservedContainerTargets","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.dns","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.docker.dns.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.env","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.docker.env.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.extraHosts","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.docker.extraHosts.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.image","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.memory","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.memorySwap","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.network","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.pidsLimit","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.readOnlyRoot","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.seccompProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.setupCommand","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.tmpfs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.docker.tmpfs.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.ulimits","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.docker.ulimits.*","kind":"core","type":["number","object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.docker.ulimits.*.hard","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.ulimits.*.soft","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.user","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.docker.workdir","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.perSession","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.prune","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.prune.idleHours","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.prune.maxAgeDays","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.scope","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.sessionToolsVisibility","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.certificateData","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["security","storage"],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.certificateData.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.certificateData.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.certificateData.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.certificateFile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.identityData","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["security","storage"],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.identityData.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.identityData.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.identityData.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.identityFile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.knownHostsData","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["security","storage"],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.knownHostsData.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.knownHostsData.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.knownHostsData.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.knownHostsFile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.strictHostKeyChecking","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.target","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.updateHostKeys","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.ssh.workspaceRoot","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.workspaceAccess","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.sandbox.workspaceRoot","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.skipBootstrap","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.subagents","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.defaults.subagents.announceTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.subagents.archiveAfterMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.subagents.maxChildrenPerAgent","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.subagents.maxConcurrent","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.subagents.maxSpawnDepth","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.subagents.model","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"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.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} +{"recordType":"path","path":"agents.defaults.timeFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.typingIntervalSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.typingMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.userTimezone","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.verboseDefault","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.workspace","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Workspace","help":"Default workspace path exposed to agent runtime tools for filesystem context and repo-aware behavior. Set this explicitly when running from wrappers so path resolution stays deterministic.","hasChildren":false} +{"recordType":"path","path":"agents.list","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent List","help":"Explicit list of configured agents with IDs and optional overrides for model, tools, identity, and workspace. Keep IDs stable over time so bindings, approvals, and session routing remain deterministic.","hasChildren":true} +{"recordType":"path","path":"agents.list.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.agentDir","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.default","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.groupChat","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.groupChat.historyLimit","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.groupChat.mentionPatterns","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.groupChat.mentionPatterns.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.heartbeat.accountId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.ackMaxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.activeHours","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.heartbeat.activeHours.end","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.activeHours.start","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.activeHours.timezone","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.directPolicy","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access","automation","storage"],"label":"Heartbeat Direct Policy","help":"Per-agent override for heartbeat direct/DM delivery policy; use \"block\" for agents that should only send heartbeat alerts to non-DM destinations.","hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.every","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.includeReasoning","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.isolatedSession","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.lightContext","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.session","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.suppressToolErrorWarnings","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Agent Heartbeat Suppress Tool Error Warnings","help":"Suppress tool error warning payloads during heartbeat runs.","hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.target","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"help":"Delivery target (\"last\", \"none\", or a channel id). Known channels: telegram, whatsapp, discord, irc, googlechat, slack, signal, imessage, line, bluebubbles, feishu, matrix, mattermost, msteams, nextcloud-talk, nostr, synology-chat, tlon, twitch, zalo, zalouser.","hasChildren":false} +{"recordType":"path","path":"agents.list.*.heartbeat.to","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.humanDelay","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.humanDelay.maxMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.humanDelay.minMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.humanDelay.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.identity","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.identity.avatar","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Identity Avatar","help":"Agent avatar (workspace-relative path, http(s) URL, or data URI).","hasChildren":false} +{"recordType":"path","path":"agents.list.*.identity.emoji","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.identity.name","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.identity.theme","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.cache","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.cache.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.cache.maxEntries","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.chunking","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.chunking.overlap","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.chunking.tokens","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.experimental","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.experimental.sessionMemory","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.extraPaths","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.extraPaths.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.fallback","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.local","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.local.modelCacheDir","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.local.modelPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.multimodal","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.multimodal.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.multimodal.maxFileBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.multimodal.modalities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.multimodal.modalities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.outputDimensionality","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.query","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid.candidateMultiplier","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid.mmr","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid.mmr.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid.mmr.lambda","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid.temporalDecay","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid.temporalDecay.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid.temporalDecay.halfLifeDays","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid.textWeight","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.query.hybrid.vectorWeight","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.query.maxResults","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.query.minScore","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.remote","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.remote.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"agents.list.*.memorySearch.remote.batch.pollIntervalMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.remote.batch.timeoutMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.remote.batch.wait","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.remote.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.remote.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.sources","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.sources.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.store","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.store.driver","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.store.path","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.store.vector","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.store.vector.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.store.vector.extensionPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.sync","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.sync.intervalMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.sync.onSearch","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.sync.onSessionStart","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.sync.sessions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.memorySearch.sync.sessions.deltaBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.sync.sessions.deltaMessages","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.sync.sessions.postCompactionForce","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.sync.watch","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.memorySearch.sync.watchDebounceMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.model","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.model.fallbacks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.model.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.model.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.name","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.params","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.params.*","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.runtime","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Runtime","help":"Optional runtime descriptor for this agent. Use embedded for default OpenClaw execution or acp for external ACP harness defaults.","hasChildren":true} +{"recordType":"path","path":"agents.list.*.runtime.acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Runtime","help":"ACP runtime defaults for this agent when runtime.type=acp. Binding-level ACP overrides still take precedence per conversation.","hasChildren":true} +{"recordType":"path","path":"agents.list.*.runtime.acp.agent","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Harness Agent","help":"Optional ACP harness agent id to use for this OpenClaw agent (for example codex, claude).","hasChildren":false} +{"recordType":"path","path":"agents.list.*.runtime.acp.backend","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Backend","help":"Optional ACP backend override for this agent's ACP sessions (falls back to global acp.backend).","hasChildren":false} +{"recordType":"path","path":"agents.list.*.runtime.acp.cwd","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Working Directory","help":"Optional default working directory for this agent's ACP sessions.","hasChildren":false} +{"recordType":"path","path":"agents.list.*.runtime.acp.mode","kind":"core","type":"string","required":false,"enumValues":["persistent","oneshot"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Mode","help":"Optional ACP session mode default for this agent (persistent or oneshot).","hasChildren":false} +{"recordType":"path","path":"agents.list.*.runtime.type","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Runtime Type","help":"Runtime type for this agent: \"embedded\" (default OpenClaw runtime) or \"acp\" (ACP harness defaults).","hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.backend","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.browser.allowHostControl","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.autoStart","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.autoStartTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.binds","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.browser.binds.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.cdpPort","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.cdpSourceRange","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Agent Sandbox Browser CDP Source Port Range","help":"Per-agent override for CDP source CIDR allowlist.","hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.containerPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.enableNoVnc","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.headless","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.image","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.network","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Agent Sandbox Browser Network","help":"Per-agent override for sandbox browser Docker network.","hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.noVncPort","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.browser.vncPort","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.docker.apparmorProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.binds","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.docker.binds.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.capDrop","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.docker.capDrop.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.containerPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.cpus","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.dangerouslyAllowContainerNamespaceJoin","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced","security","storage"],"label":"Agent Sandbox Docker Allow Container Namespace Join","help":"Per-agent DANGEROUS override for container namespace joins in sandbox Docker network mode.","hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.dangerouslyAllowExternalBindSources","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.dangerouslyAllowReservedContainerTargets","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.dns","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.docker.dns.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.env","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.docker.env.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.extraHosts","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.docker.extraHosts.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.image","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.memory","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.memorySwap","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.network","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.pidsLimit","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.readOnlyRoot","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.seccompProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.setupCommand","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.tmpfs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.docker.tmpfs.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.ulimits","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.docker.ulimits.*","kind":"core","type":["number","object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.docker.ulimits.*.hard","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.ulimits.*.soft","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.user","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.docker.workdir","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.perSession","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.prune","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.prune.idleHours","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.prune.maxAgeDays","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.scope","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.sessionToolsVisibility","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.certificateData","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["security","storage"],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.certificateData.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.certificateData.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.certificateData.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.certificateFile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.identityData","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["security","storage"],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.identityData.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.identityData.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.identityData.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.identityFile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.knownHostsData","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["security","storage"],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.knownHostsData.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.knownHostsData.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.knownHostsData.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.knownHostsFile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.strictHostKeyChecking","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.target","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.updateHostKeys","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.ssh.workspaceRoot","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.workspaceAccess","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.sandbox.workspaceRoot","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.skills","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Skill Filter","help":"Optional allowlist of skills for this agent (omit = all skills; empty = no skills).","hasChildren":true} +{"recordType":"path","path":"agents.list.*.skills.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.subagents","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.subagents.allowAgents","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.subagents.allowAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.subagents.model","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"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.thinking","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.alsoAllow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Agent Tool Allowlist Additions","help":"Per-agent additive allowlist for tools on top of global and profile policy. Keep narrow to avoid accidental privilege expansion on specialized agents.","hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.alsoAllow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.byProvider","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Tool Policy by Provider","help":"Per-agent provider-specific tool policy overrides for channel-scoped capability control. Use this when a single agent needs tighter restrictions on one provider than others.","hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.byProvider.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.byProvider.*.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.byProvider.*.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.byProvider.*.alsoAllow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.byProvider.*.alsoAllow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.byProvider.*.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.byProvider.*.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.byProvider.*.profile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.elevated","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.elevated.allowFrom","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.elevated.allowFrom.*","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.elevated.allowFrom.*.*","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.elevated.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.exec.applyPatch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.exec.applyPatch.allowModels","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.exec.applyPatch.allowModels.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.applyPatch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.applyPatch.workspaceOnly","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.approvalRunningNoticeMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"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":["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} +{"recordType":"path","path":"agents.list.*.tools.exec.pathPrepend","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.exec.pathPrepend.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBinProfiles","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBinProfiles.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBinProfiles.*.allowedValueFlags","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBinProfiles.*.allowedValueFlags.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBinProfiles.*.deniedFlags","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBinProfiles.*.deniedFlags.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBinProfiles.*.maxPositional","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBinProfiles.*.minPositional","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBins","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBins.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBinTrustedDirs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.exec.safeBinTrustedDirs.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.security","kind":"core","type":"string","required":false,"enumValues":["deny","allowlist","full"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.exec.timeoutSec","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.fs","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.fs.workspaceOnly","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.loopDetection","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.loopDetection.criticalThreshold","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.loopDetection.detectors","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.loopDetection.detectors.genericRepeat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.loopDetection.detectors.knownPollNoProgress","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.loopDetection.detectors.pingPong","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.loopDetection.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.loopDetection.globalCircuitBreakerThreshold","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.loopDetection.historySize","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.loopDetection.warningThreshold","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.profile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Agent Tool Profile","help":"Per-agent override for tool profile selection when one agent needs a different capability baseline. Use this sparingly so policy differences across agents stay intentional and reviewable.","hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.sandbox","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.sandbox.tools","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.sandbox.tools.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.sandbox.tools.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.sandbox.tools.alsoAllow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.sandbox.tools.alsoAllow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.tools.sandbox.tools.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"agents.list.*.tools.sandbox.tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.list.*.workspace","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"approvals","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approvals","help":"Approval routing controls for forwarding exec approval requests to chat destinations outside the originating session. Keep this disabled unless operators need explicit out-of-band approval visibility.","hasChildren":true} +{"recordType":"path","path":"approvals.exec","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Exec Approval Forwarding","help":"Groups exec-approval forwarding behavior including enablement, routing mode, filters, and explicit targets. Configure here when approval prompts must reach operational channels instead of only the origin thread.","hasChildren":true} +{"recordType":"path","path":"approvals.exec.agentFilter","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for forwarded approvals, for example `[\"primary\", \"ops-agent\"]`. Use this to limit forwarding blast radius and avoid notifying channels for unrelated agents.","hasChildren":true} +{"recordType":"path","path":"approvals.exec.agentFilter.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"approvals.exec.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Forward Exec Approvals","help":"Enables forwarding of exec approval requests to configured delivery destinations (default: false). Keep disabled in low-risk setups and enable only when human approval responders need channel-visible prompts.","hasChildren":false} +{"recordType":"path","path":"approvals.exec.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Forwarding Mode","help":"Controls where approval prompts are sent: \"session\" uses origin chat, \"targets\" uses configured targets, and \"both\" sends to both paths. Use \"session\" as baseline and expand only when operational workflow requires redundancy.","hasChildren":false} +{"recordType":"path","path":"approvals.exec.sessionFilter","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns, for example `[\"discord:\", \"^agent:ops:\"]`. Use narrow patterns so only intended approval contexts are forwarded to shared destinations.","hasChildren":true} +{"recordType":"path","path":"approvals.exec.sessionFilter.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"approvals.exec.targets","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Forwarding Targets","help":"Explicit delivery targets used when forwarding mode includes targets, each with channel and destination details. Keep target lists least-privilege and validate each destination before enabling broad forwarding.","hasChildren":true} +{"recordType":"path","path":"approvals.exec.targets.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"approvals.exec.targets.*.accountId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Target Account ID","help":"Optional account selector for multi-account channel setups when approvals must route through a specific account context. Use this only when the target channel has multiple configured identities.","hasChildren":false} +{"recordType":"path","path":"approvals.exec.targets.*.channel","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Target Channel","help":"Channel/provider ID used for forwarded approval delivery, such as discord, slack, or a plugin channel id. Use valid channel IDs only so approvals do not silently fail due to unknown routes.","hasChildren":false} +{"recordType":"path","path":"approvals.exec.targets.*.threadId","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Target Thread ID","help":"Optional thread/topic target for channels that support threaded delivery of forwarded approvals. Use this to keep approval traffic contained in operational threads instead of main channels.","hasChildren":false} +{"recordType":"path","path":"approvals.exec.targets.*.to","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Target Destination","help":"Destination identifier inside the target channel (channel ID, user ID, or thread root depending on provider). Verify semantics per provider because destination format differs across channel integrations.","hasChildren":false} +{"recordType":"path","path":"audio","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Audio","help":"Global audio ingestion settings used before higher-level tools process speech or media content. Configure this when you need deterministic transcription behavior for voice notes and clips.","hasChildren":true} +{"recordType":"path","path":"audio.transcription","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Audio Transcription","help":"Command-based transcription settings for converting audio files into text before agent handling. Keep a simple, deterministic command path here so failures are easy to diagnose in logs.","hasChildren":true} +{"recordType":"path","path":"audio.transcription.command","kind":"core","type":"array","required":true,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Audio Transcription Command","help":"Executable + args used to transcribe audio (first token must be a safe binary/path), for example `[\"whisper-cli\", \"--model\", \"small\", \"{input}\"]`. Prefer a pinned command so runtime environments behave consistently.","hasChildren":true} +{"recordType":"path","path":"audio.transcription.command.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"audio.transcription.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance"],"label":"Audio Transcription Timeout (sec)","help":"Maximum time allowed for the transcription command to finish before it is aborted. Increase this for longer recordings, and keep it tight in latency-sensitive deployments.","hasChildren":false} +{"recordType":"path","path":"auth","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Auth","help":"Authentication profile root used for multi-profile provider credentials and cooldown-based failover ordering. Keep profiles minimal and explicit so automatic failover behavior stays auditable.","hasChildren":true} +{"recordType":"path","path":"auth.cooldowns","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth"],"label":"Auth Cooldowns","help":"Cooldown/backoff controls for temporary profile suppression after billing-related failures and retry windows. Use these to prevent rapid re-selection of profiles that are still blocked.","hasChildren":true} +{"recordType":"path","path":"auth.cooldowns.billingBackoffHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth","reliability"],"label":"Billing Backoff (hours)","help":"Base backoff (hours) when a profile fails due to billing/insufficient credits (default: 5).","hasChildren":false} +{"recordType":"path","path":"auth.cooldowns.billingBackoffHoursByProvider","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth","reliability"],"label":"Billing Backoff Overrides","help":"Optional per-provider overrides for billing backoff (hours).","hasChildren":true} +{"recordType":"path","path":"auth.cooldowns.billingBackoffHoursByProvider.*","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"auth.cooldowns.billingMaxHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth","performance"],"label":"Billing Backoff Cap (hours)","help":"Cap (hours) for billing backoff (default: 24).","hasChildren":false} +{"recordType":"path","path":"auth.cooldowns.failureWindowHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth"],"label":"Failover Window (hours)","help":"Failure window (hours) for backoff counters (default: 24).","hasChildren":false} +{"recordType":"path","path":"auth.order","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth"],"label":"Auth Profile Order","help":"Ordered auth profile IDs per provider (used for automatic failover).","hasChildren":true} +{"recordType":"path","path":"auth.order.*","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"auth.order.*.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"auth.profiles","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access","auth","storage"],"label":"Auth Profiles","help":"Named auth profiles (provider + mode + optional email).","hasChildren":true} +{"recordType":"path","path":"auth.profiles.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"auth.profiles.*.email","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"auth.profiles.*.mode","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"auth.profiles.*.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"bindings","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Bindings","help":"Top-level binding rules for routing and persistent ACP conversation ownership. Use type=route for normal routing and type=acp for persistent ACP harness bindings.","hasChildren":true} +{"recordType":"path","path":"bindings.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"bindings.*.acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Binding Overrides","help":"Optional per-binding ACP overrides for bindings[].type=acp. This layer overrides agents.list[].runtime.acp defaults for the matched conversation.","hasChildren":true} +{"recordType":"path","path":"bindings.*.acp.backend","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Binding Backend","help":"ACP backend override for this binding (falls back to agent runtime ACP backend, then global acp.backend).","hasChildren":false} +{"recordType":"path","path":"bindings.*.acp.cwd","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Binding Working Directory","help":"Working directory override for ACP sessions created from this binding.","hasChildren":false} +{"recordType":"path","path":"bindings.*.acp.label","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Binding Label","help":"Human-friendly label for ACP status/diagnostics in this bound conversation.","hasChildren":false} +{"recordType":"path","path":"bindings.*.acp.mode","kind":"core","type":"string","required":false,"enumValues":["persistent","oneshot"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP Binding Mode","help":"ACP session mode override for this binding (persistent or oneshot).","hasChildren":false} +{"recordType":"path","path":"bindings.*.agentId","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Agent ID","help":"Target agent ID that receives traffic when the corresponding binding match rule is satisfied. Use valid configured agent IDs only so routing does not fail at runtime.","hasChildren":false} +{"recordType":"path","path":"bindings.*.comment","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"bindings.*.match","kind":"core","type":"object","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Match Rule","help":"Match rule object for deciding when a binding applies, including channel and optional account/peer constraints. Keep rules narrow to avoid accidental agent takeover across contexts.","hasChildren":true} +{"recordType":"path","path":"bindings.*.match.accountId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Account ID","help":"Optional account selector for multi-account channel setups so the binding applies only to one identity. Use this when account scoping is required for the route and leave unset otherwise.","hasChildren":false} +{"recordType":"path","path":"bindings.*.match.channel","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Channel","help":"Channel/provider identifier this binding applies to, such as `telegram`, `discord`, or a plugin channel ID. Use the configured channel key exactly so binding evaluation works reliably.","hasChildren":false} +{"recordType":"path","path":"bindings.*.match.guildId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Guild ID","help":"Optional Discord-style guild/server ID constraint for binding evaluation in multi-server deployments. Use this when the same peer identifiers can appear across different guilds.","hasChildren":false} +{"recordType":"path","path":"bindings.*.match.peer","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Peer Match","help":"Optional peer matcher for specific conversations including peer kind and peer id. Use this when only one direct/group/channel target should be pinned to an agent.","hasChildren":true} +{"recordType":"path","path":"bindings.*.match.peer.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Peer ID","help":"Conversation identifier used with peer matching, such as a chat ID, channel ID, or group ID from the provider. Keep this exact to avoid silent non-matches.","hasChildren":false} +{"recordType":"path","path":"bindings.*.match.peer.kind","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Peer Kind","help":"Peer conversation type: \"direct\", \"group\", \"channel\", or legacy \"dm\" (deprecated alias for direct). Prefer \"direct\" for new configs and keep kind aligned with channel semantics.","hasChildren":false} +{"recordType":"path","path":"bindings.*.match.roles","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Roles","help":"Optional role-based filter list used by providers that attach roles to chat context. Use this to route privileged or operational role traffic to specialized agents.","hasChildren":true} +{"recordType":"path","path":"bindings.*.match.roles.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"bindings.*.match.teamId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Team ID","help":"Optional team/workspace ID constraint used by providers that scope chats under teams. Add this when you need bindings isolated to one workspace context.","hasChildren":false} +{"recordType":"path","path":"bindings.*.type","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Binding Type","help":"Binding kind. Use \"route\" (or omit for legacy route entries) for normal routing, and \"acp\" for persistent ACP conversation bindings.","hasChildren":false} +{"recordType":"path","path":"broadcast","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Broadcast","help":"Broadcast routing map for sending the same outbound message to multiple peer IDs per source conversation. Keep this minimal and audited because one source can fan out to many destinations.","hasChildren":true} +{"recordType":"path","path":"broadcast.*","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Broadcast Destination List","help":"Per-source broadcast destination list where each key is a source peer ID and the value is an array of destination peer IDs. Keep lists intentional to avoid accidental message amplification.","hasChildren":true} +{"recordType":"path","path":"broadcast.*.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"broadcast.strategy","kind":"core","type":"string","required":false,"enumValues":["parallel","sequential"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Broadcast Strategy","help":"Delivery order for broadcast fan-out: \"parallel\" sends to all targets concurrently, while \"sequential\" sends one-by-one. Use \"parallel\" for speed and \"sequential\" for stricter ordering/backpressure control.","hasChildren":false} +{"recordType":"path","path":"browser","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Browser","help":"Browser runtime controls for local or remote CDP attachment, profile routing, and screenshot/snapshot behavior. Keep defaults unless your automation workflow requires custom browser transport settings.","hasChildren":true} +{"recordType":"path","path":"browser.attachOnly","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Browser Attach-only Mode","help":"Restricts browser mode to attach-only behavior without starting local browser processes. Use this when all browser sessions are externally managed by a remote CDP provider.","hasChildren":false} +{"recordType":"path","path":"browser.cdpPortRangeStart","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Browser CDP Port Range Start","help":"Starting local CDP port used for auto-allocated browser profile ports. Increase this when host-level port defaults conflict with other local services.","hasChildren":false} +{"recordType":"path","path":"browser.cdpUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Browser CDP URL","help":"Remote CDP websocket URL used to attach to an externally managed browser instance. Use this for centralized browser hosts and keep URL access restricted to trusted network paths.","hasChildren":false} +{"recordType":"path","path":"browser.color","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Browser Accent Color","help":"Default accent color used for browser profile/UI cues where colored identity hints are displayed. Use consistent colors to help operators identify active browser profile context quickly.","hasChildren":false} +{"recordType":"path","path":"browser.defaultProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Browser Default Profile","help":"Default browser profile name selected when callers do not explicitly choose a profile. Use a stable low-privilege profile as the default to reduce accidental cross-context state use.","hasChildren":false} +{"recordType":"path","path":"browser.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Browser Enabled","help":"Enables browser capability wiring in the gateway so browser tools and CDP-driven workflows can run. Disable when browser automation is not needed to reduce surface area and startup work.","hasChildren":false} +{"recordType":"path","path":"browser.evaluateEnabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Browser Evaluate Enabled","help":"Enables browser-side evaluate helpers for runtime script evaluation capabilities where supported. Keep disabled unless your workflows require evaluate semantics beyond snapshots/navigation.","hasChildren":false} +{"recordType":"path","path":"browser.executablePath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Browser Executable Path","help":"Explicit browser executable path when auto-discovery is insufficient for your host environment. Use absolute stable paths so launch behavior stays deterministic across restarts.","hasChildren":false} +{"recordType":"path","path":"browser.extraArgs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"browser.extraArgs.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"browser.headless","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Browser Headless Mode","help":"Forces browser launch in headless mode when the local launcher starts browser instances. Keep headless enabled for server environments and disable only when visible UI debugging is required.","hasChildren":false} +{"recordType":"path","path":"browser.noSandbox","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Browser No-Sandbox Mode","help":"Disables Chromium sandbox isolation flags for environments where sandboxing fails at runtime. Keep this off whenever possible because process isolation protections are reduced.","hasChildren":false} +{"recordType":"path","path":"browser.profiles","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Browser Profiles","help":"Named browser profile connection map used for explicit routing to CDP ports or URLs with optional metadata. Keep profile names consistent and avoid overlapping endpoint definitions.","hasChildren":true} +{"recordType":"path","path":"browser.profiles.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"browser.profiles.*.attachOnly","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Browser Profile Attach-only Mode","help":"Per-profile attach-only override that skips local browser launch and only attaches to an existing CDP endpoint. Useful when one profile is externally managed but others are locally launched.","hasChildren":false} +{"recordType":"path","path":"browser.profiles.*.cdpPort","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Browser Profile CDP Port","help":"Per-profile local CDP port used when connecting to browser instances by port instead of URL. Use unique ports per profile to avoid connection collisions.","hasChildren":false} +{"recordType":"path","path":"browser.profiles.*.cdpUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Browser Profile CDP URL","help":"Per-profile CDP websocket URL used for explicit remote browser routing by profile name. Use this when profile connections terminate on remote hosts or tunnels.","hasChildren":false} +{"recordType":"path","path":"browser.profiles.*.color","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Browser Profile Accent Color","help":"Per-profile accent color for visual differentiation in dashboards and browser-related UI hints. Use distinct colors for high-signal operator recognition of active profiles.","hasChildren":false} +{"recordType":"path","path":"browser.profiles.*.driver","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Browser Profile Driver","help":"Per-profile browser driver mode. Use \"openclaw\" (or legacy \"clawd\") for CDP-based profiles, or use \"existing-session\" for host-local Chrome DevTools MCP attachment.","hasChildren":false} +{"recordType":"path","path":"browser.profiles.*.userDataDir","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Browser Profile User Data Dir","help":"Per-profile Chromium user data directory for existing-session attachment through Chrome DevTools MCP. Use this for host-local Brave, Edge, Chromium, or non-default Chrome profiles when the built-in auto-connect path would pick the wrong browser data directory.","hasChildren":false} +{"recordType":"path","path":"browser.remoteCdpHandshakeTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Remote CDP Handshake Timeout (ms)","help":"Timeout in milliseconds for post-connect CDP handshake readiness checks against remote browser targets. Raise this for slow-start remote browsers and lower to fail fast in automation loops.","hasChildren":false} +{"recordType":"path","path":"browser.remoteCdpTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Remote CDP Timeout (ms)","help":"Timeout in milliseconds for connecting to a remote CDP endpoint before failing the browser attach attempt. Increase for high-latency tunnels, or lower for faster failure detection.","hasChildren":false} +{"recordType":"path","path":"browser.snapshotDefaults","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Browser Snapshot Defaults","help":"Default snapshot capture configuration used when callers do not provide explicit snapshot options. Tune this for consistent capture behavior across channels and automation paths.","hasChildren":true} +{"recordType":"path","path":"browser.snapshotDefaults.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Browser Snapshot Mode","help":"Default snapshot extraction mode controlling how page content is transformed for agent consumption. Choose the mode that balances readability, fidelity, and token footprint for your workflows.","hasChildren":false} +{"recordType":"path","path":"browser.ssrfPolicy","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Browser SSRF Policy","help":"Server-side request forgery guardrail settings for browser/network fetch paths that could reach internal hosts. Keep restrictive defaults in production and open only explicitly approved targets.","hasChildren":true} +{"recordType":"path","path":"browser.ssrfPolicy.allowedHostnames","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Browser Allowed Hostnames","help":"Explicit hostname allowlist exceptions for SSRF policy checks on browser/network requests. Keep this list minimal and review entries regularly to avoid stale broad access.","hasChildren":true} +{"recordType":"path","path":"browser.ssrfPolicy.allowedHostnames.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"browser.ssrfPolicy.allowPrivateNetwork","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Browser Allow Private Network","help":"Legacy alias for browser.ssrfPolicy.dangerouslyAllowPrivateNetwork. Prefer the dangerously-named key so risk intent is explicit.","hasChildren":false} +{"recordType":"path","path":"browser.ssrfPolicy.dangerouslyAllowPrivateNetwork","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced","security"],"label":"Browser Dangerously Allow Private Network","help":"Allows access to private-network address ranges from browser tooling. Default is enabled for trusted-network operator setups; disable to enforce strict public-only resolution checks.","hasChildren":false} +{"recordType":"path","path":"browser.ssrfPolicy.hostnameAllowlist","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Browser Hostname Allowlist","help":"Legacy/alternate hostname allowlist field used by SSRF policy consumers for explicit host exceptions. Use stable exact hostnames and avoid wildcard-like broad patterns.","hasChildren":true} +{"recordType":"path","path":"browser.ssrfPolicy.hostnameAllowlist.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"canvasHost","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Canvas Host","help":"Canvas host settings for serving canvas assets and local live-reload behavior used by canvas-enabled workflows. Keep disabled unless canvas-hosted assets are actively used.","hasChildren":true} +{"recordType":"path","path":"canvasHost.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Canvas Host Enabled","help":"Enables the canvas host server process and routes for serving canvas files. Keep disabled when canvas workflows are inactive to reduce exposed local services.","hasChildren":false} +{"recordType":"path","path":"canvasHost.liveReload","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["reliability"],"label":"Canvas Host Live Reload","help":"Enables automatic live-reload behavior for canvas assets during development workflows. Keep disabled in production-like environments where deterministic output is preferred.","hasChildren":false} +{"recordType":"path","path":"canvasHost.port","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Canvas Host Port","help":"TCP port used by the canvas host HTTP server when canvas hosting is enabled. Choose a non-conflicting port and align firewall/proxy policy accordingly.","hasChildren":false} +{"recordType":"path","path":"canvasHost.root","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Canvas Host Root Directory","help":"Filesystem root directory served by canvas host for canvas content and static assets. Use a dedicated directory and avoid broad repo roots for least-privilege file exposure.","hasChildren":false} +{"recordType":"path","path":"channels","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Channels","help":"Channel provider configurations plus shared defaults that control access policies, heartbeat visibility, and per-surface behavior. Keep defaults centralized and override per provider only where required.","hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"BlueBubbles","help":"iMessage via the BlueBubbles mac app + REST API.","hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.mediaLocalRoots","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.mediaLocalRoots.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.mediaMaxMb","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.password","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.password.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.password.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.password.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.sendReadReceipts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.serverUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.accounts.*.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.actions.addParticipant","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions.edit","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions.leaveGroup","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions.reactions","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions.removeParticipant","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions.renameGroup","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions.reply","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions.sendAttachment","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions.sendWithEffect","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions.setGroupIcon","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.actions.unsend","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"BlueBubbles DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.bluebubbles.allowFrom=[\"*\"].","hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.mediaLocalRoots","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.mediaLocalRoots.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.mediaMaxMb","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.password","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.bluebubbles.password.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.password.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.password.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.sendReadReceipts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.serverUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.bluebubbles.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord","help":"very well supported right now.","hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.ackReactionScope","kind":"channel","type":"string","required":false,"enumValues":["group-mentions","group-all","direct","all","off","none"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.actions.channelInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.channels","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.emojiUploads","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.events","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.memberInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.messages","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.moderation","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.permissions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.pins","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.polls","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.presence","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.roleInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.roles","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.search","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.stickers","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.stickerUploads","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.threads","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.actions.voiceStatus","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.activity","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.activityType","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.activityUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.agentComponents","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.agentComponents.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.allowBots","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.autoPresence","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.autoPresence.degradedText","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.autoPresence.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.autoPresence.exhaustedText","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.autoPresence.healthyText","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.autoPresence.intervalMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.autoPresence.minUpdateIntervalMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.commands","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.commands.native","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.commands.nativeSkills","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dm","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.dm.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.dm.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dm.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dm.groupChannels","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.dm.groupChannels.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dm.groupEnabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dm.policy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.draftChunk","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.draftChunk.breakPreference","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.draftChunk.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.draftChunk.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.eventQueue","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.eventQueue.listenerTimeout","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.eventQueue.maxConcurrency","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.eventQueue.maxQueueSize","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.agentFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.agentFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.approvers","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.approvers.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.cleanupAfterResolve","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.sessionFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.sessionFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.target","kind":"channel","type":"string","required":false,"enumValues":["dm","channel","both"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.allow","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.autoArchiveDuration","kind":"channel","type":["number","string"],"required":false,"enumValues":["60","1440","4320","10080"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.autoThread","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.ignoreOtherMentions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.includeThreadStarter","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.roles","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.roles.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.ignoreOtherMentions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.roles","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.roles.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.slug","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.inboundWorker","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.inboundWorker.runTimeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.intents","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.intents.guildMembers","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.intents.presence","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.maxLinesPerMessage","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.pluralkit","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.pluralkit.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.pluralkit.token","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.pluralkit.token.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.pluralkit.token.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.pluralkit.token.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.proxy","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.replyToMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.retry","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.retry.attempts","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.retry.jitter","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.retry.maxDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.retry.minDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.slashCommand","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.slashCommand.ephemeral","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.status","kind":"channel","type":"string","required":false,"enumValues":["online","dnd","idle","invisible"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.streaming","kind":"channel","type":["boolean","string"],"required":false,"enumValues":["off","partial","block","progress"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.streamMode","kind":"channel","type":"string","required":false,"enumValues":["partial","block","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.threadBindings","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.threadBindings.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.threadBindings.idleHours","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.threadBindings.maxAgeHours","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.threadBindings.spawnAcpSessions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.threadBindings.spawnSubagentSessions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.token","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.token.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.token.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.token.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.ui","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.ui.components","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.ui.components.accentColor","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.autoJoin","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.autoJoin.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.autoJoin.*.channelId","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.autoJoin.*.guildId","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.daveEncryption","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.decryptionFailureTolerance","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.auto","kind":"channel","type":"string","required":false,"enumValues":["off","always","inbound","tagged"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge.lang","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge.outputFormat","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge.pitch","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge.proxy","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge.rate","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge.saveSubtitles","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge.voice","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.edge.volume","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.apiKey","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","media","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.apiKey.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.apiKey.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.apiKey.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.applyTextNormalization","kind":"channel","type":"string","required":false,"enumValues":["auto","on","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.baseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.languageCode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.modelId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.seed","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.voiceId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings.similarityBoost","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings.speed","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings.stability","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings.style","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.elevenlabs.voiceSettings.useSpeakerBoost","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.maxTextLength","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft.lang","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft.outputFormat","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft.pitch","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft.proxy","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft.rate","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft.saveSubtitles","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft.voice","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.microsoft.volume","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.mode","kind":"channel","type":"string","required":false,"enumValues":["final","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.modelOverrides","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.modelOverrides.allowModelId","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.modelOverrides.allowNormalization","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.modelOverrides.allowProvider","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.modelOverrides.allowSeed","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.modelOverrides.allowText","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.modelOverrides.allowVoice","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.modelOverrides.allowVoiceSettings","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.modelOverrides.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.openai","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.openai.apiKey","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","media","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.openai.apiKey.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.openai.apiKey.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.openai.apiKey.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.openai.baseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.openai.instructions","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.openai.model","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.openai.speed","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.openai.voice","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.prefsPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.provider","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.summaryModel","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.voice.tts.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.ackReactionScope","kind":"channel","type":"string","required":false,"enumValues":["group-mentions","group-all","direct","all","off","none"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.actions.channelInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.channels","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.emojiUploads","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.events","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.memberInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.messages","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.moderation","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.permissions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.pins","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.polls","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.presence","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.roleInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.roles","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.search","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.stickers","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.stickerUploads","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.threads","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.actions.voiceStatus","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.activity","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Presence Activity","help":"Discord presence activity text (defaults to custom status).","hasChildren":false} +{"recordType":"path","path":"channels.discord.activityType","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Presence Activity Type","help":"Discord presence activity type (0=Playing,1=Streaming,2=Listening,3=Watching,4=Custom,5=Competing).","hasChildren":false} +{"recordType":"path","path":"channels.discord.activityUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Presence Activity URL","help":"Discord presence streaming URL (required for activityType=1).","hasChildren":false} +{"recordType":"path","path":"channels.discord.agentComponents","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.agentComponents.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.allowBots","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Discord Allow Bot Messages","help":"Allow bot-authored messages to trigger Discord replies (default: false). Set \"mentions\" to only accept bot messages that mention the bot.","hasChildren":false} +{"recordType":"path","path":"channels.discord.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.autoPresence","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.autoPresence.degradedText","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Auto Presence Degraded Text","help":"Optional custom status text while runtime/model availability is degraded or unknown (idle).","hasChildren":false} +{"recordType":"path","path":"channels.discord.autoPresence.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Auto Presence Enabled","help":"Enable automatic Discord bot presence updates based on runtime/model availability signals. When enabled: healthy=>online, degraded/unknown=>idle, exhausted/unavailable=>dnd.","hasChildren":false} +{"recordType":"path","path":"channels.discord.autoPresence.exhaustedText","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Auto Presence Exhausted Text","help":"Optional custom status text while runtime detects exhausted/unavailable model quota (dnd). Supports {reason} template placeholder.","hasChildren":false} +{"recordType":"path","path":"channels.discord.autoPresence.healthyText","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","reliability"],"label":"Discord Auto Presence Healthy Text","help":"Optional custom status text while runtime is healthy (online). If omitted, falls back to static channels.discord.activity when set.","hasChildren":false} +{"recordType":"path","path":"channels.discord.autoPresence.intervalMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"Discord Auto Presence Check Interval (ms)","help":"How often to evaluate Discord auto-presence state in milliseconds (default: 30000).","hasChildren":false} +{"recordType":"path","path":"channels.discord.autoPresence.minUpdateIntervalMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"Discord Auto Presence Min Update Interval (ms)","help":"Minimum time between actual Discord presence update calls in milliseconds (default: 15000). Prevents status spam on noisy state changes.","hasChildren":false} +{"recordType":"path","path":"channels.discord.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.commands","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.commands.native","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Native Commands","help":"Override native commands for Discord (bool or \"auto\").","hasChildren":false} +{"recordType":"path","path":"channels.discord.commands.nativeSkills","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Native Skill Commands","help":"Override native skill commands for Discord (bool or \"auto\").","hasChildren":false} +{"recordType":"path","path":"channels.discord.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Config Writes","help":"Allow Discord to write config in response to channel events/commands (default: true).","hasChildren":false} +{"recordType":"path","path":"channels.discord.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.dm","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.dm.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.dm.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.dm.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.dm.groupChannels","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.dm.groupChannels.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.dm.groupEnabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.dm.policy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Discord DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.discord.allowFrom=[\"*\"] (legacy: channels.discord.dm.allowFrom).","hasChildren":false} +{"recordType":"path","path":"channels.discord.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Discord DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.discord.allowFrom=[\"*\"].","hasChildren":false} +{"recordType":"path","path":"channels.discord.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.draftChunk","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.draftChunk.breakPreference","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Draft Chunk Break Preference","help":"Preferred breakpoints for Discord draft chunks (paragraph | newline | sentence). Default: paragraph.","hasChildren":false} +{"recordType":"path","path":"channels.discord.draftChunk.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"Discord Draft Chunk Max Chars","help":"Target max size for a Discord stream preview chunk when channels.discord.streaming=\"block\" (default: 800; clamped to channels.discord.textChunkLimit).","hasChildren":false} +{"recordType":"path","path":"channels.discord.draftChunk.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Draft Chunk Min Chars","help":"Minimum chars before emitting a Discord stream preview update when channels.discord.streaming=\"block\" (default: 200).","hasChildren":false} +{"recordType":"path","path":"channels.discord.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.eventQueue","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.eventQueue.listenerTimeout","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"Discord EventQueue Listener Timeout (ms)","help":"Canonical Discord listener timeout control in ms for gateway normalization/enqueue handlers. Default is 120000 in OpenClaw; set per account via channels.discord.accounts..eventQueue.listenerTimeout.","hasChildren":false} +{"recordType":"path","path":"channels.discord.eventQueue.maxConcurrency","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"Discord EventQueue Max Concurrency","help":"Optional Discord EventQueue concurrency override (max concurrent handler executions). Set per account via channels.discord.accounts..eventQueue.maxConcurrency.","hasChildren":false} +{"recordType":"path","path":"channels.discord.eventQueue.maxQueueSize","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"Discord EventQueue Max Queue Size","help":"Optional Discord EventQueue capacity override (max queued events before backpressure). Set per account via channels.discord.accounts..eventQueue.maxQueueSize.","hasChildren":false} +{"recordType":"path","path":"channels.discord.execApprovals","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.execApprovals.agentFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.execApprovals.agentFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.execApprovals.approvers","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.execApprovals.approvers.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.execApprovals.cleanupAfterResolve","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.execApprovals.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.execApprovals.sessionFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.execApprovals.sessionFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.execApprovals.target","kind":"channel","type":"string","required":false,"enumValues":["dm","channel","both"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.allow","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.autoArchiveDuration","kind":"channel","type":["number","string"],"required":false,"enumValues":["60","1440","4320","10080"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.autoThread","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.ignoreOtherMentions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.includeThreadStarter","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.roles","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.roles.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.ignoreOtherMentions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.roles","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.roles.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.slug","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.guilds.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.inboundWorker","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.inboundWorker.runTimeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"Discord Inbound Worker Timeout (ms)","help":"Optional queued Discord inbound worker timeout in ms. This is separate from Carbon listener timeouts; defaults to 1800000 and can be disabled with 0. Set per account via channels.discord.accounts..inboundWorker.runTimeoutMs.","hasChildren":false} +{"recordType":"path","path":"channels.discord.intents","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.intents.guildMembers","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Guild Members Intent","help":"Enable the Guild Members privileged intent. Must also be enabled in the Discord Developer Portal. Default: false.","hasChildren":false} +{"recordType":"path","path":"channels.discord.intents.presence","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Presence Intent","help":"Enable the Guild Presences privileged intent. Must also be enabled in the Discord Developer Portal. Allows tracking user activities (e.g. Spotify). Default: false.","hasChildren":false} +{"recordType":"path","path":"channels.discord.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.maxLinesPerMessage","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"Discord Max Lines Per Message","help":"Soft max line count per Discord message (default: 17).","hasChildren":false} +{"recordType":"path","path":"channels.discord.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.pluralkit","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.pluralkit.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord PluralKit Enabled","help":"Resolve PluralKit proxied messages and treat system members as distinct senders.","hasChildren":false} +{"recordType":"path","path":"channels.discord.pluralkit.token","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"label":"Discord PluralKit Token","help":"Optional PluralKit token for resolving private systems or members.","hasChildren":true} +{"recordType":"path","path":"channels.discord.pluralkit.token.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.pluralkit.token.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.pluralkit.token.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.proxy","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Proxy URL","help":"Proxy URL for Discord gateway + API requests (app-id lookup and allowlist resolution). Set per account via channels.discord.accounts..proxy.","hasChildren":false} +{"recordType":"path","path":"channels.discord.replyToMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.retry","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.retry.attempts","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","reliability"],"label":"Discord Retry Attempts","help":"Max retry attempts for outbound Discord API calls (default: 3).","hasChildren":false} +{"recordType":"path","path":"channels.discord.retry.jitter","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","reliability"],"label":"Discord Retry Jitter","help":"Jitter factor (0-1) applied to Discord retry delays.","hasChildren":false} +{"recordType":"path","path":"channels.discord.retry.maxDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance","reliability"],"label":"Discord Retry Max Delay (ms)","help":"Maximum retry delay cap in ms for Discord outbound calls.","hasChildren":false} +{"recordType":"path","path":"channels.discord.retry.minDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","reliability"],"label":"Discord Retry Min Delay (ms)","help":"Minimum retry delay in ms for Discord outbound calls.","hasChildren":false} +{"recordType":"path","path":"channels.discord.slashCommand","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.slashCommand.ephemeral","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.status","kind":"channel","type":"string","required":false,"enumValues":["online","dnd","idle","invisible"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Presence Status","help":"Discord presence status (online, dnd, idle, invisible).","hasChildren":false} +{"recordType":"path","path":"channels.discord.streaming","kind":"channel","type":["boolean","string"],"required":false,"enumValues":["off","partial","block","progress"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Streaming Mode","help":"Unified Discord stream preview mode: \"off\" | \"partial\" | \"block\" | \"progress\". \"progress\" maps to \"partial\" on Discord. Legacy boolean/streamMode keys are auto-mapped.","hasChildren":false} +{"recordType":"path","path":"channels.discord.streamMode","kind":"channel","type":"string","required":false,"enumValues":["partial","block","off"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Stream Mode (Legacy)","help":"Legacy Discord preview mode alias (off | partial | block); auto-migrated to channels.discord.streaming.","hasChildren":false} +{"recordType":"path","path":"channels.discord.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.threadBindings","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.threadBindings.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Discord Thread Binding Enabled","help":"Enable Discord thread binding features (/focus, bound-thread routing/delivery, and thread-bound subagent sessions). Overrides session.threadBindings.enabled when set.","hasChildren":false} +{"recordType":"path","path":"channels.discord.threadBindings.idleHours","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Discord Thread Binding Idle Timeout (hours)","help":"Inactivity window in hours for Discord thread-bound sessions (/focus and spawned thread sessions). Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set.","hasChildren":false} +{"recordType":"path","path":"channels.discord.threadBindings.maxAgeHours","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance","storage"],"label":"Discord Thread Binding Max Age (hours)","help":"Optional hard max age in hours for Discord thread-bound sessions. Set 0 to disable hard cap (default: 0). Overrides session.threadBindings.maxAgeHours when set.","hasChildren":false} +{"recordType":"path","path":"channels.discord.threadBindings.spawnAcpSessions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Discord Thread-Bound ACP Spawn","help":"Allow /acp spawn to auto-create and bind Discord threads for ACP sessions (default: false; opt-in). Set true to enable thread-bound ACP spawns for this account/channel.","hasChildren":false} +{"recordType":"path","path":"channels.discord.threadBindings.spawnSubagentSessions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Discord Thread-Bound Subagent Spawn","help":"Allow subagent spawns with thread=true to auto-create and bind Discord threads (default: false; opt-in). Set true to enable thread-bound subagent spawns for this account/channel.","hasChildren":false} +{"recordType":"path","path":"channels.discord.token","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"label":"Discord Bot Token","help":"Discord bot token used for gateway and REST API authentication for this provider account. Keep this secret out of committed config and rotate immediately after any leak.","hasChildren":true} +{"recordType":"path","path":"channels.discord.token.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.token.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.token.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.ui","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.ui.components","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.ui.components.accentColor","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Component Accent Color","help":"Accent color for Discord component containers (hex). Set per account via channels.discord.accounts..ui.components.accentColor.","hasChildren":false} +{"recordType":"path","path":"channels.discord.voice","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.autoJoin","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Voice Auto-Join","help":"Voice channels to auto-join on startup (list of guildId/channelId entries).","hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.autoJoin.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.autoJoin.*.channelId","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.autoJoin.*.guildId","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.daveEncryption","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Voice DAVE Encryption","help":"Toggle DAVE end-to-end encryption for Discord voice joins (default: true in @discordjs/voice; Discord may require this).","hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.decryptionFailureTolerance","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Voice Decrypt Failure Tolerance","help":"Consecutive decrypt failures before DAVE attempts session recovery (passed to @discordjs/voice; default: 24).","hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Voice Enabled","help":"Enable Discord voice channel conversations (default: true). Omit channels.discord.voice to keep voice support disabled for the account.","hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","media","network"],"label":"Discord Voice Text-to-Speech","help":"Optional TTS overrides for Discord voice playback (merged with messages.tts).","hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.tts.auto","kind":"channel","type":"string","required":false,"enumValues":["off","always","inbound","tagged"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.edge","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.tts.edge.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.edge.lang","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.edge.outputFormat","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.edge.pitch","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.edge.proxy","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.edge.rate","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.edge.saveSubtitles","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.edge.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.edge.voice","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.edge.volume","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.apiKey","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","media","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.apiKey.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.apiKey.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.apiKey.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.applyTextNormalization","kind":"channel","type":"string","required":false,"enumValues":["auto","on","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.baseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.languageCode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.modelId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.seed","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.voiceId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.voiceSettings","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.voiceSettings.similarityBoost","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.voiceSettings.speed","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.voiceSettings.stability","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.voiceSettings.style","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.elevenlabs.voiceSettings.useSpeakerBoost","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.maxTextLength","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft.lang","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft.outputFormat","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft.pitch","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft.proxy","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft.rate","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft.saveSubtitles","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft.voice","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.microsoft.volume","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.mode","kind":"channel","type":"string","required":false,"enumValues":["final","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.modelOverrides","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.tts.modelOverrides.allowModelId","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.modelOverrides.allowNormalization","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.modelOverrides.allowProvider","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.modelOverrides.allowSeed","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.modelOverrides.allowText","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.modelOverrides.allowVoice","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.modelOverrides.allowVoiceSettings","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.modelOverrides.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.openai","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.tts.openai.apiKey","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","media","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.discord.voice.tts.openai.apiKey.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.openai.apiKey.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.openai.apiKey.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.openai.baseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.openai.instructions","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.openai.model","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.openai.speed","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.openai.voice","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.prefsPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.provider","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.summaryModel","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.voice.tts.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Feishu","help":"飞书/Lark enterprise messaging with doc/wiki/drive tools.","hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.appId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.appSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.appSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.appSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.appSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.blockStreamingCoalesce.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.blockStreamingCoalesce.maxDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.blockStreamingCoalesce.minDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.connectionMode","kind":"channel","type":"string","required":false,"enumValues":["websocket","webhook"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","pairing","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.dms.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.dms.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.domain","kind":"channel","type":"string","required":false,"enumValues":["feishu","lark"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.encryptKey","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.encryptKey.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.encryptKey.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.encryptKey.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","allowlist","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.groupSessionScope","kind":"channel","type":"string","required":false,"enumValues":["group","group_sender","group_topic","group_topic_sender"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.replyInThread","kind":"channel","type":"string","required":false,"enumValues":["disabled","enabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groups.*.topicSessionMode","kind":"channel","type":"string","required":false,"enumValues":["disabled","enabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groupSenderAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.groupSenderAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.groupSessionScope","kind":"channel","type":"string","required":false,"enumValues":["group","group_sender","group_topic","group_topic_sender"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.heartbeat.intervalMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.heartbeat.visibility","kind":"channel","type":"string","required":false,"enumValues":["visible","hidden"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.httpTimeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.markdown.mode","kind":"channel","type":"string","required":false,"enumValues":["native","escape","strip"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.markdown.tableMode","kind":"channel","type":"string","required":false,"enumValues":["native","ascii","simple"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.renderMode","kind":"channel","type":"string","required":false,"enumValues":["auto","raw","card"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.replyInThread","kind":"channel","type":"string","required":false,"enumValues":["disabled","enabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.resolveSenderNames","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.streaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.tools.chat","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.tools.doc","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.tools.drive","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.tools.perm","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.tools.scopes","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.tools.wiki","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.topicSessionMode","kind":"channel","type":"string","required":false,"enumValues":["disabled","enabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.typingIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.verificationToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.accounts.*.verificationToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.verificationToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.verificationToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.webhookHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.accounts.*.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.appId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.appSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.appSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.appSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.appSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.blockStreamingCoalesce.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.blockStreamingCoalesce.maxDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.blockStreamingCoalesce.minDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.connectionMode","kind":"channel","type":"string","required":true,"enumValues":["websocket","webhook"],"defaultValue":"websocket","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","pairing","allowlist"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.dms.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.dms.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.domain","kind":"channel","type":"string","required":true,"enumValues":["feishu","lark"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.dynamicAgentCreation","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.dynamicAgentCreation.agentDirTemplate","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.dynamicAgentCreation.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.dynamicAgentCreation.maxAgents","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.dynamicAgentCreation.workspaceTemplate","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.encryptKey","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.encryptKey.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.encryptKey.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.encryptKey.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","allowlist","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.groups.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.groups.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groups.*.groupSessionScope","kind":"channel","type":"string","required":false,"enumValues":["group","group_sender","group_topic","group_topic_sender"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groups.*.replyInThread","kind":"channel","type":"string","required":false,"enumValues":["disabled","enabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.groups.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groups.*.topicSessionMode","kind":"channel","type":"string","required":false,"enumValues":["disabled","enabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groupSenderAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.groupSenderAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.groupSessionScope","kind":"channel","type":"string","required":false,"enumValues":["group","group_sender","group_topic","group_topic_sender"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.heartbeat.intervalMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.heartbeat.visibility","kind":"channel","type":"string","required":false,"enumValues":["visible","hidden"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.httpTimeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.markdown.mode","kind":"channel","type":"string","required":false,"enumValues":["native","escape","strip"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.markdown.tableMode","kind":"channel","type":"string","required":false,"enumValues":["native","ascii","simple"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.reactionNotifications","kind":"channel","type":"string","required":true,"enumValues":["off","own","all"],"defaultValue":"own","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.renderMode","kind":"channel","type":"string","required":false,"enumValues":["auto","raw","card"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.replyInThread","kind":"channel","type":"string","required":false,"enumValues":["disabled","enabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.requireMention","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.resolveSenderNames","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.streaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.tools.chat","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.tools.doc","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.tools.drive","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.tools.perm","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.tools.scopes","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.tools.wiki","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.topicSessionMode","kind":"channel","type":"string","required":false,"enumValues":["disabled","enabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.typingIndicator","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.verificationToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.feishu.verificationToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.verificationToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.verificationToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.webhookHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.webhookPath","kind":"channel","type":"string","required":true,"defaultValue":"/feishu/events","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.feishu.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Google Chat","help":"Google Workspace Chat app via HTTP webhooks.","hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.allowBots","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.appPrincipal","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.audience","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.audienceType","kind":"channel","type":"string","required":false,"enumValues":["app-url","project-number"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.botUser","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.dm","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.dm.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.dm.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.dm.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.dm.policy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.groups.*.allow","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.groups.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.groups.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.replyToMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.serviceAccount","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.serviceAccount.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.serviceAccount.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.serviceAccount.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.serviceAccount.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.serviceAccountFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.serviceAccountRef","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":true,"tags":["channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.accounts.*.serviceAccountRef.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.serviceAccountRef.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.serviceAccountRef.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.streamMode","kind":"channel","type":"string","required":true,"enumValues":["replace","status_final","append"],"defaultValue":"replace","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.typingIndicator","kind":"channel","type":"string","required":false,"enumValues":["none","message","reaction"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.accounts.*.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.allowBots","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.appPrincipal","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.audience","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.audienceType","kind":"channel","type":"string","required":false,"enumValues":["app-url","project-number"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.botUser","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.dm","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.dm.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.dm.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.dm.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.dm.policy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.groups.*.allow","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.groups.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.groups.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.replyToMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.serviceAccount","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.serviceAccount.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.serviceAccount.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.serviceAccount.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.serviceAccount.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.serviceAccountFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.serviceAccountRef","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":true,"tags":["channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.googlechat.serviceAccountRef.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.serviceAccountRef.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.serviceAccountRef.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.streamMode","kind":"channel","type":"string","required":true,"enumValues":["replace","status_final","append"],"defaultValue":"replace","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.typingIndicator","kind":"channel","type":"string","required":false,"enumValues":["none","message","reaction"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.googlechat.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"iMessage","help":"this is still a work in progress.","hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.attachmentRoots","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.attachmentRoots.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.cliPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.dbPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.groups.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.includeAttachments","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.mediaMaxMb","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.region","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.remoteAttachmentRoots","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.accounts.*.remoteAttachmentRoots.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.remoteHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.service","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.attachmentRoots","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.attachmentRoots.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.cliPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"iMessage CLI Path","help":"Filesystem path to the iMessage bridge CLI binary used for send/receive operations. Set explicitly when the binary is not on PATH in service runtime environments.","hasChildren":false} +{"recordType":"path","path":"channels.imessage.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"iMessage Config Writes","help":"Allow iMessage to write config in response to channel events/commands (default: true).","hasChildren":false} +{"recordType":"path","path":"channels.imessage.dbPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"iMessage DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.imessage.allowFrom=[\"*\"].","hasChildren":false} +{"recordType":"path","path":"channels.imessage.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.groups.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.groups.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.groups.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.groups.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.includeAttachments","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.mediaMaxMb","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.region","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.remoteAttachmentRoots","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.imessage.remoteAttachmentRoots.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.remoteHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.service","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.imessage.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"IRC","help":"classic IRC networks with DM/channel routing and pairing controls.","hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.channels","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.channels.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.groups.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.host","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.mentionPatterns","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.mentionPatterns.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.nick","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.nickserv","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.accounts.*.nickserv.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.nickserv.password","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.nickserv.passwordFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.nickserv.register","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.nickserv.registerEmail","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.nickserv.service","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.password","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.passwordFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.port","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.realname","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.tls","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.accounts.*.username","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.channels","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.channels.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"IRC DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.irc.allowFrom=[\"*\"].","hasChildren":false} +{"recordType":"path","path":"channels.irc.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.groups.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.groups.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.host","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.mentionPatterns","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.mentionPatterns.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.nick","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.nickserv","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.irc.nickserv.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"IRC NickServ Enabled","help":"Enable NickServ identify/register after connect (defaults to enabled when password is configured).","hasChildren":false} +{"recordType":"path","path":"channels.irc.nickserv.password","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"label":"IRC NickServ Password","help":"NickServ password used for IDENTIFY/REGISTER (sensitive).","hasChildren":false} +{"recordType":"path","path":"channels.irc.nickserv.passwordFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["auth","channels","network","security","storage"],"label":"IRC NickServ Password File","help":"Optional file path containing NickServ password.","hasChildren":false} +{"recordType":"path","path":"channels.irc.nickserv.register","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"IRC NickServ Register","help":"If true, send NickServ REGISTER on every connect. Use once for initial registration, then disable.","hasChildren":false} +{"recordType":"path","path":"channels.irc.nickserv.registerEmail","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"IRC NickServ Register Email","help":"Email used with NickServ REGISTER (required when register=true).","hasChildren":false} +{"recordType":"path","path":"channels.irc.nickserv.service","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"IRC NickServ Service","help":"NickServ service nick (default: NickServ).","hasChildren":false} +{"recordType":"path","path":"channels.irc.password","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":false} +{"recordType":"path","path":"channels.irc.passwordFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.port","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.realname","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.tls","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.irc.username","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"LINE","help":"LINE Messaging API bot for Japan/Taiwan/Thailand markets.","hasChildren":true} +{"recordType":"path","path":"channels.line.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.channelAccessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.channelSecret","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","allowlist","pairing","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","allowlist","disabled"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.accounts.*.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.accounts.*.groups.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.accounts.*.groups.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.accounts.*.groups.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.secretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.accounts.*.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.channelAccessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.channelSecret","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","allowlist","pairing","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","allowlist","disabled"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.groups.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.groups.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.line.groups.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.secretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.line.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Matrix","help":"open protocol; install the plugin to enable.","hasChildren":true} +{"recordType":"path","path":"channels.matrix.accessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.accounts.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.actions.channelInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.actions.memberInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.actions.messages","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.actions.pins","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.allowlistOnly","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.autoJoin","kind":"channel","type":"string","required":false,"enumValues":["always","allowlist","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.autoJoinAllowlist","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.autoJoinAllowlist.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.deviceName","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.dm","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.dm.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.dm.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.dm.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.dm.policy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.encryption","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.groups.*.allow","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groups.*.autoReply","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.groups.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"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.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} +{"recordType":"path","path":"channels.matrix.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.password","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.password.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.password.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.password.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.replyToMode","kind":"channel","type":"string","required":false,"enumValues":["off","first","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.rooms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.rooms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.rooms.*.allow","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.rooms.*.autoReply","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.rooms.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.rooms.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.rooms.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.rooms.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.rooms.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.rooms.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.rooms.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.rooms.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.rooms.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.rooms.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.rooms.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.rooms.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.rooms.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.matrix.rooms.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.textChunkLimit","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.threadReplies","kind":"channel","type":"string","required":false,"enumValues":["off","inbound","always"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.matrix.userId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost","help":"self-hosted Slack-style chat; install the plugin to enable.","hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.baseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.chatmode","kind":"channel","type":"string","required":false,"enumValues":["oncall","onmessage","onchar"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.commands","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.commands.callbackPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.commands.callbackUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.commands.native","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.commands.nativeSkills","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.dmChannelRetry","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.dmChannelRetry.initialDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.dmChannelRetry.maxDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.dmChannelRetry.maxRetries","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.dmChannelRetry.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.interactions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.interactions.allowedSourceIps","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.interactions.allowedSourceIps.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.interactions.callbackBaseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.oncharPrefixes","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.accounts.*.oncharPrefixes.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.replyToMode","kind":"channel","type":"string","required":false,"enumValues":["off","first","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.baseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Base URL","help":"Base URL for your Mattermost server (e.g., https://chat.example.com).","hasChildren":false} +{"recordType":"path","path":"channels.mattermost.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"label":"Mattermost Bot Token","help":"Bot token from Mattermost System Console -> Integrations -> Bot Accounts.","hasChildren":true} +{"recordType":"path","path":"channels.mattermost.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.chatmode","kind":"channel","type":"string","required":false,"enumValues":["oncall","onmessage","onchar"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Chat Mode","help":"Reply to channel messages on mention (\"oncall\"), on trigger chars (\">\" or \"!\") (\"onchar\"), or on every message (\"onmessage\").","hasChildren":false} +{"recordType":"path","path":"channels.mattermost.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.commands","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.commands.callbackPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.commands.callbackUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.commands.native","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.commands.nativeSkills","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Config Writes","help":"Allow Mattermost to write config in response to channel events/commands (default: true).","hasChildren":false} +{"recordType":"path","path":"channels.mattermost.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.dmChannelRetry","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.dmChannelRetry.initialDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.dmChannelRetry.maxDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.dmChannelRetry.maxRetries","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.dmChannelRetry.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.interactions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.interactions.allowedSourceIps","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.interactions.allowedSourceIps.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.interactions.callbackBaseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.mattermost.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.oncharPrefixes","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Onchar Prefixes","help":"Trigger prefixes for onchar mode (default: [\">\", \"!\"]).","hasChildren":true} +{"recordType":"path","path":"channels.mattermost.oncharPrefixes.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.replyToMode","kind":"channel","type":"string","required":false,"enumValues":["off","first","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Require Mention","help":"Require @mention in channels before responding (default: true).","hasChildren":false} +{"recordType":"path","path":"channels.mattermost.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.mattermost.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Microsoft Teams","help":"Bot Framework; enterprise support.","hasChildren":true} +{"recordType":"path","path":"channels.msteams.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.appId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.appPassword","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.appPassword.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.appPassword.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.appPassword.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"MS Teams Config Writes","help":"Allow Microsoft Teams to write config in response to channel events/commands (default: true).","hasChildren":false} +{"recordType":"path","path":"channels.msteams.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.groupAllowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.mediaAllowHosts","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.mediaAllowHosts.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.mediaAuthAllowHosts","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.mediaAuthAllowHosts.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.replyStyle","kind":"channel","type":"string","required":false,"enumValues":["thread","top-level"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.sharePointSiteId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.replyStyle","kind":"channel","type":"string","required":false,"enumValues":["thread","top-level"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.channels.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.replyStyle","kind":"channel","type":"string","required":false,"enumValues":["thread","top-level"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.teams.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.teams.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.tenantId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.webhook","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.msteams.webhook.path","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.msteams.webhook.port","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Nextcloud Talk","help":"Self-hosted chat via Nextcloud Talk webhook bots.","hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiPassword","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiPassword.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiPassword.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiPassword.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiPasswordFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiUser","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.baseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.groupAllowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.rooms.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.webhookHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.webhookPublicUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.apiPassword","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.apiPassword.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.apiPassword.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.apiPassword.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.apiPasswordFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.apiUser","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.baseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.botSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.botSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.botSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.botSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.botSecretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.groupAllowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.rooms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nextcloud-talk.rooms.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.webhookHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nextcloud-talk.webhookPublicUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Nostr","help":"Decentralized protocol; encrypted DMs via NIP-04.","hasChildren":true} +{"recordType":"path","path":"channels.nostr.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nostr.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nostr.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.privateKey","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.profile","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nostr.profile.about","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.profile.banner","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.profile.displayName","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.profile.lud16","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.profile.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.profile.nip05","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.profile.picture","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.profile.website","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.nostr.relays","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.nostr.relays.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Signal","help":"signal-cli linked device; more setup (David Reagans: \"Hop on Discord.\").","hasChildren":true} +{"recordType":"path","path":"channels.signal.account","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Signal Account","help":"Signal account identifier (phone/number handle) used to bind this channel config to a specific Signal identity. Keep this aligned with your linked device/session state.","hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.account","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.accountUuid","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.autoStart","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.cliPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.groups.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.httpHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.httpPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.httpUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.ignoreAttachments","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.ignoreStories","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.mediaMaxMb","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.reactionAllowlist","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.accounts.*.reactionAllowlist.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.reactionLevel","kind":"channel","type":"string","required":false,"enumValues":["off","ack","minimal","extensive"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.receiveMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.sendReadReceipts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.startupTimeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.accountUuid","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.autoStart","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.cliPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Signal Config Writes","help":"Allow Signal to write config in response to channel events/commands (default: true).","hasChildren":false} +{"recordType":"path","path":"channels.signal.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Signal DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.signal.allowFrom=[\"*\"].","hasChildren":false} +{"recordType":"path","path":"channels.signal.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.groups.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.groups.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.groups.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.groups.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.httpHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.httpPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.httpUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.ignoreAttachments","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.ignoreStories","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.mediaMaxMb","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.reactionAllowlist","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.signal.reactionAllowlist.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.reactionLevel","kind":"channel","type":"string","required":false,"enumValues":["off","ack","minimal","extensive"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.receiveMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.sendReadReceipts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.startupTimeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.signal.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack","help":"supported (Socket Mode).","hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.actions.channelInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.actions.emojiList","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.actions.memberInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.actions.messages","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.actions.permissions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.actions.pins","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.actions.search","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.allowBots","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.appToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.appToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.appToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.appToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.capabilities","kind":"channel","type":["array","object"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.capabilities.interactiveReplies","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.allow","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.allowBots","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.channels.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.commands","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.commands.native","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.commands.nativeSkills","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.dm","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.dm.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.dm.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.dm.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.dm.groupChannels","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.dm.groupChannels.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.dm.groupEnabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.dm.policy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.dm.replyToMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.mode","kind":"channel","type":"string","required":false,"enumValues":["socket","http"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.nativeStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.reactionAllowlist","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.reactionAllowlist.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.replyToMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.replyToModeByChatType","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.replyToModeByChatType.channel","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.replyToModeByChatType.direct","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.replyToModeByChatType.group","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.signingSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.signingSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.signingSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.signingSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.slashCommand","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.slashCommand.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.slashCommand.ephemeral","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.slashCommand.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.slashCommand.sessionPrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.streaming","kind":"channel","type":["boolean","string"],"required":false,"enumValues":["off","partial","block","progress"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.streamMode","kind":"channel","type":"string","required":false,"enumValues":["replace","status_final","append"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.thread","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.thread.historyScope","kind":"channel","type":"string","required":false,"enumValues":["thread","channel"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.thread.inheritParent","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.thread.initialHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.typingReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.userToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.slack.accounts.*.userToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.userToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.userToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.userTokenReadOnly","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.accounts.*.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.actions.channelInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.actions.emojiList","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.actions.memberInfo","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.actions.messages","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.actions.permissions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.actions.pins","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.actions.search","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.allowBots","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Slack Allow Bot Messages","help":"Allow bot-authored messages to trigger Slack replies (default: false).","hasChildren":false} +{"recordType":"path","path":"channels.slack.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.appToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"label":"Slack App Token","help":"Slack app-level token used for Socket Mode connections and event transport when enabled. Use least-privilege app scopes and store this token as a secret.","hasChildren":true} +{"recordType":"path","path":"channels.slack.appToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.appToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.appToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"label":"Slack Bot Token","help":"Slack bot token used for standard chat actions in the configured workspace. Keep this credential scoped and rotate if workspace app permissions change.","hasChildren":true} +{"recordType":"path","path":"channels.slack.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.capabilities","kind":"channel","type":["array","object"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.capabilities.interactiveReplies","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Interactive Replies","help":"Enable agent-authored Slack interactive reply directives (`[[slack_buttons: ...]]`, `[[slack_select: ...]]`). Default: false.","hasChildren":false} +{"recordType":"path","path":"channels.slack.channels","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.allow","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.allowBots","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.channels.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.channels.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.commands","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.commands.native","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Native Commands","help":"Override native commands for Slack (bool or \"auto\").","hasChildren":false} +{"recordType":"path","path":"channels.slack.commands.nativeSkills","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Native Skill Commands","help":"Override native skill commands for Slack (bool or \"auto\").","hasChildren":false} +{"recordType":"path","path":"channels.slack.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Config Writes","help":"Allow Slack to write config in response to channel events/commands (default: true).","hasChildren":false} +{"recordType":"path","path":"channels.slack.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.dm","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.dm.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.dm.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.dm.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.dm.groupChannels","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.dm.groupChannels.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.dm.groupEnabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.dm.policy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Slack DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.slack.allowFrom=[\"*\"] (legacy: channels.slack.dm.allowFrom).","hasChildren":false} +{"recordType":"path","path":"channels.slack.dm.replyToMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Slack DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.slack.allowFrom=[\"*\"].","hasChildren":false} +{"recordType":"path","path":"channels.slack.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.mode","kind":"channel","type":"string","required":true,"enumValues":["socket","http"],"defaultValue":"socket","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.nativeStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Native Streaming","help":"Enable native Slack text streaming (chat.startStream/chat.appendStream/chat.stopStream) when channels.slack.streaming is partial (default: true).","hasChildren":false} +{"recordType":"path","path":"channels.slack.reactionAllowlist","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.reactionAllowlist.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.replyToMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.replyToModeByChatType","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.replyToModeByChatType.channel","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.replyToModeByChatType.direct","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.replyToModeByChatType.group","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.signingSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.slack.signingSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.signingSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.signingSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.slashCommand","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.slashCommand.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.slashCommand.ephemeral","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.slashCommand.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.slashCommand.sessionPrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.streaming","kind":"channel","type":["boolean","string"],"required":false,"enumValues":["off","partial","block","progress"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Streaming Mode","help":"Unified Slack stream preview mode: \"off\" | \"partial\" | \"block\" | \"progress\". Legacy boolean/streamMode keys are auto-mapped.","hasChildren":false} +{"recordType":"path","path":"channels.slack.streamMode","kind":"channel","type":"string","required":false,"enumValues":["replace","status_final","append"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Stream Mode (Legacy)","help":"Legacy Slack preview mode alias (replace | status_final | append); auto-migrated to channels.slack.streaming.","hasChildren":false} +{"recordType":"path","path":"channels.slack.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.thread","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.slack.thread.historyScope","kind":"channel","type":"string","required":false,"enumValues":["thread","channel"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Thread History Scope","help":"Scope for Slack thread history context (\"thread\" isolates per thread; \"channel\" reuses channel history).","hasChildren":false} +{"recordType":"path","path":"channels.slack.thread.inheritParent","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Slack Thread Parent Inheritance","help":"If true, Slack thread sessions inherit the parent channel transcript (default: false).","hasChildren":false} +{"recordType":"path","path":"channels.slack.thread.initialHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"Slack Thread Initial History Limit","help":"Maximum number of existing Slack thread messages to fetch when starting a new thread session (default: 20, set to 0 to disable).","hasChildren":false} +{"recordType":"path","path":"channels.slack.typingReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.userToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"label":"Slack User Token","help":"Optional Slack user token for workflows requiring user-context API access beyond bot permissions. Use sparingly and audit scopes because this token can carry broader authority.","hasChildren":true} +{"recordType":"path","path":"channels.slack.userToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.userToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.userToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.slack.userTokenReadOnly","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":["auth","channels","network","security"],"label":"Slack User Token Read Only","help":"When true, treat configured Slack user token usage as read-only helper behavior where possible. Keep enabled if you only need supplemental reads without user-context writes.","hasChildren":false} +{"recordType":"path","path":"channels.slack.webhookPath","kind":"channel","type":"string","required":true,"defaultValue":"/slack/events","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.synology-chat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Synology Chat","help":"Connect your Synology NAS Chat to OpenClaw with full agent capabilities.","hasChildren":true} +{"recordType":"path","path":"channels.synology-chat.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram","help":"simplest way to get started — register a bot with @BotFather and get going.","hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.actions.createForumTopic","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.actions.deleteMessage","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.actions.editForumTopic","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.actions.editMessage","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.actions.poll","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.actions.sendMessage","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.actions.sticker","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.capabilities","kind":"channel","type":["array","object"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.capabilities.inlineButtons","kind":"channel","type":"string","required":false,"enumValues":["off","dm","group","all","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.commands","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.commands.native","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.commands.nativeSkills","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.customCommands","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.customCommands.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.customCommands.*.command","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.customCommands.*.description","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.defaultTo","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.requireTopic","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.agentId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.direct.*.topics.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.draftChunk","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.draftChunk.breakPreference","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.draftChunk.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.draftChunk.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals.agentFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals.agentFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals.approvers","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals.approvers.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals.sessionFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals.sessionFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.execApprovals.target","kind":"channel","type":"string","required":false,"enumValues":["dm","channel","both"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.agentId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.groups.*.topics.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.linkPreview","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.network","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.network.autoSelectFamily","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.network.dnsResultOrder","kind":"channel","type":"string","required":false,"enumValues":["ipv4first","verbatim"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.proxy","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.reactionLevel","kind":"channel","type":"string","required":false,"enumValues":["off","ack","minimal","extensive"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.replyToMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.retry","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.retry.attempts","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.retry.jitter","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.retry.maxDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.retry.minDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.silentErrorReplies","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.streaming","kind":"channel","type":["boolean","string"],"required":false,"enumValues":["off","partial","block","progress"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.streamMode","kind":"channel","type":"string","required":false,"enumValues":["off","partial","block"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.threadBindings","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.threadBindings.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.threadBindings.idleHours","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.threadBindings.maxAgeHours","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.threadBindings.spawnAcpSessions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.threadBindings.spawnSubagentSessions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.timeoutSeconds","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.webhookCertPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.webhookHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.webhookSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.accounts.*.webhookSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.webhookSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.webhookSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.accounts.*.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.actions.createForumTopic","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.actions.deleteMessage","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.actions.editForumTopic","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.actions.editMessage","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.actions.poll","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.actions.sendMessage","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.actions.sticker","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"label":"Telegram Bot Token","help":"Telegram bot token used to authenticate Bot API requests for this account/provider config. Use secret/env substitution and rotate tokens if exposure is suspected.","hasChildren":true} +{"recordType":"path","path":"channels.telegram.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.capabilities","kind":"channel","type":["array","object"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.capabilities.inlineButtons","kind":"channel","type":"string","required":false,"enumValues":["off","dm","group","all","allowlist"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Inline Buttons","help":"Enable Telegram inline button components for supported command and interaction surfaces. Disable if your deployment needs plain-text-only compatibility behavior.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.commands","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.commands.native","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Native Commands","help":"Override native commands for Telegram (bool or \"auto\").","hasChildren":false} +{"recordType":"path","path":"channels.telegram.commands.nativeSkills","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Native Skill Commands","help":"Override native skill commands for Telegram (bool or \"auto\").","hasChildren":false} +{"recordType":"path","path":"channels.telegram.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Config Writes","help":"Allow Telegram to write config in response to channel events/commands (default: true).","hasChildren":false} +{"recordType":"path","path":"channels.telegram.customCommands","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Custom Commands","help":"Additional Telegram bot menu commands (merged with native; conflicts ignored).","hasChildren":true} +{"recordType":"path","path":"channels.telegram.customCommands.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.customCommands.*.command","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.customCommands.*.description","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.defaultTo","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.requireTopic","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.topics","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*.agentId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.direct.*.topics.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Telegram DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.telegram.allowFrom=[\"*\"].","hasChildren":false} +{"recordType":"path","path":"channels.telegram.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.draftChunk","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.draftChunk.breakPreference","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.draftChunk.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.draftChunk.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.execApprovals","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Exec Approvals","help":"Telegram-native exec approval routing and approver authorization. Enable this only when Telegram should act as an explicit exec-approval client for the selected bot account.","hasChildren":true} +{"recordType":"path","path":"channels.telegram.execApprovals.agentFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Exec Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for Telegram exec approvals, for example `[\"main\", \"ops-agent\"]`. Use this to keep approval prompts scoped to the agents you actually operate from Telegram.","hasChildren":true} +{"recordType":"path","path":"channels.telegram.execApprovals.agentFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.execApprovals.approvers","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Exec Approval Approvers","help":"Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs; prompts are only delivered to these approvers when target includes dm.","hasChildren":true} +{"recordType":"path","path":"channels.telegram.execApprovals.approvers.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.execApprovals.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Exec Approvals Enabled","help":"Enable Telegram exec approvals for this account. When false or unset, Telegram messages/buttons cannot approve exec requests.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.execApprovals.sessionFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Telegram Exec Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns before Telegram approval routing is used. Use narrow patterns so Telegram approvals only appear for intended sessions.","hasChildren":true} +{"recordType":"path","path":"channels.telegram.execApprovals.sessionFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.execApprovals.target","kind":"channel","type":"string","required":false,"enumValues":["dm","channel","both"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Exec Approval Target","help":"Controls where Telegram approval prompts are sent: \"dm\" sends to approver DMs (default), \"channel\" sends to the originating Telegram chat/topic, and \"both\" sends to both. Channel delivery exposes the command text to the chat, so only use it in trusted groups/topics.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.topics","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*.agentId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*.disableAudioPreflight","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.groups.*.topics.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.linkPreview","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.network","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.network.autoSelectFamily","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram autoSelectFamily","help":"Override Node autoSelectFamily for Telegram (true=enable, false=disable).","hasChildren":false} +{"recordType":"path","path":"channels.telegram.network.dnsResultOrder","kind":"channel","type":"string","required":false,"enumValues":["ipv4first","verbatim"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.proxy","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.reactionLevel","kind":"channel","type":"string","required":false,"enumValues":["off","ack","minimal","extensive"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.replyToMode","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.retry","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.retry.attempts","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","reliability"],"label":"Telegram Retry Attempts","help":"Max retry attempts for outbound Telegram API calls (default: 3).","hasChildren":false} +{"recordType":"path","path":"channels.telegram.retry.jitter","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","reliability"],"label":"Telegram Retry Jitter","help":"Jitter factor (0-1) applied to Telegram retry delays.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.retry.maxDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance","reliability"],"label":"Telegram Retry Max Delay (ms)","help":"Maximum retry delay cap in ms for Telegram outbound calls.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.retry.minDelayMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","reliability"],"label":"Telegram Retry Min Delay (ms)","help":"Minimum retry delay in ms for Telegram outbound calls.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.silentErrorReplies","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Silent Error Replies","help":"When true, Telegram bot replies marked as errors are sent silently (no notification sound). Default: false.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.streaming","kind":"channel","type":["boolean","string"],"required":false,"enumValues":["off","partial","block","progress"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram Streaming Mode","help":"Unified Telegram stream preview mode: \"off\" | \"partial\" | \"block\" | \"progress\" (default: \"partial\"). \"progress\" maps to \"partial\" on Telegram. Legacy boolean/streamMode keys are auto-mapped.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.streamMode","kind":"channel","type":"string","required":false,"enumValues":["off","partial","block"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.threadBindings","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.threadBindings.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Telegram Thread Binding Enabled","help":"Enable Telegram conversation binding features (/focus, /unfocus, /agents, and /session idle|max-age). Overrides session.threadBindings.enabled when set.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.threadBindings.idleHours","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Telegram Thread Binding Idle Timeout (hours)","help":"Inactivity window in hours for Telegram bound sessions. Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.threadBindings.maxAgeHours","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance","storage"],"label":"Telegram Thread Binding Max Age (hours)","help":"Optional hard max age in hours for Telegram bound sessions. Set 0 to disable hard cap (default: 0). Overrides session.threadBindings.maxAgeHours when set.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.threadBindings.spawnAcpSessions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Telegram Thread-Bound ACP Spawn","help":"Allow ACP spawns with thread=true to auto-bind Telegram current conversations when supported.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.threadBindings.spawnSubagentSessions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","storage"],"label":"Telegram Thread-Bound Subagent Spawn","help":"Allow subagent spawns with thread=true to auto-bind Telegram current conversations when supported.","hasChildren":false} +{"recordType":"path","path":"channels.telegram.timeoutSeconds","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"Telegram API Timeout (seconds)","help":"Max seconds before Telegram API requests are aborted (default: 500 per grammY).","hasChildren":false} +{"recordType":"path","path":"channels.telegram.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.webhookCertPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.webhookHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.webhookSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true} +{"recordType":"path","path":"channels.telegram.webhookSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.webhookSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.webhookSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.telegram.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Tlon","help":"decentralized messaging on Urbit; install the plugin to enable.","hasChildren":true} +{"recordType":"path","path":"channels.tlon.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.accounts.*.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.autoAcceptDmInvites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.autoAcceptGroupInvites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.autoDiscoverChannels","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.code","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.dmAllowlist","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.accounts.*.dmAllowlist.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.groupChannels","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.accounts.*.groupChannels.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.ownerShip","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.ship","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.showModelSignature","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.accounts.*.url","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.authorization","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.authorization.channelRules","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.authorization.channelRules.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.authorization.channelRules.*.allowedShips","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.authorization.channelRules.*.allowedShips.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.authorization.channelRules.*.mode","kind":"channel","type":"string","required":false,"enumValues":["restricted","open"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.autoAcceptDmInvites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.autoAcceptGroupInvites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.autoDiscoverChannels","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.code","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.defaultAuthorizedShips","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.defaultAuthorizedShips.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.dmAllowlist","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.dmAllowlist.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.groupChannels","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.tlon.groupChannels.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.ownerShip","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.ship","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.showModelSignature","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.tlon.url","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Twitch","help":"Twitch chat integration","hasChildren":true} +{"recordType":"path","path":"channels.twitch.accessToken","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts","kind":"channel","type":"object","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.twitch.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.twitch.accounts.*.accessToken","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.allowedRoles","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.twitch.accounts.*.allowedRoles.*","kind":"channel","type":"string","required":false,"enumValues":["moderator","owner","vip","subscriber","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.twitch.accounts.*.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.channel","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.clientId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.clientSecret","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.expiresIn","kind":"channel","type":["null","number"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.obtainmentTimestamp","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.refreshToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.accounts.*.username","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.allowedRoles","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.twitch.allowedRoles.*","kind":"channel","type":"string","required":false,"enumValues":["moderator","owner","vip","subscriber","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.twitch.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.channel","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.clientId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.clientSecret","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.expiresIn","kind":"channel","type":["null","number"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.twitch.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["bullets","code","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.obtainmentTimestamp","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.refreshToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.twitch.username","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"WhatsApp","help":"works with your own number; recommend a separate phone + eSIM.","hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.ackReaction","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.ackReaction.direct","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.ackReaction.emoji","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.ackReaction.group","kind":"channel","type":"string","required":true,"enumValues":["always","mentions","never"],"defaultValue":"mentions","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.authDir","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.debounceMs","kind":"channel","type":"integer","required":true,"defaultValue":0,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groupAllowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.groups.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.mediaMaxMb","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.messagePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.selfChatMode","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.sendReadReceipts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.accounts.*.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.ackReaction","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.ackReaction.direct","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.ackReaction.emoji","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.ackReaction.group","kind":"channel","type":"string","required":true,"enumValues":["always","mentions","never"],"defaultValue":"mentions","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.actions.polls","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.actions.sendMessage","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"WhatsApp Config Writes","help":"Allow WhatsApp to write config in response to channel events/commands (default: true).","hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.debounceMs","kind":"channel","type":"integer","required":true,"defaultValue":0,"deprecated":false,"sensitive":false,"tags":["channels","network","performance"],"label":"WhatsApp Message Debounce (ms)","help":"Debounce window (ms) for batching rapid consecutive messages from the same sender (0 to disable).","hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"WhatsApp DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.whatsapp.allowFrom=[\"*\"].","hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.dms","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.dms.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.dms.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groupAllowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.groups.*.toolsBySender","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*.toolsBySender.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*.toolsBySender.*.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*.toolsBySender.*.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.groups.*.toolsBySender.*.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*.toolsBySender.*.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.groups.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.groups.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.heartbeat.showAlerts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.heartbeat.showOk","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.heartbeat.useIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.whatsapp.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.mediaMaxMb","kind":"channel","type":"integer","required":true,"defaultValue":50,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.messagePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.selfChatMode","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"WhatsApp Self-Phone Mode","help":"Same-phone setup (bot uses your personal WhatsApp number).","hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.sendReadReceipts","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.whatsapp.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Zalo","help":"Vietnam-focused messaging platform with Bot API.","hasChildren":true} +{"recordType":"path","path":"channels.zalo.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.accounts.*.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.proxy","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.webhookSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.accounts.*.webhookSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.webhookSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.webhookSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.accounts.*.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.groupPolicy","kind":"channel","type":"string","required":false,"enumValues":["open","disabled","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.proxy","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.webhookSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalo.webhookSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.webhookSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.webhookSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalo.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Zalo Personal","help":"Zalo personal account via QR code login.","hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*.allow","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.accounts.*.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.messagePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.profile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.dmPolicy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.groupAllowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.groupPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","disabled","allowlist"],"defaultValue":"allowlist","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.groups","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.groups.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.groups.*.allow","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.groups.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.groups.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.groups.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.groups.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.groups.*.tools.allow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.groups.*.tools.alsoAllow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.groups.*.tools.alsoAllow.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.groups.*.tools.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"channels.zalouser.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.messagePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.profile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.zalouser.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cli","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"CLI","help":"CLI presentation controls for local command output behavior such as banner and tagline style. Use this section to keep startup output aligned with operator preference without changing runtime behavior.","hasChildren":true} +{"recordType":"path","path":"cli.banner","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"CLI Banner","help":"CLI startup banner controls for title/version line and tagline style behavior. Keep banner enabled for fast version/context checks, then tune tagline mode to your preferred noise level.","hasChildren":true} +{"recordType":"path","path":"cli.banner.taglineMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"CLI Banner Tagline Mode","help":"Controls tagline style in the CLI startup banner: \"random\" (default) picks from the rotating tagline pool, \"default\" always shows the neutral default tagline, and \"off\" hides tagline text while keeping the banner version line.","hasChildren":false} +{"recordType":"path","path":"commands","kind":"core","type":"object","required":true,"defaultValue":{"native":"auto","nativeSkills":"auto","ownerDisplay":"raw","restart":true},"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Commands","help":"Controls chat command surfaces, owner gating, and elevated command access behavior across providers. Keep defaults unless you need stricter operator controls or broader command availability.","hasChildren":true} +{"recordType":"path","path":"commands.allowFrom","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Command Elevated Access Rules","help":"Defines elevated command allow rules by channel and sender for owner-level command surfaces. Use narrow provider-specific identities so privileged commands are not exposed to broad chat audiences.","hasChildren":true} +{"recordType":"path","path":"commands.allowFrom.*","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"commands.allowFrom.*.*","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"commands.bash","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Allow Bash Chat Command","help":"Allow bash chat command (`!`; `/bash` alias) to run host shell commands (default: false; requires tools.elevated).","hasChildren":false} +{"recordType":"path","path":"commands.bashForegroundMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Bash Foreground Window (ms)","help":"How long bash waits before backgrounding (default: 2000; 0 backgrounds immediately).","hasChildren":false} +{"recordType":"path","path":"commands.config","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Allow /config","help":"Allow /config chat command to read/write config on disk (default: false).","hasChildren":false} +{"recordType":"path","path":"commands.debug","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Allow /debug","help":"Allow /debug chat command for runtime-only overrides (default: false).","hasChildren":false} +{"recordType":"path","path":"commands.mcp","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Allow /mcp","help":"Allow /mcp chat command to manage OpenClaw MCP server config under mcp.servers (default: false).","hasChildren":false} +{"recordType":"path","path":"commands.native","kind":"core","type":["boolean","string"],"required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Native Commands","help":"Registers native slash/menu commands with channels that support command registration (Discord, Slack, Telegram). Keep enabled for discoverability unless you intentionally run text-only command workflows.","hasChildren":false} +{"recordType":"path","path":"commands.nativeSkills","kind":"core","type":["boolean","string"],"required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Native Skill Commands","help":"Registers native skill commands so users can invoke skills directly from provider command menus where supported. Keep aligned with your skill policy so exposed commands match what operators expect.","hasChildren":false} +{"recordType":"path","path":"commands.ownerAllowFrom","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Command Owners","help":"Explicit owner allowlist for owner-only tools/commands. Use channel-native IDs (optionally prefixed like \"whatsapp:+15551234567\"). '*' is ignored.","hasChildren":true} +{"recordType":"path","path":"commands.ownerAllowFrom.*","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"commands.ownerDisplay","kind":"core","type":"string","required":true,"enumValues":["raw","hash"],"defaultValue":"raw","deprecated":false,"sensitive":false,"tags":["access"],"label":"Owner ID Display","help":"Controls how owner IDs are rendered in the system prompt. Allowed values: raw, hash. Default: raw.","hasChildren":false} +{"recordType":"path","path":"commands.ownerDisplaySecret","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["access","auth","security"],"label":"Owner ID Hash Secret","help":"Optional secret used to HMAC hash owner IDs when ownerDisplay=hash. Prefer env substitution.","hasChildren":false} +{"recordType":"path","path":"commands.plugins","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Allow /plugins","help":"Allow /plugins chat command to list discovered plugins and toggle plugin enablement in config (default: false).","hasChildren":false} +{"recordType":"path","path":"commands.restart","kind":"core","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Allow Restart","help":"Allow /restart and gateway restart tool actions (default: true).","hasChildren":false} +{"recordType":"path","path":"commands.text","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Text Commands","help":"Enables text-command parsing in chat input in addition to native command surfaces where available. Keep this enabled for compatibility across channels that do not support native command registration.","hasChildren":false} +{"recordType":"path","path":"commands.useAccessGroups","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Use Access Groups","help":"Enforce access-group allowlists/policies for commands.","hasChildren":false} +{"recordType":"path","path":"cron","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Cron","help":"Global scheduler settings for stored cron jobs, run concurrency, delivery fallback, and run-session retention. Keep defaults unless you are scaling job volume or integrating external webhook receivers.","hasChildren":true} +{"recordType":"path","path":"cron.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Cron Enabled","help":"Enables cron job execution for stored schedules managed by the gateway. Keep enabled for normal reminder/automation flows, and disable only to pause all cron execution without deleting jobs.","hasChildren":false} +{"recordType":"path","path":"cron.failureAlert","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"cron.failureAlert.accountId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.failureAlert.after","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.failureAlert.cooldownMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.failureAlert.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.failureAlert.mode","kind":"core","type":"string","required":false,"enumValues":["announce","webhook"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.failureDestination","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"cron.failureDestination.accountId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.failureDestination.channel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.failureDestination.mode","kind":"core","type":"string","required":false,"enumValues":["announce","webhook"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.failureDestination.to","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.maxConcurrentRuns","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["automation","performance"],"label":"Cron Max Concurrent Runs","help":"Limits how many cron jobs can execute at the same time when multiple schedules fire together. Use lower values to protect CPU/memory under heavy automation load, or raise carefully for higher throughput.","hasChildren":false} +{"recordType":"path","path":"cron.retry","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["automation","reliability"],"label":"Cron Retry Policy","help":"Overrides the default retry policy for one-shot jobs when they fail with transient errors (rate limit, overloaded, network, server_error). Omit to use defaults: maxAttempts 3, backoffMs [30000, 60000, 300000], retry all transient types.","hasChildren":true} +{"recordType":"path","path":"cron.retry.backoffMs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["automation","reliability"],"label":"Cron Retry Backoff (ms)","help":"Backoff delays in ms for each retry attempt (default: [30000, 60000, 300000]). Use shorter values for faster retries.","hasChildren":true} +{"recordType":"path","path":"cron.retry.backoffMs.*","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.retry.maxAttempts","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["automation","performance","reliability"],"label":"Cron Retry Max Attempts","help":"Max retries for one-shot jobs on transient errors before permanent disable (default: 3).","hasChildren":false} +{"recordType":"path","path":"cron.retry.retryOn","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["automation","reliability"],"label":"Cron Retry Error Types","help":"Error types to retry: rate_limit, overloaded, network, timeout, server_error. Use to restrict which errors trigger retries; omit to retry all transient types.","hasChildren":true} +{"recordType":"path","path":"cron.retry.retryOn.*","kind":"core","type":"string","required":false,"enumValues":["rate_limit","overloaded","network","timeout","server_error"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.runLog","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Cron Run Log Pruning","help":"Pruning controls for per-job cron run history files under `cron/runs/.jsonl`, including size and line retention.","hasChildren":true} +{"recordType":"path","path":"cron.runLog.keepLines","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Cron Run Log Keep Lines","help":"How many trailing run-log lines to retain when a file exceeds maxBytes (default `2000`). Increase for longer forensic history or lower for smaller disks.","hasChildren":false} +{"recordType":"path","path":"cron.runLog.maxBytes","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["automation","performance"],"label":"Cron Run Log Max Bytes","help":"Maximum bytes per cron run-log file before pruning rewrites to the last keepLines entries (for example `2mb`, default `2000000`).","hasChildren":false} +{"recordType":"path","path":"cron.sessionRetention","kind":"core","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["automation","storage"],"label":"Cron Session Retention","help":"Controls how long completed cron run sessions are kept before pruning (`24h`, `7d`, `1h30m`, or `false` to disable pruning; default: `24h`). Use shorter retention to reduce storage growth on high-frequency schedules.","hasChildren":false} +{"recordType":"path","path":"cron.store","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["automation","storage"],"label":"Cron Store Path","help":"Path to the cron job store file used to persist scheduled jobs across restarts. Set an explicit path only when you need custom storage layout, backups, or mounted volumes.","hasChildren":false} +{"recordType":"path","path":"cron.webhook","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Cron Legacy Webhook (Deprecated)","help":"Deprecated legacy fallback webhook URL used only for old jobs with `notify=true`. Migrate to per-job delivery using `delivery.mode=\"webhook\"` plus `delivery.to`, and avoid relying on this global field.","hasChildren":false} +{"recordType":"path","path":"cron.webhookToken","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","automation","security"],"label":"Cron Webhook Bearer Token","help":"Bearer token attached to cron webhook POST deliveries when webhook mode is used. Prefer secret/env substitution and rotate this token regularly if shared webhook endpoints are internet-reachable.","hasChildren":true} +{"recordType":"path","path":"cron.webhookToken.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.webhookToken.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"cron.webhookToken.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"diagnostics","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"Diagnostics","help":"Diagnostics controls for targeted tracing, telemetry export, and cache inspection during debugging. Keep baseline diagnostics minimal in production and enable deeper signals only when investigating issues.","hasChildren":true} +{"recordType":"path","path":"diagnostics.cacheTrace","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["observability","storage"],"label":"Cache Trace","help":"Cache-trace logging settings for observing cache decisions and payload context in embedded runs. Enable this temporarily for debugging and disable afterward to reduce sensitive log footprint.","hasChildren":true} +{"recordType":"path","path":"diagnostics.cacheTrace.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["observability","storage"],"label":"Cache Trace Enabled","help":"Log cache trace snapshots for embedded agent runs (default: false).","hasChildren":false} +{"recordType":"path","path":"diagnostics.cacheTrace.filePath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["observability","storage"],"label":"Cache Trace File Path","help":"JSONL output path for cache trace logs (default: $OPENCLAW_STATE_DIR/logs/cache-trace.jsonl).","hasChildren":false} +{"recordType":"path","path":"diagnostics.cacheTrace.includeMessages","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["observability","storage"],"label":"Cache Trace Include Messages","help":"Include full message payloads in trace output (default: true).","hasChildren":false} +{"recordType":"path","path":"diagnostics.cacheTrace.includePrompt","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["observability","storage"],"label":"Cache Trace Include Prompt","help":"Include prompt text in trace output (default: true).","hasChildren":false} +{"recordType":"path","path":"diagnostics.cacheTrace.includeSystem","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["observability","storage"],"label":"Cache Trace Include System","help":"Include system prompt in trace output (default: true).","hasChildren":false} +{"recordType":"path","path":"diagnostics.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"Diagnostics Enabled","help":"Master toggle for diagnostics instrumentation output in logs and telemetry wiring paths. Keep enabled for normal observability, and disable only in tightly constrained environments.","hasChildren":false} +{"recordType":"path","path":"diagnostics.flags","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"Diagnostics Flags","help":"Enable targeted diagnostics logs by flag (e.g. [\"telegram.http\"]). Supports wildcards like \"telegram.*\" or \"*\".","hasChildren":true} +{"recordType":"path","path":"diagnostics.flags.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"diagnostics.otel","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"OpenTelemetry","help":"OpenTelemetry export settings for traces, metrics, and logs emitted by gateway components. Use this when integrating with centralized observability backends and distributed tracing pipelines.","hasChildren":true} +{"recordType":"path","path":"diagnostics.otel.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"OpenTelemetry Enabled","help":"Enables OpenTelemetry export pipeline for traces, metrics, and logs based on configured endpoint/protocol settings. Keep disabled unless your collector endpoint and auth are fully configured.","hasChildren":false} +{"recordType":"path","path":"diagnostics.otel.endpoint","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"OpenTelemetry Endpoint","help":"Collector endpoint URL used for OpenTelemetry export transport, including scheme and port. Use a reachable, trusted collector endpoint and monitor ingestion errors after rollout.","hasChildren":false} +{"recordType":"path","path":"diagnostics.otel.flushIntervalMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["observability","performance"],"label":"OpenTelemetry Flush Interval (ms)","help":"Interval in milliseconds for periodic telemetry flush from buffers to the collector. Increase to reduce export chatter, or lower for faster visibility during active incident response.","hasChildren":false} +{"recordType":"path","path":"diagnostics.otel.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"OpenTelemetry Headers","help":"Additional HTTP/gRPC metadata headers sent with OpenTelemetry export requests, often used for tenant auth or routing. Keep secrets in env-backed values and avoid unnecessary header sprawl.","hasChildren":true} +{"recordType":"path","path":"diagnostics.otel.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"diagnostics.otel.logs","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"OpenTelemetry Logs Enabled","help":"Enable log signal export through OpenTelemetry in addition to local logging sinks. Use this when centralized log correlation is required across services and agents.","hasChildren":false} +{"recordType":"path","path":"diagnostics.otel.metrics","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"OpenTelemetry Metrics Enabled","help":"Enable metrics signal export to the configured OpenTelemetry collector endpoint. Keep enabled for runtime health dashboards, and disable only if metric volume must be minimized.","hasChildren":false} +{"recordType":"path","path":"diagnostics.otel.protocol","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"OpenTelemetry Protocol","help":"OTel transport protocol for telemetry export: \"http/protobuf\" or \"grpc\" depending on collector support. Use the protocol your observability backend expects to avoid dropped telemetry payloads.","hasChildren":false} +{"recordType":"path","path":"diagnostics.otel.sampleRate","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"OpenTelemetry Trace Sample Rate","help":"Trace sampling rate (0-1) controlling how much trace traffic is exported to observability backends. Lower rates reduce overhead/cost, while higher rates improve debugging fidelity.","hasChildren":false} +{"recordType":"path","path":"diagnostics.otel.serviceName","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"OpenTelemetry Service Name","help":"Service name reported in telemetry resource attributes to identify this gateway instance in observability backends. Use stable names so dashboards and alerts remain consistent over deployments.","hasChildren":false} +{"recordType":"path","path":"diagnostics.otel.traces","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"OpenTelemetry Traces Enabled","help":"Enable trace signal export to the configured OpenTelemetry collector endpoint. Keep enabled when latency/debug tracing is needed, and disable if you only want metrics/logs.","hasChildren":false} +{"recordType":"path","path":"diagnostics.stuckSessionWarnMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["observability","storage"],"label":"Stuck Session Warning Threshold (ms)","help":"Age threshold in milliseconds for emitting stuck-session warnings while a session remains in processing state. Increase for long multi-tool turns to reduce false positives; decrease for faster hang detection.","hasChildren":false} +{"recordType":"path","path":"discovery","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Discovery","help":"Service discovery settings for local mDNS advertisement and optional wide-area presence signaling. Keep discovery scoped to expected networks to avoid leaking service metadata.","hasChildren":true} +{"recordType":"path","path":"discovery.mdns","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"mDNS Discovery","help":"mDNS discovery configuration group for local network advertisement and discovery behavior tuning. Keep minimal mode for routine LAN discovery unless extra metadata is required.","hasChildren":true} +{"recordType":"path","path":"discovery.mdns.mode","kind":"core","type":"string","required":false,"enumValues":["off","minimal","full"],"deprecated":false,"sensitive":false,"tags":["network"],"label":"mDNS Discovery Mode","help":"mDNS broadcast mode (\"minimal\" default, \"full\" includes cliPath/sshPort, \"off\" disables mDNS).","hasChildren":false} +{"recordType":"path","path":"discovery.wideArea","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Wide-area Discovery","help":"Wide-area discovery configuration group for exposing discovery signals beyond local-link scopes. Enable only in deployments that intentionally aggregate gateway presence across sites.","hasChildren":true} +{"recordType":"path","path":"discovery.wideArea.domain","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Wide-area Discovery Domain","help":"Optional unicast DNS-SD domain for wide-area discovery, such as openclaw.internal. Use this when you intentionally publish gateway discovery beyond local mDNS scopes.","hasChildren":false} +{"recordType":"path","path":"discovery.wideArea.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Wide-area Discovery Enabled","help":"Enables wide-area discovery signaling when your environment needs non-local gateway discovery. Keep disabled unless cross-network discovery is operationally required.","hasChildren":false} +{"recordType":"path","path":"env","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Environment","help":"Environment import and override settings used to supply runtime variables to the gateway process. Use this section to control shell-env loading and explicit variable injection behavior.","hasChildren":true} +{"recordType":"path","path":"env.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"env.shellEnv","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Shell Environment Import","help":"Shell environment import controls for loading variables from your login shell during startup. Keep this enabled when you depend on profile-defined secrets or PATH customizations.","hasChildren":true} +{"recordType":"path","path":"env.shellEnv.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Shell Environment Import Enabled","help":"Enables loading environment variables from the user shell profile during startup initialization. Keep enabled for developer machines, or disable in locked-down service environments with explicit env management.","hasChildren":false} +{"recordType":"path","path":"env.shellEnv.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Shell Environment Import Timeout (ms)","help":"Maximum time in milliseconds allowed for shell environment resolution before fallback behavior applies. Use tighter timeouts for faster startup, or increase when shell initialization is heavy.","hasChildren":false} +{"recordType":"path","path":"env.vars","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Environment Variable Overrides","help":"Explicit key/value environment variable overrides merged into runtime process environment for OpenClaw. Use this for deterministic env configuration instead of relying only on shell profile side effects.","hasChildren":true} +{"recordType":"path","path":"env.vars.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gateway","help":"Gateway runtime surface for bind mode, auth, control UI, remote transport, and operational safety controls. Keep conservative defaults unless you intentionally expose the gateway beyond trusted local interfaces.","hasChildren":true} +{"recordType":"path","path":"gateway.allowRealIpFallback","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","network","reliability"],"label":"Gateway Allow x-real-ip Fallback","help":"Enables x-real-ip fallback when x-forwarded-for is missing in proxy scenarios. Keep disabled unless your ingress stack requires this compatibility behavior.","hasChildren":false} +{"recordType":"path","path":"gateway.auth","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Auth","help":"Authentication policy for gateway HTTP/WebSocket access including mode, credentials, trusted-proxy behavior, and rate limiting. Keep auth enabled for every non-loopback deployment.","hasChildren":true} +{"recordType":"path","path":"gateway.auth.allowTailscale","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","network"],"label":"Gateway Auth Allow Tailscale Identity","help":"Allows trusted Tailscale identity paths to satisfy gateway auth checks when configured. Use this only when your tailnet identity posture is strong and operator workflows depend on it.","hasChildren":false} +{"recordType":"path","path":"gateway.auth.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Auth Mode","help":"Gateway auth mode: \"none\", \"token\", \"password\", or \"trusted-proxy\" depending on your edge architecture. Use token/password for direct exposure, and trusted-proxy only behind hardened identity-aware proxies.","hasChildren":false} +{"recordType":"path","path":"gateway.auth.password","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["access","auth","network","security"],"label":"Gateway Password","help":"Required for Tailscale funnel.","hasChildren":true} +{"recordType":"path","path":"gateway.auth.password.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.password.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.password.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.rateLimit","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network","performance"],"label":"Gateway Auth Rate Limit","help":"Login/auth attempt throttling controls to reduce credential brute-force risk at the gateway boundary. Keep enabled in exposed environments and tune thresholds to your traffic baseline.","hasChildren":true} +{"recordType":"path","path":"gateway.auth.rateLimit.exemptLoopback","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.rateLimit.lockoutMs","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.rateLimit.maxAttempts","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.rateLimit.windowMs","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.token","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["access","auth","network","security"],"label":"Gateway Token","help":"Required by default for gateway access (unless using Tailscale Serve identity); required for non-loopback binds.","hasChildren":true} +{"recordType":"path","path":"gateway.auth.token.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.token.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.token.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.trustedProxy","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Trusted Proxy Auth","help":"Trusted-proxy auth header mapping for upstream identity providers that inject user claims. Use only with known proxy CIDRs and strict header allowlists to prevent spoofed identity headers.","hasChildren":true} +{"recordType":"path","path":"gateway.auth.trustedProxy.allowUsers","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.auth.trustedProxy.allowUsers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.trustedProxy.requiredHeaders","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.auth.trustedProxy.requiredHeaders.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.auth.trustedProxy.userHeader","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.bind","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Bind Mode","help":"Network bind profile: \"auto\", \"lan\", \"loopback\", \"custom\", or \"tailnet\" to control interface exposure. Keep \"loopback\" or \"auto\" for safest local operation unless external clients must connect.","hasChildren":false} +{"recordType":"path","path":"gateway.channelHealthCheckMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network","reliability"],"label":"Gateway Channel Health Check Interval (min)","help":"Interval in minutes for automatic channel health probing and status updates. Use lower intervals for faster detection, or higher intervals to reduce periodic probe noise.","hasChildren":false} +{"recordType":"path","path":"gateway.channelMaxRestartsPerHour","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network","performance"],"label":"Gateway Channel Max Restarts Per Hour","help":"Maximum number of health-monitor-initiated channel restarts allowed within a rolling one-hour window. Once hit, further restarts are skipped until the window expires. Default: 10.","hasChildren":false} +{"recordType":"path","path":"gateway.channelStaleEventThresholdMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Channel Stale Event Threshold (min)","help":"How many minutes a connected channel can go without receiving any event before the health monitor treats it as a stale socket and triggers a restart. Default: 30.","hasChildren":false} +{"recordType":"path","path":"gateway.controlUi","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Control UI","help":"Control UI hosting settings including enablement, pathing, and browser-origin/auth hardening behavior. Keep UI exposure minimal and pair with strong auth controls before internet-facing deployments.","hasChildren":true} +{"recordType":"path","path":"gateway.controlUi.allowedOrigins","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","network"],"label":"Control UI Allowed Origins","help":"Allowed browser origins for Control UI/WebChat websocket connections (full origins only, e.g. https://control.example.com). Required for non-loopback Control UI deployments unless dangerous Host-header fallback is explicitly enabled. Setting [\"*\"] means allow any browser origin and should be avoided outside tightly controlled local testing.","hasChildren":true} +{"recordType":"path","path":"gateway.controlUi.allowedOrigins.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.controlUi.allowInsecureAuth","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced","network","security"],"label":"Insecure Control UI Auth Toggle","help":"Loosens strict browser auth checks for Control UI when you must run a non-standard setup. Keep this off unless you trust your network and proxy path, because impersonation risk is higher.","hasChildren":false} +{"recordType":"path","path":"gateway.controlUi.basePath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network","storage"],"label":"Control UI Base Path","help":"Optional URL prefix where the Control UI is served (e.g. /openclaw).","hasChildren":false} +{"recordType":"path","path":"gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced","network","security"],"label":"Dangerously Allow Host-Header Origin Fallback","help":"DANGEROUS toggle that enables Host-header based origin fallback for Control UI/WebChat websocket checks. This mode is supported when your deployment intentionally relies on Host-header origin policy; explicit gateway.controlUi.allowedOrigins remains the recommended hardened default.","hasChildren":false} +{"recordType":"path","path":"gateway.controlUi.dangerouslyDisableDeviceAuth","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced","network","security"],"label":"Dangerously Disable Control UI Device Auth","help":"Disables Control UI device identity checks and relies on token/password only. Use only for short-lived debugging on trusted networks, then turn it off immediately.","hasChildren":false} +{"recordType":"path","path":"gateway.controlUi.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Control UI Enabled","help":"Enables serving the gateway Control UI from the gateway HTTP process when true. Keep enabled for local administration, and disable when an external control surface replaces it.","hasChildren":false} +{"recordType":"path","path":"gateway.controlUi.root","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Control UI Assets Root","help":"Optional filesystem root for Control UI assets (defaults to dist/control-ui).","hasChildren":false} +{"recordType":"path","path":"gateway.customBindHost","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Custom Bind Host","help":"Explicit bind host/IP used when gateway.bind is set to custom for manual interface targeting. Use a precise address and avoid wildcard binds unless external exposure is required.","hasChildren":false} +{"recordType":"path","path":"gateway.http","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway HTTP API","help":"Gateway HTTP API configuration grouping endpoint toggles and transport-facing API exposure controls. Keep only required endpoints enabled to reduce attack surface.","hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway HTTP Endpoints","help":"HTTP endpoint feature toggles under the gateway API surface for compatibility routes and optional integrations. Enable endpoints intentionally and monitor access patterns after rollout.","hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"OpenAI Chat Completions Endpoint","help":"Enable the OpenAI-compatible `POST /v1/chat/completions` endpoint (default: false).","hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.images","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media","network"],"label":"OpenAI Chat Completions Image Limits","help":"Image fetch/validation controls for OpenAI-compatible `image_url` parts.","hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.images.allowedMimes","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","media","network"],"label":"OpenAI Chat Completions Image MIME Allowlist","help":"Allowed MIME types for `image_url` parts (case-insensitive list).","hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.images.allowedMimes.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.images.allowUrl","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","media","network"],"label":"OpenAI Chat Completions Allow Image URLs","help":"Allow server-side URL fetches for `image_url` parts (default: false; data URIs remain supported). Set this to `false` to disable URL fetching entirely.","hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.images.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","network","performance"],"label":"OpenAI Chat Completions Image Max Bytes","help":"Max bytes per fetched/decoded `image_url` image (default: 10MB).","hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.images.maxRedirects","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","network","performance","storage"],"label":"OpenAI Chat Completions Image Max Redirects","help":"Max HTTP redirects allowed when fetching `image_url` URLs (default: 3).","hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.images.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","network","performance"],"label":"OpenAI Chat Completions Image Timeout (ms)","help":"Timeout in milliseconds for `image_url` URL fetches (default: 10000).","hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.images.urlAllowlist","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","media","network"],"label":"OpenAI Chat Completions Image URL Allowlist","help":"Optional hostname allowlist for `image_url` URL fetches; supports exact hosts and `*.example.com` wildcards. Empty or omitted lists mean no hostname allowlist restriction.","hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.images.urlAllowlist.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.maxBodyBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network","performance"],"label":"OpenAI Chat Completions Max Body Bytes","help":"Max request body size in bytes for `/v1/chat/completions` (default: 20MB).","hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.maxImageParts","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","network","performance"],"label":"OpenAI Chat Completions Max Image Parts","help":"Max number of `image_url` parts accepted from the latest user message (default: 8).","hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.chatCompletions.maxTotalImageBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","network","performance"],"label":"OpenAI Chat Completions Max Total Image Bytes","help":"Max cumulative decoded bytes across all `image_url` parts in one request (default: 20MB).","hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.responses.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.files","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.allowedMimes","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.allowedMimes.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.allowUrl","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.maxRedirects","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.pdf","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.pdf.maxPages","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.pdf.maxPixels","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.pdf.minTextChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.urlAllowlist","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.responses.files.urlAllowlist.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.images","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.responses.images.allowedMimes","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.responses.images.allowedMimes.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.images.allowUrl","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.images.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.images.maxRedirects","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.images.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.images.urlAllowlist","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.http.endpoints.responses.images.urlAllowlist.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.maxBodyBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.endpoints.responses.maxUrlParts","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.http.securityHeaders","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway HTTP Security Headers","help":"Optional HTTP response security headers applied by the gateway process itself. Prefer setting these at your reverse proxy when TLS terminates there.","hasChildren":true} +{"recordType":"path","path":"gateway.http.securityHeaders.strictTransportSecurity","kind":"core","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Strict Transport Security Header","help":"Value for the Strict-Transport-Security response header. Set only on HTTPS origins that you fully control; use false to explicitly disable.","hasChildren":false} +{"recordType":"path","path":"gateway.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Mode","help":"Gateway operation mode: \"local\" runs channels and agent runtime on this host, while \"remote\" connects through remote transport. Keep \"local\" unless you intentionally run a split remote gateway topology.","hasChildren":false} +{"recordType":"path","path":"gateway.nodes","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.nodes.allowCommands","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","network"],"label":"Gateway Node Allowlist (Extra Commands)","help":"Extra node.invoke commands to allow beyond the gateway defaults (array of command strings). Enabling dangerous commands here is a security-sensitive override and is flagged by `openclaw security audit`.","hasChildren":true} +{"recordType":"path","path":"gateway.nodes.allowCommands.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.nodes.browser","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"gateway.nodes.browser.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Node Browser Mode","help":"Node browser routing (\"auto\" = pick single connected browser node, \"manual\" = require node param, \"off\" = disable).","hasChildren":false} +{"recordType":"path","path":"gateway.nodes.browser.node","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Node Browser Pin","help":"Pin browser routing to a specific node id or name (optional).","hasChildren":false} +{"recordType":"path","path":"gateway.nodes.denyCommands","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","network"],"label":"Gateway Node Denylist","help":"Node command names to block even if present in node claims or default allowlist (exact command-name matching only, e.g. `system.run`; does not inspect shell text inside that command).","hasChildren":true} +{"recordType":"path","path":"gateway.nodes.denyCommands.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.port","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Port","help":"TCP port used by the gateway listener for API, control UI, and channel-facing ingress paths. Use a dedicated port and avoid collisions with reverse proxies or local developer services.","hasChildren":false} +{"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"],"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} +{"recordType":"path","path":"gateway.reload.deferralTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network","performance","reliability"],"label":"Restart Deferral Timeout (ms)","help":"Maximum time (ms) to wait for in-flight operations to complete before forcing a SIGUSR1 restart. Default: 300000 (5 minutes). Lower values risk aborting active subagent LLM calls.","hasChildren":false} +{"recordType":"path","path":"gateway.reload.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network","reliability"],"label":"Config Reload Mode","help":"Controls how config edits are applied: \"off\" ignores live edits, \"restart\" always restarts, \"hot\" applies in-process, and \"hybrid\" tries hot then restarts if required. Keep \"hybrid\" for safest routine updates.","hasChildren":false} +{"recordType":"path","path":"gateway.remote","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Remote Gateway","help":"Remote gateway connection settings for direct or SSH transport when this instance proxies to another runtime host. Use remote mode only when split-host operation is intentionally configured.","hasChildren":true} +{"recordType":"path","path":"gateway.remote.password","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","network","security"],"label":"Remote Gateway Password","help":"Password credential used for remote gateway authentication when password mode is enabled. Keep this secret managed externally and avoid plaintext values in committed config.","hasChildren":true} +{"recordType":"path","path":"gateway.remote.password.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.remote.password.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.remote.password.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.remote.sshIdentity","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Remote Gateway SSH Identity","help":"Optional SSH identity file path (passed to ssh -i).","hasChildren":false} +{"recordType":"path","path":"gateway.remote.sshTarget","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Remote Gateway SSH Target","help":"Remote gateway over SSH (tunnels the gateway port to localhost). Format: user@host or user@host:port.","hasChildren":false} +{"recordType":"path","path":"gateway.remote.tlsFingerprint","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["auth","network","security"],"label":"Remote Gateway TLS Fingerprint","help":"Expected sha256 TLS fingerprint for the remote gateway (pin to avoid MITM).","hasChildren":false} +{"recordType":"path","path":"gateway.remote.token","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","network","security"],"label":"Remote Gateway Token","help":"Bearer token used to authenticate this client to a remote gateway in token-auth deployments. Store via secret/env substitution and rotate alongside remote gateway auth changes.","hasChildren":true} +{"recordType":"path","path":"gateway.remote.token.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.remote.token.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.remote.token.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.remote.transport","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Remote Gateway Transport","help":"Remote connection transport: \"direct\" uses configured URL connectivity, while \"ssh\" tunnels through SSH. Use SSH when you need encrypted tunnel semantics without exposing remote ports.","hasChildren":false} +{"recordType":"path","path":"gateway.remote.url","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Remote Gateway URL","help":"Remote Gateway WebSocket URL (ws:// or wss://).","hasChildren":false} +{"recordType":"path","path":"gateway.tailscale","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Tailscale","help":"Tailscale integration settings for Serve/Funnel exposure and lifecycle handling on gateway start/exit. Keep off unless your deployment intentionally relies on Tailscale ingress.","hasChildren":true} +{"recordType":"path","path":"gateway.tailscale.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Tailscale Mode","help":"Tailscale publish mode: \"off\", \"serve\", or \"funnel\" for private or public exposure paths. Use \"serve\" for tailnet-only access and \"funnel\" only when public internet reachability is required.","hasChildren":false} +{"recordType":"path","path":"gateway.tailscale.resetOnExit","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Tailscale Reset on Exit","help":"Resets Tailscale Serve/Funnel state on gateway exit to avoid stale published routes after shutdown. Keep enabled unless another controller manages publish lifecycle outside the gateway.","hasChildren":false} +{"recordType":"path","path":"gateway.tls","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway TLS","help":"TLS certificate and key settings for terminating HTTPS directly in the gateway process. Use explicit certificates in production and avoid plaintext exposure on untrusted networks.","hasChildren":true} +{"recordType":"path","path":"gateway.tls.autoGenerate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway TLS Auto-Generate Cert","help":"Auto-generates a local TLS certificate/key pair when explicit files are not configured. Use only for local/dev setups and replace with real certificates for production traffic.","hasChildren":false} +{"recordType":"path","path":"gateway.tls.caPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network","storage"],"label":"Gateway TLS CA Path","help":"Optional CA bundle path for client verification or custom trust-chain requirements at the gateway edge. Use this when private PKI or custom certificate chains are part of deployment.","hasChildren":false} +{"recordType":"path","path":"gateway.tls.certPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network","storage"],"label":"Gateway TLS Certificate Path","help":"Filesystem path to the TLS certificate file used by the gateway when TLS is enabled. Use managed certificate paths and keep renewal automation aligned with this location.","hasChildren":false} +{"recordType":"path","path":"gateway.tls.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway TLS Enabled","help":"Enables TLS termination at the gateway listener so clients connect over HTTPS/WSS directly. Keep enabled for direct internet exposure or any untrusted network boundary.","hasChildren":false} +{"recordType":"path","path":"gateway.tls.keyPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["network","storage"],"label":"Gateway TLS Key Path","help":"Filesystem path to the TLS private key file used by the gateway when TLS is enabled. Keep this key file permission-restricted and rotate per your security policy.","hasChildren":false} +{"recordType":"path","path":"gateway.tools","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Tool Exposure Policy","help":"Gateway-level tool exposure allow/deny policy that can restrict runtime tool availability independent of agent/tool profiles. Use this for coarse emergency controls and production hardening.","hasChildren":true} +{"recordType":"path","path":"gateway.tools.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","network"],"label":"Gateway Tool Allowlist","help":"Explicit gateway-level tool allowlist when you want a narrow set of tools available at runtime. Use this for locked-down environments where tool scope must be tightly controlled.","hasChildren":true} +{"recordType":"path","path":"gateway.tools.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.tools.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","network"],"label":"Gateway Tool Denylist","help":"Explicit gateway-level tool denylist to block risky tools even if lower-level policies allow them. Use deny rules for emergency response and defense-in-depth hardening.","hasChildren":true} +{"recordType":"path","path":"gateway.tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"gateway.trustedProxies","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Trusted Proxy CIDRs","help":"CIDR/IP allowlist of upstream proxies permitted to provide forwarded client identity headers. Keep this list narrow so untrusted hops cannot impersonate users.","hasChildren":true} +{"recordType":"path","path":"gateway.trustedProxies.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hooks","help":"Inbound webhook automation surface for mapping external events into wake or agent actions in OpenClaw. Keep this locked down with explicit token/session/agent controls before exposing it beyond trusted networks.","hasChildren":true} +{"recordType":"path","path":"hooks.allowedAgentIds","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Hooks Allowed Agent IDs","help":"Allowlist of agent IDs that hook mappings are allowed to target when selecting execution agents. Use this to constrain automation events to dedicated service agents and reduce blast radius if a hook token is exposed.","hasChildren":true} +{"recordType":"path","path":"hooks.allowedAgentIds.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.allowedSessionKeyPrefixes","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Hooks Allowed Session Key Prefixes","help":"Allowlist of accepted session-key prefixes for inbound hook requests when caller-provided keys are enabled. Use narrow prefixes to prevent arbitrary session-key injection.","hasChildren":true} +{"recordType":"path","path":"hooks.allowedSessionKeyPrefixes.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.allowRequestSessionKey","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Hooks Allow Request Session Key","help":"Allows callers to supply a session key in hook requests when true, enabling caller-controlled routing. Keep false unless trusted integrators explicitly need custom session threading.","hasChildren":false} +{"recordType":"path","path":"hooks.defaultSessionKey","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Hooks Default Session Key","help":"Fallback session key used for hook deliveries when a request does not provide one through allowed channels. Use a stable but scoped key to avoid mixing unrelated automation conversations.","hasChildren":false} +{"recordType":"path","path":"hooks.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hooks Enabled","help":"Enables the hooks endpoint and mapping execution pipeline for inbound webhook requests. Keep disabled unless you are actively routing external events into the gateway.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook","help":"Gmail push integration settings used for Pub/Sub notifications and optional local callback serving. Keep this scoped to dedicated Gmail automation accounts where possible.","hasChildren":true} +{"recordType":"path","path":"hooks.gmail.account","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Account","help":"Google account identifier used for Gmail watch/subscription operations in this hook integration. Use a dedicated automation mailbox account to isolate operational permissions.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.allowUnsafeExternalContent","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Gmail Hook Allow Unsafe External Content","help":"Allows less-sanitized external Gmail content to pass into processing when enabled. Keep disabled for safer defaults, and enable only for trusted mail streams with controlled transforms.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.hookUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Callback URL","help":"Public callback URL Gmail or intermediaries invoke to deliver notifications into this hook pipeline. Keep this URL protected with token validation and restricted network exposure.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.includeBody","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Include Body","help":"When true, fetch and include email body content for downstream mapping/agent processing. Keep false unless body text is required, because this increases payload size and sensitivity.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.label","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Label","help":"Optional Gmail label filter limiting which labeled messages trigger hook events. Keep filters narrow to avoid flooding automations with unrelated inbox traffic.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Gmail Hook Max Body Bytes","help":"Maximum Gmail payload bytes processed per event when includeBody is enabled. Keep conservative limits to reduce oversized message processing cost and risk.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Gmail Hook Model Override","help":"Optional model override for Gmail-triggered runs when mailbox automations should use dedicated model behavior. Keep unset to inherit agent defaults unless mailbox tasks need specialization.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.pushToken","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Gmail Hook Push Token","help":"Shared secret token required on Gmail push hook callbacks before processing notifications. Use env substitution and rotate if callback endpoints are exposed externally.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.renewEveryMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Renew Interval (min)","help":"Renewal cadence in minutes for Gmail watch subscriptions to prevent expiration. Set below provider expiration windows and monitor renew failures in logs.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.serve","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Local Server","help":"Local callback server settings block for directly receiving Gmail notifications without a separate ingress layer. Enable only when this process should terminate webhook traffic itself.","hasChildren":true} +{"recordType":"path","path":"hooks.gmail.serve.bind","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Server Bind Address","help":"Bind address for the local Gmail callback HTTP server used when serving hooks directly. Keep loopback-only unless external ingress is intentionally required.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.serve.path","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Gmail Hook Server Path","help":"HTTP path on the local Gmail callback server where push notifications are accepted. Keep this consistent with subscription configuration to avoid dropped events.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.serve.port","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Server Port","help":"Port for the local Gmail callback HTTP server when serve mode is enabled. Use a dedicated port to avoid collisions with gateway/control interfaces.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.subscription","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Subscription","help":"Pub/Sub subscription consumed by the gateway to receive Gmail change notifications from the configured topic. Keep subscription ownership clear so multiple consumers do not race unexpectedly.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.tailscale","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Tailscale","help":"Tailscale exposure configuration block for publishing Gmail callbacks through Serve/Funnel routes. Use private tailnet modes before enabling any public ingress path.","hasChildren":true} +{"recordType":"path","path":"hooks.gmail.tailscale.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Tailscale Mode","help":"Tailscale exposure mode for Gmail callbacks: \"off\", \"serve\", or \"funnel\". Use \"serve\" for private tailnet delivery and \"funnel\" only when public internet ingress is required.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.tailscale.path","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Gmail Hook Tailscale Path","help":"Path published by Tailscale Serve/Funnel for Gmail callback forwarding when enabled. Keep it aligned with Gmail webhook config so requests reach the expected handler.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.tailscale.target","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Tailscale Target","help":"Local service target forwarded by Tailscale Serve/Funnel (for example http://127.0.0.1:8787). Use explicit loopback targets to avoid ambiguous routing.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.thinking","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Thinking Override","help":"Thinking effort override for Gmail-driven agent runs: \"off\", \"minimal\", \"low\", \"medium\", or \"high\". Keep modest defaults for routine inbox automations to control cost and latency.","hasChildren":false} +{"recordType":"path","path":"hooks.gmail.topic","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gmail Hook Pub/Sub Topic","help":"Google Pub/Sub topic name used by Gmail watch to publish change notifications for this account. Ensure the topic IAM grants Gmail publish access before enabling watches.","hasChildren":false} +{"recordType":"path","path":"hooks.internal","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Internal Hooks","help":"Internal hook runtime settings for bundled/custom event handlers loaded from module paths. Use this for trusted in-process automations and keep handler loading tightly scoped.","hasChildren":true} +{"recordType":"path","path":"hooks.internal.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Internal Hooks Enabled","help":"Enables processing for internal hook handlers and configured entries in the internal hook runtime. Keep disabled unless internal hook handlers are intentionally configured.","hasChildren":false} +{"recordType":"path","path":"hooks.internal.entries","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Internal Hook Entries","help":"Configured internal hook entry records used to register concrete runtime handlers and metadata. Keep entries explicit and versioned so production behavior is auditable.","hasChildren":true} +{"recordType":"path","path":"hooks.internal.entries.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"hooks.internal.entries.*.*","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.entries.*.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.entries.*.env","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"hooks.internal.entries.*.env.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.handlers","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Internal Hook Handlers","help":"List of internal event handlers mapping event names to modules and optional exports. Keep handler definitions explicit so event-to-code routing is auditable.","hasChildren":true} +{"recordType":"path","path":"hooks.internal.handlers.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"hooks.internal.handlers.*.event","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Internal Hook Event","help":"Internal event name that triggers this handler module when emitted by the runtime. Use stable event naming conventions to avoid accidental overlap across handlers.","hasChildren":false} +{"recordType":"path","path":"hooks.internal.handlers.*.export","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Internal Hook Export","help":"Optional named export for the internal hook handler function when module default export is not used. Set this when one module ships multiple handler entrypoints.","hasChildren":false} +{"recordType":"path","path":"hooks.internal.handlers.*.module","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Internal Hook Module","help":"Safe relative module path for the internal hook handler implementation loaded at runtime. Keep module files in reviewed directories and avoid dynamic path composition.","hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Internal Hook Install Records","help":"Install metadata for internal hook modules, including source and resolved artifacts for repeatable deployments. Use this as operational provenance and avoid manual drift edits.","hasChildren":true} +{"recordType":"path","path":"hooks.internal.installs.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"hooks.internal.installs.*.hooks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"hooks.internal.installs.*.hooks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.installedAt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.installPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.integrity","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.resolvedAt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.resolvedName","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.resolvedSpec","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.resolvedVersion","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.shasum","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.sourcePath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.spec","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.installs.*.version","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.internal.load","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Internal Hook Loader","help":"Internal hook loader settings controlling where handler modules are discovered at startup. Use constrained load roots to reduce accidental module conflicts or shadowing.","hasChildren":true} +{"recordType":"path","path":"hooks.internal.load.extraDirs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Internal Hook Extra Directories","help":"Additional directories searched for internal hook modules beyond default load paths. Keep this minimal and controlled to reduce accidental module shadowing.","hasChildren":true} +{"recordType":"path","path":"hooks.internal.load.extraDirs.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.mappings","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mappings","help":"Ordered mapping rules that match inbound hook requests and choose wake or agent actions with optional delivery routing. Use specific mappings first to avoid broad pattern rules capturing everything.","hasChildren":true} +{"recordType":"path","path":"hooks.mappings.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"hooks.mappings.*.action","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Action","help":"Mapping action type: \"wake\" triggers agent wake flow, while \"agent\" sends directly to agent handling. Use \"agent\" for immediate execution and \"wake\" when heartbeat-driven processing is preferred.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.agentId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Agent ID","help":"Target agent ID for mapping execution when action routing should not use defaults. Use dedicated automation agents to isolate webhook behavior from interactive operator sessions.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.allowUnsafeExternalContent","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Hook Mapping Allow Unsafe External Content","help":"When true, mapping content may include less-sanitized external payload data in generated messages. Keep false by default and enable only for trusted sources with reviewed transform logic.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.channel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Delivery Channel","help":"Delivery channel override for mapping outputs (for example \"last\", \"telegram\", \"discord\", \"slack\", \"signal\", \"imessage\", or \"msteams\"). Keep channel overrides explicit to avoid accidental cross-channel sends.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.deliver","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Deliver Reply","help":"Controls whether mapping execution results are delivered back to a channel destination versus being processed silently. Disable delivery for background automations that should not post user-facing output.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.id","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping ID","help":"Optional stable identifier for a hook mapping entry used for auditing, troubleshooting, and targeted updates. Use unique IDs so logs and config diffs can reference mappings unambiguously.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.match","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Match","help":"Grouping object for mapping match predicates such as path and source before action routing is applied. Keep match criteria specific so unrelated webhook traffic does not trigger automations.","hasChildren":true} +{"recordType":"path","path":"hooks.mappings.*.match.path","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Hook Mapping Match Path","help":"Path match condition for a hook mapping, usually compared against the inbound request path. Use this to split automation behavior by webhook endpoint path families.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.match.source","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Match Source","help":"Source match condition for a hook mapping, typically set by trusted upstream metadata or adapter logic. Use stable source identifiers so routing remains deterministic across retries.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.messageTemplate","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Message Template","help":"Template for synthesizing structured mapping input into the final message content sent to the target action path. Keep templates deterministic so downstream parsing and behavior remain stable.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Hook Mapping Model Override","help":"Optional model override for mapping-triggered runs when automation should use a different model than agent defaults. Use this sparingly so behavior remains predictable across mapping executions.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.name","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Name","help":"Human-readable mapping display name used in diagnostics and operator-facing config UIs. Keep names concise and descriptive so routing intent is obvious during incident review.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.sessionKey","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["security","storage"],"label":"Hook Mapping Session Key","help":"Explicit session key override for mapping-delivered messages to control thread continuity. Use stable scoped keys so repeated events correlate without leaking into unrelated conversations.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.textTemplate","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Text Template","help":"Text-only fallback template used when rich payload rendering is not desired or not supported. Use this to provide a concise, consistent summary string for chat delivery surfaces.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.thinking","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Thinking Override","help":"Optional thinking-effort override for mapping-triggered runs to tune latency versus reasoning depth. Keep low or minimal for high-volume hooks unless deeper reasoning is clearly required.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Hook Mapping Timeout (sec)","help":"Maximum runtime allowed for mapping action execution before timeout handling applies. Use tighter limits for high-volume webhook sources to prevent queue pileups.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.to","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Delivery Destination","help":"Destination identifier inside the selected channel when mapping replies should route to a fixed target. Verify provider-specific destination formats before enabling production mappings.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.transform","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Transform","help":"Transform configuration block defining module/export preprocessing before mapping action handling. Use transforms only from reviewed code paths and keep behavior deterministic for repeatable automation.","hasChildren":true} +{"recordType":"path","path":"hooks.mappings.*.transform.export","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Transform Export","help":"Named export to invoke from the transform module; defaults to module default export when omitted. Set this when one file hosts multiple transform handlers.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.transform.module","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Transform Module","help":"Relative transform module path loaded from hooks.transformsDir to rewrite incoming payloads before delivery. Keep modules local, reviewed, and free of path traversal patterns.","hasChildren":false} +{"recordType":"path","path":"hooks.mappings.*.wakeMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hook Mapping Wake Mode","help":"Wake scheduling mode: \"now\" wakes immediately, while \"next-heartbeat\" defers until the next heartbeat cycle. Use deferred mode for lower-priority automations that can tolerate slight delay.","hasChildren":false} +{"recordType":"path","path":"hooks.maxBodyBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Hooks Max Body Bytes","help":"Maximum accepted webhook payload size in bytes before the request is rejected. Keep this bounded to reduce abuse risk and protect memory usage under bursty integrations.","hasChildren":false} +{"recordType":"path","path":"hooks.path","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Hooks Endpoint Path","help":"HTTP path used by the hooks endpoint (for example `/hooks`) on the gateway control server. Use a non-guessable path and combine it with token validation for defense in depth.","hasChildren":false} +{"recordType":"path","path":"hooks.presets","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Hooks Presets","help":"Named hook preset bundles applied at load time to seed standard mappings and behavior defaults. Keep preset usage explicit so operators can audit which automations are active.","hasChildren":true} +{"recordType":"path","path":"hooks.presets.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"hooks.token","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Hooks Auth Token","help":"Shared bearer token checked by hooks ingress for request authentication before mappings run. Treat holders as full-trust callers for the hook ingress surface, not as a separate non-owner role. Use environment substitution and rotate regularly when webhook endpoints are internet-accessible.","hasChildren":false} +{"recordType":"path","path":"hooks.transformsDir","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Hooks Transforms Directory","help":"Base directory for hook transform modules referenced by mapping transform.module paths. Use a controlled repo directory so dynamic imports remain reviewable and predictable.","hasChildren":false} +{"recordType":"path","path":"logging","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Logging","help":"Logging behavior controls for severity, output destinations, formatting, and sensitive-data redaction. Keep levels and redaction strict enough for production while preserving useful diagnostics.","hasChildren":true} +{"recordType":"path","path":"logging.consoleLevel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"Console Log Level","help":"Console-specific log threshold: \"silent\", \"fatal\", \"error\", \"warn\", \"info\", \"debug\", or \"trace\" for terminal output control. Use this to keep local console quieter while retaining richer file logging if needed.","hasChildren":false} +{"recordType":"path","path":"logging.consoleStyle","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"Console Log Style","help":"Console output format style: \"pretty\", \"compact\", or \"json\" based on operator and ingestion needs. Use json for machine parsing pipelines and pretty/compact for human-first terminal workflows.","hasChildren":false} +{"recordType":"path","path":"logging.file","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["observability","storage"],"label":"Log File Path","help":"Optional file path for persisted log output in addition to or instead of console logging. Use a managed writable path and align retention/rotation with your operational policy.","hasChildren":false} +{"recordType":"path","path":"logging.level","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"Log Level","help":"Primary log level threshold for runtime logger output: \"silent\", \"fatal\", \"error\", \"warn\", \"info\", \"debug\", or \"trace\". Keep \"info\" or \"warn\" for production, and use debug/trace only during investigation.","hasChildren":false} +{"recordType":"path","path":"logging.maxFileBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"logging.redactPatterns","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["observability","privacy"],"label":"Custom Redaction Patterns","help":"Additional custom redact regex patterns applied to log output before emission/storage. Use this to mask org-specific tokens and identifiers not covered by built-in redaction rules.","hasChildren":true} +{"recordType":"path","path":"logging.redactPatterns.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"logging.redactSensitive","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["observability","privacy"],"label":"Sensitive Data Redaction Mode","help":"Sensitive redaction mode: \"off\" disables built-in masking, while \"tools\" redacts sensitive tool/config payload fields. Keep \"tools\" in shared logs unless you have isolated secure log sinks.","hasChildren":false} +{"recordType":"path","path":"mcp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"MCP","help":"Global MCP server definitions managed by OpenClaw. Embedded Pi and other runtime adapters can consume these servers without storing them inside Pi-owned project settings.","hasChildren":true} +{"recordType":"path","path":"mcp.servers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"MCP Servers","help":"Named MCP server definitions. OpenClaw stores them in its own config and runtime adapters decide which transports are supported at execution time.","hasChildren":true} +{"recordType":"path","path":"mcp.servers.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"mcp.servers.*.*","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"mcp.servers.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"mcp.servers.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"mcp.servers.*.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"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.*.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} +{"recordType":"path","path":"media.ttlHours","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Media Retention TTL (hours)","help":"Optional retention window in hours for persisted inbound media cleanup across the full media tree. Leave unset to preserve legacy behavior, or set values like 24 (1 day) or 168 (7 days) when you want automatic cleanup.","hasChildren":false} +{"recordType":"path","path":"memory","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory","help":"Memory backend configuration (global).","hasChildren":true} +{"recordType":"path","path":"memory.backend","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Memory Backend","help":"Selects the global memory engine: \"builtin\" uses OpenClaw memory internals, while \"qmd\" uses the QMD sidecar pipeline. Keep \"builtin\" unless you intentionally operate QMD.","hasChildren":false} +{"recordType":"path","path":"memory.citations","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Memory Citations Mode","help":"Controls citation visibility in replies: \"auto\" shows citations when useful, \"on\" always shows them, and \"off\" hides them. Keep \"auto\" for a balanced signal-to-noise default.","hasChildren":false} +{"recordType":"path","path":"memory.qmd","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"memory.qmd.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Binary","help":"Sets the executable path for the `qmd` binary used by the QMD backend (default: resolved from PATH). Use an explicit absolute path when multiple qmd installs exist or PATH differs across environments.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.includeDefaultMemory","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Include Default Memory","help":"Automatically indexes default memory files (MEMORY.md and memory/**/*.md) into QMD collections. Keep enabled unless you want indexing controlled only through explicit custom paths.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.limits","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"memory.qmd.limits.maxInjectedChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"QMD Max Injected Chars","help":"Caps how much QMD text can be injected into one turn across all hits. Use lower values to control prompt bloat and latency; raise only when context is consistently truncated.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.limits.maxResults","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"QMD Max Results","help":"Limits how many QMD hits are returned into the agent loop for each recall request (default: 6). Increase for broader recall context, or lower to keep prompts tighter and faster.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.limits.maxSnippetChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"QMD Max Snippet Chars","help":"Caps per-result snippet length extracted from QMD hits in characters (default: 700). Lower this when prompts bloat quickly, and raise only if answers consistently miss key details.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.limits.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"QMD Search Timeout (ms)","help":"Sets per-query QMD search timeout in milliseconds (default: 4000). Increase for larger indexes or slower environments, and lower to keep request latency bounded.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.mcporter","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD MCPorter","help":"Routes QMD work through mcporter (MCP runtime) instead of spawning `qmd` for each call. Use this when cold starts are expensive on large models; keep direct process mode for simpler local setups.","hasChildren":true} +{"recordType":"path","path":"memory.qmd.mcporter.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD MCPorter Enabled","help":"Routes QMD through an mcporter daemon instead of spawning qmd per request, reducing cold-start overhead for larger models. Keep disabled unless mcporter is installed and configured.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.mcporter.serverName","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD MCPorter Server Name","help":"Names the mcporter server target used for QMD calls (default: qmd). Change only when your mcporter setup uses a custom server name for qmd mcp keep-alive.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.mcporter.startDaemon","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD MCPorter Start Daemon","help":"Automatically starts the mcporter daemon when mcporter-backed QMD mode is enabled (default: true). Keep enabled unless process lifecycle is managed externally by your service supervisor.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.paths","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Extra Paths","help":"Adds custom directories or files to include in QMD indexing, each with an optional name and glob pattern. Use this for project-specific knowledge locations that are outside default memory paths.","hasChildren":true} +{"recordType":"path","path":"memory.qmd.paths.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"memory.qmd.paths.*.name","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"memory.qmd.paths.*.path","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"memory.qmd.paths.*.pattern","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"memory.qmd.scope","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Surface Scope","help":"Defines which sessions/channels are eligible for QMD recall using session.sendPolicy-style rules. Keep default direct-only scope unless you intentionally want cross-chat memory sharing.","hasChildren":true} +{"recordType":"path","path":"memory.qmd.scope.default","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"memory.qmd.scope.rules","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"memory.qmd.scope.rules.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"memory.qmd.scope.rules.*.action","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"memory.qmd.scope.rules.*.match","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"memory.qmd.scope.rules.*.match.channel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"memory.qmd.scope.rules.*.match.chatType","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"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.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} +{"recordType":"path","path":"memory.qmd.sessions.retentionDays","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Session Retention (days)","help":"Defines how long exported session files are kept before automatic pruning, in days (default: unlimited). Set a finite value for storage hygiene or compliance retention policies.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.update","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"memory.qmd.update.commandTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"QMD Command Timeout (ms)","help":"Sets timeout for QMD maintenance commands such as collection list/add in milliseconds (default: 30000). Increase when running on slower disks or remote filesystems that delay command completion.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.update.debounceMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"QMD Update Debounce (ms)","help":"Sets the minimum delay between consecutive QMD refresh attempts in milliseconds (default: 15000). Increase this if frequent file changes cause update thrash or unnecessary background load.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.update.embedInterval","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"QMD Embed Interval","help":"Sets how often QMD recomputes embeddings (duration string, default: 60m; set 0 to disable periodic embeds). Lower intervals improve freshness but increase embedding workload and cost.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.update.embedTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"QMD Embed Timeout (ms)","help":"Sets maximum runtime for each `qmd embed` cycle in milliseconds (default: 120000). Increase for heavier embedding workloads or slower hardware, and lower to fail fast under tight SLAs.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.update.interval","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"QMD Update Interval","help":"Sets how often QMD refreshes indexes from source content (duration string, default: 5m). Shorter intervals improve freshness but increase background CPU and I/O.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.update.onBoot","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Update on Startup","help":"Runs an initial QMD update once during gateway startup (default: true). Keep enabled so recall starts from a fresh baseline; disable only when startup speed is more important than immediate freshness.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.update.updateTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"QMD Update Timeout (ms)","help":"Sets maximum runtime for each `qmd update` cycle in milliseconds (default: 120000). Raise this for larger collections; lower it when you want quicker failure detection in automation.","hasChildren":false} +{"recordType":"path","path":"memory.qmd.update.waitForBootSync","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Wait for Boot Sync","help":"Blocks startup completion until the initial boot-time QMD sync finishes (default: false). Enable when you need fully up-to-date recall before serving traffic, and keep off for faster boot.","hasChildren":false} +{"recordType":"path","path":"messages","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Messages","help":"Message formatting, acknowledgment, queueing, debounce, and status reaction behavior for inbound/outbound chat flows. Use this section when channel responsiveness or message UX needs adjustment.","hasChildren":true} +{"recordType":"path","path":"messages.ackReaction","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Ack Reaction Emoji","help":"Emoji reaction used to acknowledge inbound messages (empty disables).","hasChildren":false} +{"recordType":"path","path":"messages.ackReactionScope","kind":"core","type":"string","required":false,"enumValues":["group-mentions","group-all","direct","all","off","none"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Ack Reaction Scope","help":"When to send ack reactions (\"group-mentions\", \"group-all\", \"direct\", \"all\", \"off\", \"none\"). \"off\"/\"none\" disables ack reactions entirely.","hasChildren":false} +{"recordType":"path","path":"messages.groupChat","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Group Chat Rules","help":"Group-message handling controls including mention triggers and history window sizing. Keep mention patterns narrow so group channels do not trigger on every message.","hasChildren":true} +{"recordType":"path","path":"messages.groupChat.historyLimit","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Group History Limit","help":"Maximum number of prior group messages loaded as context per turn for group sessions. Use higher values for richer continuity, or lower values for faster and cheaper responses.","hasChildren":false} +{"recordType":"path","path":"messages.groupChat.mentionPatterns","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Group Mention Patterns","help":"Safe case-insensitive regex patterns used to detect explicit mentions/trigger phrases in group chats. Use precise patterns to reduce false positives in high-volume channels; invalid or unsafe nested-repetition patterns are ignored.","hasChildren":true} +{"recordType":"path","path":"messages.groupChat.mentionPatterns.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.inbound","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Inbound Debounce","help":"Direct inbound debounce settings used before queue/turn processing starts. Configure this for provider-specific rapid message bursts from the same sender.","hasChildren":true} +{"recordType":"path","path":"messages.inbound.byChannel","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Inbound Debounce by Channel (ms)","help":"Per-channel inbound debounce overrides keyed by provider id in milliseconds. Use this where some providers send message fragments more aggressively than others.","hasChildren":true} +{"recordType":"path","path":"messages.inbound.byChannel.*","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.inbound.debounceMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Inbound Message Debounce (ms)","help":"Debounce window (ms) for batching rapid inbound messages from the same sender (0 to disable).","hasChildren":false} +{"recordType":"path","path":"messages.messagePrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Inbound Message Prefix","help":"Prefix text prepended to inbound user messages before they are handed to the agent runtime. Use this sparingly for channel context markers and keep it stable across sessions.","hasChildren":false} +{"recordType":"path","path":"messages.queue","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Inbound Queue","help":"Inbound message queue strategy used to buffer bursts before processing turns. Tune this for busy channels where sequential processing or batching behavior matters.","hasChildren":true} +{"recordType":"path","path":"messages.queue.byChannel","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Queue Mode by Channel","help":"Per-channel queue mode overrides keyed by provider id (for example telegram, discord, slack). Use this when one channel’s traffic pattern needs different queue behavior than global defaults.","hasChildren":true} +{"recordType":"path","path":"messages.queue.byChannel.discord","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.byChannel.imessage","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.byChannel.irc","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.byChannel.mattermost","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.byChannel.msteams","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.byChannel.signal","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.byChannel.slack","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.byChannel.telegram","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.byChannel.webchat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.byChannel.whatsapp","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.cap","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Queue Capacity","help":"Maximum number of queued inbound items retained before drop policy applies. Keep caps bounded in noisy channels so memory usage remains predictable.","hasChildren":false} +{"recordType":"path","path":"messages.queue.debounceMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Queue Debounce (ms)","help":"Global queue debounce window in milliseconds before processing buffered inbound messages. Use higher values to coalesce rapid bursts, or lower values for reduced response latency.","hasChildren":false} +{"recordType":"path","path":"messages.queue.debounceMsByChannel","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Queue Debounce by Channel (ms)","help":"Per-channel debounce overrides for queue behavior keyed by provider id. Use this to tune burst handling independently for chat surfaces with different pacing.","hasChildren":true} +{"recordType":"path","path":"messages.queue.debounceMsByChannel.*","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.queue.drop","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Queue Drop Strategy","help":"Drop strategy when queue cap is exceeded: \"old\", \"new\", or \"summarize\". Use summarize when preserving intent matters, or old/new when deterministic dropping is preferred.","hasChildren":false} +{"recordType":"path","path":"messages.queue.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Queue Mode","help":"Queue behavior mode: \"steer\", \"followup\", \"collect\", \"steer-backlog\", \"steer+backlog\", \"queue\", or \"interrupt\". Keep conservative modes unless you intentionally need aggressive interruption/backlog semantics.","hasChildren":false} +{"recordType":"path","path":"messages.removeAckAfterReply","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Remove Ack Reaction After Reply","help":"Removes the acknowledgment reaction after final reply delivery when enabled. Keep enabled for cleaner UX in channels where persistent ack reactions create clutter.","hasChildren":false} +{"recordType":"path","path":"messages.responsePrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Outbound Response Prefix","help":"Prefix text prepended to outbound assistant replies before sending to channels. Use for lightweight branding/context tags and avoid long prefixes that reduce content density.","hasChildren":false} +{"recordType":"path","path":"messages.statusReactions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Status Reactions","help":"Lifecycle status reactions that update the emoji on the trigger message as the agent progresses (queued → thinking → tool → done/error).","hasChildren":true} +{"recordType":"path","path":"messages.statusReactions.emojis","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Status Reaction Emojis","help":"Override default status reaction emojis. Keys: thinking, compacting, tool, coding, web, done, error, stallSoft, stallHard. Must be valid Telegram reaction emojis.","hasChildren":true} +{"recordType":"path","path":"messages.statusReactions.emojis.coding","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.emojis.compacting","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.emojis.done","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.emojis.error","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.emojis.stallHard","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.emojis.stallSoft","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.emojis.thinking","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.emojis.tool","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.emojis.web","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable Status Reactions","help":"Enable lifecycle status reactions for Telegram. When enabled, the ack reaction becomes the initial 'queued' state and progresses through thinking, tool, done/error automatically. Default: false.","hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.timing","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Status Reaction Timing","help":"Override default timing. Keys: debounceMs (700), stallSoftMs (25000), stallHardMs (60000), doneHoldMs (1500), errorHoldMs (2500).","hasChildren":true} +{"recordType":"path","path":"messages.statusReactions.timing.debounceMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.timing.doneHoldMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.timing.errorHoldMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.timing.stallHardMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.statusReactions.timing.stallSoftMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.suppressToolErrors","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Suppress Tool Error Warnings","help":"When true, suppress ⚠️ tool-error warnings from being shown to the user. The agent already sees errors in context and can retry. Default: false.","hasChildren":false} +{"recordType":"path","path":"messages.tts","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Message Text-to-Speech","help":"Text-to-speech policy for reading agent replies aloud on supported voice or audio surfaces. Keep disabled unless voice playback is part of your operator/user workflow.","hasChildren":true} +{"recordType":"path","path":"messages.tts.auto","kind":"core","type":"string","required":false,"enumValues":["off","always","inbound","tagged"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.edge","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"messages.tts.edge.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.edge.lang","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.edge.outputFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.edge.pitch","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.edge.proxy","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.edge.rate","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.edge.saveSubtitles","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.edge.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.edge.voice","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.edge.volume","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"messages.tts.elevenlabs.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","media","security"],"hasChildren":true} +{"recordType":"path","path":"messages.tts.elevenlabs.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.applyTextNormalization","kind":"core","type":"string","required":false,"enumValues":["auto","on","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.languageCode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.modelId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.seed","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.voiceId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.voiceSettings","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"messages.tts.elevenlabs.voiceSettings.similarityBoost","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.voiceSettings.speed","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.voiceSettings.stability","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.voiceSettings.style","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.elevenlabs.voiceSettings.useSpeakerBoost","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.maxTextLength","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.microsoft","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"messages.tts.microsoft.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.microsoft.lang","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.microsoft.outputFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.microsoft.pitch","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.microsoft.proxy","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.microsoft.rate","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.microsoft.saveSubtitles","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.microsoft.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.microsoft.voice","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.microsoft.volume","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.mode","kind":"core","type":"string","required":false,"enumValues":["final","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.modelOverrides","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"messages.tts.modelOverrides.allowModelId","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.modelOverrides.allowNormalization","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.modelOverrides.allowProvider","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.modelOverrides.allowSeed","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.modelOverrides.allowText","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.modelOverrides.allowVoice","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.modelOverrides.allowVoiceSettings","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.modelOverrides.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.openai","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"messages.tts.openai.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","media","security"],"hasChildren":true} +{"recordType":"path","path":"messages.tts.openai.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.openai.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.openai.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.openai.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.openai.instructions","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.openai.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.openai.speed","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.openai.voice","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.prefsPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.summaryModel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"messages.tts.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"meta","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Metadata","help":"Metadata fields automatically maintained by OpenClaw to record write/version history for this config file. Keep these values system-managed and avoid manual edits unless debugging migration history.","hasChildren":true} +{"recordType":"path","path":"meta.lastTouchedAt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Config Last Touched At","help":"ISO timestamp of the last config write (auto-set).","hasChildren":false} +{"recordType":"path","path":"meta.lastTouchedVersion","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Config Last Touched Version","help":"Auto-set when OpenClaw writes the config.","hasChildren":false} +{"recordType":"path","path":"models","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Models","help":"Model catalog root for provider definitions, merge/replace behavior, and optional Bedrock discovery integration. Keep provider definitions explicit and validated before relying on production failover paths.","hasChildren":true} +{"recordType":"path","path":"models.bedrockDiscovery","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Bedrock Model Discovery","help":"Automatic AWS Bedrock model discovery settings used to synthesize provider model entries from account visibility. Keep discovery scoped and refresh intervals conservative to reduce API churn.","hasChildren":true} +{"recordType":"path","path":"models.bedrockDiscovery.defaultContextWindow","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Bedrock Default Context Window","help":"Fallback context-window value applied to discovered models when provider metadata lacks explicit limits. Use realistic defaults to avoid oversized prompts that exceed true provider constraints.","hasChildren":false} +{"recordType":"path","path":"models.bedrockDiscovery.defaultMaxTokens","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["auth","models","performance","security"],"label":"Bedrock Default Max Tokens","help":"Fallback max-token value applied to discovered models without explicit output token limits. Use conservative defaults to reduce truncation surprises and unexpected token spend.","hasChildren":false} +{"recordType":"path","path":"models.bedrockDiscovery.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Bedrock Discovery Enabled","help":"Enables periodic Bedrock model discovery and catalog refresh for Bedrock-backed providers. Keep disabled unless Bedrock is actively used and IAM permissions are correctly configured.","hasChildren":false} +{"recordType":"path","path":"models.bedrockDiscovery.providerFilter","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Bedrock Discovery Provider Filter","help":"Optional provider allowlist filter for Bedrock discovery so only selected providers are refreshed. Use this to limit discovery scope in multi-provider environments.","hasChildren":true} +{"recordType":"path","path":"models.bedrockDiscovery.providerFilter.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.bedrockDiscovery.refreshInterval","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["models","performance"],"label":"Bedrock Discovery Refresh Interval (s)","help":"Refresh cadence for Bedrock discovery polling in seconds to detect newly available models over time. Use longer intervals in production to reduce API cost and control-plane noise.","hasChildren":false} +{"recordType":"path","path":"models.bedrockDiscovery.region","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Bedrock Discovery Region","help":"AWS region used for Bedrock discovery calls when discovery is enabled for your deployment. Use the region where your Bedrock models are provisioned to avoid empty discovery results.","hasChildren":false} +{"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"],"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"],"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} +{"recordType":"path","path":"models.providers.*.headers.*.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.headers.*.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"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"],"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} +{"recordType":"path","path":"models.providers.*.models.*.compat.requiresAssistantAfterToolResult","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.requiresMistralToolIds","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.requiresOpenAiAnthropicToolPayload","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.requiresThinkingAsText","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.requiresToolResultName","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.supportsDeveloperRole","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.supportsReasoningEffort","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.supportsStore","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.supportsStrictMode","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.supportsTools","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.supportsUsageInStreaming","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.thinkingFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.toolCallArgumentsEncoding","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.compat.toolSchemaProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.contextWindow","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.cost","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"models.providers.*.models.*.cost.cacheRead","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.cost.cacheWrite","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.cost.input","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.cost.output","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"models.providers.*.models.*.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.input","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"models.providers.*.models.*.input.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.maxTokens","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.name","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"models.providers.*.models.*.reasoning","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"nodeHost","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Node Host","help":"Node host controls for features exposed from this gateway node to other nodes or clients. Keep defaults unless you intentionally proxy local capabilities across your node network.","hasChildren":true} +{"recordType":"path","path":"nodeHost.browserProxy","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Node Browser Proxy","help":"Groups browser-proxy settings for exposing local browser control through node routing. Enable only when remote node workflows need your local browser profiles.","hasChildren":true} +{"recordType":"path","path":"nodeHost.browserProxy.allowProfiles","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","network","storage"],"label":"Node Browser Proxy Allowed Profiles","help":"Optional allowlist of browser profile names exposed through node proxy routing. Leave empty to expose all configured profiles, or use a tight list to enforce least-privilege profile access.","hasChildren":true} +{"recordType":"path","path":"nodeHost.browserProxy.allowProfiles.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"nodeHost.browserProxy.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Node Browser Proxy Enabled","help":"Expose the local browser control server through node proxy routing so remote clients can use this host's browser capabilities. Keep disabled unless remote automation explicitly depends on it.","hasChildren":false} +{"recordType":"path","path":"plugins","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugins","help":"Plugin system controls for enabling extensions, constraining load scope, configuring entries, and tracking installs. Keep plugin policy explicit and least-privilege in production environments.","hasChildren":true} +{"recordType":"path","path":"plugins.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Allowlist","help":"Optional allowlist of plugin IDs; when set, only listed plugins are eligible to load. Use this to enforce approved extension inventories in controlled environments.","hasChildren":true} +{"recordType":"path","path":"plugins.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Denylist","help":"Optional denylist of plugin IDs that are blocked even if allowlists or paths include them. Use deny rules for emergency rollback and hard blocks on risky plugins.","hasChildren":true} +{"recordType":"path","path":"plugins.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable Plugins","help":"Enable or disable plugin/extension loading globally during startup and config reload (default: true). Keep enabled only when extension capabilities are required by your deployment.","hasChildren":false} +{"recordType":"path","path":"plugins.entries","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Entries","help":"Per-plugin settings keyed by plugin ID including enablement and plugin-specific runtime configuration payloads. Use this for scoped plugin tuning without changing global loader policy.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.*","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.*.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Config","help":"Plugin-defined configuration payload interpreted by that plugin's own schema and validation rules. Use only documented fields from the plugin to prevent ignored or invalid settings.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.*.config.*","kind":"plugin","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.*.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Enabled","help":"Per-plugin enablement override for a specific entry, applied on top of global plugin policy (restart required). Use this to stage plugin rollout gradually across environments.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.*.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.*.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.*.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.*.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.*.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.*.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.acpx","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACPX Runtime","help":"ACP runtime backend powered by acpx with configurable command path and version policy. (plugin: acpx)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.acpx.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACPX Runtime Config","help":"Plugin-defined config payload for acpx.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.acpx.config.command","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"acpx Command","help":"Optional path/command override for acpx (for example /home/user/repos/acpx/dist/cli.js). Leave unset to use plugin-local bundled acpx.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.acpx.config.cwd","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Working Directory","help":"Default cwd for ACP session operations when not set per session.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.acpx.config.expectedVersion","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Expected acpx Version","help":"Exact version to enforce (for example 0.1.16) or \"any\" to skip strict version matching.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.acpx.config.mcpServers","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"MCP Servers","help":"Named MCP server definitions to inject into ACPX-backed session bootstrap. Each entry needs a command and can include args and env.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.acpx.config.mcpServers.*","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.acpx.config.mcpServers.*.args","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.acpx.config.mcpServers.*.args.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.acpx.config.mcpServers.*.command","kind":"plugin","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.acpx.config.mcpServers.*.env","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"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.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} +{"recordType":"path","path":"plugins.entries.acpx.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable ACPX Runtime","hasChildren":false} +{"recordType":"path","path":"plugins.entries.acpx.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.acpx.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.acpx.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.acpx.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.acpx.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.acpx.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.amazon-bedrock","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/amazon-bedrock-provider","help":"OpenClaw Amazon Bedrock provider plugin (plugin: amazon-bedrock)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.amazon-bedrock.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/amazon-bedrock-provider Config","help":"Plugin-defined config payload for amazon-bedrock.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.amazon-bedrock.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/amazon-bedrock-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.amazon-bedrock.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.amazon-bedrock.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.amazon-bedrock.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.amazon-bedrock.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.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.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} +{"recordType":"path","path":"plugins.entries.anthropic.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.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.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.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.anthropic.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.bluebubbles","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/bluebubbles","help":"OpenClaw BlueBubbles channel plugin (plugin: bluebubbles)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.bluebubbles.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/bluebubbles Config","help":"Plugin-defined config payload for bluebubbles.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.bluebubbles.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/bluebubbles","hasChildren":false} +{"recordType":"path","path":"plugins.entries.bluebubbles.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.bluebubbles.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.bluebubbles.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.bluebubbles.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.bluebubbles.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.bluebubbles.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.brave","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/brave-plugin","help":"OpenClaw Brave plugin (plugin: brave)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.brave.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/brave-plugin Config","help":"Plugin-defined config payload for brave.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.brave.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.brave.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Brave Search API Key","help":"Brave Search API key (fallback: BRAVE_API_KEY env var).","hasChildren":false} +{"recordType":"path","path":"plugins.entries.brave.config.webSearch.mode","kind":"plugin","type":"string","required":false,"enumValues":["web","llm-context"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Brave Search Mode","help":"Brave Search mode: web or llm-context.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.brave.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/brave-plugin","hasChildren":false} +{"recordType":"path","path":"plugins.entries.brave.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.brave.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.brave.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.brave.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.brave.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.brave.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.byteplus","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/byteplus-provider","help":"OpenClaw BytePlus provider plugin (plugin: byteplus)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.byteplus.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/byteplus-provider Config","help":"Plugin-defined config payload for byteplus.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.byteplus.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/byteplus-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.byteplus.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.byteplus.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.byteplus.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.byteplus.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.byteplus.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.byteplus.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.chutes","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/chutes-provider","help":"OpenClaw Chutes.ai provider plugin (plugin: chutes)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.chutes.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/chutes-provider Config","help":"Plugin-defined config payload for chutes.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.chutes.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/chutes-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.chutes.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.chutes.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.chutes.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.chutes.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.chutes.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.chutes.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.cloudflare-ai-gateway","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/cloudflare-ai-gateway-provider","help":"OpenClaw Cloudflare AI Gateway provider plugin (plugin: cloudflare-ai-gateway)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.cloudflare-ai-gateway.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/cloudflare-ai-gateway-provider Config","help":"Plugin-defined config payload for cloudflare-ai-gateway.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.cloudflare-ai-gateway.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/cloudflare-ai-gateway-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.cloudflare-ai-gateway.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.cloudflare-ai-gateway.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.cloudflare-ai-gateway.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.cloudflare-ai-gateway.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.cloudflare-ai-gateway.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.cloudflare-ai-gateway.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.copilot-proxy","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/copilot-proxy","help":"OpenClaw Copilot Proxy provider plugin (plugin: copilot-proxy)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.copilot-proxy.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/copilot-proxy Config","help":"Plugin-defined config payload for copilot-proxy.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.copilot-proxy.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/copilot-proxy","hasChildren":false} +{"recordType":"path","path":"plugins.entries.copilot-proxy.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.copilot-proxy.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.copilot-proxy.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.copilot-proxy.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.copilot-proxy.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.copilot-proxy.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.device-pair","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Device Pairing","help":"Generate setup codes and approve device pairing requests. (plugin: device-pair)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.device-pair.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Device Pairing Config","help":"Plugin-defined config payload for device-pair.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.device-pair.config.publicUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gateway URL","help":"Public WebSocket URL used for /pair setup codes (ws/wss or http/https).","hasChildren":false} +{"recordType":"path","path":"plugins.entries.device-pair.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable Device Pairing","hasChildren":false} +{"recordType":"path","path":"plugins.entries.device-pair.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.device-pair.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.device-pair.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.device-pair.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.device-pair.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.device-pair.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.diagnostics-otel","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"@openclaw/diagnostics-otel","help":"OpenClaw diagnostics OpenTelemetry exporter (plugin: diagnostics-otel)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.diagnostics-otel.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"@openclaw/diagnostics-otel Config","help":"Plugin-defined config payload for diagnostics-otel.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.diagnostics-otel.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["observability"],"label":"Enable @openclaw/diagnostics-otel","hasChildren":false} +{"recordType":"path","path":"plugins.entries.diagnostics-otel.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.diagnostics-otel.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.diagnostics-otel.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.diagnostics-otel.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.diagnostics-otel.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.diagnostics-otel.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.diffs","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Diffs","help":"Read-only diff viewer and file renderer for agents. (plugin: diffs)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.diffs.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Diffs Config","help":"Plugin-defined config payload for diffs.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"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,"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,"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} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults.imageFormat","kind":"plugin","type":"string","required":false,"enumValues":["png","pdf"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults.imageMaxWidth","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults.imageQuality","kind":"plugin","type":"string","required":false,"enumValues":["standard","hq","print"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults.imageScale","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults.layout","kind":"plugin","type":"string","required":false,"enumValues":["unified","split"],"defaultValue":"unified","deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Layout","help":"Initial diff layout shown in the viewer.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults.lineSpacing","kind":"plugin","type":"number","required":false,"defaultValue":1.6,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Line Spacing","help":"Line-height multiplier applied to diff rows.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults.mode","kind":"plugin","type":"string","required":false,"enumValues":["view","image","file","both"],"defaultValue":"both","deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Output Mode","help":"Tool default when mode is omitted. Use view for canvas/gateway viewer, file for PNG/PDF, or both.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults.showLineNumbers","kind":"plugin","type":"boolean","required":false,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Show Line Numbers","help":"Show line numbers by default.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults.theme","kind":"plugin","type":"string","required":false,"enumValues":["light","dark"],"defaultValue":"dark","deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Theme","help":"Initial viewer theme.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.config.defaults.wordWrap","kind":"plugin","type":"boolean","required":false,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Word Wrap","help":"Wrap long lines by default.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.config.security","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.diffs.config.security.allowRemoteViewer","kind":"plugin","type":"boolean","required":false,"defaultValue":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Remote Viewer","help":"Allow non-loopback access to diff viewer URLs when the token path is known.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable Diffs","hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.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.diffs.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.diffs.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.diffs.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.diffs.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.diffs.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.discord","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/discord","help":"OpenClaw Discord channel plugin (plugin: discord)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.discord.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/discord Config","help":"Plugin-defined config payload for discord.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.discord.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/discord","hasChildren":false} +{"recordType":"path","path":"plugins.entries.discord.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.discord.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.discord.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.discord.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.discord.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.discord.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.elevenlabs","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/elevenlabs-speech","help":"OpenClaw ElevenLabs speech plugin (plugin: elevenlabs)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.elevenlabs.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/elevenlabs-speech Config","help":"Plugin-defined config payload for elevenlabs.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.elevenlabs.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/elevenlabs-speech","hasChildren":false} +{"recordType":"path","path":"plugins.entries.elevenlabs.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.elevenlabs.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.elevenlabs.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.elevenlabs.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.elevenlabs.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.elevenlabs.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.fal","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/fal-provider","help":"OpenClaw fal provider plugin (plugin: fal)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.fal.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/fal-provider Config","help":"Plugin-defined config payload for fal.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.fal.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/fal-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.fal.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.fal.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.fal.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.fal.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.fal.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.fal.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.feishu","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/feishu","help":"OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng) (plugin: feishu)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.feishu.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/feishu Config","help":"Plugin-defined config payload for feishu.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.feishu.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/feishu","hasChildren":false} +{"recordType":"path","path":"plugins.entries.feishu.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.feishu.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.feishu.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.feishu.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.feishu.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.feishu.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.firecrawl","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/firecrawl-plugin","help":"OpenClaw Firecrawl plugin (plugin: firecrawl)","hasChildren":true} +{"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"],"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} +{"recordType":"path","path":"plugins.entries.firecrawl.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.firecrawl.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.firecrawl.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.firecrawl.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.github-copilot","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/github-copilot-provider","help":"OpenClaw GitHub Copilot provider plugin (plugin: github-copilot)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.github-copilot.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/github-copilot-provider Config","help":"Plugin-defined config payload for github-copilot.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.github-copilot.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/github-copilot-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.github-copilot.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.github-copilot.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.github-copilot.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.github-copilot.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.github-copilot.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.github-copilot.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.google","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/google-plugin","help":"OpenClaw Google plugin (plugin: google)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.google.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/google-plugin Config","help":"Plugin-defined config payload for google.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.google.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.google.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Gemini Search API Key","help":"Gemini API key for Google Search grounding (fallback: GEMINI_API_KEY env var).","hasChildren":false} +{"recordType":"path","path":"plugins.entries.google.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Gemini Search Model","help":"Gemini model override for web search grounding.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.google.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/google-plugin","hasChildren":false} +{"recordType":"path","path":"plugins.entries.google.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.google.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.google.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.google.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.google.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.google.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.googlechat","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/googlechat","help":"OpenClaw Google Chat channel plugin (plugin: googlechat)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.googlechat.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/googlechat Config","help":"Plugin-defined config payload for googlechat.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.googlechat.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/googlechat","hasChildren":false} +{"recordType":"path","path":"plugins.entries.googlechat.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.googlechat.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.googlechat.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.googlechat.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.googlechat.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.googlechat.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.huggingface","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/huggingface-provider","help":"OpenClaw Hugging Face provider plugin (plugin: huggingface)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.huggingface.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/huggingface-provider Config","help":"Plugin-defined config payload for huggingface.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.huggingface.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/huggingface-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.huggingface.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.huggingface.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.huggingface.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.huggingface.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.huggingface.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.huggingface.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.imessage","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/imessage","help":"OpenClaw iMessage channel plugin (plugin: imessage)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.imessage.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/imessage Config","help":"Plugin-defined config payload for imessage.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.imessage.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/imessage","hasChildren":false} +{"recordType":"path","path":"plugins.entries.imessage.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.imessage.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.imessage.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.imessage.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.imessage.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.imessage.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.irc","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/irc","help":"OpenClaw IRC channel plugin (plugin: irc)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.irc.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/irc Config","help":"Plugin-defined config payload for irc.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.irc.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/irc","hasChildren":false} +{"recordType":"path","path":"plugins.entries.irc.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.irc.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.irc.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.irc.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.irc.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.irc.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.kilocode","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/kilocode-provider","help":"OpenClaw Kilo Gateway provider plugin (plugin: kilocode)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.kilocode.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/kilocode-provider Config","help":"Plugin-defined config payload for kilocode.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.kilocode.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/kilocode-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.kilocode.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.kilocode.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.kilocode.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.kilocode.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.kilocode.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.kilocode.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.kimi","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/kimi-provider","help":"OpenClaw Kimi provider plugin (plugin: kimi)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.kimi.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/kimi-provider Config","help":"Plugin-defined config payload for kimi.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.kimi.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/kimi-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.kimi.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.kimi.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.kimi.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.kimi.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.kimi.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.kimi.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.line","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/line","help":"OpenClaw LINE channel plugin (plugin: line)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.line.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/line Config","help":"Plugin-defined config payload for line.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.line.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/line","hasChildren":false} +{"recordType":"path","path":"plugins.entries.line.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.line.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.line.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.line.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.line.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.line.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.llm-task","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"LLM Task","help":"Generic JSON-only LLM tool for structured tasks callable from workflows. (plugin: llm-task)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.llm-task.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"LLM Task Config","help":"Plugin-defined config payload for llm-task.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.llm-task.config.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.llm-task.config.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.llm-task.config.defaultAuthProfileId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.llm-task.config.defaultModel","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.llm-task.config.defaultProvider","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.llm-task.config.maxTokens","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.llm-task.config.timeoutMs","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.llm-task.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable LLM Task","hasChildren":false} +{"recordType":"path","path":"plugins.entries.llm-task.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.llm-task.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.llm-task.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.llm-task.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.llm-task.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.llm-task.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.lobster","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Lobster","help":"Typed workflow tool with resumable approvals. (plugin: lobster)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.lobster.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Lobster Config","help":"Plugin-defined config payload for lobster.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.lobster.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable Lobster","hasChildren":false} +{"recordType":"path","path":"plugins.entries.lobster.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.lobster.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.lobster.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.lobster.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.lobster.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.lobster.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.matrix","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/matrix","help":"OpenClaw Matrix channel plugin (plugin: matrix)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.matrix.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/matrix Config","help":"Plugin-defined config payload for matrix.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.matrix.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/matrix","hasChildren":false} +{"recordType":"path","path":"plugins.entries.matrix.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.matrix.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.matrix.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.matrix.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.matrix.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.matrix.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.mattermost","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/mattermost","help":"OpenClaw Mattermost channel plugin (plugin: mattermost)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.mattermost.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/mattermost Config","help":"Plugin-defined config payload for mattermost.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.mattermost.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/mattermost","hasChildren":false} +{"recordType":"path","path":"plugins.entries.mattermost.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.mattermost.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.mattermost.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.mattermost.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.mattermost.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.mattermost.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.memory-core","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/memory-core","help":"OpenClaw core memory search plugin (plugin: memory-core)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.memory-core.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/memory-core Config","help":"Plugin-defined config payload for memory-core.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.memory-core.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/memory-core","hasChildren":false} +{"recordType":"path","path":"plugins.entries.memory-core.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.memory-core.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.memory-core.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.memory-core.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.memory-core.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.memory-core.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.memory-lancedb","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"@openclaw/memory-lancedb","help":"OpenClaw LanceDB-backed long-term memory plugin with auto-recall/capture (plugin: memory-lancedb)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.memory-lancedb.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"@openclaw/memory-lancedb Config","help":"Plugin-defined config payload for memory-lancedb.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.memory-lancedb.config.autoCapture","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Auto-Capture","help":"Automatically capture important information from conversations","hasChildren":false} +{"recordType":"path","path":"plugins.entries.memory-lancedb.config.autoRecall","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Auto-Recall","help":"Automatically inject relevant memories into context","hasChildren":false} +{"recordType":"path","path":"plugins.entries.memory-lancedb.config.captureMaxChars","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","performance","storage"],"label":"Capture Max Chars","help":"Maximum message length eligible for auto-capture","hasChildren":false} +{"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"],"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} +{"recordType":"path","path":"plugins.entries.memory-lancedb.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.memory-lancedb.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.memory-lancedb.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.memory-lancedb.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.memory-lancedb.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.memory-lancedb.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.microsoft","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/microsoft-speech","help":"OpenClaw Microsoft speech plugin (plugin: microsoft)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.microsoft.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/microsoft-speech Config","help":"Plugin-defined config payload for microsoft.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.microsoft.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/microsoft-speech","hasChildren":false} +{"recordType":"path","path":"plugins.entries.microsoft.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.microsoft.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.microsoft.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.microsoft.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.microsoft.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.microsoft.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.minimax","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"@openclaw/minimax-provider","help":"OpenClaw MiniMax provider and OAuth plugin (plugin: minimax)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.minimax.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"@openclaw/minimax-provider Config","help":"Plugin-defined config payload for minimax.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.minimax.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Enable @openclaw/minimax-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.minimax.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.minimax.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.minimax.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.minimax.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.minimax.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.minimax.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.mistral","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/mistral-provider","help":"OpenClaw Mistral provider plugin (plugin: mistral)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.mistral.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/mistral-provider Config","help":"Plugin-defined config payload for mistral.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.mistral.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/mistral-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.mistral.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.mistral.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.mistral.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.mistral.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.mistral.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.mistral.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.modelstudio","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/modelstudio-provider","help":"OpenClaw Model Studio provider plugin (plugin: modelstudio)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.modelstudio.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/modelstudio-provider Config","help":"Plugin-defined config payload for modelstudio.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.modelstudio.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/modelstudio-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.modelstudio.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.modelstudio.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.modelstudio.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.modelstudio.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.modelstudio.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.modelstudio.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.moonshot","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/moonshot-provider","help":"OpenClaw Moonshot provider plugin (plugin: moonshot)","hasChildren":true} +{"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"],"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} +{"recordType":"path","path":"plugins.entries.moonshot.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.moonshot.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.moonshot.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.moonshot.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.moonshot.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.msteams","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/msteams","help":"OpenClaw Microsoft Teams channel plugin (plugin: msteams)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.msteams.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/msteams Config","help":"Plugin-defined config payload for msteams.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.msteams.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/msteams","hasChildren":false} +{"recordType":"path","path":"plugins.entries.msteams.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.msteams.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.msteams.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.msteams.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.msteams.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.msteams.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.nextcloud-talk","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/nextcloud-talk","help":"OpenClaw Nextcloud Talk channel plugin (plugin: nextcloud-talk)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.nextcloud-talk.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/nextcloud-talk Config","help":"Plugin-defined config payload for nextcloud-talk.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.nextcloud-talk.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/nextcloud-talk","hasChildren":false} +{"recordType":"path","path":"plugins.entries.nextcloud-talk.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.nextcloud-talk.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.nextcloud-talk.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.nextcloud-talk.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.nextcloud-talk.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.nextcloud-talk.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.nostr","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/nostr","help":"OpenClaw Nostr channel plugin for NIP-04 encrypted DMs (plugin: nostr)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.nostr.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/nostr Config","help":"Plugin-defined config payload for nostr.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.nostr.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/nostr","hasChildren":false} +{"recordType":"path","path":"plugins.entries.nostr.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.nostr.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.nostr.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.nostr.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.nostr.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.nostr.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.nvidia","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/nvidia-provider","help":"OpenClaw NVIDIA provider plugin (plugin: nvidia)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.nvidia.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/nvidia-provider Config","help":"Plugin-defined config payload for nvidia.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.nvidia.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/nvidia-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.nvidia.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.nvidia.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.nvidia.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.nvidia.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.nvidia.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.nvidia.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.ollama","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/ollama-provider","help":"OpenClaw Ollama provider plugin (plugin: ollama)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.ollama.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/ollama-provider Config","help":"Plugin-defined config payload for ollama.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.ollama.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/ollama-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.ollama.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.ollama.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.ollama.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.ollama.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.ollama.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.ollama.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.open-prose","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"OpenProse","help":"OpenProse VM skill pack with a /prose slash command. (plugin: open-prose)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.open-prose.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"OpenProse Config","help":"Plugin-defined config payload for open-prose.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.open-prose.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable OpenProse","hasChildren":false} +{"recordType":"path","path":"plugins.entries.open-prose.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.open-prose.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.open-prose.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.open-prose.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.open-prose.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.open-prose.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.openai","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/openai-provider","help":"OpenClaw OpenAI provider plugins (plugin: openai)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.openai.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/openai-provider Config","help":"Plugin-defined config payload for openai.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openai.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/openai-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openai.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.openai.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.openai.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.openai.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.openai.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.openai.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.opencode","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/opencode-provider","help":"OpenClaw OpenCode Zen provider plugin (plugin: opencode)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.opencode-go","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/opencode-go-provider","help":"OpenClaw OpenCode Go provider plugin (plugin: opencode-go)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.opencode-go.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/opencode-go-provider Config","help":"Plugin-defined config payload for opencode-go.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.opencode-go.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/opencode-go-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.opencode-go.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.opencode-go.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.opencode-go.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.opencode-go.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.opencode-go.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.opencode-go.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.opencode.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/opencode-provider Config","help":"Plugin-defined config payload for opencode.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.opencode.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/opencode-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.opencode.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.opencode.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.opencode.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.opencode.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.opencode.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.opencode.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.openrouter","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/openrouter-provider","help":"OpenClaw OpenRouter provider plugin (plugin: openrouter)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.openrouter.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/openrouter-provider Config","help":"Plugin-defined config payload for openrouter.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openrouter.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/openrouter-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openrouter.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.openrouter.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.openrouter.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.openrouter.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.openrouter.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.openrouter.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.openshell","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"OpenShell Sandbox","help":"Sandbox backend powered by OpenShell with mirrored local workspaces and SSH-based command execution. (plugin: openshell)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.openshell.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"OpenShell Sandbox Config","help":"Plugin-defined config payload for openshell.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.openshell.config.autoProviders","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Auto-create Providers","help":"When enabled, pass --auto-providers during sandbox create.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.config.command","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"OpenShell Command","help":"Path or command name for the openshell CLI.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.config.from","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Sandbox Source","help":"OpenShell sandbox source for first-time create. Defaults to openclaw.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.config.gateway","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gateway Name","help":"Optional OpenShell gateway name passed as --gateway.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.config.gatewayEndpoint","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gateway Endpoint","help":"Optional OpenShell gateway endpoint passed as --gateway-endpoint.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.config.gpu","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"GPU","help":"Request GPU resources when creating the sandbox.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.config.policy","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Policy File","help":"Optional path to a custom OpenShell sandbox policy YAML.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.config.providers","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Providers","help":"Provider names to attach when a sandbox is created.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.openshell.config.providers.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.config.remoteAgentWorkspaceDir","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Remote Agent Dir","help":"Mirror path for the real agent workspace when workspaceAccess is read-only.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.config.remoteWorkspaceDir","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Remote Workspace Dir","help":"Primary writable workspace inside the OpenShell sandbox.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.config.timeoutSeconds","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","performance"],"label":"Command Timeout Seconds","help":"Timeout for openshell CLI operations such as create/upload/download.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable OpenShell Sandbox","hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.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.openshell.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.openshell.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.openshell.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.openshell.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.openshell.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.perplexity","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/perplexity-plugin","help":"OpenClaw Perplexity plugin (plugin: perplexity)","hasChildren":true} +{"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"],"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} +{"recordType":"path","path":"plugins.entries.perplexity.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.perplexity.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.perplexity.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.perplexity.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.perplexity.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.phone-control","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Phone Control","help":"Arm/disarm high-risk phone node commands (camera/screen/writes) with an optional auto-expiry. (plugin: phone-control)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.phone-control.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Phone Control Config","help":"Plugin-defined config payload for phone-control.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.phone-control.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable Phone Control","hasChildren":false} +{"recordType":"path","path":"plugins.entries.phone-control.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.phone-control.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.phone-control.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.phone-control.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.phone-control.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.phone-control.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.qianfan","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/qianfan-provider","help":"OpenClaw Qianfan provider plugin (plugin: qianfan)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.qianfan.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/qianfan-provider Config","help":"Plugin-defined config payload for qianfan.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.qianfan.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/qianfan-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.qianfan.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.qianfan.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.qianfan.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.qianfan.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.qianfan.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.qianfan.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.qwen-portal-auth","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"qwen-portal-auth","help":"Plugin entry for qwen-portal-auth.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.qwen-portal-auth.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"qwen-portal-auth Config","help":"Plugin-defined config payload for qwen-portal-auth.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.qwen-portal-auth.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable qwen-portal-auth","hasChildren":false} +{"recordType":"path","path":"plugins.entries.qwen-portal-auth.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.qwen-portal-auth.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.qwen-portal-auth.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.qwen-portal-auth.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.qwen-portal-auth.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.qwen-portal-auth.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.sglang","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/sglang-provider","help":"OpenClaw SGLang provider plugin (plugin: sglang)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.sglang.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/sglang-provider Config","help":"Plugin-defined config payload for sglang.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.sglang.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/sglang-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.sglang.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.sglang.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.sglang.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.sglang.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.sglang.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.sglang.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.signal","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/signal","help":"OpenClaw Signal channel plugin (plugin: signal)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.signal.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/signal Config","help":"Plugin-defined config payload for signal.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.signal.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/signal","hasChildren":false} +{"recordType":"path","path":"plugins.entries.signal.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.signal.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.signal.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.signal.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.signal.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.signal.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.slack","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/slack","help":"OpenClaw Slack channel plugin (plugin: slack)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.slack.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/slack Config","help":"Plugin-defined config payload for slack.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.slack.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/slack","hasChildren":false} +{"recordType":"path","path":"plugins.entries.slack.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.slack.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.slack.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.slack.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.slack.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.slack.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.synology-chat","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/synology-chat","help":"Synology Chat channel plugin for OpenClaw (plugin: synology-chat)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.synology-chat.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/synology-chat Config","help":"Plugin-defined config payload for synology-chat.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.synology-chat.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/synology-chat","hasChildren":false} +{"recordType":"path","path":"plugins.entries.synology-chat.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.synology-chat.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.synology-chat.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.synology-chat.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.synology-chat.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.synology-chat.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.synthetic","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/synthetic-provider","help":"OpenClaw Synthetic provider plugin (plugin: synthetic)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.synthetic.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/synthetic-provider Config","help":"Plugin-defined config payload for synthetic.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.synthetic.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/synthetic-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.synthetic.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.synthetic.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.synthetic.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.synthetic.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.synthetic.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.synthetic.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.talk-voice","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Talk Voice","help":"Manage Talk voice selection (list/set). (plugin: talk-voice)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.talk-voice.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Talk Voice Config","help":"Plugin-defined config payload for talk-voice.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.talk-voice.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable Talk Voice","hasChildren":false} +{"recordType":"path","path":"plugins.entries.talk-voice.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.talk-voice.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.talk-voice.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.talk-voice.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.talk-voice.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.talk-voice.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.telegram","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/telegram","help":"OpenClaw Telegram channel plugin (plugin: telegram)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.telegram.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/telegram Config","help":"Plugin-defined config payload for telegram.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.telegram.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/telegram","hasChildren":false} +{"recordType":"path","path":"plugins.entries.telegram.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.telegram.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.telegram.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.telegram.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.telegram.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.telegram.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.thread-ownership","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Thread Ownership","help":"Prevents multiple agents from responding in the same Slack thread. Uses HTTP calls to the slack-forwarder ownership API. (plugin: thread-ownership)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.thread-ownership.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Thread Ownership Config","help":"Plugin-defined config payload for thread-ownership.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.thread-ownership.config.abTestChannels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"A/B Test Channels","help":"Slack channel IDs where thread ownership is enforced","hasChildren":true} +{"recordType":"path","path":"plugins.entries.thread-ownership.config.abTestChannels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.thread-ownership.config.forwarderUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Forwarder URL","help":"Base URL of the slack-forwarder ownership API (default: http://slack-forwarder:8750)","hasChildren":false} +{"recordType":"path","path":"plugins.entries.thread-ownership.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Enable Thread Ownership","hasChildren":false} +{"recordType":"path","path":"plugins.entries.thread-ownership.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.thread-ownership.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.thread-ownership.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.thread-ownership.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.thread-ownership.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.thread-ownership.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.tlon","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/tlon","help":"OpenClaw Tlon/Urbit channel plugin (plugin: tlon)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.tlon.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/tlon Config","help":"Plugin-defined config payload for tlon.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.tlon.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/tlon","hasChildren":false} +{"recordType":"path","path":"plugins.entries.tlon.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.tlon.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.tlon.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.tlon.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.tlon.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.tlon.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.together","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/together-provider","help":"OpenClaw Together provider plugin (plugin: together)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.together.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/together-provider Config","help":"Plugin-defined config payload for together.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.together.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/together-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.together.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.together.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.together.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.together.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.together.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.together.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.twitch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/twitch","help":"OpenClaw Twitch channel plugin (plugin: twitch)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.twitch.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/twitch Config","help":"Plugin-defined config payload for twitch.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.twitch.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/twitch","hasChildren":false} +{"recordType":"path","path":"plugins.entries.twitch.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.twitch.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.twitch.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.twitch.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.twitch.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.twitch.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.venice","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/venice-provider","help":"OpenClaw Venice provider plugin (plugin: venice)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.venice.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/venice-provider Config","help":"Plugin-defined config payload for venice.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.venice.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/venice-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.venice.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.venice.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.venice.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.venice.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.venice.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.venice.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.vercel-ai-gateway","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/vercel-ai-gateway-provider","help":"OpenClaw Vercel AI Gateway provider plugin (plugin: vercel-ai-gateway)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.vercel-ai-gateway.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/vercel-ai-gateway-provider Config","help":"Plugin-defined config payload for vercel-ai-gateway.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.vercel-ai-gateway.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/vercel-ai-gateway-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.vercel-ai-gateway.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.vercel-ai-gateway.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.vercel-ai-gateway.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.vercel-ai-gateway.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.vercel-ai-gateway.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.vercel-ai-gateway.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.vllm","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/vllm-provider","help":"OpenClaw vLLM provider plugin (plugin: vllm)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.vllm.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/vllm-provider Config","help":"Plugin-defined config payload for vllm.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.vllm.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/vllm-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.vllm.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.vllm.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.vllm.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.vllm.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.vllm.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.vllm.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.voice-call","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/voice-call","help":"OpenClaw voice-call plugin (plugin: voice-call)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/voice-call Config","help":"Plugin-defined config payload for voice-call.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.allowFrom","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Inbound Allowlist","hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.allowFrom.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.fromNumber","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"From Number","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.inboundGreeting","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Inbound Greeting","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.inboundPolicy","kind":"plugin","type":"string","required":false,"enumValues":["disabled","allowlist","pairing","open"],"deprecated":false,"sensitive":false,"tags":["access"],"label":"Inbound Policy","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.maxConcurrentCalls","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.maxDurationSeconds","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.outbound","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.outbound.defaultMode","kind":"plugin","type":"string","required":false,"enumValues":["notify","conversation"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Call Mode","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.outbound.notifyHangupDelaySec","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Notify Hangup Delay (sec)","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.plivo","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.plivo.authId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.plivo.authToken","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.provider","kind":"plugin","type":"string","required":false,"enumValues":["telnyx","twilio","plivo","mock"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Provider","help":"Use twilio, telnyx, or mock for dev/no-network.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.publicUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Public Webhook URL","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.responseModel","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Response Model","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.responseSystemPrompt","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Response System Prompt","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.responseTimeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","performance"],"label":"Response Timeout (ms)","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.ringTimeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.serve","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.serve.bind","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Webhook Bind","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.serve.path","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Webhook Path","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.serve.port","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Webhook Port","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.silenceTimeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.skipSignatureVerification","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Skip Signature Verification","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.staleCallReaperSeconds","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.store","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Call Log Store Path","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable Streaming","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.maxConnections","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.maxPendingConnections","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.maxPendingConnectionsPerIp","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.openaiApiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["advanced","auth","security"],"label":"OpenAI Realtime API Key","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.preStartTimeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.silenceDurationMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.streamPath","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Media Stream Path","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.sttModel","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"Realtime STT Model","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.sttProvider","kind":"plugin","type":"string","required":false,"enumValues":["openai-realtime"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.streaming.vadThreshold","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.stt","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.stt.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.stt.provider","kind":"plugin","type":"string","required":false,"enumValues":["openai"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.tailscale","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.tailscale.mode","kind":"plugin","type":"string","required":false,"enumValues":["off","serve","funnel"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Tailscale Mode","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.tailscale.path","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Tailscale Path","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.telnyx","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.telnyx.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Telnyx API Key","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.telnyx.connectionId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Telnyx Connection ID","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.telnyx.publicKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["security"],"label":"Telnyx Public Key","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.toNumber","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default To Number","hasChildren":false} +{"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} +{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.allowModelId","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.allowNormalization","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.allowProvider","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.allowSeed","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.allowText","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"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.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} +{"recordType":"path","path":"plugins.entries.voice-call.config.tunnel.allowNgrokFreeTierLoopbackBypass","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced"],"label":"Allow ngrok Free Tier (Loopback Bypass)","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.tunnel.ngrokAuthToken","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["advanced","auth","security"],"label":"ngrok Auth Token","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.tunnel.ngrokDomain","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ngrok Domain","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.tunnel.provider","kind":"plugin","type":"string","required":false,"enumValues":["none","ngrok","tailscale-serve","tailscale-funnel"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Tunnel Provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.twilio","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.twilio.accountSid","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Twilio Account SID","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.twilio.authToken","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Twilio Auth Token","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.webhookSecurity","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.webhookSecurity.allowedHosts","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.webhookSecurity.allowedHosts.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.webhookSecurity.trustedProxyIPs","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.voice-call.config.webhookSecurity.trustedProxyIPs.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.config.webhookSecurity.trustForwardingHeaders","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/voice-call","hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.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.voice-call.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.voice-call.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.voice-call.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.voice-call.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.voice-call.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.volcengine","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/volcengine-provider","help":"OpenClaw Volcengine provider plugin (plugin: volcengine)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.volcengine.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/volcengine-provider Config","help":"Plugin-defined config payload for volcengine.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.volcengine.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/volcengine-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.volcengine.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.volcengine.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.volcengine.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.volcengine.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.volcengine.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.volcengine.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.whatsapp","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/whatsapp","help":"OpenClaw WhatsApp channel plugin (plugin: whatsapp)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.whatsapp.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/whatsapp Config","help":"Plugin-defined config payload for whatsapp.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.whatsapp.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/whatsapp","hasChildren":false} +{"recordType":"path","path":"plugins.entries.whatsapp.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.whatsapp.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.whatsapp.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.whatsapp.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.whatsapp.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.whatsapp.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.xai","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/xai-plugin","help":"OpenClaw xAI plugin (plugin: xai)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.xai.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/xai-plugin Config","help":"Plugin-defined config payload for xai.","hasChildren":true} +{"recordType":"path","path":"plugins.entries.xai.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"plugins.entries.xai.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Grok Search API Key","help":"xAI API key for Grok web search (fallback: XAI_API_KEY env var).","hasChildren":false} +{"recordType":"path","path":"plugins.entries.xai.config.webSearch.inlineCitations","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Inline Citations","help":"Include inline markdown citations in Grok responses.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.xai.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Grok Search Model","help":"Grok model override for web search.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.xai.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/xai-plugin","hasChildren":false} +{"recordType":"path","path":"plugins.entries.xai.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.xai.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.xai.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.xai.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.xai.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.xai.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.xiaomi","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/xiaomi-provider","help":"OpenClaw Xiaomi provider plugin (plugin: xiaomi)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.xiaomi.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/xiaomi-provider Config","help":"Plugin-defined config payload for xiaomi.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.xiaomi.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/xiaomi-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.xiaomi.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.xiaomi.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.xiaomi.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.xiaomi.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.xiaomi.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.xiaomi.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.zai","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/zai-provider","help":"OpenClaw Z.AI provider plugin (plugin: zai)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.zai.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/zai-provider Config","help":"Plugin-defined config payload for zai.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.zai.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/zai-provider","hasChildren":false} +{"recordType":"path","path":"plugins.entries.zai.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.zai.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.zai.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.zai.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.zai.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.zai.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.zalo","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/zalo","help":"OpenClaw Zalo channel plugin (plugin: zalo)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.zalo.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/zalo Config","help":"Plugin-defined config payload for zalo.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.zalo.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/zalo","hasChildren":false} +{"recordType":"path","path":"plugins.entries.zalo.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.zalo.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.zalo.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.zalo.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.zalo.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.zalo.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.zalouser","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/zalouser","help":"OpenClaw Zalo Personal Account plugin via native zca-js integration (plugin: zalouser)","hasChildren":true} +{"recordType":"path","path":"plugins.entries.zalouser.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/zalouser Config","help":"Plugin-defined config payload for zalouser.","hasChildren":false} +{"recordType":"path","path":"plugins.entries.zalouser.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/zalouser","hasChildren":false} +{"recordType":"path","path":"plugins.entries.zalouser.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.zalouser.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.zalouser.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.zalouser.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.zalouser.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.entries.zalouser.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.installs","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Install Records","help":"CLI-managed install metadata (used by `openclaw plugins update` to locate install sources).","hasChildren":true} +{"recordType":"path","path":"plugins.installs.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"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 (usually ~/.openclaw/extensions/).","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} +{"recordType":"path","path":"plugins.installs.*.marketplaceSource","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Marketplace Source","help":"Original marketplace source used to resolve the install (for example a repo path or Git URL).","hasChildren":false} +{"recordType":"path","path":"plugins.installs.*.resolvedAt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Resolution Time","help":"ISO timestamp when npm package metadata was last resolved for this install record.","hasChildren":false} +{"recordType":"path","path":"plugins.installs.*.resolvedName","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Resolved Package Name","help":"Resolved npm package name from the fetched artifact.","hasChildren":false} +{"recordType":"path","path":"plugins.installs.*.resolvedSpec","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Resolved Package Spec","help":"Resolved exact npm spec (@) from the fetched artifact.","hasChildren":false} +{"recordType":"path","path":"plugins.installs.*.resolvedVersion","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Resolved Package Version","help":"Resolved npm package version from the fetched artifact (useful for non-pinned specs).","hasChildren":false} +{"recordType":"path","path":"plugins.installs.*.shasum","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Resolved Shasum","help":"Resolved npm dist shasum for the fetched artifact (if reported by npm).","hasChildren":false} +{"recordType":"path","path":"plugins.installs.*.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Install Source","help":"Install source (\"npm\", \"archive\", or \"path\").","hasChildren":false} +{"recordType":"path","path":"plugins.installs.*.sourcePath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Plugin Install Source Path","help":"Original archive/path used for install (if any).","hasChildren":false} +{"recordType":"path","path":"plugins.installs.*.spec","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Install Spec","help":"Original npm spec used for install (if source is npm).","hasChildren":false} +{"recordType":"path","path":"plugins.installs.*.version","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Install Version","help":"Version recorded at install time (if available).","hasChildren":false} +{"recordType":"path","path":"plugins.load","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Loader","help":"Plugin loader configuration group for specifying filesystem paths where plugins are discovered. Keep load paths explicit and reviewed to avoid accidental untrusted extension loading.","hasChildren":true} +{"recordType":"path","path":"plugins.load.paths","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Plugin Load Paths","help":"Additional plugin files or directories scanned by the loader beyond built-in defaults. Use dedicated extension directories and avoid broad paths with unrelated executable content.","hasChildren":true} +{"recordType":"path","path":"plugins.load.paths.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"plugins.slots","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Slots","help":"Selects which plugins own exclusive runtime slots such as memory so only one plugin provides that capability. Use explicit slot ownership to avoid overlapping providers with conflicting behavior.","hasChildren":true} +{"recordType":"path","path":"plugins.slots.contextEngine","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Context Engine Plugin","help":"Selects the active context engine plugin by id so one plugin provides context orchestration behavior.","hasChildren":false} +{"recordType":"path","path":"plugins.slots.memory","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Plugin","help":"Select the active memory plugin by id, or \"none\" to disable memory plugins.","hasChildren":false} +{"recordType":"path","path":"secrets","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"secrets.defaults","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"secrets.defaults.env","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.defaults.exec","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.defaults.file","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"secrets.providers.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"secrets.providers.*.allowInsecurePath","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.allowlist","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"secrets.providers.*.allowlist.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.allowSymlinkCommand","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"secrets.providers.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.command","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.env","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"secrets.providers.*.env.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.jsonOnly","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.maxOutputBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.noOutputTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.passEnv","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"secrets.providers.*.passEnv.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.path","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.providers.*.trustedDirs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"secrets.providers.*.trustedDirs.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.resolution","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"secrets.resolution.maxBatchBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.resolution.maxProviderConcurrency","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"secrets.resolution.maxRefsPerProvider","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session","help":"Global session routing, reset, delivery policy, and maintenance controls for conversation history behavior. Keep defaults unless you need stricter isolation, retention, or delivery constraints.","hasChildren":true} +{"recordType":"path","path":"session.agentToAgent","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Agent-to-Agent","help":"Groups controls for inter-agent session exchanges, including loop prevention limits on reply chaining. Keep defaults unless you run advanced agent-to-agent automation with strict turn caps.","hasChildren":true} +{"recordType":"path","path":"session.agentToAgent.maxPingPongTurns","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Agent-to-Agent Ping-Pong Turns","help":"Max reply-back turns between requester and target agents during agent-to-agent exchanges (0-5). Use lower values to hard-limit chatter loops and preserve predictable run completion.","hasChildren":false} +{"recordType":"path","path":"session.dmScope","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"DM Session Scope","help":"DM session scoping: \"main\" keeps continuity, while \"per-peer\", \"per-channel-peer\", and \"per-account-channel-peer\" increase isolation. Use isolated modes for shared inboxes or multi-account deployments.","hasChildren":false} +{"recordType":"path","path":"session.identityLinks","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Identity Links","help":"Maps canonical identities to provider-prefixed peer IDs so equivalent users resolve to one DM thread (example: telegram:123456). Use this when the same human appears across multiple channels or accounts.","hasChildren":true} +{"recordType":"path","path":"session.identityLinks.*","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"session.identityLinks.*.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.idleMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Idle Minutes","help":"Applies a legacy idle reset window in minutes for session reuse behavior across inactivity gaps. Use this only for compatibility and prefer structured reset policies under session.reset/session.resetByType.","hasChildren":false} +{"recordType":"path","path":"session.mainKey","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Main Key","help":"Overrides the canonical main session key used for continuity when dmScope or routing logic points to \"main\". Use a stable value only if you intentionally need custom session anchoring.","hasChildren":false} +{"recordType":"path","path":"session.maintenance","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Maintenance","help":"Automatic session-store maintenance controls for pruning age, entry caps, and file rotation behavior. Start in warn mode to observe impact, then enforce once thresholds are tuned.","hasChildren":true} +{"recordType":"path","path":"session.maintenance.highWaterBytes","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Disk High-water Target","help":"Target size after disk-budget cleanup (high-water mark). Defaults to 80% of maxDiskBytes; set explicitly for tighter reclaim behavior on constrained disks.","hasChildren":false} +{"recordType":"path","path":"session.maintenance.maxDiskBytes","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Session Max Disk Budget","help":"Optional per-agent sessions-directory disk budget (for example `500mb`). Use this to cap session storage per agent; when exceeded, warn mode reports pressure and enforce mode performs oldest-first cleanup.","hasChildren":false} +{"recordType":"path","path":"session.maintenance.maxEntries","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Session Max Entries","help":"Caps total session entry count retained in the store to prevent unbounded growth over time. Use lower limits for constrained environments, or higher limits when longer history is required.","hasChildren":false} +{"recordType":"path","path":"session.maintenance.mode","kind":"core","type":"string","required":false,"enumValues":["enforce","warn"],"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Maintenance Mode","help":"Determines whether maintenance policies are only reported (\"warn\") or actively applied (\"enforce\"). Keep \"warn\" during rollout and switch to \"enforce\" after validating safe thresholds.","hasChildren":false} +{"recordType":"path","path":"session.maintenance.pruneAfter","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Prune After","help":"Removes entries older than this duration (for example `30d` or `12h`) during maintenance passes. Use this as the primary age-retention control and align it with data retention policy.","hasChildren":false} +{"recordType":"path","path":"session.maintenance.pruneDays","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Prune Days (Deprecated)","help":"Deprecated age-retention field kept for compatibility with legacy configs using day counts. Use session.maintenance.pruneAfter instead so duration syntax and behavior are consistent.","hasChildren":false} +{"recordType":"path","path":"session.maintenance.resetArchiveRetention","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset Archive Retention","help":"Retention for reset transcript archives (`*.reset.`). Accepts a duration (for example `30d`), or `false` to disable cleanup. Defaults to pruneAfter so reset artifacts do not grow forever.","hasChildren":false} +{"recordType":"path","path":"session.maintenance.rotateBytes","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Rotate Size","help":"Rotates the session store when file size exceeds a threshold such as `10mb` or `1gb`. Use this to bound single-file growth and keep backup/restore operations manageable.","hasChildren":false} +{"recordType":"path","path":"session.parentForkMaxTokens","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["auth","performance","security","storage"],"label":"Session Parent Fork Max Tokens","help":"Maximum parent-session token count allowed for thread/session inheritance forking. If the parent exceeds this, OpenClaw starts a fresh thread session instead of forking; set 0 to disable this protection.","hasChildren":false} +{"recordType":"path","path":"session.reset","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset Policy","help":"Defines the default reset policy object used when no type-specific or channel-specific override applies. Set this first, then layer resetByType or resetByChannel only where behavior must differ.","hasChildren":true} +{"recordType":"path","path":"session.reset.atHour","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Daily Reset Hour","help":"Sets local-hour boundary (0-23) for daily reset mode so sessions roll over at predictable times. Use with mode=daily and align to operator timezone expectations for human-readable behavior.","hasChildren":false} +{"recordType":"path","path":"session.reset.idleMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset Idle Minutes","help":"Sets inactivity window before reset for idle mode and can also act as secondary guard with daily mode. Use larger values to preserve continuity or smaller values for fresher short-lived threads.","hasChildren":false} +{"recordType":"path","path":"session.reset.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset Mode","help":"Selects reset strategy: \"daily\" resets at a configured hour and \"idle\" resets after inactivity windows. Keep one clear mode per policy to avoid surprising context turnover patterns.","hasChildren":false} +{"recordType":"path","path":"session.resetByChannel","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset by Channel","help":"Provides channel-specific reset overrides keyed by provider/channel id for fine-grained behavior control. Use this only when one channel needs exceptional reset behavior beyond type-level policies.","hasChildren":true} +{"recordType":"path","path":"session.resetByChannel.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"session.resetByChannel.*.atHour","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByChannel.*.idleMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByChannel.*.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset by Chat Type","help":"Overrides reset behavior by chat type (direct, group, thread) when defaults are not sufficient. Use this when group/thread traffic needs different reset cadence than direct messages.","hasChildren":true} +{"recordType":"path","path":"session.resetByType.direct","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset (Direct)","help":"Defines reset policy for direct chats and supersedes the base session.reset configuration for that type. Use this as the canonical direct-message override instead of the legacy dm alias.","hasChildren":true} +{"recordType":"path","path":"session.resetByType.direct.atHour","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.direct.idleMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.direct.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.dm","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset (DM Deprecated Alias)","help":"Deprecated alias for direct reset behavior kept for backward compatibility with older configs. Use session.resetByType.direct instead so future tooling and validation remain consistent.","hasChildren":true} +{"recordType":"path","path":"session.resetByType.dm.atHour","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.dm.idleMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.dm.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.group","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset (Group)","help":"Defines reset policy for group chat sessions where continuity and noise patterns differ from DMs. Use shorter idle windows for busy groups if context drift becomes a problem.","hasChildren":true} +{"recordType":"path","path":"session.resetByType.group.atHour","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.group.idleMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.group.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.thread","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset (Thread)","help":"Defines reset policy for thread-scoped sessions, including focused channel thread workflows. Use this when thread sessions should expire faster or slower than other chat types.","hasChildren":true} +{"recordType":"path","path":"session.resetByType.thread.atHour","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.thread.idleMinutes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetByType.thread.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.resetTriggers","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Reset Triggers","help":"Lists message triggers that force a session reset when matched in inbound content. Use sparingly for explicit reset phrases so context is not dropped unexpectedly during normal conversation.","hasChildren":true} +{"recordType":"path","path":"session.resetTriggers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"session.scope","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Scope","help":"Sets base session grouping strategy: \"per-sender\" isolates by sender and \"global\" shares one session per channel context. Keep \"per-sender\" for safer multi-user behavior unless deliberate shared context is required.","hasChildren":false} +{"recordType":"path","path":"session.sendPolicy","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Session Send Policy","help":"Controls cross-session send permissions using allow/deny rules evaluated against channel, chatType, and key prefixes. Use this to fence where session tools can deliver messages in complex environments.","hasChildren":true} +{"recordType":"path","path":"session.sendPolicy.default","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Session Send Policy Default Action","help":"Sets fallback action when no sendPolicy rule matches: \"allow\" or \"deny\". Keep \"allow\" for simpler setups, or choose \"deny\" when you require explicit allow rules for every destination.","hasChildren":false} +{"recordType":"path","path":"session.sendPolicy.rules","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Session Send Policy Rules","help":"Ordered allow/deny rules evaluated before the default action, for example `{ action: \"deny\", match: { channel: \"discord\" } }`. Put most specific rules first so broad rules do not shadow exceptions.","hasChildren":true} +{"recordType":"path","path":"session.sendPolicy.rules.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"session.sendPolicy.rules.*.action","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Session Send Rule Action","help":"Defines rule decision as \"allow\" or \"deny\" when the corresponding match criteria are satisfied. Use deny-first ordering when enforcing strict boundaries with explicit allow exceptions.","hasChildren":false} +{"recordType":"path","path":"session.sendPolicy.rules.*.match","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Session Send Rule Match","help":"Defines optional rule match conditions that can combine channel, chatType, and key-prefix constraints. Keep matches narrow so policy intent stays readable and debugging remains straightforward.","hasChildren":true} +{"recordType":"path","path":"session.sendPolicy.rules.*.match.channel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Session Send Rule Channel","help":"Matches rule application to a specific channel/provider id (for example discord, telegram, slack). Use this when one channel should permit or deny delivery independently of others.","hasChildren":false} +{"recordType":"path","path":"session.sendPolicy.rules.*.match.chatType","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Session Send Rule Chat Type","help":"Matches rule application to chat type (direct, group, thread) so behavior varies by conversation form. Use this when DM and group destinations require different safety boundaries.","hasChildren":false} +{"recordType":"path","path":"session.sendPolicy.rules.*.match.keyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Session Send Rule Key Prefix","help":"Matches a normalized session-key prefix after internal key normalization steps in policy consumers. Use this for general prefix controls, and prefer rawKeyPrefix when exact full-key matching is required.","hasChildren":false} +{"recordType":"path","path":"session.sendPolicy.rules.*.match.rawKeyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access","storage"],"label":"Session Send Rule Raw Key Prefix","help":"Matches the raw, unnormalized session-key prefix for exact full-key policy targeting. Use this when normalized keyPrefix is too broad and you need agent-prefixed or transport-specific precision.","hasChildren":false} +{"recordType":"path","path":"session.store","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Store Path","help":"Sets the session storage file path used to persist session records across restarts. Use an explicit path only when you need custom disk layout, backup routing, or mounted-volume storage.","hasChildren":false} +{"recordType":"path","path":"session.threadBindings","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Thread Bindings","help":"Shared defaults for thread-bound session routing behavior across providers that support thread focus workflows. Configure global defaults here and override per channel only when behavior differs.","hasChildren":true} +{"recordType":"path","path":"session.threadBindings.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Thread Binding Enabled","help":"Global master switch for thread-bound session routing features and focused thread delivery behavior. Keep enabled for modern thread workflows unless you need to disable thread binding globally.","hasChildren":false} +{"recordType":"path","path":"session.threadBindings.idleHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Thread Binding Idle Timeout (hours)","help":"Default inactivity window in hours for thread-bound sessions across providers/channels (0 disables idle auto-unfocus). Default: 24.","hasChildren":false} +{"recordType":"path","path":"session.threadBindings.maxAgeHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Thread Binding Max Age (hours)","help":"Optional hard max age in hours for thread-bound sessions across providers/channels (0 disables hard cap). Default: 0.","hasChildren":false} +{"recordType":"path","path":"session.typingIntervalSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Session Typing Interval (seconds)","help":"Controls interval for repeated typing indicators while replies are being prepared in typing-capable channels. Increase to reduce chatty updates or decrease for more active typing feedback.","hasChildren":false} +{"recordType":"path","path":"session.typingMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Session Typing Mode","help":"Controls typing behavior timing: \"never\", \"instant\", \"thinking\", or \"message\" based emission points. Keep conservative modes in high-volume channels to avoid unnecessary typing noise.","hasChildren":false} +{"recordType":"path","path":"skills","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Skills","hasChildren":true} +{"recordType":"path","path":"skills.allowBundled","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"skills.allowBundled.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.entries","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"skills.entries.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"skills.entries.*.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"hasChildren":true} +{"recordType":"path","path":"skills.entries.*.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.entries.*.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.entries.*.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.entries.*.config","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"skills.entries.*.config.*","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.entries.*.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.entries.*.env","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"skills.entries.*.env.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.install","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"skills.install.nodeManager","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.install.preferBrew","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.limits","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"skills.limits.maxCandidatesPerRoot","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.limits.maxSkillFileBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.limits.maxSkillsInPrompt","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.limits.maxSkillsLoadedPerSource","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.limits.maxSkillsPromptChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.load","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"skills.load.extraDirs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"skills.load.extraDirs.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"skills.load.watch","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Watch Skills","help":"Enable filesystem watching for skill-definition changes so updates can be applied without full process restart. Keep enabled in development workflows and disable in immutable production images.","hasChildren":false} +{"recordType":"path","path":"skills.load.watchDebounceMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["automation","performance"],"label":"Skills Watch Debounce (ms)","help":"Debounce window in milliseconds for coalescing rapid skill file changes before reload logic runs. Increase to reduce reload churn on frequent writes, or lower for faster edit feedback.","hasChildren":false} +{"recordType":"path","path":"talk","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Talk","help":"Talk-mode voice synthesis settings for voice identity, model selection, output format, and interruption behavior. Use this section to tune human-facing voice UX while controlling latency and cost.","hasChildren":true} +{"recordType":"path","path":"talk.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","media","security"],"label":"Talk API Key","help":"Use this legacy ElevenLabs API key for Talk mode only during migration, and keep secrets in env-backed storage. Prefer talk.providers.elevenlabs.apiKey (fallback: ELEVENLABS_API_KEY).","hasChildren":true} +{"recordType":"path","path":"talk.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"talk.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"talk.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"talk.interruptOnSpeech","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Talk Interrupt on Speech","help":"If true (default), stop assistant speech when the user starts speaking in Talk mode. Keep enabled for conversational turn-taking.","hasChildren":false} +{"recordType":"path","path":"talk.modelId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","models"],"label":"Talk Model ID","help":"Legacy ElevenLabs model ID for Talk mode (default: eleven_v3). Prefer talk.providers.elevenlabs.modelId.","hasChildren":false} +{"recordType":"path","path":"talk.outputFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Talk Output Format","help":"Use this legacy ElevenLabs output format for Talk mode (for example pcm_44100 or mp3_44100_128) only during migration. Prefer talk.providers.elevenlabs.outputFormat.","hasChildren":false} +{"recordType":"path","path":"talk.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Talk Active Provider","help":"Active Talk provider id (for example \"elevenlabs\").","hasChildren":false} +{"recordType":"path","path":"talk.providers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Talk Provider Settings","help":"Provider-specific Talk settings keyed by provider id. During migration, prefer this over legacy talk.* keys.","hasChildren":true} +{"recordType":"path","path":"talk.providers.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"talk.providers.*.*","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"talk.providers.*.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","media","security"],"label":"Talk Provider API Key","help":"Provider API key for Talk mode.","hasChildren":true} +{"recordType":"path","path":"talk.providers.*.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"talk.providers.*.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"talk.providers.*.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"talk.providers.*.modelId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","models"],"label":"Talk Provider Model ID","help":"Provider default model ID for Talk mode.","hasChildren":false} +{"recordType":"path","path":"talk.providers.*.outputFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Talk Provider Output Format","help":"Provider default output format for Talk mode.","hasChildren":false} +{"recordType":"path","path":"talk.providers.*.voiceAliases","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Talk Provider Voice Aliases","help":"Optional provider voice alias map for Talk directives.","hasChildren":true} +{"recordType":"path","path":"talk.providers.*.voiceAliases.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"talk.providers.*.voiceId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Talk Provider Voice ID","help":"Provider default voice ID for Talk mode.","hasChildren":false} +{"recordType":"path","path":"talk.silenceTimeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance"],"label":"Talk Silence Timeout (ms)","help":"Milliseconds of user silence before Talk mode finalizes and sends the current transcript. Leave unset to keep the platform default pause window (700 ms on macOS and Android, 900 ms on iOS).","hasChildren":false} +{"recordType":"path","path":"talk.voiceAliases","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Talk Voice Aliases","help":"Use this legacy ElevenLabs voice alias map (for example {\"Clawd\":\"EXAVITQu4vr4xnSDxMaL\"}) only during migration. Prefer talk.providers.elevenlabs.voiceAliases.","hasChildren":true} +{"recordType":"path","path":"talk.voiceAliases.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"talk.voiceId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Talk Voice ID","help":"Legacy ElevenLabs default voice ID for Talk mode. Prefer talk.providers.elevenlabs.voiceId.","hasChildren":false} +{"recordType":"path","path":"tools","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Tools","help":"Global tool access policy and capability configuration across web, exec, media, messaging, and elevated surfaces. Use this section to constrain risky capabilities before broad rollout.","hasChildren":true} +{"recordType":"path","path":"tools.agentToAgent","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Agent-to-Agent Tool Access","help":"Policy for allowing agent-to-agent tool calls and constraining which target agents can be reached. Keep disabled or tightly scoped unless cross-agent orchestration is intentionally enabled.","hasChildren":true} +{"recordType":"path","path":"tools.agentToAgent.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"Agent-to-Agent Target Allowlist","help":"Allowlist of target agent IDs permitted for agent_to_agent calls when orchestration is enabled. Use explicit allowlists to avoid uncontrolled cross-agent call graphs.","hasChildren":true} +{"recordType":"path","path":"tools.agentToAgent.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.agentToAgent.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Agent-to-Agent Tool","help":"Enables the agent_to_agent tool surface so one agent can invoke another agent at runtime. Keep off in simple deployments and enable only when orchestration value outweighs complexity.","hasChildren":false} +{"recordType":"path","path":"tools.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"Tool Allowlist","help":"Absolute tool allowlist that replaces profile-derived defaults for strict environments. Use this only when you intentionally run a tightly curated subset of tool capabilities.","hasChildren":true} +{"recordType":"path","path":"tools.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.alsoAllow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"Tool Allowlist Additions","help":"Extra tool allowlist entries merged on top of the selected tool profile and default policy. Keep this list small and explicit so audits can quickly identify intentional policy exceptions.","hasChildren":true} +{"recordType":"path","path":"tools.alsoAllow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.byProvider","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Tool Policy by Provider","help":"Per-provider tool allow/deny overrides keyed by channel/provider ID to tailor capabilities by surface. Use this when one provider needs stricter controls than global tool policy.","hasChildren":true} +{"recordType":"path","path":"tools.byProvider.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.byProvider.*.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.byProvider.*.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.byProvider.*.alsoAllow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.byProvider.*.alsoAllow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.byProvider.*.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.byProvider.*.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.byProvider.*.profile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"Tool Denylist","help":"Global tool denylist that blocks listed tools even when profile or provider rules would allow them. Use deny rules for emergency lockouts and long-term defense-in-depth.","hasChildren":true} +{"recordType":"path","path":"tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.elevated","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Elevated Tool Access","help":"Elevated tool access controls for privileged command surfaces that should only be reachable from trusted senders. Keep disabled unless operator workflows explicitly require elevated actions.","hasChildren":true} +{"recordType":"path","path":"tools.elevated.allowFrom","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"Elevated Tool Allow Rules","help":"Sender allow rules for elevated tools, usually keyed by channel/provider identity formats. Use narrow, explicit identities so elevated commands cannot be triggered by unintended users.","hasChildren":true} +{"recordType":"path","path":"tools.elevated.allowFrom.*","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.elevated.allowFrom.*.*","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.elevated.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Elevated Tool Access","help":"Enables elevated tool execution path when sender and policy checks pass. Keep disabled in public/shared channels and enable only for trusted owner-operated contexts.","hasChildren":false} +{"recordType":"path","path":"tools.exec","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Tool","help":"Exec-tool policy grouping for shell execution host, security mode, approval behavior, and runtime bindings. Keep conservative defaults in production and tighten elevated execution paths.","hasChildren":true} +{"recordType":"path","path":"tools.exec.applyPatch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.exec.applyPatch.allowModels","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"apply_patch Model Allowlist","help":"Optional allowlist of model ids (e.g. \"gpt-5.2\" or \"openai/gpt-5.2\").","hasChildren":true} +{"recordType":"path","path":"tools.exec.applyPatch.allowModels.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.exec.applyPatch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable apply_patch","help":"Experimental. Enables apply_patch for OpenAI models when allowed by tool policy.","hasChildren":false} +{"recordType":"path","path":"tools.exec.applyPatch.workspaceOnly","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced","security","tools"],"label":"apply_patch Workspace-Only","help":"Restrict apply_patch paths to the workspace directory (default: true). Set false to allow writing outside the workspace (dangerous).","hasChildren":false} +{"recordType":"path","path":"tools.exec.ask","kind":"core","type":"string","required":false,"enumValues":["off","on-miss","always"],"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Ask","help":"Approval strategy for when exec commands require human confirmation before running. Use stricter ask behavior in shared channels and lower-friction settings in private operator contexts.","hasChildren":false} +{"recordType":"path","path":"tools.exec.backgroundMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"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":["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} +{"recordType":"path","path":"tools.exec.pathPrepend","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["storage","tools"],"label":"Exec PATH Prepend","help":"Directories to prepend to PATH for exec runs (gateway/sandbox).","hasChildren":true} +{"recordType":"path","path":"tools.exec.pathPrepend.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.exec.safeBinProfiles","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage","tools"],"label":"Exec Safe Bin Profiles","help":"Optional per-binary safe-bin profiles (positional limits + allowed/denied flags).","hasChildren":true} +{"recordType":"path","path":"tools.exec.safeBinProfiles.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.exec.safeBinProfiles.*.allowedValueFlags","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.exec.safeBinProfiles.*.allowedValueFlags.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.exec.safeBinProfiles.*.deniedFlags","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.exec.safeBinProfiles.*.deniedFlags.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.exec.safeBinProfiles.*.maxPositional","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.exec.safeBinProfiles.*.minPositional","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.exec.safeBins","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Safe Bins","help":"Allow stdin-only safe binaries to run without explicit allowlist entries.","hasChildren":true} +{"recordType":"path","path":"tools.exec.safeBins.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.exec.safeBinTrustedDirs","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["storage","tools"],"label":"Exec Safe Bin Trusted Dirs","help":"Additional explicit directories trusted for safe-bin path checks (PATH entries are never auto-trusted).","hasChildren":true} +{"recordType":"path","path":"tools.exec.safeBinTrustedDirs.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.exec.security","kind":"core","type":"string","required":false,"enumValues":["deny","allowlist","full"],"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Security","help":"Execution security posture selector controlling sandbox/approval expectations for command execution. Keep strict security mode for untrusted prompts and relax only for trusted operator workflows.","hasChildren":false} +{"recordType":"path","path":"tools.exec.timeoutSec","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.fs","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.fs.workspaceOnly","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Workspace-only FS tools","help":"Restrict filesystem tools (read/write/edit/apply_patch) to the workspace directory (default: false).","hasChildren":false} +{"recordType":"path","path":"tools.links","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.links.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Link Understanding","help":"Enable automatic link understanding pre-processing so URLs can be summarized before agent reasoning. Keep enabled for richer context, and disable when strict minimal processing is required.","hasChildren":false} +{"recordType":"path","path":"tools.links.maxLinks","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Link Understanding Max Links","help":"Maximum number of links expanded per turn during link understanding. Use lower values to control latency/cost in chatty threads and higher values when multi-link context is critical.","hasChildren":false} +{"recordType":"path","path":"tools.links.models","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["models","tools"],"label":"Link Understanding Models","help":"Preferred model list for link understanding tasks, evaluated in order as fallbacks when supported. Use lightweight models first for routine summarization and heavier models only when needed.","hasChildren":true} +{"recordType":"path","path":"tools.links.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.links.models.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.links.models.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.links.models.*.command","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.links.models.*.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.links.models.*.type","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.links.scope","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Link Understanding Scope","help":"Controls when link understanding runs relative to conversation context and message type. Keep scope conservative to avoid unnecessary fetches on messages where links are not actionable.","hasChildren":true} +{"recordType":"path","path":"tools.links.scope.default","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.links.scope.rules","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.links.scope.rules.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.links.scope.rules.*.action","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.links.scope.rules.*.match","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.links.scope.rules.*.match.channel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.links.scope.rules.*.match.chatType","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.links.scope.rules.*.match.keyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.links.scope.rules.*.match.rawKeyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.links.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Link Understanding Timeout (sec)","help":"Per-link understanding timeout budget in seconds before unresolved links are skipped. Keep this bounded to avoid long stalls when external sites are slow or unreachable.","hasChildren":false} +{"recordType":"path","path":"tools.loopDetection","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.loopDetection.criticalThreshold","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Tool-loop Critical Threshold","help":"Critical threshold for repetitive patterns when detector is enabled (default: 20).","hasChildren":false} +{"recordType":"path","path":"tools.loopDetection.detectors","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.loopDetection.detectors.genericRepeat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Tool-loop Generic Repeat Detection","help":"Enable generic repeated same-tool/same-params loop detection (default: true).","hasChildren":false} +{"recordType":"path","path":"tools.loopDetection.detectors.knownPollNoProgress","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Tool-loop Poll No-Progress Detection","help":"Enable known poll tool no-progress loop detection (default: true).","hasChildren":false} +{"recordType":"path","path":"tools.loopDetection.detectors.pingPong","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Tool-loop Ping-Pong Detection","help":"Enable ping-pong loop detection (default: true).","hasChildren":false} +{"recordType":"path","path":"tools.loopDetection.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Tool-loop Detection","help":"Enable repetitive tool-call loop detection and backoff safety checks (default: false).","hasChildren":false} +{"recordType":"path","path":"tools.loopDetection.globalCircuitBreakerThreshold","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["reliability","tools"],"label":"Tool-loop Global Circuit Breaker Threshold","help":"Global no-progress breaker threshold (default: 30).","hasChildren":false} +{"recordType":"path","path":"tools.loopDetection.historySize","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Tool-loop History Size","help":"Tool history window size for loop detection (default: 30).","hasChildren":false} +{"recordType":"path","path":"tools.loopDetection.warningThreshold","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Tool-loop Warning Threshold","help":"Warning threshold for repetitive patterns when detector is enabled (default: 10).","hasChildren":false} +{"recordType":"path","path":"tools.media","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.attachments","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Audio Understanding Attachment Policy","help":"Attachment policy for audio inputs indicating which uploaded files are eligible for audio processing. Keep restrictive defaults in mixed-content channels to avoid unintended audio workloads.","hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"tools.media.audio.deepgram.smartFormat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.echoFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Transcript Echo Format","help":"Format string for the echoed transcript message. Use `{transcript}` as a placeholder for the transcribed text. Default: '📝 \"{transcript}\"'.","hasChildren":false} +{"recordType":"path","path":"tools.media.audio.echoTranscript","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Echo Transcript to Chat","help":"Echo the audio transcript back to the originating chat before agent processing. When enabled, users immediately see what was heard from their voice note, helping them verify transcription accuracy before the agent acts on it. Default: false.","hasChildren":false} +{"recordType":"path","path":"tools.media.audio.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Enable Audio Understanding","help":"Enable audio understanding so voice notes or audio clips can be transcribed/summarized for agent context. Disable when audio ingestion is outside policy or unnecessary for your workflows.","hasChildren":false} +{"recordType":"path","path":"tools.media.audio.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.language","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Audio Understanding Language","help":"Preferred language hint for audio understanding/transcription when provider support is available. Set this to improve recognition accuracy for known primary languages.","hasChildren":false} +{"recordType":"path","path":"tools.media.audio.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance","tools"],"label":"Audio Understanding Max Bytes","help":"Maximum accepted audio payload size in bytes before processing is rejected or clipped by policy. Set this based on expected recording length and upstream provider limits.","hasChildren":false} +{"recordType":"path","path":"tools.media.audio.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance","tools"],"label":"Audio Understanding Max Chars","help":"Maximum characters retained from audio understanding output to prevent oversized transcript injection. Increase for long-form dictation, or lower to keep conversational turns compact.","hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["media","models","tools"],"label":"Audio Understanding Models","help":"Ordered model preferences specifically for audio understanding, used before shared media model fallback. Choose models optimized for transcription quality in your primary language/domain.","hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"tools.media.audio.models.*.deepgram","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.models.*.deepgram.detectLanguage","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.deepgram.punctuate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.deepgram.smartFormat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.models.*.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.language","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.preferredProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.profile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.providerOptions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.models.*.providerOptions.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.models.*.providerOptions.*.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.models.*.type","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Audio Understanding Prompt","help":"Instruction template guiding audio understanding output style, such as concise summary versus near-verbatim transcript. Keep wording consistent so downstream automations can rely on output format.","hasChildren":false} +{"recordType":"path","path":"tools.media.audio.providerOptions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.providerOptions.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.providerOptions.*.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.scope","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Audio Understanding Scope","help":"Scope selector for when audio understanding runs across inbound messages and attachments. Keep focused scopes in high-volume channels to reduce cost and avoid accidental transcription.","hasChildren":true} +{"recordType":"path","path":"tools.media.audio.scope.default","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.scope.rules","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.scope.rules.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.scope.rules.*.action","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.scope.rules.*.match","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.audio.scope.rules.*.match.channel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.scope.rules.*.match.chatType","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.scope.rules.*.match.keyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.scope.rules.*.match.rawKeyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.audio.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance","tools"],"label":"Audio Understanding Timeout (sec)","help":"Timeout in seconds for audio understanding execution before the operation is cancelled. Use longer timeouts for long recordings and tighter ones for interactive chat responsiveness.","hasChildren":false} +{"recordType":"path","path":"tools.media.concurrency","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance","tools"],"label":"Media Understanding Concurrency","help":"Maximum number of concurrent media understanding operations per turn across image, audio, and video tasks. Lower this in resource-constrained deployments to prevent CPU/network saturation.","hasChildren":false} +{"recordType":"path","path":"tools.media.image","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.attachments","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Image Understanding Attachment Policy","help":"Attachment handling policy for image inputs, including which message attachments qualify for image analysis. Use restrictive settings in untrusted channels to reduce unexpected processing.","hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"tools.media.image.deepgram.smartFormat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.echoFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.echoTranscript","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Enable Image Understanding","help":"Enable image understanding so attached or referenced images can be interpreted into textual context. Disable if you need text-only operation or want to avoid image-processing cost.","hasChildren":false} +{"recordType":"path","path":"tools.media.image.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.language","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance","tools"],"label":"Image Understanding Max Bytes","help":"Maximum accepted image payload size in bytes before the item is skipped or truncated by policy. Keep limits realistic for your provider caps and infrastructure bandwidth.","hasChildren":false} +{"recordType":"path","path":"tools.media.image.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance","tools"],"label":"Image Understanding Max Chars","help":"Maximum characters returned from image understanding output after model response normalization. Use tighter limits to reduce prompt bloat and larger limits for detail-heavy OCR tasks.","hasChildren":false} +{"recordType":"path","path":"tools.media.image.models","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["media","models","tools"],"label":"Image Understanding Models","help":"Ordered model preferences specifically for image understanding when you want to override shared media models. Put the most reliable multimodal model first to reduce fallback attempts.","hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"tools.media.image.models.*.deepgram","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.models.*.deepgram.detectLanguage","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.deepgram.punctuate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.deepgram.smartFormat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.models.*.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.language","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.preferredProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.profile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.providerOptions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.models.*.providerOptions.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.models.*.providerOptions.*.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.models.*.type","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Image Understanding Prompt","help":"Instruction template used for image understanding requests to shape extraction style and detail level. Keep prompts deterministic so outputs stay consistent across turns and channels.","hasChildren":false} +{"recordType":"path","path":"tools.media.image.providerOptions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.providerOptions.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.providerOptions.*.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.scope","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Image Understanding Scope","help":"Scope selector for when image understanding is attempted (for example only explicit requests versus broader auto-detection). Keep narrow scope in busy channels to control token and API spend.","hasChildren":true} +{"recordType":"path","path":"tools.media.image.scope.default","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.scope.rules","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.scope.rules.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.scope.rules.*.action","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.scope.rules.*.match","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.image.scope.rules.*.match.channel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.scope.rules.*.match.chatType","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.scope.rules.*.match.keyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.scope.rules.*.match.rawKeyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.image.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance","tools"],"label":"Image Understanding Timeout (sec)","help":"Timeout in seconds for each image understanding request before it is aborted. Increase for high-resolution analysis and lower it for latency-sensitive operator workflows.","hasChildren":false} +{"recordType":"path","path":"tools.media.models","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["media","models","tools"],"label":"Media Understanding Shared Models","help":"Shared fallback model list used by media understanding tools when modality-specific model lists are not set. Keep this aligned with available multimodal providers to avoid runtime fallback churn.","hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"tools.media.models.*.deepgram","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.models.*.deepgram.detectLanguage","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.deepgram.punctuate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.deepgram.smartFormat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.models.*.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.language","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.preferredProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.profile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.providerOptions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.models.*.providerOptions.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.models.*.providerOptions.*.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.models.*.type","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.attachments","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Video Understanding Attachment Policy","help":"Attachment eligibility policy for video analysis, defining which message files can trigger video processing. Keep this explicit in shared channels to prevent accidental large media workloads.","hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"tools.media.video.deepgram.smartFormat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.echoFormat","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.echoTranscript","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Enable Video Understanding","help":"Enable video understanding so clips can be summarized into text for downstream reasoning and responses. Disable when processing video is out of policy or too expensive for your deployment.","hasChildren":false} +{"recordType":"path","path":"tools.media.video.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.language","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance","tools"],"label":"Video Understanding Max Bytes","help":"Maximum accepted video payload size in bytes before policy rejection or trimming occurs. Tune this to provider and infrastructure limits to avoid repeated timeout/failure loops.","hasChildren":false} +{"recordType":"path","path":"tools.media.video.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance","tools"],"label":"Video Understanding Max Chars","help":"Maximum characters retained from video understanding output to control prompt growth. Raise for dense scene descriptions and lower when concise summaries are preferred.","hasChildren":false} +{"recordType":"path","path":"tools.media.video.models","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["media","models","tools"],"label":"Video Understanding Models","help":"Ordered model preferences specifically for video understanding before shared media fallback applies. Prioritize models with strong multimodal video support to minimize degraded summaries.","hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"tools.media.video.models.*.deepgram","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.models.*.deepgram.detectLanguage","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.deepgram.punctuate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.deepgram.smartFormat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.models.*.headers.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.language","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.maxBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.preferredProfile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.profile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.providerOptions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.models.*.providerOptions.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.models.*.providerOptions.*.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.models.*.type","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.prompt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Video Understanding Prompt","help":"Instruction template for video understanding describing desired summary granularity and focus areas. Keep this stable so output quality remains predictable across model/provider fallbacks.","hasChildren":false} +{"recordType":"path","path":"tools.media.video.providerOptions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.providerOptions.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.providerOptions.*.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.scope","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools"],"label":"Video Understanding Scope","help":"Scope selector controlling when video understanding is attempted across incoming events. Narrow scope in noisy channels, and broaden only where video interpretation is core to workflow.","hasChildren":true} +{"recordType":"path","path":"tools.media.video.scope.default","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.scope.rules","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.scope.rules.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.scope.rules.*.action","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.scope.rules.*.match","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.media.video.scope.rules.*.match.channel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.scope.rules.*.match.chatType","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.scope.rules.*.match.keyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.scope.rules.*.match.rawKeyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.media.video.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["media","performance","tools"],"label":"Video Understanding Timeout (sec)","help":"Timeout in seconds for each video understanding request before cancellation. Use conservative values in interactive channels and longer values for offline or batch-heavy processing.","hasChildren":false} +{"recordType":"path","path":"tools.message","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.message.allowCrossContextSend","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"Allow Cross-Context Messaging","help":"Legacy override: allow cross-context sends across all providers.","hasChildren":false} +{"recordType":"path","path":"tools.message.broadcast","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.message.broadcast.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Message Broadcast","help":"Enable broadcast action (default: true).","hasChildren":false} +{"recordType":"path","path":"tools.message.crossContext","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.message.crossContext.allowAcrossProviders","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"Allow Cross-Context (Across Providers)","help":"Allow sends across different providers (default: false).","hasChildren":false} +{"recordType":"path","path":"tools.message.crossContext.allowWithinProvider","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"Allow Cross-Context (Same Provider)","help":"Allow sends to other channels within the same provider (default: true).","hasChildren":false} +{"recordType":"path","path":"tools.message.crossContext.marker","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.message.crossContext.marker.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Cross-Context Marker","help":"Add a visible origin marker when sending cross-context (default: true).","hasChildren":false} +{"recordType":"path","path":"tools.message.crossContext.marker.prefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Cross-Context Marker Prefix","help":"Text prefix for cross-context markers (supports \"{channel}\").","hasChildren":false} +{"recordType":"path","path":"tools.message.crossContext.marker.suffix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Cross-Context Marker Suffix","help":"Text suffix for cross-context markers (supports \"{channel}\").","hasChildren":false} +{"recordType":"path","path":"tools.profile","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage","tools"],"label":"Tool Profile","help":"Global tool profile name used to select a predefined tool policy baseline before applying allow/deny overrides. Use this for consistent environment posture across agents and keep profile names stable.","hasChildren":false} +{"recordType":"path","path":"tools.sandbox","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage","tools"],"label":"Sandbox Tool Policy","help":"Tool policy wrapper for sandboxed agent executions so sandbox runs can have distinct capability boundaries. Use this to enforce stronger safety in sandbox contexts.","hasChildren":true} +{"recordType":"path","path":"tools.sandbox.tools","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["storage","tools"],"label":"Sandbox Tool Allow/Deny Policy","help":"Allow/deny tool policy applied when agents run in sandboxed execution environments. Keep policies minimal so sandbox tasks cannot escalate into unnecessary external actions.","hasChildren":true} +{"recordType":"path","path":"tools.sandbox.tools.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.sandbox.tools.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.sandbox.tools.alsoAllow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.sandbox.tools.alsoAllow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.sandbox.tools.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.sandbox.tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.sessions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.sessions_spawn","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.sessions_spawn.attachments","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.sessions_spawn.attachments.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.sessions_spawn.attachments.maxFileBytes","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.sessions_spawn.attachments.maxFiles","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.sessions_spawn.attachments.maxTotalBytes","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.sessions_spawn.attachments.retainOnSessionKeep","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.sessions.visibility","kind":"core","type":"string","required":false,"enumValues":["self","tree","agent","all"],"deprecated":false,"sensitive":false,"tags":["storage","tools"],"label":"Session Tools Visibility","help":"Controls which sessions can be targeted by sessions_list/sessions_history/sessions_send. (\"tree\" default = current session + spawned subagent sessions; \"self\" = only current; \"agent\" = any session in the current agent id; \"all\" = any session; cross-agent still requires tools.agentToAgent).","hasChildren":false} +{"recordType":"path","path":"tools.subagents","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Subagent Tool Policy","help":"Tool policy wrapper for spawned subagents to restrict or expand tool availability compared to parent defaults. Use this to keep delegated agent capabilities scoped to task intent.","hasChildren":true} +{"recordType":"path","path":"tools.subagents.tools","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Subagent Tool Allow/Deny Policy","help":"Allow/deny tool policy applied to spawned subagent runtimes for per-subagent hardening. Keep this narrower than parent scope when subagents run semi-autonomous workflows.","hasChildren":true} +{"recordType":"path","path":"tools.subagents.tools.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.subagents.tools.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.subagents.tools.alsoAllow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.subagents.tools.alsoAllow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.subagents.tools.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.subagents.tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.web","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Web Tools","help":"Web-tool policy grouping for search/fetch providers, limits, and fallback behavior tuning. Keep enabled settings aligned with API key availability and outbound networking policy.","hasChildren":true} +{"recordType":"path","path":"tools.web.fetch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.web.fetch.cacheTtlMinutes","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage","tools"],"label":"Web Fetch Cache TTL (min)","help":"Cache TTL in minutes for web_fetch results.","hasChildren":false} +{"recordType":"path","path":"tools.web.fetch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Web Fetch Tool","help":"Enable the web_fetch tool (lightweight HTTP fetch).","hasChildren":false} +{"recordType":"path","path":"tools.web.fetch.firecrawl","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"label":"Firecrawl API Key","help":"Firecrawl API key (fallback: FIRECRAWL_API_KEY env var).","hasChildren":true} +{"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"],"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} +{"recordType":"path","path":"tools.web.fetch.firecrawl.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Firecrawl Timeout (sec)","help":"Timeout in seconds for Firecrawl requests.","hasChildren":false} +{"recordType":"path","path":"tools.web.fetch.maxChars","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Fetch Max Chars","help":"Max characters returned by web_fetch (truncated).","hasChildren":false} +{"recordType":"path","path":"tools.web.fetch.maxCharsCap","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Fetch Hard Max Chars","help":"Hard cap for web_fetch maxChars (applies to config and tool calls).","hasChildren":false} +{"recordType":"path","path":"tools.web.fetch.maxRedirects","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage","tools"],"label":"Web Fetch Max Redirects","help":"Maximum redirects allowed for web_fetch (default: 3).","hasChildren":false} +{"recordType":"path","path":"tools.web.fetch.readability","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Web Fetch Readability Extraction","help":"Use Readability to extract main content from HTML (fallbacks to basic HTML cleanup).","hasChildren":false} +{"recordType":"path","path":"tools.web.fetch.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Fetch Timeout (sec)","help":"Timeout in seconds for web_fetch requests.","hasChildren":false} +{"recordType":"path","path":"tools.web.fetch.userAgent","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Web Fetch User-Agent","help":"Override User-Agent header for web_fetch requests.","hasChildren":false} +{"recordType":"path","path":"tools.web.search","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.web.search.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true} +{"recordType":"path","path":"tools.web.search.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.web.search.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.web.search.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"tools.web.search.brave","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.web.search.brave.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"tools.web.search.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Web Search Tool","help":"Enable the web_search tool (requires a provider API key).","hasChildren":false} +{"recordType":"path","path":"tools.web.search.firecrawl","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"tools.web.search.firecrawl.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true} +{"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":[],"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":[],"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":[],"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} +{"recordType":"path","path":"tools.web.search.kimi.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"tools.web.search.perplexity.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true} +{"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":[],"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} +{"recordType":"path","path":"ui","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"UI","help":"UI presentation settings for accenting and assistant identity shown in control surfaces. Use this for branding and readability customization without changing runtime behavior.","hasChildren":true} +{"recordType":"path","path":"ui.assistant","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Assistant Appearance","help":"Assistant display identity settings for name and avatar shown in UI surfaces. Keep these values aligned with your operator-facing persona and support expectations.","hasChildren":true} +{"recordType":"path","path":"ui.assistant.avatar","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Assistant Avatar","help":"Assistant avatar image source used in UI surfaces (URL, path, or data URI depending on runtime support). Use trusted assets and consistent branding dimensions for clean rendering.","hasChildren":false} +{"recordType":"path","path":"ui.assistant.name","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Assistant Name","help":"Display name shown for the assistant in UI views, chat chrome, and status contexts. Keep this stable so operators can reliably identify which assistant persona is active.","hasChildren":false} +{"recordType":"path","path":"ui.seamColor","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Accent Color","help":"Primary accent color used by UI surfaces for emphasis, badges, and visual identity cues. Use high-contrast values that remain readable across light/dark themes.","hasChildren":false} +{"recordType":"path","path":"update","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Updates","help":"Update-channel and startup-check behavior for keeping OpenClaw runtime versions current. Use conservative channels in production and more experimental channels only in controlled environments.","hasChildren":true} +{"recordType":"path","path":"update.auto","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} +{"recordType":"path","path":"update.auto.betaCheckIntervalHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Auto Update Beta Check Interval (hours)","help":"How often beta-channel checks run in hours (default: 1).","hasChildren":false} +{"recordType":"path","path":"update.auto.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Auto Update Enabled","help":"Enable background auto-update for package installs (default: false).","hasChildren":false} +{"recordType":"path","path":"update.auto.stableDelayHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Auto Update Stable Delay (hours)","help":"Minimum delay before stable-channel auto-apply starts (default: 6).","hasChildren":false} +{"recordType":"path","path":"update.auto.stableJitterHours","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Auto Update Stable Jitter (hours)","help":"Extra stable-channel rollout spread window in hours (default: 12).","hasChildren":false} +{"recordType":"path","path":"update.channel","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Update Channel","help":"Update channel for git + npm installs (\"stable\", \"beta\", or \"dev\").","hasChildren":false} +{"recordType":"path","path":"update.checkOnStart","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Update Check on Start","help":"Check for npm updates when the gateway starts (default: true).","hasChildren":false} +{"recordType":"path","path":"web","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Web Channel","help":"Web channel runtime settings for heartbeat and reconnect behavior when operating web-based chat surfaces. Use reconnect values tuned to your network reliability profile and expected uptime needs.","hasChildren":true} +{"recordType":"path","path":"web.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Web Channel Enabled","help":"Enables the web channel runtime and related websocket lifecycle behavior. Keep disabled when web chat is unused to reduce active connection management overhead.","hasChildren":false} +{"recordType":"path","path":"web.heartbeatSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["automation"],"label":"Web Channel Heartbeat Interval (sec)","help":"Heartbeat interval in seconds for web channel connectivity and liveness maintenance. Use shorter intervals for faster detection, or longer intervals to reduce keepalive chatter.","hasChildren":false} +{"recordType":"path","path":"web.reconnect","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Web Channel Reconnect Policy","help":"Reconnect backoff policy for web channel reconnect attempts after transport failure. Keep bounded retries and jitter tuned to avoid thundering-herd reconnect behavior.","hasChildren":true} +{"recordType":"path","path":"web.reconnect.factor","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Web Reconnect Backoff Factor","help":"Exponential backoff multiplier used between reconnect attempts in web channel retry loops. Keep factor above 1 and tune with jitter for stable large-fleet reconnect behavior.","hasChildren":false} +{"recordType":"path","path":"web.reconnect.initialMs","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Web Reconnect Initial Delay (ms)","help":"Initial reconnect delay in milliseconds before the first retry after disconnection. Use modest delays to recover quickly without immediate retry storms.","hasChildren":false} +{"recordType":"path","path":"web.reconnect.jitter","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Web Reconnect Jitter","help":"Randomization factor (0-1) applied to reconnect delays to desynchronize clients after outage events. Keep non-zero jitter in multi-client deployments to reduce synchronized spikes.","hasChildren":false} +{"recordType":"path","path":"web.reconnect.maxAttempts","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Web Reconnect Max Attempts","help":"Maximum reconnect attempts before giving up for the current failure sequence (0 means no retries). Use finite caps for controlled failure handling in automation-sensitive environments.","hasChildren":false} +{"recordType":"path","path":"web.reconnect.maxMs","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Web Reconnect Max Delay (ms)","help":"Maximum reconnect backoff cap in milliseconds to bound retry delay growth over repeated failures. Use a reasonable cap so recovery remains timely after prolonged outages.","hasChildren":false} +{"recordType":"path","path":"wizard","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Setup Wizard State","help":"Setup wizard state tracking fields that record the most recent guided setup run details. Keep these fields for observability and troubleshooting of setup flows across upgrades.","hasChildren":true} +{"recordType":"path","path":"wizard.lastRunAt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Wizard Last Run Timestamp","help":"ISO timestamp for when the setup wizard most recently completed on this host. Use this to confirm setup recency during support and operational audits.","hasChildren":false} +{"recordType":"path","path":"wizard.lastRunCommand","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Wizard Last Run Command","help":"Command invocation recorded for the latest wizard run to preserve execution context. Use this to reproduce setup steps when verifying setup regressions.","hasChildren":false} +{"recordType":"path","path":"wizard.lastRunCommit","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Wizard Last Run Commit","help":"Source commit identifier recorded for the last wizard execution in development builds. Use this to correlate setup behavior with exact source state during debugging.","hasChildren":false} +{"recordType":"path","path":"wizard.lastRunMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Wizard Last Run Mode","help":"Wizard execution mode recorded as \"local\" or \"remote\" for the most recent setup flow. Use this to understand whether setup targeted direct local runtime or remote gateway topology.","hasChildren":false} +{"recordType":"path","path":"wizard.lastRunVersion","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Wizard Last Run Version","help":"OpenClaw version recorded at the time of the most recent wizard run on this config. Use this when diagnosing behavior differences across version-to-version setup changes.","hasChildren":false} diff --git a/docs/.i18n/glossary.zh-CN.json b/docs/.i18n/glossary.zh-CN.json index bde108074c2..d1b1f3f3058 100644 --- a/docs/.i18n/glossary.zh-CN.json +++ b/docs/.i18n/glossary.zh-CN.json @@ -47,6 +47,22 @@ "source": "Quick Start", "target": "快速开始" }, + { + "source": "Capability Cookbook", + "target": "能力扩展手册" + }, + { + "source": "Setup Wizard Reference", + "target": "设置向导参考" + }, + { + "source": "CLI Setup Reference", + "target": "CLI 设置参考" + }, + { + "source": "Setup Wizard (CLI)", + "target": "设置向导(CLI)" + }, { "source": "Docs directory", "target": "文档目录" @@ -123,6 +139,22 @@ "source": "Network model", "target": "网络模型" }, + { + "source": "Doctor", + "target": "Doctor" + }, + { + "source": "Polls", + "target": "投票" + }, + { + "source": "Release Policy", + "target": "发布策略" + }, + { + "source": "Release policy", + "target": "发布策略" + }, { "source": "for full details", "target": "了解详情" diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 715bc9df52a..00000000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -docs.openclaw.ai diff --git a/docs/assets/openclaw-logo-text-dark.svg b/docs/assets/openclaw-logo-text-dark.svg new file mode 100644 index 00000000000..317a203c8a4 --- /dev/null +++ b/docs/assets/openclaw-logo-text-dark.svg @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/openclaw-logo-text.svg b/docs/assets/openclaw-logo-text.svg new file mode 100644 index 00000000000..34038af7b3e --- /dev/null +++ b/docs/assets/openclaw-logo-text.svg @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/auth-credential-semantics.md b/docs/auth-credential-semantics.md index 17adb38f9ae..8c5c643b333 100644 --- a/docs/auth-credential-semantics.md +++ b/docs/auth-credential-semantics.md @@ -1,3 +1,11 @@ +--- +title: "Auth Credential Semantics" +summary: "Canonical credential eligibility and resolution semantics for auth profiles" +read_when: + - Working on auth profile resolution or credential routing + - Debugging model auth failures or profile order +--- + # Auth Credential Semantics This document defines the canonical credential eligibility and resolution semantics used across: diff --git a/docs/automation/cron-jobs.md b/docs/automation/cron-jobs.md index effa8f3ab81..d58683aedea 100644 --- a/docs/automation/cron-jobs.md +++ b/docs/automation/cron-jobs.md @@ -25,7 +25,9 @@ Troubleshooting: [/automation/troubleshooting](/automation/troubleshooting) - Jobs persist under `~/.openclaw/cron/` so restarts don’t lose schedules. - Two execution styles: - **Main session**: enqueue a system event, then run on the next heartbeat. - - **Isolated**: run a dedicated agent turn in `cron:`, with delivery (announce by default or none). + - **Isolated**: run a dedicated agent turn in `cron:` or a custom session, with delivery (announce by default or none). + - **Current session**: bind to the session where the cron is created (`sessionTarget: "current"`). + - **Custom session**: run in a persistent named session (`sessionTarget: "session:custom-id"`). - Wakeups are first-class: a job can request “wake now” vs “next heartbeat”. - Webhook posting is per job via `delivery.mode = "webhook"` + `delivery.to = ""`. - Legacy fallback remains for stored jobs with `notify: true` when `cron.webhook` is set, migrate those jobs to webhook delivery mode. @@ -86,6 +88,14 @@ Think of a cron job as: **when** to run + **what** to do. 2. **Choose where it runs** - `sessionTarget: "main"` → run during the next heartbeat with main context. - `sessionTarget: "isolated"` → run a dedicated agent turn in `cron:`. + - `sessionTarget: "current"` → bind to the current session (resolved at creation time to `session:`). + - `sessionTarget: "session:custom-id"` → run in a persistent named session that maintains context across runs. + + Default behavior (unchanged): + - `systemEvent` payloads default to `main` + - `agentTurn` payloads default to `isolated` + + To use current session binding, explicitly set `sessionTarget: "current"`. 3. **Choose the payload** - Main session → `payload.kind = "systemEvent"` @@ -147,12 +157,13 @@ See [Heartbeat](/gateway/heartbeat). #### Isolated jobs (dedicated cron sessions) -Isolated jobs run a dedicated agent turn in session `cron:`. +Isolated jobs run a dedicated agent turn in session `cron:` or a custom session. Key behaviors: - Prompt is prefixed with `[cron: ]` for traceability. -- Each run starts a **fresh session id** (no prior conversation carry-over). +- Each run starts a **fresh session id** (no prior conversation carry-over), unless using a custom session. +- Custom sessions (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries. - Default behavior: if `delivery` is omitted, isolated jobs announce a summary (`delivery.mode = "announce"`). - `delivery.mode` chooses what happens: - `announce`: deliver a summary to the target channel and post a brief summary to the main session. @@ -321,12 +332,42 @@ Recurring, isolated job with delivery: } ``` +Recurring job bound to current session (auto-resolved at creation): + +```json +{ + "name": "Daily standup", + "schedule": { "kind": "cron", "expr": "0 9 * * *" }, + "sessionTarget": "current", + "payload": { + "kind": "agentTurn", + "message": "Summarize yesterday's progress." + } +} +``` + +Recurring job in a custom persistent session: + +```json +{ + "name": "Project monitor", + "schedule": { "kind": "every", "everyMs": 300000 }, + "sessionTarget": "session:project-alpha-monitor", + "payload": { + "kind": "agentTurn", + "message": "Check project status and update the running log." + } +} +``` + Notes: - `schedule.kind`: `at` (`at`), `every` (`everyMs`), or `cron` (`expr`, optional `tz`). - `schedule.at` accepts ISO 8601 (timezone optional; treated as UTC when omitted). - `everyMs` is milliseconds. -- `sessionTarget` must be `"main"` or `"isolated"` and must match `payload.kind`. +- `sessionTarget`: `"main"`, `"isolated"`, `"current"`, or `"session:"`. +- `"current"` is resolved to `"session:"` at creation time. +- Custom sessions (`session:xxx`) maintain persistent context across runs. - Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun` (defaults to true for `at`), `delivery`. - `wakeMode` defaults to `"now"` when omitted. @@ -659,7 +700,7 @@ openclaw system event --mode now --text "Next heartbeat: check battery." ## Troubleshooting -### “Nothing runs” +### "Nothing runs" - Check cron is enabled: `cron.enabled` and `OPENCLAW_SKIP_CRON`. - Check the Gateway is running continuously (cron runs inside the Gateway process). diff --git a/docs/automation/cron-vs-heartbeat.md b/docs/automation/cron-vs-heartbeat.md index 9676d960d23..09f9187c368 100644 --- a/docs/automation/cron-vs-heartbeat.md +++ b/docs/automation/cron-vs-heartbeat.md @@ -219,13 +219,13 @@ See [Lobster](/tools/lobster) for full usage and examples. Both heartbeat and cron can interact with the main session, but differently: -| | Heartbeat | Cron (main) | Cron (isolated) | -| ------- | ------------------------------- | ------------------------ | -------------------------- | -| Session | Main | Main (via system event) | `cron:` | -| History | Shared | Shared | Fresh each run | -| Context | Full | Full | None (starts clean) | -| Model | Main session model | Main session model | Can override | -| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Announce summary (default) | +| | Heartbeat | Cron (main) | Cron (isolated) | +| ------- | ------------------------------- | ------------------------ | ----------------------------------------------- | +| Session | Main | Main (via system event) | `cron:` or custom session | +| History | Shared | Shared | Fresh each run (isolated) / Persistent (custom) | +| Context | Full | Full | None (isolated) / Cumulative (custom) | +| Model | Main session model | Main session model | Can override | +| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Announce summary (default) | ### When to use main session cron diff --git a/docs/automation/hooks.md b/docs/automation/hooks.md index deda79d3db5..a470bef8540 100644 --- a/docs/automation/hooks.md +++ b/docs/automation/hooks.md @@ -17,7 +17,7 @@ Hooks are small scripts that run when something happens. There are two kinds: - **Hooks** (this page): run inside the Gateway when agent events fire, like `/new`, `/reset`, `/stop`, or lifecycle events. - **Webhooks**: external HTTP webhooks that let other systems trigger work in OpenClaw. See [Webhook Hooks](/automation/webhook) or use `openclaw webhooks` for Gmail helper commands. -Hooks can also be bundled inside plugins; see [Plugins](/tools/plugin#plugin-hooks). +Hooks can also be bundled inside plugins; see [Plugin hooks](/plugins/architecture#provider-runtime-hooks). Common uses: diff --git a/docs/automation/webhook.md b/docs/automation/webhook.md index b35ee9d4469..38676a8fdbe 100644 --- a/docs/automation/webhook.md +++ b/docs/automation/webhook.md @@ -38,6 +38,7 @@ Every request must include the hook token. Prefer headers: - `Authorization: Bearer ` (recommended) - `x-openclaw-token: ` - Query-string tokens are rejected (`?token=...` returns `400`). +- Treat `hooks.token` holders as full-trust callers for the hook ingress surface on that gateway. Hook payload content is still untrusted, but this is not a separate non-owner auth boundary. ## Endpoints @@ -205,6 +206,7 @@ curl -X POST http://127.0.0.1:18789/hooks/gmail \ - Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy. - Use a dedicated hook token; do not reuse gateway auth tokens. +- Prefer a dedicated hook agent with strict `tools.profile` and sandboxing so hook ingress has a narrower blast radius. - Repeated auth failures are rate-limited per client address to slow brute-force attempts. - If you use multi-agent routing, set `hooks.allowedAgentIds` to limit explicit `agentId` selection. - Keep `hooks.allowRequestSessionKey=false` unless you require caller-selected sessions. diff --git a/docs/brave-search.md b/docs/brave-search.md index a8bba5c3e91..12cd78c358f 100644 --- a/docs/brave-search.md +++ b/docs/brave-search.md @@ -20,11 +20,21 @@ OpenClaw supports Brave Search API as a `web_search` provider. ```json5 { + plugins: { + entries: { + brave: { + config: { + webSearch: { + apiKey: "BRAVE_API_KEY_HERE", + }, + }, + }, + }, + }, tools: { web: { search: { provider: "brave", - apiKey: "BRAVE_API_KEY_HERE", maxResults: 5, timeoutSeconds: 30, }, @@ -33,6 +43,9 @@ OpenClaw supports Brave Search API as a `web_search` provider. } ``` +Provider-specific Brave search settings now live under `plugins.entries.brave.config.webSearch.*`. +Legacy `tools.web.search.apiKey` still loads through the compatibility shim, but it is no longer the canonical config path. + ## Tool parameters | Parameter | Description | @@ -73,7 +86,7 @@ await web_search({ ## Notes - OpenClaw uses the Brave **Search** plan. If you have a legacy subscription (e.g. the original Free plan with 2,000 queries/month), it remains valid but does not include newer features like LLM Context or higher rate limits. -- Each Brave plan includes **$5/month in free credit** (renewing). The Search plan costs $5 per 1,000 requests, so the credit covers 1,000 queries/month. Set your usage limit in the Brave dashboard to avoid unexpected charges. See the [Brave API portal](https://brave.com/search/api/) for current plans. +- Each Brave plan includes **\$5/month in free credit** (renewing). The Search plan costs \$5 per 1,000 requests, so the credit covers 1,000 queries/month. Set your usage limit in the Brave dashboard to avoid unexpected charges. See the [Brave API portal](https://brave.com/search/api/) for current plans. - The Search plan includes the LLM Context endpoint and AI inference rights. Storing results to train or tune models requires a plan with explicit storage rights. See the Brave [Terms of Service](https://api-dashboard.search.brave.com/terms-of-service). - Results are cached for 15 minutes by default (configurable via `cacheTtlMinutes`). diff --git a/docs/channels/bluebubbles.md b/docs/channels/bluebubbles.md index 9c2f0eb6de4..bf328656ff3 100644 --- a/docs/channels/bluebubbles.md +++ b/docs/channels/bluebubbles.md @@ -126,7 +126,7 @@ launchctl load ~/Library/LaunchAgents/com.user.poke-messages.plist ## Onboarding -BlueBubbles is available in the interactive setup wizard: +BlueBubbles is available in interactive onboarding: ``` openclaw onboard diff --git a/docs/channels/channel-routing.md b/docs/channels/channel-routing.md index 2d824359311..63c5806ebae 100644 --- a/docs/channels/channel-routing.md +++ b/docs/channels/channel-routing.md @@ -118,6 +118,11 @@ Session stores live under the state directory (default `~/.openclaw`): You can override the store path via `session.store` and `{agentId}` templating. +Gateway and ACP session discovery also scans disk-backed agent stores under the +default `agents/` root and under templated `session.store` roots. Discovered +stores must stay inside that resolved agent root and use a regular +`sessions.json` file. Symlinks and out-of-root paths are ignored. + ## WebChat behavior WebChat attaches to the **selected agent** and defaults to the agent’s main diff --git a/docs/channels/discord.md b/docs/channels/discord.md index e179417e9b8..0f7b6ac7074 100644 --- a/docs/channels/discord.md +++ b/docs/channels/discord.md @@ -96,8 +96,10 @@ You will need to create a new application with a bot, add the bot to your server Your Discord bot token is a secret (like a password). Set it on the machine running OpenClaw before messaging your agent. ```bash -openclaw config set channels.discord.token '"YOUR_BOT_TOKEN"' --json -openclaw config set channels.discord.enabled true --json +export DISCORD_BOT_TOKEN="YOUR_BOT_TOKEN" +openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN --dry-run +openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN +openclaw config set channels.discord.enabled true --strict-json openclaw gateway ``` @@ -121,7 +123,11 @@ openclaw gateway channels: { discord: { enabled: true, - token: "YOUR_BOT_TOKEN", + token: { + source: "env", + provider: "default", + id: "DISCORD_BOT_TOKEN", + }, }, }, } @@ -133,7 +139,7 @@ openclaw gateway DISCORD_BOT_TOKEN=... ``` - SecretRef values are also supported for `channels.discord.token` (env/file/exec providers). See [Secrets Management](/gateway/secrets). + Plaintext `token` values are supported. SecretRef values are also supported for `channels.discord.token` across env/file/exec providers. See [Secrets Management](/gateway/secrets). @@ -168,7 +174,7 @@ openclaw pairing approve discord Token resolution is account-aware. Config token values win over env fallback. `DISCORD_BOT_TOKEN` is only used for the default account. -For advanced outbound calls (message tool/channel actions), an explicit per-call `token` is used for that call. Account policy/retry settings still come from the selected account in the active runtime snapshot. +For advanced outbound calls (message tool/channel actions), an explicit per-call `token` is used for that call. This applies to send and read/probe-style actions (for example read/search/fetch/thread/pins/permissions). Account policy/retry settings still come from the selected account in the active runtime snapshot. ## Recommended: Set up a guild workspace diff --git a/docs/channels/feishu.md b/docs/channels/feishu.md index 67e4fd60379..ad018aa4d03 100644 --- a/docs/channels/feishu.md +++ b/docs/channels/feishu.md @@ -30,9 +30,9 @@ openclaw plugins install @openclaw/feishu There are two ways to add the Feishu channel: -### Method 1: onboarding wizard (recommended) +### Method 1: onboarding (recommended) -If you just installed OpenClaw, run the wizard: +If you just installed OpenClaw, run onboarding: ```bash openclaw onboard @@ -193,16 +193,18 @@ Edit `~/.openclaw/openclaw.json`: } ``` -If you use `connectionMode: "webhook"`, set `verificationToken`. The Feishu webhook server binds to `127.0.0.1` by default; set `webhookHost` only if you intentionally need a different bind address. +If you use `connectionMode: "webhook"`, set both `verificationToken` and `encryptKey`. The Feishu webhook server binds to `127.0.0.1` by default; set `webhookHost` only if you intentionally need a different bind address. -#### Verification Token (webhook mode) +#### Verification Token and Encrypt Key (webhook mode) -When using webhook mode, set `channels.feishu.verificationToken` in your config. To get the value: +When using webhook mode, set both `channels.feishu.verificationToken` and `channels.feishu.encryptKey` in your config. To get the values: 1. In Feishu Open Platform, open your app 2. Go to **Development** → **Events & Callbacks** (开发配置 → 事件与回调) 3. Open the **Encryption** tab (加密策略) -4. Copy **Verification Token** +4. Copy **Verification Token** and **Encrypt Key** + +The screenshot below shows where to find the **Verification Token**. The **Encrypt Key** is listed in the same **Encryption** section. ![Verification Token location](../images/feishu-verification-token.png) @@ -530,6 +532,75 @@ Feishu supports streaming replies via interactive cards. When enabled, the bot u Set `streaming: false` to wait for the full reply before sending. +### ACP sessions + +Feishu supports ACP for: + +- DMs +- group topic conversations + +Feishu ACP is text-command driven. There are no native slash-command menus, so use `/acp ...` messages directly in the conversation. + +#### Persistent ACP bindings + +Use top-level typed ACP bindings to pin a Feishu DM or topic conversation to a persistent ACP session. + +```json5 +{ + agents: { + list: [ + { + id: "codex", + runtime: { + type: "acp", + acp: { + agent: "codex", + backend: "acpx", + mode: "persistent", + cwd: "/workspace/openclaw", + }, + }, + }, + ], + }, + bindings: [ + { + type: "acp", + agentId: "codex", + match: { + channel: "feishu", + accountId: "default", + peer: { kind: "direct", id: "ou_1234567890" }, + }, + }, + { + type: "acp", + agentId: "codex", + match: { + channel: "feishu", + accountId: "default", + peer: { kind: "group", id: "oc_group_chat:topic:om_topic_root" }, + }, + acp: { label: "codex-feishu-topic" }, + }, + ], +} +``` + +#### Thread-bound ACP spawn from chat + +In a Feishu DM or topic conversation, you can spawn and bind an ACP session in place: + +```text +/acp spawn codex --thread here +``` + +Notes: + +- `--thread here` works for DMs and Feishu topics. +- Follow-up messages in the bound DM/topic route directly to that ACP session. +- v1 does not target generic non-topic group chats. + ### Multi-agent routing Use `bindings` to route Feishu DMs or groups to different agents. @@ -600,6 +671,7 @@ Key options: | `channels.feishu.connectionMode` | Event transport mode | `websocket` | | `channels.feishu.defaultAccount` | Default account ID for outbound routing | `default` | | `channels.feishu.verificationToken` | Required for webhook mode | - | +| `channels.feishu.encryptKey` | Required for webhook mode | - | | `channels.feishu.webhookPath` | Webhook route path | `/feishu/events` | | `channels.feishu.webhookHost` | Webhook bind host | `127.0.0.1` | | `channels.feishu.webhookPort` | Webhook bind port | `3000` | @@ -639,7 +711,7 @@ Key options: - ✅ Images - ✅ Files - ✅ Audio -- ✅ Video +- ✅ Video/media - ✅ Stickers ### Send @@ -648,4 +720,28 @@ Key options: - ✅ Images - ✅ Files - ✅ Audio -- ⚠️ Rich text (partial support) +- ✅ Video/media +- ✅ Interactive cards +- ⚠️ Rich text (post-style formatting and cards, not arbitrary Feishu authoring features) + +### Threads and replies + +- ✅ Inline replies +- ✅ Topic-thread replies where Feishu exposes `reply_in_thread` +- ✅ Media replies stay thread-aware when replying to a thread/topic message + +## Runtime action surface + +Feishu currently exposes these runtime actions: + +- `send` +- `read` +- `edit` +- `thread-reply` +- `pin` +- `list-pins` +- `unpin` +- `member-info` +- `channel-info` +- `channel-list` +- `react` and `reactions` when reactions are enabled in config diff --git a/docs/channels/googlechat.md b/docs/channels/googlechat.md index 09693589af7..bc9d435f4de 100644 --- a/docs/channels/googlechat.md +++ b/docs/channels/googlechat.md @@ -145,7 +145,7 @@ Configure your tunnel's ingress rules to only route the webhook path: - `audienceType: "app-url"` → audience is your HTTPS webhook URL. - `audienceType: "project-number"` → audience is the Cloud project number. 3. Messages are routed by space: - - DMs use session key `agent::googlechat:dm:`. + - DMs use session key `agent::googlechat:direct:`. - Spaces use session key `agent::googlechat:group:`. 4. DM access is pairing by default. Unknown senders receive a pairing code; approve with: - `openclaw pairing approve googlechat ` diff --git a/docs/channels/group-messages.md b/docs/channels/group-messages.md index e6a00ab5c5e..c1858bf1d96 100644 --- a/docs/channels/group-messages.md +++ b/docs/channels/group-messages.md @@ -11,9 +11,9 @@ Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback). -## What’s implemented (2025-12-03) +## Current implementation (2025-12-03) -- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bot’s E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`channels.whatsapp.groups`) and overridden per group via `/activation`. When `channels.whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all). +- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, safe regex patterns, or the bot’s E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`channels.whatsapp.groups`) and overridden per group via `/activation`. When `channels.whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all). - Group policy: `channels.whatsapp.groupPolicy` controls whether group messages are accepted (`open|disabled|allowlist`). `allowlist` uses `channels.whatsapp.groupAllowFrom` (fallback: explicit `channels.whatsapp.allowFrom`). Default is `allowlist` (blocked until you add senders). - Per-group sessions: session keys look like `agent::whatsapp:group:` so commands such as `/verbose on` or `/think high` (sent as standalone messages) are scoped to that group; personal DM state is untouched. Heartbeats are skipped for group threads. - Context injection: **pending-only** group messages (default 50) that _did not_ trigger a run are prefixed under `[Chat messages since your last reply - for context]`, with the triggering line under `[Current message - respond to this]`. Messages already in the session are not re-injected. @@ -50,7 +50,7 @@ Add a `groupChat` block to `~/.openclaw/openclaw.json` so display-name pings wor Notes: -- The regexes are case-insensitive; they cover a display-name ping like `@openclaw` and the raw number with or without `+`/spaces. +- The regexes are case-insensitive and use the same safe-regex guardrails as other config regex surfaces; invalid patterns and unsafe nested repetition are ignored. - WhatsApp still sends canonical mentions via `mentionedJids` when someone taps the contact, so the number fallback is rarely needed but is a useful safety net. ### Activation command (owner-only) diff --git a/docs/channels/groups.md b/docs/channels/groups.md index 3f9df076454..a6bd8621784 100644 --- a/docs/channels/groups.md +++ b/docs/channels/groups.md @@ -243,7 +243,7 @@ Replying to a bot message counts as an implicit mention (when the channel suppor Notes: -- `mentionPatterns` are case-insensitive regexes. +- `mentionPatterns` are case-insensitive safe regex patterns; invalid patterns and unsafe nested-repetition forms are ignored. - Surfaces that provide explicit mentions still pass; patterns are a fallback. - Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group). - Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured). diff --git a/docs/channels/irc.md b/docs/channels/irc.md index 00403b6f92d..900c531da81 100644 --- a/docs/channels/irc.md +++ b/docs/channels/irc.md @@ -7,6 +7,8 @@ read_when: - You are configuring IRC allowlists, group policy, or mention gating --- +# IRC + Use IRC when you want OpenClaw in classic channels (`#room`) and direct messages. IRC ships as an extension plugin, but it is configured in the main config under `channels.irc`. diff --git a/docs/channels/matrix.md b/docs/channels/matrix.md index 9bb56d1ddb7..4d9d0fa0e4f 100644 --- a/docs/channels/matrix.md +++ b/docs/channels/matrix.md @@ -1,83 +1,70 @@ --- -summary: "Matrix support status, capabilities, and configuration" +summary: "Matrix support status, setup, and configuration examples" read_when: - - Working on Matrix channel features + - Setting up Matrix in OpenClaw + - Configuring Matrix E2EE and verification title: "Matrix" --- # Matrix (plugin) -Matrix is an open, decentralized messaging protocol. OpenClaw connects as a Matrix **user** -on any homeserver, so you need a Matrix account for the bot. Once it is logged in, you can DM -the bot directly or invite it to rooms (Matrix "groups"). Beeper is a valid client option too, -but it requires E2EE to be enabled. - -Status: supported via plugin (@vector-im/matrix-bot-sdk). Direct messages, rooms, threads, media, reactions, -polls (send + poll-start as text), location, and E2EE (with crypto support). +Matrix is the Matrix channel plugin for OpenClaw. +It uses the official `matrix-js-sdk` and supports DMs, rooms, threads, media, reactions, polls, location, and E2EE. ## Plugin required -Matrix ships as a plugin and is not bundled with the core install. +Matrix is a plugin and is not bundled with core OpenClaw. -Install via CLI (npm registry): +Install from npm: ```bash openclaw plugins install @openclaw/matrix ``` -Local checkout (when running from a git repo): +Install from a local checkout: ```bash openclaw plugins install ./extensions/matrix ``` -If you choose Matrix during configure/onboarding and a git checkout is detected, -OpenClaw will offer the local install path automatically. - -Details: [Plugins](/tools/plugin) +See [Plugins](/tools/plugin) for plugin behavior and install rules. ## Setup -1. Install the Matrix plugin: - - From npm: `openclaw plugins install @openclaw/matrix` - - From a local checkout: `openclaw plugins install ./extensions/matrix` -2. Create a Matrix account on a homeserver: - - Browse hosting options at [https://matrix.org/ecosystem/hosting/](https://matrix.org/ecosystem/hosting/) - - Or host it yourself. -3. Get an access token for the bot account: - - Use the Matrix login API with `curl` at your home server: +1. Install the plugin. +2. Create a Matrix account on your homeserver. +3. Configure `channels.matrix` with either: + - `homeserver` + `accessToken`, or + - `homeserver` + `userId` + `password`. +4. Restart the gateway. +5. Start a DM with the bot or invite it to a room. - ```bash - curl --request POST \ - --url https://matrix.example.org/_matrix/client/v3/login \ - --header 'Content-Type: application/json' \ - --data '{ - "type": "m.login.password", - "identifier": { - "type": "m.id.user", - "user": "your-user-name" - }, - "password": "your-password" - }' - ``` +Interactive setup paths: - - Replace `matrix.example.org` with your homeserver URL. - - Or set `channels.matrix.userId` + `channels.matrix.password`: OpenClaw calls the same - login endpoint, stores the access token in `~/.openclaw/credentials/matrix/credentials.json`, - and reuses it on next start. +```bash +openclaw channels add +openclaw configure --section channels +``` -4. Configure credentials: - - Env: `MATRIX_HOMESERVER`, `MATRIX_ACCESS_TOKEN` (or `MATRIX_USER_ID` + `MATRIX_PASSWORD`) - - Or config: `channels.matrix.*` - - If both are set, config takes precedence. - - With access token: user ID is fetched automatically via `/whoami`. - - When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`). -5. Restart the gateway (or finish onboarding). -6. Start a DM with the bot or invite it to a room from any Matrix client - (Element, Beeper, etc.; see [https://matrix.org/ecosystem/clients/](https://matrix.org/ecosystem/clients/)). Beeper requires E2EE, - so set `channels.matrix.encryption: true` and verify the device. +What the Matrix wizard actually asks for: -Minimal config (access token, user ID auto-fetched): +- homeserver URL +- auth method: access token or password +- user ID only when you choose password auth +- optional device name +- whether to enable E2EE +- whether to configure Matrix room access now + +Wizard behavior that matters: + +- If Matrix auth env vars already exist for the selected account, and that account does not already have auth saved in config, the wizard offers an env shortcut and only writes `enabled: true` for that account. +- When you add another Matrix account interactively, the entered account name is normalized into the account ID used in config and env vars. For example, `Ops Bot` becomes `ops-bot`. +- DM allowlist prompts accept full `@user:server` values immediately. Display names only work when live directory lookup finds one exact match; otherwise the wizard asks you to retry with a full Matrix ID. +- Room allowlist prompts accept room IDs and aliases directly. They can also resolve joined-room names live, but unresolved names are only kept as typed during setup and are ignored later by runtime allowlist resolution. Prefer `!room:server` or `#alias:server`. +- Runtime room/session identity uses the stable Matrix room ID. Room-declared aliases are only used as lookup inputs, not as the long-term session key or stable group identity. +- To resolve room names before saving them, use `openclaw channels resolve --channel matrix "Project Room"`. + +Minimal token-based setup: ```json5 { @@ -85,14 +72,14 @@ Minimal config (access token, user ID auto-fetched): matrix: { enabled: true, homeserver: "https://matrix.example.org", - accessToken: "syt_***", + accessToken: "syt_xxx", dm: { policy: "pairing" }, }, }, } ``` -E2EE config (end to end encryption enabled): +Password-based setup (token is cached after login): ```json5 { @@ -100,7 +87,92 @@ E2EE config (end to end encryption enabled): matrix: { enabled: true, homeserver: "https://matrix.example.org", - accessToken: "syt_***", + userId: "@bot:example.org", + password: "replace-me", // pragma: allowlist secret + deviceName: "OpenClaw Gateway", + }, + }, +} +``` + +Matrix stores cached credentials in `~/.openclaw/credentials/matrix/`. +The default account uses `credentials.json`; named accounts use `credentials-.json`. + +Environment variable equivalents (used when the config key is not set): + +- `MATRIX_HOMESERVER` +- `MATRIX_ACCESS_TOKEN` +- `MATRIX_USER_ID` +- `MATRIX_PASSWORD` +- `MATRIX_DEVICE_ID` +- `MATRIX_DEVICE_NAME` + +For non-default accounts, use account-scoped env vars: + +- `MATRIX__HOMESERVER` +- `MATRIX__ACCESS_TOKEN` +- `MATRIX__USER_ID` +- `MATRIX__PASSWORD` +- `MATRIX__DEVICE_ID` +- `MATRIX__DEVICE_NAME` + +Example for account `ops`: + +- `MATRIX_OPS_HOMESERVER` +- `MATRIX_OPS_ACCESS_TOKEN` + +For normalized account ID `ops-bot`, use: + +- `MATRIX_OPS_BOT_HOMESERVER` +- `MATRIX_OPS_BOT_ACCESS_TOKEN` + +The interactive wizard only offers the env-var shortcut when those auth env vars are already present and the selected account does not already have Matrix auth saved in config. + +## Configuration example + +This is a practical baseline config with DM pairing, room allowlist, and E2EE enabled: + +```json5 +{ + channels: { + matrix: { + enabled: true, + homeserver: "https://matrix.example.org", + accessToken: "syt_xxx", + encryption: true, + + dm: { + policy: "pairing", + }, + + groupPolicy: "allowlist", + groupAllowFrom: ["@admin:example.org"], + groups: { + "!roomid:example.org": { + requireMention: true, + }, + }, + + autoJoin: "allowlist", + autoJoinAllowlist: ["!roomid:example.org"], + threadReplies: "inbound", + replyToMode: "off", + }, + }, +} +``` + +## E2EE setup + +Enable encryption: + +```json5 +{ + channels: { + matrix: { + enabled: true, + homeserver: "https://matrix.example.org", + accessToken: "syt_xxx", encryption: true, dm: { policy: "pairing" }, }, @@ -108,60 +180,371 @@ E2EE config (end to end encryption enabled): } ``` -## Encryption (E2EE) +Check verification status: -End-to-end encryption is **supported** via the Rust crypto SDK. +```bash +openclaw matrix verify status +``` -Enable with `channels.matrix.encryption: true`: +Verbose status (full diagnostics): -- If the crypto module loads, encrypted rooms are decrypted automatically. -- Outbound media is encrypted when sending to encrypted rooms. -- On first connection, OpenClaw requests device verification from your other sessions. -- Verify the device in another Matrix client (Element, etc.) to enable key sharing. -- If the crypto module cannot be loaded, E2EE is disabled and encrypted rooms will not decrypt; - OpenClaw logs a warning. -- If you see missing crypto module errors (for example, `@matrix-org/matrix-sdk-crypto-nodejs-*`), - allow build scripts for `@matrix-org/matrix-sdk-crypto-nodejs` and run - `pnpm rebuild @matrix-org/matrix-sdk-crypto-nodejs` or fetch the binary with - `node node_modules/@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js`. +```bash +openclaw matrix verify status --verbose +``` -Crypto state is stored per account + access token in -`~/.openclaw/matrix/accounts//__//crypto/` -(SQLite database). Sync state lives alongside it in `bot-storage.json`. -If the access token (device) changes, a new store is created and the bot must be -re-verified for encrypted rooms. +Include the stored recovery key in machine-readable output: -**Device verification:** -When E2EE is enabled, the bot will request verification from your other sessions on startup. -Open Element (or another client) and approve the verification request to establish trust. -Once verified, the bot can decrypt messages in encrypted rooms. +```bash +openclaw matrix verify status --include-recovery-key --json +``` -## Multi-account +Bootstrap cross-signing and verification state: -Multi-account support: use `channels.matrix.accounts` with per-account credentials and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. +```bash +openclaw matrix verify bootstrap +``` -Each account runs as a separate Matrix user on any homeserver. Per-account config -inherits from the top-level `channels.matrix` settings and can override any option -(DM policy, groups, encryption, etc.). +Verbose bootstrap diagnostics: + +```bash +openclaw matrix verify bootstrap --verbose +``` + +Force a fresh cross-signing identity reset before bootstrapping: + +```bash +openclaw matrix verify bootstrap --force-reset-cross-signing +``` + +Verify this device with a recovery key: + +```bash +openclaw matrix verify device "" +``` + +Verbose device verification details: + +```bash +openclaw matrix verify device "" --verbose +``` + +Check room-key backup health: + +```bash +openclaw matrix verify backup status +``` + +Verbose backup health diagnostics: + +```bash +openclaw matrix verify backup status --verbose +``` + +Restore room keys from server backup: + +```bash +openclaw matrix verify backup restore +``` + +Verbose restore diagnostics: + +```bash +openclaw matrix verify backup restore --verbose +``` + +Delete the current server backup and create a fresh backup baseline: + +```bash +openclaw matrix verify backup reset --yes +``` + +All `verify` commands are concise by default (including quiet internal SDK logging) and show detailed diagnostics only with `--verbose`. +Use `--json` for full machine-readable output when scripting. + +In multi-account setups, Matrix CLI commands use the implicit Matrix default account unless you pass `--account `. +If you configure multiple named accounts, set `channels.matrix.defaultAccount` first or those implicit CLI operations will stop and ask you to choose an account explicitly. +Use `--account` whenever you want verification or device operations to target a named account explicitly: + +```bash +openclaw matrix verify status --account assistant +openclaw matrix verify backup restore --account assistant +openclaw matrix devices list --account assistant +``` + +When encryption is disabled or unavailable for a named account, Matrix warnings and verification errors point at that account's config key, for example `channels.matrix.accounts.assistant.encryption`. + +### What "verified" means + +OpenClaw treats this Matrix device as verified only when it is verified by your own cross-signing identity. +In practice, `openclaw matrix verify status --verbose` exposes three trust signals: + +- `Locally trusted`: this device is trusted by the current client only +- `Cross-signing verified`: the SDK reports the device as verified through cross-signing +- `Signed by owner`: the device is signed by your own self-signing key + +`Verified by owner` becomes `yes` only when cross-signing verification or owner-signing is present. +Local trust by itself is not enough for OpenClaw to treat the device as fully verified. + +### What bootstrap does + +`openclaw matrix verify bootstrap` is the repair and setup command for encrypted Matrix accounts. +It does all of the following in order: + +- bootstraps secret storage, reusing an existing recovery key when possible +- bootstraps cross-signing and uploads missing public cross-signing keys +- attempts to mark and cross-sign the current device +- creates a new server-side room-key backup if one does not already exist + +If the homeserver requires interactive auth to upload cross-signing keys, OpenClaw tries the upload without auth first, then with `m.login.dummy`, then with `m.login.password` when `channels.matrix.password` is configured. + +Use `--force-reset-cross-signing` only when you intentionally want to discard the current cross-signing identity and create a new one. + +If you intentionally want to discard the current room-key backup and start a new backup baseline for future messages, use `openclaw matrix verify backup reset --yes`. +Do this only when you accept that unrecoverable old encrypted history will stay unavailable. + +### Fresh backup baseline + +If you want to keep future encrypted messages working and accept losing unrecoverable old history, run these commands in order: + +```bash +openclaw matrix verify backup reset --yes +openclaw matrix verify backup status --verbose +openclaw matrix verify status +``` + +Add `--account ` to each command when you want to target a named Matrix account explicitly. + +### Startup behavior + +When `encryption: true`, Matrix defaults `startupVerification` to `"if-unverified"`. +On startup, if this device is still unverified, Matrix will request self-verification in another Matrix client, +skip duplicate requests while one is already pending, and apply a local cooldown before retrying after restarts. +Failed request attempts retry sooner than successful request creation by default. +Set `startupVerification: "off"` to disable automatic startup requests, or tune `startupVerificationCooldownHours` +if you want a shorter or longer retry window. + +Startup also performs a conservative crypto bootstrap pass automatically. +That pass tries to reuse the current secret storage and cross-signing identity first, and avoids resetting cross-signing unless you run an explicit bootstrap repair flow. + +If startup finds broken bootstrap state and `channels.matrix.password` is configured, OpenClaw can attempt a stricter repair path. +If the current device is already owner-signed, OpenClaw preserves that identity instead of resetting it automatically. + +Upgrading from the previous public Matrix plugin: + +- OpenClaw automatically reuses the same Matrix account, access token, and device identity when possible. +- Before any actionable Matrix migration changes run, OpenClaw creates or reuses a recovery snapshot under `~/Backups/openclaw-migrations/`. +- If you use multiple Matrix accounts, set `channels.matrix.defaultAccount` before upgrading from the old flat-store layout so OpenClaw knows which account should receive that shared legacy state. +- If the previous plugin stored a Matrix room-key backup decryption key locally, startup or `openclaw doctor --fix` will import it into the new recovery-key flow automatically. +- If the Matrix access token changed after migration was prepared, startup now scans sibling token-hash storage roots for pending legacy restore state before giving up on the automatic backup restore. +- If the Matrix access token changes later for the same account, homeserver, and user, OpenClaw now prefers reusing the most complete existing token-hash storage root instead of starting from an empty Matrix state directory. +- On the next gateway start, backed-up room keys are restored automatically into the new crypto store. +- If the old plugin had local-only room keys that were never backed up, OpenClaw will warn clearly. Those keys cannot be exported automatically from the previous rust crypto store, so some old encrypted history may remain unavailable until recovered manually. +- See [Matrix migration](/install/migrating-matrix) for the full upgrade flow, limits, recovery commands, and common migration messages. + +Encrypted runtime state is organized under per-account, per-user token-hash roots in +`~/.openclaw/matrix/accounts//__//`. +That directory contains the sync store (`bot-storage.json`), crypto store (`crypto/`), +recovery key file (`recovery-key.json`), IndexedDB snapshot (`crypto-idb-snapshot.json`), +thread bindings (`thread-bindings.json`), and startup verification state (`startup-verification.json`) +when those features are in use. +When the token changes but the account identity stays the same, OpenClaw reuses the best existing +root for that account/homeserver/user tuple so prior sync state, crypto state, thread bindings, +and startup verification state remain visible. + +### Node crypto store model + +Matrix E2EE in this plugin uses the official `matrix-js-sdk` Rust crypto path in Node. +That path expects IndexedDB-backed persistence when you want crypto state to survive restarts. + +OpenClaw currently provides that in Node by: + +- using `fake-indexeddb` as the IndexedDB API shim expected by the SDK +- restoring the Rust crypto IndexedDB contents from `crypto-idb-snapshot.json` before `initRustCrypto` +- persisting the updated IndexedDB contents back to `crypto-idb-snapshot.json` after init and during runtime + +This is compatibility/storage plumbing, not a custom crypto implementation. +The snapshot file is sensitive runtime state and is stored with restrictive file permissions. +Under OpenClaw's security model, the gateway host and local OpenClaw state directory are already inside the trusted operator boundary, so this is primarily an operational durability concern rather than a separate remote trust boundary. + +Planned improvement: + +- add SecretRef support for persistent Matrix key material so recovery keys and related store-encryption secrets can be sourced from OpenClaw secrets providers instead of only local files + +## Automatic verification notices + +Matrix now posts verification lifecycle notices directly into the Matrix room as `m.notice` messages. +That includes: + +- verification request notices +- verification ready notices (with explicit "Verify by emoji" guidance) +- verification start and completion notices +- SAS details (emoji and decimal) when available + +Incoming verification requests from another Matrix client are tracked and auto-accepted by OpenClaw. +When SAS emoji verification becomes available, OpenClaw starts that SAS flow automatically for inbound requests and confirms its own side. +You still need to compare the emoji or decimal SAS in your Matrix client and confirm "They match" there to complete the verification. + +OpenClaw does not auto-accept self-initiated duplicate flows blindly. Startup skips creating a new request when a self-verification request is already pending. + +Verification protocol/system notices are not forwarded to the agent chat pipeline, so they do not produce `NO_REPLY`. + +### Device hygiene + +Old OpenClaw-managed Matrix devices can accumulate on the account and make encrypted-room trust harder to reason about. +List them with: + +```bash +openclaw matrix devices list +``` + +Remove stale OpenClaw-managed devices with: + +```bash +openclaw matrix devices prune-stale +``` + +### Direct Room Repair + +If direct-message state gets out of sync, OpenClaw can end up with stale `m.direct` mappings that point at old solo rooms instead of the live DM. Inspect the current mapping for a peer with: + +```bash +openclaw matrix direct inspect --user-id @alice:example.org +``` + +Repair it with: + +```bash +openclaw matrix direct repair --user-id @alice:example.org +``` + +Repair keeps the Matrix-specific logic inside the plugin: + +- it prefers a strict 1:1 DM that is already mapped in `m.direct` +- otherwise it falls back to any currently joined strict 1:1 DM with that user +- if no healthy DM exists, it creates a fresh direct room and rewrites `m.direct` to point at it + +The repair flow does not delete old rooms automatically. It only picks the healthy DM and updates the mapping so new Matrix sends, verification notices, and other direct-message flows target the right room again. + +## Threads + +Matrix supports native Matrix threads for both automatic replies and message-tool sends. + +- `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. +- 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. +- Top-level Matrix room/DM `/focus` creates a new Matrix thread and binds it to the target session when `threadBindings.spawnSubagentSessions=true`. +- Running `/focus` or `/acp spawn --thread here` inside an existing Matrix thread binds that current thread instead. + +### Thread Binding Config + +Matrix inherits global defaults from `session.threadBindings`, and also supports per-channel overrides: + +- `threadBindings.enabled` +- `threadBindings.idleHours` +- `threadBindings.maxAgeHours` +- `threadBindings.spawnSubagentSessions` +- `threadBindings.spawnAcpSessions` + +Matrix thread-bound spawn flags are opt-in: + +- Set `threadBindings.spawnSubagentSessions: true` to allow top-level `/focus` to create and bind new Matrix threads. +- Set `threadBindings.spawnAcpSessions: true` to allow `/acp spawn --thread auto|here` to bind ACP sessions to Matrix threads. + +## Reactions + +Matrix supports outbound reaction actions, inbound reaction notifications, and inbound ack reactions. + +- Outbound reaction tooling is gated by `channels["matrix"].actions.reactions`. +- `react` adds a reaction to a specific Matrix event. +- `reactions` lists the current reaction summary for a specific Matrix event. +- `emoji=""` removes the bot account's own reactions on that event. +- `remove: true` removes only the specified emoji reaction from the bot account. + +Ack reactions use the standard OpenClaw resolution order: + +- `channels["matrix"].accounts..ackReaction` +- `channels["matrix"].ackReaction` +- `messages.ackReaction` +- agent identity emoji fallback + +Ack reaction scope resolves in this order: + +- `channels["matrix"].accounts..ackReactionScope` +- `channels["matrix"].ackReactionScope` +- `messages.ackReactionScope` + +Reaction notification mode resolves in this order: + +- `channels["matrix"].accounts..reactionNotifications` +- `channels["matrix"].reactionNotifications` +- default: `own` + +Current behavior: + +- `reactionNotifications: "own"` forwards added `m.reaction` events when they target bot-authored Matrix messages. +- `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. + +## DM and room policy example + +```json5 +{ + channels: { + matrix: { + dm: { + policy: "allowlist", + allowFrom: ["@admin:example.org"], + }, + groupPolicy: "allowlist", + groupAllowFrom: ["@admin:example.org"], + groups: { + "!roomid:example.org": { + requireMention: true, + }, + }, + }, + }, +} +``` + +See [Groups](/channels/groups) for mention-gating and allowlist behavior. + +Pairing example for Matrix DMs: + +```bash +openclaw pairing list matrix +openclaw pairing approve matrix +``` + +If an unapproved Matrix user keeps messaging you before approval, OpenClaw reuses the same pending pairing code and may send a reminder reply again after a short cooldown instead of minting a new code. + +See [Pairing](/channels/pairing) for the shared DM pairing flow and storage layout. + +## Multi-account example ```json5 { channels: { matrix: { enabled: true, + defaultAccount: "assistant", dm: { policy: "pairing" }, accounts: { assistant: { - name: "Main assistant", homeserver: "https://matrix.example.org", - accessToken: "syt_assistant_***", + accessToken: "syt_assistant_xxx", encryption: true, }, alerts: { - name: "Alerts bot", homeserver: "https://matrix.example.org", - accessToken: "syt_alerts_***", - dm: { policy: "allowlist", allowFrom: ["@admin:example.org"] }, + accessToken: "syt_alerts_xxx", + dm: { + policy: "allowlist", + allowFrom: ["@ops:example.org"], + }, }, }, }, @@ -169,135 +552,60 @@ inherits from the top-level `channels.matrix` settings and can override any opti } ``` -Notes: +Top-level `channels.matrix` values act as defaults for named accounts unless an account overrides them. +Set `defaultAccount` when you want OpenClaw to prefer one named Matrix account for implicit routing, probing, and CLI operations. +If you configure multiple named accounts, set `defaultAccount` or pass `--account ` for CLI commands that rely on implicit account selection. +Pass `--account ` to `openclaw matrix verify ...` and `openclaw matrix devices ...` when you want to override that implicit selection for one command. -- Account startup is serialized to avoid race conditions with concurrent module imports. -- Env variables (`MATRIX_HOMESERVER`, `MATRIX_ACCESS_TOKEN`, etc.) only apply to the **default** account. -- Base channel settings (DM policy, group policy, mention gating, etc.) apply to all accounts unless overridden per account. -- Use `bindings[].match.accountId` to route each account to a different agent. -- Crypto state is stored per account + access token (separate key stores per account). +## Target resolution -## Routing model +Matrix accepts these target forms anywhere OpenClaw asks you for a room or user target: -- Replies always go back to Matrix. -- DMs share the agent's main session; rooms map to group sessions. +- Users: `@user:server`, `user:@user:server`, or `matrix:user:@user:server` +- Rooms: `!room:server`, `room:!room:server`, or `matrix:room:!room:server` +- Aliases: `#alias:server`, `channel:#alias:server`, or `matrix:channel:#alias:server` -## Access control (DMs) +Live directory lookup uses the logged-in Matrix account: -- Default: `channels.matrix.dm.policy = "pairing"`. Unknown senders get a pairing code. -- Approve via: - - `openclaw pairing list matrix` - - `openclaw pairing approve matrix ` -- Public DMs: `channels.matrix.dm.policy="open"` plus `channels.matrix.dm.allowFrom=["*"]`. -- `channels.matrix.dm.allowFrom` accepts full Matrix user IDs (example: `@user:server`). The wizard resolves display names to user IDs when directory search finds a single exact match. -- Do not use display names or bare localparts (example: `"Alice"` or `"alice"`). They are ambiguous and are ignored for allowlist matching. Use full `@user:server` IDs. +- User lookups query the Matrix user directory on that homeserver. +- Room lookups accept explicit room IDs and aliases directly, then fall back to searching joined room names for that account. +- Joined-room name lookup is best-effort. If a room name cannot be resolved to an ID or alias, it is ignored by runtime allowlist resolution. -## Rooms (groups) +## Configuration reference -- Default: `channels.matrix.groupPolicy = "allowlist"` (mention-gated). Use `channels.defaults.groupPolicy` to override the default when unset. -- Runtime note: if `channels.matrix` is completely missing, runtime falls back to `groupPolicy="allowlist"` for room checks (even if `channels.defaults.groupPolicy` is set). -- Allowlist rooms with `channels.matrix.groups` (room IDs or aliases; names are resolved to IDs when directory search finds a single exact match): - -```json5 -{ - channels: { - matrix: { - groupPolicy: "allowlist", - groups: { - "!roomId:example.org": { allow: true }, - "#alias:example.org": { allow: true }, - }, - groupAllowFrom: ["@owner:example.org"], - }, - }, -} -``` - -- `requireMention: false` enables auto-reply in that room. -- `groups."*"` can set defaults for mention gating across rooms. -- `groupAllowFrom` restricts which senders can trigger the bot in rooms (full Matrix user IDs). -- Per-room `users` allowlists can further restrict senders inside a specific room (use full Matrix user IDs). -- The configure wizard prompts for room allowlists (room IDs, aliases, or names) and resolves names only on an exact, unique match. -- On startup, OpenClaw resolves room/user names in allowlists to IDs and logs the mapping; unresolved entries are ignored for allowlist matching. -- Invites are auto-joined by default; control with `channels.matrix.autoJoin` and `channels.matrix.autoJoinAllowlist`. -- To allow **no rooms**, set `channels.matrix.groupPolicy: "disabled"` (or keep an empty allowlist). -- Legacy key: `channels.matrix.rooms` (same shape as `groups`). - -## Threads - -- Reply threading is supported. -- `channels.matrix.threadReplies` controls whether replies stay in threads: - - `off`, `inbound` (default), `always` -- `channels.matrix.replyToMode` controls reply-to metadata when not replying in a thread: - - `off` (default), `first`, `all` - -## Capabilities - -| Feature | Status | -| --------------- | ------------------------------------------------------------------------------------- | -| Direct messages | ✅ Supported | -| Rooms | ✅ Supported | -| Threads | ✅ Supported | -| Media | ✅ Supported | -| E2EE | ✅ Supported (crypto module required) | -| Reactions | ✅ Supported (send/read via tools) | -| Polls | ✅ Send supported; inbound poll starts are converted to text (responses/ends ignored) | -| Location | ✅ Supported (geo URI; altitude ignored) | -| Native commands | ✅ Supported | - -## Troubleshooting - -Run this ladder first: - -```bash -openclaw status -openclaw gateway status -openclaw logs --follow -openclaw doctor -openclaw channels status --probe -``` - -Then confirm DM pairing state if needed: - -```bash -openclaw pairing list matrix -``` - -Common failures: - -- Logged in but room messages ignored: room blocked by `groupPolicy` or room allowlist. -- DMs ignored: sender pending approval when `channels.matrix.dm.policy="pairing"`. -- Encrypted rooms fail: crypto support or encryption settings mismatch. - -For triage flow: [/channels/troubleshooting](/channels/troubleshooting). - -## Configuration reference (Matrix) - -Full configuration: [Configuration](/gateway/configuration) - -Provider options: - -- `channels.matrix.enabled`: enable/disable channel startup. -- `channels.matrix.homeserver`: homeserver URL. -- `channels.matrix.userId`: Matrix user ID (optional with access token). -- `channels.matrix.accessToken`: access token. -- `channels.matrix.password`: password for login (token stored). -- `channels.matrix.deviceName`: device display name. -- `channels.matrix.encryption`: enable E2EE (default: false). -- `channels.matrix.initialSyncLimit`: initial sync limit. -- `channels.matrix.threadReplies`: `off | inbound | always` (default: inbound). -- `channels.matrix.textChunkLimit`: outbound text chunk size (chars). -- `channels.matrix.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking. -- `channels.matrix.dm.policy`: `pairing | allowlist | open | disabled` (default: pairing). -- `channels.matrix.dm.allowFrom`: DM allowlist (full Matrix user IDs). `open` requires `"*"`. The wizard resolves names to IDs when possible. -- `channels.matrix.groupPolicy`: `allowlist | open | disabled` (default: allowlist). -- `channels.matrix.groupAllowFrom`: allowlisted senders for group messages (full Matrix user IDs). -- `channels.matrix.allowlistOnly`: force allowlist rules for DMs + rooms. -- `channels.matrix.groups`: group allowlist + per-room settings map. -- `channels.matrix.rooms`: legacy group allowlist/config. -- `channels.matrix.replyToMode`: reply-to mode for threads/tags. -- `channels.matrix.mediaMaxMb`: inbound/outbound media cap (MB). -- `channels.matrix.autoJoin`: invite handling (`always | allowlist | off`, default: always). -- `channels.matrix.autoJoinAllowlist`: allowed room IDs/aliases for auto-join. -- `channels.matrix.accounts`: multi-account configuration keyed by account ID (each account inherits top-level settings). -- `channels.matrix.actions`: per-action tool gating (reactions/messages/pins/memberInfo/channelInfo). +- `enabled`: enable or disable the channel. +- `name`: optional label for the account. +- `defaultAccount`: preferred account ID when multiple Matrix accounts are configured. +- `homeserver`: homeserver URL, for example `https://matrix.example.org`. +- `userId`: full Matrix user ID, for example `@bot:example.org`. +- `accessToken`: access token for token-based auth. +- `password`: password for password-based login. +- `deviceId`: explicit Matrix device ID. +- `deviceName`: device display name for password login. +- `avatarUrl`: stored self-avatar URL for profile sync and `set-profile` updates. +- `initialSyncLimit`: startup sync event limit. +- `encryption`: enable E2EE. +- `allowlistOnly`: force allowlist-only behavior for DMs and rooms. +- `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. +- `replyToMode`: `off`, `first`, or `all`. +- `threadReplies`: `off`, `inbound`, or `always`. +- `threadBindings`: per-channel overrides for thread-bound session routing and lifecycle. +- `startupVerification`: automatic self-verification request mode on startup (`if-unverified`, `off`). +- `startupVerificationCooldownHours`: cooldown before retrying automatic startup verification requests. +- `textChunkLimit`: outbound message chunk size. +- `chunkMode`: `length` or `newline`. +- `responsePrefix`: optional message prefix for outbound replies. +- `ackReaction`: optional ack reaction override for this channel/account. +- `ackReactionScope`: optional ack reaction scope override (`group-mentions`, `group-all`, `direct`, `all`, `none`, `off`). +- `reactionNotifications`: inbound reaction notification mode (`own`, `off`). +- `mediaMaxMb`: outbound media size cap in MB. +- `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`). +- `dm.allowFrom` entries should be full Matrix user IDs unless you already resolved them through live directory lookup. +- `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`. +- `actions`: per-action tool gating (`messages`, `reactions`, `pins`, `profile`, `memberInfo`, `channelInfo`, `verification`). diff --git a/docs/channels/mattermost.md b/docs/channels/mattermost.md index 6a7ee8bb472..41f6ffa19a0 100644 --- a/docs/channels/mattermost.md +++ b/docs/channels/mattermost.md @@ -28,7 +28,7 @@ Local checkout (when running from a git repo): openclaw plugins install ./extensions/mattermost ``` -If you choose Mattermost during configure/onboarding and a git checkout is detected, +If you choose Mattermost during setup and a git checkout is detected, OpenClaw will offer the local install path automatically. Details: [Plugins](/tools/plugin) @@ -129,6 +129,35 @@ Notes: - `onchar` still responds to explicit @mentions. - `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred. +## Threading and sessions + +Use `channels.mattermost.replyToMode` to control whether channel and group replies stay in the +main channel or start a thread under the triggering post. + +- `off` (default): only reply in a thread when the inbound post is already in one. +- `first`: for top-level channel/group posts, start a thread under that post and route the + conversation to a thread-scoped session. +- `all`: same behavior as `first` for Mattermost today. +- Direct messages ignore this setting and stay non-threaded. + +Config example: + +```json5 +{ + channels: { + mattermost: { + replyToMode: "all", + }, + }, +} +``` + +Notes: + +- Thread-scoped sessions use the triggering post id as the thread root. +- `first` and `all` are currently equivalent because once Mattermost has a thread root, + follow-up chunks and media continue in that same thread. + ## Access control (DMs) - Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code). @@ -162,6 +191,35 @@ OpenClaw resolves them **user-first**: If you need deterministic behavior, always use the explicit prefixes (`user:` / `channel:`). +## DM channel retry + +When OpenClaw sends to a Mattermost DM target and needs to resolve the direct channel first, it +retries transient direct-channel creation failures by default. + +Use `channels.mattermost.dmChannelRetry` to tune that behavior globally for the Mattermost plugin, +or `channels.mattermost.accounts..dmChannelRetry` for one account. + +```json5 +{ + channels: { + mattermost: { + dmChannelRetry: { + maxRetries: 3, + initialDelayMs: 1000, + maxDelayMs: 10000, + timeoutMs: 30000, + }, + }, + }, +} +``` + +Notes: + +- This applies only to DM channel creation (`/api/v4/channels/direct`), not every Mattermost API call. +- Retries apply to transient failures such as rate limits, 5xx responses, and network or timeout errors. +- 4xx client errors other than `429` are treated as permanent and are not retried. + ## Reactions (message tool) - Use `message action=react` with `channel=mattermost`. diff --git a/docs/channels/msteams.md b/docs/channels/msteams.md index 9c4a583e1b5..88cba3ce6aa 100644 --- a/docs/channels/msteams.md +++ b/docs/channels/msteams.md @@ -33,7 +33,7 @@ Local checkout (when running from a git repo): openclaw plugins install ./extensions/msteams ``` -If you choose Teams during configure/onboarding and a git checkout is detected, +If you choose Teams during setup and a git checkout is detected, OpenClaw will offer the local install path automatically. Details: [Plugins](/tools/plugin) @@ -114,11 +114,11 @@ Example: **Teams + channel allowlist** - Scope group/channel replies by listing teams and channels under `channels.msteams.teams`. -- Keys can be team IDs or names; channel keys can be conversation IDs or names. +- Keys should use stable team IDs and channel conversation IDs. - When `groupPolicy="allowlist"` and a teams allowlist is present, only listed teams/channels are accepted (mention‑gated). - The configure wizard accepts `Team/Channel` entries and stores them for you. - On startup, OpenClaw resolves team/channel and user allowlist names to IDs (when Graph permissions allow) - and logs the mapping; unresolved entries are kept as typed. + and logs the mapping; unresolved team/channel names are kept as typed but ignored for routing by default unless `channels.msteams.dangerouslyAllowNameMatching: true` is enabled. Example: @@ -457,7 +457,7 @@ Key settings (see `/gateway/configuration` for shared channel patterns): - `channels.msteams.webhook.path` (default `/api/messages`) - `channels.msteams.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing) - `channels.msteams.allowFrom`: DM allowlist (AAD object IDs recommended). The wizard resolves names to IDs during setup when Graph access is available. -- `channels.msteams.dangerouslyAllowNameMatching`: break-glass toggle to re-enable mutable UPN/display-name matching. +- `channels.msteams.dangerouslyAllowNameMatching`: break-glass toggle to re-enable mutable UPN/display-name matching and direct team/channel name routing. - `channels.msteams.textChunkLimit`: outbound text chunk size. - `channels.msteams.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking. - `channels.msteams.mediaAllowHosts`: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains). diff --git a/docs/channels/nextcloud-talk.md b/docs/channels/nextcloud-talk.md index 7797b1276ff..f8be8d74f0c 100644 --- a/docs/channels/nextcloud-talk.md +++ b/docs/channels/nextcloud-talk.md @@ -25,7 +25,7 @@ Local checkout (when running from a git repo): openclaw plugins install ./extensions/nextcloud-talk ``` -If you choose Nextcloud Talk during configure/onboarding and a git checkout is detected, +If you choose Nextcloud Talk during setup and a git checkout is detected, OpenClaw will offer the local install path automatically. Details: [Plugins](/tools/plugin) @@ -43,7 +43,7 @@ Details: [Plugins](/tools/plugin) 4. Configure OpenClaw: - Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret` - Or env: `NEXTCLOUD_TALK_BOT_SECRET` (default account only) -5. Restart the gateway (or finish onboarding). +5. Restart the gateway (or finish setup). Minimal config: diff --git a/docs/channels/nostr.md b/docs/channels/nostr.md index 3368933d6c4..c8d5d69753b 100644 --- a/docs/channels/nostr.md +++ b/docs/channels/nostr.md @@ -16,7 +16,7 @@ Nostr is a decentralized protocol for social networking. This channel enables Op ### Onboarding (recommended) -- The onboarding wizard (`openclaw onboard`) and `openclaw channels add` list optional channel plugins. +- Onboarding (`openclaw onboard`) and `openclaw channels add` list optional channel plugins. - Selecting Nostr prompts you to install the plugin on demand. Install defaults: @@ -40,6 +40,15 @@ openclaw plugins install --link /extensions/nostr Restart the Gateway after installing or enabling plugins. +### Non-interactive setup + +```bash +openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY" +openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY" --relay-urls "wss://relay.damus.io,wss://relay.primal.net" +``` + +Use `--use-env` to keep `NOSTR_PRIVATE_KEY` in the environment instead of storing the key in config. + ## Quick setup 1. Generate a Nostr keypair (if needed): diff --git a/docs/channels/pairing.md b/docs/channels/pairing.md index d402de16662..1ba3c6c92f2 100644 --- a/docs/channels/pairing.md +++ b/docs/channels/pairing.md @@ -72,7 +72,7 @@ If you use the `device-pair` plugin, you can do first-time device pairing entire The setup code is a base64-encoded JSON payload that contains: - `url`: the Gateway WebSocket URL (`ws://...` or `wss://...`) -- `token`: a short-lived pairing token +- `bootstrapToken`: a short-lived single-device bootstrap token used for the initial pairing handshake Treat the setup code like a password while it is valid. diff --git a/docs/channels/signal.md b/docs/channels/signal.md index b216af120ce..cfc050b6e75 100644 --- a/docs/channels/signal.md +++ b/docs/channels/signal.md @@ -195,6 +195,8 @@ Groups: - `channels.signal.groupPolicy = open | allowlist | disabled`. - `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set. +- `channels.signal.groups["" | "*"]` can override group behavior with `requireMention`, `tools`, and `toolsBySender`. +- Use `channels.signal.accounts..groups` for per-account overrides in multi-account setups. - Runtime note: if `channels.signal` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set). ## How it works (behavior) @@ -312,6 +314,8 @@ Provider options: - `channels.signal.allowFrom`: DM allowlist (E.164 or `uuid:`). `open` requires `"*"`. Signal has no usernames; use phone/UUID ids. - `channels.signal.groupPolicy`: `open | allowlist | disabled` (default: allowlist). - `channels.signal.groupAllowFrom`: group sender allowlist. +- `channels.signal.groups`: per-group overrides keyed by Signal group id (or `"*"`). Supported fields: `requireMention`, `tools`, `toolsBySender`. +- `channels.signal.accounts..groups`: per-account version of `channels.signal.groups` for multi-account setups. - `channels.signal.historyLimit`: max group messages to include as context (0 disables). - `channels.signal.dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `channels.signal.dms[""].historyLimit`. - `channels.signal.textChunkLimit`: outbound chunk size (chars). diff --git a/docs/channels/slack.md b/docs/channels/slack.md index c099120c699..aa9127ea630 100644 --- a/docs/channels/slack.md +++ b/docs/channels/slack.md @@ -169,15 +169,15 @@ For actions/directory reads, user token can be preferred when configured. For wr - `allowlist` - `disabled` - Channel allowlist lives under `channels.slack.channels`. + Channel allowlist lives under `channels.slack.channels` and should use stable channel IDs. Runtime note: if `channels.slack` is completely missing (env-only setup), runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set). Name/ID resolution: - channel allowlist entries and DM allowlist entries are resolved at startup when token access allows - - unresolved entries are kept as configured - - inbound authorization matching is ID-first by default; direct username/slug matching requires `channels.slack.dangerouslyAllowNameMatching: true` + - unresolved channel-name entries are kept as configured but ignored for routing by default + - inbound authorization and channel routing are ID-first by default; direct username/slug matching requires `channels.slack.dangerouslyAllowNameMatching: true` @@ -190,7 +190,7 @@ For actions/directory reads, user token can be preferred when configured. For wr - mention regex patterns (`agents.list[].groupChat.mentionPatterns`, fallback `messages.groupChat.mentionPatterns`) - implicit reply-to-bot thread behavior - Per-channel controls (`channels.slack.channels.`): + Per-channel controls (`channels.slack.channels.`; names only via startup resolution or `dangerouslyAllowNameMatching`): - `requireMention` - `users` (allowlist) @@ -218,6 +218,55 @@ For actions/directory reads, user token can be preferred when configured. For wr - if encoded option values exceed Slack limits, the flow falls back to buttons - For long option payloads, Slash command argument menus use a confirm dialog before dispatching a selected value. +## Interactive replies + +Slack can render agent-authored interactive reply controls, but this feature is disabled by default. + +Enable it globally: + +```json5 +{ + channels: { + slack: { + capabilities: { + interactiveReplies: true, + }, + }, + }, +} +``` + +Or enable it for one Slack account only: + +```json5 +{ + channels: { + slack: { + accounts: { + ops: { + capabilities: { + interactiveReplies: true, + }, + }, + }, + }, + }, +} +``` + +When enabled, agents can emit Slack-only reply directives: + +- `[[slack_buttons: Approve:approve, Reject:reject]]` +- `[[slack_select: Choose a target | Canary:canary, Production:production]]` + +These directives compile into Slack Block Kit and route clicks or selections back through the existing Slack interaction event path. + +Notes: + +- This is Slack-specific UI. Other channels do not translate Slack Block Kit directives into their own button systems. +- The interactive callback values are OpenClaw-generated opaque tokens, not raw agent-authored values. +- If generated interactive blocks would exceed Slack Block Kit limits, OpenClaw falls back to the original text reply instead of sending an invalid blocks payload. + Default slash command settings: - `enabled: false` diff --git a/docs/channels/synology-chat.md b/docs/channels/synology-chat.md index 89e96b318a3..aae655f27b7 100644 --- a/docs/channels/synology-chat.md +++ b/docs/channels/synology-chat.md @@ -27,13 +27,17 @@ Details: [Plugins](/tools/plugin) ## Quick setup 1. Install and enable the Synology Chat plugin. + - `openclaw onboard` now shows Synology Chat in the same channel setup list as `openclaw channels add`. + - Non-interactive setup: `openclaw channels add --channel synology-chat --token --url ` 2. In Synology Chat integrations: - Create an incoming webhook and copy its URL. - Create an outgoing webhook with your secret token. 3. Point the outgoing webhook URL to your OpenClaw gateway: - `https://gateway-host/webhook/synology` by default. - Or your custom `channels.synology-chat.webhookPath`. -4. Configure `channels.synology-chat` in OpenClaw. +4. Finish setup in OpenClaw. + - Guided: `openclaw onboard` + - Direct: `openclaw channels add --channel synology-chat --token --url ` 5. Restart gateway and send a DM to the Synology Chat bot. Minimal config: diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md index f2467d12b0a..2758982b8d7 100644 --- a/docs/channels/telegram.md +++ b/docs/channels/telegram.md @@ -115,7 +115,7 @@ Token resolution order is account-aware. In practice, config values win over env `channels.telegram.allowFrom` accepts numeric Telegram user IDs. `telegram:` / `tg:` prefixes are accepted and normalized. `dmPolicy: "allowlist"` with empty `allowFrom` blocks all DMs and is rejected by config validation. - The onboarding wizard accepts `@username` input and resolves it to numeric IDs. + Onboarding accepts `@username` input and resolves it to numeric IDs. If you upgraded and your config contains `@username` allowlist entries, run `openclaw doctor --fix` to resolve them (best-effort; requires a Telegram bot token). If you previously relied on pairing-store allowlist files, `openclaw doctor --fix` can recover entries into `channels.telegram.allowFrom` in allowlist flows (for example when `dmPolicy: "allowlist"` has no explicit IDs yet). @@ -335,9 +335,10 @@ curl "https://api.telegram.org/bot/getUpdates" If native commands are disabled, built-ins are removed. Custom/plugin commands may still register if configured. - Common setup failure: + Common setup failures: - - `setMyCommands failed` usually means outbound DNS/HTTPS to `api.telegram.org` is blocked. + - `setMyCommands failed` with `BOT_COMMANDS_TOO_MUCH` means the Telegram menu still overflowed after trimming; reduce plugin/skill/custom commands or disable `channels.telegram.commands.native`. + - `setMyCommands failed` with network/fetch errors usually means outbound DNS/HTTPS to `api.telegram.org` is blocked. ### Device pairing commands (`device-pair` plugin) @@ -781,6 +782,11 @@ openclaw message poll --channel telegram --target -1001234567890:topic:42 \ - `--poll-public` - `--thread-id` for forum topics (or use a `:topic:` target) + Telegram send also supports: + + - `--buttons` for inline keyboards when `channels.telegram.capabilities.inlineButtons` allows it + - `--force-document` to send outbound images and GIFs as documents instead of compressed photo or animated-media uploads + Action gating: - `channels.telegram.actions.sendMessage=false` disables outbound Telegram messages, including polls @@ -843,7 +849,8 @@ openclaw message poll --channel telegram --target -1001234567890:topic:42 \ - authorize your sender identity (pairing and/or numeric `allowFrom`) - command authorization still applies even when group policy is `open` - - `setMyCommands failed` usually indicates DNS/HTTPS reachability issues to `api.telegram.org` + - `setMyCommands failed` with `BOT_COMMANDS_TOO_MUCH` means the native menu has too many entries; reduce plugin/skill/custom commands or disable native menus + - `setMyCommands failed` with network/fetch errors usually indicates DNS/HTTPS reachability issues to `api.telegram.org` diff --git a/docs/channels/troubleshooting.md b/docs/channels/troubleshooting.md index 2848947c479..a7850801948 100644 --- a/docs/channels/troubleshooting.md +++ b/docs/channels/troubleshooting.md @@ -44,12 +44,13 @@ Full troubleshooting: [/channels/whatsapp#troubleshooting-quick](/channels/whats ### Telegram failure signatures -| Symptom | Fastest check | Fix | -| --------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------- | -| `/start` but no usable reply flow | `openclaw pairing list telegram` | Approve pairing or change DM policy. | -| Bot online but group stays silent | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot. | -| Send failures with network errors | Inspect logs for Telegram API call failures | Fix DNS/IPv6/proxy routing to `api.telegram.org`. | -| Upgraded and allowlist blocks you | `openclaw security audit` and config allowlists | Run `openclaw doctor --fix` or replace `@username` with numeric sender IDs. | +| Symptom | Fastest check | Fix | +| ----------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------- | +| `/start` but no usable reply flow | `openclaw pairing list telegram` | Approve pairing or change DM policy. | +| Bot online but group stays silent | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot. | +| Send failures with network errors | Inspect logs for Telegram API call failures | Fix DNS/IPv6/proxy routing to `api.telegram.org`. | +| `setMyCommands` rejected at startup | Inspect logs for `BOT_COMMANDS_TOO_MUCH` | Reduce plugin/skill/custom Telegram commands or disable native menus. | +| Upgraded and allowlist blocks you | `openclaw security audit` and config allowlists | Run `openclaw doctor --fix` or replace `@username` with numeric sender IDs. | Full troubleshooting: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting) diff --git a/docs/channels/twitch.md b/docs/channels/twitch.md index 32670f31540..d184a2d8432 100644 --- a/docs/channels/twitch.md +++ b/docs/channels/twitch.md @@ -255,7 +255,7 @@ openclaw doctor openclaw channels status --probe ``` -### Bot doesn't respond to messages +### Bot does not respond to messages **Check access control:** Ensure your user ID is in `allowFrom`, or temporarily remove `allowFrom` and set `allowedRoles: ["all"]` to test. diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md index cad9fe77ee3..681c67ef016 100644 --- a/docs/channels/whatsapp.md +++ b/docs/channels/whatsapp.md @@ -9,6 +9,21 @@ title: "WhatsApp" Status: production-ready via WhatsApp Web (Baileys). Gateway owns linked session(s). +## Install (on demand) + +- Onboarding (`openclaw onboard`) and `openclaw channels add --channel whatsapp` + prompt to install the WhatsApp plugin the first time you select it. +- `openclaw channels login --channel whatsapp` also offers the install flow when + the plugin is not present yet. +- Dev channel + git checkout: defaults to the local plugin path. +- Stable/Beta: defaults to the npm package `@openclaw/whatsapp`. + +Manual install stays available: + +```bash +openclaw plugins install @openclaw/whatsapp +``` + Default DM policy is pairing for unknown senders. @@ -76,7 +91,7 @@ openclaw pairing approve whatsapp -OpenClaw recommends running WhatsApp on a separate number when possible. (The channel metadata and onboarding flow are optimized for that setup, but personal-number setups are also supported.) +OpenClaw recommends running WhatsApp on a separate number when possible. (The channel metadata and setup flow are optimized for that setup, but personal-number setups are also supported.) ## Deployment patterns diff --git a/docs/channels/zalo.md b/docs/channels/zalo.md index 77b288b0ab7..b327f596f74 100644 --- a/docs/channels/zalo.md +++ b/docs/channels/zalo.md @@ -7,14 +7,14 @@ title: "Zalo" # Zalo (Bot API) -Status: experimental. DMs are supported; group handling is available with explicit group policy controls. +Status: experimental. DMs are supported. The [Capabilities](#capabilities) section below reflects current Marketplace-bot behavior. ## Plugin required Zalo ships as a plugin and is not bundled with the core install. - Install via CLI: `openclaw plugins install @openclaw/zalo` -- Or select **Zalo** during onboarding and confirm the install prompt +- Or select **Zalo** during setup and confirm the install prompt - Details: [Plugins](/tools/plugin) ## Quick setup (beginner) @@ -22,11 +22,11 @@ Zalo ships as a plugin and is not bundled with the core install. 1. Install the Zalo plugin: - From a source checkout: `openclaw plugins install ./extensions/zalo` - From npm (if published): `openclaw plugins install @openclaw/zalo` - - Or pick **Zalo** in onboarding and confirm the install prompt + - Or pick **Zalo** in setup and confirm the install prompt 2. Set the token: - Env: `ZALO_BOT_TOKEN=...` - - Or config: `channels.zalo.botToken: "..."`. -3. Restart the gateway (or finish onboarding). + - Or config: `channels.zalo.accounts.default.botToken: "..."`. +3. Restart the gateway (or finish setup). 4. DM access is pairing by default; approve the pairing code on first contact. Minimal config: @@ -36,8 +36,12 @@ Minimal config: channels: { zalo: { enabled: true, - botToken: "12345689:abc-xyz", - dmPolicy: "pairing", + accounts: { + default: { + botToken: "12345689:abc-xyz", + dmPolicy: "pairing", + }, + }, }, }, } @@ -48,10 +52,13 @@ Minimal config: Zalo is a Vietnam-focused messaging app; its Bot API lets the Gateway run a bot for 1:1 conversations. It is a good fit for support or notifications where you want deterministic routing back to Zalo. +This page reflects current OpenClaw behavior for **Zalo Bot Creator / Marketplace bots**. +**Zalo Official Account (OA) bots** are a different Zalo product surface and may behave differently. + - A Zalo Bot API channel owned by the Gateway. - Deterministic routing: replies go back to Zalo; the model never chooses channels. - DMs share the agent's main session. -- Groups are supported with policy controls (`groupPolicy` + `groupAllowFrom`) and default to fail-closed allowlist behavior. +- The [Capabilities](#capabilities) section below shows current Marketplace-bot support. ## Setup (fast path) @@ -59,7 +66,7 @@ It is a good fit for support or notifications where you want deterministic routi 1. Go to [https://bot.zaloplatforms.com](https://bot.zaloplatforms.com) and sign in. 2. Create a new bot and configure its settings. -3. Copy the bot token (format: `12345689:abc-xyz`). +3. Copy the full bot token (typically `numeric_id:secret`). For Marketplace bots, the usable runtime token may appear in the bot's welcome message after creation. ### 2) Configure the token (env or config) @@ -70,13 +77,19 @@ Example: channels: { zalo: { enabled: true, - botToken: "12345689:abc-xyz", - dmPolicy: "pairing", + accounts: { + default: { + botToken: "12345689:abc-xyz", + dmPolicy: "pairing", + }, + }, }, }, } ``` +If you later move to a Zalo bot surface where groups are available, you can add group-specific config such as `groupPolicy` and `groupAllowFrom` explicitly. For current Marketplace-bot behavior, see [Capabilities](#capabilities). + Env option: `ZALO_BOT_TOKEN=...` (works for the default account only). Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`. @@ -109,14 +122,23 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and ## Access control (Groups) +For **Zalo Bot Creator / Marketplace bots**, group support was not available in practice because the bot could not be added to a group at all. + +That means the group-related config keys below exist in the schema, but were not usable for Marketplace bots: + - `channels.zalo.groupPolicy` controls group inbound handling: `open | allowlist | disabled`. -- Default behavior is fail-closed: `allowlist`. - `channels.zalo.groupAllowFrom` restricts which sender IDs can trigger the bot in groups. - If `groupAllowFrom` is unset, Zalo falls back to `allowFrom` for sender checks. -- `groupPolicy: "disabled"` blocks all group messages. -- `groupPolicy: "open"` allows any group member (mention-gated). - Runtime note: if `channels.zalo` is missing entirely, runtime still falls back to `groupPolicy="allowlist"` for safety. +The group policy values (when group access is available on your bot surface) are: + +- `groupPolicy: "disabled"` — blocks all group messages. +- `groupPolicy: "open"` — allows any group member (mention-gated). +- `groupPolicy: "allowlist"` — fail-closed default; only allowed senders are accepted. + +If you are using a different Zalo bot product surface and have verified working group behavior, document that separately rather than assuming it matches the Marketplace-bot flow. + ## Long-polling vs webhook - Default: long-polling (no public URL required). @@ -133,23 +155,36 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and ## Supported message types +For a quick support snapshot, see [Capabilities](#capabilities). The notes below add detail where the behavior needs extra context. + - **Text messages**: Full support with 2000 character chunking. -- **Image messages**: Download and process inbound images; send images via `sendPhoto`. -- **Stickers**: Logged but not fully processed (no agent response). -- **Unsupported types**: Logged (e.g., messages from protected users). +- **Plain URLs in text**: Behave like normal text input. +- **Link previews / rich link cards**: See the Marketplace-bot status in [Capabilities](#capabilities); they did not reliably trigger a reply. +- **Image messages**: See the Marketplace-bot status in [Capabilities](#capabilities); inbound image handling was unreliable (typing indicator without a final reply). +- **Stickers**: See the Marketplace-bot status in [Capabilities](#capabilities). +- **Voice notes / audio files / video / generic file attachments**: See the Marketplace-bot status in [Capabilities](#capabilities). +- **Unsupported types**: Logged (for example, messages from protected users). ## Capabilities -| Feature | Status | -| --------------- | -------------------------------------------------------- | -| Direct messages | ✅ Supported | -| Groups | ⚠️ Supported with policy controls (allowlist by default) | -| Media (images) | ✅ Supported | -| Reactions | ❌ Not supported | -| Threads | ❌ Not supported | -| Polls | ❌ Not supported | -| Native commands | ❌ Not supported | -| Streaming | ⚠️ Blocked (2000 char limit) | +This table summarizes current **Zalo Bot Creator / Marketplace bot** behavior in OpenClaw. + +| Feature | Status | +| --------------------------- | --------------------------------------- | +| Direct messages | ✅ Supported | +| Groups | ❌ Not available for Marketplace bots | +| Media (inbound images) | ⚠️ Limited / verify in your environment | +| Media (outbound images) | ⚠️ Not re-tested for Marketplace bots | +| Plain URLs in text | ✅ Supported | +| Link previews | ⚠️ Unreliable for Marketplace bots | +| Reactions | ❌ Not supported | +| Stickers | ⚠️ No agent reply for Marketplace bots | +| Voice notes / audio / video | ⚠️ No agent reply for Marketplace bots | +| File attachments | ⚠️ No agent reply for Marketplace bots | +| Threads | ❌ Not supported | +| Polls | ❌ Not supported | +| Native commands | ❌ Not supported | +| Streaming | ⚠️ Blocked (2000 char limit) | ## Delivery targets (CLI/cron) @@ -175,6 +210,8 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and Full configuration: [Configuration](/gateway/configuration) +The flat top-level keys (`channels.zalo.botToken`, `channels.zalo.dmPolicy`, and similar) are a legacy single-account shorthand. Prefer `channels.zalo.accounts..*` for new configs. Both forms are still documented here because they exist in the schema. + Provider options: - `channels.zalo.enabled`: enable/disable channel startup. @@ -182,7 +219,7 @@ Provider options: - `channels.zalo.tokenFile`: read token from a regular file path. Symlinks are rejected. - `channels.zalo.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing). - `channels.zalo.allowFrom`: DM allowlist (user IDs). `open` requires `"*"`. The wizard will ask for numeric IDs. -- `channels.zalo.groupPolicy`: `open | allowlist | disabled` (default: allowlist). +- `channels.zalo.groupPolicy`: `open | allowlist | disabled` (default: allowlist). Present in config; see [Capabilities](#capabilities) and [Access control (Groups)](#access-control-groups) for current Marketplace-bot behavior. - `channels.zalo.groupAllowFrom`: group sender allowlist (user IDs). Falls back to `allowFrom` when unset. - `channels.zalo.mediaMaxMb`: inbound/outbound media cap (MB, default 5). - `channels.zalo.webhookUrl`: enable webhook mode (HTTPS required). @@ -198,7 +235,7 @@ Multi-account options: - `channels.zalo.accounts..enabled`: enable/disable account. - `channels.zalo.accounts..dmPolicy`: per-account DM policy. - `channels.zalo.accounts..allowFrom`: per-account allowlist. -- `channels.zalo.accounts..groupPolicy`: per-account group policy. +- `channels.zalo.accounts..groupPolicy`: per-account group policy. Present in config; see [Capabilities](#capabilities) and [Access control (Groups)](#access-control-groups) for current Marketplace-bot behavior. - `channels.zalo.accounts..groupAllowFrom`: per-account group sender allowlist. - `channels.zalo.accounts..webhookUrl`: per-account webhook URL. - `channels.zalo.accounts..webhookSecret`: per-account webhook secret. diff --git a/docs/channels/zalouser.md b/docs/channels/zalouser.md index 9b62244e234..4847430c8ac 100644 --- a/docs/channels/zalouser.md +++ b/docs/channels/zalouser.md @@ -41,7 +41,7 @@ No external `zca`/`openzca` CLI binary is required. } ``` -4. Restart the Gateway (or finish onboarding). +4. Restart the Gateway (or finish setup). 5. DM access defaults to pairing; approve the pairing code on first contact. ## What it is @@ -74,7 +74,7 @@ openclaw directory groups list --channel zalouser --query "work" `channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`). -`channels.zalouser.allowFrom` accepts user IDs or names. During onboarding, names are resolved to IDs using the plugin's in-process contact lookup. +`channels.zalouser.allowFrom` accepts user IDs or names. During setup, names are resolved to IDs using the plugin's in-process contact lookup. Approve via: @@ -86,11 +86,13 @@ Approve via: - Default: `channels.zalouser.groupPolicy = "open"` (groups allowed). Use `channels.defaults.groupPolicy` to override the default when unset. - Restrict to an allowlist with: - `channels.zalouser.groupPolicy = "allowlist"` - - `channels.zalouser.groups` (keys are group IDs or names; controls which groups are allowed) + - `channels.zalouser.groups` (keys should be stable group IDs; names are resolved to IDs on startup when possible) - `channels.zalouser.groupAllowFrom` (controls which senders in allowed groups can trigger the bot) - Block all groups: `channels.zalouser.groupPolicy = "disabled"`. - The configure wizard can prompt for group allowlists. -- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping; unresolved entries are kept as typed. +- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping. +- Group allowlist matching is ID-only by default. Unresolved names are ignored for auth unless `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled. +- `channels.zalouser.dangerouslyAllowNameMatching: true` is a break-glass compatibility mode that re-enables mutable group-name matching. - If `groupAllowFrom` is unset, runtime falls back to `allowFrom` for group sender checks. - Sender checks apply to both normal group messages and control commands (for example `/new`, `/reset`). diff --git a/docs/ci.md b/docs/ci.md index 16a7e670964..e8710b87cb1 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -9,32 +9,32 @@ read_when: # CI Pipeline -The CI runs on every push to `main` and every pull request. It uses smart scoping to skip expensive jobs when only docs or native code changed. +The CI runs on every push to `main` and every pull request. It uses smart scoping to skip expensive jobs when only unrelated areas changed. ## Job Overview -| Job | Purpose | When it runs | -| ----------------- | ------------------------------------------------------- | ------------------------------------------------- | -| `docs-scope` | Detect docs-only changes | Always | -| `changed-scope` | Detect which areas changed (node/macos/android/windows) | Non-docs PRs | -| `check` | TypeScript types, lint, format | Push to `main`, or PRs with Node-relevant changes | -| `check-docs` | Markdown lint + broken link check | Docs changed | -| `code-analysis` | LOC threshold check (1000 lines) | PRs only | -| `secrets` | Detect leaked secrets | Always | -| `build-artifacts` | Build dist once, share with other jobs | Non-docs, node changes | -| `release-check` | Validate npm pack contents | After build | -| `checks` | Node/Bun tests + protocol check | Non-docs, node changes | -| `checks-windows` | Windows-specific tests | Non-docs, windows-relevant changes | -| `macos` | Swift lint/build/test + TS tests | PRs with macos changes | -| `android` | Gradle build + tests | Non-docs, android changes | +| Job | Purpose | When it runs | +| ----------------- | ------------------------------------------------------- | ---------------------------------- | +| `docs-scope` | Detect docs-only changes | Always | +| `changed-scope` | Detect which areas changed (node/macos/android/windows) | Non-doc changes | +| `check` | TypeScript types, lint, format | Non-docs, node changes | +| `check-docs` | Markdown lint + broken link check | Docs changed | +| `secrets` | Detect leaked secrets | Always | +| `build-artifacts` | Build dist once, share with `release-check` | Pushes to `main`, node changes | +| `release-check` | Validate npm pack contents | Pushes to `main` after build | +| `checks` | Node tests + protocol check on PRs; Bun compat on push | Non-docs, node changes | +| `compat-node22` | Minimum supported Node runtime compatibility | Pushes to `main`, node changes | +| `checks-windows` | Windows-specific tests | Non-docs, windows-relevant changes | +| `macos` | Swift lint/build/test + TS tests | PRs with macos changes | +| `android` | Gradle build + tests | Non-docs, android changes | ## Fail-Fast Order Jobs are ordered so cheap checks fail before expensive ones run: -1. `docs-scope` + `code-analysis` + `check` (parallel, ~1-2 min) -2. `build-artifacts` (blocked on above) -3. `checks`, `checks-windows`, `macos`, `android` (blocked on build) +1. `docs-scope` + `changed-scope` + `check` + `secrets` (parallel, cheap gates first) +2. PRs: `checks` (Linux Node test split into 2 shards), `checks-windows`, `macos`, `android` +3. Pushes to `main`: `build-artifacts` + `release-check` + Bun compat + `compat-node22` Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`. diff --git a/docs/cli/acp.md b/docs/cli/acp.md index 9e239fc8bdf..d30deb49eca 100644 --- a/docs/cli/acp.md +++ b/docs/cli/acp.md @@ -8,7 +8,7 @@ title: "acp" # acp -Run the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/) bridge that talks to a OpenClaw Gateway. +Run the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/) bridge that talks to an OpenClaw Gateway. This command speaks ACP over stdio for IDEs and forwards prompts to the Gateway over WebSocket. It keeps ACP sessions mapped to Gateway session keys. @@ -102,7 +102,7 @@ Permission model (client debug mode): ## How to use this Use ACP when an IDE (or other client) speaks Agent Client Protocol and you want -it to drive a OpenClaw Gateway session. +it to drive an OpenClaw Gateway session. 1. Ensure the Gateway is running (local or remote). 2. Configure the Gateway target (config or flags). diff --git a/docs/cli/agent.md b/docs/cli/agent.md index 93c8d04b41a..430bdf50743 100644 --- a/docs/cli/agent.md +++ b/docs/cli/agent.md @@ -25,4 +25,5 @@ openclaw agent --agent ops --message "Generate report" --deliver --reply-channel ## Notes -- When this command triggers `models.json` regeneration, SecretRef-managed provider credentials are persisted as non-secret markers (for example env var names or `secretref-managed`), not resolved secret plaintext. +- When this command triggers `models.json` regeneration, SecretRef-managed provider credentials are persisted as non-secret markers (for example env var names, `secretref-env:ENV_VAR_NAME`, or `secretref-managed`), not resolved secret plaintext. +- Marker writes are source-authoritative: OpenClaw persists markers from the active source config snapshot, not from resolved runtime secret values. diff --git a/docs/cli/browser.md b/docs/cli/browser.md index 8e0ddad92ef..c5cb5ab9984 100644 --- a/docs/cli/browser.md +++ b/docs/cli/browser.md @@ -1,9 +1,9 @@ --- -summary: "CLI reference for `openclaw browser` (profiles, tabs, actions, extension relay)" +summary: "CLI reference for `openclaw browser` (profiles, tabs, actions, Chrome MCP, and CDP)" read_when: - You use `openclaw browser` and want examples for common tasks - You want to control a browser running on another machine via a node host - - You want to use the Chrome extension relay (attach/detach via toolbar button) + - You want to attach to your local signed-in Chrome via Chrome MCP title: "browser" --- @@ -14,7 +14,6 @@ Manage OpenClaw’s browser control server and run browser actions (tabs, snapsh Related: - Browser tool + API: [Browser tool](/tools/browser) -- Chrome extension relay: [Chrome extension](/tools/chrome-extension) ## Common flags @@ -27,7 +26,7 @@ Related: ## Quick start (local) ```bash -openclaw browser --browser-profile chrome tabs +openclaw browser profiles openclaw browser --browser-profile openclaw start openclaw browser --browser-profile openclaw open https://example.com openclaw browser --browser-profile openclaw snapshot @@ -37,12 +36,14 @@ openclaw browser --browser-profile openclaw snapshot Profiles are named browser routing configs. In practice: -- `openclaw`: launches/attaches to a dedicated OpenClaw-managed Chrome instance (isolated user data dir). -- `chrome`: controls your existing Chrome tab(s) via the Chrome extension relay. +- `openclaw`: launches or attaches to a dedicated OpenClaw-managed Chrome instance (isolated user data dir). +- `user`: controls your existing signed-in Chrome session via Chrome DevTools MCP. +- custom CDP profiles: point at a local or remote CDP endpoint. ```bash openclaw browser profiles openclaw browser create-profile --name work --color "#FF5A36" +openclaw browser create-profile --name chrome-live --driver existing-session openclaw browser delete-profile --name work ``` @@ -83,20 +84,18 @@ openclaw browser click openclaw browser type "hello" ``` -## Chrome extension relay (attach via toolbar button) +## Existing Chrome via MCP -This mode lets the agent control an existing Chrome tab that you attach manually (it does not auto-attach). - -Install the unpacked extension to a stable path: +Use the built-in `user` profile, or create your own `existing-session` profile: ```bash -openclaw browser extension install -openclaw browser extension path +openclaw browser --browser-profile user tabs +openclaw browser create-profile --name chrome-live --driver existing-session +openclaw browser create-profile --name brave-live --driver existing-session --user-data-dir "~/Library/Application Support/BraveSoftware/Brave-Browser" +openclaw browser --browser-profile chrome-live tabs ``` -Then Chrome → `chrome://extensions` → enable “Developer mode” → “Load unpacked” → select the printed folder. - -Full guide: [Chrome extension](/tools/chrome-extension) +This path is host-only. For Docker, headless servers, Browserless, or other remote setups, use a CDP profile instead. ## Remote browser control (node host proxy) diff --git a/docs/cli/channels.md b/docs/cli/channels.md index 654fbef5fa9..96b9ef33f8c 100644 --- a/docs/cli/channels.md +++ b/docs/cli/channels.md @@ -30,10 +30,11 @@ openclaw channels logs --channel all ```bash openclaw channels add --channel telegram --token +openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY" openclaw channels remove --channel telegram --delete ``` -Tip: `openclaw channels add --help` shows per-channel flags (token, app token, signal-cli paths, etc). +Tip: `openclaw channels add --help` shows per-channel flags (token, private key, app token, signal-cli paths, etc). When you run `openclaw channels add` without flags, the interactive wizard can prompt: diff --git a/docs/cli/config.md b/docs/cli/config.md index fa0d62e8511..1eb376f0fa0 100644 --- a/docs/cli/config.md +++ b/docs/cli/config.md @@ -7,9 +7,9 @@ title: "config" # `openclaw config` -Config helpers: get/set/unset/validate values by path and print the active -config file. Run without a subcommand to open -the configure wizard (same as `openclaw configure`). +Config helpers for non-interactive edits in `openclaw.json`: get/set/unset/validate +values by path and print the active config file. Run without a subcommand to +open the configure wizard (same as `openclaw configure`). ## Examples @@ -19,7 +19,10 @@ openclaw config get browser.executablePath openclaw config set browser.executablePath "/usr/bin/google-chrome" openclaw config set agents.defaults.heartbeat.every "2h" openclaw config set agents.list[0].tools.exec.node "node-id-or-name" -openclaw config unset tools.web.search.apiKey +openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN +openclaw config set secrets.providers.vaultfile --provider-source file --provider-path /etc/openclaw/secrets.json --provider-mode json +openclaw config unset plugins.entries.brave.config.webSearch.apiKey +openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN --dry-run openclaw config validate openclaw config validate --json ``` @@ -51,6 +54,230 @@ openclaw config set gateway.port 19001 --strict-json openclaw config set channels.whatsapp.groups '["*"]' --strict-json ``` +## `config set` modes + +`openclaw config set` supports four assignment styles: + +1. Value mode: `openclaw config set ` +2. SecretRef builder mode: + +```bash +openclaw config set channels.discord.token \ + --ref-provider default \ + --ref-source env \ + --ref-id DISCORD_BOT_TOKEN +``` + +3. Provider builder mode (`secrets.providers.` path only): + +```bash +openclaw config set secrets.providers.vault \ + --provider-source exec \ + --provider-command /usr/local/bin/openclaw-vault \ + --provider-arg read \ + --provider-arg openai/api-key \ + --provider-timeout-ms 5000 +``` + +4. Batch mode (`--batch-json` or `--batch-file`): + +```bash +openclaw config set --batch-json '[ + { + "path": "secrets.providers.default", + "provider": { "source": "env" } + }, + { + "path": "channels.discord.token", + "ref": { "source": "env", "provider": "default", "id": "DISCORD_BOT_TOKEN" } + } +]' +``` + +```bash +openclaw config set --batch-file ./config-set.batch.json --dry-run +``` + +Batch parsing always uses the batch payload (`--batch-json`/`--batch-file`) as the source of truth. +`--strict-json` / `--json` do not change batch parsing behavior. + +JSON path/value mode remains supported for both SecretRefs and providers: + +```bash +openclaw config set channels.discord.token \ + '{"source":"env","provider":"default","id":"DISCORD_BOT_TOKEN"}' \ + --strict-json + +openclaw config set secrets.providers.vaultfile \ + '{"source":"file","path":"/etc/openclaw/secrets.json","mode":"json"}' \ + --strict-json +``` + +## Provider Builder Flags + +Provider builder targets must use `secrets.providers.` as the path. + +Common flags: + +- `--provider-source ` +- `--provider-timeout-ms ` (`file`, `exec`) + +Env provider (`--provider-source env`): + +- `--provider-allowlist ` (repeatable) + +File provider (`--provider-source file`): + +- `--provider-path ` (required) +- `--provider-mode ` +- `--provider-max-bytes ` + +Exec provider (`--provider-source exec`): + +- `--provider-command ` (required) +- `--provider-arg ` (repeatable) +- `--provider-no-output-timeout-ms ` +- `--provider-max-output-bytes ` +- `--provider-json-only` +- `--provider-env ` (repeatable) +- `--provider-pass-env ` (repeatable) +- `--provider-trusted-dir ` (repeatable) +- `--provider-allow-insecure-path` +- `--provider-allow-symlink-command` + +Hardened exec provider example: + +```bash +openclaw config set secrets.providers.vault \ + --provider-source exec \ + --provider-command /usr/local/bin/openclaw-vault \ + --provider-arg read \ + --provider-arg openai/api-key \ + --provider-json-only \ + --provider-pass-env VAULT_TOKEN \ + --provider-trusted-dir /usr/local/bin \ + --provider-timeout-ms 5000 +``` + +## Dry run + +Use `--dry-run` to validate changes without writing `openclaw.json`. + +```bash +openclaw config set channels.discord.token \ + --ref-provider default \ + --ref-source env \ + --ref-id DISCORD_BOT_TOKEN \ + --dry-run + +openclaw config set channels.discord.token \ + --ref-provider default \ + --ref-source env \ + --ref-id DISCORD_BOT_TOKEN \ + --dry-run \ + --json + +openclaw config set channels.discord.token \ + --ref-provider vault \ + --ref-source exec \ + --ref-id discord/token \ + --dry-run \ + --allow-exec +``` + +Dry-run behavior: + +- Builder mode: runs SecretRef resolvability checks for changed refs/providers. +- JSON mode (`--strict-json`, `--json`, or batch mode): runs schema validation plus SecretRef resolvability checks. +- Exec SecretRef checks are skipped by default during dry-run to avoid command side effects. +- Use `--allow-exec` with `--dry-run` to opt in to exec SecretRef checks (this may execute provider commands). +- `--allow-exec` is dry-run only and errors if used without `--dry-run`. + +`--dry-run --json` prints a machine-readable report: + +- `ok`: whether dry-run passed +- `operations`: number of assignments evaluated +- `checks`: whether schema/resolvability checks ran +- `checks.resolvabilityComplete`: whether resolvability checks ran to completion (false when exec refs are skipped) +- `refsChecked`: number of refs actually resolved during dry-run +- `skippedExecRefs`: number of exec refs skipped because `--allow-exec` was not set +- `errors`: structured schema/resolvability failures when `ok=false` + +### JSON Output Shape + +```json5 +{ + ok: boolean, + operations: number, + configPath: string, + inputModes: ["value" | "json" | "builder", ...], + checks: { + schema: boolean, + resolvability: boolean, + resolvabilityComplete: boolean, + }, + refsChecked: number, + skippedExecRefs: number, + errors?: [ + { + kind: "schema" | "resolvability", + message: string, + ref?: string, // present for resolvability errors + }, + ], +} +``` + +Success example: + +```json +{ + "ok": true, + "operations": 1, + "configPath": "~/.openclaw/openclaw.json", + "inputModes": ["builder"], + "checks": { + "schema": false, + "resolvability": true, + "resolvabilityComplete": true + }, + "refsChecked": 1, + "skippedExecRefs": 0 +} +``` + +Failure example: + +```json +{ + "ok": false, + "operations": 1, + "configPath": "~/.openclaw/openclaw.json", + "inputModes": ["builder"], + "checks": { + "schema": false, + "resolvability": true, + "resolvabilityComplete": true + }, + "refsChecked": 1, + "skippedExecRefs": 0, + "errors": [ + { + "kind": "resolvability", + "message": "Error: Environment variable \"MISSING_TEST_SECRET\" is not set.", + "ref": "env:default:MISSING_TEST_SECRET" + } + ] +} +``` + +If dry-run fails: + +- `config schema validation failed`: your post-change config shape is invalid; fix path/value or provider/ref object shape. +- `SecretRef assignment(s) could not be resolved`: referenced provider/ref currently cannot resolve (missing env var, invalid file pointer, exec provider failure, or provider/source mismatch). +- `Dry run note: skipped exec SecretRef resolvability check(s)`: dry-run skipped exec refs; rerun with `--allow-exec` if you need exec resolvability validation. +- For batch mode, fix failing entries and rerun `--dry-run` before writing. + ## Subcommands - `config file`: Print the active config file path (resolved from `OPENCLAW_CONFIG_PATH` or default location). diff --git a/docs/cli/daemon.md b/docs/cli/daemon.md index 8f6042e7400..f21c3930ece 100644 --- a/docs/cli/daemon.md +++ b/docs/cli/daemon.md @@ -34,13 +34,15 @@ openclaw daemon uninstall ## Common options -- `status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--deep`, `--json` +- `status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--require-rpc`, `--deep`, `--json` - `install`: `--port`, `--runtime `, `--token`, `--force`, `--json` - lifecycle (`uninstall|start|stop|restart`): `--json` Notes: - `status` resolves configured auth SecretRefs for probe auth when possible. +- If a required auth SecretRef is unresolved in this command path, `daemon status --json` reports `rpc.authWarning` when probe connectivity/auth fails; pass `--token`/`--password` explicitly or resolve the secret source first. +- If the probe succeeds, unresolved auth-ref warnings are suppressed to avoid false positives. - On Linux systemd installs, `status` token-drift checks include both `Environment=` and `EnvironmentFile=` unit sources. - When token auth requires a token and `gateway.auth.token` is SecretRef-managed, `install` validates that the SecretRef is resolvable but does not persist the resolved token into service environment metadata. - If token auth requires a token and the configured token SecretRef is unresolved, install fails closed. diff --git a/docs/cli/directory.md b/docs/cli/directory.md index 9d8f8a92b68..15ba7ba60e1 100644 --- a/docs/cli/directory.md +++ b/docs/cli/directory.md @@ -40,7 +40,7 @@ openclaw message send --channel slack --target user:U012ABCDEF --message "hello" - Zalo (plugin): user id (Bot API) - Zalo Personal / `zalouser` (plugin): thread id (DM/group) from `zca` (`me`, `friend list`, `group list`) -## Self (“me”) +## Self ("me") ```bash openclaw directory self --channel zalouser diff --git a/docs/cli/docs.md b/docs/cli/docs.md index 6b79aabe6f1..744c50e1432 100644 --- a/docs/cli/docs.md +++ b/docs/cli/docs.md @@ -10,6 +10,6 @@ title: "docs" Search the live docs index. ```bash -openclaw docs browser extension +openclaw docs browser existing-session openclaw docs sandbox allowHostControl ``` diff --git a/docs/cli/doctor.md b/docs/cli/doctor.md index 90e5fa7d7a2..d5429b5b01c 100644 --- a/docs/cli/doctor.md +++ b/docs/cli/doctor.md @@ -31,6 +31,9 @@ Notes: - Doctor also scans `~/.openclaw/cron/jobs.json` (or `cron.store`) for legacy cron job shapes and can rewrite them in place before the scheduler has to auto-normalize them at runtime. - Doctor includes a memory-search readiness check and can recommend `openclaw configure --section model` when embedding credentials are missing. - If sandbox mode is enabled but Docker is unavailable, doctor reports a high-signal warning with remediation (`install Docker` or `openclaw config set agents.defaults.sandbox.mode off`). +- If `gateway.auth.token`/`gateway.auth.password` are SecretRef-managed and unavailable in the current command path, doctor reports a read-only warning and does not write plaintext fallback credentials. +- If channel SecretRef inspection fails in a fix path, doctor continues and reports a warning instead of exiting early. +- Telegram `allowFrom` username auto-resolution (`doctor --fix`) requires a resolvable Telegram token in the current command path. If token inspection is unavailable, doctor reports a warning and skips auto-resolution for that pass. ## macOS: `launchctl` env overrides diff --git a/docs/cli/gateway.md b/docs/cli/gateway.md index 95c20e3aa7c..d36fbde6c35 100644 --- a/docs/cli/gateway.md +++ b/docs/cli/gateway.md @@ -95,6 +95,7 @@ openclaw gateway health --url ws://127.0.0.1:18789 ```bash openclaw gateway status openclaw gateway status --json +openclaw gateway status --require-rpc ``` Options: @@ -105,11 +106,14 @@ Options: - `--timeout `: probe timeout (default `10000`). - `--no-probe`: skip the RPC probe (service-only view). - `--deep`: scan system-level services too. +- `--require-rpc`: exit non-zero when the RPC probe fails. Cannot be combined with `--no-probe`. Notes: - `gateway status` resolves configured auth SecretRefs for probe auth when possible. -- If a required auth SecretRef is unresolved in this command path, probe auth can fail; pass `--token`/`--password` explicitly or resolve the secret source first. +- If a required auth SecretRef is unresolved in this command path, `gateway status --json` reports `rpc.authWarning` when probe connectivity/auth fails; pass `--token`/`--password` explicitly or resolve the secret source first. +- If the probe succeeds, unresolved auth-ref warnings are suppressed to avoid false positives. +- Use `--require-rpc` in scripts and automation when a listening service is not enough and you need the Gateway RPC itself to be healthy. - On Linux systemd installs, service auth drift checks read both `Environment=` and `EnvironmentFile=` values from the unit (including `%h`, quoted paths, multiple files, and optional `-` files). ### `gateway probe` @@ -126,6 +130,23 @@ openclaw gateway probe openclaw gateway probe --json ``` +Interpretation: + +- `Reachable: yes` means at least one target accepted a WebSocket connect. +- `RPC: ok` means detail RPC calls (`health`/`status`/`system-presence`/`config.get`) also succeeded. +- `RPC: limited - missing scope: operator.read` means connect succeeded but detail RPC is scope-limited. This is reported as **degraded** reachability, not full failure. +- Exit code is non-zero only when no probed target is reachable. + +JSON notes (`--json`): + +- Top level: + - `ok`: at least one target is reachable. + - `degraded`: at least one target had scope-limited detail RPC. +- Per target (`targets[].connect`): + - `ok`: reachability after connect + degraded classification. + - `rpcOk`: full detail RPC success. + - `scopeLimited`: detail RPC failed due to missing operator scope. + #### Remote over SSH (Mac app parity) The macOS app “Remote over SSH” mode uses a local port-forward so the remote gateway (which may be bound to loopback only) becomes reachable at `ws://127.0.0.1:`. diff --git a/docs/cli/hooks.md b/docs/cli/hooks.md index 8aaaa6fd63d..939dac99c66 100644 --- a/docs/cli/hooks.md +++ b/docs/cli/hooks.md @@ -13,7 +13,7 @@ Manage agent hooks (event-driven automations for commands like `/new`, `/reset`, Related: - Hooks: [Hooks](/automation/hooks) -- Plugin hooks: [Plugins](/tools/plugin#plugin-hooks) +- Plugin hooks: [Plugin hooks](/plugins/architecture#provider-runtime-hooks) ## List All Hooks diff --git a/docs/cli/index.md b/docs/cli/index.md index cbcd5bff0b5..f1555b4ea26 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -88,7 +88,7 @@ OpenClaw uses a lobster palette for CLI output. - `error` (#E23D2D): errors, failures. - `muted` (#8B7F77): de-emphasis, metadata. -Palette source of truth: `src/terminal/palette.ts` (aka “lobster seam”). +Palette source of truth: `src/terminal/palette.ts` (the “lobster palette”). ## Command tree @@ -101,6 +101,8 @@ openclaw [--dev] [--profile ] get set unset + file + validate completion doctor dashboard @@ -274,17 +276,18 @@ Note: plugins can add additional top-level commands (for example `openclaw voice ## Secrets - `openclaw secrets reload` — re-resolve refs and atomically swap the runtime snapshot. -- `openclaw secrets audit` — scan for plaintext residues, unresolved refs, and precedence drift. -- `openclaw secrets configure` — interactive helper for provider setup + SecretRef mapping + preflight/apply. -- `openclaw secrets apply --from ` — apply a previously generated plan (`--dry-run` supported). +- `openclaw secrets audit` — scan for plaintext residues, unresolved refs, and precedence drift (`--allow-exec` to execute exec providers during audit). +- `openclaw secrets configure` — interactive helper for provider setup + SecretRef mapping + preflight/apply (`--allow-exec` to execute exec providers during preflight and exec-containing apply flows). +- `openclaw secrets apply --from ` — apply a previously generated plan (`--dry-run` supported; use `--allow-exec` to permit exec providers in dry-run and exec-containing write plans). ## Plugins Manage extensions and their config: - `openclaw plugins list` — discover plugins (use `--json` for machine output). -- `openclaw plugins info ` — show details for a plugin. -- `openclaw plugins install ` — install a plugin (or add a plugin path to `plugins.load.paths`). +- `openclaw plugins inspect ` — show details for a plugin (`info` is an alias). +- `openclaw plugins install ` — install a plugin (or add a plugin path to `plugins.load.paths`). +- `openclaw plugins marketplace list ` — list marketplace entries before install. - `openclaw plugins enable ` / `disable ` — toggle `plugins.entries..enabled`. - `openclaw plugins doctor` — report plugin load errors. @@ -317,27 +320,27 @@ Initialize config + workspace. Options: - `--workspace `: agent workspace path (default `~/.openclaw/workspace`). -- `--wizard`: run the onboarding wizard. -- `--non-interactive`: run wizard without prompts. -- `--mode `: wizard mode. +- `--wizard`: run onboarding. +- `--non-interactive`: run onboarding without prompts. +- `--mode `: onboard mode. - `--remote-url `: remote Gateway URL. - `--remote-token `: remote Gateway token. -Wizard auto-runs when any wizard flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`). +Onboarding auto-runs when any onboarding flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`). ### `onboard` -Interactive wizard to set up gateway, workspace, and skills. +Interactive onboarding for gateway, workspace, and skills. Options: - `--workspace ` -- `--reset` (reset config + credentials + sessions before wizard) +- `--reset` (reset config + credentials + sessions before onboarding) - `--reset-scope ` (default `config+creds+sessions`; use `full` to also remove workspace) - `--non-interactive` - `--mode ` - `--flow ` (manual is an alias for advanced) -- `--auth-choice ` +- `--auth-choice ` - `--token-provider ` (non-interactive; used with `--auth-choice token`) - `--token ` (non-interactive; used with `--auth-choice token`) - `--token-profile-id ` (non-interactive; default: `:manual`) @@ -355,8 +358,8 @@ Options: - `--minimax-api-key ` - `--opencode-zen-api-key ` - `--opencode-go-api-key ` -- `--custom-base-url ` (non-interactive; used with `--auth-choice custom-api-key`) -- `--custom-model-id ` (non-interactive; used with `--auth-choice custom-api-key`) +- `--custom-base-url ` (non-interactive; used with `--auth-choice custom-api-key` or `--auth-choice ollama`) +- `--custom-model-id ` (non-interactive; used with `--auth-choice custom-api-key` or `--auth-choice ollama`) - `--custom-api-key ` (non-interactive; optional; used with `--auth-choice custom-api-key`; falls back to `CUSTOM_API_KEY` when omitted) - `--custom-provider-id ` (non-interactive; optional custom provider id) - `--custom-compatibility ` (non-interactive; optional; default `openai`) @@ -392,7 +395,15 @@ subcommand launches the wizard. Subcommands: - `config get `: print a config value (dot/bracket path). -- `config set `: set a value (JSON5 or raw string). +- `config set`: supports four assignment modes: + - value mode: `config set ` (JSON5-or-string parsing) + - SecretRef builder mode: `config set --ref-provider --ref-source --ref-id ` + - provider builder mode: `config set secrets.providers. --provider-source ...` + - batch mode: `config set --batch-json ''` or `config set --batch-file ` +- `config set --dry-run`: validate assignments without writing `openclaw.json` (exec SecretRef checks are skipped by default). +- `config set --allow-exec --dry-run`: opt in to exec SecretRef dry-run checks (may execute provider commands). +- `config set --dry-run --json`: emit machine-readable dry-run output (checks + completeness signal, operations, refs checked/skipped, errors). +- `config set --strict-json`: require JSON5 parsing for path/value input. `--json` remains a legacy alias for strict parsing outside dry-run output mode. - `config unset `: remove a value. - `config file`: print the active config file path. - `config validate`: validate the current config against the schema without starting the gateway. @@ -676,7 +687,7 @@ Surfaces: Notes: - Data comes directly from provider usage endpoints (no estimates). -- Providers: Anthropic, GitHub Copilot, OpenAI Codex OAuth, plus Gemini CLI/Antigravity when those provider plugins are enabled. +- Providers: Anthropic, GitHub Copilot, OpenAI Codex OAuth, plus Gemini CLI via the bundled `google` plugin and Antigravity where configured. - If no matching credentials exist, usage is hidden. - Details: see [Usage tracking](/concepts/usage-tracking). @@ -780,9 +791,10 @@ Subcommands: Notes: - `gateway status` probes the Gateway RPC by default using the service’s resolved port/config (override with `--url/--token/--password`). -- `gateway status` supports `--no-probe`, `--deep`, and `--json` for scripting. +- `gateway status` supports `--no-probe`, `--deep`, `--require-rpc`, and `--json` for scripting. - `gateway status` also surfaces legacy or extra gateway services when it can detect them (`--deep` adds system-level scans). Profile-named OpenClaw services are treated as first-class and aren't flagged as "extra". - `gateway status` prints which config path the CLI uses vs which config the service likely uses (service env), plus the resolved probe target URL. +- If gateway auth SecretRefs are unresolved in the current command path, `gateway status --json` reports `rpc.authWarning` only when probe connectivity/auth fails (warnings are suppressed when probe succeeds). - On Linux systemd installs, status token-drift checks include both `Environment=` and `EnvironmentFile=` unit sources. - `gateway install|uninstall|start|stop|restart` support `--json` for scripting (default output stays human-friendly). - `gateway install` defaults to Node runtime; bun is **not recommended** (WhatsApp/Telegram bugs). diff --git a/docs/cli/message.md b/docs/cli/message.md index 195e884a01d..665d0e74bd2 100644 --- a/docs/cli/message.md +++ b/docs/cli/message.md @@ -50,6 +50,16 @@ Name lookup: - `--dry-run` - `--verbose` +## SecretRef behavior + +- `openclaw message` resolves supported channel SecretRefs before running the selected action. +- Resolution is scoped to the active action target when possible: + - channel-scoped when `--channel` is set (or inferred from prefixed targets like `discord:...`) + - account-scoped when `--account` is set (channel globals + selected account surfaces) + - when `--account` is omitted, OpenClaw does not force a `default` account SecretRef scope +- Unresolved SecretRefs on unrelated channels do not block a targeted message action. +- If the selected channel/account SecretRef is unresolved, the command fails closed for that action. + ## Actions ### Core @@ -59,6 +69,7 @@ Name lookup: - Required: `--target`, plus `--message` or `--media` - Optional: `--media`, `--reply-to`, `--thread-id`, `--gif-playback` - Telegram only: `--buttons` (requires `channels.telegram.capabilities.inlineButtons` to allow it) + - Telegram only: `--force-document` (send images and GIFs as documents to avoid Telegram compression) - Telegram only: `--thread-id` (forum topic id) - Slack only: `--thread-id` (thread timestamp; `--reply-to` uses the same field) - WhatsApp only: `--gif-playback` @@ -258,3 +269,10 @@ Send Telegram inline buttons: openclaw message send --channel telegram --target @mychat --message "Choose:" \ --buttons '[ [{"text":"Yes","callback_data":"cmd:yes"}], [{"text":"No","callback_data":"cmd:no"}] ]' ``` + +Send a Telegram image as a document to avoid compression: + +```bash +openclaw message send --channel telegram --target @mychat \ + --media ./diagram.png --force-document +``` diff --git a/docs/cli/onboard.md b/docs/cli/onboard.md index 36629a3bb8d..0b0e9c78beb 100644 --- a/docs/cli/onboard.md +++ b/docs/cli/onboard.md @@ -1,5 +1,5 @@ --- -summary: "CLI reference for `openclaw onboard` (interactive onboarding wizard)" +summary: "CLI reference for `openclaw onboard` (interactive onboarding)" read_when: - You want guided setup for gateway, workspace, auth, channels, and skills title: "onboard" @@ -7,13 +7,13 @@ title: "onboard" # `openclaw onboard` -Interactive onboarding wizard (local or remote Gateway setup). +Interactive onboarding for local or remote Gateway setup. ## Related guides -- CLI onboarding hub: [Onboarding Wizard (CLI)](/start/wizard) +- CLI onboarding hub: [Onboarding (CLI)](/start/wizard) - Onboarding overview: [Onboarding Overview](/start/onboarding-overview) -- CLI onboarding reference: [CLI Onboarding Reference](/start/wizard-cli-reference) +- CLI onboarding reference: [CLI Setup Reference](/start/wizard-cli-reference) - CLI automation: [CLI Automation](/start/wizard-cli-automation) - macOS onboarding: [Onboarding (macOS App)](/start/onboarding) @@ -43,6 +43,18 @@ openclaw onboard --non-interactive \ `--custom-api-key` is optional in non-interactive mode. If omitted, onboarding checks `CUSTOM_API_KEY`. +Non-interactive Ollama: + +```bash +openclaw onboard --non-interactive \ + --auth-choice ollama \ + --custom-base-url "http://ollama-host:11434" \ + --custom-model-id "qwen3.5:27b" \ + --accept-risk +``` + +`--custom-base-url` defaults to `http://127.0.0.1:11434`. `--custom-model-id` is optional; if omitted, onboarding uses Ollama's suggested defaults. Cloud model IDs such as `kimi-k2.5:cloud` also work here. + Store provider keys as refs instead of plaintext: ```bash @@ -83,6 +95,13 @@ openclaw onboard --non-interactive \ --accept-risk ``` +Non-interactive local gateway health: + +- Unless you pass `--skip-health`, onboarding waits for a reachable local gateway before it exits successfully. +- `--install-daemon` starts the managed gateway install path first. Without it, you must already have a local gateway running, for example `openclaw gateway run`. +- If you only want config/workspace/bootstrap writes in automation, use `--skip-health`. +- On native Windows, `--install-daemon` tries Scheduled Tasks first and falls back to a per-user Startup-folder login item if task creation is denied. + Interactive onboarding behavior with reference mode: - Choose **Use secret reference** when prompted. @@ -121,7 +140,7 @@ Flow notes: - `quickstart`: minimal prompts, auto-generates a gateway token. - `manual`: full prompts for port/bind/auth (alias of `advanced`). -- Local onboarding DM scope behavior: [CLI Onboarding Reference](/start/wizard-cli-reference#outputs-and-internals). +- Local onboarding DM scope behavior: [CLI Setup Reference](/start/wizard-cli-reference#outputs-and-internals). - Fastest first chat: `openclaw dashboard` (Control UI, no channel setup). - Custom Provider: connect any OpenAI or Anthropic compatible endpoint, including hosted providers not listed. Use Unknown to auto-detect. diff --git a/docs/cli/plugins.md b/docs/cli/plugins.md index 0b054f5a4aa..47ef4930b8a 100644 --- a/docs/cli/plugins.md +++ b/docs/cli/plugins.md @@ -1,18 +1,19 @@ --- -summary: "CLI reference for `openclaw plugins` (list, install, uninstall, enable/disable, doctor)" +summary: "CLI reference for `openclaw plugins` (list, install, marketplace, uninstall, enable/disable, doctor)" read_when: - - You want to install or manage in-process Gateway plugins + - You want to install or manage Gateway plugins or compatible bundles - You want to debug plugin load failures title: "plugins" --- # `openclaw plugins` -Manage Gateway plugins/extensions (loaded in-process). +Manage Gateway plugins/extensions and compatible bundles. Related: - Plugin system: [Plugins](/tools/plugin) +- Bundle compatibility: [Plugin bundles](/plugins/bundles) - Plugin manifest + schema: [Plugin manifest](/plugins/manifest) - Security hardening: [Security](/gateway/security) @@ -20,27 +21,35 @@ Related: ```bash openclaw plugins list -openclaw plugins info +openclaw plugins install +openclaw plugins inspect openclaw plugins enable openclaw plugins disable openclaw plugins uninstall openclaw plugins doctor openclaw plugins update openclaw plugins update --all +openclaw plugins marketplace list ``` Bundled plugins ship with OpenClaw but start disabled. Use `plugins enable` to activate them. -All plugins must ship a `openclaw.plugin.json` file with an inline JSON Schema -(`configSchema`, even if empty). Missing/invalid manifests or schemas prevent -the plugin from loading and fail config validation. +Native OpenClaw plugins must ship `openclaw.plugin.json` with an inline JSON +Schema (`configSchema`, even if empty). Compatible bundles use their own bundle +manifests instead. + +`plugins list` shows `Format: openclaw` or `Format: bundle`. Verbose list/info +output also shows the bundle subtype (`codex`, `claude`, or `cursor`) plus detected bundle +capabilities. ### Install ```bash openclaw plugins install openclaw plugins install --pin +openclaw plugins install @ +openclaw plugins install --marketplace ``` Security note: treat plugin installs like running code. Prefer pinned versions. @@ -60,6 +69,45 @@ name, use an explicit scoped spec (for example `@scope/diffs`). Supported archives: `.zip`, `.tgz`, `.tar.gz`, `.tar`. +Claude marketplace installs are also supported. + +Use `plugin@marketplace` shorthand when the marketplace name exists in Claude's +local registry cache at `~/.claude/plugins/known_marketplaces.json`: + +```bash +openclaw plugins marketplace list +openclaw plugins install @ +``` + +Use `--marketplace` when you want to pass the marketplace source explicitly: + +```bash +openclaw plugins install --marketplace +openclaw plugins install --marketplace +openclaw plugins install --marketplace ./my-marketplace +``` + +Marketplace sources can be: + +- a Claude known-marketplace name from `~/.claude/plugins/known_marketplaces.json` +- a local marketplace root or `marketplace.json` path +- a GitHub repo shorthand such as `owner/repo` +- a git URL + +For local paths and archives, OpenClaw auto-detects: + +- native OpenClaw plugins (`openclaw.plugin.json`) +- Codex-compatible bundles (`.codex-plugin/plugin.json`) +- Claude-compatible bundles (`.claude-plugin/plugin.json` or the default Claude + component layout) +- Cursor-compatible bundles (`.cursor-plugin/plugin.json`) + +Compatible bundles install into the normal extensions root and participate in +the same list/info/enable/disable flow. Today, bundle skills, Claude +command-skills, Claude `settings.json` defaults, Cursor command-skills, and compatible Codex hook +directories are supported; other detected bundle capabilities are shown in +diagnostics/info but are not yet wired into runtime execution. + Use `--link` to avoid copying a local directory (adds to `plugins.load.paths`): ```bash @@ -95,8 +143,34 @@ openclaw plugins update --all openclaw plugins update --dry-run ``` -Updates only apply to plugins installed from npm (tracked in `plugins.installs`). +Updates apply to tracked installs in `plugins.installs`, currently npm and +marketplace installs. When a stored integrity hash exists and the fetched artifact hash changes, OpenClaw prints a warning and asks for confirmation before proceeding. Use global `--yes` to bypass prompts in CI/non-interactive runs. + +### Inspect + +```bash +openclaw plugins inspect +openclaw plugins inspect --json +``` + +Deep introspection for a single plugin. Shows identity, load status, source, +registered capabilities, hooks, tools, commands, services, gateway methods, +HTTP routes, policy flags, diagnostics, and install metadata. + +Each plugin is classified by what it actually registers at runtime: + +- **plain-capability** — one capability type (e.g. a provider-only plugin) +- **hybrid-capability** — multiple capability types (e.g. text + speech + images) +- **hook-only** — only hooks, no capabilities or surfaces +- **non-capability** — tools/commands/services but no capabilities + +See [Plugin shapes](/plugins/architecture#plugin-shapes) for more on the capability model. + +The `--json` flag outputs a machine-readable report suitable for scripting and +auditing. + +`info` is an alias for `inspect`. diff --git a/docs/cli/qr.md b/docs/cli/qr.md index 2fc070ca1bd..1575b16d029 100644 --- a/docs/cli/qr.md +++ b/docs/cli/qr.md @@ -17,7 +17,7 @@ openclaw qr openclaw qr --setup-code-only openclaw qr --json openclaw qr --remote -openclaw qr --url wss://gateway.example/ws --token '' +openclaw qr --url wss://gateway.example/ws ``` ## Options @@ -25,8 +25,8 @@ openclaw qr --url wss://gateway.example/ws --token '' - `--remote`: use `gateway.remote.url` plus remote token/password from config - `--url `: override gateway URL used in payload - `--public-url `: override public URL used in payload -- `--token `: override gateway token for payload -- `--password `: override gateway password for payload +- `--token `: override which gateway token the bootstrap flow authenticates against +- `--password `: override which gateway password the bootstrap flow authenticates against - `--setup-code-only`: print only setup code - `--no-ascii`: skip ASCII QR rendering - `--json`: emit JSON (`setupCode`, `gatewayUrl`, `auth`, `urlSource`) @@ -34,6 +34,7 @@ openclaw qr --url wss://gateway.example/ws --token '' ## Notes - `--token` and `--password` are mutually exclusive. +- The setup code itself now carries an opaque short-lived `bootstrapToken`, not the shared gateway token/password. - With `--remote`, if effectively active remote credentials are configured as SecretRefs and you do not pass `--token` or `--password`, the command resolves them from the active gateway snapshot. If gateway is unavailable, the command fails fast. - Without `--remote`, local gateway auth SecretRefs are resolved when no CLI auth override is passed: - `gateway.auth.token` resolves when token auth can win (explicit `gateway.auth.mode="token"` or inferred mode where no password source wins). diff --git a/docs/cli/sandbox.md b/docs/cli/sandbox.md index e8e4614a9ff..5764851dc70 100644 --- a/docs/cli/sandbox.md +++ b/docs/cli/sandbox.md @@ -1,17 +1,29 @@ --- title: Sandbox CLI -summary: "Manage sandbox containers and inspect effective sandbox policy" -read_when: "You are managing sandbox containers or debugging sandbox/tool-policy behavior." +summary: "Manage sandbox runtimes and inspect effective sandbox policy" +read_when: "You are managing sandbox runtimes or debugging sandbox/tool-policy behavior." status: active --- # Sandbox CLI -Manage Docker-based sandbox containers for isolated agent execution. +Manage sandbox runtimes for isolated agent execution. ## Overview -OpenClaw can run agents in isolated Docker containers for security. The `sandbox` commands help you manage these containers, especially after updates or configuration changes. +OpenClaw can run agents in isolated sandbox runtimes for security. The `sandbox` commands help you inspect and recreate those runtimes after updates or configuration changes. + +Today that usually means: + +- Docker sandbox containers +- SSH sandbox runtimes when `agents.defaults.sandbox.backend = "ssh"` +- OpenShell sandbox runtimes when `agents.defaults.sandbox.backend = "openshell"` + +For `ssh` and OpenShell `remote`, recreate matters more than with Docker: + +- the remote workspace is canonical after the initial seed +- `openclaw sandbox recreate` deletes that canonical remote workspace for the selected scope +- next use seeds it again from the current local workspace ## Commands @@ -28,7 +40,7 @@ openclaw sandbox explain --json ### `openclaw sandbox list` -List all sandbox containers with their status and configuration. +List all sandbox runtimes with their status and configuration. ```bash openclaw sandbox list @@ -38,15 +50,16 @@ openclaw sandbox list --json # JSON output **Output includes:** -- Container name and status (running/stopped) -- Docker image and whether it matches config +- Runtime name and status +- Backend (`docker`, `openshell`, etc.) +- Config label and whether it matches current config - Age (time since creation) - Idle time (time since last use) - Associated session/agent ### `openclaw sandbox recreate` -Remove sandbox containers to force recreation with updated images/config. +Remove sandbox runtimes to force recreation with updated config. ```bash openclaw sandbox recreate --all # Recreate all containers @@ -64,11 +77,11 @@ openclaw sandbox recreate --all --force # Skip confirmation - `--browser`: Only recreate browser containers - `--force`: Skip confirmation prompt -**Important:** Containers are automatically recreated when the agent is next used. +**Important:** Runtimes are automatically recreated when the agent is next used. ## Use Cases -### After updating Docker images +### After updating a Docker image ```bash # Pull new image @@ -91,6 +104,37 @@ openclaw sandbox recreate --all openclaw sandbox recreate --all ``` +### After changing SSH target or SSH auth material + +```bash +# Edit config: +# - agents.defaults.sandbox.backend +# - agents.defaults.sandbox.ssh.target +# - agents.defaults.sandbox.ssh.workspaceRoot +# - agents.defaults.sandbox.ssh.identityFile / certificateFile / knownHostsFile +# - agents.defaults.sandbox.ssh.identityData / certificateData / knownHostsData + +openclaw sandbox recreate --all +``` + +For the core `ssh` backend, recreate deletes the per-scope remote workspace root +on the SSH target. The next run seeds it again from the local workspace. + +### After changing OpenShell source, policy, or mode + +```bash +# Edit config: +# - agents.defaults.sandbox.backend +# - plugins.entries.openshell.config.from +# - plugins.entries.openshell.config.mode +# - plugins.entries.openshell.config.policy + +openclaw sandbox recreate --all +``` + +For OpenShell `remote` mode, recreate deletes the canonical remote workspace +for that scope. The next run seeds it again from the local workspace. + ### After changing setupCommand ```bash @@ -108,16 +152,16 @@ openclaw sandbox recreate --agent alfred ## Why is this needed? -**Problem:** When you update sandbox Docker images or configuration: +**Problem:** When you update sandbox configuration: -- Existing containers continue running with old settings -- Containers are only pruned after 24h of inactivity -- Regularly-used agents keep old containers running indefinitely +- Existing runtimes continue running with old settings +- Runtimes are only pruned after 24h of inactivity +- Regularly-used agents keep old runtimes alive indefinitely -**Solution:** Use `openclaw sandbox recreate` to force removal of old containers. They'll be recreated automatically with current settings when next needed. +**Solution:** Use `openclaw sandbox recreate` to force removal of old runtimes. They'll be recreated automatically with current settings when next needed. -Tip: prefer `openclaw sandbox recreate` over manual `docker rm`. It uses the -Gateway’s container naming and avoids mismatches when scope/session keys change. +Tip: prefer `openclaw sandbox recreate` over manual backend-specific cleanup. +It uses the Gateway’s runtime registry and avoids mismatches when scope/session keys change. ## Configuration @@ -129,6 +173,7 @@ Sandbox settings live in `~/.openclaw/openclaw.json` under `agents.defaults.sand "defaults": { "sandbox": { "mode": "all", // off, non-main, all + "backend": "docker", // docker, ssh, openshell "scope": "agent", // session, agent, shared "docker": { "image": "openclaw-sandbox:bookworm-slim", diff --git a/docs/cli/secrets.md b/docs/cli/secrets.md index f90a5de8ec0..baefdc91886 100644 --- a/docs/cli/secrets.md +++ b/docs/cli/secrets.md @@ -14,9 +14,9 @@ Use `openclaw secrets` to manage SecretRefs and keep the active runtime snapshot Command roles: - `reload`: gateway RPC (`secrets.reload`) that re-resolves refs and swaps runtime snapshot only on full success (no config writes). -- `audit`: read-only scan of configuration/auth/generated-model stores and legacy residues for plaintext, unresolved refs, and precedence drift. +- `audit`: read-only scan of configuration/auth/generated-model stores and legacy residues for plaintext, unresolved refs, and precedence drift (exec refs are skipped unless `--allow-exec` is set). - `configure`: interactive planner for provider setup, target mapping, and preflight (TTY required). -- `apply`: execute a saved plan (`--dry-run` for validation only), then scrub targeted plaintext residues. +- `apply`: execute a saved plan (`--dry-run` for validation only; dry-run skips exec checks by default, and write mode rejects exec-containing plans unless `--allow-exec` is set), then scrub targeted plaintext residues. Recommended operator loop: @@ -29,6 +29,8 @@ openclaw secrets audit --check openclaw secrets reload ``` +If your plan includes `exec` SecretRefs/providers, pass `--allow-exec` on both dry-run and write apply commands. + Exit code note for CI/gates: - `audit --check` returns `1` on findings. @@ -73,6 +75,7 @@ Header residue note: openclaw secrets audit openclaw secrets audit --check openclaw secrets audit --json +openclaw secrets audit --allow-exec ``` Exit behavior: @@ -83,6 +86,7 @@ Exit behavior: Report shape highlights: - `status`: `clean | findings | unresolved` +- `resolution`: `refsChecked`, `skippedExecRefs`, `resolvabilityComplete` - `summary`: `plaintextCount`, `unresolvedRefCount`, `shadowedRefCount`, `legacyResidueCount` - finding codes: - `PLAINTEXT_FOUND` @@ -115,6 +119,7 @@ Flags: - `--providers-only`: configure `secrets.providers` only, skip credential mapping. - `--skip-provider-setup`: skip provider setup and map credentials to existing providers. - `--agent `: scope `auth-profiles.json` target discovery and writes to one agent store. +- `--allow-exec`: allow exec SecretRef checks during preflight/apply (may execute provider commands). Notes: @@ -124,6 +129,7 @@ Notes: - `configure` supports creating new `auth-profiles.json` mappings directly in the picker flow. - Canonical supported surface: [SecretRef Credential Surface](/reference/secretref-credential-surface). - It performs preflight resolution before apply. +- If preflight/apply includes exec refs, keep `--allow-exec` set for both steps. - Generated plans default to scrub options (`scrubEnv`, `scrubAuthProfilesForProviderTargets`, `scrubLegacyAuthJson` all enabled). - Apply path is one-way for scrubbed plaintext values. - Without `--apply`, CLI still prompts `Apply this plan now?` after preflight. @@ -141,10 +147,19 @@ Apply or preflight a plan generated previously: ```bash openclaw secrets apply --from /tmp/openclaw-secrets-plan.json +openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --allow-exec openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run +openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run --allow-exec openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --json ``` +Exec behavior: + +- `--dry-run` validates preflight without writing files. +- exec SecretRef checks are skipped by default in dry-run. +- write mode rejects plans that contain exec SecretRefs/providers unless `--allow-exec` is set. +- Use `--allow-exec` to opt in to exec provider checks/execution in either mode. + Plan contract details (allowed target paths, validation rules, and failure semantics): - [Secrets Apply Plan Contract](/gateway/secrets-plan-contract) diff --git a/docs/cli/security.md b/docs/cli/security.md index cc705b31a30..28b65f3629b 100644 --- a/docs/cli/security.md +++ b/docs/cli/security.md @@ -19,6 +19,8 @@ Related: ```bash openclaw security audit openclaw security audit --deep +openclaw security audit --deep --password +openclaw security audit --deep --token openclaw security audit --fix openclaw security audit --json ``` @@ -28,7 +30,7 @@ This is for cooperative/shared inbox hardening. A single Gateway shared by mutua It also emits `security.trust_model.multi_user_heuristic` when config suggests likely shared-user ingress (for example open DM/group policy, configured group targets, or wildcard sender rules), and reminds you that OpenClaw is a personal-assistant trust model by default. For intentional shared-user setups, the audit guidance is to sandbox all sessions, keep filesystem access workspace-scoped, and keep personal/private identities or credentials off that runtime. It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled. -For webhook ingress, it warns when `hooks.defaultSessionKey` is unset, when request `sessionKey` overrides are enabled, and when overrides are enabled without `hooks.allowedSessionKeyPrefixes`. +For webhook ingress, it warns when `hooks.token` reuses the Gateway token, when `hooks.defaultSessionKey` is unset, when `hooks.allowedAgentIds` is unrestricted, when request `sessionKey` overrides are enabled, and when overrides are enabled without `hooks.allowedSessionKeyPrefixes`. It also warns when sandbox Docker settings are configured while sandbox mode is off, when `gateway.nodes.denyCommands` uses ineffective pattern-like/unknown entries (exact node command-name matching only, not shell-text filtering), when `gateway.nodes.allowCommands` explicitly enables dangerous node commands, when global `tools.profile="minimal"` is overridden by agent tool profiles, when open groups expose runtime/filesystem tools without sandbox/workspace guards, and when installed extension plugin tools may be reachable under permissive tool policy. It also flags `gateway.allowRealIpFallback=true` (header-spoofing risk if proxies are misconfigured) and `discovery.mdns.mode="full"` (metadata leakage via mDNS TXT records). It also warns when sandbox browser uses Docker `bridge` network without `sandbox.browser.cdpSourceRange`. @@ -40,6 +42,12 @@ It warns when `gateway.auth.mode="none"` leaves Gateway HTTP APIs reachable with Settings prefixed with `dangerous`/`dangerously` are explicit break-glass operator overrides; enabling one is not, by itself, a security vulnerability report. For the complete dangerous-parameter inventory, see the "Insecure or dangerous flags summary" section in [Security](/gateway/security). +SecretRef behavior: + +- `security audit` resolves supported SecretRefs in read-only mode for its targeted paths. +- If a SecretRef is unavailable in the current command path, audit continues and reports `secretDiagnostics` (instead of crashing). +- `--token` and `--password` only override deep-probe auth for that command invocation; they do not rewrite config or SecretRef mappings. + ## JSON output Use `--json` for CI/policy checks: diff --git a/docs/cli/sessions.md b/docs/cli/sessions.md index 4ed5ace54ee..6ea2df094f0 100644 --- a/docs/cli/sessions.md +++ b/docs/cli/sessions.md @@ -24,6 +24,12 @@ Scope selection: - `--all-agents`: aggregate all configured agent stores - `--store `: explicit store path (cannot be combined with `--agent` or `--all-agents`) +`openclaw sessions --all-agents` reads configured agent stores. Gateway and ACP +session discovery are broader: they also include disk-only stores found under +the default `agents/` root or a templated `session.store` root. Those +discovered stores must resolve to regular `sessions.json` files inside the +agent root; symlinks and out-of-root paths are skipped. + JSON examples: `openclaw sessions --all-agents --json`: @@ -54,7 +60,7 @@ openclaw sessions cleanup --dry-run openclaw sessions cleanup --agent work --dry-run openclaw sessions cleanup --all-agents --dry-run openclaw sessions cleanup --enforce -openclaw sessions cleanup --enforce --active-key "agent:main:telegram:dm:123" +openclaw sessions cleanup --enforce --active-key "agent:main:telegram:direct:123" openclaw sessions cleanup --json ``` diff --git a/docs/cli/setup.md b/docs/cli/setup.md index 340a53a30d7..e13cd89e5b2 100644 --- a/docs/cli/setup.md +++ b/docs/cli/setup.md @@ -1,7 +1,7 @@ --- summary: "CLI reference for `openclaw setup` (initialize config + workspace)" read_when: - - You’re doing first-run setup without the full onboarding wizard + - You’re doing first-run setup without full CLI onboarding - You want to set the default workspace path title: "setup" --- @@ -13,7 +13,7 @@ Initialize `~/.openclaw/openclaw.json` and the agent workspace. Related: - Getting started: [Getting started](/start/getting-started) -- Wizard: [Onboarding](/start/onboarding) +- CLI onboarding: [Onboarding (CLI)](/start/wizard) ## Examples @@ -22,7 +22,7 @@ openclaw setup openclaw setup --workspace ~/.openclaw/workspace ``` -To run the wizard via setup: +To run onboarding via setup: ```bash openclaw setup --wizard diff --git a/docs/cli/status.md b/docs/cli/status.md index 856c341b036..3f0f5bb5bf8 100644 --- a/docs/cli/status.md +++ b/docs/cli/status.md @@ -26,3 +26,5 @@ Notes: - Update info surfaces in the Overview; if an update is available, status prints a hint to run `openclaw update` (see [Updating](/install/updating)). - Read-only status surfaces (`status`, `status --json`, `status --all`) resolve supported SecretRefs for their targeted config paths when possible. - If a supported channel SecretRef is configured but unavailable in the current command path, status stays read-only and reports degraded output instead of crashing. Human output shows warnings such as “configured token unavailable in this command path”, and JSON output includes `secretDiagnostics`. +- When command-local SecretRef resolution succeeds, status prefers the resolved snapshot and clears transient “secret unavailable” channel markers from the final output. +- `status --all` includes a Secrets overview row and a diagnosis section that summarizes secret diagnostics (truncated for readability) without stopping report generation. diff --git a/docs/cli/update.md b/docs/cli/update.md index 7a1840096f2..d1c61518b0c 100644 --- a/docs/cli/update.md +++ b/docs/cli/update.md @@ -21,6 +21,7 @@ openclaw update wizard openclaw update --channel beta openclaw update --channel dev openclaw update --tag beta +openclaw update --tag main openclaw update --dry-run openclaw update --no-restart openclaw update --json @@ -31,7 +32,7 @@ openclaw --update - `--no-restart`: skip restarting the Gateway service after a successful update. - `--channel `: set the update channel (git + npm; persisted in config). -- `--tag `: override the npm dist-tag or version for this update only. +- `--tag `: override the package target for this update only. For package installs, `main` maps to `github:openclaw/openclaw#main`. - `--dry-run`: preview planned update actions (channel/tag/target/restart flow) without writing config, installing, syncing plugins, or restarting. - `--json`: print machine-readable `UpdateRunResult` JSON. - `--timeout `: per-step timeout (default is 1200s). diff --git a/docs/concepts/agent-loop.md b/docs/concepts/agent-loop.md index 32c4c149b20..bf60b23f1d7 100644 --- a/docs/concepts/agent-loop.md +++ b/docs/concepts/agent-loop.md @@ -92,7 +92,7 @@ These run inside the agent loop or gateway pipeline: - **`session_start` / `session_end`**: session lifecycle boundaries. - **`gateway_start` / `gateway_stop`**: gateway lifecycle events. -See [Plugins](/tools/plugin#plugin-hooks) for the hook API and registration details. +See [Plugin hooks](/plugins/architecture#provider-runtime-hooks) for the hook API and registration details. ## Streaming + partial replies diff --git a/docs/concepts/compaction.md b/docs/concepts/compaction.md index 73f6372c3f7..550d3b385d4 100644 --- a/docs/concepts/compaction.md +++ b/docs/concepts/compaction.md @@ -97,6 +97,25 @@ compaction and can run alongside it. See [OpenAI provider](/providers/openai) for model params and overrides. +## Custom context engines + +Compaction behavior is owned by the active +[context engine](/concepts/context-engine). The legacy engine uses the built-in +summarization described above. Plugin engines (selected via +`plugins.slots.contextEngine`) can implement any compaction strategy — DAG +summaries, vector retrieval, incremental condensation, etc. + +When a plugin engine sets `ownsCompaction: true`, OpenClaw delegates all +compaction decisions to the engine and does not run built-in auto-compaction. + +When `ownsCompaction` is `false` or unset, OpenClaw may still use Pi's +built-in in-attempt auto-compaction, but the active engine's `compact()` method +still handles `/compact` and overflow recovery. There is no automatic fallback +to the legacy engine's compaction path. + +If you are building a non-owning context engine, implement `compact()` by +calling `delegateCompactionToRuntime(...)` from `openclaw/plugin-sdk/core`. + ## Tips - Use `/compact` when sessions feel stale or context is bloated. diff --git a/docs/concepts/context-engine.md b/docs/concepts/context-engine.md new file mode 100644 index 00000000000..0b2ec1cd78b --- /dev/null +++ b/docs/concepts/context-engine.md @@ -0,0 +1,268 @@ +--- +summary: "Context engine: pluggable context assembly, compaction, and subagent lifecycle" +read_when: + - You want to understand how OpenClaw assembles model context + - You are switching between the legacy engine and a plugin engine + - You are building a context engine plugin +title: "Context Engine" +--- + +# Context Engine + +A **context engine** controls how OpenClaw builds model context for each run. +It decides which messages to include, how to summarize older history, and how +to manage context across subagent boundaries. + +OpenClaw ships with a built-in `legacy` engine. Plugins can register +alternative engines that replace the active context-engine lifecycle. + +## Quick start + +Check which engine is active: + +```bash +openclaw doctor +# or inspect config directly: +cat ~/.openclaw/openclaw.json | jq '.plugins.slots.contextEngine' +``` + +### Installing a context engine plugin + +Context engine plugins are installed like any other OpenClaw plugin. Install +first, then select the engine in the slot: + +```bash +# Install from npm +openclaw plugins install @martian-engineering/lossless-claw + +# Or install from a local path (for development) +openclaw plugins install -l ./my-context-engine +``` + +Then enable the plugin and select it as the active engine in your config: + +```json5 +// openclaw.json +{ + plugins: { + slots: { + contextEngine: "lossless-claw", // must match the plugin's registered engine id + }, + entries: { + "lossless-claw": { + enabled: true, + // Plugin-specific config goes here (see the plugin's docs) + }, + }, + }, +} +``` + +Restart the gateway after installing and configuring. + +To switch back to the built-in engine, set `contextEngine` to `"legacy"` (or +remove the key entirely — `"legacy"` is the default). + +## How it works + +Every time OpenClaw runs a model prompt, the context engine participates at +four lifecycle points: + +1. **Ingest** — called when a new message is added to the session. The engine + can store or index the message in its own data store. +2. **Assemble** — called before each model run. The engine returns an ordered + set of messages (and an optional `systemPromptAddition`) that fit within + the token budget. +3. **Compact** — called when the context window is full, or when the user runs + `/compact`. The engine summarizes older history to free space. +4. **After turn** — called after a run completes. The engine can persist state, + trigger background compaction, or update indexes. + +### Subagent lifecycle (optional) + +OpenClaw currently calls one subagent lifecycle hook: + +- **onSubagentEnded** — clean up when a subagent session completes or is swept. + +The `prepareSubagentSpawn` hook is part of the interface for future use, but +the runtime does not invoke it yet. + +### System prompt addition + +The `assemble` method can return a `systemPromptAddition` string. OpenClaw +prepends this to the system prompt for the run. This lets engines inject +dynamic recall guidance, retrieval instructions, or context-aware hints +without requiring static workspace files. + +## The legacy engine + +The built-in `legacy` engine preserves OpenClaw's original behavior: + +- **Ingest**: no-op (the session manager handles message persistence directly). +- **Assemble**: pass-through (the existing sanitize → validate → limit pipeline + in the runtime handles context assembly). +- **Compact**: delegates to the built-in summarization compaction, which creates + a single summary of older messages and keeps recent messages intact. +- **After turn**: no-op. + +The legacy engine does not register tools or provide a `systemPromptAddition`. + +When no `plugins.slots.contextEngine` is set (or it's set to `"legacy"`), this +engine is used automatically. + +## Plugin engines + +A plugin can register a context engine using the plugin API: + +```ts +export default function register(api) { + api.registerContextEngine("my-engine", () => ({ + info: { + id: "my-engine", + name: "My Context Engine", + ownsCompaction: true, + }, + + async ingest({ sessionId, message, isHeartbeat }) { + // Store the message in your data store + return { ingested: true }; + }, + + async assemble({ sessionId, messages, tokenBudget }) { + // Return messages that fit the budget + return { + messages: buildContext(messages, tokenBudget), + estimatedTokens: countTokens(messages), + systemPromptAddition: "Use lcm_grep to search history...", + }; + }, + + async compact({ sessionId, force }) { + // Summarize older context + return { ok: true, compacted: true }; + }, + })); +} +``` + +Then enable it in config: + +```json5 +{ + plugins: { + slots: { + contextEngine: "my-engine", + }, + entries: { + "my-engine": { + enabled: true, + }, + }, + }, +} +``` + +### The ContextEngine interface + +Required members: + +| Member | Kind | Purpose | +| ------------------ | -------- | -------------------------------------------------------- | +| `info` | Property | Engine id, name, version, and whether it owns compaction | +| `ingest(params)` | Method | Store a single message | +| `assemble(params)` | Method | Build context for a model run (returns `AssembleResult`) | +| `compact(params)` | Method | Summarize/reduce context | + +`assemble` returns an `AssembleResult` with: + +- `messages` — the ordered messages to send to the model. +- `estimatedTokens` (required, `number`) — the engine's estimate of total + tokens in the assembled context. OpenClaw uses this for compaction threshold + decisions and diagnostic reporting. +- `systemPromptAddition` (optional, `string`) — prepended to the system prompt. + +Optional members: + +| Member | Kind | Purpose | +| ------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------- | +| `bootstrap(params)` | Method | Initialize engine state for a session. Called once when the engine first sees a session (e.g., import history). | +| `ingestBatch(params)` | Method | Ingest a completed turn as a batch. Called after a run completes, with all messages from that turn at once. | +| `afterTurn(params)` | Method | Post-run lifecycle work (persist state, trigger background compaction). | +| `prepareSubagentSpawn(params)` | Method | Set up shared state for a child session. | +| `onSubagentEnded(params)` | Method | Clean up after a subagent ends. | +| `dispose()` | Method | Release resources. Called during gateway shutdown or plugin reload — not per-session. | + +### ownsCompaction + +`ownsCompaction` controls whether Pi's built-in in-attempt auto-compaction stays +enabled for the run: + +- `true` — the engine owns compaction behavior. OpenClaw disables Pi's built-in + auto-compaction for that run, and the engine's `compact()` implementation is + responsible for `/compact`, overflow recovery compaction, and any proactive + compaction it wants to do in `afterTurn()`. +- `false` or unset — Pi's built-in auto-compaction may still run during prompt + execution, but the active engine's `compact()` method is still called for + `/compact` and overflow recovery. + +`ownsCompaction: false` does **not** mean OpenClaw automatically falls back to +the legacy engine's compaction path. + +That means there are two valid plugin patterns: + +- **Owning mode** — implement your own compaction algorithm and set + `ownsCompaction: true`. +- **Delegating mode** — set `ownsCompaction: false` and have `compact()` call + `delegateCompactionToRuntime(...)` from `openclaw/plugin-sdk/core` to use + OpenClaw's built-in compaction behavior. + +A no-op `compact()` is unsafe for an active non-owning engine because it +disables the normal `/compact` and overflow-recovery compaction path for that +engine slot. + +## Configuration reference + +```json5 +{ + plugins: { + slots: { + // Select the active context engine. Default: "legacy". + // Set to a plugin id to use a plugin engine. + contextEngine: "legacy", + }, + }, +} +``` + +The slot is exclusive at run time — only one registered context engine is +resolved for a given run or compaction operation. Other enabled +`kind: "context-engine"` plugins can still load and run their registration +code; `plugins.slots.contextEngine` only selects which registered engine id +OpenClaw resolves when it needs a context engine. + +## Relationship to compaction and memory + +- **Compaction** is one responsibility of the context engine. The legacy engine + delegates to OpenClaw's built-in summarization. Plugin engines can implement + any compaction strategy (DAG summaries, vector retrieval, etc.). +- **Memory plugins** (`plugins.slots.memory`) are separate from context engines. + Memory plugins provide search/retrieval; context engines control what the + model sees. They can work together — a context engine might use memory + plugin data during assembly. +- **Session pruning** (trimming old tool results in-memory) still runs + regardless of which context engine is active. + +## Tips + +- Use `openclaw doctor` to verify your engine is loading correctly. +- If switching engines, existing sessions continue with their current history. + The new engine takes over for future runs. +- Engine errors are logged and surfaced in diagnostics. If a plugin engine + fails to register or the selected engine id cannot be resolved, OpenClaw + does not fall back automatically; runs fail until you fix the plugin or + switch `plugins.slots.contextEngine` back to `"legacy"`. +- For development, use `openclaw plugins install -l ./my-engine` to link a + local plugin directory without copying. + +See also: [Compaction](/concepts/compaction), [Context](/concepts/context), +[Plugins](/tools/plugin), [Plugin manifest](/plugins/manifest). diff --git a/docs/concepts/context.md b/docs/concepts/context.md index abc5e5af47c..107afc164ae 100644 --- a/docs/concepts/context.md +++ b/docs/concepts/context.md @@ -116,7 +116,7 @@ Large files are truncated per-file using `agents.defaults.bootstrapMaxChars` (de When truncation occurs, the runtime can inject an in-prompt warning block under Project Context. Configure this with `agents.defaults.bootstrapPromptTruncationWarning` (`off`, `once`, `always`; default `once`). -## Skills: what’s injected vs loaded on-demand +## Skills: injected vs loaded on-demand The system prompt includes a compact **skills list** (name + description + location). This list has real overhead. @@ -131,7 +131,7 @@ Tools affect context in two ways: `/context detail` breaks down the biggest tool schemas so you can see what dominates. -## Commands, directives, and “inline shortcuts” +## Commands, directives, and "inline shortcuts" Slash commands are handled by the Gateway. There are a few different behaviors: @@ -157,7 +157,10 @@ By default, OpenClaw uses the built-in `legacy` context engine for assembly and compaction. If you install a plugin that provides `kind: "context-engine"` and select it with `plugins.slots.contextEngine`, OpenClaw delegates context assembly, `/compact`, and related subagent context lifecycle hooks to that -engine instead. +engine instead. `ownsCompaction: false` does not auto-fallback to the legacy +engine; the active engine must still implement `compact()` correctly. See +[Context Engine](/concepts/context-engine) for the full +pluggable interface, lifecycle hooks, and configuration. ## What `/context` actually reports diff --git a/docs/concepts/features.md b/docs/concepts/features.md index 1d04af9187d..03528032b40 100644 --- a/docs/concepts/features.md +++ b/docs/concepts/features.md @@ -5,6 +5,8 @@ read_when: title: "Features" --- +# Features + ## Highlights diff --git a/docs/concepts/memory.md b/docs/concepts/memory.md index b3940945249..2649125dc45 100644 --- a/docs/concepts/memory.md +++ b/docs/concepts/memory.md @@ -23,6 +23,8 @@ The default workspace layout uses two memory layers: - Read today + yesterday at session start. - `MEMORY.md` (optional) - Curated long-term memory. + - If both `MEMORY.md` and `memory.md` exist at the workspace root, OpenClaw only loads `MEMORY.md`. + - Lowercase `memory.md` is only used as a fallback when `MEMORY.md` is absent. - **Only load in the main, private session** (never in group contexts). These files live under the workspace (`agents.defaults.workspace`, default @@ -284,9 +286,46 @@ Notes: - Paths can be absolute or workspace-relative. - Directories are scanned recursively for `.md` files. -- Only Markdown files are indexed. +- By default, only Markdown files are indexed. +- If `memorySearch.multimodal.enabled = true`, OpenClaw also indexes supported image/audio files under `extraPaths` only. Default memory roots (`MEMORY.md`, `memory.md`, `memory/**/*.md`) stay Markdown-only. - Symlinks are ignored (files or directories). +### Multimodal memory files (Gemini image + audio) + +OpenClaw can index image and audio files from `memorySearch.extraPaths` when using Gemini embedding 2: + +```json5 +agents: { + defaults: { + memorySearch: { + provider: "gemini", + model: "gemini-embedding-2-preview", + extraPaths: ["assets/reference", "voice-notes"], + multimodal: { + enabled: true, + modalities: ["image", "audio"], // or ["all"] + maxFileBytes: 10000000 + }, + remote: { + apiKey: "YOUR_GEMINI_API_KEY" + } + } + } +} +``` + +Notes: + +- Multimodal memory is currently supported only for `gemini-embedding-2-preview`. +- Multimodal indexing applies only to files discovered through `memorySearch.extraPaths`. +- Supported modalities in this phase: image and audio. +- `memorySearch.fallback` must stay `"none"` while multimodal memory is enabled. +- Matching image/audio file bytes are uploaded to the configured Gemini embedding endpoint during indexing. +- Supported image extensions: `.jpg`, `.jpeg`, `.png`, `.webp`, `.gif`, `.heic`, `.heif`. +- Supported audio extensions: `.mp3`, `.wav`, `.ogg`, `.opus`, `.m4a`, `.aac`, `.flac`. +- Search queries remain text, but Gemini can compare those text queries against indexed image/audio embeddings. +- `memory_get` still reads Markdown only; binary files are searchable but not returned as raw file contents. + ### Gemini embeddings (native) Set the provider to `gemini` to use the Gemini embeddings API directly: @@ -310,6 +349,29 @@ Notes: - `remote.baseUrl` is optional (defaults to the Gemini API base URL). - `remote.headers` lets you add extra headers if needed. - Default model: `gemini-embedding-001`. +- `gemini-embedding-2-preview` is also supported: 8192 token limit and configurable dimensions (768 / 1536 / 3072, default 3072). + +#### Gemini Embedding 2 (preview) + +```json5 +agents: { + defaults: { + memorySearch: { + provider: "gemini", + model: "gemini-embedding-2-preview", + outputDimensionality: 3072, // optional: 768, 1536, or 3072 (default) + remote: { + apiKey: "YOUR_GEMINI_API_KEY" + } + } + } +} +``` + +> **⚠️ Re-index required:** Switching from `gemini-embedding-001` (768 dimensions) +> to `gemini-embedding-2-preview` (3072 dimensions) changes the vector size. The same is true if you +> change `outputDimensionality` between 768, 1536, and 3072. +> OpenClaw will automatically reindex when it detects a model or dimension change. If you want to use a **custom OpenAI-compatible endpoint** (OpenRouter, vLLM, or a proxy), you can use the `remote` configuration with the OpenAI provider: diff --git a/docs/concepts/model-failover.md b/docs/concepts/model-failover.md index 80b3420d07c..80592bcc2c9 100644 --- a/docs/concepts/model-failover.md +++ b/docs/concepts/model-failover.md @@ -70,7 +70,7 @@ they are tried first, but OpenClaw may rotate to another profile on rate limits/ User‑pinned profiles stay locked to that profile; if it fails and model fallbacks are configured, OpenClaw moves to the next model instead of switching profiles. -### Why OAuth can “look lost” +### Why OAuth can "look lost" If you have both an OAuth profile and an API key profile for the same provider, round‑robin can switch between them across messages unless pinned. To force a single profile: diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index 4f3d80b2420..98f68bef5cc 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -16,6 +16,107 @@ For model selection rules, see [/concepts/models](/concepts/models). - Model refs use `provider/model` (example: `opencode/claude-opus-4-6`). - If you set `agents.defaults.models`, it becomes the allowlist. - CLI helpers: `openclaw onboard`, `openclaw models list`, `openclaw models set `. +- Provider plugins can inject model catalogs via `registerProvider({ catalog })`; + OpenClaw merges that output into `models.providers` before writing + `models.json`. +- Provider manifests can declare `providerAuthEnvVars` so generic env-based + auth probes do not need to load plugin runtime. The remaining core env-var + map is now just for non-plugin/core providers and a few generic-precedence + cases such as Anthropic API-key-first onboarding. +- Provider plugins can also own provider runtime behavior via + `resolveDynamicModel`, `prepareDynamicModel`, `normalizeResolvedModel`, + `capabilities`, `prepareExtraParams`, `wrapStreamFn`, `formatApiKey`, + `refreshOAuth`, `buildAuthDoctorHint`, + `isCacheTtlEligible`, `buildMissingAuthMessage`, + `suppressBuiltInModel`, `augmentModelCatalog`, `isBinaryThinking`, + `supportsXHighThinking`, `resolveDefaultThinkingLevel`, + `isModernModelRef`, `prepareRuntimeAuth`, `resolveUsageAuth`, and + `fetchUsageSnapshot`. +- Note: provider runtime `capabilities` is shared runner metadata (provider + family, transcript/tooling quirks, transport/cache hints). It is not the + same as the [public capability model](/plugins/architecture#public-capability-model) + which describes what a plugin registers (text inference, speech, etc.). + +## Plugin-owned provider behavior + +Provider plugins can now own most provider-specific logic while OpenClaw keeps +the generic inference loop. + +Typical split: + +- `auth[].run` / `auth[].runNonInteractive`: provider owns onboarding/login + flows for `openclaw onboard`, `openclaw models auth`, and headless setup +- `wizard.setup` / `wizard.modelPicker`: provider owns auth-choice labels, + legacy aliases, onboarding allowlist hints, and setup entries in onboarding/model pickers +- `catalog`: provider appears in `models.providers` +- `resolveDynamicModel`: provider accepts model ids not present in the local + static catalog yet +- `prepareDynamicModel`: provider needs a metadata refresh before retrying + dynamic resolution +- `normalizeResolvedModel`: provider needs transport or base URL rewrites +- `capabilities`: provider publishes transcript/tooling/provider-family quirks +- `prepareExtraParams`: provider defaults or normalizes per-model request params +- `wrapStreamFn`: provider applies request headers/body/model compat wrappers +- `formatApiKey`: provider formats stored auth profiles into the runtime + `apiKey` string expected by the transport +- `refreshOAuth`: provider owns OAuth refresh when the shared `pi-ai` + refreshers are not enough +- `buildAuthDoctorHint`: provider appends repair guidance when OAuth refresh + fails +- `isCacheTtlEligible`: provider decides which upstream model ids support prompt-cache TTL +- `buildMissingAuthMessage`: provider replaces the generic auth-store error + with a provider-specific recovery hint +- `suppressBuiltInModel`: provider hides stale upstream rows and can return a + vendor-owned error for direct resolution failures +- `augmentModelCatalog`: provider appends synthetic/final catalog rows after + discovery and config merging +- `isBinaryThinking`: provider owns binary on/off thinking UX +- `supportsXHighThinking`: provider opts selected models into `xhigh` +- `resolveDefaultThinkingLevel`: provider owns default `/think` policy for a + model family +- `isModernModelRef`: provider owns live/smoke preferred-model matching +- `prepareRuntimeAuth`: provider turns a configured credential into a short + lived runtime token +- `resolveUsageAuth`: provider resolves usage/quota credentials for `/usage` + and related status/reporting surfaces +- `fetchUsageSnapshot`: provider owns the usage endpoint fetch/parsing while + core still owns the summary shell and formatting + +Current bundled examples: + +- `anthropic`: Claude 4.6 forward-compat fallback, auth repair hints, usage + endpoint fetching, and cache-TTL/provider-family metadata +- `openrouter`: pass-through model ids, request wrappers, provider capability + hints, and cache-TTL policy +- `github-copilot`: onboarding/device login, forward-compat model fallback, + Claude-thinking transcript hints, runtime token exchange, and usage endpoint + fetching +- `openai`: GPT-5.4 forward-compat fallback, direct OpenAI transport + normalization, Codex-aware missing-auth hints, Spark suppression, synthetic + OpenAI/Codex catalog rows, thinking/live-model policy, and + provider-family metadata +- `google` and `google-gemini-cli`: Gemini 3.1 forward-compat fallback and + modern-model matching; Gemini CLI OAuth also owns auth-profile token + formatting, usage-token parsing, and quota endpoint fetching for usage + surfaces +- `moonshot`: shared transport, plugin-owned thinking payload normalization +- `kilocode`: shared transport, plugin-owned request headers, reasoning payload + normalization, Gemini transcript hints, and cache-TTL policy +- `zai`: GLM-5 forward-compat fallback, `tool_stream` defaults, cache-TTL + policy, binary-thinking/live-model policy, and usage auth + quota fetching +- `mistral`, `opencode`, and `opencode-go`: plugin-owned capability metadata +- `byteplus`, `cloudflare-ai-gateway`, `huggingface`, `kimi-coding`, + `modelstudio`, `nvidia`, `qianfan`, `synthetic`, `together`, `venice`, + `vercel-ai-gateway`, and `volcengine`: plugin-owned catalogs only +- `qwen-portal`: plugin-owned catalog, OAuth login, and OAuth refresh +- `minimax` and `xiaomi`: plugin-owned catalogs plus usage auth/snapshot logic + +The bundled `openai` plugin now owns both provider ids: `openai` and +`openai-codex`. + +That covers providers that still fit OpenClaw's normal transports. A provider +that needs a totally custom request executor is a separate, deeper extension +surface. ## API key rotation @@ -47,6 +148,8 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no** - Override per model via `agents.defaults.models["openai/"].params.transport` (`"sse"`, `"websocket"`, or `"auto"`) - OpenAI Responses WebSocket warm-up defaults to enabled via `params.openaiWsWarmup` (`true`/`false`) - OpenAI priority processing can be enabled via `agents.defaults.models["openai/"].params.serviceTier` +- OpenAI fast mode can be enabled per model via `agents.defaults.models["/"].params.fastMode` +- `openai/gpt-5.3-codex-spark` is intentionally suppressed in OpenClaw because the live OpenAI API rejects it; Spark is treated as Codex-only ```json5 { @@ -61,6 +164,7 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no** - Optional rotation: `ANTHROPIC_API_KEYS`, `ANTHROPIC_API_KEY_1`, `ANTHROPIC_API_KEY_2`, plus `OPENCLAW_LIVE_ANTHROPIC_KEY` (single override) - Example model: `anthropic/claude-opus-4-6` - CLI: `openclaw onboard --auth-choice token` (paste setup-token) or `openclaw models auth paste-token --provider anthropic` +- Direct API-key models support the shared `/fast` toggle and `params.fastMode`; OpenClaw maps that to Anthropic `service_tier` (`auto` vs `standard_only`) - Policy note: setup-token support is technical compatibility; Anthropic has blocked some subscription usage outside Claude Code in the past. Verify current Anthropic terms and decide based on your risk tolerance. - Recommendation: Anthropic API key auth is the safer, recommended path over subscription setup-token auth. @@ -78,6 +182,8 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no** - CLI: `openclaw onboard --auth-choice openai-codex` or `openclaw models auth login --provider openai-codex` - Default transport is `auto` (WebSocket-first, SSE fallback) - Override per model via `agents.defaults.models["openai-codex/"].params.transport` (`"sse"`, `"websocket"`, or `"auto"`) +- Shares the same `/fast` toggle and `params.fastMode` config as direct `openai/*` +- `openai-codex/gpt-5.3-codex-spark` remains available when the Codex OAuth catalog exposes it; entitlement-dependent - Policy note: OpenAI Codex OAuth is explicitly supported for external tools/workflows like OpenClaw. ```json5 @@ -109,16 +215,13 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no** - Compatibility: legacy OpenClaw config using `google/gemini-3.1-flash-preview` is normalized to `google/gemini-3-flash-preview` - CLI: `openclaw onboard --auth-choice gemini-api-key` -### Google Vertex, Antigravity, and Gemini CLI +### Google Vertex and Gemini CLI -- Providers: `google-vertex`, `google-antigravity`, `google-gemini-cli` -- Auth: Vertex uses gcloud ADC; Antigravity/Gemini CLI use their respective auth flows -- Caution: Antigravity and Gemini CLI OAuth in OpenClaw are unofficial integrations. Some users have reported Google account restrictions after using third-party clients. Review Google terms and use a non-critical account if you choose to proceed. -- Antigravity OAuth is shipped as a bundled plugin (`google-antigravity-auth`, disabled by default). - - Enable: `openclaw plugins enable google-antigravity-auth` - - Login: `openclaw models auth login --provider google-antigravity --set-default` -- Gemini CLI OAuth is shipped as a bundled plugin (`google-gemini-cli-auth`, disabled by default). - - Enable: `openclaw plugins enable google-gemini-cli-auth` +- Providers: `google-vertex`, `google-gemini-cli` +- Auth: Vertex uses gcloud ADC; Gemini CLI uses its OAuth flow +- Caution: Gemini CLI OAuth in OpenClaw is an unofficial integration. Some users have reported Google account restrictions after using third-party clients. Review Google terms and use a non-critical account if you choose to proceed. +- Gemini CLI OAuth is shipped as part of the bundled `google` plugin. + - Enable: `openclaw plugins enable google` - Login: `openclaw models auth login --provider google-gemini-cli --set-default` - Note: you do **not** paste a client id or secret into `openclaw.json`. The CLI login flow stores tokens in auth profiles on the gateway host. @@ -149,12 +252,26 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no** See [/providers/kilocode](/providers/kilocode) for setup details. -### Other built-in providers +### Other bundled provider plugins - OpenRouter: `openrouter` (`OPENROUTER_API_KEY`) - Example model: `openrouter/anthropic/claude-sonnet-4-5` - Kilo Gateway: `kilocode` (`KILOCODE_API_KEY`) - Example model: `kilocode/anthropic/claude-opus-4.6` +- MiniMax: `minimax` (`MINIMAX_API_KEY`) +- Moonshot: `moonshot` (`MOONSHOT_API_KEY`) +- Kimi Coding: `kimi-coding` (`KIMI_API_KEY` or `KIMICODE_API_KEY`) +- Qianfan: `qianfan` (`QIANFAN_API_KEY`) +- Model Studio: `modelstudio` (`MODELSTUDIO_API_KEY`) +- NVIDIA: `nvidia` (`NVIDIA_API_KEY`) +- Together: `together` (`TOGETHER_API_KEY`) +- Venice: `venice` (`VENICE_API_KEY`) +- Xiaomi: `xiaomi` (`XIAOMI_API_KEY`) +- Vercel AI Gateway: `vercel-ai-gateway` (`AI_GATEWAY_API_KEY`) +- Hugging Face Inference: `huggingface` (`HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN`) +- Cloudflare AI Gateway: `cloudflare-ai-gateway` (`CLOUDFLARE_AI_GATEWAY_API_KEY`) +- Volcengine: `volcengine` (`VOLCANO_ENGINE_API_KEY`) +- BytePlus: `byteplus` (`BYTEPLUS_API_KEY`) - xAI: `xai` (`XAI_API_KEY`) - Mistral: `mistral` (`MISTRAL_API_KEY`) - Example model: `mistral/mistral-large-latest` @@ -164,13 +281,17 @@ See [/providers/kilocode](/providers/kilocode) for setup details. - GLM models on Cerebras use ids `zai-glm-4.7` and `zai-glm-4.6`. - OpenAI-compatible base URL: `https://api.cerebras.ai/v1`. - GitHub Copilot: `github-copilot` (`COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN`) -- Hugging Face Inference: `huggingface` (`HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN`) — OpenAI-compatible router; example model: `huggingface/deepseek-ai/DeepSeek-R1`; CLI: `openclaw onboard --auth-choice huggingface-api-key`. See [Hugging Face (Inference)](/providers/huggingface). +- Hugging Face Inference example model: `huggingface/deepseek-ai/DeepSeek-R1`; CLI: `openclaw onboard --auth-choice huggingface-api-key`. See [Hugging Face (Inference)](/providers/huggingface). ## Providers via `models.providers` (custom/base URL) Use `models.providers` (or `models.json`) to add **custom** providers or OpenAI/Anthropic‑compatible proxies. +Many of the bundled provider plugins below already publish a default catalog. +Use explicit `models.providers.` entries only when you want to override the +default base URL, headers, or model list. + ### Moonshot AI (Kimi) Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider: @@ -181,20 +302,15 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider: Kimi K2 model IDs: - - -{/_ moonshot-kimi-k2-model-refs:start _/ && null} - - +[//]: # "moonshot-kimi-k2-model-refs:start" - `moonshot/kimi-k2.5` - `moonshot/kimi-k2-0905-preview` - `moonshot/kimi-k2-turbo-preview` - `moonshot/kimi-k2-thinking` - `moonshot/kimi-k2-thinking-turbo` - - {/_ moonshot-kimi-k2-model-refs:end _/ && null} - + +[//]: # "moonshot-kimi-k2-model-refs:end" ```json5 { @@ -235,10 +351,9 @@ Kimi Coding uses Moonshot AI's Anthropic-compatible endpoint: ### Qwen OAuth (free tier) Qwen provides OAuth access to Qwen Coder + Vision via a device-code flow. -Enable the bundled plugin, then log in: +The bundled provider plugin is enabled by default, so just log in: ```bash -openclaw plugins enable qwen-portal-auth openclaw models auth login --provider qwen-portal --set-default ``` @@ -352,12 +467,12 @@ See [/providers/minimax](/providers/minimax) for setup details, model options, a ### Ollama -Ollama is a local LLM runtime that provides an OpenAI-compatible API: +Ollama ships as a bundled provider plugin and uses Ollama's native API: - Provider: `ollama` - Auth: None required (local server) - Example model: `ollama/llama3.3` -- Installation: [https://ollama.ai](https://ollama.ai) +- Installation: [https://ollama.com/download](https://ollama.com/download) ```bash # Install Ollama, then pull a model: @@ -372,11 +487,15 @@ ollama pull llama3.3 } ``` -Ollama is automatically detected when running locally at `http://127.0.0.1:11434/v1`. See [/providers/ollama](/providers/ollama) for model recommendations and custom configuration. +Ollama is detected locally at `http://127.0.0.1:11434` when you opt in with +`OLLAMA_API_KEY`, and the bundled provider plugin adds Ollama directly to +`openclaw onboard` and the model picker. See [/providers/ollama](/providers/ollama) +for onboarding, cloud/local mode, and custom configuration. ### vLLM -vLLM is a local (or self-hosted) OpenAI-compatible server: +vLLM ships as a bundled provider plugin for local/self-hosted OpenAI-compatible +servers: - Provider: `vllm` - Auth: Optional (depends on your server) @@ -400,6 +519,34 @@ Then set a model (replace with one of the IDs returned by `/v1/models`): See [/providers/vllm](/providers/vllm) for details. +### SGLang + +SGLang ships as a bundled provider plugin for fast self-hosted +OpenAI-compatible servers: + +- Provider: `sglang` +- Auth: Optional (depends on your server) +- Default base URL: `http://127.0.0.1:30000/v1` + +To opt in to auto-discovery locally (any value works if your server does not +enforce auth): + +```bash +export SGLANG_API_KEY="sglang-local" +``` + +Then set a model (replace with one of the IDs returned by `/v1/models`): + +```json5 +{ + agents: { + defaults: { model: { primary: "sglang/your-model-id" } }, + }, +} +``` + +See [/providers/sglang](/providers/sglang) for details. + ### Local proxies (LM Studio, vLLM, LiteLLM, etc.) Example (OpenAI‑compatible): diff --git a/docs/concepts/models.md b/docs/concepts/models.md index f87eead821c..0a32e1b5d8b 100644 --- a/docs/concepts/models.md +++ b/docs/concepts/models.md @@ -26,6 +26,7 @@ Related: - `agents.defaults.models` is the allowlist/catalog of models OpenClaw can use (plus aliases). - `agents.defaults.imageModel` is used **only when** the primary model can’t accept images. +- `agents.defaults.imageGenerationModel` is used by the shared image-generation capability. If omitted, `image_generate` can still infer a provider default from compatible auth-backed image-generation plugins. - Per-agent defaults can override `agents.defaults.model` via `agents.list[].model` plus bindings (see [/concepts/multi-agent](/concepts/multi-agent)). ## Quick model policy @@ -34,9 +35,9 @@ Related: - Use fallbacks for cost/latency-sensitive tasks and lower-stakes chat. - For tool-enabled agents or untrusted inputs, avoid older/weaker model tiers. -## Setup wizard (recommended) +## Onboarding (recommended) -If you don’t want to hand-edit config, run the onboarding wizard: +If you don’t want to hand-edit config, run onboarding: ```bash openclaw onboard @@ -49,6 +50,7 @@ subscription** (OAuth) and **Anthropic** (API key or `claude setup-token`). - `agents.defaults.model.primary` and `agents.defaults.model.fallbacks` - `agents.defaults.imageModel.primary` and `agents.defaults.imageModel.fallbacks` +- `agents.defaults.imageGenerationModel.primary` and `agents.defaults.imageGenerationModel.fallbacks` - `agents.defaults.models` (allowlist + aliases + provider params) - `models.providers` (custom providers written into `models.json`) @@ -58,7 +60,7 @@ to `zai/*`. Provider configuration examples (including OpenCode) live in [/gateway/configuration](/gateway/configuration#opencode). -## “Model is not allowed” (and why replies stop) +## "Model is not allowed" (and why replies stop) If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for session overrides. When a user selects a model that isn’t in that allowlist, @@ -207,7 +209,7 @@ mode, pass `--yes` to accept defaults. ## Models registry (`models.json`) Custom providers in `models.providers` are written into `models.json` under the -agent directory (default `~/.openclaw/agents//models.json`). This file +agent directory (default `~/.openclaw/agents//agent/models.json`). This file is merged by default unless `models.mode` is set to `replace`. Merge mode precedence for matching provider IDs: @@ -215,7 +217,9 @@ Merge mode precedence for matching provider IDs: - Non-empty `baseUrl` already present in the agent `models.json` wins. - Non-empty `apiKey` in the agent `models.json` wins only when that provider is not SecretRef-managed in current config/auth-profile context. - SecretRef-managed provider `apiKey` values are refreshed from source markers (`ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs) instead of persisting resolved secrets. +- SecretRef-managed provider header values are refreshed from source markers (`secretref-env:ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs). - Empty or missing agent `apiKey`/`baseUrl` fall back to config `models.providers`. - Other provider fields are refreshed from config and normalized catalog data. -This marker-based persistence applies whenever OpenClaw regenerates `models.json`, including command-driven paths like `openclaw agent`. +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`. diff --git a/docs/concepts/multi-agent.md b/docs/concepts/multi-agent.md index 6f0bd086690..3f52fa77e74 100644 --- a/docs/concepts/multi-agent.md +++ b/docs/concepts/multi-agent.md @@ -9,7 +9,7 @@ status: active Goal: multiple _isolated_ agents (separate workspace + `agentDir` + sessions), plus multiple channel accounts (e.g. two WhatsApps) in one running Gateway. Inbound is routed to an agent via bindings. -## What is “one agent”? +## What is "one agent"? An **agent** is a fully scoped brain with its own: diff --git a/docs/concepts/presence.md b/docs/concepts/presence.md index a185205793a..1c9a7e3a12a 100644 --- a/docs/concepts/presence.md +++ b/docs/concepts/presence.md @@ -45,7 +45,7 @@ even before any clients connect. Every WS client begins with a `connect` request. On successful handshake the Gateway upserts a presence entry for that connection. -#### Why one‑off CLI commands don’t show up +#### Why one-off CLI commands do not show up The CLI often connects for short, one‑off commands. To avoid spamming the Instances list, `client.mode === "cli"` is **not** turned into a presence entry. diff --git a/docs/concepts/session-tool.md b/docs/concepts/session-tool.md index 90b48a7db53..fe444eb2c66 100644 --- a/docs/concepts/session-tool.md +++ b/docs/concepts/session-tool.md @@ -75,6 +75,25 @@ Behavior: - Returns messages array in the raw transcript format. - When given a `sessionId`, OpenClaw resolves it to the corresponding session key (missing ids error). +## Gateway session history and live transcript APIs + +Control UI and gateway clients can use the lower level history and live transcript surfaces directly. + +HTTP: + +- `GET /sessions/{sessionKey}/history` +- Query params: `limit`, `cursor`, `includeTools=1`, `follow=1` +- Unknown sessions return HTTP `404` with `error.type = "not_found"` +- `follow=1` upgrades the response to an SSE stream of transcript updates for that session + +WebSocket: + +- `sessions.subscribe` subscribes to all session lifecycle and transcript events visible to the client +- `sessions.messages.subscribe { key }` subscribes only to `session.message` events for one session +- `sessions.messages.unsubscribe { key }` removes that targeted transcript subscription +- `session.message` carries appended transcript messages plus live usage metadata when available +- `sessions.changed` emits `phase: "message"` for transcript appends so session lists can refresh counters and previews + ## sessions_send Send a message into another session. diff --git a/docs/concepts/session.md b/docs/concepts/session.md index 6c9010d2c11..2f00325b730 100644 --- a/docs/concepts/session.md +++ b/docs/concepts/session.md @@ -191,16 +191,16 @@ the workspace is writable. See [Memory](/concepts/memory) and - Direct chats follow `session.dmScope` (default `main`). - `main`: `agent::` (continuity across devices/channels). - Multiple phone numbers and channels can map to the same agent main key; they act as transports into one conversation. - - `per-peer`: `agent::dm:`. - - `per-channel-peer`: `agent:::dm:`. - - `per-account-channel-peer`: `agent::::dm:` (accountId defaults to `default`). + - `per-peer`: `agent::direct:`. + - `per-channel-peer`: `agent:::direct:`. + - `per-account-channel-peer`: `agent::::direct:` (accountId defaults to `default`). - If `session.identityLinks` matches a provider-prefixed peer id (for example `telegram:123`), the canonical key replaces `` so the same person shares a session across channels. - Group chats isolate state: `agent:::group:` (rooms/channels use `agent:::channel:`). - Telegram forum topics append `:topic:` to the group id for isolation. - Legacy `group:` keys are still recognized for migration. - Inbound contexts may still use `group:`; the channel is inferred from `Provider` and normalized to the canonical `agent:::group:` form. - Other sources: - - Cron jobs: `cron:` + - Cron jobs: `cron:` (isolated) or custom `session:` (persistent) - Webhooks: `hook:` (unless explicitly set by the hook) - Node runs: `node-` @@ -281,7 +281,7 @@ Runtime override (owner only): - `openclaw status` — shows store path and recent sessions. - `openclaw sessions --json` — dumps every entry (filter with `--active `). - `openclaw gateway call sessions.list --params '{}'` — fetch sessions from the running gateway (use `--url`/`--token` for remote gateway access). -- Send `/status` as a standalone message in chat to see whether the agent is reachable, how much of the session context is used, current thinking/verbose toggles, and when your WhatsApp web creds were last refreshed (helps spot relink needs). +- Send `/status` as a standalone message in chat to see whether the agent is reachable, how much of the session context is used, current thinking/fast/verbose toggles, and when your WhatsApp web creds were last refreshed (helps spot relink needs). - Send `/context list` or `/context detail` to see what’s in the system prompt and injected workspace files (and the biggest context contributors). - Send `/stop` (or standalone abort phrases like `stop`, `stop action`, `stop run`, `stop openclaw`) to abort the current run, clear queued followups for that session, and stop any sub-agent runs spawned from it (the reply includes the stopped count). - Send `/compact` (optional instructions) as a standalone message to summarize older context and free up window space. See [/concepts/compaction](/concepts/compaction). diff --git a/docs/concepts/streaming.md b/docs/concepts/streaming.md index c31048cb268..3f69ada2b91 100644 --- a/docs/concepts/streaming.md +++ b/docs/concepts/streaming.md @@ -90,7 +90,7 @@ more natural. - Modes: `off` (default), `natural` (800–2500ms), `custom` (`minMs`/`maxMs`). - Applies only to **block replies**, not final replies or tool summaries. -## “Stream chunks or everything” +## "Stream chunks or everything" This maps to: diff --git a/docs/concepts/system-prompt.md b/docs/concepts/system-prompt.md index 1a5edfcc6e3..a1d1b482fb2 100644 --- a/docs/concepts/system-prompt.md +++ b/docs/concepts/system-prompt.md @@ -59,7 +59,7 @@ Bootstrap files are trimmed and appended under **Project Context** so the model - `USER.md` - `HEARTBEAT.md` - `BOOTSTRAP.md` (only on brand-new workspaces) -- `MEMORY.md` and/or `memory.md` (when present in the workspace; either or both may be injected) +- `MEMORY.md` when present, otherwise `memory.md` as a lowercase fallback All of these files are **injected into the context window** on every turn, which means they consume tokens. Keep them concise — especially `MEMORY.md`, which can diff --git a/docs/concepts/typebox.md b/docs/concepts/typebox.md index 92c6eef2fe9..274e9e3beaa 100644 --- a/docs/concepts/typebox.md +++ b/docs/concepts/typebox.md @@ -185,7 +185,7 @@ ws.on("message", (data) => { }); ``` -## Worked example: add a method end‑to‑end +## Worked example: add a method end-to-end Example: add a new `system.echo` request that returns `{ ok: true, text }`. diff --git a/docs/design/kilo-gateway-integration.md b/docs/design/kilo-gateway-integration.md deleted file mode 100644 index 4f34e553c0f..00000000000 --- a/docs/design/kilo-gateway-integration.md +++ /dev/null @@ -1,534 +0,0 @@ -# Kilo Gateway Provider Integration Design - -## Overview - -This document outlines the design for integrating "Kilo Gateway" as a first-class provider in OpenClaw, modeled after the existing OpenRouter implementation. Kilo Gateway uses an OpenAI-compatible completions API with a different base URL. - -## Design Decisions - -### 1. Provider Naming - -**Recommendation: `kilocode`** - -Rationale: - -- Matches the user config example provided (`kilocode` provider key) -- Consistent with existing provider naming patterns (e.g., `openrouter`, `opencode`, `moonshot`) -- Short and memorable -- Avoids confusion with generic "kilo" or "gateway" terms - -Alternative considered: `kilo-gateway` - rejected because hyphenated names are less common in the codebase and `kilocode` is more concise. - -### 2. Default Model Reference - -**Recommendation: `kilocode/anthropic/claude-opus-4.6`** - -Rationale: - -- Based on user config example -- Claude Opus 4.5 is a capable default model -- Explicit model selection avoids reliance on auto-routing - -### 3. Base URL Configuration - -**Recommendation: Hardcoded default with config override** - -- **Default Base URL:** `https://api.kilo.ai/api/gateway/` -- **Configurable:** Yes, via `models.providers.kilocode.baseUrl` - -This matches the pattern used by other providers like Moonshot, Venice, and Synthetic. - -### 4. Model Scanning - -**Recommendation: No dedicated model scanning endpoint initially** - -Rationale: - -- Kilo Gateway proxies to OpenRouter, so models are dynamic -- Users can manually configure models in their config -- If Kilo Gateway exposes a `/models` endpoint in the future, scanning can be added - -### 5. Special Handling - -**Recommendation: Inherit OpenRouter behavior for Anthropic models** - -Since Kilo Gateway proxies to OpenRouter, the same special handling should apply: - -- Cache TTL eligibility for `anthropic/*` models -- Extra params (cacheControlTtl) for `anthropic/*` models -- Transcript policy follows OpenRouter patterns - -## Files to Modify - -### Core Credential Management - -#### 1. `src/commands/onboard-auth.credentials.ts` - -Add: - -```typescript -export const KILOCODE_DEFAULT_MODEL_REF = "kilocode/anthropic/claude-opus-4.6"; - -export async function setKilocodeApiKey(key: string, agentDir?: string) { - upsertAuthProfile({ - profileId: "kilocode:default", - credential: { - type: "api_key", - provider: "kilocode", - key, - }, - agentDir: resolveAuthAgentDir(agentDir), - }); -} -``` - -#### 2. `src/agents/model-auth.ts` - -Add to `envMap` in `resolveEnvApiKey()`: - -```typescript -const envMap: Record = { - // ... existing entries - kilocode: "KILOCODE_API_KEY", -}; -``` - -#### 3. `src/config/io.ts` - -Add to `SHELL_ENV_EXPECTED_KEYS`: - -```typescript -const SHELL_ENV_EXPECTED_KEYS = [ - // ... existing entries - "KILOCODE_API_KEY", -]; -``` - -### Config Application - -#### 4. `src/commands/onboard-auth.config-core.ts` - -Add new functions: - -```typescript -export const KILOCODE_BASE_URL = "https://api.kilo.ai/api/gateway/"; - -export function applyKilocodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig { - const models = { ...cfg.agents?.defaults?.models }; - models[KILOCODE_DEFAULT_MODEL_REF] = { - ...models[KILOCODE_DEFAULT_MODEL_REF], - alias: models[KILOCODE_DEFAULT_MODEL_REF]?.alias ?? "Kilo Gateway", - }; - - const providers = { ...cfg.models?.providers }; - const existingProvider = providers.kilocode; - const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< - string, - unknown - > as { apiKey?: string }; - const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; - const normalizedApiKey = resolvedApiKey?.trim(); - - providers.kilocode = { - ...existingProviderRest, - baseUrl: KILOCODE_BASE_URL, - api: "openai-completions", - ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), - }; - - return { - ...cfg, - agents: { - ...cfg.agents, - defaults: { - ...cfg.agents?.defaults, - models, - }, - }, - models: { - mode: cfg.models?.mode ?? "merge", - providers, - }, - }; -} - -export function applyKilocodeConfig(cfg: OpenClawConfig): OpenClawConfig { - const next = applyKilocodeProviderConfig(cfg); - const existingModel = next.agents?.defaults?.model; - return { - ...next, - agents: { - ...next.agents, - defaults: { - ...next.agents?.defaults, - model: { - ...(existingModel && "fallbacks" in (existingModel as Record) - ? { - fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks, - } - : undefined), - primary: KILOCODE_DEFAULT_MODEL_REF, - }, - }, - }, - }; -} -``` - -### Auth Choice System - -#### 5. `src/commands/onboard-types.ts` - -Add to `AuthChoice` type: - -```typescript -export type AuthChoice = - // ... existing choices - "kilocode-api-key"; -// ... -``` - -Add to `OnboardOptions`: - -```typescript -export type OnboardOptions = { - // ... existing options - kilocodeApiKey?: string; - // ... -}; -``` - -#### 6. `src/commands/auth-choice-options.ts` - -Add to `AuthChoiceGroupId`: - -```typescript -export type AuthChoiceGroupId = - // ... existing groups - "kilocode"; -// ... -``` - -Add to `AUTH_CHOICE_GROUP_DEFS`: - -```typescript -{ - value: "kilocode", - label: "Kilo Gateway", - hint: "API key (OpenRouter-compatible)", - choices: ["kilocode-api-key"], -}, -``` - -Add to `buildAuthChoiceOptions()`: - -```typescript -options.push({ - value: "kilocode-api-key", - label: "Kilo Gateway API key", - hint: "OpenRouter-compatible gateway", -}); -``` - -#### 7. `src/commands/auth-choice.preferred-provider.ts` - -Add mapping: - -```typescript -const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial> = { - // ... existing mappings - "kilocode-api-key": "kilocode", -}; -``` - -### Auth Choice Application - -#### 8. `src/commands/auth-choice.apply.api-providers.ts` - -Add import: - -```typescript -import { - // ... existing imports - applyKilocodeConfig, - applyKilocodeProviderConfig, - KILOCODE_DEFAULT_MODEL_REF, - setKilocodeApiKey, -} from "./onboard-auth.js"; -``` - -Add handling for `kilocode-api-key`: - -```typescript -if (authChoice === "kilocode-api-key") { - const store = ensureAuthProfileStore(params.agentDir, { - allowKeychainPrompt: false, - }); - const profileOrder = resolveAuthProfileOrder({ - cfg: nextConfig, - store, - provider: "kilocode", - }); - const existingProfileId = profileOrder.find((profileId) => Boolean(store.profiles[profileId])); - const existingCred = existingProfileId ? store.profiles[existingProfileId] : undefined; - let profileId = "kilocode:default"; - let mode: "api_key" | "oauth" | "token" = "api_key"; - let hasCredential = false; - - if (existingProfileId && existingCred?.type) { - profileId = existingProfileId; - mode = - existingCred.type === "oauth" ? "oauth" : existingCred.type === "token" ? "token" : "api_key"; - hasCredential = true; - } - - if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "kilocode") { - await setKilocodeApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); - hasCredential = true; - } - - if (!hasCredential) { - const envKey = resolveEnvApiKey("kilocode"); - if (envKey) { - const useExisting = await params.prompter.confirm({ - message: `Use existing KILOCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, - initialValue: true, - }); - if (useExisting) { - await setKilocodeApiKey(envKey.apiKey, params.agentDir); - hasCredential = true; - } - } - } - - if (!hasCredential) { - const key = await params.prompter.text({ - message: "Enter Kilo Gateway API key", - validate: validateApiKeyInput, - }); - await setKilocodeApiKey(normalizeApiKeyInput(String(key)), params.agentDir); - hasCredential = true; - } - - if (hasCredential) { - nextConfig = applyAuthProfileConfig(nextConfig, { - profileId, - provider: "kilocode", - mode, - }); - } - { - const applied = await applyDefaultModelChoice({ - config: nextConfig, - setDefaultModel: params.setDefaultModel, - defaultModel: KILOCODE_DEFAULT_MODEL_REF, - applyDefaultConfig: applyKilocodeConfig, - applyProviderConfig: applyKilocodeProviderConfig, - noteDefault: KILOCODE_DEFAULT_MODEL_REF, - noteAgentModel, - prompter: params.prompter, - }); - nextConfig = applied.config; - agentModelOverride = applied.agentModelOverride ?? agentModelOverride; - } - return { config: nextConfig, agentModelOverride }; -} -``` - -Also add tokenProvider mapping at the top of the function: - -```typescript -if (params.opts.tokenProvider === "kilocode") { - authChoice = "kilocode-api-key"; -} -``` - -### CLI Registration - -#### 9. `src/cli/program/register.onboard.ts` - -Add CLI option: - -```typescript -.option("--kilocode-api-key ", "Kilo Gateway API key") -``` - -Add to action handler: - -```typescript -kilocodeApiKey: opts.kilocodeApiKey as string | undefined, -``` - -Update auth-choice help text: - -```typescript -.option( - "--auth-choice ", - "Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|kilocode-api-key|ai-gateway-api-key|...", -) -``` - -### Non-Interactive Onboarding - -#### 10. `src/commands/onboard-non-interactive/local/auth-choice.ts` - -Add handling for `kilocode-api-key`: - -```typescript -if (authChoice === "kilocode-api-key") { - const resolved = await resolveNonInteractiveApiKey({ - provider: "kilocode", - cfg: baseConfig, - flagValue: opts.kilocodeApiKey, - flagName: "--kilocode-api-key", - envVar: "KILOCODE_API_KEY", - }); - await setKilocodeApiKey(resolved.apiKey, agentDir); - nextConfig = applyAuthProfileConfig(nextConfig, { - profileId: "kilocode:default", - provider: "kilocode", - mode: "api_key", - }); - // ... apply default model -} -``` - -### Export Updates - -#### 11. `src/commands/onboard-auth.ts` - -Add exports: - -```typescript -export { - // ... existing exports - applyKilocodeConfig, - applyKilocodeProviderConfig, - KILOCODE_BASE_URL, -} from "./onboard-auth.config-core.js"; - -export { - // ... existing exports - KILOCODE_DEFAULT_MODEL_REF, - setKilocodeApiKey, -} from "./onboard-auth.credentials.js"; -``` - -### Special Handling (Optional) - -#### 12. `src/agents/pi-embedded-runner/cache-ttl.ts` - -Add Kilo Gateway support for Anthropic models: - -```typescript -export function isCacheTtlEligibleProvider(provider: string, modelId: string): boolean { - const normalizedProvider = provider.toLowerCase(); - const normalizedModelId = modelId.toLowerCase(); - if (normalizedProvider === "anthropic") return true; - if (normalizedProvider === "openrouter" && normalizedModelId.startsWith("anthropic/")) - return true; - if (normalizedProvider === "kilocode" && normalizedModelId.startsWith("anthropic/")) return true; - return false; -} -``` - -#### 13. `src/agents/transcript-policy.ts` - -Add Kilo Gateway handling (similar to OpenRouter): - -```typescript -const isKilocodeGemini = provider === "kilocode" && modelId.toLowerCase().includes("gemini"); - -// Include in needsNonImageSanitize check -const needsNonImageSanitize = - isGoogle || isAnthropic || isMistral || isOpenRouterGemini || isKilocodeGemini; -``` - -## Configuration Structure - -### User Config Example - -```json -{ - "models": { - "mode": "merge", - "providers": { - "kilocode": { - "baseUrl": "https://api.kilo.ai/api/gateway/", - "apiKey": "xxxxx", - "api": "openai-completions", - "models": [ - { - "id": "anthropic/claude-opus-4.6", - "name": "Anthropic: Claude Opus 4.6" - }, - { "id": "minimax/minimax-m2.5:free", "name": "Minimax: Minimax M2.5" } - ] - } - } - } -} -``` - -### Auth Profile Structure - -```json -{ - "profiles": { - "kilocode:default": { - "type": "api_key", - "provider": "kilocode", - "key": "xxxxx" - } - } -} -``` - -## Testing Considerations - -1. **Unit Tests:** - - Test `setKilocodeApiKey()` writes correct profile - - Test `applyKilocodeConfig()` sets correct defaults - - Test `resolveEnvApiKey("kilocode")` returns correct env var - -2. **Integration Tests:** - - Test onboarding flow with `--auth-choice kilocode-api-key` - - Test non-interactive onboarding with `--kilocode-api-key` - - Test model selection with `kilocode/` prefix - -3. **E2E Tests:** - - Test actual API calls through Kilo Gateway (live tests) - -## Migration Notes - -- No migration needed for existing users -- New users can immediately use `kilocode-api-key` auth choice -- Existing manual config with `kilocode` provider will continue to work - -## Future Considerations - -1. **Model Catalog:** If Kilo Gateway exposes a `/models` endpoint, add scanning support similar to `scanOpenRouterModels()` - -2. **OAuth Support:** If Kilo Gateway adds OAuth, extend the auth system accordingly - -3. **Rate Limiting:** Consider adding rate limit handling specific to Kilo Gateway if needed - -4. **Documentation:** Add docs at `docs/providers/kilocode.md` explaining setup and usage - -## Summary of Changes - -| File | Change Type | Description | -| ----------------------------------------------------------- | ----------- | ----------------------------------------------------------------------- | -| `src/commands/onboard-auth.credentials.ts` | Add | `KILOCODE_DEFAULT_MODEL_REF`, `setKilocodeApiKey()` | -| `src/agents/model-auth.ts` | Modify | Add `kilocode` to `envMap` | -| `src/config/io.ts` | Modify | Add `KILOCODE_API_KEY` to shell env keys | -| `src/commands/onboard-auth.config-core.ts` | Add | `applyKilocodeProviderConfig()`, `applyKilocodeConfig()` | -| `src/commands/onboard-types.ts` | Modify | Add `kilocode-api-key` to `AuthChoice`, add `kilocodeApiKey` to options | -| `src/commands/auth-choice-options.ts` | Modify | Add `kilocode` group and option | -| `src/commands/auth-choice.preferred-provider.ts` | Modify | Add `kilocode-api-key` mapping | -| `src/commands/auth-choice.apply.api-providers.ts` | Modify | Add `kilocode-api-key` handling | -| `src/cli/program/register.onboard.ts` | Modify | Add `--kilocode-api-key` option | -| `src/commands/onboard-non-interactive/local/auth-choice.ts` | Modify | Add non-interactive handling | -| `src/commands/onboard-auth.ts` | Modify | Export new functions | -| `src/agents/pi-embedded-runner/cache-ttl.ts` | Modify | Add kilocode support | -| `src/agents/transcript-policy.ts` | Modify | Add kilocode Gemini handling | diff --git a/docs/docs.json b/docs/docs.json index e6cf5ba382b..1e5cf45d4d5 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -59,9 +59,13 @@ "source": "/compaction", "destination": "/concepts/compaction" }, + { + "source": "/context-engine", + "destination": "/concepts/context-engine" + }, { "source": "/cron", - "destination": "/cron-jobs" + "destination": "/automation/cron-jobs" }, { "source": "/minimax", @@ -469,7 +473,7 @@ }, { "source": "/mac/release", - "destination": "/platforms/mac/release" + "destination": "/reference/RELEASING" }, { "source": "/mac/remote", @@ -509,11 +513,11 @@ }, { "source": "/model", - "destination": "/models" + "destination": "/concepts/models" }, { "source": "/model/", - "destination": "/models" + "destination": "/concepts/models" }, { "source": "/models", @@ -531,10 +535,6 @@ "source": "/onboarding", "destination": "/start/onboarding" }, - { - "source": "/onboarding-config-protocol", - "destination": "/experiments/onboarding-config-protocol" - }, { "source": "/pairing", "destination": "/channels/pairing" @@ -555,10 +555,6 @@ "source": "/presence", "destination": "/concepts/presence" }, - { - "source": "/proposals/model-config", - "destination": "/experiments/proposals/model-config" - }, { "source": "/provider-routing", "destination": "/channels/channel-routing" @@ -579,10 +575,6 @@ "source": "/remote-gateway-readme", "destination": "/gateway/remote-gateway-readme" }, - { - "source": "/research/memory", - "destination": "/experiments/research/memory" - }, { "source": "/rpc", "destination": "/reference/rpc" @@ -876,6 +868,7 @@ "group": "Hosting and deployment", "pages": [ "vps", + "install/kubernetes", "install/fly", "install/hetzner", "install/gcp", @@ -951,6 +944,7 @@ "concepts/agent-loop", "concepts/system-prompt", "concepts/context", + "concepts/context-engine", "concepts/agent-workspace", "concepts/oauth" ] @@ -996,9 +990,8 @@ "pages": [ "tools/apply-patch", "brave-search", - "perplexity", + "tools/btw", "tools/diffs", - "tools/pdf", "tools/elevated", "tools/exec", "tools/exec-approvals", @@ -1006,6 +999,8 @@ "tools/llm-task", "tools/lobster", "tools/loop-detection", + "tools/pdf", + "perplexity", "tools/reactions", "tools/thinking", "tools/web" @@ -1016,7 +1011,6 @@ "pages": [ "tools/browser", "tools/browser-login", - "tools/chrome-extension", "tools/browser-linux-troubleshooting" ] }, @@ -1043,11 +1037,15 @@ { "group": "Extensions", "pages": [ + "plugins/building-extensions", + "plugins/architecture", "plugins/community", + "plugins/bundles", "plugins/voice-call", "plugins/zalouser", "plugins/manifest", "plugins/agent-tools", + "tools/capability-cookbook", "prose" ] }, @@ -1105,11 +1103,13 @@ "providers/claude-max-api-proxy", "providers/deepgram", "providers/github-copilot", + "providers/google", "providers/huggingface", "providers/kilocode", "providers/litellm", "providers/glm", "providers/minimax", + "providers/modelstudio", "providers/moonshot", "providers/mistral", "providers/nvidia", @@ -1118,13 +1118,17 @@ "providers/opencode-go", "providers/opencode", "providers/openrouter", + "providers/perplexity-provider", "providers/qianfan", "providers/qwen", + "providers/sglang", "providers/synthetic", "providers/together", "providers/vercel-ai-gateway", "providers/venice", "providers/vllm", + "providers/volcengine", + "providers/xai", "providers/xiaomi", "providers/zai" ] @@ -1164,7 +1168,6 @@ "platforms/mac/permissions", "platforms/mac/remote", "platforms/mac/signing", - "platforms/mac/release", "platforms/mac/bundled-gateway", "platforms/mac/xpc", "platforms/mac/skills", @@ -1206,6 +1209,7 @@ "pages": [ "gateway/security/index", "gateway/sandboxing", + "gateway/openshell", "gateway/sandbox-vs-tool-policy-vs-elevated" ] }, @@ -1240,7 +1244,6 @@ "group": "Security", "pages": [ "security/formal-verification", - "security/README", "security/THREAT-MODEL-ATLAS", "security/CONTRIBUTING-THREAT-MODEL" ] @@ -1350,23 +1353,8 @@ "pages": ["reference/credits"] }, { - "group": "Release notes", + "group": "Release policy", "pages": ["reference/RELEASING", "reference/test"] - }, - { - "group": "Experiments", - "pages": [ - "design/kilo-gateway-integration", - "experiments/onboarding-config-protocol", - "experiments/plans/acp-thread-bound-agents", - "experiments/plans/acp-unified-streaming-refactor", - "experiments/plans/browser-evaluate-cdp-refactor", - "experiments/plans/openresponses-gateway", - "experiments/plans/pty-process-supervision", - "experiments/plans/session-binding-channel-agnostic", - "experiments/research/memory", - "experiments/proposals/model-config" - ] } ] }, @@ -1595,14 +1583,13 @@ "pages": [ "zh-CN/tools/apply-patch", "zh-CN/brave-search", - "zh-CN/perplexity", - "zh-CN/tools/diffs", "zh-CN/tools/elevated", "zh-CN/tools/exec", "zh-CN/tools/exec-approvals", "zh-CN/tools/firecrawl", "zh-CN/tools/llm-task", "zh-CN/tools/lobster", + "zh-CN/perplexity", "zh-CN/tools/reactions", "zh-CN/tools/thinking", "zh-CN/tools/web" @@ -1613,7 +1600,6 @@ "pages": [ "zh-CN/tools/browser", "zh-CN/tools/browser-login", - "zh-CN/tools/chrome-extension", "zh-CN/tools/browser-linux-troubleshooting" ] }, @@ -1639,6 +1625,7 @@ { "group": "扩展", "pages": [ + "zh-CN/plugins/architecture", "zh-CN/plugins/voice-call", "zh-CN/plugins/zalouser", "zh-CN/plugins/manifest", @@ -1750,7 +1737,6 @@ "zh-CN/platforms/mac/permissions", "zh-CN/platforms/mac/remote", "zh-CN/platforms/mac/signing", - "zh-CN/platforms/mac/release", "zh-CN/platforms/mac/bundled-gateway", "zh-CN/platforms/mac/xpc", "zh-CN/platforms/mac/skills", @@ -1933,29 +1919,8 @@ "pages": ["zh-CN/reference/credits"] }, { - "group": "发布说明", + "group": "发布策略", "pages": ["zh-CN/reference/RELEASING", "zh-CN/reference/test"] - }, - { - "group": "实验性功能", - "pages": [ - "zh-CN/experiments/onboarding-config-protocol", - "zh-CN/experiments/plans/openresponses-gateway", - "zh-CN/experiments/plans/cron-add-hardening", - "zh-CN/experiments/plans/group-policy-hardening", - "zh-CN/experiments/research/memory", - "zh-CN/experiments/proposals/model-config" - ] - }, - { - "group": "重构方案", - "pages": [ - "zh-CN/refactor/clawnet", - "zh-CN/refactor/exec-host", - "zh-CN/refactor/outbound-session-mirroring", - "zh-CN/refactor/plugin-sdk", - "zh-CN/refactor/strict-config" - ] } ] }, diff --git a/docs/experiments/onboarding-config-protocol.md b/docs/experiments/onboarding-config-protocol.md deleted file mode 100644 index 9427d47b7f6..00000000000 --- a/docs/experiments/onboarding-config-protocol.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -summary: "RPC protocol notes for onboarding wizard and config schema" -read_when: "Changing onboarding wizard steps or config schema endpoints" -title: "Onboarding and Config Protocol" ---- - -# Onboarding + Config Protocol - -Purpose: shared onboarding + config surfaces across CLI, macOS app, and Web UI. - -## Components - -- Wizard engine (shared session + prompts + onboarding state). -- CLI onboarding uses the same wizard flow as the UI clients. -- Gateway RPC exposes wizard + config schema endpoints. -- macOS onboarding uses the wizard step model. -- Web UI renders config forms from JSON Schema + UI hints. - -## Gateway RPC - -- `wizard.start` params: `{ mode?: "local"|"remote", workspace?: string }` -- `wizard.next` params: `{ sessionId, answer?: { stepId, value? } }` -- `wizard.cancel` params: `{ sessionId }` -- `wizard.status` params: `{ sessionId }` -- `config.schema` params: `{}` -- `config.schema.lookup` params: `{ path }` - - `path` accepts standard config segments plus slash-delimited plugin ids, for example `plugins.entries.pack/one.config`. - -Responses (shape) - -- Wizard: `{ sessionId, done, step?, status?, error? }` -- Config schema: `{ schema, uiHints, version, generatedAt }` -- Config schema lookup: `{ path, schema, hint?, hintPath?, children[] }` - -## UI Hints - -- `uiHints` keyed by path; optional metadata (label/help/group/order/advanced/sensitive/placeholder). -- Sensitive fields render as password inputs; no redaction layer. -- Unsupported schema nodes fall back to the raw JSON editor. - -## Notes - -- This doc is the single place to track protocol refactors for onboarding/config. diff --git a/docs/experiments/plans/acp-persistent-bindings-discord-channels-telegram-topics.md b/docs/experiments/plans/acp-persistent-bindings-discord-channels-telegram-topics.md deleted file mode 100644 index e85ddeaf4a7..00000000000 --- a/docs/experiments/plans/acp-persistent-bindings-discord-channels-telegram-topics.md +++ /dev/null @@ -1,375 +0,0 @@ -# ACP Persistent Bindings for Discord Channels and Telegram Topics - -Status: Draft - -## Summary - -Introduce persistent ACP bindings that map: - -- Discord channels (and existing threads, where needed), and -- Telegram forum topics in groups/supergroups (`chatId:topic:topicId`) - -to long-lived ACP sessions, with binding state stored in top-level `bindings[]` entries using explicit binding types. - -This makes ACP usage in high-traffic messaging channels predictable and durable, so users can create dedicated channels/topics such as `codex`, `claude-1`, or `claude-myrepo`. - -## Why - -Current thread-bound ACP behavior is optimized for ephemeral Discord thread workflows. Telegram does not have the same thread model; it has forum topics in groups/supergroups. Users want stable, always-on ACP “workspaces” in chat surfaces, not only temporary thread sessions. - -## Goals - -- Support durable ACP binding for: - - Discord channels/threads - - Telegram forum topics (groups/supergroups) -- Make binding source-of-truth config-driven. -- Keep `/acp`, `/new`, `/reset`, `/focus`, and delivery behavior consistent across Discord and Telegram. -- Preserve existing temporary binding flows for ad-hoc usage. - -## Non-Goals - -- Full redesign of ACP runtime/session internals. -- Removing existing ephemeral binding flows. -- Expanding to every channel in the first iteration. -- Implementing Telegram channel direct-messages topics (`direct_messages_topic_id`) in this phase. -- Implementing Telegram private-chat topic variants in this phase. - -## UX Direction - -### 1) Two binding types - -- **Persistent binding**: saved in config, reconciled on startup, intended for “named workspace” channels/topics. -- **Temporary binding**: runtime-only, expires by idle/max-age policy. - -### 2) Command behavior - -- `/acp spawn ... --thread here|auto|off` remains available. -- Add explicit bind lifecycle controls: - - `/acp bind [session|agent] [--persist]` - - `/acp unbind [--persist]` - - `/acp status` includes whether binding is `persistent` or `temporary`. -- In bound conversations, `/new` and `/reset` reset the bound ACP session in place and keep the binding attached. - -### 3) Conversation identity - -- Use canonical conversation IDs: - - Discord: channel/thread ID. - - Telegram topic: `chatId:topic:topicId`. -- Never key Telegram bindings by bare topic ID alone. - -## Config Model (Proposed) - -Unify routing and persistent ACP binding configuration in top-level `bindings[]` with explicit `type` discriminator: - -```jsonc -{ - "agents": { - "list": [ - { - "id": "main", - "default": true, - "workspace": "~/.openclaw/workspace-main", - "runtime": { "type": "embedded" }, - }, - { - "id": "codex", - "workspace": "~/.openclaw/workspace-codex", - "runtime": { - "type": "acp", - "acp": { - "agent": "codex", - "backend": "acpx", - "mode": "persistent", - "cwd": "/workspace/repo-a", - }, - }, - }, - { - "id": "claude", - "workspace": "~/.openclaw/workspace-claude", - "runtime": { - "type": "acp", - "acp": { - "agent": "claude", - "backend": "acpx", - "mode": "persistent", - "cwd": "/workspace/repo-b", - }, - }, - }, - ], - }, - "acp": { - "enabled": true, - "backend": "acpx", - "allowedAgents": ["codex", "claude"], - }, - "bindings": [ - // Route bindings (existing behavior) - { - "type": "route", - "agentId": "main", - "match": { "channel": "discord", "accountId": "default" }, - }, - { - "type": "route", - "agentId": "main", - "match": { "channel": "telegram", "accountId": "default" }, - }, - // Persistent ACP conversation bindings - { - "type": "acp", - "agentId": "codex", - "match": { - "channel": "discord", - "accountId": "default", - "peer": { "kind": "channel", "id": "222222222222222222" }, - }, - "acp": { - "label": "codex-main", - "mode": "persistent", - "cwd": "/workspace/repo-a", - "backend": "acpx", - }, - }, - { - "type": "acp", - "agentId": "claude", - "match": { - "channel": "discord", - "accountId": "default", - "peer": { "kind": "channel", "id": "333333333333333333" }, - }, - "acp": { - "label": "claude-repo-b", - "mode": "persistent", - "cwd": "/workspace/repo-b", - }, - }, - { - "type": "acp", - "agentId": "codex", - "match": { - "channel": "telegram", - "accountId": "default", - "peer": { "kind": "group", "id": "-1001234567890:topic:42" }, - }, - "acp": { - "label": "tg-codex-42", - "mode": "persistent", - }, - }, - ], - "channels": { - "discord": { - "guilds": { - "111111111111111111": { - "channels": { - "222222222222222222": { - "enabled": true, - "requireMention": false, - }, - "333333333333333333": { - "enabled": true, - "requireMention": false, - }, - }, - }, - }, - }, - "telegram": { - "groups": { - "-1001234567890": { - "topics": { - "42": { - "requireMention": false, - }, - }, - }, - }, - }, - }, -} -``` - -### Minimal Example (No Per-Binding ACP Overrides) - -```jsonc -{ - "agents": { - "list": [ - { "id": "main", "default": true, "runtime": { "type": "embedded" } }, - { - "id": "codex", - "runtime": { - "type": "acp", - "acp": { "agent": "codex", "backend": "acpx", "mode": "persistent" }, - }, - }, - { - "id": "claude", - "runtime": { - "type": "acp", - "acp": { "agent": "claude", "backend": "acpx", "mode": "persistent" }, - }, - }, - ], - }, - "acp": { "enabled": true, "backend": "acpx" }, - "bindings": [ - { - "type": "route", - "agentId": "main", - "match": { "channel": "discord", "accountId": "default" }, - }, - { - "type": "route", - "agentId": "main", - "match": { "channel": "telegram", "accountId": "default" }, - }, - - { - "type": "acp", - "agentId": "codex", - "match": { - "channel": "discord", - "accountId": "default", - "peer": { "kind": "channel", "id": "222222222222222222" }, - }, - }, - { - "type": "acp", - "agentId": "claude", - "match": { - "channel": "discord", - "accountId": "default", - "peer": { "kind": "channel", "id": "333333333333333333" }, - }, - }, - { - "type": "acp", - "agentId": "codex", - "match": { - "channel": "telegram", - "accountId": "default", - "peer": { "kind": "group", "id": "-1009876543210:topic:5" }, - }, - }, - ], -} -``` - -Notes: - -- `bindings[].type` is explicit: - - `route`: normal agent routing. - - `acp`: persistent ACP harness binding for a matched conversation. -- For `type: "acp"`, `match.peer.id` is the canonical conversation key: - - Discord channel/thread: raw channel/thread ID. - - Telegram topic: `chatId:topic:topicId`. -- `bindings[].acp.backend` is optional. Backend fallback order: - 1. `bindings[].acp.backend` - 2. `agents.list[].runtime.acp.backend` - 3. global `acp.backend` -- `mode`, `cwd`, and `label` follow the same override pattern (`binding override -> agent runtime default -> global/default behavior`). -- Keep existing `session.threadBindings.*` and `channels.discord.threadBindings.*` for temporary binding policies. -- Persistent entries declare desired state; runtime reconciles to actual ACP sessions/bindings. -- One active ACP binding per conversation node is the intended model. -- Backward compatibility: missing `type` is interpreted as `route` for legacy entries. - -### Backend Selection - -- ACP session initialization already uses configured backend selection during spawn (`acp.backend` today). -- This proposal extends spawn/reconcile logic to prefer typed ACP binding overrides: - - `bindings[].acp.backend` for conversation-local override. - - `agents.list[].runtime.acp.backend` for per-agent defaults. -- If no override exists, keep current behavior (`acp.backend` default). - -## Architecture Fit in Current System - -### Reuse existing components - -- `SessionBindingService` already supports channel-agnostic conversation references. -- ACP spawn/bind flows already support binding through service APIs. -- Telegram already carries topic/thread context via `MessageThreadId` and `chatId`. - -### New/extended components - -- **Telegram binding adapter** (parallel to Discord adapter): - - register adapter per Telegram account, - - resolve/list/bind/unbind/touch by canonical conversation ID. -- **Typed binding resolver/index**: - - split `bindings[]` into `route` and `acp` views, - - keep `resolveAgentRoute` on `route` bindings only, - - resolve persistent ACP intent from `acp` bindings only. -- **Inbound binding resolution for Telegram**: - - resolve bound session before route finalization (Discord already does this). -- **Persistent binding reconciler**: - - on startup: load configured top-level `type: "acp"` bindings, ensure ACP sessions exist, ensure bindings exist. - - on config change: apply deltas safely. -- **Cutover model**: - - no channel-local ACP binding fallback is read, - - persistent ACP bindings are sourced only from top-level `bindings[].type="acp"` entries. - -## Phased Delivery - -### Phase 1: Typed binding schema foundation - -- Extend config schema to support `bindings[].type` discriminator: - - `route`, - - `acp` with optional `acp` override object (`mode`, `backend`, `cwd`, `label`). -- Extend agent schema with runtime descriptor to mark ACP-native agents (`agents.list[].runtime.type`). -- Add parser/indexer split for route vs ACP bindings. - -### Phase 2: Runtime resolution + Discord/Telegram parity - -- Resolve persistent ACP bindings from top-level `type: "acp"` entries for: - - Discord channels/threads, - - Telegram forum topics (`chatId:topic:topicId` canonical IDs). -- Implement Telegram binding adapter and inbound bound-session override parity with Discord. -- Do not include Telegram direct/private topic variants in this phase. - -### Phase 3: Command parity and resets - -- Align `/acp`, `/new`, `/reset`, and `/focus` behavior in bound Telegram/Discord conversations. -- Ensure binding survives reset flows as configured. - -### Phase 4: Hardening - -- Better diagnostics (`/acp status`, startup reconciliation logs). -- Conflict handling and health checks. - -## Guardrails and Policy - -- Respect ACP enablement and sandbox restrictions exactly as today. -- Keep explicit account scoping (`accountId`) to avoid cross-account bleed. -- Fail closed on ambiguous routing. -- Keep mention/access policy behavior explicit per channel config. - -## Testing Plan - -- Unit: - - conversation ID normalization (especially Telegram topic IDs), - - reconciler create/update/delete paths, - - `/acp bind --persist` and unbind flows. -- Integration: - - inbound Telegram topic -> bound ACP session resolution, - - inbound Discord channel/thread -> persistent binding precedence. -- Regression: - - temporary bindings continue to work, - - unbound channels/topics keep current routing behavior. - -## Open Questions - -- Should `/acp spawn --thread auto` in Telegram topic default to `here`? -- Should persistent bindings always bypass mention-gating in bound conversations, or require explicit `requireMention=false`? -- Should `/focus` gain `--persist` as an alias for `/acp bind --persist`? - -## Rollout - -- Ship as opt-in per conversation (`bindings[].type="acp"` entry present). -- Start with Discord + Telegram only. -- Add docs with examples for: - - “one channel/topic per agent” - - “multiple channels/topics per same agent with different `cwd`” - - “team naming patterns (`codex-1`, `claude-repo-x`)". diff --git a/docs/experiments/plans/acp-thread-bound-agents.md b/docs/experiments/plans/acp-thread-bound-agents.md deleted file mode 100644 index a0637cedee5..00000000000 --- a/docs/experiments/plans/acp-thread-bound-agents.md +++ /dev/null @@ -1,800 +0,0 @@ ---- -summary: "Integrate ACP coding agents via a first-class ACP control plane in core and plugin-backed runtimes (acpx first)" -owner: "onutc" -status: "draft" -last_updated: "2026-02-25" -title: "ACP Thread Bound Agents" ---- - -# ACP Thread Bound Agents - -## Overview - -This plan defines how OpenClaw should support ACP coding agents in thread-capable channels (Discord first) with production-level lifecycle and recovery. - -Related document: - -- [Unified Runtime Streaming Refactor Plan](/experiments/plans/acp-unified-streaming-refactor) - -Target user experience: - -- a user spawns or focuses an ACP session into a thread -- user messages in that thread route to the bound ACP session -- agent output streams back to the same thread persona -- session can be persistent or one shot with explicit cleanup controls - -## Decision summary - -Long term recommendation is a hybrid architecture: - -- OpenClaw core owns ACP control plane concerns - - session identity and metadata - - thread binding and routing decisions - - delivery invariants and duplicate suppression - - lifecycle cleanup and recovery semantics -- ACP runtime backend is pluggable - - first backend is an acpx-backed plugin service - - runtime does ACP transport, queueing, cancel, reconnect - -OpenClaw should not reimplement ACP transport internals in core. -OpenClaw should not rely on a pure plugin-only interception path for routing. - -## North-star architecture (holy grail) - -Treat ACP as a first-class control plane in OpenClaw, with pluggable runtime adapters. - -Non-negotiable invariants: - -- every ACP thread binding references a valid ACP session record -- every ACP session has explicit lifecycle state (`creating`, `idle`, `running`, `cancelling`, `closed`, `error`) -- every ACP run has explicit run state (`queued`, `running`, `completed`, `failed`, `cancelled`) -- spawn, bind, and initial enqueue are atomic -- command retries are idempotent (no duplicate runs or duplicate Discord outputs) -- bound-thread channel output is a projection of ACP run events, never ad-hoc side effects - -Long-term ownership model: - -- `AcpSessionManager` is the single ACP writer and orchestrator -- manager lives in gateway process first; can be moved to a dedicated sidecar later behind the same interface -- per ACP session key, manager owns one in-memory actor (serialized command execution) -- adapters (`acpx`, future backends) are transport/runtime implementations only - -Long-term persistence model: - -- move ACP control-plane state to a dedicated SQLite store (WAL mode) under OpenClaw state dir -- keep `SessionEntry.acp` as compatibility projection during migration, not source-of-truth -- store ACP events append-only to support replay, crash recovery, and deterministic delivery - -### Delivery strategy (bridge to holy-grail) - -- short-term bridge - - keep current thread binding mechanics and existing ACP config surface - - fix metadata-gap bugs and route ACP turns through a single core ACP branch - - add idempotency keys and fail-closed routing checks immediately -- long-term cutover - - move ACP source-of-truth to control-plane DB + actors - - make bound-thread delivery purely event-projection based - - remove legacy fallback behavior that depends on opportunistic session-entry metadata - -## Why not pure plugin only - -Current plugin hooks are not sufficient for end to end ACP session routing without core changes. - -- inbound routing from thread binding resolves to a session key in core dispatch first -- message hooks are fire-and-forget and cannot short-circuit the main reply path -- plugin commands are good for control operations but not for replacing core per-turn dispatch flow - -Result: - -- ACP runtime can be pluginized -- ACP routing branch must exist in core - -## Existing foundation to reuse - -Already implemented and should remain canonical: - -- thread binding target supports `subagent` and `acp` -- inbound thread routing override resolves by binding before normal dispatch -- outbound thread identity via webhook in reply delivery -- `/focus` and `/unfocus` flow with ACP target compatibility -- persistent binding store with restore on startup -- unbind lifecycle on archive, delete, unfocus, reset, and delete - -This plan extends that foundation rather than replacing it. - -## Architecture - -### Boundary model - -Core (must be in OpenClaw core): - -- ACP session-mode dispatch branch in the reply pipeline -- delivery arbitration to avoid parent plus thread duplication -- ACP control-plane persistence (with `SessionEntry.acp` compatibility projection during migration) -- lifecycle unbind and runtime detach semantics tied to session reset/delete - -Plugin backend (acpx implementation): - -- ACP runtime worker supervision -- acpx process invocation and event parsing -- ACP command handlers (`/acp ...`) and operator UX -- backend-specific config defaults and diagnostics - -### Runtime ownership model - -- one gateway process owns ACP orchestration state -- ACP execution runs in supervised child processes via acpx backend -- process strategy is long lived per active ACP session key, not per message - -This avoids startup cost on every prompt and keeps cancel and reconnect semantics reliable. - -### Core runtime contract - -Add a core ACP runtime contract so routing code does not depend on CLI details and can switch backends without changing dispatch logic: - -```ts -export type AcpRuntimePromptMode = "prompt" | "steer"; - -export type AcpRuntimeHandle = { - sessionKey: string; - backend: string; - runtimeSessionName: string; -}; - -export type AcpRuntimeEvent = - | { type: "text_delta"; stream: "output" | "thought"; text: string } - | { type: "tool_call"; name: string; argumentsText: string } - | { type: "done"; usage?: Record } - | { type: "error"; code: string; message: string; retryable?: boolean }; - -export interface AcpRuntime { - ensureSession(input: { - sessionKey: string; - agent: string; - mode: "persistent" | "oneshot"; - cwd?: string; - env?: Record; - idempotencyKey: string; - }): Promise; - - submit(input: { - handle: AcpRuntimeHandle; - text: string; - mode: AcpRuntimePromptMode; - idempotencyKey: string; - }): Promise<{ runtimeRunId: string }>; - - stream(input: { - handle: AcpRuntimeHandle; - runtimeRunId: string; - onEvent: (event: AcpRuntimeEvent) => Promise | void; - signal?: AbortSignal; - }): Promise; - - cancel(input: { - handle: AcpRuntimeHandle; - runtimeRunId?: string; - reason?: string; - idempotencyKey: string; - }): Promise; - - close(input: { handle: AcpRuntimeHandle; reason: string; idempotencyKey: string }): Promise; - - health?(): Promise<{ ok: boolean; details?: string }>; -} -``` - -Implementation detail: - -- first backend: `AcpxRuntime` shipped as a plugin service -- core resolves runtime via registry and fails with explicit operator error when no ACP runtime backend is available - -### Control-plane data model and persistence - -Long-term source-of-truth is a dedicated ACP SQLite database (WAL mode), for transactional updates and crash-safe recovery: - -- `acp_sessions` - - `session_key` (pk), `backend`, `agent`, `mode`, `cwd`, `state`, `created_at`, `updated_at`, `last_error` -- `acp_runs` - - `run_id` (pk), `session_key` (fk), `state`, `requester_message_id`, `idempotency_key`, `started_at`, `ended_at`, `error_code`, `error_message` -- `acp_bindings` - - `binding_key` (pk), `thread_id`, `channel_id`, `account_id`, `session_key` (fk), `expires_at`, `bound_at` -- `acp_events` - - `event_id` (pk), `run_id` (fk), `seq`, `kind`, `payload_json`, `created_at` -- `acp_delivery_checkpoint` - - `run_id` (pk/fk), `last_event_seq`, `last_discord_message_id`, `updated_at` -- `acp_idempotency` - - `scope`, `idempotency_key`, `result_json`, `created_at`, unique `(scope, idempotency_key)` - -```ts -export type AcpSessionMeta = { - backend: string; - agent: string; - runtimeSessionName: string; - mode: "persistent" | "oneshot"; - cwd?: string; - state: "idle" | "running" | "error"; - lastActivityAt: number; - lastError?: string; -}; -``` - -Storage rules: - -- keep `SessionEntry.acp` as a compatibility projection during migration -- process ids and sockets stay in memory only -- durable lifecycle and run status live in ACP DB, not generic session JSON -- if runtime owner dies, gateway rehydrates from ACP DB and resumes from checkpoints - -### Routing and delivery - -Inbound: - -- keep current thread binding lookup as first routing step -- if bound target is ACP session, route to ACP runtime branch instead of `getReplyFromConfig` -- explicit `/acp steer` command uses `mode: "steer"` - -Outbound: - -- ACP event stream is normalized to OpenClaw reply chunks -- delivery target is resolved through existing bound destination path -- when a bound thread is active for that session turn, parent channel completion is suppressed - -Streaming policy: - -- stream partial output with coalescing window -- configurable min interval and max chunk bytes to stay under Discord rate limits -- final message always emitted on completion or failure - -### State machines and transaction boundaries - -Session state machine: - -- `creating -> idle -> running -> idle` -- `running -> cancelling -> idle | error` -- `idle -> closed` -- `error -> idle | closed` - -Run state machine: - -- `queued -> running -> completed` -- `running -> failed | cancelled` -- `queued -> cancelled` - -Required transaction boundaries: - -- spawn transaction - - create ACP session row - - create/update ACP thread binding row - - enqueue initial run row -- close transaction - - mark session closed - - delete/expire binding rows - - write final close event -- cancel transaction - - mark target run cancelling/cancelled with idempotency key - -No partial success is allowed across these boundaries. - -### Per-session actor model - -`AcpSessionManager` runs one actor per ACP session key: - -- actor mailbox serializes `submit`, `cancel`, `close`, and `stream` side effects -- actor owns runtime handle hydration and runtime adapter process lifecycle for that session -- actor writes run events in-order (`seq`) before any Discord delivery -- actor updates delivery checkpoints after successful outbound send - -This removes cross-turn races and prevents duplicate or out-of-order thread output. - -### Idempotency and delivery projection - -All external ACP actions must carry idempotency keys: - -- spawn idempotency key -- prompt/steer idempotency key -- cancel idempotency key -- close idempotency key - -Delivery rules: - -- Discord messages are derived from `acp_events` plus `acp_delivery_checkpoint` -- retries resume from checkpoint without re-sending already delivered chunks -- final reply emission is exactly-once per run from projection logic - -### Recovery and self-healing - -On gateway start: - -- load non-terminal ACP sessions (`creating`, `idle`, `running`, `cancelling`, `error`) -- recreate actors lazily on first inbound event or eagerly under configured cap -- reconcile any `running` runs missing heartbeats and mark `failed` or recover via adapter - -On inbound Discord thread message: - -- if binding exists but ACP session is missing, fail closed with explicit stale-binding message -- optionally auto-unbind stale binding after operator-safe validation -- never silently route stale ACP bindings to normal LLM path - -### Lifecycle and safety - -Supported operations: - -- cancel current run: `/acp cancel` -- unbind thread: `/unfocus` -- close ACP session: `/acp close` -- auto close idle sessions by effective TTL - -TTL policy: - -- effective TTL is minimum of - - global/session TTL - - Discord thread binding TTL - - ACP runtime owner TTL - -Safety controls: - -- allowlist ACP agents by name -- restrict workspace roots for ACP sessions -- env allowlist passthrough -- max concurrent ACP sessions per account and globally -- bounded restart backoff for runtime crashes - -## Config surface - -Core keys: - -- `acp.enabled` -- `acp.dispatch.enabled` (independent ACP routing kill switch) -- `acp.backend` (default `acpx`) -- `acp.defaultAgent` -- `acp.allowedAgents[]` -- `acp.maxConcurrentSessions` -- `acp.stream.coalesceIdleMs` -- `acp.stream.maxChunkChars` -- `acp.runtime.ttlMinutes` -- `acp.controlPlane.store` (`sqlite` default) -- `acp.controlPlane.storePath` -- `acp.controlPlane.recovery.eagerActors` -- `acp.controlPlane.recovery.reconcileRunningAfterMs` -- `acp.controlPlane.checkpoint.flushEveryEvents` -- `acp.controlPlane.checkpoint.flushEveryMs` -- `acp.idempotency.ttlHours` -- `channels.discord.threadBindings.spawnAcpSessions` - -Plugin/backend keys (acpx plugin section): - -- backend command/path overrides -- backend env allowlist -- backend per-agent presets -- backend startup/stop timeouts -- backend max inflight runs per session - -## Implementation specification - -### Control-plane modules (new) - -Add dedicated ACP control-plane modules in core: - -- `src/acp/control-plane/manager.ts` - - owns ACP actors, lifecycle transitions, command serialization -- `src/acp/control-plane/store.ts` - - SQLite schema management, transactions, query helpers -- `src/acp/control-plane/events.ts` - - typed ACP event definitions and serialization -- `src/acp/control-plane/checkpoint.ts` - - durable delivery checkpoints and replay cursors -- `src/acp/control-plane/idempotency.ts` - - idempotency key reservation and response replay -- `src/acp/control-plane/recovery.ts` - - boot-time reconciliation and actor rehydrate plan - -Compatibility bridge modules: - -- `src/acp/runtime/session-meta.ts` - - remains temporarily for projection into `SessionEntry.acp` - - must stop being source-of-truth after migration cutover - -### Required invariants (must enforce in code) - -- ACP session creation and thread bind are atomic (single transaction) -- there is at most one active run per ACP session actor at a time -- event `seq` is strictly increasing per run -- delivery checkpoint never advances past last committed event -- idempotency replay returns previous success payload for duplicate command keys -- stale/missing ACP metadata cannot route into normal non-ACP reply path - -### Core touchpoints - -Core files to change: - -- `src/auto-reply/reply/dispatch-from-config.ts` - - ACP branch calls `AcpSessionManager.submit` and event-projection delivery - - remove direct ACP fallback that bypasses control-plane invariants -- `src/auto-reply/reply/inbound-context.ts` (or nearest normalized context boundary) - - expose normalized routing keys and idempotency seeds for ACP control plane -- `src/config/sessions/types.ts` - - keep `SessionEntry.acp` as projection-only compatibility field -- `src/gateway/server-methods/sessions.ts` - - reset/delete/archive must call ACP manager close/unbind transaction path -- `src/infra/outbound/bound-delivery-router.ts` - - enforce fail-closed destination behavior for ACP bound session turns -- `src/discord/monitor/thread-bindings.ts` - - add ACP stale-binding validation helpers wired to control-plane lookups -- `src/auto-reply/reply/commands-acp.ts` - - route spawn/cancel/close/steer through ACP manager APIs -- `src/agents/acp-spawn.ts` - - stop ad-hoc metadata writes; call ACP manager spawn transaction -- `src/plugin-sdk/**` and plugin runtime bridge - - expose ACP backend registration and health semantics cleanly - -Core files explicitly not replaced: - -- `src/discord/monitor/message-handler.preflight.ts` - - keep thread binding override behavior as the canonical session-key resolver - -### ACP runtime registry API - -Add a core registry module: - -- `src/acp/runtime/registry.ts` - -Required API: - -```ts -export type AcpRuntimeBackend = { - id: string; - runtime: AcpRuntime; - healthy?: () => boolean; -}; - -export function registerAcpRuntimeBackend(backend: AcpRuntimeBackend): void; -export function unregisterAcpRuntimeBackend(id: string): void; -export function getAcpRuntimeBackend(id?: string): AcpRuntimeBackend | null; -export function requireAcpRuntimeBackend(id?: string): AcpRuntimeBackend; -``` - -Behavior: - -- `requireAcpRuntimeBackend` throws a typed ACP backend missing error when unavailable -- plugin service registers backend on `start` and unregisters on `stop` -- runtime lookups are read-only and process-local - -### acpx runtime plugin contract (implementation detail) - -For the first production backend (`extensions/acpx`), OpenClaw and acpx are -connected with a strict command contract: - -- backend id: `acpx` -- plugin service id: `acpx-runtime` -- runtime handle encoding: `runtimeSessionName = acpx:v1:` -- encoded payload fields: - - `name` (acpx named session; uses OpenClaw `sessionKey`) - - `agent` (acpx agent command) - - `cwd` (session workspace root) - - `mode` (`persistent | oneshot`) - -Command mapping: - -- ensure session: - - `acpx --format json --json-strict --cwd sessions ensure --name ` -- prompt turn: - - `acpx --format json --json-strict --cwd prompt --session --file -` -- cancel: - - `acpx --format json --json-strict --cwd cancel --session ` -- close: - - `acpx --format json --json-strict --cwd sessions close ` - -Streaming: - -- OpenClaw consumes ndjson events from `acpx --format json --json-strict` -- `text` => `text_delta/output` -- `thought` => `text_delta/thought` -- `tool_call` => `tool_call` -- `done` => `done` -- `error` => `error` - -### Session schema patch - -Patch `SessionEntry` in `src/config/sessions/types.ts`: - -```ts -type SessionAcpMeta = { - backend: string; - agent: string; - runtimeSessionName: string; - mode: "persistent" | "oneshot"; - cwd?: string; - state: "idle" | "running" | "error"; - lastActivityAt: number; - lastError?: string; -}; -``` - -Persisted field: - -- `SessionEntry.acp?: SessionAcpMeta` - -Migration rules: - -- phase A: dual-write (`acp` projection + ACP SQLite source-of-truth) -- phase B: read-primary from ACP SQLite, fallback-read from legacy `SessionEntry.acp` -- phase C: migration command backfills missing ACP rows from valid legacy entries -- phase D: remove fallback-read and keep projection optional for UX only -- legacy fields (`cliSessionIds`, `claudeCliSessionId`) remain untouched - -### Error contract - -Add stable ACP error codes and user-facing messages: - -- `ACP_BACKEND_MISSING` - - message: `ACP runtime backend is not configured. Install and enable the acpx runtime plugin.` -- `ACP_BACKEND_UNAVAILABLE` - - message: `ACP runtime backend is currently unavailable. Try again in a moment.` -- `ACP_SESSION_INIT_FAILED` - - message: `Could not initialize ACP session runtime.` -- `ACP_TURN_FAILED` - - message: `ACP turn failed before completion.` - -Rules: - -- return actionable user-safe message in-thread -- log detailed backend/system error only in runtime logs -- never silently fall back to normal LLM path when ACP routing was explicitly selected - -### Duplicate delivery arbitration - -Single routing rule for ACP bound turns: - -- if an active thread binding exists for the target ACP session and requester context, deliver only to that bound thread -- do not also send to parent channel for the same turn -- if bound destination selection is ambiguous, fail closed with explicit error (no implicit parent fallback) -- if no active binding exists, use normal session destination behavior - -### Observability and operational readiness - -Required metrics: - -- ACP spawn success/failure count by backend and error code -- ACP run latency percentiles (queue wait, runtime turn time, delivery projection time) -- ACP actor restart count and restart reason -- stale-binding detection count -- idempotency replay hit rate -- Discord delivery retry and rate-limit counters - -Required logs: - -- structured logs keyed by `sessionKey`, `runId`, `backend`, `threadId`, `idempotencyKey` -- explicit state transition logs for session and run state machines -- adapter command logs with redaction-safe arguments and exit summary - -Required diagnostics: - -- `/acp sessions` includes state, active run, last error, and binding status -- `/acp doctor` (or equivalent) validates backend registration, store health, and stale bindings - -### Config precedence and effective values - -ACP enablement precedence: - -- account override: `channels.discord.accounts..threadBindings.spawnAcpSessions` -- channel override: `channels.discord.threadBindings.spawnAcpSessions` -- global ACP gate: `acp.enabled` -- dispatch gate: `acp.dispatch.enabled` -- backend availability: registered backend for `acp.backend` - -Auto-enable behavior: - -- when ACP is configured (`acp.enabled=true`, `acp.dispatch.enabled=true`, or - `acp.backend=acpx`), plugin auto-enable marks `plugins.entries.acpx.enabled=true` - unless denylisted or explicitly disabled - -TTL effective value: - -- `min(session ttl, discord thread binding ttl, acp runtime ttl)` - -### Test map - -Unit tests: - -- `src/acp/runtime/registry.test.ts` (new) -- `src/auto-reply/reply/dispatch-from-config.acp.test.ts` (new) -- `src/infra/outbound/bound-delivery-router.test.ts` (extend ACP fail-closed cases) -- `src/config/sessions/types.test.ts` or nearest session-store tests (ACP metadata persistence) - -Integration tests: - -- `src/discord/monitor/reply-delivery.test.ts` (bound ACP delivery target behavior) -- `src/discord/monitor/message-handler.preflight*.test.ts` (bound ACP session-key routing continuity) -- acpx plugin runtime tests in backend package (service register/start/stop + event normalization) - -Gateway e2e tests: - -- `src/gateway/server.sessions.gateway-server-sessions-a.e2e.test.ts` (extend ACP reset/delete lifecycle coverage) -- ACP thread turn roundtrip e2e for spawn, message, stream, cancel, unfocus, restart recovery - -### Rollout guard - -Add independent ACP dispatch kill switch: - -- `acp.dispatch.enabled` default `false` for first release -- when disabled: - - ACP spawn/focus control commands may still bind sessions - - ACP dispatch path does not activate - - user receives explicit message that ACP dispatch is disabled by policy -- after canary validation, default can be flipped to `true` in a later release - -## Command and UX plan - -### New commands - -- `/acp spawn [--mode persistent|oneshot] [--thread auto|here|off]` -- `/acp cancel [session]` -- `/acp steer ` -- `/acp close [session]` -- `/acp sessions` - -### Existing command compatibility - -- `/focus ` continues to support ACP targets -- `/unfocus` keeps current semantics -- `/session idle` and `/session max-age` replace the old TTL override - -## Phased rollout - -### Phase 0 ADR and schema freeze - -- ship ADR for ACP control-plane ownership and adapter boundaries -- freeze DB schema (`acp_sessions`, `acp_runs`, `acp_bindings`, `acp_events`, `acp_delivery_checkpoint`, `acp_idempotency`) -- define stable ACP error codes, event contract, and state-transition guards - -### Phase 1 Control-plane foundation in core - -- implement `AcpSessionManager` and per-session actor runtime -- implement ACP SQLite store and transaction helpers -- implement idempotency store and replay helpers -- implement event append + delivery checkpoint modules -- wire spawn/cancel/close APIs to manager with transactional guarantees - -### Phase 2 Core routing and lifecycle integration - -- route thread-bound ACP turns from dispatch pipeline into ACP manager -- enforce fail-closed routing when ACP binding/session invariants fail -- integrate reset/delete/archive/unfocus lifecycle with ACP close/unbind transactions -- add stale-binding detection and optional auto-unbind policy - -### Phase 3 acpx backend adapter/plugin - -- implement `acpx` adapter against runtime contract (`ensureSession`, `submit`, `stream`, `cancel`, `close`) -- add backend health checks and startup/teardown registration -- normalize acpx ndjson events into ACP runtime events -- enforce backend timeouts, process supervision, and restart/backoff policy - -### Phase 4 Delivery projection and channel UX (Discord first) - -- implement event-driven channel projection with checkpoint resume (Discord first) -- coalesce streaming chunks with rate-limit aware flush policy -- guarantee exactly-once final completion message per run -- ship `/acp spawn`, `/acp cancel`, `/acp steer`, `/acp close`, `/acp sessions` - -### Phase 5 Migration and cutover - -- introduce dual-write to `SessionEntry.acp` projection plus ACP SQLite source-of-truth -- add migration utility for legacy ACP metadata rows -- flip read path to ACP SQLite primary -- remove legacy fallback routing that depends on missing `SessionEntry.acp` - -### Phase 6 Hardening, SLOs, and scale limits - -- enforce concurrency limits (global/account/session), queue policies, and timeout budgets -- add full telemetry, dashboards, and alert thresholds -- chaos-test crash recovery and duplicate-delivery suppression -- publish runbook for backend outage, DB corruption, and stale-binding remediation - -### Full implementation checklist - -- core control-plane modules and tests -- DB migrations and rollback plan -- ACP manager API integration across dispatch and commands -- adapter registration interface in plugin runtime bridge -- acpx adapter implementation and tests -- thread-capable channel delivery projection logic with checkpoint replay (Discord first) -- lifecycle hooks for reset/delete/archive/unfocus -- stale-binding detector and operator-facing diagnostics -- config validation and precedence tests for all new ACP keys -- operational docs and troubleshooting runbook - -## Test plan - -Unit tests: - -- ACP DB transaction boundaries (spawn/bind/enqueue atomicity, cancel, close) -- ACP state-machine transition guards for sessions and runs -- idempotency reservation/replay semantics across all ACP commands -- per-session actor serialization and queue ordering -- acpx event parser and chunk coalescer -- runtime supervisor restart and backoff policy -- config precedence and effective TTL calculation -- core ACP routing branch selection and fail-closed behavior when backend/session is invalid - -Integration tests: - -- fake ACP adapter process for deterministic streaming and cancel behavior -- ACP manager + dispatch integration with transactional persistence -- thread-bound inbound routing to ACP session key -- thread-bound outbound delivery suppresses parent channel duplication -- checkpoint replay recovers after delivery failure and resumes from last event -- plugin service registration and teardown of ACP runtime backend - -Gateway e2e tests: - -- spawn ACP with thread, exchange multi-turn prompts, unfocus -- gateway restart with persisted ACP DB and bindings, then continue same session -- concurrent ACP sessions in multiple threads have no cross-talk -- duplicate command retries (same idempotency key) do not create duplicate runs or replies -- stale-binding scenario yields explicit error and optional auto-clean behavior - -## Risks and mitigations - -- Duplicate deliveries during transition - - Mitigation: single destination resolver and idempotent event checkpoint -- Runtime process churn under load - - Mitigation: long lived per session owners + concurrency caps + backoff -- Plugin absent or misconfigured - - Mitigation: explicit operator-facing error and fail-closed ACP routing (no implicit fallback to normal session path) -- Config confusion between subagent and ACP gates - - Mitigation: explicit ACP keys and command feedback that includes effective policy source -- Control-plane store corruption or migration bugs - - Mitigation: WAL mode, backup/restore hooks, migration smoke tests, and read-only fallback diagnostics -- Actor deadlocks or mailbox starvation - - Mitigation: watchdog timers, actor health probes, and bounded mailbox depth with rejection telemetry - -## Acceptance checklist - -- ACP session spawn can create or bind a thread in a supported channel adapter (currently Discord) -- all thread messages route to bound ACP session only -- ACP outputs appear in the same thread identity with streaming or batches -- no duplicate output in parent channel for bound turns -- spawn+bind+initial enqueue are atomic in persistent store -- ACP command retries are idempotent and do not duplicate runs or outputs -- cancel, close, unfocus, archive, reset, and delete perform deterministic cleanup -- crash restart preserves mapping and resumes multi turn continuity -- concurrent thread bound ACP sessions work independently -- ACP backend missing state produces clear actionable error -- stale bindings are detected and surfaced explicitly (with optional safe auto-clean) -- control-plane metrics and diagnostics are available for operators -- new unit, integration, and e2e coverage passes - -## Addendum: targeted refactors for current implementation (status) - -These are non-blocking follow-ups to keep the ACP path maintainable after the current feature set lands. - -### 1) Centralize ACP dispatch policy evaluation (completed) - -- implemented via shared ACP policy helpers in `src/acp/policy.ts` -- dispatch, ACP command lifecycle handlers, and ACP spawn path now consume shared policy logic - -### 2) Split ACP command handler by subcommand domain (completed) - -- `src/auto-reply/reply/commands-acp.ts` is now a thin router -- subcommand behavior is split into: - - `src/auto-reply/reply/commands-acp/lifecycle.ts` - - `src/auto-reply/reply/commands-acp/runtime-options.ts` - - `src/auto-reply/reply/commands-acp/diagnostics.ts` - - shared helpers in `src/auto-reply/reply/commands-acp/shared.ts` - -### 3) Split ACP session manager by responsibility (completed) - -- manager is split into: - - `src/acp/control-plane/manager.ts` (public facade + singleton) - - `src/acp/control-plane/manager.core.ts` (manager implementation) - - `src/acp/control-plane/manager.types.ts` (manager types/deps) - - `src/acp/control-plane/manager.utils.ts` (normalization + helper functions) - -### 4) Optional acpx runtime adapter cleanup - -- `extensions/acpx/src/runtime.ts` can be split into: -- process execution/supervision -- ndjson event parsing/normalization -- runtime API surface (`submit`, `cancel`, `close`, etc.) -- improves testability and makes backend behavior easier to audit diff --git a/docs/experiments/plans/acp-unified-streaming-refactor.md b/docs/experiments/plans/acp-unified-streaming-refactor.md deleted file mode 100644 index 3834fb9f8d8..00000000000 --- a/docs/experiments/plans/acp-unified-streaming-refactor.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -summary: "Holy grail refactor plan for one unified runtime streaming pipeline across main, subagent, and ACP" -owner: "onutc" -status: "draft" -last_updated: "2026-02-25" -title: "Unified Runtime Streaming Refactor Plan" ---- - -# Unified Runtime Streaming Refactor Plan - -## Objective - -Deliver one shared streaming pipeline for `main`, `subagent`, and `acp` so all runtimes get identical coalescing, chunking, delivery ordering, and crash recovery behavior. - -## Why this exists - -- Current behavior is split across multiple runtime-specific shaping paths. -- Formatting/coalescing bugs can be fixed in one path but remain in others. -- Delivery consistency, duplicate suppression, and recovery semantics are harder to reason about. - -## Target architecture - -Single pipeline, runtime-specific adapters: - -1. Runtime adapters emit canonical events only. -2. Shared stream assembler coalesces and finalizes text/tool/status events. -3. Shared channel projector applies channel-specific chunking/formatting once. -4. Shared delivery ledger enforces idempotent send/replay semantics. -5. Outbound channel adapter executes sends and records delivery checkpoints. - -Canonical event contract: - -- `turn_started` -- `text_delta` -- `block_final` -- `tool_started` -- `tool_finished` -- `status` -- `turn_completed` -- `turn_failed` -- `turn_cancelled` - -## Workstreams - -### 1) Canonical streaming contract - -- Define strict event schema + validation in core. -- Add adapter contract tests to guarantee each runtime emits compatible events. -- Reject malformed runtime events early and surface structured diagnostics. - -### 2) Shared stream processor - -- Replace runtime-specific coalescer/projector logic with one processor. -- Processor owns text delta buffering, idle flush, max-chunk splitting, and completion flush. -- Move ACP/main/subagent config resolution into one helper to prevent drift. - -### 3) Shared channel projection - -- Keep channel adapters dumb: accept finalized blocks and send. -- Move Discord-specific chunking quirks to channel projector only. -- Keep pipeline channel-agnostic before projection. - -### 4) Delivery ledger + replay - -- Add per-turn/per-chunk delivery IDs. -- Record checkpoints before and after physical send. -- On restart, replay pending chunks idempotently and avoid duplicates. - -### 5) Migration and cutover - -- Phase 1: shadow mode (new pipeline computes output but old path sends; compare). -- Phase 2: runtime-by-runtime cutover (`acp`, then `subagent`, then `main` or reverse by risk). -- Phase 3: delete legacy runtime-specific streaming code. - -## Non-goals - -- No changes to ACP policy/permissions model in this refactor. -- No channel-specific feature expansion outside projection compatibility fixes. -- No transport/backend redesign (acpx plugin contract remains as-is unless needed for event parity). - -## Risks and mitigations - -- Risk: behavioral regressions in existing main/subagent paths. - Mitigation: shadow mode diffing + adapter contract tests + channel e2e tests. -- Risk: duplicate sends during crash recovery. - Mitigation: durable delivery IDs + idempotent replay in delivery adapter. -- Risk: runtime adapters diverge again. - Mitigation: required shared contract test suite for all adapters. - -## Acceptance criteria - -- All runtimes pass shared streaming contract tests. -- Discord ACP/main/subagent produce equivalent spacing/chunking behavior for tiny deltas. -- Crash/restart replay sends no duplicate chunk for the same delivery ID. -- Legacy ACP projector/coalescer path is removed. -- Streaming config resolution is shared and runtime-independent. diff --git a/docs/experiments/plans/browser-evaluate-cdp-refactor.md b/docs/experiments/plans/browser-evaluate-cdp-refactor.md deleted file mode 100644 index 5832c8a65e6..00000000000 --- a/docs/experiments/plans/browser-evaluate-cdp-refactor.md +++ /dev/null @@ -1,232 +0,0 @@ ---- -summary: "Plan: isolate browser act:evaluate from Playwright queue using CDP, with end-to-end deadlines and safer ref resolution" -read_when: - - Working on browser `act:evaluate` timeout, abort, or queue blocking issues - - Planning CDP based isolation for evaluate execution -owner: "openclaw" -status: "draft" -last_updated: "2026-02-10" -title: "Browser Evaluate CDP Refactor" ---- - -# Browser Evaluate CDP Refactor Plan - -## Context - -`act:evaluate` executes user provided JavaScript in the page. Today it runs via Playwright -(`page.evaluate` or `locator.evaluate`). Playwright serializes CDP commands per page, so a -stuck or long running evaluate can block the page command queue and make every later action -on that tab look "stuck". - -PR #13498 adds a pragmatic safety net (bounded evaluate, abort propagation, and best-effort -recovery). This document describes a larger refactor that makes `act:evaluate` inherently -isolated from Playwright so a stuck evaluate cannot wedge normal Playwright operations. - -## Goals - -- `act:evaluate` cannot permanently block later browser actions on the same tab. -- Timeouts are single source of truth end to end so a caller can rely on a budget. -- Abort and timeout are treated the same way across HTTP and in-process dispatch. -- Element targeting for evaluate is supported without switching everything off Playwright. -- Maintain backward compatibility for existing callers and payloads. - -## Non-goals - -- Replace all browser actions (click, type, wait, etc.) with CDP implementations. -- Remove the existing safety net introduced in PR #13498 (it remains a useful fallback). -- Introduce new unsafe capabilities beyond the existing `browser.evaluateEnabled` gate. -- Add process isolation (worker process/thread) for evaluate. If we still see hard to recover - stuck states after this refactor, that is a follow-up idea. - -## Current Architecture (Why It Gets Stuck) - -At a high level: - -- Callers send `act:evaluate` to the browser control service. -- The route handler calls into Playwright to execute the JavaScript. -- Playwright serializes page commands, so an evaluate that never finishes blocks the queue. -- A stuck queue means later click/type/wait operations on the tab can appear to hang. - -## Proposed Architecture - -### 1. Deadline Propagation - -Introduce a single budget concept and derive everything from it: - -- Caller sets `timeoutMs` (or a deadline in the future). -- The outer request timeout, route handler logic, and the execution budget inside the page - all use the same budget, with small headroom where needed for serialization overhead. -- Abort is propagated as an `AbortSignal` everywhere so cancellation is consistent. - -Implementation direction: - -- Add a small helper (for example `createBudget({ timeoutMs, signal })`) that returns: - - `signal`: the linked AbortSignal - - `deadlineAtMs`: absolute deadline - - `remainingMs()`: remaining budget for child operations -- Use this helper in: - - `src/browser/client-fetch.ts` (HTTP and in-process dispatch) - - `src/node-host/runner.ts` (proxy path) - - browser action implementations (Playwright and CDP) - -### 2. Separate Evaluate Engine (CDP Path) - -Add a CDP based evaluate implementation that does not share Playwright's per page command -queue. The key property is that the evaluate transport is a separate WebSocket connection -and a separate CDP session attached to the target. - -Implementation direction: - -- New module, for example `src/browser/cdp-evaluate.ts`, that: - - Connects to the configured CDP endpoint (browser level socket). - - Uses `Target.attachToTarget({ targetId, flatten: true })` to get a `sessionId`. - - Runs either: - - `Runtime.evaluate` for page level evaluate, or - - `DOM.resolveNode` plus `Runtime.callFunctionOn` for element evaluate. - - On timeout or abort: - - Sends `Runtime.terminateExecution` best-effort for the session. - - Closes the WebSocket and returns a clear error. - -Notes: - -- This still executes JavaScript in the page, so termination can have side effects. The win - is that it does not wedge the Playwright queue, and it is cancelable at the transport - layer by killing the CDP session. - -### 3. Ref Story (Element Targeting Without A Full Rewrite) - -The hard part is element targeting. CDP needs a DOM handle or `backendDOMNodeId`, while -today most browser actions use Playwright locators based on refs from snapshots. - -Recommended approach: keep existing refs, but attach an optional CDP resolvable id. - -#### 3.1 Extend Stored Ref Info - -Extend the stored role ref metadata to optionally include a CDP id: - -- Today: `{ role, name, nth }` -- Proposed: `{ role, name, nth, backendDOMNodeId?: number }` - -This keeps all existing Playwright based actions working and allows CDP evaluate to accept -the same `ref` value when the `backendDOMNodeId` is available. - -#### 3.2 Populate backendDOMNodeId At Snapshot Time - -When producing a role snapshot: - -1. Generate the existing role ref map as today (role, name, nth). -2. Fetch the AX tree via CDP (`Accessibility.getFullAXTree`) and compute a parallel map of - `(role, name, nth) -> backendDOMNodeId` using the same duplicate handling rules. -3. Merge the id back into the stored ref info for the current tab. - -If mapping fails for a ref, leave `backendDOMNodeId` undefined. This makes the feature -best-effort and safe to roll out. - -#### 3.3 Evaluate Behavior With Ref - -In `act:evaluate`: - -- If `ref` is present and has `backendDOMNodeId`, run element evaluate via CDP. -- If `ref` is present but has no `backendDOMNodeId`, fall back to the Playwright path (with - the safety net). - -Optional escape hatch: - -- Extend the request shape to accept `backendDOMNodeId` directly for advanced callers (and - for debugging), while keeping `ref` as the primary interface. - -### 4. Keep A Last Resort Recovery Path - -Even with CDP evaluate, there are other ways to wedge a tab or a connection. Keep the -existing recovery mechanisms (terminate execution + disconnect Playwright) as a last resort -for: - -- legacy callers -- environments where CDP attach is blocked -- unexpected Playwright edge cases - -## Implementation Plan (Single Iteration) - -### Deliverables - -- A CDP based evaluate engine that runs outside the Playwright per-page command queue. -- A single end-to-end timeout/abort budget used consistently by callers and handlers. -- Ref metadata that can optionally carry `backendDOMNodeId` for element evaluate. -- `act:evaluate` prefers the CDP engine when possible and falls back to Playwright when not. -- Tests that prove a stuck evaluate does not wedge later actions. -- Logs/metrics that make failures and fallbacks visible. - -### Implementation Checklist - -1. Add a shared "budget" helper to link `timeoutMs` + upstream `AbortSignal` into: - - a single `AbortSignal` - - an absolute deadline - - a `remainingMs()` helper for downstream operations -2. Update all caller paths to use that helper so `timeoutMs` means the same thing everywhere: - - `src/browser/client-fetch.ts` (HTTP and in-process dispatch) - - `src/node-host/runner.ts` (node proxy path) - - CLI wrappers that call `/act` (add `--timeout-ms` to `browser evaluate`) -3. Implement `src/browser/cdp-evaluate.ts`: - - connect to the browser-level CDP socket - - `Target.attachToTarget` to get a `sessionId` - - run `Runtime.evaluate` for page evaluate - - run `DOM.resolveNode` + `Runtime.callFunctionOn` for element evaluate - - on timeout/abort: best-effort `Runtime.terminateExecution` then close the socket -4. Extend stored role ref metadata to optionally include `backendDOMNodeId`: - - keep existing `{ role, name, nth }` behavior for Playwright actions - - add `backendDOMNodeId?: number` for CDP element targeting -5. Populate `backendDOMNodeId` during snapshot creation (best-effort): - - fetch AX tree via CDP (`Accessibility.getFullAXTree`) - - compute `(role, name, nth) -> backendDOMNodeId` and merge into the stored ref map - - if mapping is ambiguous or missing, leave the id undefined -6. Update `act:evaluate` routing: - - if no `ref`: always use CDP evaluate - - if `ref` resolves to a `backendDOMNodeId`: use CDP element evaluate - - otherwise: fall back to Playwright evaluate (still bounded and abortable) -7. Keep the existing "last resort" recovery path as a fallback, not the default path. -8. Add tests: - - stuck evaluate times out within budget and the next click/type succeeds - - abort cancels evaluate (client disconnect or timeout) and unblocks subsequent actions - - mapping failures cleanly fall back to Playwright -9. Add observability: - - evaluate duration and timeout counters - - terminateExecution usage - - fallback rate (CDP -> Playwright) and reasons - -### Acceptance Criteria - -- A deliberately hung `act:evaluate` returns within the caller budget and does not wedge the - tab for later actions. -- `timeoutMs` behaves consistently across CLI, agent tool, node proxy, and in-process calls. -- If `ref` can be mapped to `backendDOMNodeId`, element evaluate uses CDP; otherwise the - fallback path is still bounded and recoverable. - -## Testing Plan - -- Unit tests: - - `(role, name, nth)` matching logic between role refs and AX tree nodes. - - Budget helper behavior (headroom, remaining time math). -- Integration tests: - - CDP evaluate timeout returns within budget and does not block the next action. - - Abort cancels evaluate and triggers termination best-effort. -- Contract tests: - - Ensure `BrowserActRequest` and `BrowserActResponse` remain compatible. - -## Risks And Mitigations - -- Mapping is imperfect: - - Mitigation: best-effort mapping, fallback to Playwright evaluate, and add debug tooling. -- `Runtime.terminateExecution` has side effects: - - Mitigation: only use on timeout/abort and document the behavior in errors. -- Extra overhead: - - Mitigation: only fetch AX tree when snapshots are requested, cache per target, and keep - CDP session short lived. -- Extension relay limitations: - - Mitigation: use browser level attach APIs when per page sockets are not available, and - keep the current Playwright path as fallback. - -## Open Questions - -- Should the new engine be configurable as `playwright`, `cdp`, or `auto`? -- Do we want to expose a new "nodeRef" format for advanced users, or keep `ref` only? -- How should frame snapshots and selector scoped snapshots participate in AX mapping? diff --git a/docs/experiments/plans/discord-async-inbound-worker.md b/docs/experiments/plans/discord-async-inbound-worker.md deleted file mode 100644 index 70397b51338..00000000000 --- a/docs/experiments/plans/discord-async-inbound-worker.md +++ /dev/null @@ -1,337 +0,0 @@ ---- -summary: "Status and next steps for decoupling Discord gateway listeners from long-running agent turns with a Discord-specific inbound worker" -owner: "openclaw" -status: "in_progress" -last_updated: "2026-03-05" -title: "Discord Async Inbound Worker Plan" ---- - -# Discord Async Inbound Worker Plan - -## Objective - -Remove Discord listener timeout as a user-facing failure mode by making inbound Discord turns asynchronous: - -1. Gateway listener accepts and normalizes inbound events quickly. -2. A Discord run queue stores serialized jobs keyed by the same ordering boundary we use today. -3. A worker executes the actual agent turn outside the Carbon listener lifetime. -4. Replies are delivered back to the originating channel or thread after the run completes. - -This is the long-term fix for queued Discord runs timing out at `channels.discord.eventQueue.listenerTimeout` while the agent run itself is still making progress. - -## Current status - -This plan is partially implemented. - -Already done: - -- Discord listener timeout and Discord run timeout are now separate settings. -- Accepted inbound Discord turns are enqueued into `src/discord/monitor/inbound-worker.ts`. -- The worker now owns the long-running turn instead of the Carbon listener. -- Existing per-route ordering is preserved by queue key. -- Timeout regression coverage exists for the Discord worker path. - -What this means in plain language: - -- the production timeout bug is fixed -- the long-running turn no longer dies just because the Discord listener budget expires -- the worker architecture is not finished yet - -What is still missing: - -- `DiscordInboundJob` is still only partially normalized and still carries live runtime references -- command semantics (`stop`, `new`, `reset`, future session controls) are not yet fully worker-native -- worker observability and operator status are still minimal -- there is still no restart durability - -## Why this exists - -Current behavior ties the full agent turn to the listener lifetime: - -- `src/discord/monitor/listeners.ts` applies the timeout and abort boundary. -- `src/discord/monitor/message-handler.ts` keeps the queued run inside that boundary. -- `src/discord/monitor/message-handler.process.ts` performs media loading, routing, dispatch, typing, draft streaming, and final reply delivery inline. - -That architecture has two bad properties: - -- long but healthy turns can be aborted by the listener watchdog -- users can see no reply even when the downstream runtime would have produced one - -Raising the timeout helps but does not change the failure mode. - -## Non-goals - -- Do not redesign non-Discord channels in this pass. -- Do not broaden this into a generic all-channel worker framework in the first implementation. -- Do not extract a shared cross-channel inbound worker abstraction yet; only share low-level primitives when duplication is obvious. -- Do not add durable crash recovery in the first pass unless needed to land safely. -- Do not change route selection, binding semantics, or ACP policy in this plan. - -## Current constraints - -The current Discord processing path still depends on some live runtime objects that should not stay inside the long-term job payload: - -- Carbon `Client` -- raw Discord event shapes -- in-memory guild history map -- thread binding manager callbacks -- live typing and draft stream state - -We already moved execution onto a worker queue, but the normalization boundary is still incomplete. Right now the worker is "run later in the same process with some of the same live objects," not a fully data-only job boundary. - -## Target architecture - -### 1. Listener stage - -`DiscordMessageListener` remains the ingress point, but its job becomes: - -- run preflight and policy checks -- normalize accepted input into a serializable `DiscordInboundJob` -- enqueue the job into a per-session or per-channel async queue -- return immediately to Carbon once the enqueue succeeds - -The listener should no longer own the end-to-end LLM turn lifetime. - -### 2. Normalized job payload - -Introduce a serializable job descriptor that contains only the data needed to run the turn later. - -Minimum shape: - -- route identity - - `agentId` - - `sessionKey` - - `accountId` - - `channel` -- delivery identity - - destination channel id - - reply target message id - - thread id if present -- sender identity - - sender id, label, username, tag -- channel context - - guild id - - channel name or slug - - thread metadata - - resolved system prompt override -- normalized message body - - base text - - effective message text - - attachment descriptors or resolved media references -- gating decisions - - mention requirement outcome - - command authorization outcome - - bound session or agent metadata if applicable - -The job payload must not contain live Carbon objects or mutable closures. - -Current implementation status: - -- partially done -- `src/discord/monitor/inbound-job.ts` exists and defines the worker handoff -- the payload still contains live Discord runtime context and should be reduced further - -### 3. Worker stage - -Add a Discord-specific worker runner responsible for: - -- reconstructing the turn context from `DiscordInboundJob` -- loading media and any additional channel metadata needed for the run -- dispatching the agent turn -- delivering final reply payloads -- updating status and diagnostics - -Recommended location: - -- `src/discord/monitor/inbound-worker.ts` -- `src/discord/monitor/inbound-job.ts` - -### 4. Ordering model - -Ordering must remain equivalent to today for a given route boundary. - -Recommended key: - -- use the same queue key logic as `resolveDiscordRunQueueKey(...)` - -This preserves existing behavior: - -- one bound agent conversation does not interleave with itself -- different Discord channels can still progress independently - -### 5. Timeout model - -After cutover, there are two separate timeout classes: - -- listener timeout - - only covers normalization and enqueue - - should be short -- run timeout - - optional, worker-owned, explicit, and user-visible - - should not be inherited accidentally from Carbon listener settings - -This removes the current accidental coupling between "Discord gateway listener stayed alive" and "agent run is healthy." - -## Recommended implementation phases - -### Phase 1: normalization boundary - -- Status: partially implemented -- Done: - - extracted `buildDiscordInboundJob(...)` - - added worker handoff tests -- Remaining: - - make `DiscordInboundJob` plain data only - - move live runtime dependencies to worker-owned services instead of per-job payload - - stop rebuilding process context by stitching live listener refs back into the job - -### Phase 2: in-memory worker queue - -- Status: implemented -- Done: - - added `DiscordInboundWorkerQueue` keyed by resolved run queue key - - listener enqueues jobs instead of directly awaiting `processDiscordMessage(...)` - - worker executes jobs in-process, in memory only - -This is the first functional cutover. - -### Phase 3: process split - -- Status: not started -- Move delivery, typing, and draft streaming ownership behind worker-facing adapters. -- Replace direct use of live preflight context with worker context reconstruction. -- Keep `processDiscordMessage(...)` temporarily as a facade if needed, then split it. - -### Phase 4: command semantics - -- Status: not started - Make sure native Discord commands still behave correctly when work is queued: - -- `stop` -- `new` -- `reset` -- any future session-control commands - -The worker queue must expose enough run state for commands to target the active or queued turn. - -### Phase 5: observability and operator UX - -- Status: not started -- emit queue depth and active worker counts into monitor status -- record enqueue time, start time, finish time, and timeout or cancellation reason -- surface worker-owned timeout or delivery failures clearly in logs - -### Phase 6: optional durability follow-up - -- Status: not started - Only after the in-memory version is stable: - -- decide whether queued Discord jobs should survive gateway restart -- if yes, persist job descriptors and delivery checkpoints -- if no, document the explicit in-memory boundary - -This should be a separate follow-up unless restart recovery is required to land. - -## File impact - -Current primary files: - -- `src/discord/monitor/listeners.ts` -- `src/discord/monitor/message-handler.ts` -- `src/discord/monitor/message-handler.preflight.ts` -- `src/discord/monitor/message-handler.process.ts` -- `src/discord/monitor/status.ts` - -Current worker files: - -- `src/discord/monitor/inbound-job.ts` -- `src/discord/monitor/inbound-worker.ts` -- `src/discord/monitor/inbound-job.test.ts` -- `src/discord/monitor/message-handler.queue.test.ts` - -Likely next touch points: - -- `src/auto-reply/dispatch.ts` -- `src/discord/monitor/reply-delivery.ts` -- `src/discord/monitor/thread-bindings.ts` -- `src/discord/monitor/native-command.ts` - -## Next step now - -The next step is to make the worker boundary real instead of partial. - -Do this next: - -1. Move live runtime dependencies out of `DiscordInboundJob` -2. Keep those dependencies on the Discord worker instance instead -3. Reduce queued jobs to plain Discord-specific data: - - route identity - - delivery target - - sender info - - normalized message snapshot - - gating and binding decisions -4. Reconstruct worker execution context from that plain data inside the worker - -In practice, that means: - -- `client` -- `threadBindings` -- `guildHistories` -- `discordRestFetch` -- other mutable runtime-only handles - -should stop living on each queued job and instead live on the worker itself or behind worker-owned adapters. - -After that lands, the next follow-up should be command-state cleanup for `stop`, `new`, and `reset`. - -## Testing plan - -Keep the existing timeout repro coverage in: - -- `src/discord/monitor/message-handler.queue.test.ts` - -Add new tests for: - -1. listener returns after enqueue without awaiting full turn -2. per-route ordering is preserved -3. different channels still run concurrently -4. replies are delivered to the original message destination -5. `stop` cancels the active worker-owned run -6. worker failure produces visible diagnostics without blocking later jobs -7. ACP-bound Discord channels still route correctly under worker execution - -## Risks and mitigations - -- Risk: command semantics drift from current synchronous behavior - Mitigation: land command-state plumbing in the same cutover, not later - -- Risk: reply delivery loses thread or reply-to context - Mitigation: make delivery identity first-class in `DiscordInboundJob` - -- Risk: duplicate sends during retries or queue restarts - Mitigation: keep first pass in-memory only, or add explicit delivery idempotency before persistence - -- Risk: `message-handler.process.ts` becomes harder to reason about during migration - Mitigation: split into normalization, execution, and delivery helpers before or during worker cutover - -## Acceptance criteria - -The plan is complete when: - -1. Discord listener timeout no longer aborts healthy long-running turns. -2. Listener lifetime and agent-turn lifetime are separate concepts in code. -3. Existing per-session ordering is preserved. -4. ACP-bound Discord channels work through the same worker path. -5. `stop` targets the worker-owned run instead of the old listener-owned call stack. -6. Timeout and delivery failures become explicit worker outcomes, not silent listener drops. - -## Remaining landing strategy - -Finish this in follow-up PRs: - -1. make `DiscordInboundJob` plain-data only and move live runtime refs onto the worker -2. clean up command-state ownership for `stop`, `new`, and `reset` -3. add worker observability and operator status -4. decide whether durability is needed or explicitly document the in-memory boundary - -This is still a bounded follow-up if kept Discord-only and if we continue to avoid a premature cross-channel worker abstraction. diff --git a/docs/experiments/plans/openresponses-gateway.md b/docs/experiments/plans/openresponses-gateway.md deleted file mode 100644 index 8ca63c34ec9..00000000000 --- a/docs/experiments/plans/openresponses-gateway.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -summary: "Plan: Add OpenResponses /v1/responses endpoint and deprecate chat completions cleanly" -read_when: - - Designing or implementing `/v1/responses` gateway support - - Planning migration from Chat Completions compatibility -owner: "openclaw" -status: "draft" -last_updated: "2026-01-19" -title: "OpenResponses Gateway Plan" ---- - -# OpenResponses Gateway Integration Plan - -## Context - -OpenClaw Gateway currently exposes a minimal OpenAI-compatible Chat Completions endpoint at -`/v1/chat/completions` (see [OpenAI Chat Completions](/gateway/openai-http-api)). - -Open Responses is an open inference standard based on the OpenAI Responses API. It is designed -for agentic workflows and uses item-based inputs plus semantic streaming events. The OpenResponses -spec defines `/v1/responses`, not `/v1/chat/completions`. - -## Goals - -- Add a `/v1/responses` endpoint that adheres to OpenResponses semantics. -- Keep Chat Completions as a compatibility layer that is easy to disable and eventually remove. -- Standardize validation and parsing with isolated, reusable schemas. - -## Non-goals - -- Full OpenResponses feature parity in the first pass (images, files, hosted tools). -- Replacing internal agent execution logic or tool orchestration. -- Changing the existing `/v1/chat/completions` behavior during the first phase. - -## Research Summary - -Sources: OpenResponses OpenAPI, OpenResponses specification site, and the Hugging Face blog post. - -Key points extracted: - -- `POST /v1/responses` accepts `CreateResponseBody` fields like `model`, `input` (string or - `ItemParam[]`), `instructions`, `tools`, `tool_choice`, `stream`, `max_output_tokens`, and - `max_tool_calls`. -- `ItemParam` is a discriminated union of: - - `message` items with roles `system`, `developer`, `user`, `assistant` - - `function_call` and `function_call_output` - - `reasoning` - - `item_reference` -- Successful responses return a `ResponseResource` with `object: "response"`, `status`, and - `output` items. -- Streaming uses semantic events such as: - - `response.created`, `response.in_progress`, `response.completed`, `response.failed` - - `response.output_item.added`, `response.output_item.done` - - `response.content_part.added`, `response.content_part.done` - - `response.output_text.delta`, `response.output_text.done` -- The spec requires: - - `Content-Type: text/event-stream` - - `event:` must match the JSON `type` field - - terminal event must be literal `[DONE]` -- Reasoning items may expose `content`, `encrypted_content`, and `summary`. -- HF examples include `OpenResponses-Version: latest` in requests (optional header). - -## Proposed Architecture - -- Add `src/gateway/open-responses.schema.ts` containing Zod schemas only (no gateway imports). -- Add `src/gateway/openresponses-http.ts` (or `open-responses-http.ts`) for `/v1/responses`. -- Keep `src/gateway/openai-http.ts` intact as a legacy compatibility adapter. -- Add config `gateway.http.endpoints.responses.enabled` (default `false`). -- Keep `gateway.http.endpoints.chatCompletions.enabled` independent; allow both endpoints to be - toggled separately. -- Emit a startup warning when Chat Completions is enabled to signal legacy status. - -## Deprecation Path for Chat Completions - -- Maintain strict module boundaries: no shared schema types between responses and chat completions. -- Make Chat Completions opt-in by config so it can be disabled without code changes. -- Update docs to label Chat Completions as legacy once `/v1/responses` is stable. -- Optional future step: map Chat Completions requests to the Responses handler for a simpler - removal path. - -## Phase 1 Support Subset - -- Accept `input` as string or `ItemParam[]` with message roles and `function_call_output`. -- Extract system and developer messages into `extraSystemPrompt`. -- Use the most recent `user` or `function_call_output` as the current message for agent runs. -- Reject unsupported content parts (image/file) with `invalid_request_error`. -- Return a single assistant message with `output_text` content. -- Return `usage` with zeroed values until token accounting is wired. - -## Validation Strategy (No SDK) - -- Implement Zod schemas for the supported subset of: - - `CreateResponseBody` - - `ItemParam` + message content part unions - - `ResponseResource` - - Streaming event shapes used by the gateway -- Keep schemas in a single, isolated module to avoid drift and allow future codegen. - -## Streaming Implementation (Phase 1) - -- SSE lines with both `event:` and `data:`. -- Required sequence (minimum viable): - - `response.created` - - `response.output_item.added` - - `response.content_part.added` - - `response.output_text.delta` (repeat as needed) - - `response.output_text.done` - - `response.content_part.done` - - `response.completed` - - `[DONE]` - -## Tests and Verification Plan - -- Add e2e coverage for `/v1/responses`: - - Auth required - - Non-stream response shape - - Stream event ordering and `[DONE]` - - Session routing with headers and `user` -- Keep `src/gateway/openai-http.test.ts` unchanged. -- Manual: curl to `/v1/responses` with `stream: true` and verify event ordering and terminal - `[DONE]`. - -## Doc Updates (Follow-up) - -- Add a new docs page for `/v1/responses` usage and examples. -- Update `/gateway/openai-http-api` with a legacy note and pointer to `/v1/responses`. diff --git a/docs/experiments/plans/pty-process-supervision.md b/docs/experiments/plans/pty-process-supervision.md deleted file mode 100644 index 4ec898058cd..00000000000 --- a/docs/experiments/plans/pty-process-supervision.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -summary: "Production plan for reliable interactive process supervision (PTY + non-PTY) with explicit ownership, unified lifecycle, and deterministic cleanup" -read_when: - - Working on exec/process lifecycle ownership and cleanup - - Debugging PTY and non-PTY supervision behavior -owner: "openclaw" -status: "in-progress" -last_updated: "2026-02-15" -title: "PTY and Process Supervision Plan" ---- - -# PTY and Process Supervision Plan - -## 1. Problem and goal - -We need one reliable lifecycle for long-running command execution across: - -- `exec` foreground runs -- `exec` background runs -- `process` follow up actions (`poll`, `log`, `send-keys`, `paste`, `submit`, `kill`, `remove`) -- CLI agent runner subprocesses - -The goal is not just to support PTY. The goal is predictable ownership, cancellation, timeout, and cleanup with no unsafe process matching heuristics. - -## 2. Scope and boundaries - -- Keep implementation internal in `src/process/supervisor`. -- Do not create a new package for this. -- Keep current behavior compatibility where practical. -- Do not broaden scope to terminal replay or tmux style session persistence. - -## 3. Implemented in this branch - -### Supervisor baseline already present - -- Supervisor module is in place under `src/process/supervisor/*`. -- Exec runtime and CLI runner are already routed through supervisor spawn and wait. -- Registry finalization is idempotent. - -### This pass completed - -1. Explicit PTY command contract - -- `SpawnInput` is now a discriminated union in `src/process/supervisor/types.ts`. -- PTY runs require `ptyCommand` instead of reusing generic `argv`. -- Supervisor no longer rebuilds PTY command strings from argv joins in `src/process/supervisor/supervisor.ts`. -- Exec runtime now passes `ptyCommand` directly in `src/agents/bash-tools.exec-runtime.ts`. - -2. Process layer type decoupling - -- Supervisor types no longer import `SessionStdin` from agents. -- Process local stdin contract lives in `src/process/supervisor/types.ts` (`ManagedRunStdin`). -- Adapters now depend only on process level types: - - `src/process/supervisor/adapters/child.ts` - - `src/process/supervisor/adapters/pty.ts` - -3. Process tool lifecycle ownership improvement - -- `src/agents/bash-tools.process.ts` now requests cancellation through supervisor first. -- `process kill/remove` now use process-tree fallback termination when supervisor lookup misses. -- `remove` keeps deterministic remove behavior by dropping running session entries immediately after termination is requested. - -4. Single source watchdog defaults - -- Added shared defaults in `src/agents/cli-watchdog-defaults.ts`. -- `src/agents/cli-backends.ts` consumes the shared defaults. -- `src/agents/cli-runner/reliability.ts` consumes the same shared defaults. - -5. Dead helper cleanup - -- Removed unused `killSession` helper path from `src/agents/bash-tools.shared.ts`. - -6. Direct supervisor path tests added - -- Added `src/agents/bash-tools.process.supervisor.test.ts` to cover kill and remove routing through supervisor cancellation. - -7. Reliability gap fixes completed - -- `src/agents/bash-tools.process.ts` now falls back to real OS-level process termination when supervisor lookup misses. -- `src/process/supervisor/adapters/child.ts` now uses process-tree termination semantics for default cancel/timeout kill paths. -- Added shared process-tree utility in `src/process/kill-tree.ts`. - -8. PTY contract edge-case coverage added - -- Added `src/process/supervisor/supervisor.pty-command.test.ts` for verbatim PTY command forwarding and empty-command rejection. -- Added `src/process/supervisor/adapters/child.test.ts` for process-tree kill behavior in child adapter cancellation. - -## 4. Remaining gaps and decisions - -### Reliability status - -The two required reliability gaps for this pass are now closed: - -- `process kill/remove` now has a real OS termination fallback when supervisor lookup misses. -- child cancel/timeout now uses process-tree kill semantics for default kill path. -- Regression tests were added for both behaviors. - -### Durability and startup reconciliation - -Restart behavior is now explicitly defined as in-memory lifecycle only. - -- `reconcileOrphans()` remains a no-op in `src/process/supervisor/supervisor.ts` by design. -- Active runs are not recovered after process restart. -- This boundary is intentional for this implementation pass to avoid partial persistence risks. - -### Maintainability follow-ups - -1. `runExecProcess` in `src/agents/bash-tools.exec-runtime.ts` still handles multiple responsibilities and can be split into focused helpers in a follow-up. - -## 5. Implementation plan - -The implementation pass for required reliability and contract items is complete. - -Completed: - -- `process kill/remove` fallback real termination -- process-tree cancellation for child adapter default kill path -- regression tests for fallback kill and child adapter kill path -- PTY command edge-case tests under explicit `ptyCommand` -- explicit in-memory restart boundary with `reconcileOrphans()` no-op by design - -Optional follow-up: - -- split `runExecProcess` into focused helpers with no behavior drift - -## 6. File map - -### Process supervisor - -- `src/process/supervisor/types.ts` updated with discriminated spawn input and process local stdin contract. -- `src/process/supervisor/supervisor.ts` updated to use explicit `ptyCommand`. -- `src/process/supervisor/adapters/child.ts` and `src/process/supervisor/adapters/pty.ts` decoupled from agent types. -- `src/process/supervisor/registry.ts` idempotent finalize unchanged and retained. - -### Exec and process integration - -- `src/agents/bash-tools.exec-runtime.ts` updated to pass PTY command explicitly and keep fallback path. -- `src/agents/bash-tools.process.ts` updated to cancel via supervisor with real process-tree fallback termination. -- `src/agents/bash-tools.shared.ts` removed direct kill helper path. - -### CLI reliability - -- `src/agents/cli-watchdog-defaults.ts` added as shared baseline. -- `src/agents/cli-backends.ts` and `src/agents/cli-runner/reliability.ts` now consume same defaults. - -## 7. Validation run in this pass - -Unit tests: - -- `pnpm vitest src/process/supervisor/registry.test.ts` -- `pnpm vitest src/process/supervisor/supervisor.test.ts` -- `pnpm vitest src/process/supervisor/supervisor.pty-command.test.ts` -- `pnpm vitest src/process/supervisor/adapters/child.test.ts` -- `pnpm vitest src/agents/cli-backends.test.ts` -- `pnpm vitest src/agents/bash-tools.exec.pty-cleanup.test.ts` -- `pnpm vitest src/agents/bash-tools.process.poll-timeout.test.ts` -- `pnpm vitest src/agents/bash-tools.process.supervisor.test.ts` -- `pnpm vitest src/process/exec.test.ts` - -E2E targets: - -- `pnpm vitest src/agents/cli-runner.test.ts` -- `pnpm vitest run src/agents/bash-tools.exec.pty-fallback.test.ts src/agents/bash-tools.exec.background-abort.test.ts src/agents/bash-tools.process.send-keys.test.ts` - -Typecheck note: - -- Use `pnpm build` (and `pnpm check` for full lint/docs gate) in this repo. Older notes that mention `pnpm tsgo` are obsolete. - -## 8. Operational guarantees preserved - -- Exec env hardening behavior is unchanged. -- Approval and allowlist flow is unchanged. -- Output sanitization and output caps are unchanged. -- PTY adapter still guarantees wait settlement on forced kill and listener disposal. - -## 9. Definition of done - -1. Supervisor is lifecycle owner for managed runs. -2. PTY spawn uses explicit command contract with no argv reconstruction. -3. Process layer has no type dependency on agent layer for supervisor stdin contracts. -4. Watchdog defaults are single source. -5. Targeted unit and e2e tests remain green. -6. Restart durability boundary is explicitly documented or fully implemented. - -## 10. Summary - -The branch now has a coherent and safer supervision shape: - -- explicit PTY contract -- cleaner process layering -- supervisor driven cancellation path for process operations -- real fallback termination when supervisor lookup misses -- process-tree cancellation for child-run default kill paths -- unified watchdog defaults -- explicit in-memory restart boundary (no orphan reconciliation across restart in this pass) diff --git a/docs/experiments/plans/session-binding-channel-agnostic.md b/docs/experiments/plans/session-binding-channel-agnostic.md deleted file mode 100644 index aa1f926b36b..00000000000 --- a/docs/experiments/plans/session-binding-channel-agnostic.md +++ /dev/null @@ -1,226 +0,0 @@ ---- -summary: "Channel agnostic session binding architecture and iteration 1 delivery scope" -read_when: - - Refactoring channel-agnostic session routing and bindings - - Investigating duplicate, stale, or missing session delivery across channels -owner: "onutc" -status: "in-progress" -last_updated: "2026-02-21" -title: "Session Binding Channel Agnostic Plan" ---- - -# Session Binding Channel Agnostic Plan - -## Overview - -This document defines the long term channel agnostic session binding model and the concrete scope for the next implementation iteration. - -Goal: - -- make subagent bound session routing a core capability -- keep channel specific behavior in adapters -- avoid regressions in normal Discord behavior - -## Why this exists - -Current behavior mixes: - -- completion content policy -- destination routing policy -- Discord specific details - -This caused edge cases such as: - -- duplicate main and thread delivery under concurrent runs -- stale token usage on reused binding managers -- missing activity accounting for webhook sends - -## Iteration 1 scope - -This iteration is intentionally limited. - -### 1. Add channel agnostic core interfaces - -Add core types and service interfaces for bindings and routing. - -Proposed core types: - -```ts -export type BindingTargetKind = "subagent" | "session"; -export type BindingStatus = "active" | "ending" | "ended"; - -export type ConversationRef = { - channel: string; - accountId: string; - conversationId: string; - parentConversationId?: string; -}; - -export type SessionBindingRecord = { - bindingId: string; - targetSessionKey: string; - targetKind: BindingTargetKind; - conversation: ConversationRef; - status: BindingStatus; - boundAt: number; - expiresAt?: number; - metadata?: Record; -}; -``` - -Core service contract: - -```ts -export interface SessionBindingService { - bind(input: { - targetSessionKey: string; - targetKind: BindingTargetKind; - conversation: ConversationRef; - metadata?: Record; - ttlMs?: number; - }): Promise; - - listBySession(targetSessionKey: string): SessionBindingRecord[]; - resolveByConversation(ref: ConversationRef): SessionBindingRecord | null; - touch(bindingId: string, at?: number): void; - unbind(input: { - bindingId?: string; - targetSessionKey?: string; - reason: string; - }): Promise; -} -``` - -### 2. Add one core delivery router for subagent completions - -Add a single destination resolution path for completion events. - -Router contract: - -```ts -export interface BoundDeliveryRouter { - resolveDestination(input: { - eventKind: "task_completion"; - targetSessionKey: string; - requester?: ConversationRef; - failClosed: boolean; - }): { - binding: SessionBindingRecord | null; - mode: "bound" | "fallback"; - reason: string; - }; -} -``` - -For this iteration: - -- only `task_completion` is routed through this new path -- existing paths for other event kinds remain as-is - -### 3. Keep Discord as adapter - -Discord remains the first adapter implementation. - -Adapter responsibilities: - -- create/reuse thread conversations -- send bound messages via webhook or channel send -- validate thread state (archived/deleted) -- map adapter metadata (webhook identity, thread ids) - -### 4. Fix currently known correctness issues - -Required in this iteration: - -- refresh token usage when reusing existing thread binding manager -- record outbound activity for webhook based Discord sends -- stop implicit main channel fallback when a bound thread destination is selected for session mode completion - -### 5. Preserve current runtime safety defaults - -No behavior change for users with thread bound spawn disabled. - -Defaults stay: - -- `channels.discord.threadBindings.spawnSubagentSessions = false` - -Result: - -- normal Discord users stay on current behavior -- new core path affects only bound session completion routing where enabled - -## Not in iteration 1 - -Explicitly deferred: - -- ACP binding targets (`targetKind: "acp"`) -- new channel adapters beyond Discord -- global replacement of all delivery paths (`spawn_ack`, future `subagent_message`) -- protocol level changes -- store migration/versioning redesign for all binding persistence - -Notes on ACP: - -- interface design keeps room for ACP -- ACP implementation is not started in this iteration - -## Routing invariants - -These invariants are mandatory for iteration 1. - -- destination selection and content generation are separate steps -- if session mode completion resolves to an active bound destination, delivery must target that destination -- no hidden reroute from bound destination to main channel -- fallback behavior must be explicit and observable - -## Compatibility and rollout - -Compatibility target: - -- no regression for users with thread bound spawning off -- no change to non-Discord channels in this iteration - -Rollout: - -1. Land interfaces and router behind current feature gates. -2. Route Discord completion mode bound deliveries through router. -3. Keep legacy path for non-bound flows. -4. Verify with targeted tests and canary runtime logs. - -## Tests required in iteration 1 - -Unit and integration coverage required: - -- manager token rotation uses latest token after manager reuse -- webhook sends update channel activity timestamps -- two active bound sessions in same requester channel do not duplicate to main channel -- completion for bound session mode run resolves to thread destination only -- disabled spawn flag keeps legacy behavior unchanged - -## Proposed implementation files - -Core: - -- `src/infra/outbound/session-binding-service.ts` (new) -- `src/infra/outbound/bound-delivery-router.ts` (new) -- `src/agents/subagent-announce.ts` (completion destination resolution integration) - -Discord adapter and runtime: - -- `src/discord/monitor/thread-bindings.manager.ts` -- `src/discord/monitor/reply-delivery.ts` -- `src/discord/send.outbound.ts` - -Tests: - -- `src/discord/monitor/provider*.test.ts` -- `src/discord/monitor/reply-delivery.test.ts` -- `src/agents/subagent-announce.format.test.ts` - -## Done criteria for iteration 1 - -- core interfaces exist and are wired for completion routing -- correctness fixes above are merged with tests -- no main and thread duplicate completion delivery in session mode bound runs -- no behavior change for disabled bound spawn deployments -- ACP remains explicitly deferred diff --git a/docs/experiments/proposals/acp-bound-command-auth.md b/docs/experiments/proposals/acp-bound-command-auth.md deleted file mode 100644 index 1d02e9e8469..00000000000 --- a/docs/experiments/proposals/acp-bound-command-auth.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -summary: "Proposal: long-term command authorization model for ACP-bound conversations" -read_when: - - Designing native command auth behavior in Telegram/Discord ACP-bound channels/topics -title: "ACP Bound Command Authorization (Proposal)" ---- - -# ACP Bound Command Authorization (Proposal) - -Status: Proposed, **not implemented yet**. - -This document describes a long-term authorization model for native commands in -ACP-bound conversations. It is an experiments proposal and does not replace -current production behavior. - -For implemented behavior, read source and tests in: - -- `src/telegram/bot-native-commands.ts` -- `src/discord/monitor/native-command.ts` -- `src/auto-reply/reply/commands-core.ts` - -## Problem - -Today we have command-specific checks (for example `/new` and `/reset`) that -need to work inside ACP-bound channels/topics even when allowlists are empty. -This solves immediate UX pain, but command-name-based exceptions do not scale. - -## Long-term shape - -Move command authorization from ad-hoc handler logic to command metadata plus a -shared policy evaluator. - -### 1) Add auth policy metadata to command definitions - -Each command definition should declare an auth policy. Example shape: - -```ts -type CommandAuthPolicy = - | { mode: "owner_or_allowlist" } // default, current strict behavior - | { mode: "bound_acp_or_owner_or_allowlist" } // allow in explicitly bound ACP conversations - | { mode: "owner_only" }; -``` - -`/new` and `/reset` would use `bound_acp_or_owner_or_allowlist`. -Most other commands would remain `owner_or_allowlist`. - -### 2) Share one evaluator across channels - -Introduce one helper that evaluates command auth using: - -- command policy metadata -- sender authorization state -- resolved conversation binding state - -Both Telegram and Discord native handlers should call the same helper to avoid -behavior drift. - -### 3) Use binding-match as the bypass boundary - -When policy allows bound ACP bypass, authorize only if a configured binding -match was resolved for the current conversation (not just because current -session key looks ACP-like). - -This keeps the boundary explicit and minimizes accidental widening. - -## Why this is better - -- Scales to future commands without adding more command-name conditionals. -- Keeps behavior consistent across channels. -- Preserves current security model by requiring explicit binding match. -- Keeps allowlists optional hardening instead of a universal requirement. - -## Rollout plan (future) - -1. Add command auth policy field to command registry types and command data. -2. Implement shared evaluator and migrate Telegram + Discord native handlers. -3. Move `/new` and `/reset` to metadata-driven policy. -4. Add tests per policy mode and channel surface. - -## Non-goals - -- This proposal does not change ACP session lifecycle behavior. -- This proposal does not require allowlists for all ACP-bound commands. -- This proposal does not change existing route binding semantics. - -## Note - -This proposal is intentionally additive and does not delete or replace existing -experiments documents. diff --git a/docs/experiments/proposals/model-config.md b/docs/experiments/proposals/model-config.md deleted file mode 100644 index 6a0ef6524b0..00000000000 --- a/docs/experiments/proposals/model-config.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -summary: "Exploration: model config, auth profiles, and fallback behavior" -read_when: - - Exploring future model selection + auth profile ideas -title: "Model Config Exploration" ---- - -# Model Config (Exploration) - -This document captures **ideas** for future model configuration. It is not a -shipping spec. For current behavior, see: - -- [Models](/concepts/models) -- [Model failover](/concepts/model-failover) -- [OAuth + profiles](/concepts/oauth) - -## Motivation - -Operators want: - -- Multiple auth profiles per provider (personal vs work). -- Simple `/model` selection with predictable fallbacks. -- Clear separation between text models and image-capable models. - -## Possible direction (high level) - -- Keep model selection simple: `provider/model` with optional aliases. -- Let providers have multiple auth profiles, with an explicit order. -- Use a global fallback list so all sessions fail over consistently. -- Only override image routing when explicitly configured. - -## Open questions - -- Should profile rotation be per-provider or per-model? -- How should the UI surface profile selection for a session? -- What is the safest migration path from legacy config keys? diff --git a/docs/experiments/research/memory.md b/docs/experiments/research/memory.md deleted file mode 100644 index 99135e78be9..00000000000 --- a/docs/experiments/research/memory.md +++ /dev/null @@ -1,228 +0,0 @@ ---- -summary: "Research notes: offline memory system for Clawd workspaces (Markdown source-of-truth + derived index)" -read_when: - - Designing workspace memory (~/.openclaw/workspace) beyond daily Markdown logs - - Deciding: standalone CLI vs deep OpenClaw integration - - Adding offline recall + reflection (retain/recall/reflect) -title: "Workspace Memory Research" ---- - -# Workspace Memory v2 (offline): research notes - -Target: Clawd-style workspace (`agents.defaults.workspace`, default `~/.openclaw/workspace`) where “memory” is stored as one Markdown file per day (`memory/YYYY-MM-DD.md`) plus a small set of stable files (e.g. `memory.md`, `SOUL.md`). - -This doc proposes an **offline-first** memory architecture that keeps Markdown as the canonical, reviewable source of truth, but adds **structured recall** (search, entity summaries, confidence updates) via a derived index. - -## Why change? - -The current setup (one file per day) is excellent for: - -- “append-only” journaling -- human editing -- git-backed durability + auditability -- low-friction capture (“just write it down”) - -It’s weak for: - -- high-recall retrieval (“what did we decide about X?”, “last time we tried Y?”) -- entity-centric answers (“tell me about Alice / The Castle / warelay”) without rereading many files -- opinion/preference stability (and evidence when it changes) -- time constraints (“what was true during Nov 2025?”) and conflict resolution - -## Design goals - -- **Offline**: works without network; can run on laptop/Castle; no cloud dependency. -- **Explainable**: retrieved items should be attributable (file + location) and separable from inference. -- **Low ceremony**: daily logging stays Markdown, no heavy schema work. -- **Incremental**: v1 is useful with FTS only; semantic/vector and graphs are optional upgrades. -- **Agent-friendly**: makes “recall within token budgets” easy (return small bundles of facts). - -## North star model (Hindsight × Letta) - -Two pieces to blend: - -1. **Letta/MemGPT-style control loop** - -- keep a small “core” always in context (persona + key user facts) -- everything else is out-of-context and retrieved via tools -- memory writes are explicit tool calls (append/replace/insert), persisted, then re-injected next turn - -2. **Hindsight-style memory substrate** - -- separate what’s observed vs what’s believed vs what’s summarized -- support retain/recall/reflect -- confidence-bearing opinions that can evolve with evidence -- entity-aware retrieval + temporal queries (even without full knowledge graphs) - -## Proposed architecture (Markdown source-of-truth + derived index) - -### Canonical store (git-friendly) - -Keep `~/.openclaw/workspace` as canonical human-readable memory. - -Suggested workspace layout: - -``` -~/.openclaw/workspace/ - memory.md # small: durable facts + preferences (core-ish) - memory/ - YYYY-MM-DD.md # daily log (append; narrative) - bank/ # “typed” memory pages (stable, reviewable) - world.md # objective facts about the world - experience.md # what the agent did (first-person) - opinions.md # subjective prefs/judgments + confidence + evidence pointers - entities/ - Peter.md - The-Castle.md - warelay.md - ... -``` - -Notes: - -- **Daily log stays daily log**. No need to turn it into JSON. -- The `bank/` files are **curated**, produced by reflection jobs, and can still be edited by hand. -- `memory.md` remains “small + core-ish”: the things you want Clawd to see every session. - -### Derived store (machine recall) - -Add a derived index under the workspace (not necessarily git tracked): - -``` -~/.openclaw/workspace/.memory/index.sqlite -``` - -Back it with: - -- SQLite schema for facts + entity links + opinion metadata -- SQLite **FTS5** for lexical recall (fast, tiny, offline) -- optional embeddings table for semantic recall (still offline) - -The index is always **rebuildable from Markdown**. - -## Retain / Recall / Reflect (operational loop) - -### Retain: normalize daily logs into “facts” - -Hindsight’s key insight that matters here: store **narrative, self-contained facts**, not tiny snippets. - -Practical rule for `memory/YYYY-MM-DD.md`: - -- at end of day (or during), add a `## Retain` section with 2–5 bullets that are: - - narrative (cross-turn context preserved) - - self-contained (standalone makes sense later) - - tagged with type + entity mentions - -Example: - -``` -## Retain -- W @Peter: Currently in Marrakech (Nov 27–Dec 1, 2025) for Andy’s birthday. -- B @warelay: I fixed the Baileys WS crash by wrapping connection.update handlers in try/catch (see memory/2025-11-27.md). -- O(c=0.95) @Peter: Prefers concise replies (<1500 chars) on WhatsApp; long content goes into files. -``` - -Minimal parsing: - -- Type prefix: `W` (world), `B` (experience/biographical), `O` (opinion), `S` (observation/summary; usually generated) -- Entities: `@Peter`, `@warelay`, etc (slugs map to `bank/entities/*.md`) -- Opinion confidence: `O(c=0.0..1.0)` optional - -If you don’t want authors to think about it: the reflect job can infer these bullets from the rest of the log, but having an explicit `## Retain` section is the easiest “quality lever”. - -### Recall: queries over the derived index - -Recall should support: - -- **lexical**: “find exact terms / names / commands” (FTS5) -- **entity**: “tell me about X” (entity pages + entity-linked facts) -- **temporal**: “what happened around Nov 27” / “since last week” -- **opinion**: “what does Peter prefer?” (with confidence + evidence) - -Return format should be agent-friendly and cite sources: - -- `kind` (`world|experience|opinion|observation`) -- `timestamp` (source day, or extracted time range if present) -- `entities` (`["Peter","warelay"]`) -- `content` (the narrative fact) -- `source` (`memory/2025-11-27.md#L12` etc) - -### Reflect: produce stable pages + update beliefs - -Reflection is a scheduled job (daily or heartbeat `ultrathink`) that: - -- updates `bank/entities/*.md` from recent facts (entity summaries) -- updates `bank/opinions.md` confidence based on reinforcement/contradiction -- optionally proposes edits to `memory.md` (“core-ish” durable facts) - -Opinion evolution (simple, explainable): - -- each opinion has: - - statement - - confidence `c ∈ [0,1]` - - last_updated - - evidence links (supporting + contradicting fact IDs) -- when new facts arrive: - - find candidate opinions by entity overlap + similarity (FTS first, embeddings later) - - update confidence by small deltas; big jumps require strong contradiction + repeated evidence - -## CLI integration: standalone vs deep integration - -Recommendation: **deep integration in OpenClaw**, but keep a separable core library. - -### Why integrate into OpenClaw? - -- OpenClaw already knows: - - the workspace path (`agents.defaults.workspace`) - - the session model + heartbeats - - logging + troubleshooting patterns -- You want the agent itself to call the tools: - - `openclaw memory recall "…" --k 25 --since 30d` - - `openclaw memory reflect --since 7d` - -### Why still split a library? - -- keep memory logic testable without gateway/runtime -- reuse from other contexts (local scripts, future desktop app, etc.) - -Shape: -The memory tooling is intended to be a small CLI + library layer, but this is exploratory only. - -## “S-Collide” / SuCo: when to use it (research) - -If “S-Collide” refers to **SuCo (Subspace Collision)**: it’s an ANN retrieval approach that targets strong recall/latency tradeoffs by using learned/structured collisions in subspaces (paper: arXiv 2411.14754, 2024). - -Pragmatic take for `~/.openclaw/workspace`: - -- **don’t start** with SuCo. -- start with SQLite FTS + (optional) simple embeddings; you’ll get most UX wins immediately. -- consider SuCo/HNSW/ScaNN-class solutions only once: - - corpus is big (tens/hundreds of thousands of chunks) - - brute-force embedding search becomes too slow - - recall quality is meaningfully bottlenecked by lexical search - -Offline-friendly alternatives (in increasing complexity): - -- SQLite FTS5 + metadata filters (zero ML) -- Embeddings + brute force (works surprisingly far if chunk count is low) -- HNSW index (common, robust; needs a library binding) -- SuCo (research-grade; attractive if there’s a solid implementation you can embed) - -Open question: - -- what’s the **best** offline embedding model for “personal assistant memory” on your machines (laptop + desktop)? - - if you already have Ollama: embed with a local model; otherwise ship a small embedding model in the toolchain. - -## Smallest useful pilot - -If you want a minimal, still-useful version: - -- Add `bank/` entity pages and a `## Retain` section in daily logs. -- Use SQLite FTS for recall with citations (path + line numbers). -- Add embeddings only if recall quality or scale demands it. - -## References - -- Letta / MemGPT concepts: “core memory blocks” + “archival memory” + tool-driven self-editing memory. -- Hindsight Technical Report: “retain / recall / reflect”, four-network memory, narrative fact extraction, opinion confidence evolution. -- SuCo: arXiv 2411.14754 (2024): “Subspace Collision” approximate nearest neighbor retrieval. diff --git a/docs/gateway/authentication.md b/docs/gateway/authentication.md index 28314dd85a3..895124bd8c3 100644 --- a/docs/gateway/authentication.md +++ b/docs/gateway/authentication.md @@ -49,7 +49,7 @@ openclaw models status openclaw doctor ``` -If you’d rather not manage env vars yourself, the onboarding wizard can store +If you’d rather not manage env vars yourself, onboarding can store API keys for daemon use: `openclaw onboard`. See [Help](/help) for details on env inheritance (`env.shellEnv`, @@ -159,7 +159,7 @@ Use `--agent ` to target a specific agent; omit it to use the configured def ## Troubleshooting -### “No credentials found” +### "No credentials found" If the Anthropic token profile is missing, run `claude setup-token` on the **gateway host**, then re-check: diff --git a/docs/gateway/bonjour.md b/docs/gateway/bonjour.md index 03643717d55..16aa5c68d2b 100644 --- a/docs/gateway/bonjour.md +++ b/docs/gateway/bonjour.md @@ -12,7 +12,7 @@ OpenClaw uses Bonjour (mDNS / DNS‑SD) as a **LAN‑only convenience** to disco an active Gateway (WebSocket endpoint). It is best‑effort and does **not** replace SSH or Tailnet-based connectivity. -## Wide‑area Bonjour (Unicast DNS‑SD) over Tailscale +## Wide-area Bonjour (Unicast DNS-SD) over Tailscale If the node and gateway are on different networks, multicast mDNS won’t cross the boundary. You can keep the same discovery UX by switching to **unicast DNS‑SD** @@ -38,7 +38,7 @@ iOS/Android nodes browse both `local.` and your configured wide‑area domain. } ``` -### One‑time DNS server setup (gateway host) +### One-time DNS server setup (gateway host) ```bash openclaw dns setup --apply @@ -84,7 +84,7 @@ Only the Gateway advertises `_openclaw-gw._tcp`. - `_openclaw-gw._tcp` — gateway transport beacon (used by macOS/iOS/Android nodes). -## TXT keys (non‑secret hints) +## TXT keys (non-secret hints) The Gateway advertises small non‑secret hints to make UI flows convenient: diff --git a/docs/gateway/configuration-examples.md b/docs/gateway/configuration-examples.md index 9767f2db674..5627f93395d 100644 --- a/docs/gateway/configuration-examples.md +++ b/docs/gateway/configuration-examples.md @@ -434,7 +434,7 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number. nodeManager: "npm", }, entries: { - "nano-banana-pro": { + "image-lab": { enabled: true, apiKey: "GEMINI_KEY_HERE", env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" }, diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md index 1e48f69d6f8..49c743db623 100644 --- a/docs/gateway/configuration-reference.md +++ b/docs/gateway/configuration-reference.md @@ -655,12 +655,12 @@ See the full channel index: [Channels](/channels). ### Group chat mention gating -Group messages default to **require mention** (metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, Google Chat, and iMessage group chats. +Group messages default to **require mention** (metadata mention or safe regex patterns). Applies to WhatsApp, Telegram, Discord, Google Chat, and iMessage group chats. **Mention types:** - **Metadata mentions**: Native platform @-mentions. Ignored in WhatsApp self-chat mode. -- **Text patterns**: Regex patterns in `agents.list[].groupChat.mentionPatterns`. Always checked. +- **Text patterns**: Safe regex patterns in `agents.list[].groupChat.mentionPatterns`. Invalid patterns and unsafe nested repetition are ignored. - Mention gating is enforced only when detection is possible (native mentions or at least one pattern). ```json5 @@ -875,6 +875,10 @@ Time format in system prompt. Default: `auto` (OS preference). primary: "openrouter/qwen/qwen-2.5-vl-72b-instruct:free", fallbacks: ["openrouter/google/gemini-2.0-flash-vision:free"], }, + imageGenerationModel: { + primary: "openai/gpt-image-1", + fallbacks: ["google/gemini-3.1-flash-image-preview"], + }, pdfModel: { primary: "anthropic/claude-opus-4-6", fallbacks: ["openai/gpt-5-mini"], @@ -899,6 +903,11 @@ Time format in system prompt. Default: `auto` (OS preference). - `imageModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`). - Used by the `image` tool path as its vision-model config. - Also used as fallback routing when the selected/default model cannot accept image input. +- `imageGenerationModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`). + - Used by the shared image-generation capability and any future tool/plugin surface that generates images. + - Typical values: `google/gemini-3-pro-image-preview` for the native Nano Banana-style flow, `fal/fal-ai/flux/dev` for fal, or `openai/gpt-image-1` for OpenAI Images. + - If omitted, `image_generate` can still infer a best-effort provider default from compatible auth-backed image-generation providers. + - Typical values: `google/gemini-3-pro-image-preview`, `fal/fal-ai/flux/dev`, `openai/gpt-image-1`. - `pdfModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`). - Used by the `pdf` tool for model routing. - If omitted, the PDF tool falls back to `imageModel`, then to best-effort provider defaults. @@ -975,6 +984,7 @@ Periodic heartbeat runs. model: "openai/gpt-5.2-mini", includeReasoning: false, lightContext: false, // default: false; true keeps only HEARTBEAT.md from workspace bootstrap files + isolatedSession: false, // default: false; true runs each heartbeat in a fresh session (no conversation history) session: "main", to: "+15555550123", directPolicy: "allow", // allow (default) | block @@ -992,6 +1002,7 @@ Periodic heartbeat runs. - `suppressToolErrorWarnings`: when true, suppresses tool error warning payloads during heartbeat runs. - `directPolicy`: direct/DM delivery policy. `allow` (default) permits direct-target delivery. `block` suppresses direct-target delivery and emits `reason=dm-blocked`. - `lightContext`: when true, heartbeat runs use lightweight bootstrap context and keep only `HEARTBEAT.md` from workspace bootstrap files. +- `isolatedSession`: when true, each heartbeat runs in a fresh session with no prior conversation history. Same isolation pattern as cron `sessionTarget: "isolated"`. Reduces per-heartbeat token cost from ~100K to ~2-5K tokens. - Per-agent: set `agents.list[].heartbeat`. When any agent defines `heartbeat`, **only those agents** run heartbeats. - Heartbeats run full agent turns — shorter intervals burn more tokens. @@ -1003,6 +1014,7 @@ Periodic heartbeat runs. defaults: { compaction: { mode: "safeguard", // default | safeguard + timeoutSeconds: 900, reserveTokensFloor: 24000, identifierPolicy: "strict", // strict | off | custom identifierInstructions: "Preserve deployment IDs, ticket IDs, and host:port pairs exactly.", // used when identifierPolicy=custom @@ -1021,6 +1033,7 @@ Periodic heartbeat runs. ``` - `mode`: `default` or `safeguard` (chunked summarization for long histories). See [Compaction](/concepts/compaction). +- `timeoutSeconds`: maximum seconds allowed for a single compaction operation before OpenClaw aborts it. Default: `900`. - `identifierPolicy`: `strict` (default), `off`, or `custom`. `strict` prepends built-in opaque identifier retention guidance during compaction summarization. - `identifierInstructions`: optional custom identifier-preservation text used when `identifierPolicy=custom`. - `postCompactionSections`: optional AGENTS.md H2/H3 section names to re-inject after compaction. Defaults to `["Session Startup", "Red Lines"]`; set `[]` to disable reinjection. When unset or explicitly set to that default pair, older `Every Session`/`Safety` headings are also accepted as a legacy fallback. @@ -1113,7 +1126,7 @@ See [Typing Indicators](/concepts/typing-indicators). ### `agents.defaults.sandbox` -Optional **Docker sandboxing** for the embedded agent. See [Sandboxing](/gateway/sandboxing) for the full guide. +Optional sandboxing for the embedded agent. See [Sandboxing](/gateway/sandboxing) for the full guide. ```json5 { @@ -1121,6 +1134,7 @@ Optional **Docker sandboxing** for the embedded agent. See [Sandboxing](/gateway defaults: { sandbox: { mode: "non-main", // off | non-main | all + backend: "docker", // docker | ssh | openshell scope: "agent", // session | agent | shared workspaceAccess: "none", // none | ro | rw workspaceRoot: "~/.openclaw/sandboxes", @@ -1149,6 +1163,20 @@ Optional **Docker sandboxing** for the embedded agent. See [Sandboxing](/gateway extraHosts: ["internal.service:10.0.0.5"], binds: ["/home/user/source:/source:rw"], }, + ssh: { + target: "user@gateway-host:22", + command: "ssh", + workspaceRoot: "/tmp/openclaw-sandboxes", + strictHostKeyChecking: true, + updateHostKeys: true, + identityFile: "~/.ssh/id_ed25519", + certificateFile: "~/.ssh/id_ed25519-cert.pub", + knownHostsFile: "~/.ssh/known_hosts", + // SecretRefs / inline contents also supported: + // identityData: { source: "env", provider: "default", id: "SSH_IDENTITY" }, + // certificateData: { source: "env", provider: "default", id: "SSH_CERTIFICATE" }, + // knownHostsData: { source: "env", provider: "default", id: "SSH_KNOWN_HOSTS" }, + }, browser: { enabled: false, image: "openclaw-sandbox-browser:bookworm-slim", @@ -1195,6 +1223,39 @@ Optional **Docker sandboxing** for the embedded agent. See [Sandboxing](/gateway +**Backend:** + +- `docker`: local Docker runtime (default) +- `ssh`: generic SSH-backed remote runtime +- `openshell`: OpenShell runtime + +When `backend: "openshell"` is selected, runtime-specific settings move to +`plugins.entries.openshell.config`. + +**SSH backend config:** + +- `target`: SSH target in `user@host[:port]` form +- `command`: SSH client command (default: `ssh`) +- `workspaceRoot`: absolute remote root used for per-scope workspaces +- `identityFile` / `certificateFile` / `knownHostsFile`: existing local files passed to OpenSSH +- `identityData` / `certificateData` / `knownHostsData`: inline contents or SecretRefs that OpenClaw materializes into temp files at runtime +- `strictHostKeyChecking` / `updateHostKeys`: OpenSSH host-key policy knobs + +**SSH auth precedence:** + +- `identityData` wins over `identityFile` +- `certificateData` wins over `certificateFile` +- `knownHostsData` wins over `knownHostsFile` +- SecretRef-backed `*Data` values are resolved from the active secrets runtime snapshot before the sandbox session starts + +**SSH backend behavior:** + +- seeds the remote workspace once after create or recreate +- then keeps the remote SSH workspace canonical +- routes `exec`, file tools, and media paths over SSH +- does not sync remote changes back to the host automatically +- does not support sandbox browser containers + **Workspace access:** - `none`: per-scope sandbox workspace under `~/.openclaw/sandboxes` @@ -1207,6 +1268,40 @@ Optional **Docker sandboxing** for the embedded agent. See [Sandboxing](/gateway - `agent`: one container + workspace per agent (default) - `shared`: shared container and workspace (no cross-session isolation) +**OpenShell plugin config:** + +```json5 +{ + plugins: { + entries: { + openshell: { + enabled: true, + config: { + mode: "mirror", // mirror | remote + from: "openclaw", + remoteWorkspaceDir: "/sandbox", + remoteAgentWorkspaceDir: "/agent", + gateway: "lab", // optional + gatewayEndpoint: "https://lab.example", // optional + policy: "strict", // optional OpenShell policy id + providers: ["openai"], // optional + autoProviders: true, + timeoutSeconds: 120, + }, + }, + }, + }, +} +``` + +**OpenShell mode:** + +- `mirror`: seed remote from local before exec, sync back after exec; local workspace stays canonical +- `remote`: seed remote once when the sandbox is created, then keep the remote workspace canonical + +In `remote` mode, host-local edits made outside OpenClaw are not synced into the sandbox automatically after the seed step. +Transport is SSH into the OpenShell sandbox, but the plugin owns sandbox lifecycle and optional mirror sync. + **`setupCommand`** runs once after container creation (via `sh -lc`). Needs network egress, writable root, root user. **Containers default to `network: "none"`** — set to `"bridge"` (or a custom bridge network) if the agent needs outbound access. @@ -1256,6 +1351,8 @@ noVNC observer access uses VNC auth by default and OpenClaw emits a short-lived +Browser sandboxing and `sandbox.docker.binds` are currently Docker-only. + Build images: ```bash @@ -2014,9 +2111,11 @@ OpenClaw uses the pi-coding-agent model catalog. Add custom providers via `model - Non-empty agent `models.json` `baseUrl` values win. - Non-empty agent `apiKey` values win only when that provider is not SecretRef-managed in current config/auth-profile context. - SecretRef-managed provider `apiKey` values are refreshed from source markers (`ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs) instead of persisting resolved secrets. + - SecretRef-managed provider header values are refreshed from source markers (`secretref-env:ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs). - Empty or missing agent `apiKey`/`baseUrl` fall back to `models.providers` in config. - Matching model `contextWindow`/`maxTokens` use the higher value between explicit config and implicit catalog values. - Use `models.mode: "replace"` when you want config to fully rewrite `models.json`. + - Marker persistence is source-authoritative: markers are written from the active source config snapshot (pre-resolution), not from resolved runtime secret values. ### Provider field details @@ -2196,7 +2295,7 @@ Anthropic-compatible, built-in provider. Shortcut: `openclaw onboard --auth-choi { id: "hf:MiniMaxAI/MiniMax-M2.5", name: "MiniMax M2.5", - reasoning: false, + reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 192000, @@ -2236,7 +2335,7 @@ Base URL should omit `/v1` (Anthropic client appends it). Shortcut: `openclaw on { id: "MiniMax-M2.5", name: "MiniMax M2.5", - reasoning: false, + reasoning: true, input: ["text"], cost: { input: 15, output: 60, cacheRead: 2, cacheWrite: 10 }, contextWindow: 200000, @@ -2275,7 +2374,7 @@ See [Local Models](/gateway/local-models). TL;DR: run MiniMax M2.5 via LM Studio nodeManager: "npm", // npm | pnpm | yarn }, entries: { - "nano-banana-pro": { + "image-lab": { apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" }, }, @@ -2317,12 +2416,16 @@ See [Local Models](/gateway/local-models). TL;DR: run MiniMax M2.5 via LM Studio ``` - Loaded from `~/.openclaw/extensions`, `/.openclaw/extensions`, plus `plugins.load.paths`. +- Discovery accepts native OpenClaw plugins plus compatible Codex bundles and Claude bundles, including manifestless Claude default-layout bundles. - **Config changes require a gateway restart.** - `allow`: optional allowlist (only listed plugins load). `deny` wins. - `plugins.entries..apiKey`: plugin-level API key convenience field (when supported by the plugin). - `plugins.entries..env`: plugin-scoped env var map. -- `plugins.entries..hooks.allowPromptInjection`: when `false`, core blocks `before_prompt_build` and ignores prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride`. -- `plugins.entries..config`: plugin-defined config object (validated by plugin schema). +- `plugins.entries..hooks.allowPromptInjection`: when `false`, core blocks `before_prompt_build` and ignores prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride`. Applies to native plugin hooks and supported bundle-provided hook directories. +- `plugins.entries..subagent.allowModelOverride`: explicitly trust this plugin to request per-run `provider` and `model` overrides for background subagent runs. +- `plugins.entries..subagent.allowedModels`: optional allowlist of canonical `provider/model` targets for trusted subagent overrides. Use `"*"` only when you intentionally want to allow any model. +- `plugins.entries..config`: plugin-defined config object (validated by native OpenClaw plugin schema when available). +- Enabled Claude bundle plugins can also contribute embedded Pi defaults from `settings.json`; OpenClaw applies those as sanitized agent settings, not as raw OpenClaw config patches. - `plugins.slots.memory`: pick the active memory plugin id, or `"none"` to disable memory plugins. - `plugins.slots.contextEngine`: pick the active context engine plugin id; defaults to `"legacy"` unless you install and select another engine. - `plugins.installs`: CLI-managed install metadata used by `openclaw plugins update`. @@ -2340,7 +2443,7 @@ See [Plugins](/tools/plugin). browser: { enabled: true, evaluateEnabled: true, - defaultProfile: "chrome", + defaultProfile: "user", ssrfPolicy: { dangerouslyAllowPrivateNetwork: true, // default trusted-network mode // allowPrivateNetwork: true, // legacy alias @@ -2350,13 +2453,19 @@ See [Plugins](/tools/plugin). profiles: { openclaw: { cdpPort: 18800, color: "#FF4500" }, work: { cdpPort: 18801, color: "#0066CC" }, + user: { driver: "existing-session", attachOnly: true, color: "#00AA00" }, + brave: { + driver: "existing-session", + attachOnly: true, + userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser", + color: "#FB542B", + }, remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" }, }, color: "#FF4500", // headless: false, // noSandbox: false, // extraArgs: [], - // relayBindHost: "0.0.0.0", // only when the extension relay must be reachable across namespaces (for example WSL2) // executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", // attachOnly: false, }, @@ -2366,14 +2475,17 @@ See [Plugins](/tools/plugin). - `evaluateEnabled: false` disables `act:evaluate` and `wait --fn`. - `ssrfPolicy.dangerouslyAllowPrivateNetwork` defaults to `true` when unset (trusted-network model). - Set `ssrfPolicy.dangerouslyAllowPrivateNetwork: false` for strict public-only browser navigation. +- In strict mode, remote CDP profile endpoints (`profiles.*.cdpUrl`) are subject to the same private-network blocking during reachability/discovery checks. - `ssrfPolicy.allowPrivateNetwork` remains supported as a legacy alias. - In strict mode, use `ssrfPolicy.hostnameAllowlist` and `ssrfPolicy.allowedHostnames` for explicit exceptions. - Remote profiles are attach-only (start/stop/reset disabled). +- `existing-session` profiles are host-only and use Chrome MCP instead of CDP. +- `existing-session` profiles can set `userDataDir` to target a specific + Chromium-based browser profile such as Brave or Edge. - Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary. - Control service: loopback only (port derived from `gateway.port`, default `18791`). - `extraArgs` appends extra launch flags to local Chromium startup (for example `--disable-gpu`, window sizing, or debug flags). -- `relayBindHost` changes where the Chrome extension relay listens. Leave unset for loopback-only access; set an explicit non-loopback bind address such as `0.0.0.0` only when the relay must cross a namespace boundary (for example WSL2) and the host network is already trusted. --- @@ -2445,6 +2557,14 @@ See [Plugins](/tools/plugin). // Remove tools from the default HTTP deny list allow: ["gateway"], }, + push: { + apns: { + relay: { + baseUrl: "https://relay.example.com", + timeoutMs: 10000, + }, + }, + }, }, } ``` @@ -2470,6 +2590,16 @@ See [Plugins](/tools/plugin). - `remote.transport`: `ssh` (default) or `direct` (ws/wss). For `direct`, `remote.url` must be `ws://` or `wss://`. - `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1`: client-side break-glass override that allows plaintext `ws://` to trusted private-network IPs; default remains loopback-only for plaintext. - `gateway.remote.token` / `.password` are remote-client credential fields. They do not configure gateway auth by themselves. +- `gateway.push.apns.relay.baseUrl`: base HTTPS URL for the external APNs relay used by official/TestFlight iOS builds after they publish relay-backed registrations to the gateway. This URL must match the relay URL compiled into the iOS build. +- `gateway.push.apns.relay.timeoutMs`: gateway-to-relay send timeout in milliseconds. Defaults to `10000`. +- Relay-backed registrations are delegated to a specific gateway identity. The paired iOS app fetches `gateway.identity.get`, includes that identity in the relay registration, and forwards a registration-scoped send grant to the gateway. Another gateway cannot reuse that stored registration. +- `OPENCLAW_APNS_RELAY_BASE_URL` / `OPENCLAW_APNS_RELAY_TIMEOUT_MS`: temporary env overrides for the relay config above. +- `OPENCLAW_APNS_RELAY_ALLOW_HTTP=true`: development-only escape hatch for loopback HTTP relay URLs. Production relay URLs should stay on HTTPS. +- `gateway.channelHealthCheckMinutes`: channel health-monitor interval in minutes. Set `0` to disable health-monitor restarts globally. Default: `5`. +- `gateway.channelStaleEventThresholdMinutes`: stale-socket threshold in minutes. Keep this greater than or equal to `gateway.channelHealthCheckMinutes`. Default: `30`. +- `gateway.channelMaxRestartsPerHour`: maximum health-monitor restarts per channel/account in a rolling hour. Default: `10`. +- `channels..healthMonitor.enabled`: per-channel opt-out for health-monitor restarts while keeping the global monitor enabled. +- `channels..accounts..healthMonitor.enabled`: per-account override for multi-account channels. When set, it takes precedence over the channel-level override. - Local gateway call paths can use `gateway.remote.*` as fallback only when `gateway.auth.*` is unset. - If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking). - `trustedProxies`: reverse proxy IPs that terminate TLS. Only list proxies you control. @@ -2487,6 +2617,8 @@ See [Plugins](/tools/plugin). - `gateway.http.endpoints.responses.maxUrlParts` - `gateway.http.endpoints.responses.files.urlAllowlist` - `gateway.http.endpoints.responses.images.urlAllowlist` + Empty allowlists are treated as unset; use `gateway.http.endpoints.responses.files.allowUrl=false` + and/or `gateway.http.endpoints.responses.images.allowUrl=false` to disable URL fetching. - Optional response hardening header: - `gateway.http.securityHeaders.strictTransportSecurity` (set only for HTTPS origins you control; see [Trusted Proxy Auth](/gateway/trusted-proxy-auth#tls-termination-and-hsts)) @@ -2831,7 +2963,7 @@ Notes: ## Wizard -Metadata written by CLI wizards (`onboard`, `configure`, `doctor`): +Metadata written by CLI guided setup flows (`onboard`, `configure`, `doctor`): ```json5 { diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index ece612d101d..b8977ca10ac 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -38,7 +38,7 @@ See the [full reference](/gateway/configuration-reference) for every available f ```bash - openclaw onboard # full setup wizard + openclaw onboard # full onboarding flow openclaw configure # config wizard ``` @@ -46,7 +46,7 @@ See the [full reference](/gateway/configuration-reference) for every available f ```bash openclaw config get agents.defaults.workspace openclaw config set agents.defaults.heartbeat.every "2h" - openclaw config unset tools.web.search.apiKey + openclaw config unset plugins.entries.brave.config.webSearch.apiKey ``` @@ -170,11 +170,41 @@ When validation fails: ``` - **Metadata mentions**: native @-mentions (WhatsApp tap-to-mention, Telegram @bot, etc.) - - **Text patterns**: regex patterns in `mentionPatterns` + - **Text patterns**: safe regex patterns in `mentionPatterns` - See [full reference](/gateway/configuration-reference#group-chat-mention-gating) for per-channel overrides and self-chat mode. + + Control how aggressively the gateway restarts channels that look stale: + + ```json5 + { + gateway: { + channelHealthCheckMinutes: 5, + channelStaleEventThresholdMinutes: 30, + channelMaxRestartsPerHour: 10, + }, + channels: { + telegram: { + healthMonitor: { enabled: false }, + accounts: { + alerts: { + healthMonitor: { enabled: true }, + }, + }, + }, + }, + } + ``` + + - Set `gateway.channelHealthCheckMinutes: 0` to disable health-monitor restarts globally. + - `channelStaleEventThresholdMinutes` should be greater than or equal to the check interval. + - Use `channels..healthMonitor.enabled` or `channels..accounts..healthMonitor.enabled` to disable auto-restarts for one channel or account without disabling the global monitor. + - See [Health Checks](/gateway/health) for operational debugging and the [full reference](/gateway/configuration-reference#gateway) for all fields. + + + Sessions control conversation continuity and isolation: @@ -225,6 +255,63 @@ When validation fails: + + Relay-backed push is configured in `openclaw.json`. + + Set this in gateway config: + + ```json5 + { + gateway: { + push: { + apns: { + relay: { + baseUrl: "https://relay.example.com", + // Optional. Default: 10000 + timeoutMs: 10000, + }, + }, + }, + }, + } + ``` + + CLI equivalent: + + ```bash + openclaw config set gateway.push.apns.relay.baseUrl https://relay.example.com + ``` + + What this does: + + - Lets the gateway send `push.test`, wake nudges, and reconnect wakes through the external relay. + - Uses a registration-scoped send grant forwarded by the paired iOS app. The gateway does not need a deployment-wide relay token. + - Binds each relay-backed registration to the gateway identity that the iOS app paired with, so another gateway cannot reuse the stored registration. + - Keeps local/manual iOS builds on direct APNs. Relay-backed sends apply only to official distributed builds that registered through the relay. + - Must match the relay base URL baked into the official/TestFlight iOS build, so registration and send traffic reach the same relay deployment. + + End-to-end flow: + + 1. Install an official/TestFlight iOS build that was compiled with the same relay base URL. + 2. Configure `gateway.push.apns.relay.baseUrl` on the gateway. + 3. Pair the iOS app to the gateway and let both node and operator sessions connect. + 4. The iOS app fetches the gateway identity, registers with the relay using App Attest plus the app receipt, and then publishes the relay-backed `push.apns.register` payload to the paired gateway. + 5. The gateway stores the relay handle and send grant, then uses them for `push.test`, wake nudges, and reconnect wakes. + + Operational notes: + + - If you switch the iOS app to a different gateway, reconnect the app so it can publish a new relay registration bound to that gateway. + - If you ship a new iOS build that points at a different relay deployment, the app refreshes its cached relay registration instead of reusing the old relay origin. + + Compatibility note: + + - `OPENCLAW_APNS_RELAY_BASE_URL` and `OPENCLAW_APNS_RELAY_TIMEOUT_MS` still work as temporary env overrides. + - `OPENCLAW_APNS_RELAY_ALLOW_HTTP=true` remains a loopback-only development escape hatch; do not persist HTTP relay URLs in config. + + See [iOS App](/platforms/ios#relay-backed-push-for-official-builds) for the end-to-end flow and [Authentication and trust flow](/platforms/ios#authentication-and-trust-flow) for the relay security model. + + + ```json5 { @@ -415,7 +502,7 @@ Control-plane write RPCs (`config.apply`, `config.patch`, `update.run`) are rate openclaw gateway call config.apply --params '{ "raw": "{ agents: { defaults: { workspace: \"~/.openclaw/workspace\" } } }", "baseHash": "", - "sessionKey": "agent:main:whatsapp:dm:+15555550123" + "sessionKey": "agent:main:whatsapp:direct:+15555550123" }' ``` @@ -510,11 +597,11 @@ Rules: }, skills: { entries: { - "nano-banana-pro": { + "image-lab": { apiKey: { source: "file", provider: "filemain", - id: "/skills/entries/nano-banana-pro/apiKey", + id: "/skills/entries/image-lab/apiKey", }, }, }, diff --git a/docs/gateway/discovery.md b/docs/gateway/discovery.md index af1144125d3..cfdc3afdfe0 100644 --- a/docs/gateway/discovery.md +++ b/docs/gateway/discovery.md @@ -29,7 +29,7 @@ Protocol details: - [Gateway protocol](/gateway/protocol) - [Bridge protocol (legacy)](/gateway/bridge-protocol) -## Why we keep both “direct” and SSH +## Why we keep both "direct" and SSH - **Direct WS** is the best UX on the same network and within a tailnet: - auto-discovery on LAN via Bonjour diff --git a/docs/gateway/doctor.md b/docs/gateway/doctor.md index 95027906750..78430476051 100644 --- a/docs/gateway/doctor.md +++ b/docs/gateway/doctor.md @@ -63,6 +63,7 @@ cat ~/.openclaw/openclaw.json - Health check + restart prompt. - 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`). - Legacy on-disk state migration (sessions/agent dir/WhatsApp auth). - Legacy cron store migration (`jobId`, `schedule.cron`, top-level delivery/payload fields, payload `provider`, simple `notify: true` webhook fallback jobs). @@ -128,6 +129,8 @@ Current migrations: - `agent.model`/`allowedModels`/`modelAliases`/`modelFallbacks`/`imageModelFallbacks` → `agents.defaults.models` + `agents.defaults.model.primary/fallbacks` + `agents.defaults.imageModel.primary/fallbacks` - `browser.ssrfPolicy.allowPrivateNetwork` → `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` +- `browser.profiles.*.driver: "extension"` → `"existing-session"` +- remove `browser.relayBindHost` (legacy extension relay setting) Doctor warnings also include account-default guidance for multi-account channels: @@ -141,6 +144,35 @@ manually, it overrides the built-in OpenCode catalog from `@mariozechner/pi-ai`. That can force models onto the wrong API or zero out costs. Doctor warns so you can remove the override and restore per-model API routing + costs. +### 2c) Browser migration and Chrome MCP readiness + +If your browser config still points at the removed Chrome extension path, doctor +normalizes it to the current host-local Chrome MCP attach model: + +- `browser.profiles.*.driver: "extension"` becomes `"existing-session"` +- `browser.relayBindHost` is removed + +Doctor also audits the host-local Chrome MCP path when you use `defaultProfile: +"user"` or a configured `existing-session` profile: + +- checks whether Google Chrome is installed on the same host for default + auto-connect profiles +- checks the detected Chrome version and warns when it is below Chrome 144 +- reminds you to enable remote debugging in the browser inspect page (for + example `chrome://inspect/#remote-debugging`, `brave://inspect/#remote-debugging`, + or `edge://inspect/#remote-debugging`) + +Doctor cannot enable the Chrome-side setting for you. Host-local Chrome MCP +still requires: + +- a Chromium-based browser 144+ on the gateway/node host +- the browser running locally +- remote debugging enabled in that browser +- approving the first attach consent prompt in the browser + +This check does **not** apply to Docker, sandbox, remote-browser, or other +headless flows. Those continue to use raw CDP. + ### 3) Legacy state migrations (disk layout) Doctor can migrate older on-disk layouts into the current structure: diff --git a/docs/gateway/health.md b/docs/gateway/health.md index 8a6f270979a..f8bfd6a319d 100644 --- a/docs/gateway/health.md +++ b/docs/gateway/health.md @@ -24,6 +24,15 @@ Short guide to verify channel connectivity without guessing. - Session store: `ls -l ~/.openclaw/agents//sessions/sessions.json` (path can be overridden in config). Count and recent recipients are surfaced via `status`. - Relink flow: `openclaw channels logout && openclaw channels login --verbose` when status codes 409–515 or `loggedOut` appear in logs. (Note: the QR login flow auto-restarts once for status 515 after pairing.) +## Health monitor config + +- `gateway.channelHealthCheckMinutes`: how often the gateway checks channel health. Default: `5`. Set `0` to disable health-monitor restarts globally. +- `gateway.channelStaleEventThresholdMinutes`: how long a connected channel can stay idle before the health monitor treats it as stale and restarts it. Default: `30`. Keep this greater than or equal to `gateway.channelHealthCheckMinutes`. +- `gateway.channelMaxRestartsPerHour`: rolling one-hour cap for health-monitor restarts per channel/account. Default: `10`. +- `channels..healthMonitor.enabled`: disable health-monitor restarts for a specific channel while leaving global monitoring enabled. +- `channels..accounts..healthMonitor.enabled`: multi-account override that wins over the channel-level setting. +- These per-channel overrides apply to the built-in channel monitors that expose them today: Discord, Google Chat, iMessage, Microsoft Teams, Signal, Slack, Telegram, and WhatsApp. + ## When something fails - `logged out` or status 409–515 → relink with `openclaw channels logout` then `openclaw channels login`. diff --git a/docs/gateway/heartbeat.md b/docs/gateway/heartbeat.md index 90c5d9d3c75..e0de2294cfa 100644 --- a/docs/gateway/heartbeat.md +++ b/docs/gateway/heartbeat.md @@ -22,7 +22,8 @@ Troubleshooting: [/automation/troubleshooting](/automation/troubleshooting) 3. Decide where heartbeat messages should go (`target: "none"` is the default; set `target: "last"` to route to the last contact). 4. Optional: enable heartbeat reasoning delivery for transparency. 5. Optional: use lightweight bootstrap context if heartbeat runs only need `HEARTBEAT.md`. -6. Optional: restrict heartbeats to active hours (local time). +6. Optional: enable isolated sessions to avoid sending full conversation history each heartbeat. +7. Optional: restrict heartbeats to active hours (local time). Example config: @@ -35,6 +36,7 @@ Example config: target: "last", // explicit delivery to last contact (default is "none") directPolicy: "allow", // default: allow direct/DM targets; set "block" to suppress lightContext: true, // optional: only inject HEARTBEAT.md from bootstrap files + isolatedSession: true, // optional: fresh session each run (no conversation history) // activeHours: { start: "08:00", end: "24:00" }, // includeReasoning: true, // optional: send separate `Reasoning:` message too }, @@ -91,6 +93,7 @@ and logged; a message that is only `HEARTBEAT_OK` is dropped. model: "anthropic/claude-opus-4-6", includeReasoning: false, // default: false (deliver separate Reasoning: message when available) lightContext: false, // default: false; true keeps only HEARTBEAT.md from workspace bootstrap files + isolatedSession: false, // default: false; true runs each heartbeat in a fresh session (no conversation history) target: "last", // default: none | options: last | none | (core or plugin, e.g. "bluebubbles") to: "+15551234567", // optional channel-specific override accountId: "ops-bot", // optional multi-account channel id @@ -212,6 +215,7 @@ Use `accountId` to target a specific account on multi-account channels like Tele - `model`: optional model override for heartbeat runs (`provider/model`). - `includeReasoning`: when enabled, also deliver the separate `Reasoning:` message when available (same shape as `/reasoning on`). - `lightContext`: when true, heartbeat runs use lightweight bootstrap context and keep only `HEARTBEAT.md` from workspace bootstrap files. +- `isolatedSession`: when true, each heartbeat runs in a fresh session with no prior conversation history. Uses the same isolation pattern as cron `sessionTarget: "isolated"`. Dramatically reduces per-heartbeat token cost. Combine with `lightContext: true` for maximum savings. Delivery routing still uses the main session context. - `session`: optional session key for heartbeat runs. - `main` (default): agent main session. - Explicit session key (copy from `openclaw sessions --json` or the [sessions CLI](/cli/sessions)). @@ -380,6 +384,10 @@ off in group chats. ## Cost awareness -Heartbeats run full agent turns. Shorter intervals burn more tokens. Keep -`HEARTBEAT.md` small and consider a cheaper `model` or `target: "none"` if you -only want internal state updates. +Heartbeats run full agent turns. Shorter intervals burn more tokens. To reduce cost: + +- Use `isolatedSession: true` to avoid sending full conversation history (~100K tokens down to ~2-5K per run). +- Use `lightContext: true` to limit bootstrap files to just `HEARTBEAT.md`. +- Set a cheaper `model` (e.g. `ollama/llama3.2:1b`). +- Keep `HEARTBEAT.md` small. +- Use `target: "none"` if you only want internal state updates. diff --git a/docs/gateway/local-models.md b/docs/gateway/local-models.md index 8a07a827467..4059f988776 100644 --- a/docs/gateway/local-models.md +++ b/docs/gateway/local-models.md @@ -11,6 +11,8 @@ title: "Local Models" Local is doable, but OpenClaw expects large context + strong defenses against prompt injection. Small cards truncate context and leak safety. Aim high: **≥2 maxed-out Mac Studios or equivalent GPU rig (~$30k+)**. A single **24 GB** GPU works only for lighter prompts with higher latency. Use the **largest / full-size model variant you can run**; aggressively quantized or “small” checkpoints raise prompt-injection risk (see [Security](/gateway/security)). +If you want the lowest-friction local setup, start with [Ollama](/providers/ollama) and `openclaw onboard`. This page is the opinionated guide for higher-end local stacks and custom OpenAI-compatible local servers. + ## Recommended: LM Studio + MiniMax M2.5 (Responses API, full-size) Best current local stack. Load MiniMax M2.5 in LM Studio, enable the local server (default `http://127.0.0.1:1234`), and use Responses API to keep reasoning separate from final text. diff --git a/docs/gateway/multiple-gateways.md b/docs/gateway/multiple-gateways.md index d6f35e08a46..6d1cf423b98 100644 --- a/docs/gateway/multiple-gateways.md +++ b/docs/gateway/multiple-gateways.md @@ -70,7 +70,7 @@ openclaw --profile rescue onboard # better choose completely different base port, like 19789, # - rest of the onboarding is the same as normal -# To install the service (if not happened automatically during onboarding) +# To install the service (if not happened automatically during setup) openclaw --profile rescue gateway install ``` diff --git a/docs/gateway/network-model.md b/docs/gateway/network-model.md index b57ff91f143..f5fb9a258ea 100644 --- a/docs/gateway/network-model.md +++ b/docs/gateway/network-model.md @@ -5,6 +5,8 @@ read_when: title: "Network model" --- +# Network Model + Most operations flow through the Gateway (`openclaw gateway`), a single long-running process that owns channel connections and the WebSocket control plane. diff --git a/docs/gateway/openresponses-http-api.md b/docs/gateway/openresponses-http-api.md index bcba166db9d..8305da62ee5 100644 --- a/docs/gateway/openresponses-http-api.md +++ b/docs/gateway/openresponses-http-api.md @@ -18,77 +18,16 @@ This endpoint is **disabled by default**. Enable it in config first. Under the hood, requests are executed as a normal Gateway agent run (same codepath as `openclaw agent`), so routing/permissions/config match your Gateway. -## Authentication +## Authentication, security, and routing -Uses the Gateway auth configuration. Send a bearer token: +Operational behavior matches [OpenAI Chat Completions](/gateway/openai-http-api): -- `Authorization: Bearer ` +- use `Authorization: Bearer ` with the normal Gateway auth config +- treat the endpoint as full operator access for the gateway instance +- select agents with `model: "openclaw:"`, `model: "agent:"`, or `x-openclaw-agent-id` +- use `x-openclaw-session-key` for explicit session routing -Notes: - -- When `gateway.auth.mode="token"`, use `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`). -- When `gateway.auth.mode="password"`, use `gateway.auth.password` (or `OPENCLAW_GATEWAY_PASSWORD`). -- If `gateway.auth.rateLimit` is configured and too many auth failures occur, the endpoint returns `429` with `Retry-After`. - -## Security boundary (important) - -Treat this endpoint as a **full operator-access** surface for the gateway instance. - -- HTTP bearer auth here is not a narrow per-user scope model. -- A valid Gateway token/password for this endpoint should be treated like an owner/operator credential. -- Requests run through the same control-plane agent path as trusted operator actions. -- There is no separate non-owner/per-user tool boundary on this endpoint; once a caller passes Gateway auth here, OpenClaw treats that caller as a trusted operator for this gateway. -- If the target agent policy allows sensitive tools, this endpoint can use them. -- Keep this endpoint on loopback/tailnet/private ingress only; do not expose it directly to the public internet. - -See [Security](/gateway/security) and [Remote access](/gateway/remote). - -## Choosing an agent - -No custom headers required: encode the agent id in the OpenResponses `model` field: - -- `model: "openclaw:"` (example: `"openclaw:main"`, `"openclaw:beta"`) -- `model: "agent:"` (alias) - -Or target a specific OpenClaw agent by header: - -- `x-openclaw-agent-id: ` (default: `main`) - -Advanced: - -- `x-openclaw-session-key: ` to fully control session routing. - -## Enabling the endpoint - -Set `gateway.http.endpoints.responses.enabled` to `true`: - -```json5 -{ - gateway: { - http: { - endpoints: { - responses: { enabled: true }, - }, - }, - }, -} -``` - -## Disabling the endpoint - -Set `gateway.http.endpoints.responses.enabled` to `false`: - -```json5 -{ - gateway: { - http: { - endpoints: { - responses: { enabled: false }, - }, - }, - }, -} -``` +Enable or disable this endpoint with `gateway.http.endpoints.responses.enabled`. ## Session behavior @@ -205,6 +144,8 @@ URL fetch defaults: - Optional hostname allowlists are supported per input type (`files.urlAllowlist`, `images.urlAllowlist`). - Exact host: `"cdn.example.com"` - Wildcard subdomains: `"*.assets.example.com"` (does not match apex) + - Empty or omitted allowlists mean no hostname allowlist restriction. +- To disable URL-based fetches entirely, set `files.allowUrl: false` and/or `images.allowUrl: false`. ## File + image limits (config) diff --git a/docs/gateway/openshell.md b/docs/gateway/openshell.md new file mode 100644 index 00000000000..af9983e1141 --- /dev/null +++ b/docs/gateway/openshell.md @@ -0,0 +1,307 @@ +--- +title: OpenShell +summary: "Use OpenShell as a managed sandbox backend for OpenClaw agents" +read_when: + - You want cloud-managed sandboxes instead of local Docker + - You are setting up the OpenShell plugin + - You need to choose between mirror and remote workspace modes +--- + +# OpenShell + +OpenShell is a managed sandbox backend for OpenClaw. Instead of running Docker +containers locally, OpenClaw delegates sandbox lifecycle to the `openshell` CLI, +which provisions remote environments with SSH-based command execution. + +The OpenShell plugin reuses the same core SSH transport and remote filesystem +bridge as the generic [SSH backend](/gateway/sandboxing#ssh-backend). It adds +OpenShell-specific lifecycle (`sandbox create/get/delete`, `sandbox ssh-config`) +and an optional `mirror` workspace mode. + +## Prerequisites + +- The `openshell` CLI installed and on `PATH` (or set a custom path via + `plugins.entries.openshell.config.command`) +- An OpenShell account with sandbox access +- OpenClaw Gateway running on the host + +## Quick start + +1. Enable the plugin and set the sandbox backend: + +```json5 +{ + agents: { + defaults: { + sandbox: { + mode: "all", + backend: "openshell", + scope: "session", + workspaceAccess: "rw", + }, + }, + }, + plugins: { + entries: { + openshell: { + enabled: true, + config: { + from: "openclaw", + mode: "remote", + }, + }, + }, + }, +} +``` + +2. Restart the Gateway. On the next agent turn, OpenClaw creates an OpenShell + sandbox and routes tool execution through it. + +3. Verify: + +```bash +openclaw sandbox list +openclaw sandbox explain +``` + +## Workspace modes + +This is the most important decision when using OpenShell. + +### `mirror` + +Use `plugins.entries.openshell.config.mode: "mirror"` when you want the **local +workspace to stay canonical**. + +Behavior: + +- Before `exec`, OpenClaw syncs the local workspace into the OpenShell sandbox. +- After `exec`, OpenClaw syncs the remote workspace back to the local workspace. +- File tools still operate through the sandbox bridge, but the local workspace + remains the source of truth between turns. + +Best for: + +- You edit files locally outside OpenClaw and want those changes visible in the + sandbox automatically. +- You want the OpenShell sandbox to behave as much like the Docker backend as + possible. +- You want the host workspace to reflect sandbox writes after each exec turn. + +Tradeoff: extra sync cost before and after each exec. + +### `remote` + +Use `plugins.entries.openshell.config.mode: "remote"` when you want the +**OpenShell workspace to become canonical**. + +Behavior: + +- When the sandbox is first created, OpenClaw seeds the remote workspace from + the local workspace once. +- After that, `exec`, `read`, `write`, `edit`, and `apply_patch` operate + directly against the remote OpenShell workspace. +- OpenClaw does **not** sync remote changes back into the local workspace. +- Prompt-time media reads still work because file and media tools read through + the sandbox bridge. + +Best for: + +- The sandbox should live primarily on the remote side. +- You want lower per-turn sync overhead. +- You do not want host-local edits to silently overwrite remote sandbox state. + +Important: if you edit files on the host outside OpenClaw after the initial seed, +the remote sandbox does **not** see those changes. Use +`openclaw sandbox recreate` to re-seed. + +### Choosing a mode + +| | `mirror` | `remote` | +| ------------------------ | -------------------------- | ------------------------- | +| **Canonical workspace** | Local host | Remote OpenShell | +| **Sync direction** | Bidirectional (each exec) | One-time seed | +| **Per-turn overhead** | Higher (upload + download) | Lower (direct remote ops) | +| **Local edits visible?** | Yes, on next exec | No, until recreate | +| **Best for** | Development workflows | Long-running agents, CI | + +## Configuration reference + +All OpenShell config lives under `plugins.entries.openshell.config`: + +| Key | Type | Default | Description | +| ------------------------- | ------------------------ | ------------- | ----------------------------------------------------- | +| `mode` | `"mirror"` or `"remote"` | `"mirror"` | Workspace sync mode | +| `command` | `string` | `"openshell"` | Path or name of the `openshell` CLI | +| `from` | `string` | `"openclaw"` | Sandbox source for first-time create | +| `gateway` | `string` | — | OpenShell gateway name (`--gateway`) | +| `gatewayEndpoint` | `string` | — | OpenShell gateway endpoint URL (`--gateway-endpoint`) | +| `policy` | `string` | — | OpenShell policy ID for sandbox creation | +| `providers` | `string[]` | `[]` | Provider names to attach when sandbox is created | +| `gpu` | `boolean` | `false` | Request GPU resources | +| `autoProviders` | `boolean` | `true` | Pass `--auto-providers` during sandbox create | +| `remoteWorkspaceDir` | `string` | `"/sandbox"` | Primary writable workspace inside the sandbox | +| `remoteAgentWorkspaceDir` | `string` | `"/agent"` | Agent workspace mount path (for read-only access) | +| `timeoutSeconds` | `number` | `120` | Timeout for `openshell` CLI operations | + +Sandbox-level settings (`mode`, `scope`, `workspaceAccess`) are configured under +`agents.defaults.sandbox` as with any backend. See +[Sandboxing](/gateway/sandboxing) for the full matrix. + +## Examples + +### Minimal remote setup + +```json5 +{ + agents: { + defaults: { + sandbox: { + mode: "all", + backend: "openshell", + }, + }, + }, + plugins: { + entries: { + openshell: { + enabled: true, + config: { + from: "openclaw", + mode: "remote", + }, + }, + }, + }, +} +``` + +### Mirror mode with GPU + +```json5 +{ + agents: { + defaults: { + sandbox: { + mode: "all", + backend: "openshell", + scope: "agent", + workspaceAccess: "rw", + }, + }, + }, + plugins: { + entries: { + openshell: { + enabled: true, + config: { + from: "openclaw", + mode: "mirror", + gpu: true, + providers: ["openai"], + timeoutSeconds: 180, + }, + }, + }, + }, +} +``` + +### Per-agent OpenShell with custom gateway + +```json5 +{ + agents: { + defaults: { + sandbox: { mode: "off" }, + }, + list: [ + { + id: "researcher", + sandbox: { + mode: "all", + backend: "openshell", + scope: "agent", + workspaceAccess: "rw", + }, + }, + ], + }, + plugins: { + entries: { + openshell: { + enabled: true, + config: { + from: "openclaw", + mode: "remote", + gateway: "lab", + gatewayEndpoint: "https://lab.example", + policy: "strict", + }, + }, + }, + }, +} +``` + +## Lifecycle management + +OpenShell sandboxes are managed through the normal sandbox CLI: + +```bash +# List all sandbox runtimes (Docker + OpenShell) +openclaw sandbox list + +# Inspect effective policy +openclaw sandbox explain + +# Recreate (deletes remote workspace, re-seeds on next use) +openclaw sandbox recreate --all +``` + +For `remote` mode, **recreate is especially important**: it deletes the canonical +remote workspace for that scope. The next use seeds a fresh remote workspace from +the local workspace. + +For `mirror` mode, recreate mainly resets the remote execution environment because +the local workspace remains canonical. + +### When to recreate + +Recreate after changing any of these: + +- `agents.defaults.sandbox.backend` +- `plugins.entries.openshell.config.from` +- `plugins.entries.openshell.config.mode` +- `plugins.entries.openshell.config.policy` + +```bash +openclaw sandbox recreate --all +``` + +## Current limitations + +- Sandbox browser is not supported on the OpenShell backend. +- `sandbox.docker.binds` does not apply to OpenShell. +- Docker-specific runtime knobs under `sandbox.docker.*` apply only to the Docker + backend. + +## How it works + +1. OpenClaw calls `openshell sandbox create` (with `--from`, `--gateway`, + `--policy`, `--providers`, `--gpu` flags as configured). +2. OpenClaw calls `openshell sandbox ssh-config ` to get SSH connection + details for the sandbox. +3. Core writes the SSH config to a temp file and opens an SSH session using the + same remote filesystem bridge as the generic SSH backend. +4. In `mirror` mode: sync local to remote before exec, run, sync back after exec. +5. In `remote` mode: seed once on create, then operate directly on the remote + workspace. + +## See also + +- [Sandboxing](/gateway/sandboxing) -- modes, scopes, and backend comparison +- [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) -- debugging blocked tools +- [Multi-Agent Sandbox and Tools](/tools/multi-agent-sandbox-tools) -- per-agent overrides +- [Sandbox CLI](/cli/sandbox) -- `openclaw sandbox` commands diff --git a/docs/gateway/remote.md b/docs/gateway/remote.md index dcbae985b74..a1bc4720ad6 100644 --- a/docs/gateway/remote.md +++ b/docs/gateway/remote.md @@ -126,7 +126,7 @@ WebChat no longer uses a separate HTTP port. The SwiftUI chat UI connects direct - Forward `18789` over SSH (see above), then connect clients to `ws://127.0.0.1:18789`. - On macOS, prefer the app’s “Remote over SSH” mode, which manages the tunnel automatically. -## macOS app “Remote over SSH” +## macOS app "Remote over SSH" The macOS menu bar app can drive the same setup end-to-end (remote status checks, WebChat, and Voice Wake forwarding). diff --git a/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md b/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md index 9e7fecfd949..515acb1d0e9 100644 --- a/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md +++ b/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md @@ -95,7 +95,7 @@ Available groups: - `group:nodes`: `nodes` - `group:openclaw`: all built-in OpenClaw tools (excludes provider plugins) -## Elevated: exec-only “run on host” +## Elevated: exec-only "run on host" Elevated does **not** grant extra tools; it only affects `exec`. @@ -112,9 +112,9 @@ Gates: See [Elevated Mode](/tools/elevated). -## Common “sandbox jail” fixes +## Common "sandbox jail" fixes -### “Tool X blocked by sandbox tool policy” +### "Tool X blocked by sandbox tool policy" Fix-it keys (pick one): @@ -123,6 +123,12 @@ Fix-it keys (pick one): - remove it from `tools.sandbox.tools.deny` (or per-agent `agents.list[].tools.sandbox.tools.deny`) - or add it to `tools.sandbox.tools.allow` (or per-agent allow) -### “I thought this was main, why is it sandboxed?” +### "I thought this was main, why is it sandboxed?" In `"non-main"` mode, group/channel keys are _not_ main. Use the main session key (shown by `sandbox explain`) or switch mode to `"off"`. + +## See also + +- [Sandboxing](/gateway/sandboxing) -- full sandbox reference (modes, scopes, backends, images) +- [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) -- per-agent overrides and precedence +- [Elevated Mode](/tools/elevated) diff --git a/docs/gateway/sandboxing.md b/docs/gateway/sandboxing.md index d62af2f4f7d..736dc7c6261 100644 --- a/docs/gateway/sandboxing.md +++ b/docs/gateway/sandboxing.md @@ -7,7 +7,7 @@ status: active # Sandboxing -OpenClaw can run **tools inside Docker containers** to reduce blast radius. +OpenClaw can run **tools inside sandbox backends** to reduce blast radius. This is **optional** and controlled by configuration (`agents.defaults.sandbox` or `agents.list[].sandbox`). If sandboxing is off, tools run on the host. The Gateway stays on the host; tool execution runs in an isolated sandbox @@ -54,6 +54,208 @@ Not sandboxed: - `"agent"`: one container per agent. - `"shared"`: one container shared by all sandboxed sessions. +## Backend + +`agents.defaults.sandbox.backend` controls **which runtime** provides the sandbox: + +- `"docker"` (default): local Docker-backed sandbox runtime. +- `"ssh"`: generic SSH-backed remote sandbox runtime. +- `"openshell"`: OpenShell-backed sandbox runtime. + +SSH-specific config lives under `agents.defaults.sandbox.ssh`. +OpenShell-specific config lives under `plugins.entries.openshell.config`. + +### Choosing a backend + +| | Docker | SSH | OpenShell | +| ------------------- | -------------------------------- | ------------------------------ | --------------------------------------------------- | +| **Where it runs** | Local container | Any SSH-accessible host | OpenShell managed sandbox | +| **Setup** | `scripts/sandbox-setup.sh` | SSH key + target host | OpenShell plugin enabled | +| **Workspace model** | Bind-mount or copy | Remote-canonical (seed once) | `mirror` or `remote` | +| **Network control** | `docker.network` (default: none) | Depends on remote host | Depends on OpenShell | +| **Browser sandbox** | Supported | Not supported | Not supported yet | +| **Bind mounts** | `docker.binds` | N/A | N/A | +| **Best for** | Local dev, full isolation | Offloading to a remote machine | Managed remote sandboxes with optional two-way sync | + +### SSH backend + +Use `backend: "ssh"` when you want OpenClaw to sandbox `exec`, file tools, and media reads on +an arbitrary SSH-accessible machine. + +```json5 +{ + agents: { + defaults: { + sandbox: { + mode: "all", + backend: "ssh", + scope: "session", + workspaceAccess: "rw", + ssh: { + target: "user@gateway-host:22", + workspaceRoot: "/tmp/openclaw-sandboxes", + strictHostKeyChecking: true, + updateHostKeys: true, + identityFile: "~/.ssh/id_ed25519", + certificateFile: "~/.ssh/id_ed25519-cert.pub", + knownHostsFile: "~/.ssh/known_hosts", + // Or use SecretRefs / inline contents instead of local files: + // identityData: { source: "env", provider: "default", id: "SSH_IDENTITY" }, + // certificateData: { source: "env", provider: "default", id: "SSH_CERTIFICATE" }, + // knownHostsData: { source: "env", provider: "default", id: "SSH_KNOWN_HOSTS" }, + }, + }, + }, + }, +} +``` + +How it works: + +- OpenClaw creates a per-scope remote root under `sandbox.ssh.workspaceRoot`. +- On first use after create or recreate, OpenClaw seeds that remote workspace from the local workspace once. +- After that, `exec`, `read`, `write`, `edit`, `apply_patch`, prompt media reads, and inbound media staging run directly against the remote workspace over SSH. +- OpenClaw does not sync remote changes back to the local workspace automatically. + +Authentication material: + +- `identityFile`, `certificateFile`, `knownHostsFile`: use existing local files and pass them through OpenSSH config. +- `identityData`, `certificateData`, `knownHostsData`: use inline strings or SecretRefs. OpenClaw resolves them through the normal secrets runtime snapshot, writes them to temp files with `0600`, and deletes them when the SSH session ends. +- If both `*File` and `*Data` are set for the same item, `*Data` wins for that SSH session. + +This is a **remote-canonical** model. The remote SSH workspace becomes the real sandbox state after the initial seed. + +Important consequences: + +- Host-local edits made outside OpenClaw after the seed step are not visible remotely until you recreate the sandbox. +- `openclaw sandbox recreate` deletes the per-scope remote root and seeds again from local on next use. +- Browser sandboxing is not supported on the SSH backend. +- `sandbox.docker.*` settings do not apply to the SSH backend. + +### OpenShell backend + +Use `backend: "openshell"` when you want OpenClaw to sandbox tools in an +OpenShell-managed remote environment. For the full setup guide, configuration +reference, and workspace mode comparison, see the dedicated +[OpenShell page](/gateway/openshell). + +OpenShell reuses the same core SSH transport and remote filesystem bridge as the +generic SSH backend, and adds OpenShell-specific lifecycle +(`sandbox create/get/delete`, `sandbox ssh-config`) plus the optional `mirror` +workspace mode. + +```json5 +{ + agents: { + defaults: { + sandbox: { + mode: "all", + backend: "openshell", + scope: "session", + workspaceAccess: "rw", + }, + }, + }, + plugins: { + entries: { + openshell: { + enabled: true, + config: { + from: "openclaw", + mode: "remote", // mirror | remote + remoteWorkspaceDir: "/sandbox", + remoteAgentWorkspaceDir: "/agent", + }, + }, + }, + }, +} +``` + +OpenShell modes: + +- `mirror` (default): local workspace stays canonical. OpenClaw syncs local files into OpenShell before exec and syncs the remote workspace back after exec. +- `remote`: OpenShell workspace is canonical after the sandbox is created. OpenClaw seeds the remote workspace once from the local workspace, then file tools and exec run directly against the remote sandbox without syncing changes back. + +Remote transport details: + +- OpenClaw asks OpenShell for sandbox-specific SSH config via `openshell sandbox ssh-config `. +- Core writes that SSH config to a temp file, opens the SSH session, and reuses the same remote filesystem bridge used by `backend: "ssh"`. +- In `mirror` mode only the lifecycle differs: sync local to remote before exec, then sync back after exec. + +Current OpenShell limitations: + +- sandbox browser is not supported yet +- `sandbox.docker.binds` is not supported on the OpenShell backend +- Docker-specific runtime knobs under `sandbox.docker.*` still apply only to the Docker backend + +#### Workspace modes + +OpenShell has two workspace models. This is the part that matters most in practice. + +##### `mirror` + +Use `plugins.entries.openshell.config.mode: "mirror"` when you want the **local workspace to stay canonical**. + +Behavior: + +- Before `exec`, OpenClaw syncs the local workspace into the OpenShell sandbox. +- After `exec`, OpenClaw syncs the remote workspace back to the local workspace. +- File tools still operate through the sandbox bridge, but the local workspace remains the source of truth between turns. + +Use this when: + +- you edit files locally outside OpenClaw and want those changes to show up in the sandbox automatically +- you want the OpenShell sandbox to behave as much like the Docker backend as possible +- you want the host workspace to reflect sandbox writes after each exec turn + +Tradeoff: + +- extra sync cost before and after exec + +##### `remote` + +Use `plugins.entries.openshell.config.mode: "remote"` when you want the **OpenShell workspace to become canonical**. + +Behavior: + +- When the sandbox is first created, OpenClaw seeds the remote workspace from the local workspace once. +- After that, `exec`, `read`, `write`, `edit`, and `apply_patch` operate directly against the remote OpenShell workspace. +- OpenClaw does **not** sync remote changes back into the local workspace after exec. +- Prompt-time media reads still work because file and media tools read through the sandbox bridge instead of assuming a local host path. +- Transport is SSH into the OpenShell sandbox returned by `openshell sandbox ssh-config`. + +Important consequences: + +- If you edit files on the host outside OpenClaw after the seed step, the remote sandbox will **not** see those changes automatically. +- If the sandbox is recreated, the remote workspace is seeded from the local workspace again. +- With `scope: "agent"` or `scope: "shared"`, that remote workspace is shared at that same scope. + +Use this when: + +- the sandbox should live primarily on the remote OpenShell side +- you want lower per-turn sync overhead +- you do not want host-local edits to silently overwrite remote sandbox state + +Choose `mirror` if you think of the sandbox as a temporary execution environment. +Choose `remote` if you think of the sandbox as the real workspace. + +#### OpenShell lifecycle + +OpenShell sandboxes are still managed through the normal sandbox lifecycle: + +- `openclaw sandbox list` shows OpenShell runtimes as well as Docker runtimes +- `openclaw sandbox recreate` deletes the current runtime and lets OpenClaw recreate it on next use +- prune logic is backend-aware too + +For `remote` mode, recreate is especially important: + +- recreate deletes the canonical remote workspace for that scope +- the next use seeds a fresh remote workspace from the local workspace + +For `mirror` mode, recreate mainly resets the remote execution environment +because the local workspace remains canonical anyway. + ## Workspace access `agents.defaults.sandbox.workspaceAccess` controls **what the sandbox can see**: @@ -62,6 +264,12 @@ Not sandboxed: - `"ro"`: mounts the agent workspace read-only at `/agent` (disables `write`/`edit`/`apply_patch`). - `"rw"`: mounts the agent workspace read/write at `/workspace`. +With the OpenShell backend: + +- `mirror` mode still uses the local workspace as the canonical source between exec turns +- `remote` mode uses the remote OpenShell workspace as the canonical source after the initial seed +- `workspaceAccess: "ro"` and `"none"` still restrict write behavior the same way + Inbound media is copied into the active sandbox workspace (`media/inbound/*`). Skills note: the `read` tool is sandbox-rooted. With `workspaceAccess: "none"`, OpenClaw mirrors eligible skills into the sandbox workspace (`.../skills`) so @@ -116,7 +324,7 @@ Security notes: ## Images + setup -Default image: `openclaw-sandbox:bookworm-slim` +Default Docker image: `openclaw-sandbox:bookworm-slim` Build it once: @@ -145,7 +353,7 @@ Sandboxed browser image: scripts/sandbox-browser-setup.sh ``` -By default, sandbox containers run with **no network**. +By default, Docker sandbox containers run with **no network**. Override with `agents.defaults.sandbox.docker.network`. The bundled sandbox browser image also applies conservative Chromium startup defaults @@ -254,6 +462,8 @@ See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for preceden ## Related docs +- [OpenShell](/gateway/openshell) -- managed sandbox backend setup, workspace modes, and config reference - [Sandbox Configuration](/gateway/configuration#agentsdefaults-sandbox) -- [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) +- [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) -- debugging "why is this blocked?" +- [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) -- per-agent overrides and precedence - [Security](/gateway/security) diff --git a/docs/gateway/secrets-plan-contract.md b/docs/gateway/secrets-plan-contract.md index 83ed10b06dd..b27518bdb1e 100644 --- a/docs/gateway/secrets-plan-contract.md +++ b/docs/gateway/secrets-plan-contract.md @@ -81,6 +81,12 @@ Invalid plan target path for models.providers.apiKey: models.providers.openai.ba No writes are committed for an invalid plan. +## Exec provider consent behavior + +- `--dry-run` skips exec SecretRef checks by default. +- Plans containing exec SecretRefs/providers are rejected in write mode unless `--allow-exec` is set. +- When validating/applying exec-containing plans, pass `--allow-exec` in both dry-run and write commands. + ## Runtime and audit scope notes - Ref-only `auth-profiles.json` entries (`keyRef`/`tokenRef`) are included in runtime resolution and audit coverage. @@ -94,6 +100,10 @@ openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run # Then apply for real openclaw secrets apply --from /tmp/openclaw-secrets-plan.json + +# For exec-containing plans, opt in explicitly in both modes +openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run --allow-exec +openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --allow-exec ``` If apply fails with an invalid target path message, regenerate the plan with `openclaw secrets configure` or fix the target path to a supported shape above. diff --git a/docs/gateway/secrets.md b/docs/gateway/secrets.md index 93cd508d4f1..d404399ac65 100644 --- a/docs/gateway/secrets.md +++ b/docs/gateway/secrets.md @@ -41,6 +41,9 @@ Examples of inactive surfaces: - Web search provider-specific keys that are not selected by `tools.web.search.provider`. In auto mode (provider unset), keys are consulted by precedence for provider auto-detection until one resolves. After selection, non-selected provider keys are treated as inactive until selected. +- Sandbox SSH auth material (`agents.defaults.sandbox.ssh.identityData`, + `certificateData`, `knownHostsData`, plus per-agent overrides) is active only + when the effective sandbox backend is `ssh` for the default agent or an enabled agent. - `gateway.remote.token` / `gateway.remote.password` SecretRefs are active if one of these is true: - `gateway.mode=remote` - `gateway.remote.url` is configured @@ -67,7 +70,7 @@ active-surface policy, so you can see why a credential was treated as active or When onboarding runs in interactive mode and you choose SecretRef storage, OpenClaw runs preflight validation before saving: -- Env refs: validates env var name and confirms a non-empty value is visible during onboarding. +- Env refs: validates env var name and confirms a non-empty value is visible during setup. - Provider refs (`file` or `exec`): validates provider selection, resolves `id`, and checks resolved value type. - Quickstart reuse path: when `gateway.auth.token` is already a SecretRef, onboarding resolves it before probe/dashboard bootstrap (for `env`, `file`, and `exec` refs) using the same fail-fast gate. @@ -285,6 +288,35 @@ Optional per-id errors: } ``` +## Sandbox SSH auth material + +The core `ssh` sandbox backend also supports SecretRefs for SSH auth material: + +```json5 +{ + agents: { + defaults: { + sandbox: { + mode: "all", + backend: "ssh", + ssh: { + target: "user@gateway-host:22", + identityData: { source: "env", provider: "default", id: "SSH_IDENTITY" }, + certificateData: { source: "env", provider: "default", id: "SSH_CERTIFICATE" }, + knownHostsData: { source: "env", provider: "default", id: "SSH_KNOWN_HOSTS" }, + }, + }, + }, + }, +} +``` + +Runtime behavior: + +- OpenClaw resolves these refs during sandbox activation, not lazily during each SSH call. +- Resolved values are written to temp files with restrictive permissions and used in generated SSH config. +- If the effective sandbox backend is not `ssh`, these refs stay inactive and do not block startup. + ## Supported credential surface Canonical supported and unsupported credentials are listed in: @@ -348,7 +380,7 @@ Command paths can opt into supported SecretRef resolution via gateway snapshot R There are two broad behaviors: - Strict command paths (for example `openclaw memory` remote-memory paths and `openclaw qr --remote`) read from the active snapshot and fail fast when a required SecretRef is unavailable. -- Read-only command paths (for example `openclaw status`, `openclaw status --all`, `openclaw channels status`, `openclaw channels resolve`, and read-only doctor/config repair flows) also prefer the active snapshot, but degrade instead of aborting when a targeted SecretRef is unavailable in that command path. +- Read-only command paths (for example `openclaw status`, `openclaw status --all`, `openclaw channels status`, `openclaw channels resolve`, `openclaw security audit`, and read-only doctor/config repair flows) also prefer the active snapshot, but degrade instead of aborting when a targeted SecretRef is unavailable in that command path. Read-only behavior: @@ -382,6 +414,11 @@ Findings include: - precedence shadowing (`auth-profiles.json` taking priority over `openclaw.json` refs) - legacy residues (`auth.json`, OAuth reminders) +Exec note: + +- By default, audit skips exec SecretRef resolvability checks to avoid command side effects. +- Use `openclaw secrets audit --allow-exec` to execute exec providers during audit. + Header residue note: - Sensitive provider header detection is name-heuristic based (common auth/credential header names and fragments such as `authorization`, `x-api-key`, `token`, `secret`, `password`, and `credential`). @@ -397,6 +434,11 @@ Interactive helper that: - runs preflight resolution - can apply immediately +Exec note: + +- Preflight skips exec SecretRef checks unless `--allow-exec` is set. +- If you apply directly from `configure --apply` and the plan includes exec refs/providers, keep `--allow-exec` set for the apply step too. + Helpful modes: - `openclaw secrets configure --providers-only` @@ -415,9 +457,16 @@ Apply a saved plan: ```bash openclaw secrets apply --from /tmp/openclaw-secrets-plan.json +openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --allow-exec openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run +openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run --allow-exec ``` +Exec note: + +- dry-run skips exec checks unless `--allow-exec` is set. +- write mode rejects plans containing exec SecretRefs/providers unless `--allow-exec` is set. + For strict target/path contract details and exact rejection rules, see: - [Secrets Apply Plan Contract](/gateway/secrets-plan-contract) diff --git a/docs/gateway/security/index.md b/docs/gateway/security/index.md index 3084adf82ad..8cea1b42766 100644 --- a/docs/gateway/security/index.md +++ b/docs/gateway/security/index.md @@ -243,7 +243,10 @@ High-signal `checkId` values you will most likely see in real deployments (not e | `gateway.real_ip_fallback_enabled` | warn/critical | Trusting `X-Real-IP` fallback can enable source-IP spoofing via proxy misconfig | `gateway.allowRealIpFallback`, `gateway.trustedProxies` | no | | `discovery.mdns_full_mode` | warn/critical | mDNS full mode advertises `cliPath`/`sshPort` metadata on local network | `discovery.mdns.mode`, `gateway.bind` | no | | `config.insecure_or_dangerous_flags` | warn | Any insecure/dangerous debug flags enabled | multiple keys (see finding detail) | no | +| `hooks.token_reuse_gateway_token` | critical | Hook ingress token also unlocks Gateway auth | `hooks.token`, `gateway.auth.token` | no | | `hooks.token_too_short` | warn | Easier brute force on hook ingress | `hooks.token` | no | +| `hooks.default_session_key_unset` | warn | Hook agent runs fan out into generated per-request sessions | `hooks.defaultSessionKey` | no | +| `hooks.allowed_agent_ids_unrestricted` | warn/critical | Authenticated hook callers may route to any configured agent | `hooks.allowedAgentIds` | no | | `hooks.request_session_key_enabled` | warn/critical | External caller can choose sessionKey | `hooks.allowRequestSessionKey` | no | | `hooks.request_session_key_prefixes_missing` | warn/critical | No bound on external session key shapes | `hooks.allowedSessionKeyPrefixes` | no | | `logging.redact_off` | warn | Sensitive values leak to logs/status | `logging.redactSensitive` | yes | @@ -304,6 +307,7 @@ schema: - `channels.googlechat.dangerouslyAllowNameMatching` - `channels.googlechat.accounts..dangerouslyAllowNameMatching` - `channels.msteams.dangerouslyAllowNameMatching` +- `channels.zalouser.dangerouslyAllowNameMatching` (extension channel) - `channels.irc.dangerouslyAllowNameMatching` (extension channel) - `channels.irc.accounts..dangerouslyAllowNameMatching` (extension channel) - `channels.mattermost.dangerouslyAllowNameMatching` (extension channel) @@ -354,6 +358,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - If the gateway itself terminates HTTPS, you can set `gateway.http.securityHeaders.strictTransportSecurity` to emit the HSTS header from OpenClaw responses. - Detailed deployment guidance is in [Trusted Proxy Auth](/gateway/trusted-proxy-auth#tls-termination-and-hsts). - For non-loopback Control UI deployments, `gateway.controlUi.allowedOrigins` is required by default. +- `gateway.controlUi.allowedOrigins: ["*"]` is an explicit allow-all browser-origin policy, not a hardened default. Avoid it outside tightly controlled local testing. - `gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true` enables Host-header origin fallback mode; treat it as a dangerous operator-selected policy. - Treat DNS rebinding and proxy-host header behavior as deployment hardening concerns; keep `trustedProxies` tight and avoid exposing the gateway directly to the public internet. @@ -494,7 +499,7 @@ Treat the snippet above as **secure DM mode**: If you run multiple accounts on the same channel, use `per-account-channel-peer` instead. If the same person contacts you on multiple channels, use `session.identityLinks` to collapse those DM sessions into one canonical identity. See [Session Management](/concepts/session) and [Configuration](/gateway/configuration). -## Allowlists (DM + groups) — terminology +## Allowlists (DM + groups) - terminology OpenClaw has two separate “who can trigger me?” layers: @@ -567,6 +572,8 @@ tool calls. Reduce the blast radius by: - For OpenResponses URL inputs (`input_file` / `input_image`), set tight `gateway.http.endpoints.responses.files.urlAllowlist` and `gateway.http.endpoints.responses.images.urlAllowlist`, and keep `maxUrlParts` low. + Empty allowlists are treated as unset; use `files.allowUrl: false` / `images.allowUrl: false` + if you want to disable URL fetching entirely. - Enabling sandboxing and strict tool allowlists for any agent that touches untrusted input. - Keeping secrets out of prompts; pass them via env/config on the gateway host instead. @@ -737,7 +744,7 @@ In minimal mode, the Gateway still broadcasts enough for device discovery (`role Gateway auth is **required by default**. If no token/password is configured, the Gateway refuses WebSocket connections (fail‑closed). -The onboarding wizard generates a token by default (even for loopback) so +Onboarding generates a token by default (even for loopback) so local clients must authenticate. Set a token so **all** WS clients must authenticate: @@ -833,7 +840,7 @@ Avoid: - Exposing relay/control ports over LAN or public Internet. - Tailscale Funnel for browser control endpoints (public exposure). -### 0.7) Secrets on disk (what’s sensitive) +### 0.7) Secrets on disk (sensitive data) Assume anything under `~/.openclaw/` (or `$OPENCLAW_STATE_DIR/`) may contain secrets or private data: @@ -989,10 +996,9 @@ access those accounts and data. Treat browser profiles as **sensitive state**: - Treat browser downloads as untrusted input; prefer an isolated downloads directory. - Disable browser sync/password managers in the agent profile if possible (reduces blast radius). - For remote gateways, assume “browser control” is equivalent to “operator access” to whatever that profile can reach. -- Keep the Gateway and node hosts tailnet-only; avoid exposing relay/control ports to LAN or public Internet. -- The Chrome extension relay’s CDP endpoint is auth-gated; only OpenClaw clients can connect. +- Keep the Gateway and node hosts tailnet-only; avoid exposing browser control ports to LAN or public Internet. - Disable browser proxy routing when you don’t need it (`gateway.nodes.browser.mode="off"`). -- Chrome extension relay mode is **not** “safer”; it can take over your existing Chrome tabs. Assume it can act as you in whatever that tab/profile can reach. +- Chrome MCP existing-session mode is **not** “safer”; it can act as you in whatever that host Chrome profile can reach. ### Browser SSRF policy (trusted-network default) diff --git a/docs/gateway/troubleshooting.md b/docs/gateway/troubleshooting.md index ebea28a6541..aa75b9cf2b5 100644 --- a/docs/gateway/troubleshooting.md +++ b/docs/gateway/troubleshooting.md @@ -289,19 +289,18 @@ Look for: - Valid browser executable path. - CDP profile reachability. -- Extension relay tab attachment for `profile="chrome"`. +- Local Chrome availability for `existing-session` / `user` profiles. Common signatures: - `Failed to start Chrome CDP on port` → browser process failed to launch. - `browser.executablePath not found` → configured path is invalid. -- `Chrome extension relay is running, but no tab is connected` → extension relay not attached. +- `No Chrome tabs found for profile="user"` → the Chrome MCP attach profile has no open local Chrome tabs. - `Browser attachOnly is enabled ... not reachable` → attach-only profile has no reachable target. Related: - [/tools/browser-linux-troubleshooting](/tools/browser-linux-troubleshooting) -- [/tools/chrome-extension](/tools/chrome-extension) - [/tools/browser](/tools/browser) ## If you upgraded and something suddenly broke diff --git a/docs/help/debugging.md b/docs/help/debugging.md index 61539ec39a3..04fd150ef20 100644 --- a/docs/help/debugging.md +++ b/docs/help/debugging.md @@ -40,11 +40,17 @@ pnpm gateway:watch This maps to: ```bash -node --watch-path src --watch-path tsconfig.json --watch-path package.json --watch-preserve-output scripts/run-node.mjs gateway --force +node scripts/watch-node.mjs gateway --force ``` -Add any gateway CLI flags after `gateway:watch` and they will be passed through -on each restart. +The watcher restarts on build-relevant files under `src/`, extension source files, +extension `package.json` and `openclaw.plugin.json` metadata, `tsconfig.json`, +`package.json`, and `tsdown.config.ts`. Extension metadata changes restart the +gateway without forcing a `tsdown` rebuild; source and config changes still +rebuild `dist` first. + +Add any gateway CLI flags after `gateway:watch` and they will be passed through on +each restart. ## Dev profile + dev gateway (--dev) diff --git a/docs/help/faq.md b/docs/help/faq.md index 8b738b60fc2..5e892da6a7b 100644 --- a/docs/help/faq.md +++ b/docs/help/faq.md @@ -13,8 +13,8 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, ## Table of contents - [Quick start and first-run setup] - - [Im stuck what's the fastest way to get unstuck?](#im-stuck-whats-the-fastest-way-to-get-unstuck) - - [What's the recommended way to install and set up OpenClaw?](#whats-the-recommended-way-to-install-and-set-up-openclaw) + - [I am stuck - fastest way to get unstuck](#i-am-stuck---fastest-way-to-get-unstuck) + - [Recommended way to install and set up OpenClaw](#recommended-way-to-install-and-set-up-openclaw) - [How do I open the dashboard after onboarding?](#how-do-i-open-the-dashboard-after-onboarding) - [How do I authenticate the dashboard (token) on localhost vs remote?](#how-do-i-authenticate-the-dashboard-token-on-localhost-vs-remote) - [What runtime do I need?](#what-runtime-do-i-need) @@ -23,20 +23,20 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [It is stuck on "wake up my friend" / onboarding will not hatch. What now?](#it-is-stuck-on-wake-up-my-friend-onboarding-will-not-hatch-what-now) - [Can I migrate my setup to a new machine (Mac mini) without redoing onboarding?](#can-i-migrate-my-setup-to-a-new-machine-mac-mini-without-redoing-onboarding) - [Where do I see what is new in the latest version?](#where-do-i-see-what-is-new-in-the-latest-version) - - [I can't access docs.openclaw.ai (SSL error). What now?](#i-cant-access-docsopenclawai-ssl-error-what-now) - - [What's the difference between stable and beta?](#whats-the-difference-between-stable-and-beta) - - [How do I install the beta version, and what's the difference between beta and dev?](#how-do-i-install-the-beta-version-and-whats-the-difference-between-beta-and-dev) + - [Cannot access docs.openclaw.ai (SSL error)](#cannot-access-docsopenclawai-ssl-error) + - [Difference between stable and beta](#difference-between-stable-and-beta) + - [How do I install the beta version and what is the difference between beta and dev](#how-do-i-install-the-beta-version-and-what-is-the-difference-between-beta-and-dev) - [How do I try the latest bits?](#how-do-i-try-the-latest-bits) - [How long does install and onboarding usually take?](#how-long-does-install-and-onboarding-usually-take) - [Installer stuck? How do I get more feedback?](#installer-stuck-how-do-i-get-more-feedback) - [Windows install says git not found or openclaw not recognized](#windows-install-says-git-not-found-or-openclaw-not-recognized) - [Windows exec output shows garbled Chinese text what should I do](#windows-exec-output-shows-garbled-chinese-text-what-should-i-do) - - [The docs didn't answer my question - how do I get a better answer?](#the-docs-didnt-answer-my-question-how-do-i-get-a-better-answer) + - [The docs did not answer my question - how do I get a better answer](#the-docs-did-not-answer-my-question---how-do-i-get-a-better-answer) - [How do I install OpenClaw on Linux?](#how-do-i-install-openclaw-on-linux) - [How do I install OpenClaw on a VPS?](#how-do-i-install-openclaw-on-a-vps) - [Where are the cloud/VPS install guides?](#where-are-the-cloudvps-install-guides) - [Can I ask OpenClaw to update itself?](#can-i-ask-openclaw-to-update-itself) - - [What does the onboarding wizard actually do?](#what-does-the-onboarding-wizard-actually-do) + - [What does onboarding actually do?](#what-does-onboarding-actually-do) - [Do I need a Claude or OpenAI subscription to run this?](#do-i-need-a-claude-or-openai-subscription-to-run-this) - [Can I use Claude Max subscription without an API key](#can-i-use-claude-max-subscription-without-an-api-key) - [How does Anthropic "setup-token" auth work?](#how-does-anthropic-setuptoken-auth-work) @@ -57,7 +57,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [Can multiple people use one WhatsApp number with different OpenClaw instances?](#can-multiple-people-use-one-whatsapp-number-with-different-openclaw-instances) - [Can I run a "fast chat" agent and an "Opus for coding" agent?](#can-i-run-a-fast-chat-agent-and-an-opus-for-coding-agent) - [Does Homebrew work on Linux?](#does-homebrew-work-on-linux) - - [What's the difference between the hackable (git) install and npm install?](#whats-the-difference-between-the-hackable-git-install-and-npm-install) + - [Difference between the hackable git install and npm install](#difference-between-the-hackable-git-install-and-npm-install) - [Can I switch between npm and git installs later?](#can-i-switch-between-npm-and-git-installs-later) - [Should I run the Gateway on my laptop or a VPS?](#should-i-run-the-gateway-on-my-laptop-or-a-vps) - [How important is it to run OpenClaw on a dedicated machine?](#how-important-is-it-to-run-openclaw-on-a-dedicated-machine) @@ -65,7 +65,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [Can I run OpenClaw in a VM and what are the requirements](#can-i-run-openclaw-in-a-vm-and-what-are-the-requirements) - [What is OpenClaw?](#what-is-openclaw) - [What is OpenClaw, in one paragraph?](#what-is-openclaw-in-one-paragraph) - - [What's the value proposition?](#whats-the-value-proposition) + - [Value proposition](#value-proposition) - [I just set it up what should I do first](#i-just-set-it-up-what-should-i-do-first) - [What are the top five everyday use cases for OpenClaw](#what-are-the-top-five-everyday-use-cases-for-openclaw) - [Can OpenClaw help with lead gen outreach ads and blogs for a SaaS](#can-openclaw-help-with-lead-gen-outreach-ads-and-blogs-for-a-saas) @@ -80,7 +80,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [Can OpenClaw run tasks on a schedule or continuously in the background?](#can-openclaw-run-tasks-on-a-schedule-or-continuously-in-the-background) - [Can I run Apple macOS-only skills from Linux?](#can-i-run-apple-macos-only-skills-from-linux) - [Do you have a Notion or HeyGen integration?](#do-you-have-a-notion-or-heygen-integration) - - [How do I install the Chrome extension for browser takeover?](#how-do-i-install-the-chrome-extension-for-browser-takeover) + - [How do I use my existing signed-in Chrome with OpenClaw?](#how-do-i-use-my-existing-signed-in-chrome-with-openclaw) - [Sandboxing and memory](#sandboxing-and-memory) - [Is there a dedicated sandboxing doc?](#is-there-a-dedicated-sandboxing-doc) - [How do I bind a host folder into the sandbox?](#how-do-i-bind-a-host-folder-into-the-sandbox) @@ -92,7 +92,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [Is all data used with OpenClaw saved locally?](#is-all-data-used-with-openclaw-saved-locally) - [Where does OpenClaw store its data?](#where-does-openclaw-store-its-data) - [Where should AGENTS.md / SOUL.md / USER.md / MEMORY.md live?](#where-should-agentsmd-soulmd-usermd-memorymd-live) - - [What's the recommended backup strategy?](#whats-the-recommended-backup-strategy) + - [Recommended backup strategy](#recommended-backup-strategy) - [How do I completely uninstall OpenClaw?](#how-do-i-completely-uninstall-openclaw) - [Can agents work outside the workspace?](#can-agents-work-outside-the-workspace) - [I'm in remote mode - where is the session store?](#im-in-remote-mode-where-is-the-session-store) @@ -116,7 +116,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [Is there a benefit to using a node on my personal laptop instead of SSH from a VPS?](#is-there-a-benefit-to-using-a-node-on-my-personal-laptop-instead-of-ssh-from-a-vps) - [Do nodes run a gateway service?](#do-nodes-run-a-gateway-service) - [Is there an API / RPC way to apply config?](#is-there-an-api-rpc-way-to-apply-config) - - [What's a minimal "sane" config for a first install?](#whats-a-minimal-sane-config-for-a-first-install) + - [Minimal sane config for a first install](#minimal-sane-config-for-a-first-install) - [How do I set up Tailscale on a VPS and connect from my Mac?](#how-do-i-set-up-tailscale-on-a-vps-and-connect-from-my-mac) - [How do I connect a Mac node to a remote Gateway (Tailscale Serve)?](#how-do-i-connect-a-mac-node-to-a-remote-gateway-tailscale-serve) - [Should I install on a second laptop or just add a node?](#should-i-install-on-a-second-laptop-or-just-add-a-node) @@ -135,7 +135,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [Why am I getting heartbeat messages every 30 minutes?](#why-am-i-getting-heartbeat-messages-every-30-minutes) - [Do I need to add a "bot account" to a WhatsApp group?](#do-i-need-to-add-a-bot-account-to-a-whatsapp-group) - [How do I get the JID of a WhatsApp group?](#how-do-i-get-the-jid-of-a-whatsapp-group) - - [Why doesn't OpenClaw reply in a group?](#why-doesnt-openclaw-reply-in-a-group) + - [Why does OpenClaw not reply in a group](#why-does-openclaw-not-reply-in-a-group) - [Do groups/threads share context with DMs?](#do-groupsthreads-share-context-with-dms) - [How many workspaces and agents can I create?](#how-many-workspaces-and-agents-can-i-create) - [Can I run multiple bots or chats at the same time (Slack), and how should I set that up?](#can-i-run-multiple-bots-or-chats-at-the-same-time-slack-and-how-should-i-set-that-up) @@ -162,7 +162,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [What is an auth profile?](#what-is-an-auth-profile) - [What are typical profile IDs?](#what-are-typical-profile-ids) - [Can I control which auth profile is tried first?](#can-i-control-which-auth-profile-is-tried-first) - - [OAuth vs API key: what's the difference?](#oauth-vs-api-key-whats-the-difference) + - [OAuth vs API key - what is the difference](#oauth-vs-api-key---what-is-the-difference) - [Gateway: ports, "already running", and remote mode](#gateway-ports-already-running-and-remote-mode) - [What port does the Gateway use?](#what-port-does-the-gateway-use) - [Why does `openclaw gateway status` say `Runtime: running` but `RPC probe: failed`?](#why-does-openclaw-gateway-status-say-runtime-running-but-rpc-probe-failed) @@ -170,7 +170,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [What does "another gateway instance is already listening" mean?](#what-does-another-gateway-instance-is-already-listening-mean) - [How do I run OpenClaw in remote mode (client connects to a Gateway elsewhere)?](#how-do-i-run-openclaw-in-remote-mode-client-connects-to-a-gateway-elsewhere) - [The Control UI says "unauthorized" (or keeps reconnecting). What now?](#the-control-ui-says-unauthorized-or-keeps-reconnecting-what-now) - - [I set `gateway.bind: "tailnet"` but it can't bind / nothing listens](#i-set-gatewaybind-tailnet-but-it-cant-bind-nothing-listens) + - [I set gateway.bind tailnet but it cannot bind and nothing listens](#i-set-gatewaybind-tailnet-but-it-cannot-bind-and-nothing-listens) - [Can I run multiple Gateways on the same host?](#can-i-run-multiple-gateways-on-the-same-host) - [What does "invalid handshake" / code 1008 mean?](#what-does-invalid-handshake-code-1008-mean) - [Logging and debugging](#logging-and-debugging) @@ -179,11 +179,11 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [I closed my terminal on Windows - how do I restart OpenClaw?](#i-closed-my-terminal-on-windows-how-do-i-restart-openclaw) - [The Gateway is up but replies never arrive. What should I check?](#the-gateway-is-up-but-replies-never-arrive-what-should-i-check) - ["Disconnected from gateway: no reason" - what now?](#disconnected-from-gateway-no-reason-what-now) - - [Telegram setMyCommands fails with network errors. What should I check?](#telegram-setmycommands-fails-with-network-errors-what-should-i-check) + - [Telegram setMyCommands fails. What should I check?](#telegram-setmycommands-fails-what-should-i-check) - [TUI shows no output. What should I check?](#tui-shows-no-output-what-should-i-check) - [How do I completely stop then start the Gateway?](#how-do-i-completely-stop-then-start-the-gateway) - [ELI5: `openclaw gateway restart` vs `openclaw gateway`](#eli5-openclaw-gateway-restart-vs-openclaw-gateway) - - [What's the fastest way to get more details when something fails?](#whats-the-fastest-way-to-get-more-details-when-something-fails) + - [Fastest way to get more details when something fails](#fastest-way-to-get-more-details-when-something-fails) - [Media and attachments](#media-and-attachments) - [My skill generated an image/PDF, but nothing was sent](#my-skill-generated-an-imagepdf-but-nothing-was-sent) - [Security and access control](#security-and-access-control) @@ -192,15 +192,15 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [Should my bot have its own email GitHub account or phone number](#should-my-bot-have-its-own-email-github-account-or-phone-number) - [Can I give it autonomy over my text messages and is that safe](#can-i-give-it-autonomy-over-my-text-messages-and-is-that-safe) - [Can I use cheaper models for personal assistant tasks?](#can-i-use-cheaper-models-for-personal-assistant-tasks) - - [I ran `/start` in Telegram but didn't get a pairing code](#i-ran-start-in-telegram-but-didnt-get-a-pairing-code) + - [I ran /start in Telegram but did not get a pairing code](#i-ran-start-in-telegram-but-did-not-get-a-pairing-code) - [WhatsApp: will it message my contacts? How does pairing work?](#whatsapp-will-it-message-my-contacts-how-does-pairing-work) -- [Chat commands, aborting tasks, and "it won't stop"](#chat-commands-aborting-tasks-and-it-wont-stop) +- [Chat commands, aborting tasks, and "it will not stop"](#chat-commands-aborting-tasks-and-it-will-not-stop) - [How do I stop internal system messages from showing in chat](#how-do-i-stop-internal-system-messages-from-showing-in-chat) - [How do I stop/cancel a running task?](#how-do-i-stopcancel-a-running-task) - [How do I send a Discord message from Telegram? ("Cross-context messaging denied")](#how-do-i-send-a-discord-message-from-telegram-crosscontext-messaging-denied) - [Why does it feel like the bot "ignores" rapid-fire messages?](#why-does-it-feel-like-the-bot-ignores-rapidfire-messages) -## First 60 seconds if something's broken +## First 60 seconds if something is broken 1. **Quick status (first check)** @@ -267,7 +267,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, ## Quick start and first-run setup -### Im stuck what's the fastest way to get unstuck +### I am stuck - fastest way to get unstuck Use a local AI agent that can **see your machine**. That is far more effective than asking in Discord, because most "I'm stuck" cases are **local config or environment issues** that @@ -312,12 +312,12 @@ What they do: Other useful CLI checks: `openclaw status --all`, `openclaw logs --follow`, `openclaw gateway status`, `openclaw health --verbose`. -Quick debug loop: [First 60 seconds if something's broken](#first-60-seconds-if-somethings-broken). +Quick debug loop: [First 60 seconds if something is broken](#first-60-seconds-if-something-is-broken). Install docs: [Install](/install), [Installer flags](/install/installer), [Updating](/install/updating). -### What's the recommended way to install and set up OpenClaw +### Recommended way to install and set up OpenClaw -The repo recommends running from source and using the onboarding wizard: +The repo recommends running from source and using onboarding: ```bash curl -fsSL https://openclaw.ai/install.sh | bash @@ -445,7 +445,7 @@ Newest entries are at the top. If the top section is marked **Unreleased**, the section is the latest shipped version. Entries are grouped by **Highlights**, **Changes**, and **Fixes** (plus docs/other sections when needed). -### I can't access docs.openclaw.ai SSL error What now +### Cannot access docs.openclaw.ai (SSL error) Some Comcast/Xfinity connections incorrectly block `docs.openclaw.ai` via Xfinity Advanced Security. Disable it or allowlist `docs.openclaw.ai`, then retry. More @@ -455,7 +455,7 @@ Please help us unblock it by reporting here: [https://spa.xfinity.com/check_url_ If you still can't reach the site, the docs are mirrored on GitHub: [https://github.com/openclaw/openclaw/tree/main/docs](https://github.com/openclaw/openclaw/tree/main/docs) -### What's the difference between stable and beta +### Difference between stable and beta **Stable** and **beta** are **npm dist-tags**, not separate code lines: @@ -469,7 +469,7 @@ that same version to `latest`**. That's why beta and stable can point at the See what changed: [https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md) -### How do I install the beta version and what's the difference between beta and dev +### How do I install the beta version and what is the difference between beta and dev **Beta** is the npm dist-tag `beta` (may match `latest`). **Dev** is the moving head of `main` (git); when published, it uses the npm dist-tag `dev`. @@ -497,7 +497,7 @@ Rough guide: - **Onboarding:** 5-15 minutes depending on how many channels/models you configure If it hangs, use [Installer stuck](/help/faq#installer-stuck-how-do-i-get-more-feedback) -and the fast debug loop in [Im stuck](/help/faq#im-stuck--whats-the-fastest-way-to-get-unstuck). +and the fast debug loop in [I am stuck](/help/faq#i-am-stuck---fastest-way-to-get-unstuck). ### How do I try the latest bits @@ -614,7 +614,7 @@ If you still reproduce this on latest OpenClaw, track/report it in: - [Issue #30640](https://github.com/openclaw/openclaw/issues/30640) -### The docs didn't answer my question how do I get a better answer +### The docs did not answer my question - how do I get a better answer Use the **hackable (git) install** so you have the full source and docs locally, then ask your bot (or Claude/Codex) _from that folder_ so it can read the repo and answer precisely. @@ -627,7 +627,7 @@ More detail: [Install](/install) and [Installer flags](/install/installer). ### How do I install OpenClaw on Linux -Short answer: follow the Linux guide, then run the onboarding wizard. +Short answer: follow the Linux guide, then run onboarding. - Linux quick path + service install: [Linux](/platforms/linux). - Full walkthrough: [Getting Started](/start/getting-started). @@ -685,7 +685,7 @@ openclaw gateway restart Docs: [Update](/cli/update), [Updating](/install/updating). -### What does the onboarding wizard actually do +### What does onboarding actually do `openclaw onboard` is the recommended setup path. In **local mode** it walks you through: @@ -723,7 +723,7 @@ If you want the clearest and safest supported path for production, use an Anthro ### How does Anthropic setuptoken auth work -`claude setup-token` generates a **token string** via the Claude Code CLI (it is not available in the web console). You can run it on **any machine**. Choose **Anthropic token (paste setup-token)** in the wizard or paste it with `openclaw models auth paste-token --provider anthropic`. The token is stored as an auth profile for the **anthropic** provider and used like an API key (no auto-refresh). More detail: [OAuth](/concepts/oauth). +`claude setup-token` generates a **token string** via the Claude Code CLI (it is not available in the web console). You can run it on **any machine**. Choose **Anthropic token (paste setup-token)** in onboarding or paste it with `openclaw models auth paste-token --provider anthropic`. The token is stored as an auth profile for the **anthropic** provider and used like an API key (no auto-refresh). More detail: [OAuth](/concepts/oauth). ### Where do I find an Anthropic setuptoken @@ -733,7 +733,7 @@ It is **not** in the Anthropic Console. The setup-token is generated by the **Cl claude setup-token ``` -Copy the token it prints, then choose **Anthropic token (paste setup-token)** in the wizard. If you want to run it on the gateway host, use `openclaw models auth setup-token --provider anthropic`. If you ran `claude setup-token` elsewhere, paste it on the gateway host with `openclaw models auth paste-token --provider anthropic`. See [Anthropic](/providers/anthropic). +Copy the token it prints, then choose **Anthropic token (paste setup-token)** in onboarding. If you want to run it on the gateway host, use `openclaw models auth setup-token --provider anthropic`. If you ran `claude setup-token` elsewhere, paste it on the gateway host with `openclaw models auth paste-token --provider anthropic`. See [Anthropic](/providers/anthropic). ### Do you support Claude subscription auth (Claude Pro or Max) @@ -767,15 +767,15 @@ Yes - via pi-ai's **Amazon Bedrock (Converse)** provider with **manual config**. ### How does Codex auth work -OpenClaw supports **OpenAI Code (Codex)** via OAuth (ChatGPT sign-in). The wizard can run the OAuth flow and will set the default model to `openai-codex/gpt-5.4` when appropriate. See [Model providers](/concepts/model-providers) and [Wizard](/start/wizard). +OpenClaw supports **OpenAI Code (Codex)** via OAuth (ChatGPT sign-in). Onboarding can run the OAuth flow and will set the default model to `openai-codex/gpt-5.4` when appropriate. See [Model providers](/concepts/model-providers) and [Onboarding (CLI)](/start/wizard). ### Do you support OpenAI subscription auth Codex OAuth Yes. OpenClaw fully supports **OpenAI Code (Codex) subscription OAuth**. OpenAI explicitly allows subscription OAuth usage in external tools/workflows -like OpenClaw. The onboarding wizard can run the OAuth flow for you. +like OpenClaw. Onboarding can run the OAuth flow for you. -See [OAuth](/concepts/oauth), [Model providers](/concepts/model-providers), and [Wizard](/start/wizard). +See [OAuth](/concepts/oauth), [Model providers](/concepts/model-providers), and [Onboarding (CLI)](/start/wizard). ### How do I set up Gemini CLI OAuth @@ -783,7 +783,7 @@ Gemini CLI uses a **plugin auth flow**, not a client id or secret in `openclaw.j Steps: -1. Enable the plugin: `openclaw plugins enable google-gemini-cli-auth` +1. Enable the plugin: `openclaw plugins enable google` 2. Login: `openclaw models auth login --provider google-gemini-cli --set-default` This stores OAuth tokens in auth profiles on the gateway host. Details: [Model providers](/concepts/model-providers). @@ -844,7 +844,7 @@ without WhatsApp/Telegram. `channels.telegram.allowFrom` is **the human sender's Telegram user ID** (numeric). It is not the bot username. -The onboarding wizard accepts `@username` input and resolves it to a numeric ID, but OpenClaw authorization uses numeric IDs only. +Onboarding accepts `@username` input and resolves it to a numeric ID, but OpenClaw authorization uses numeric IDs only. Safer (no third-party bot): @@ -882,7 +882,7 @@ brew install If you run OpenClaw via systemd, ensure the service PATH includes `/home/linuxbrew/.linuxbrew/bin` (or your brew prefix) so `brew`-installed tools resolve in non-login shells. Recent builds also prepend common user bin dirs on Linux systemd services (for example `~/.local/bin`, `~/.npm-global/bin`, `~/.local/share/pnpm`, `~/.bun/bin`) and honor `PNPM_HOME`, `NPM_CONFIG_PREFIX`, `BUN_INSTALL`, `VOLTA_HOME`, `ASDF_DATA_DIR`, `NVM_DIR`, and `FNM_DIR` when set. -### What's the difference between the hackable git install and npm install +### Difference between the hackable git install and npm install - **Hackable (git) install:** full source checkout, editable, best for contributors. You run builds locally and can patch code/docs. @@ -918,7 +918,7 @@ openclaw gateway restart Doctor detects a gateway service entrypoint mismatch and offers to rewrite the service config to match the current install (use `--repair` in automation). -Backup tips: see [Backup strategy](/help/faq#whats-the-recommended-backup-strategy). +Backup tips: see [Backup strategy](/help/faq#recommended-backup-strategy). ### Should I run the Gateway on my laptop or a VPS @@ -981,7 +981,7 @@ If you are running macOS in a VM, see [macOS VM](/install/macos-vm). OpenClaw is a personal AI assistant you run on your own devices. It replies on the messaging surfaces you already use (WhatsApp, Telegram, Slack, Mattermost (plugin), Discord, Google Chat, Signal, iMessage, WebChat) and can also do voice + a live Canvas on supported platforms. The **Gateway** is the always-on control plane; the assistant is the product. -### What's the value proposition +### Value proposition OpenClaw is not "just a Claude wrapper." It's a **local-first control plane** that lets you run a capable assistant on **your own hardware**, reachable from the chat apps you already use, with @@ -1214,22 +1214,23 @@ clawhub update --all ClawHub installs into `./skills` under your current directory (or falls back to your configured OpenClaw workspace); OpenClaw treats that as `/skills` on the next session. For shared skills across agents, place them in `~/.openclaw/skills//SKILL.md`. Some skills expect binaries installed via Homebrew; on Linux that means Linuxbrew (see the Homebrew Linux FAQ entry above). See [Skills](/tools/skills) and [ClawHub](/tools/clawhub). -### How do I install the Chrome extension for browser takeover +### How do I use my existing signed-in Chrome with OpenClaw -Use the built-in installer, then load the unpacked extension in Chrome: +Use the built-in `user` browser profile, which attaches through Chrome DevTools MCP: ```bash -openclaw browser extension install -openclaw browser extension path +openclaw browser --browser-profile user tabs +openclaw browser --browser-profile user snapshot ``` -Then Chrome → `chrome://extensions` → enable "Developer mode" → "Load unpacked" → pick that folder. +If you want a custom name, create an explicit MCP profile: -Full guide (including remote Gateway + security notes): [Chrome extension](/tools/chrome-extension) +```bash +openclaw browser create-profile --name chrome-live --driver existing-session +openclaw browser --browser-profile chrome-live tabs +``` -If the Gateway runs on the same machine as Chrome (default setup), you usually **do not** need anything extra. -If the Gateway runs elsewhere, run a node host on the browser machine so the Gateway can proxy browser actions. -You still need to click the extension button on the tab you want to control (it doesn't auto-attach). +This path is host-local. If the Gateway runs elsewhere, either run a node host on the browser machine or use remote CDP instead. ## Sandboxing and memory @@ -1358,7 +1359,8 @@ Your **workspace** (AGENTS.md, memory files, skills, etc.) is separate and confi These files live in the **agent workspace**, not `~/.openclaw`. - **Workspace (per agent)**: `AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, - `MEMORY.md` (or `memory.md`), `memory/YYYY-MM-DD.md`, optional `HEARTBEAT.md`. + `MEMORY.md` (or legacy fallback `memory.md` when `MEMORY.md` is absent), + `memory/YYYY-MM-DD.md`, optional `HEARTBEAT.md`. - **State dir (`~/.openclaw`)**: config, credentials, auth profiles, sessions, logs, and shared skills (`~/.openclaw/skills`). @@ -1379,7 +1381,7 @@ AGENTS.md or MEMORY.md** rather than relying on chat history. See [Agent workspace](/concepts/agent-workspace) and [Memory](/concepts/memory). -### What's the recommended backup strategy +### Recommended backup strategy Put your **agent workspace** in a **private** git repo and back it up somewhere private (for example GitHub private). This captures memory + AGENTS/SOUL/USER @@ -1503,12 +1505,22 @@ Environment alternatives: ```json5 { + plugins: { + entries: { + brave: { + config: { + webSearch: { + apiKey: "BRAVE_API_KEY_HERE", + }, + }, + }, + }, + }, tools: { web: { search: { enabled: true, provider: "brave", - apiKey: "BRAVE_API_KEY_HERE", maxResults: 5, }, fetch: { @@ -1519,6 +1531,9 @@ Environment alternatives: } ``` +Provider-specific web-search config now lives under `plugins.entries..config.webSearch.*`. +Legacy `tools.web.search.*` provider paths still load temporarily for compatibility, but they should not be used for new configs. + Notes: - If you use allowlists, add `web_search`/`web_fetch` or `group:web`. @@ -1664,13 +1679,12 @@ setup is an always-on host plus your laptop as a node. - **No inbound SSH required.** Nodes connect out to the Gateway WebSocket and use device pairing. - **Safer execution controls.** `system.run` is gated by node allowlists/approvals on that laptop. - **More device tools.** Nodes expose `canvas`, `camera`, and `screen` in addition to `system.run`. -- **Local browser automation.** Keep the Gateway on a VPS, but run Chrome locally and relay control - with the Chrome extension + a node host on the laptop. +- **Local browser automation.** Keep the Gateway on a VPS, but run Chrome locally through a node host on the laptop, or attach to local Chrome on the host via Chrome MCP. SSH is fine for ad-hoc shell access, but nodes are simpler for ongoing agent workflows and device automation. -Docs: [Nodes](/nodes), [Nodes CLI](/cli/nodes), [Chrome extension](/tools/chrome-extension). +Docs: [Nodes](/nodes), [Nodes CLI](/cli/nodes), [Browser](/tools/browser). ### Should I install on a second laptop or just add a node @@ -1713,7 +1727,7 @@ Avoid it: Docs: [Config](/cli/config), [Configure](/cli/configure), [Doctor](/gateway/doctor). -### What's a minimal sane config for a first install +### Minimal sane config for a first install ```json5 { @@ -1900,7 +1914,7 @@ Non-interactive full reset: openclaw reset --scope full --yes --non-interactive ``` -Then re-run onboarding: +Then re-run setup: ```bash openclaw onboard --install-daemon @@ -1908,7 +1922,7 @@ openclaw onboard --install-daemon Notes: -- The onboarding wizard also offers **Reset** if it sees an existing config. See [Wizard](/start/wizard). +- Onboarding also offers **Reset** if it sees an existing config. See [Onboarding (CLI)](/start/wizard). - If you used profiles (`--profile` / `OPENCLAW_PROFILE`), reset each state dir (defaults are `~/.openclaw-`). - Dev reset: `openclaw gateway --dev --reset` (dev-only; wipes dev config + credentials + sessions + workspace). @@ -2005,7 +2019,7 @@ openclaw directory groups list --channel whatsapp Docs: [WhatsApp](/channels/whatsapp), [Directory](/cli/directory), [Logs](/cli/logs). -### Why doesn't OpenClaw reply in a group +### Why does OpenClaw not reply in a group Two common causes: @@ -2038,18 +2052,18 @@ Yes. Use **Multi-Agent Routing** to run multiple isolated agents and route inbou channel/account/peer. Slack is supported as a channel and can be bound to specific agents. Browser access is powerful but not "do anything a human can" - anti-bot, CAPTCHAs, and MFA can -still block automation. For the most reliable browser control, use the Chrome extension relay -on the machine that runs the browser (and keep the Gateway anywhere). +still block automation. For the most reliable browser control, use local Chrome MCP on the host, +or use CDP on the machine that actually runs the browser. Best-practice setup: - Always-on Gateway host (VPS/Mac mini). - One agent per role (bindings). - Slack channel(s) bound to those agents. -- Local browser via extension relay (or a node) when needed. +- Local browser via Chrome MCP or a node when needed. Docs: [Multi-Agent Routing](/concepts/multi-agent), [Slack](/channels/slack), -[Browser](/tools/browser), [Chrome extension](/tools/chrome-extension), [Nodes](/nodes). +[Browser](/tools/browser), [Nodes](/nodes). ## Models: defaults, selection, aliases, switching @@ -2084,8 +2098,21 @@ More context: [Models](/concepts/models). ### Can I use selfhosted models llamacpp vLLM Ollama -Yes. If your local server exposes an OpenAI-compatible API, you can point a -custom provider at it. Ollama is supported directly and is the easiest path. +Yes. Ollama is the easiest path for local models. + +Quickest setup: + +1. Install Ollama from `https://ollama.com/download` +2. Pull a local model such as `ollama pull glm-4.7-flash` +3. If you want Ollama Cloud too, run `ollama signin` +4. Run `openclaw onboard` and choose `Ollama` +5. Pick `Local` or `Cloud + Local` + +Notes: + +- `Cloud + Local` gives you Ollama Cloud models plus your local Ollama models +- cloud models such as `kimi-k2.5:cloud` do not need a local pull +- for manual switching, use `openclaw models list` and `openclaw models set ollama/` Security note: smaller or heavily quantized models are more vulnerable to prompt injection. We strongly recommend **large models** for any bot that can use tools. @@ -2435,7 +2462,7 @@ To target a specific agent: openclaw models auth order set --provider anthropic --agent main anthropic:default ``` -### OAuth vs API key what's the difference +### OAuth vs API key - what is the difference OpenClaw supports both: @@ -2527,7 +2554,7 @@ Fix: - `openclaw devices rotate --device --role operator` - Still stuck? Run `openclaw status --all` and follow [Troubleshooting](/gateway/troubleshooting). See [Dashboard](/web/dashboard) for auth details. -### I set gatewaybind tailnet but it can't bind nothing listens +### I set gateway.bind tailnet but it cannot bind and nothing listens `tailnet` bind picks a Tailscale IP from your network interfaces (100.64.0.0/10). If the machine isn't on Tailscale (or the interface is down), there's nothing to bind to. @@ -2697,7 +2724,7 @@ openclaw logs --follow Docs: [Dashboard](/web/dashboard), [Remote access](/gateway/remote), [Troubleshooting](/gateway/troubleshooting). -### Telegram setMyCommands fails with network errors What should I check +### Telegram setMyCommands fails What should I check Start with logs and channel status: @@ -2706,7 +2733,11 @@ openclaw channels status openclaw channels logs --channel telegram ``` -If you are on a VPS or behind a proxy, confirm outbound HTTPS is allowed and DNS works. +Then match the error: + +- `BOT_COMMANDS_TOO_MUCH`: the Telegram menu has too many entries. OpenClaw already trims to the Telegram limit and retries with fewer commands, but some menu entries still need to be dropped. Reduce plugin/skill/custom commands, or disable `channels.telegram.commands.native` if you do not need the menu. +- `TypeError: fetch failed`, `Network request for 'setMyCommands' failed!`, or similar network errors: if you are on a VPS or behind a proxy, confirm outbound HTTPS is allowed and DNS works for `api.telegram.org`. + If the Gateway is remote, make sure you are looking at logs on the Gateway host. Docs: [Telegram](/channels/telegram), [Channel troubleshooting](/channels/troubleshooting). @@ -2754,7 +2785,7 @@ Docs: [Gateway service runbook](/gateway). If you installed the service, use the gateway commands. Use `openclaw gateway` when you want a one-off, foreground run. -### What's the fastest way to get more details when something fails +### Fastest way to get more details when something fails Start the Gateway with `--verbose` to get more console detail. Then inspect the log file for channel auth, model routing, and RPC errors. @@ -2836,7 +2867,7 @@ more susceptible to instruction hijacking, so avoid them for tool-enabled agents or when reading untrusted content. If you must use a smaller model, lock down tools and run inside a sandbox. See [Security](/gateway/security). -### I ran start in Telegram but didn't get a pairing code +### I ran start in Telegram but did not get a pairing code Pairing codes are sent **only** when an unknown sender messages the bot and `dmPolicy: "pairing"` is enabled. `/start` by itself doesn't generate a code. @@ -2868,7 +2899,7 @@ openclaw pairing list whatsapp Wizard phone number prompt: it's used to set your **allowlist/owner** so your own DMs are permitted. It's not used for auto-sending. If you run on your personal WhatsApp number, use that number and enable `channels.whatsapp.selfChatMode`. -## Chat commands, aborting tasks, and "it won't stop" +## Chat commands, aborting tasks, and "it will not stop" ### How do I stop internal system messages from showing in chat diff --git a/docs/help/testing.md b/docs/help/testing.md index db374bb03da..ee0a5b357a0 100644 --- a/docs/help/testing.md +++ b/docs/help/testing.md @@ -52,16 +52,31 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost): - Runs in CI - No real keys required - Should be fast and stable +- Scheduler note: + - `pnpm test` now keeps a small checked-in behavioral manifest for true pool/isolation overrides and a separate timing snapshot for the slowest unit files. + - Shared unit coverage stays on, but the wrapper peels the heaviest measured files into dedicated lanes instead of relying on a growing hand-maintained exclusion list. + - Refresh the timing snapshot with `pnpm test:perf:update-timings` after major suite shape changes. +- Embedded runner note: + - When you change message-tool discovery inputs or compaction runtime context, + keep both levels of coverage. + - Add focused helper regressions for pure routing/normalization boundaries. + - Also keep the embedded runner integration suites healthy: + `src/agents/pi-embedded-runner/compact.hooks.test.ts`, + `src/agents/pi-embedded-runner/run.overflow-compaction.test.ts`, and + `src/agents/pi-embedded-runner/run.overflow-compaction.loop.test.ts`. + - Those suites verify that scoped ids and compaction behavior still flow + through the real `run.ts` / `compact.ts` paths; helper-only tests are not a + sufficient substitute for those integration paths. - Pool note: - - OpenClaw uses Vitest `vmForks` on Node 22/23 for faster unit shards. - - On Node 24+, OpenClaw automatically falls back to regular `forks` to avoid Node VM linking errors (`ERR_VM_MODULE_LINK_FAILURE` / `module is already linked`). + - OpenClaw uses Vitest `vmForks` on Node 22, 23, and 24 for faster unit shards. + - On Node 25+, OpenClaw automatically falls back to regular `forks` until the repo is re-validated there. - Override manually with `OPENCLAW_TEST_VM_FORKS=0` (force `forks`) or `OPENCLAW_TEST_VM_FORKS=1` (force `vmForks`). ### E2E (gateway smoke) - Command: `pnpm test:e2e` - Config: `vitest.e2e.config.ts` -- Files: `src/**/*.e2e.test.ts` +- Files: `src/**/*.e2e.test.ts`, `test/**/*.e2e.test.ts` - Runtime defaults: - Uses Vitest `vmForks` for faster file startup. - Uses adaptive workers (CI: 2-4, local: 4-8). @@ -77,6 +92,23 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost): - No real keys required - More moving parts than unit tests (can be slower) +### E2E: OpenShell backend smoke + +- Command: `pnpm test:e2e:openshell` +- File: `test/openshell-sandbox.e2e.test.ts` +- Scope: + - Starts an isolated OpenShell gateway on the host via Docker + - Creates a sandbox from a temporary local Dockerfile + - Exercises OpenClaw's OpenShell backend over real `sandbox ssh-config` + SSH exec + - Verifies remote-canonical filesystem behavior through the sandbox fs bridge +- Expectations: + - Opt-in only; not part of the default `pnpm test:e2e` run + - Requires a local `openshell` CLI plus a working Docker daemon + - Uses isolated `HOME` / `XDG_CONFIG_HOME`, then destroys the test gateway and sandbox +- Useful overrides: + - `OPENCLAW_E2E_OPENSHELL=1` to enable the test when running the broader e2e suite manually + - `OPENCLAW_E2E_OPENSHELL_COMMAND=/path/to/openshell` to point at a non-default CLI binary or wrapper script + ### Live (real providers + real models) - Command: `pnpm test:live` @@ -148,7 +180,7 @@ Live tests are split into two layers so we can isolate failures: - Separates “provider API is broken / key is invalid” from “gateway agent pipeline is broken” - Contains small, isolated regressions (example: OpenAI Responses/Codex Responses reasoning replay + tool-call flows) -### Layer 2: Gateway + dev agent smoke (what “@openclaw” actually does) +### Layer 2: Gateway + dev agent smoke (what "@openclaw" actually does) - Test: `src/gateway/gateway-models.profiles.live.test.ts` - Goal: @@ -343,9 +375,33 @@ If you want to rely on env keys (e.g. exported in your `~/.profile`), run local - Enable: `BYTEPLUS_API_KEY=... BYTEPLUS_LIVE_TEST=1 pnpm test:live src/agents/byteplus.live.test.ts` - Optional model override: `BYTEPLUS_CODING_MODEL=ark-code-latest` -## Docker runners (optional “works in Linux” checks) +## Image generation live -These run `pnpm test:live` inside the repo Docker image, mounting your local config dir and workspace (and sourcing `~/.profile` if mounted): +- Test: `src/image-generation/runtime.live.test.ts` +- Command: `pnpm test:live src/image-generation/runtime.live.test.ts` +- Scope: + - Enumerates every registered image-generation provider plugin + - Loads missing provider env vars from your login shell (`~/.profile`) before probing + - Uses live/env API keys ahead of stored auth profiles by default, so stale test keys in `auth-profiles.json` do not mask real shell credentials + - Skips providers with no usable auth/profile/model + - Runs the stock image-generation variants through the shared runtime capability: + - `google:flash-generate` + - `google:pro-generate` + - `google:pro-edit` + - `openai:default-generate` +- Current bundled providers covered: + - `openai` + - `google` +- Optional narrowing: + - `OPENCLAW_LIVE_IMAGE_GENERATION_PROVIDERS="openai,google"` + - `OPENCLAW_LIVE_IMAGE_GENERATION_MODELS="openai/gpt-image-1,google/gemini-3.1-flash-image-preview"` + - `OPENCLAW_LIVE_IMAGE_GENERATION_CASES="google:flash-generate,google:pro-edit"` +- Optional auth behavior: + - `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` to force profile-store auth and ignore env-only overrides + +## Docker runners (optional "works in Linux" checks) + +These run `pnpm test:live` inside the repo Docker image, mounting your local config dir and workspace (and sourcing `~/.profile` if mounted). They also bind-mount CLI auth homes like `~/.codex`, `~/.claude`, `~/.qwen`, and `~/.minimax` when present, then copy them into the container home before the run so external-CLI OAuth can refresh tokens without mutating the host auth store: - Direct models: `pnpm test:docker:live-models` (script: `scripts/test-live-models-docker.sh`) - Gateway + dev agent: `pnpm test:docker:live-gateway` (script: `scripts/test-live-gateway-models-docker.sh`) @@ -356,6 +412,9 @@ These run `pnpm test:live` inside the repo Docker image, mounting your local con The live-model Docker runners also bind-mount the current checkout read-only and stage it into a temporary workdir inside the container. This keeps the runtime image slim while still running Vitest against your exact local source/config. +`test:docker:live-models` still runs `pnpm test:live`, so pass through +`OPENCLAW_LIVE_GATEWAY_*` as well when you need to narrow or exclude gateway +live coverage from that Docker lane. Manual ACP plain-language thread smoke (not CI): @@ -367,7 +426,9 @@ Useful env vars: - `OPENCLAW_CONFIG_DIR=...` (default: `~/.openclaw`) mounted to `/home/node/.openclaw` - `OPENCLAW_WORKSPACE_DIR=...` (default: `~/.openclaw/workspace`) mounted to `/home/node/.openclaw/workspace` - `OPENCLAW_PROFILE_FILE=...` (default: `~/.profile`) mounted to `/home/node/.profile` and sourced before running tests +- External CLI auth dirs under `$HOME` (`.codex`, `.claude`, `.qwen`, `.minimax`) are mounted read-only under `/host-auth/...`, then copied into `/home/node/...` before tests start - `OPENCLAW_LIVE_GATEWAY_MODELS=...` / `OPENCLAW_LIVE_MODELS=...` to narrow the run +- `OPENCLAW_LIVE_GATEWAY_PROVIDERS=...` / `OPENCLAW_LIVE_PROVIDERS=...` to filter providers in-container - `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` to ensure creds come from the profile store (not env) ## Docs sanity @@ -400,6 +461,55 @@ Future evals should stay deterministic first: - A small suite of skill-focused scenarios (use vs avoid, gating, prompt injection). - Optional live evals (opt-in, env-gated) only after the CI-safe suite is in place. +## Contract tests (plugin and channel shape) + +Contract tests verify that every registered plugin and channel conforms to its +interface contract. They iterate over all discovered plugins and run a suite of +shape and behavior assertions. + +### Commands + +- All contracts: `pnpm test:contracts` +- Channel contracts only: `pnpm test:contracts:channels` +- Provider contracts only: `pnpm test:contracts:plugins` + +### Channel contracts + +Located in `src/channels/plugins/contracts/*.contract.test.ts`: + +- **plugin** - Basic plugin shape (id, name, capabilities) +- **setup** - Setup wizard contract +- **session-binding** - Session binding behavior +- **outbound-payload** - Message payload structure +- **inbound** - Inbound message handling +- **actions** - Channel action handlers +- **threading** - Thread ID handling +- **directory** - Directory/roster API +- **group-policy** - Group policy enforcement +- **status** - Channel status probes +- **registry** - Plugin registry shape + +### Provider contracts + +Located in `src/plugins/contracts/*.contract.test.ts`: + +- **auth** - Auth flow contract +- **auth-choice** - Auth choice/selection +- **catalog** - Model catalog API +- **discovery** - Plugin discovery +- **loader** - Plugin loading +- **runtime** - Provider runtime +- **shape** - Plugin shape/interface +- **wizard** - Setup wizard + +### When to run + +- After changing plugin-sdk exports or subpaths +- After adding or modifying a channel or provider plugin +- After refactoring plugin registration or discovery + +Contract tests run in CI and do not require real API keys. + ## Adding regressions (guidance) When you fix a provider/model issue discovered in live: diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md index 951e1a480d7..42991a83c48 100644 --- a/docs/help/troubleshooting.md +++ b/docs/help/troubleshooting.md @@ -3,7 +3,7 @@ summary: "Symptom first troubleshooting hub for OpenClaw" read_when: - OpenClaw is not working and you need the fastest path to a fix - You want a triage flow before diving into deep runbooks -title: "Troubleshooting" +title: "General Troubleshooting" --- # Troubleshooting @@ -28,7 +28,7 @@ Good output in one line: - `openclaw status` → shows configured channels and no obvious auth errors. - `openclaw status --all` → full report is present and shareable. -- `openclaw gateway probe` → expected gateway target is reachable. +- `openclaw gateway probe` → expected gateway target is reachable (`Reachable: yes`). `RPC: limited - missing scope: operator.read` is degraded diagnostics, not a connect failure. - `openclaw gateway status` → `Runtime: running` and `RPC probe: ok`. - `openclaw doctor` → no blocking config/service errors. - `openclaw channels status --probe` → channels report `connected` or `ready`. @@ -63,7 +63,7 @@ Example: } ``` -Reference: [/tools/plugin#distribution-npm](/tools/plugin#distribution-npm) +Reference: [Plugin architecture](/plugins/architecture) ## Decision tree @@ -278,13 +278,13 @@ flowchart TD Good output looks like: - Browser status shows `running: true` and a chosen browser/profile. - - `openclaw` profile starts or `chrome` relay has an attached tab. + - `openclaw` starts, or `user` can see local Chrome tabs. Common log signatures: - `Failed to start Chrome CDP on port` → local browser launch failed. - `browser.executablePath not found` → configured binary path is wrong. - - `Chrome extension relay is running, but no tab is connected` → extension not attached. + - `No Chrome tabs found for profile="user"` → the Chrome MCP attach profile has no open local Chrome tabs. - `Browser attachOnly is enabled ... not reachable` → attach-only profile has no live CDP target. Deep pages: @@ -292,7 +292,6 @@ flowchart TD - [/gateway/troubleshooting#browser-tool-fails](/gateway/troubleshooting#browser-tool-fails) - [/tools/browser-linux-troubleshooting](/tools/browser-linux-troubleshooting) - [/tools/browser-wsl2-windows-remote-cdp-troubleshooting](/tools/browser-wsl2-windows-remote-cdp-troubleshooting) - - [/tools/chrome-extension](/tools/chrome-extension) diff --git a/docs/index.md b/docs/index.md index f838ebf4cab..25162bc9676 100644 --- a/docs/index.md +++ b/docs/index.md @@ -33,7 +33,7 @@ title: "OpenClaw" Install OpenClaw and bring up the Gateway in minutes. - + Guided setup with `openclaw onboard` and pairing flows. @@ -54,7 +54,7 @@ OpenClaw is a **self-hosted gateway** that connects your favorite chat apps — - **Agent-native**: built for coding agents with tool use, sessions, memory, and multi-agent routing - **Open source**: MIT licensed, community-driven -**What do you need?** Node 22+, an API key from your chosen provider, and 5 minutes. For best quality and security, use the strongest latest-generation model available. +**What do you need?** Node 24 (recommended), or Node 22 LTS (`22.16+`) for compatibility, an API key from your chosen provider, and 5 minutes. For best quality and security, use the strongest latest-generation model available. ## How it works diff --git a/docs/install/ansible.md b/docs/install/ansible.md index be91aedaadd..d19383398d6 100644 --- a/docs/install/ansible.md +++ b/docs/install/ansible.md @@ -46,7 +46,7 @@ The Ansible playbook installs and configures: 1. **Tailscale** (mesh VPN for secure remote access) 2. **UFW firewall** (SSH + Tailscale ports only) 3. **Docker CE + Compose V2** (for agent sandboxes) -4. **Node.js 22.x + pnpm** (runtime dependencies) +4. **Node.js 24 + pnpm** (runtime dependencies; Node 22 LTS, currently `22.16+`, remains supported for compatibility) 5. **OpenClaw** (host-based, not containerized) 6. **Systemd service** (auto-start with security hardening) @@ -154,7 +154,7 @@ If you're locked out: - SSH access (port 22) is always allowed - The gateway is **only** accessible via Tailscale by design -### Service won't start +### Service will not start ```bash # Check logs diff --git a/docs/install/bun.md b/docs/install/bun.md index 9b3dcb2c224..5cbe76ce3ac 100644 --- a/docs/install/bun.md +++ b/docs/install/bun.md @@ -45,7 +45,7 @@ bun run vitest run Bun may block dependency lifecycle scripts unless explicitly trusted (`bun pm untrusted` / `bun pm trust`). For this repo, the commonly blocked scripts are not required: -- `@whiskeysockets/baileys` `preinstall`: checks Node major >= 20 (we run Node 22+). +- `@whiskeysockets/baileys` `preinstall`: checks Node major >= 20 (OpenClaw defaults to Node 24 and still supports Node 22 LTS, currently `22.16+`). - `protobufjs` `postinstall`: emits warnings about incompatible version schemes (no build artifacts). If you hit a real runtime issue that requires these scripts, trust them explicitly: diff --git a/docs/install/development-channels.md b/docs/install/development-channels.md index a585ce9f2a9..d5eab403ce3 100644 --- a/docs/install/development-channels.md +++ b/docs/install/development-channels.md @@ -1,77 +1,119 @@ --- -summary: "Stable, beta, and dev channels: semantics, switching, and tagging" +summary: "Stable, beta, and dev channels: semantics, switching, pinning, and tagging" read_when: - You want to switch between stable/beta/dev + - You want to pin a specific version, tag, or SHA - You are tagging or publishing prereleases title: "Development Channels" --- # Development channels -Last updated: 2026-01-21 - OpenClaw ships three update channels: -- **stable**: npm dist-tag `latest`. +- **stable**: npm dist-tag `latest`. Recommended for most users. - **beta**: npm dist-tag `beta` (builds under test). - **dev**: moving head of `main` (git). npm dist-tag: `dev` (when published). + The `main` branch is for experimentation and active development. It may contain + incomplete features or breaking changes. Do not use it for production gateways. We ship builds to **beta**, test them, then **promote a vetted build to `latest`** -without changing the version number — dist-tags are the source of truth for npm installs. +without changing the version number -- dist-tags are the source of truth for npm installs. ## Switching channels -Git checkout: - ```bash openclaw update --channel stable openclaw update --channel beta openclaw update --channel dev ``` -- `stable`/`beta` check out the latest matching tag (often the same tag). -- `dev` switches to `main` and rebases on the upstream. +`--channel` persists your choice in config (`update.channel`) and aligns the +install method: -npm/pnpm global install: +- **`stable`/`beta`** (package installs): updates via the matching npm dist-tag. +- **`stable`/`beta`** (git installs): checks out the latest matching git tag. +- **`dev`**: ensures a git checkout (default `~/openclaw`, override with + `OPENCLAW_GIT_DIR`), switches to `main`, rebases on upstream, builds, and + installs the global CLI from that checkout. + +Tip: if you want stable + dev in parallel, keep two clones and point your +gateway at the stable one. + +## One-off version or tag targeting + +Use `--tag` to target a specific dist-tag, version, or package spec for a single +update **without** changing your persisted channel: ```bash -openclaw update --channel stable -openclaw update --channel beta -openclaw update --channel dev +# Install a specific version +openclaw update --tag 2026.3.14 + +# Install from the beta dist-tag (one-off, does not persist) +openclaw update --tag beta + +# Install from GitHub main branch (npm tarball) +openclaw update --tag main + +# Install a specific npm package spec +openclaw update --tag openclaw@2026.3.12 ``` -This updates via the corresponding npm dist-tag (`latest`, `beta`, `dev`). +Notes: -When you **explicitly** switch channels with `--channel`, OpenClaw also aligns -the install method: +- `--tag` applies to **package (npm) installs only**. Git installs ignore it. +- The tag is not persisted. Your next `openclaw update` uses your configured + channel as usual. +- Downgrade protection: if the target version is older than your current version, + OpenClaw prompts for confirmation (skip with `--yes`). -- `dev` ensures a git checkout (default `~/openclaw`, override with `OPENCLAW_GIT_DIR`), - updates it, and installs the global CLI from that checkout. -- `stable`/`beta` installs from npm using the matching dist-tag. +## Dry run -Tip: if you want stable + dev in parallel, keep two clones and point your gateway at the stable one. +Preview what `openclaw update` would do without making changes: + +```bash +openclaw update --dry-run +openclaw update --channel beta --dry-run +openclaw update --tag 2026.3.14 --dry-run +openclaw update --dry-run --json +``` + +The dry run shows the effective channel, target version, planned actions, and +whether a downgrade confirmation would be required. ## Plugins and channels -When you switch channels with `openclaw update`, OpenClaw also syncs plugin sources: +When you switch channels with `openclaw update`, OpenClaw also syncs plugin +sources: - `dev` prefers bundled plugins from the git checkout. - `stable` and `beta` restore npm-installed plugin packages. +- npm-installed plugins are updated after the core update completes. + +## Checking current status + +```bash +openclaw update status +``` + +Shows the active channel, install kind (git or package), current version, and +source (config, git tag, git branch, or default). ## Tagging best practices -- Tag releases you want git checkouts to land on (`vYYYY.M.D` for stable, `vYYYY.M.D-beta.N` for beta). +- Tag releases you want git checkouts to land on (`vYYYY.M.D` for stable, + `vYYYY.M.D-beta.N` for beta). - `vYYYY.M.D.beta.N` is also recognized for compatibility, but prefer `-beta.N`. - Legacy `vYYYY.M.D-` tags are still recognized as stable (non-beta). - Keep tags immutable: never move or reuse a tag. - npm dist-tags remain the source of truth for npm installs: - - `latest` → stable - - `beta` → candidate build - - `dev` → main snapshot (optional) + - `latest` -> stable + - `beta` -> candidate build + - `dev` -> main snapshot (optional) ## macOS app availability -Beta and dev builds may **not** include a macOS app release. That’s OK: +Beta and dev builds may **not** include a macOS app release. That is OK: - The git tag and npm dist-tag can still be published. -- Call out “no macOS build for this beta” in release notes or changelog. +- Call out "no macOS build for this beta" in release notes or changelog. diff --git a/docs/install/docker-vm-runtime.md b/docs/install/docker-vm-runtime.md new file mode 100644 index 00000000000..77436f44486 --- /dev/null +++ b/docs/install/docker-vm-runtime.md @@ -0,0 +1,138 @@ +--- +summary: "Shared Docker VM runtime steps for long-lived OpenClaw Gateway hosts" +read_when: + - You are deploying OpenClaw on a cloud VM with Docker + - You need the shared binary bake, persistence, and update flow +title: "Docker VM Runtime" +--- + +# Docker VM Runtime + +Shared runtime steps for VM-based Docker installs such as GCP, Hetzner, and similar VPS providers. + +## Bake required binaries into the image + +Installing binaries inside a running container is a trap. +Anything installed at runtime will be lost on restart. + +All external binaries required by skills must be installed at image build time. + +The examples below show three common binaries only: + +- `gog` for Gmail access +- `goplaces` for Google Places +- `wacli` for WhatsApp + +These are examples, not a complete list. +You may install as many binaries as needed using the same pattern. + +If you add new skills later that depend on additional binaries, you must: + +1. Update the Dockerfile +2. Rebuild the image +3. Restart the containers + +**Example Dockerfile** + +```dockerfile +FROM node:24-bookworm + +RUN apt-get update && apt-get install -y socat && rm -rf /var/lib/apt/lists/* + +# Example binary 1: Gmail CLI +RUN curl -L https://github.com/steipete/gog/releases/latest/download/gog_Linux_x86_64.tar.gz \ + | tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/gog + +# Example binary 2: Google Places CLI +RUN curl -L https://github.com/steipete/goplaces/releases/latest/download/goplaces_Linux_x86_64.tar.gz \ + | tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/goplaces + +# Example binary 3: WhatsApp CLI +RUN curl -L https://github.com/steipete/wacli/releases/latest/download/wacli_Linux_x86_64.tar.gz \ + | tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/wacli + +# Add more binaries below using the same pattern + +WORKDIR /app +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./ +COPY ui/package.json ./ui/package.json +COPY scripts ./scripts + +RUN corepack enable +RUN pnpm install --frozen-lockfile + +COPY . . +RUN pnpm build +RUN pnpm ui:install +RUN pnpm ui:build + +ENV NODE_ENV=production + +CMD ["node","dist/index.js"] +``` + +## Build and launch + +```bash +docker compose build +docker compose up -d openclaw-gateway +``` + +If build fails with `Killed` or `exit code 137` during `pnpm install --frozen-lockfile`, the VM is out of memory. +Use a larger machine class before retrying. + +Verify binaries: + +```bash +docker compose exec openclaw-gateway which gog +docker compose exec openclaw-gateway which goplaces +docker compose exec openclaw-gateway which wacli +``` + +Expected output: + +``` +/usr/local/bin/gog +/usr/local/bin/goplaces +/usr/local/bin/wacli +``` + +Verify Gateway: + +```bash +docker compose logs -f openclaw-gateway +``` + +Expected output: + +``` +[gateway] listening on ws://0.0.0.0:18789 +``` + +## What persists where + +OpenClaw runs in Docker, but Docker is not the source of truth. +All long-lived state must survive restarts, rebuilds, and reboots. + +| Component | Location | Persistence mechanism | Notes | +| ------------------- | --------------------------------- | ---------------------- | -------------------------------- | +| Gateway config | `/home/node/.openclaw/` | Host volume mount | Includes `openclaw.json`, tokens | +| Model auth profiles | `/home/node/.openclaw/` | Host volume mount | OAuth tokens, API keys | +| Skill configs | `/home/node/.openclaw/skills/` | Host volume mount | Skill-level state | +| Agent workspace | `/home/node/.openclaw/workspace/` | Host volume mount | Code and agent artifacts | +| WhatsApp session | `/home/node/.openclaw/` | Host volume mount | Preserves QR login | +| Gmail keyring | `/home/node/.openclaw/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` | +| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time | +| Node runtime | Container filesystem | Docker image | Rebuilt every image build | +| OS packages | Container filesystem | Docker image | Do not install at runtime | +| Docker container | Ephemeral | Restartable | Safe to destroy | + +## Updates + +To update OpenClaw on the VM: + +```bash +git pull +docker compose build +docker compose up -d +``` diff --git a/docs/install/docker.md b/docs/install/docker.md index c6337c3db48..f4913a5138a 100644 --- a/docs/install/docker.md +++ b/docs/install/docker.md @@ -51,7 +51,7 @@ From repo root: This script: - builds the gateway image locally (or pulls a remote image if `OPENCLAW_IMAGE` is set) -- runs the onboarding wizard +- runs onboarding - prints optional provider setup hints - starts the gateway via Docker Compose - generates a gateway token and writes it to `.env` @@ -165,13 +165,13 @@ Common tags: The main Docker image currently uses: -- `node:22-bookworm` +- `node:24-bookworm` The docker image now publishes OCI base-image annotations (sha256 is an example, and points at the pinned multi-arch manifest list for that tag): -- `org.opencontainers.image.base.name=docker.io/library/node:22-bookworm` -- `org.opencontainers.image.base.digest=sha256:b501c082306a4f528bc4038cbf2fbb58095d583d0419a259b2114b5ac53d12e9` +- `org.opencontainers.image.base.name=docker.io/library/node:24-bookworm` +- `org.opencontainers.image.base.digest=sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b` - `org.opencontainers.image.source=https://github.com/openclaw/openclaw` - `org.opencontainers.image.url=https://openclaw.ai` - `org.opencontainers.image.documentation=https://docs.openclaw.ai/install/docker` @@ -408,7 +408,7 @@ To speed up rebuilds, order your Dockerfile so dependency layers are cached. This avoids re-running `pnpm install` unless lockfiles change: ```dockerfile -FROM node:22-bookworm +FROM node:24-bookworm # Install Bun (required for build scripts) RUN curl -fsSL https://bun.sh/install | bash @@ -713,6 +713,7 @@ an optional noVNC observer (headful via Xvfb). Notes: +- Docker and other headless/container browser flows stay on raw CDP. Chrome MCP `existing-session` is for host-local Chrome, not container takeover. - Headful (Xvfb) reduces bot blocking vs headless. - Headless can still be used by setting `agents.defaults.sandbox.browser.headless=true`. - No full desktop environment (GNOME) is needed; Xvfb provides the display. diff --git a/docs/install/gcp.md b/docs/install/gcp.md index 2c6bdd8ac1f..7ff4a00d087 100644 --- a/docs/install/gcp.md +++ b/docs/install/gcp.md @@ -281,77 +281,20 @@ services: --- -## 10) Bake required binaries into the image (critical) +## 10) Shared Docker VM runtime steps -Installing binaries inside a running container is a trap. -Anything installed at runtime will be lost on restart. +Use the shared runtime guide for the common Docker host flow: -All external binaries required by skills must be installed at image build time. - -The examples below show three common binaries only: - -- `gog` for Gmail access -- `goplaces` for Google Places -- `wacli` for WhatsApp - -These are examples, not a complete list. -You may install as many binaries as needed using the same pattern. - -If you add new skills later that depend on additional binaries, you must: - -1. Update the Dockerfile -2. Rebuild the image -3. Restart the containers - -**Example Dockerfile** - -```dockerfile -FROM node:22-bookworm - -RUN apt-get update && apt-get install -y socat && rm -rf /var/lib/apt/lists/* - -# Example binary 1: Gmail CLI -RUN curl -L https://github.com/steipete/gog/releases/latest/download/gog_Linux_x86_64.tar.gz \ - | tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/gog - -# Example binary 2: Google Places CLI -RUN curl -L https://github.com/steipete/goplaces/releases/latest/download/goplaces_Linux_x86_64.tar.gz \ - | tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/goplaces - -# Example binary 3: WhatsApp CLI -RUN curl -L https://github.com/steipete/wacli/releases/latest/download/wacli_Linux_x86_64.tar.gz \ - | tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/wacli - -# Add more binaries below using the same pattern - -WORKDIR /app -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./ -COPY ui/package.json ./ui/package.json -COPY scripts ./scripts - -RUN corepack enable -RUN pnpm install --frozen-lockfile - -COPY . . -RUN pnpm build -RUN pnpm ui:install -RUN pnpm ui:build - -ENV NODE_ENV=production - -CMD ["node","dist/index.js"] -``` +- [Bake required binaries into the image](/install/docker-vm-runtime#bake-required-binaries-into-the-image) +- [Build and launch](/install/docker-vm-runtime#build-and-launch) +- [What persists where](/install/docker-vm-runtime#what-persists-where) +- [Updates](/install/docker-vm-runtime#updates) --- -## 11) Build and launch +## 11) GCP-specific launch notes -```bash -docker compose build -docker compose up -d openclaw-gateway -``` - -If build fails with `Killed` / `exit code 137` during `pnpm install --frozen-lockfile`, the VM is out of memory. Use `e2-small` minimum, or `e2-medium` for more reliable first builds. +On GCP, if build fails with `Killed` or `exit code 137` during `pnpm install --frozen-lockfile`, the VM is out of memory. Use `e2-small` minimum, or `e2-medium` for more reliable first builds. When binding to LAN (`OPENCLAW_GATEWAY_BIND=lan`), configure a trusted browser origin before continuing: @@ -361,39 +304,7 @@ docker compose run --rm openclaw-cli config set gateway.controlUi.allowedOrigins If you changed the gateway port, replace `18789` with your configured port. -Verify binaries: - -```bash -docker compose exec openclaw-gateway which gog -docker compose exec openclaw-gateway which goplaces -docker compose exec openclaw-gateway which wacli -``` - -Expected output: - -``` -/usr/local/bin/gog -/usr/local/bin/goplaces -/usr/local/bin/wacli -``` - ---- - -## 12) Verify Gateway - -```bash -docker compose logs -f openclaw-gateway -``` - -Success: - -``` -[gateway] listening on ws://0.0.0.0:18789 -``` - ---- - -## 13) Access from your laptop +## 12) Access from your laptop Create an SSH tunnel to forward the Gateway port: @@ -420,38 +331,8 @@ docker compose run --rm openclaw-cli devices list docker compose run --rm openclaw-cli devices approve ``` ---- - -## What persists where (source of truth) - -OpenClaw runs in Docker, but Docker is not the source of truth. -All long-lived state must survive restarts, rebuilds, and reboots. - -| Component | Location | Persistence mechanism | Notes | -| ------------------- | --------------------------------- | ---------------------- | -------------------------------- | -| Gateway config | `/home/node/.openclaw/` | Host volume mount | Includes `openclaw.json`, tokens | -| Model auth profiles | `/home/node/.openclaw/` | Host volume mount | OAuth tokens, API keys | -| Skill configs | `/home/node/.openclaw/skills/` | Host volume mount | Skill-level state | -| Agent workspace | `/home/node/.openclaw/workspace/` | Host volume mount | Code and agent artifacts | -| WhatsApp session | `/home/node/.openclaw/` | Host volume mount | Preserves QR login | -| Gmail keyring | `/home/node/.openclaw/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` | -| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time | -| Node runtime | Container filesystem | Docker image | Rebuilt every image build | -| OS packages | Container filesystem | Docker image | Do not install at runtime | -| Docker container | Ephemeral | Restartable | Safe to destroy | - ---- - -## Updates - -To update OpenClaw on the VM: - -```bash -cd ~/openclaw -git pull -docker compose build -docker compose up -d -``` +Need the shared persistence and update reference again? +See [Docker VM Runtime](/install/docker-vm-runtime#what-persists-where) and [Docker VM Runtime updates](/install/docker-vm-runtime#updates). --- diff --git a/docs/install/hetzner.md b/docs/install/hetzner.md index 9baf90278b8..46bc76d6243 100644 --- a/docs/install/hetzner.md +++ b/docs/install/hetzner.md @@ -202,107 +202,20 @@ services: --- -## 7) Bake required binaries into the image (critical) +## 7) Shared Docker VM runtime steps -Installing binaries inside a running container is a trap. -Anything installed at runtime will be lost on restart. +Use the shared runtime guide for the common Docker host flow: -All external binaries required by skills must be installed at image build time. - -The examples below show three common binaries only: - -- `gog` for Gmail access -- `goplaces` for Google Places -- `wacli` for WhatsApp - -These are examples, not a complete list. -You may install as many binaries as needed using the same pattern. - -If you add new skills later that depend on additional binaries, you must: - -1. Update the Dockerfile -2. Rebuild the image -3. Restart the containers - -**Example Dockerfile** - -```dockerfile -FROM node:22-bookworm - -RUN apt-get update && apt-get install -y socat && rm -rf /var/lib/apt/lists/* - -# Example binary 1: Gmail CLI -RUN curl -L https://github.com/steipete/gog/releases/latest/download/gog_Linux_x86_64.tar.gz \ - | tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/gog - -# Example binary 2: Google Places CLI -RUN curl -L https://github.com/steipete/goplaces/releases/latest/download/goplaces_Linux_x86_64.tar.gz \ - | tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/goplaces - -# Example binary 3: WhatsApp CLI -RUN curl -L https://github.com/steipete/wacli/releases/latest/download/wacli_Linux_x86_64.tar.gz \ - | tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/wacli - -# Add more binaries below using the same pattern - -WORKDIR /app -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./ -COPY ui/package.json ./ui/package.json -COPY scripts ./scripts - -RUN corepack enable -RUN pnpm install --frozen-lockfile - -COPY . . -RUN pnpm build -RUN pnpm ui:install -RUN pnpm ui:build - -ENV NODE_ENV=production - -CMD ["node","dist/index.js"] -``` +- [Bake required binaries into the image](/install/docker-vm-runtime#bake-required-binaries-into-the-image) +- [Build and launch](/install/docker-vm-runtime#build-and-launch) +- [What persists where](/install/docker-vm-runtime#what-persists-where) +- [Updates](/install/docker-vm-runtime#updates) --- -## 8) Build and launch +## 8) Hetzner-specific access -```bash -docker compose build -docker compose up -d openclaw-gateway -``` - -Verify binaries: - -```bash -docker compose exec openclaw-gateway which gog -docker compose exec openclaw-gateway which goplaces -docker compose exec openclaw-gateway which wacli -``` - -Expected output: - -``` -/usr/local/bin/gog -/usr/local/bin/goplaces -/usr/local/bin/wacli -``` - ---- - -## 9) Verify Gateway - -```bash -docker compose logs -f openclaw-gateway -``` - -Success: - -``` -[gateway] listening on ws://0.0.0.0:18789 -``` - -From your laptop: +After the shared build and launch steps, tunnel from your laptop: ```bash ssh -N -L 18789:127.0.0.1:18789 root@YOUR_VPS_IP @@ -316,25 +229,7 @@ Paste your gateway token. --- -## What persists where (source of truth) - -OpenClaw runs in Docker, but Docker is not the source of truth. -All long-lived state must survive restarts, rebuilds, and reboots. - -| Component | Location | Persistence mechanism | Notes | -| ------------------- | --------------------------------- | ---------------------- | -------------------------------- | -| Gateway config | `/home/node/.openclaw/` | Host volume mount | Includes `openclaw.json`, tokens | -| Model auth profiles | `/home/node/.openclaw/` | Host volume mount | OAuth tokens, API keys | -| Skill configs | `/home/node/.openclaw/skills/` | Host volume mount | Skill-level state | -| Agent workspace | `/home/node/.openclaw/workspace/` | Host volume mount | Code and agent artifacts | -| WhatsApp session | `/home/node/.openclaw/` | Host volume mount | Preserves QR login | -| Gmail keyring | `/home/node/.openclaw/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` | -| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time | -| Node runtime | Container filesystem | Docker image | Rebuilt every image build | -| OS packages | Container filesystem | Docker image | Do not install at runtime | -| Docker container | Ephemeral | Restartable | Safe to destroy | - ---- +The shared persistence map lives in [Docker VM Runtime](/install/docker-vm-runtime#what-persists-where). ## Infrastructure as Code (Terraform) diff --git a/docs/install/index.md b/docs/install/index.md index 285324ed6b7..7130cf9faac 100644 --- a/docs/install/index.md +++ b/docs/install/index.md @@ -13,7 +13,7 @@ Already followed [Getting Started](/start/getting-started)? You're all set — t ## System requirements -- **[Node 22+](/install/node)** (the [installer script](#install-methods) will install it if missing) +- **[Node 24 (recommended)](/install/node)** (Node 22 LTS, currently `22.16+`, is still supported for compatibility; the [installer script](#install-methods) will install Node 24 if missing) - macOS, Linux, or Windows - `pnpm` only if you build from source @@ -33,7 +33,7 @@ For VPS/cloud hosts, avoid third-party "1-click" marketplace images when possibl - Downloads the CLI, installs it globally via npm, and launches the onboarding wizard. + Downloads the CLI, installs it globally via npm, and launches onboarding. @@ -70,7 +70,7 @@ For VPS/cloud hosts, avoid third-party "1-click" marketplace images when possibl - If you already have Node 22+ and prefer to manage the install yourself: + If you already manage Node yourself, we recommend Node 24. OpenClaw still supports Node 22 LTS, currently `22.16+`, for compatibility: @@ -102,6 +102,16 @@ For VPS/cloud hosts, avoid third-party "1-click" marketplace images when possibl + Want the current GitHub `main` head with a package-manager install? + + ```bash + npm install -g github:openclaw/openclaw#main + ``` + + ```bash + pnpm add -g github:openclaw/openclaw#main + ``` + diff --git a/docs/install/installer.md b/docs/install/installer.md index 78334681ad4..5859c22fd0d 100644 --- a/docs/install/installer.md +++ b/docs/install/installer.md @@ -70,8 +70,8 @@ Recommended for most interactive installs on macOS/Linux/WSL. Supports macOS and Linux (including WSL). If macOS is detected, installs Homebrew if missing. - - Checks Node version and installs Node 22 if needed (Homebrew on macOS, NodeSource setup scripts on Linux apt/dnf/yum). + + Checks Node version and installs Node 24 if needed (Homebrew on macOS, NodeSource setup scripts on Linux apt/dnf/yum). OpenClaw still supports Node 22 LTS, currently `22.16+`, for compatibility. Installs Git if missing. @@ -116,6 +116,11 @@ The script exits with code `2` for invalid method selection or invalid `--instal curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --install-method git ``` + + ```bash + curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --version main + ``` + ```bash curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --dry-run @@ -126,39 +131,39 @@ The script exits with code `2` for invalid method selection or invalid `--instal -| Flag | Description | -| ------------------------------- | ---------------------------------------------------------- | -| `--install-method npm\|git` | Choose install method (default: `npm`). Alias: `--method` | -| `--npm` | Shortcut for npm method | -| `--git` | Shortcut for git method. Alias: `--github` | -| `--version ` | npm version or dist-tag (default: `latest`) | -| `--beta` | Use beta dist-tag if available, else fallback to `latest` | -| `--git-dir ` | Checkout directory (default: `~/openclaw`). Alias: `--dir` | -| `--no-git-update` | Skip `git pull` for existing checkout | -| `--no-prompt` | Disable prompts | -| `--no-onboard` | Skip onboarding | -| `--onboard` | Enable onboarding | -| `--dry-run` | Print actions without applying changes | -| `--verbose` | Enable debug output (`set -x`, npm notice-level logs) | -| `--help` | Show usage (`-h`) | +| Flag | Description | +| ------------------------------------- | ---------------------------------------------------------- | +| `--install-method npm\|git` | Choose install method (default: `npm`). Alias: `--method` | +| `--npm` | Shortcut for npm method | +| `--git` | Shortcut for git method. Alias: `--github` | +| `--version ` | npm version, dist-tag, or package spec (default: `latest`) | +| `--beta` | Use beta dist-tag if available, else fallback to `latest` | +| `--git-dir ` | Checkout directory (default: `~/openclaw`). Alias: `--dir` | +| `--no-git-update` | Skip `git pull` for existing checkout | +| `--no-prompt` | Disable prompts | +| `--no-onboard` | Skip onboarding | +| `--onboard` | Enable onboarding | +| `--dry-run` | Print actions without applying changes | +| `--verbose` | Enable debug output (`set -x`, npm notice-level logs) | +| `--help` | Show usage (`-h`) | -| Variable | Description | -| ------------------------------------------- | --------------------------------------------- | -| `OPENCLAW_INSTALL_METHOD=git\|npm` | Install method | -| `OPENCLAW_VERSION=latest\|next\|` | npm version or dist-tag | -| `OPENCLAW_BETA=0\|1` | Use beta if available | -| `OPENCLAW_GIT_DIR=` | Checkout directory | -| `OPENCLAW_GIT_UPDATE=0\|1` | Toggle git updates | -| `OPENCLAW_NO_PROMPT=1` | Disable prompts | -| `OPENCLAW_NO_ONBOARD=1` | Skip onboarding | -| `OPENCLAW_DRY_RUN=1` | Dry run mode | -| `OPENCLAW_VERBOSE=1` | Debug mode | -| `OPENCLAW_NPM_LOGLEVEL=error\|warn\|notice` | npm log level | -| `SHARP_IGNORE_GLOBAL_LIBVIPS=0\|1` | Control sharp/libvips behavior (default: `1`) | +| Variable | Description | +| ------------------------------------------------------- | --------------------------------------------- | +| `OPENCLAW_INSTALL_METHOD=git\|npm` | Install method | +| `OPENCLAW_VERSION=latest\|next\|main\|\|` | npm version, dist-tag, or package spec | +| `OPENCLAW_BETA=0\|1` | Use beta if available | +| `OPENCLAW_GIT_DIR=` | Checkout directory | +| `OPENCLAW_GIT_UPDATE=0\|1` | Toggle git updates | +| `OPENCLAW_NO_PROMPT=1` | Disable prompts | +| `OPENCLAW_NO_ONBOARD=1` | Skip onboarding | +| `OPENCLAW_DRY_RUN=1` | Dry run mode | +| `OPENCLAW_VERBOSE=1` | Debug mode | +| `OPENCLAW_NPM_LOGLEVEL=error\|warn\|notice` | npm log level | +| `SHARP_IGNORE_GLOBAL_LIBVIPS=0\|1` | Control sharp/libvips behavior (default: `1`) | @@ -175,7 +180,7 @@ Designed for environments where you want everything under a local prefix (defaul - Downloads Node tarball (default `22.22.0`) to `/tools/node-v` and verifies SHA-256. + Downloads a pinned supported Node tarball (currently default `22.22.0`) to `/tools/node-v` and verifies SHA-256. If Git is missing, attempts install via apt/dnf/yum on Linux or Homebrew on macOS. @@ -251,8 +256,8 @@ Designed for environments where you want everything under a local prefix (defaul Requires PowerShell 5+. - - If missing, attempts install via winget, then Chocolatey, then Scoop. + + If missing, attempts install via winget, then Chocolatey, then Scoop. Node 22 LTS, currently `22.16+`, remains supported for compatibility. - `npm` method (default): global npm install using selected `-Tag` @@ -276,6 +281,11 @@ Designed for environments where you want everything under a local prefix (defaul & ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -InstallMethod git ``` + + ```powershell + & ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -Tag main + ``` + ```powershell & ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -InstallMethod git -GitDir "C:\openclaw" @@ -299,14 +309,14 @@ Designed for environments where you want everything under a local prefix (defaul -| Flag | Description | -| ------------------------- | ------------------------------------------------------ | -| `-InstallMethod npm\|git` | Install method (default: `npm`) | -| `-Tag ` | npm dist-tag (default: `latest`) | -| `-GitDir ` | Checkout directory (default: `%USERPROFILE%\openclaw`) | -| `-NoOnboard` | Skip onboarding | -| `-NoGitUpdate` | Skip `git pull` | -| `-DryRun` | Print actions only | +| Flag | Description | +| --------------------------- | ---------------------------------------------------------- | +| `-InstallMethod npm\|git` | Install method (default: `npm`) | +| `-Tag ` | npm dist-tag, version, or package spec (default: `latest`) | +| `-GitDir ` | Checkout directory (default: `%USERPROFILE%\openclaw`) | +| `-NoOnboard` | Skip onboarding | +| `-NoGitUpdate` | Skip `git pull` | +| `-DryRun` | Print actions only | diff --git a/docs/install/kubernetes.md b/docs/install/kubernetes.md new file mode 100644 index 00000000000..577ff9d2df5 --- /dev/null +++ b/docs/install/kubernetes.md @@ -0,0 +1,191 @@ +--- +summary: "Deploy OpenClaw Gateway to a Kubernetes cluster with Kustomize" +read_when: + - You want to run OpenClaw on a Kubernetes cluster + - You want to test OpenClaw in a Kubernetes environment +title: "Kubernetes" +--- + +# OpenClaw on Kubernetes + +A minimal starting point for running OpenClaw on Kubernetes — not a production-ready deployment. It covers the core resources and is meant to be adapted to your environment. + +## Why not Helm? + +OpenClaw is a single container with some config files. The interesting customization is in agent content (markdown files, skills, config overrides), not infrastructure templating. Kustomize handles overlays without the overhead of a Helm chart. If your deployment grows more complex, a Helm chart can be layered on top of these manifests. + +## What you need + +- A running Kubernetes cluster (AKS, EKS, GKE, k3s, kind, OpenShift, etc.) +- `kubectl` connected to your cluster +- An API key for at least one model provider + +## Quick start + +```bash +# Replace with your provider: ANTHROPIC, GEMINI, OPENAI, or OPENROUTER +export _API_KEY="..." +./scripts/k8s/deploy.sh + +kubectl port-forward svc/openclaw 18789:18789 -n openclaw +open http://localhost:18789 +``` + +Retrieve the gateway token and paste it into the Control UI: + +```bash +kubectl get secret openclaw-secrets -n openclaw -o jsonpath='{.data.OPENCLAW_GATEWAY_TOKEN}' | base64 -d +``` + +For local debugging, `./scripts/k8s/deploy.sh --show-token` prints the token after deploy. + +## Local testing with Kind + +If you don't have a cluster, create one locally with [Kind](https://kind.sigs.k8s.io/): + +```bash +./scripts/k8s/create-kind.sh # auto-detects docker or podman +./scripts/k8s/create-kind.sh --delete # tear down +``` + +Then deploy as usual with `./scripts/k8s/deploy.sh`. + +## Step by step + +### 1) Deploy + +**Option A** — API key in environment (one step): + +```bash +# Replace with your provider: ANTHROPIC, GEMINI, OPENAI, or OPENROUTER +export _API_KEY="..." +./scripts/k8s/deploy.sh +``` + +The script creates a Kubernetes Secret with the API key and an auto-generated gateway token, then deploys. If the Secret already exists, it preserves the current gateway token and any provider keys not being changed. + +**Option B** — create the secret separately: + +```bash +export _API_KEY="..." +./scripts/k8s/deploy.sh --create-secret +./scripts/k8s/deploy.sh +``` + +Use `--show-token` with either command if you want the token printed to stdout for local testing. + +### 2) Access the gateway + +```bash +kubectl port-forward svc/openclaw 18789:18789 -n openclaw +open http://localhost:18789 +``` + +## What gets deployed + +``` +Namespace: openclaw (configurable via OPENCLAW_NAMESPACE) +├── Deployment/openclaw # Single pod, init container + gateway +├── Service/openclaw # ClusterIP on port 18789 +├── PersistentVolumeClaim # 10Gi for agent state and config +├── ConfigMap/openclaw-config # openclaw.json + AGENTS.md +└── Secret/openclaw-secrets # Gateway token + API keys +``` + +## Customization + +### Agent instructions + +Edit the `AGENTS.md` in `scripts/k8s/manifests/configmap.yaml` and redeploy: + +```bash +./scripts/k8s/deploy.sh +``` + +### Gateway config + +Edit `openclaw.json` in `scripts/k8s/manifests/configmap.yaml`. See [Gateway configuration](/gateway/configuration) for the full reference. + +### Add providers + +Re-run with additional keys exported: + +```bash +export ANTHROPIC_API_KEY="..." +export OPENAI_API_KEY="..." +./scripts/k8s/deploy.sh --create-secret +./scripts/k8s/deploy.sh +``` + +Existing provider keys stay in the Secret unless you overwrite them. + +Or patch the Secret directly: + +```bash +kubectl patch secret openclaw-secrets -n openclaw \ + -p '{"stringData":{"_API_KEY":"..."}}' +kubectl rollout restart deployment/openclaw -n openclaw +``` + +### Custom namespace + +```bash +OPENCLAW_NAMESPACE=my-namespace ./scripts/k8s/deploy.sh +``` + +### Custom image + +Edit the `image` field in `scripts/k8s/manifests/deployment.yaml`: + +```yaml +image: ghcr.io/openclaw/openclaw:2026.3.1 +``` + +### Expose beyond port-forward + +The default manifests bind the gateway to loopback inside the pod. That works with `kubectl port-forward`, but it does not work with a Kubernetes `Service` or Ingress path that needs to reach the pod IP. + +If you want to expose the gateway through an Ingress or load balancer: + +- Change the gateway bind in `scripts/k8s/manifests/configmap.yaml` from `loopback` to a non-loopback bind that matches your deployment model +- Keep gateway auth enabled and use a proper TLS-terminated entrypoint +- Configure the Control UI for remote access using the supported web security model (for example HTTPS/Tailscale Serve and explicit allowed origins when needed) + +## Re-deploy + +```bash +./scripts/k8s/deploy.sh +``` + +This applies all manifests and restarts the pod to pick up any config or secret changes. + +## Teardown + +```bash +./scripts/k8s/deploy.sh --delete +``` + +This deletes the namespace and all resources in it, including the PVC. + +## Architecture notes + +- The gateway binds to loopback inside the pod by default, so the included setup is for `kubectl port-forward` +- No cluster-scoped resources — everything lives in a single namespace +- Security: `readOnlyRootFilesystem`, `drop: ALL` capabilities, non-root user (UID 1000) +- The default config keeps the Control UI on the safer local-access path: loopback bind plus `kubectl port-forward` to `http://127.0.0.1:18789` +- If you move beyond localhost access, use the supported remote model: HTTPS/Tailscale plus the appropriate gateway bind and Control UI origin settings +- Secrets are generated in a temp directory and applied directly to the cluster — no secret material is written to the repo checkout + +## File structure + +``` +scripts/k8s/ +├── deploy.sh # Creates namespace + secret, deploys via kustomize +├── create-kind.sh # Local Kind cluster (auto-detects docker/podman) +└── manifests/ + ├── kustomization.yaml # Kustomize base + ├── configmap.yaml # openclaw.json + AGENTS.md + ├── deployment.yaml # Pod spec with security hardening + ├── pvc.yaml # 10Gi persistent storage + └── service.yaml # ClusterIP on 18789 +``` diff --git a/docs/install/macos-vm.md b/docs/install/macos-vm.md index f2eadfda113..2bbd8e65051 100644 --- a/docs/install/macos-vm.md +++ b/docs/install/macos-vm.md @@ -112,7 +112,7 @@ After setup completes, enable SSH: --- -## 4) Get the VM's IP address +## 4) Get the VM IP address ```bash lume get openclaw diff --git a/docs/install/migrating-matrix.md b/docs/install/migrating-matrix.md new file mode 100644 index 00000000000..d1e85c5ecd1 --- /dev/null +++ b/docs/install/migrating-matrix.md @@ -0,0 +1,344 @@ +--- +summary: "How OpenClaw upgrades the previous Matrix plugin in place, including encrypted-state recovery limits and manual recovery steps." +read_when: + - Upgrading an existing Matrix installation + - Migrating encrypted Matrix history and device state +title: "Matrix migration" +--- + +# Matrix migration + +This page covers upgrades from the previous public `matrix` plugin to the current implementation. + +For most users, the upgrade is in place: + +- the plugin stays `@openclaw/matrix` +- the channel stays `matrix` +- your config stays under `channels.matrix` +- cached credentials stay under `~/.openclaw/credentials/matrix/` +- runtime state stays under `~/.openclaw/matrix/` + +You do not need to rename config keys or reinstall the plugin under a new name. + +## What the migration does automatically + +When the gateway starts, and when you run [`openclaw doctor --fix`](/gateway/doctor), OpenClaw tries to repair old Matrix state automatically. +Before any actionable Matrix migration step mutates on-disk state, OpenClaw creates or reuses a focused recovery snapshot. + +When you use `openclaw update`, the exact trigger depends on how OpenClaw is installed: + +- source installs run `openclaw doctor --fix` during the update flow, then restart the gateway by default +- package-manager installs update the package, run a non-interactive doctor pass, then rely on the default gateway restart so startup can finish Matrix migration +- if you use `openclaw update --no-restart`, startup-backed Matrix migration is deferred until you later run `openclaw doctor --fix` and restart the gateway + +Automatic migration covers: + +- creating or reusing a pre-migration snapshot under `~/Backups/openclaw-migrations/` +- reusing your cached Matrix credentials +- keeping the same account selection and `channels.matrix` config +- moving the oldest flat Matrix sync store into the current account-scoped location +- moving the oldest flat Matrix crypto store into the current account-scoped location when the target account can be resolved safely +- extracting a previously saved Matrix room-key backup decryption key from the old rust crypto store, when that key exists locally +- reusing the most complete existing token-hash storage root for the same Matrix account, homeserver, and user when the access token changes later +- scanning sibling token-hash storage roots for pending encrypted-state restore metadata when the Matrix access token changed but the account/device identity stayed the same +- restoring backed-up room keys into the new crypto store on the next Matrix startup + +Snapshot details: + +- OpenClaw writes a marker file at `~/.openclaw/matrix/migration-snapshot.json` after a successful snapshot so later startup and repair passes can reuse the same archive. +- These automatic Matrix migration snapshots back up config + state only (`includeWorkspace: false`). +- If Matrix only has warning-only migration state, for example because `userId` or `accessToken` is still missing, OpenClaw does not create the snapshot yet because no Matrix mutation is actionable. +- If the snapshot step fails, OpenClaw skips Matrix migration for that run instead of mutating state without a recovery point. + +About multi-account upgrades: + +- the oldest flat Matrix store (`~/.openclaw/matrix/bot-storage.json` and `~/.openclaw/matrix/crypto/`) came from a single-store layout, so OpenClaw can only migrate it into one resolved Matrix account target +- already account-scoped legacy Matrix stores are detected and prepared per configured Matrix account + +## What the migration cannot do automatically + +The previous public Matrix plugin did **not** automatically create Matrix room-key backups. It persisted local crypto state and requested device verification, but it did not guarantee that your room keys were backed up to the homeserver. + +That means some encrypted installs can only be migrated partially. + +OpenClaw cannot automatically recover: + +- local-only room keys that were never backed up +- encrypted state when the target Matrix account cannot be resolved yet because `homeserver`, `userId`, or `accessToken` are still unavailable +- automatic migration of one shared flat Matrix store when multiple Matrix accounts are configured but `channels.matrix.defaultAccount` is not set +- custom plugin path installs that are pinned to a repo path instead of the standard Matrix package +- a missing recovery key when the old store had backed-up keys but did not keep the decryption key locally + +Current warning scope: + +- custom Matrix plugin path installs are surfaced by both gateway startup and `openclaw doctor` + +If your old installation had local-only encrypted history that was never backed up, some older encrypted messages may remain unreadable after the upgrade. + +## Recommended upgrade flow + +1. Update OpenClaw and the Matrix plugin normally. + Prefer plain `openclaw update` without `--no-restart` so startup can finish the Matrix migration immediately. +2. Run: + + ```bash + openclaw doctor --fix + ``` + + If Matrix has actionable migration work, doctor will create or reuse the pre-migration snapshot first and print the archive path. + +3. Start or restart the gateway. +4. Check current verification and backup state: + + ```bash + openclaw matrix verify status + openclaw matrix verify backup status + ``` + +5. If OpenClaw tells you a recovery key is needed, run: + + ```bash + openclaw matrix verify backup restore --recovery-key "" + ``` + +6. If this device is still unverified, run: + + ```bash + openclaw matrix verify device "" + ``` + +7. If you are intentionally abandoning unrecoverable old history and want a fresh backup baseline for future messages, run: + + ```bash + openclaw matrix verify backup reset --yes + ``` + +8. If no server-side key backup exists yet, create one for future recoveries: + + ```bash + openclaw matrix verify bootstrap + ``` + +## How encrypted migration works + +Encrypted migration is a two-stage process: + +1. Startup or `openclaw doctor --fix` creates or reuses the pre-migration snapshot if encrypted migration is actionable. +2. Startup or `openclaw doctor --fix` inspects the old Matrix crypto store through the active Matrix plugin install. +3. If a backup decryption key is found, OpenClaw writes it into the new recovery-key flow and marks room-key restore as pending. +4. On the next Matrix startup, OpenClaw restores backed-up room keys into the new crypto store automatically. + +If the old store reports room keys that were never backed up, OpenClaw warns instead of pretending recovery succeeded. + +## Common messages and what they mean + +### Upgrade and detection messages + +`Matrix plugin upgraded in place.` + +- Meaning: the old on-disk Matrix state was detected and migrated into the current layout. +- What to do: nothing unless the same output also includes warnings. + +`Matrix migration snapshot created before applying Matrix upgrades.` + +- Meaning: OpenClaw created a recovery archive before mutating Matrix state. +- What to do: keep the printed archive path until you confirm migration succeeded. + +`Matrix migration snapshot reused before applying Matrix upgrades.` + +- Meaning: OpenClaw found an existing Matrix migration snapshot marker and reused that archive instead of creating a duplicate backup. +- What to do: keep the printed archive path until you confirm migration succeeded. + +`Legacy Matrix state detected at ... but channels.matrix is not configured yet.` + +- Meaning: old Matrix state exists, but OpenClaw cannot map it to a current Matrix account because Matrix is not configured. +- What to do: configure `channels.matrix`, then rerun `openclaw doctor --fix` or restart the gateway. + +`Legacy Matrix state detected at ... but the new account-scoped target could not be resolved yet (need homeserver, userId, and access token for channels.matrix...).` + +- Meaning: OpenClaw found old state, but it still cannot determine the exact current account/device root. +- What to do: start the gateway once with a working Matrix login, or rerun `openclaw doctor --fix` after cached credentials exist. + +`Legacy Matrix state detected at ... but multiple Matrix accounts are configured and channels.matrix.defaultAccount is not set.` + +- Meaning: OpenClaw found one shared flat Matrix store, but it refuses to guess which named Matrix account should receive it. +- What to do: set `channels.matrix.defaultAccount` to the intended account, then rerun `openclaw doctor --fix` or restart the gateway. + +`Matrix legacy sync store not migrated because the target already exists (...)` + +- Meaning: the new account-scoped location already has a sync or crypto store, so OpenClaw did not overwrite it automatically. +- What to do: verify that the current account is the correct one before manually removing or moving the conflicting target. + +`Failed migrating Matrix legacy sync store (...)` or `Failed migrating Matrix legacy crypto store (...)` + +- Meaning: OpenClaw tried to move old Matrix state but the filesystem operation failed. +- What to do: inspect filesystem permissions and disk state, then rerun `openclaw doctor --fix`. + +`Legacy Matrix encrypted state detected at ... but channels.matrix is not configured yet.` + +- Meaning: OpenClaw found an old encrypted Matrix store, but there is no current Matrix config to attach it to. +- What to do: configure `channels.matrix`, then rerun `openclaw doctor --fix` or restart the gateway. + +`Legacy Matrix encrypted state detected at ... but the account-scoped target could not be resolved yet (need homeserver, userId, and access token for channels.matrix...).` + +- Meaning: the encrypted store exists, but OpenClaw cannot safely decide which current account/device it belongs to. +- What to do: start the gateway once with a working Matrix login, or rerun `openclaw doctor --fix` after cached credentials are available. + +`Legacy Matrix encrypted state detected at ... but multiple Matrix accounts are configured and channels.matrix.defaultAccount is not set.` + +- Meaning: OpenClaw found one shared flat legacy crypto store, but it refuses to guess which named Matrix account should receive it. +- What to do: set `channels.matrix.defaultAccount` to the intended account, then rerun `openclaw doctor --fix` or restart the gateway. + +`Matrix migration warnings are present, but no on-disk Matrix mutation is actionable yet. No pre-migration snapshot was needed.` + +- Meaning: OpenClaw detected old Matrix state, but the migration is still blocked on missing identity or credential data. +- What to do: finish Matrix login or config setup, then rerun `openclaw doctor --fix` or restart the gateway. + +`Legacy Matrix encrypted state was detected, but the Matrix plugin helper is unavailable. Install or repair @openclaw/matrix so OpenClaw can inspect the old rust crypto store before upgrading.` + +- Meaning: OpenClaw found old encrypted Matrix state, but it could not load the helper entrypoint from the Matrix plugin that normally inspects that store. +- What to do: reinstall or repair the Matrix plugin (`openclaw plugins install @openclaw/matrix`, or `openclaw plugins install ./extensions/matrix` for a repo checkout), then rerun `openclaw doctor --fix` or restart the gateway. + +`Matrix plugin helper path is unsafe: ... Reinstall @openclaw/matrix and try again.` + +- Meaning: OpenClaw found a helper file path that escapes the plugin root or fails plugin boundary checks, so it refused to import it. +- What to do: reinstall the Matrix plugin from a trusted path, then rerun `openclaw doctor --fix` or restart the gateway. + +`gateway: failed creating a Matrix migration snapshot; skipping Matrix migration for now: ...` + +- Meaning: OpenClaw refused to mutate Matrix state because it could not create the recovery snapshot first. +- What to do: resolve the backup error, then rerun `openclaw doctor --fix` or restart the gateway. + +`Failed migrating legacy Matrix client storage: ...` + +- Meaning: the Matrix client-side fallback found old flat storage, but the move failed. OpenClaw now aborts that fallback instead of silently starting with a fresh store. +- What to do: inspect filesystem permissions or conflicts, keep the old state intact, and retry after fixing the error. + +`Matrix is installed from a custom path: ...` + +- Meaning: Matrix is pinned to a path install, so mainline updates do not automatically replace it with the repo's standard Matrix package. +- What to do: reinstall with `openclaw plugins install @openclaw/matrix` when you want to return to the default Matrix plugin. + +### Encrypted-state recovery messages + +`matrix: restored X/Y room key(s) from legacy encrypted-state backup` + +- Meaning: backed-up room keys were restored successfully into the new crypto store. +- What to do: usually nothing. + +`matrix: N legacy local-only room key(s) were never backed up and could not be restored automatically` + +- Meaning: some old room keys existed only in the old local store and had never been uploaded to Matrix backup. +- What to do: expect some old encrypted history to remain unavailable unless you can recover those keys manually from another verified client. + +`Legacy Matrix encrypted state for account "..." has backed-up room keys, but no local backup decryption key was found. Ask the operator to run "openclaw matrix verify backup restore --recovery-key " after upgrade if they have the recovery key.` + +- Meaning: backup exists, but OpenClaw could not recover the recovery key automatically. +- What to do: run `openclaw matrix verify backup restore --recovery-key ""`. + +`Failed inspecting legacy Matrix encrypted state for account "...": ...` + +- Meaning: OpenClaw found the old encrypted store, but it could not inspect it safely enough to prepare recovery. +- What to do: rerun `openclaw doctor --fix`. If it repeats, keep the old state directory intact and recover using another verified Matrix client plus `openclaw matrix verify backup restore --recovery-key ""`. + +`Legacy Matrix backup key was found for account "...", but .../recovery-key.json already contains a different recovery key. Leaving the existing file unchanged.` + +- Meaning: OpenClaw detected a backup key conflict and refused to overwrite the current recovery-key file automatically. +- What to do: verify which recovery key is correct before retrying any restore command. + +`Legacy Matrix encrypted state for account "..." cannot be fully converted automatically because the old rust crypto store does not expose all local room keys for export.` + +- Meaning: this is the hard limit of the old storage format. +- What to do: backed-up keys can still be restored, but local-only encrypted history may remain unavailable. + +`matrix: failed restoring room keys from legacy encrypted-state backup: ...` + +- Meaning: the new plugin attempted restore but Matrix returned an error. +- What to do: run `openclaw matrix verify backup status`, then retry with `openclaw matrix verify backup restore --recovery-key ""` if needed. + +### Manual recovery messages + +`Backup key is not loaded on this device. Run 'openclaw matrix verify backup restore' to load it and restore old room keys.` + +- Meaning: OpenClaw knows you should have a backup key, but it is not active on this device. +- What to do: run `openclaw matrix verify backup restore`, or pass `--recovery-key` if needed. + +`Store a recovery key with 'openclaw matrix verify device ', then run 'openclaw matrix verify backup restore'.` + +- Meaning: this device does not currently have the recovery key stored. +- What to do: verify the device with your recovery key first, then restore the backup. + +`Backup key mismatch on this device. Re-run 'openclaw matrix verify device ' with the matching recovery key.` + +- Meaning: the stored key does not match the active Matrix backup. +- What to do: rerun `openclaw matrix verify device ""` with the correct key. + +If you accept losing unrecoverable old encrypted history, you can instead reset the current backup baseline with `openclaw matrix verify backup reset --yes`. + +`Backup trust chain is not verified on this device. Re-run 'openclaw matrix verify device '.` + +- Meaning: the backup exists, but this device does not trust the cross-signing chain strongly enough yet. +- What to do: rerun `openclaw matrix verify device ""`. + +`Matrix recovery key is required` + +- Meaning: you tried a recovery step without supplying a recovery key when one was required. +- What to do: rerun the command with your recovery key. + +`Invalid Matrix recovery key: ...` + +- Meaning: the provided key could not be parsed or did not match the expected format. +- What to do: retry with the exact recovery key from your Matrix client or recovery-key file. + +`Matrix device is still unverified after applying recovery key. Verify your recovery key and ensure cross-signing is available.` + +- Meaning: the key was applied, but the device still could not complete verification. +- What to do: confirm you used the correct key and that cross-signing is available on the account, then retry. + +`Matrix key backup is not active on this device after loading from secret storage.` + +- Meaning: secret storage did not produce an active backup session on this device. +- What to do: verify the device first, then recheck with `openclaw matrix verify backup status`. + +`Matrix crypto backend cannot load backup keys from secret storage. Verify this device with 'openclaw matrix verify device ' first.` + +- Meaning: this device cannot restore from secret storage until device verification is complete. +- What to do: run `openclaw matrix verify device ""` first. + +### Custom plugin install messages + +`Matrix is installed from a custom path that no longer exists: ...` + +- Meaning: your plugin install record points at a local path that is gone. +- What to do: reinstall with `openclaw plugins install @openclaw/matrix`, or if you are running from a repo checkout, `openclaw plugins install ./extensions/matrix`. + +## If encrypted history still does not come back + +Run these checks in order: + +```bash +openclaw matrix verify status --verbose +openclaw matrix verify backup status --verbose +openclaw matrix verify backup restore --recovery-key "" --verbose +``` + +If the backup restores successfully but some old rooms are still missing history, those missing keys were probably never backed up by the previous plugin. + +## If you want to start fresh for future messages + +If you accept losing unrecoverable old encrypted history and only want a clean backup baseline going forward, run these commands in order: + +```bash +openclaw matrix verify backup reset --yes +openclaw matrix verify backup status --verbose +openclaw matrix verify status +``` + +If the device is still unverified after that, finish verification from your Matrix client by comparing the SAS emoji or decimal codes and confirming that they match. + +## Related pages + +- [Matrix](/channels/matrix) +- [Doctor](/gateway/doctor) +- [Migrating](/install/migrating) +- [Plugins](/tools/plugin) diff --git a/docs/install/migrating.md b/docs/install/migrating.md index f9e82fd9777..64c136be425 100644 --- a/docs/install/migrating.md +++ b/docs/install/migrating.md @@ -1,5 +1,5 @@ --- -summary: "Move (migrate) a OpenClaw install from one machine to another" +summary: "Move (migrate) an OpenClaw install from one machine to another" read_when: - You are moving OpenClaw to a new laptop/server - You want to preserve sessions, auth, and channel logins (WhatsApp, etc.) @@ -8,7 +8,7 @@ title: "Migration Guide" # Migrating OpenClaw to a new machine -This guide migrates a OpenClaw Gateway from one machine to another **without redoing onboarding**. +This guide migrates an OpenClaw Gateway from one machine to another **without redoing onboarding**. The migration is simple conceptually: @@ -67,7 +67,7 @@ Those live under `$OPENCLAW_STATE_DIR`. ## Migration steps (recommended) -### Step 0 — Make a backup (old machine) +### Step 0 - Make a backup (old machine) On the **old** machine, stop the gateway first so files aren’t changing mid-copy: @@ -87,7 +87,7 @@ tar -czf openclaw-workspace.tgz .openclaw/workspace If you have multiple profiles/state dirs (e.g. `~/.openclaw-main`, `~/.openclaw-work`), archive each. -### Step 1 — Install OpenClaw on the new machine +### Step 1 - Install OpenClaw on the new machine On the **new** machine, install the CLI (and Node if needed): @@ -95,7 +95,7 @@ On the **new** machine, install the CLI (and Node if needed): At this stage, it’s OK if onboarding creates a fresh `~/.openclaw/` — you will overwrite it in the next step. -### Step 2 — Copy the state dir + workspace to the new machine +### Step 2 - Copy the state dir + workspace to the new machine Copy **both**: @@ -113,7 +113,7 @@ After copying, ensure: - Hidden directories were included (e.g. `.openclaw/`) - File ownership is correct for the user running the gateway -### Step 3 — Run Doctor (migrations + service repair) +### Step 3 - Run Doctor (migrations + service repair) On the **new** machine: diff --git a/docs/install/node.md b/docs/install/node.md index 8c57fde4f72..9cf2f59ec77 100644 --- a/docs/install/node.md +++ b/docs/install/node.md @@ -9,7 +9,7 @@ read_when: # Node.js -OpenClaw requires **Node 22 or newer**. The [installer script](/install#install-methods) will detect and install Node automatically — this page is for when you want to set up Node yourself and make sure everything is wired up correctly (versions, PATH, global installs). +OpenClaw requires **Node 22.16 or newer**. **Node 24 is the default and recommended runtime** for installs, CI, and release workflows. Node 22 remains supported via the active LTS line. The [installer script](/install#install-methods) will detect and install Node automatically — this page is for when you want to set up Node yourself and make sure everything is wired up correctly (versions, PATH, global installs). ## Check your version @@ -17,7 +17,7 @@ OpenClaw requires **Node 22 or newer**. The [installer script](/install#install- node -v ``` -If this prints `v22.x.x` or higher, you're good. If Node isn't installed or the version is too old, pick an install method below. +If this prints `v24.x.x` or higher, you're on the recommended default. If it prints `v22.16.x` or higher, you're on the supported Node 22 LTS path, but we still recommend upgrading to Node 24 when convenient. If Node isn't installed or the version is too old, pick an install method below. ## Install Node @@ -36,7 +36,7 @@ If this prints `v22.x.x` or higher, you're good. If Node isn't installed or the **Ubuntu / Debian:** ```bash - curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - + curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - sudo apt-get install -y nodejs ``` @@ -77,8 +77,8 @@ If this prints `v22.x.x` or higher, you're good. If Node isn't installed or the Example with fnm: ```bash -fnm install 22 -fnm use 22 +fnm install 24 +fnm use 24 ``` diff --git a/docs/install/northflank.mdx b/docs/install/northflank.mdx index d3157d72e74..03a41d1013b 100644 --- a/docs/install/northflank.mdx +++ b/docs/install/northflank.mdx @@ -21,7 +21,7 @@ and you configure everything via the `/setup` web wizard. ## What you get - Hosted OpenClaw Gateway + Control UI -- Web setup wizard at `/setup` (no terminal commands) +- Web setup at `/setup` (no terminal commands) - Persistent storage via Northflank Volume (`/data`) so config/credentials/workspace survive redeploys ## Setup flow @@ -32,7 +32,7 @@ and you configure everything via the `/setup` web wizard. 4. Click **Run setup**. 5. Open the Control UI at `https:///openclaw` -If Telegram DMs are set to pairing, the setup wizard can approve the pairing code. +If Telegram DMs are set to pairing, web setup can approve the pairing code. ## Getting chat tokens diff --git a/docs/install/railway.mdx b/docs/install/railway.mdx index 73f23fbe48a..1548069b4fd 100644 --- a/docs/install/railway.mdx +++ b/docs/install/railway.mdx @@ -29,13 +29,13 @@ Railway will either: Then open: -- `https:///setup` — setup wizard (password protected) +- `https:///setup` — web setup (password protected) - `https:///openclaw` — Control UI ## What you get - Hosted OpenClaw Gateway + Control UI -- Web setup wizard at `/setup` (no terminal commands) +- Web setup at `/setup` (no terminal commands) - Persistent storage via Railway Volume (`/data`) so config/credentials/workspace survive redeploys - Backup export at `/setup/export` to migrate off Railway later @@ -70,7 +70,7 @@ Set these variables on the service: 3. (Optional) Add Telegram/Discord/Slack tokens. 4. Click **Run setup**. -If Telegram DMs are set to pairing, the setup wizard can approve the pairing code. +If Telegram DMs are set to pairing, web setup can approve the pairing code. ## Getting chat tokens diff --git a/docs/install/render.mdx b/docs/install/render.mdx index ae945687025..e7a8b26346d 100644 --- a/docs/install/render.mdx +++ b/docs/install/render.mdx @@ -73,7 +73,7 @@ The Blueprint defaults to `starter`. To use free tier, change `plan: free` in yo ## After deployment -### Complete the setup wizard +### Complete web setup 1. Navigate to `https://.onrender.com/setup` 2. Enter your `SETUP_PASSWORD` @@ -135,7 +135,7 @@ This downloads a portable backup you can restore on any OpenClaw host. ## Troubleshooting -### Service won't start +### Service will not start Check the deploy logs in the Render Dashboard. Common issues: diff --git a/docs/install/updating.md b/docs/install/updating.md index f94c2600776..0b88d91ed9e 100644 --- a/docs/install/updating.md +++ b/docs/install/updating.md @@ -22,7 +22,7 @@ curl -fsSL https://openclaw.ai/install.sh | bash Notes: -- Add `--no-onboard` if you don’t want the onboarding wizard to run again. +- Add `--no-onboard` if you don’t want onboarding to run again. - For **source installs**, use: ```bash @@ -65,7 +65,25 @@ openclaw update --channel dev openclaw update --channel stable ``` -Use `--tag ` for a one-off install tag/version. +Use `--tag ` for a one-off package target override. + +For the current GitHub `main` head via a package-manager install: + +```bash +openclaw update --tag main +``` + +Manual equivalents: + +```bash +npm i -g github:openclaw/openclaw#main +``` + +```bash +pnpm add -g github:openclaw/openclaw#main +``` + +You can also pass an explicit package spec to `--tag` for one-off updates (for example a GitHub ref or tarball URL). See [Development channels](/install/development-channels) for channel semantics and release notes. @@ -250,7 +268,7 @@ git checkout main git pull ``` -## If you’re stuck +## If you are stuck - Run `openclaw doctor` again and read the output carefully (it often tells you the fix). - Check: [Troubleshooting](/gateway/troubleshooting) diff --git a/docs/ja-JP/start/wizard.md b/docs/ja-JP/start/wizard.md index 19f53125857..d7a9a77bb57 100644 --- a/docs/ja-JP/start/wizard.md +++ b/docs/ja-JP/start/wizard.md @@ -67,7 +67,7 @@ openclaw agents add -推奨:エージェントが `web_search` を使用できるように、Brave Search APIキーを設定してください(`web_fetch` はキーなしで動作します)。最も簡単な方法:`openclaw configure --section web` を実行すると `tools.web.search.apiKey` が保存されます。ドキュメント:[Webツール](/tools/web)。 +推奨:エージェントが `web_search` を使用できるように、Brave Search APIキーを設定してください(`web_fetch` はキーなしで動作します)。最も簡単な方法:`openclaw configure --section web` を実行すると `plugins.entries.brave.config.webSearch.apiKey` に保存されます。旧 `tools.web.search.apiKey` パスは互換用に引き続き読み込まれますが、新しい設定では使用しないでください。ドキュメント:[Webツール](/tools/web)。 ## 関連ドキュメント diff --git a/docs/nodes/audio.md b/docs/nodes/audio.md index 1be35610323..57e9ab14d8a 100644 --- a/docs/nodes/audio.md +++ b/docs/nodes/audio.md @@ -5,7 +5,7 @@ read_when: title: "Audio and Voice Notes" --- -# Audio / Voice Notes — 2026-01-17 +# Audio / Voice Notes (2026-01-17) ## What works diff --git a/docs/nodes/images.md b/docs/nodes/images.md index c5f7bade748..6236ad089ef 100644 --- a/docs/nodes/images.md +++ b/docs/nodes/images.md @@ -5,7 +5,7 @@ read_when: title: "Image and Media Support" --- -# Image & Media Support — 2025-12-05 +# Image & Media Support (2025-12-05) The WhatsApp channel runs via **Baileys Web**. This document captures the current media handling rules for send, gateway, and agent replies. diff --git a/docs/nodes/index.md b/docs/nodes/index.md index 7c087162c46..f23a2c979cf 100644 --- a/docs/nodes/index.md +++ b/docs/nodes/index.md @@ -285,6 +285,8 @@ Available families: - `photos.latest` - `contacts.search`, `contacts.add` - `calendar.events`, `calendar.add` +- `callLog.search` +- `sms.search` - `motion.activity`, `motion.pedometer` Example invokes: diff --git a/docs/nodes/media-understanding.md b/docs/nodes/media-understanding.md index dae748633bd..9d20c0c83d4 100644 --- a/docs/nodes/media-understanding.md +++ b/docs/nodes/media-understanding.md @@ -6,10 +6,14 @@ read_when: title: "Media Understanding" --- -# Media Understanding (Inbound) — 2026-01-17 +# Media Understanding - Inbound (2026-01-17) OpenClaw can **summarize inbound media** (image/audio/video) before the reply pipeline runs. It auto‑detects when local tools or provider keys are available, and can be disabled or customized. If understanding is off, models still receive the original files/URLs as usual. +Vendor-specific media behavior is registered by vendor plugins, while OpenClaw +core owns the shared `tools.media` config, fallback order, and reply-pipeline +integration. + ## Goals - Optional: pre‑digest inbound media into short text for faster routing + better command parsing. @@ -17,7 +21,7 @@ OpenClaw can **summarize inbound media** (image/audio/video) before the reply pi - Support **provider APIs** and **CLI fallbacks**. - Allow multiple models with ordered fallback (error/size/timeout). -## High‑level behavior +## High-level behavior 1. Collect inbound attachments (`MediaPaths`, `MediaUrls`, `MediaTypes`). 2. For each enabled capability (image/audio/video), select attachments per policy (default: **first**). @@ -184,7 +188,10 @@ If you set `capabilities`, the entry only runs for those media types. For shared lists, OpenClaw can infer defaults: - `openai`, `anthropic`, `minimax`: **image** +- `moonshot`: **image + video** - `google` (Gemini API): **image + audio + video** +- `mistral`: **audio** +- `zai`: **image** - `groq`: **audio** - `deepgram`: **audio** @@ -193,11 +200,11 @@ If you omit `capabilities`, the entry is eligible for the list it appears in. ## Provider support matrix (OpenClaw integrations) -| Capability | Provider integration | Notes | -| ---------- | ------------------------------------------------ | --------------------------------------------------------- | -| Image | OpenAI / Anthropic / Google / others via `pi-ai` | Any image-capable model in the registry works. | -| Audio | OpenAI, Groq, Deepgram, Google, Mistral | Provider transcription (Whisper/Deepgram/Gemini/Voxtral). | -| Video | Google (Gemini API) | Provider video understanding. | +| Capability | Provider integration | Notes | +| ---------- | -------------------------------------------------- | ----------------------------------------------------------------------- | +| Image | OpenAI, Anthropic, Google, MiniMax, Moonshot, Z.AI | Vendor plugins register image support against core media understanding. | +| Audio | OpenAI, Groq, Deepgram, Google, Mistral | Provider transcription (Whisper/Deepgram/Gemini/Voxtral). | +| Video | Google, Moonshot | Provider video understanding via vendor plugins. | ## Model selection guidance @@ -327,7 +334,7 @@ When `mode: "all"`, outputs are labeled `[Image 1/2]`, `[Audio 2/2]`, etc. } ``` -### 4) Multi‑modal single entry (explicit capabilities) +### 4) Multi-modal single entry (explicit capabilities) ```json5 { diff --git a/docs/perplexity.md b/docs/perplexity.md index f7eccc9453e..3ad4c50c3f7 100644 --- a/docs/perplexity.md +++ b/docs/perplexity.md @@ -12,22 +12,22 @@ OpenClaw supports Perplexity Search API as a `web_search` provider. It returns structured results with `title`, `url`, and `snippet` fields. For compatibility, OpenClaw also supports legacy Perplexity Sonar/OpenRouter setups. -If you use `OPENROUTER_API_KEY`, an `sk-or-...` key in `tools.web.search.perplexity.apiKey`, or set `tools.web.search.perplexity.baseUrl` / `model`, the provider switches to the chat-completions path and returns AI-synthesized answers with citations instead of structured Search API results. +If you use `OPENROUTER_API_KEY`, an `sk-or-...` key in `plugins.entries.perplexity.config.webSearch.apiKey`, or set `plugins.entries.perplexity.config.webSearch.baseUrl` / `model`, the provider switches to the chat-completions path and returns AI-synthesized answers with citations instead of structured Search API results. ## Getting a Perplexity API key -1. Create a Perplexity account at +1. Create a Perplexity account at [perplexity.ai/settings/api](https://www.perplexity.ai/settings/api) 2. Generate an API key in the dashboard 3. Store the key in config or set `PERPLEXITY_API_KEY` in the Gateway environment. ## OpenRouter compatibility -If you were already using OpenRouter for Perplexity Sonar, keep `provider: "perplexity"` and set `OPENROUTER_API_KEY` in the Gateway environment, or store an `sk-or-...` key in `tools.web.search.perplexity.apiKey`. +If you were already using OpenRouter for Perplexity Sonar, keep `provider: "perplexity"` and set `OPENROUTER_API_KEY` in the Gateway environment, or store an `sk-or-...` key in `plugins.entries.perplexity.config.webSearch.apiKey`. -Optional legacy controls: +Optional compatibility controls: -- `tools.web.search.perplexity.baseUrl` -- `tools.web.search.perplexity.model` +- `plugins.entries.perplexity.config.webSearch.baseUrl` +- `plugins.entries.perplexity.config.webSearch.model` ## Config examples @@ -35,13 +35,21 @@ Optional legacy controls: ```json5 { + plugins: { + entries: { + perplexity: { + config: { + webSearch: { + apiKey: "pplx-...", + }, + }, + }, + }, + }, tools: { web: { search: { provider: "perplexity", - perplexity: { - apiKey: "pplx-...", - }, }, }, }, @@ -52,15 +60,23 @@ Optional legacy controls: ```json5 { + plugins: { + entries: { + perplexity: { + config: { + webSearch: { + apiKey: "", + baseUrl: "https://openrouter.ai/api/v1", + model: "perplexity/sonar-pro", + }, + }, + }, + }, + }, tools: { web: { search: { provider: "perplexity", - perplexity: { - apiKey: "", - baseUrl: "https://openrouter.ai/api/v1", - model: "perplexity/sonar-pro", - }, }, }, }, @@ -70,7 +86,7 @@ Optional legacy controls: ## Where to set the key **Via config:** run `openclaw configure --section web`. It stores the key in -`~/.openclaw/openclaw.json` under `tools.web.search.perplexity.apiKey`. +`~/.openclaw/openclaw.json` under `plugins.entries.perplexity.config.webSearch.apiKey`. That field also accepts SecretRef objects. **Via environment:** set `PERPLEXITY_API_KEY` or `OPENROUTER_API_KEY` @@ -151,7 +167,7 @@ await web_search({ ## Notes - Perplexity Search API returns structured web search results (`title`, `url`, `snippet`) -- OpenRouter or explicit `baseUrl` / `model` switches Perplexity back to Sonar chat completions for compatibility +- OpenRouter or explicit `plugins.entries.perplexity.config.webSearch.baseUrl` / `model` switches Perplexity back to Sonar chat completions for compatibility - Results are cached for 15 minutes by default (configurable via `cacheTtlMinutes`) See [Web tools](/tools/web) for the full web_search configuration. diff --git a/docs/pi-dev.md b/docs/pi-dev.md index 322bd13cd39..3b0918c4928 100644 --- a/docs/pi-dev.md +++ b/docs/pi-dev.md @@ -76,5 +76,5 @@ If you only want to reset sessions, delete `agents//sessions/` and `age ## References -- [https://docs.openclaw.ai/testing](https://docs.openclaw.ai/testing) -- [https://docs.openclaw.ai/start/getting-started](https://docs.openclaw.ai/start/getting-started) +- [Testing](/help/testing) +- [Getting Started](/start/getting-started) diff --git a/docs/pi.md b/docs/pi.md index 2689b480963..f12c687906c 100644 --- a/docs/pi.md +++ b/docs/pi.md @@ -119,19 +119,24 @@ src/agents/ │ ├── browser-tool.ts │ ├── canvas-tool.ts │ ├── cron-tool.ts -│ ├── discord-actions*.ts │ ├── gateway-tool.ts │ ├── image-tool.ts │ ├── message-tool.ts │ ├── nodes-tool.ts │ ├── session*.ts -│ ├── slack-actions.ts -│ ├── telegram-actions.ts │ ├── web-*.ts -│ └── whatsapp-actions.ts +│ └── ... └── ... ``` +Channel-specific message action runtimes now live in the plugin-owned extension +directories instead of under `src/agents/tools`, for example: + +- `extensions/discord/src/actions/runtime*.ts` +- `extensions/slack/src/action-runtime.ts` +- `extensions/telegram/src/action-runtime.ts` +- `extensions/whatsapp/src/action-runtime.ts` + ## Core Integration Flow ### 1. Running an Embedded Agent diff --git a/docs/platforms/android.md b/docs/platforms/android.md index 4df71b83e73..384b5311c33 100644 --- a/docs/platforms/android.md +++ b/docs/platforms/android.md @@ -9,6 +9,8 @@ title: "Android App" # Android App (Node) +> **Note:** The Android app has not been publicly released yet. The source code is available in the [OpenClaw repository](https://github.com/openclaw/openclaw) under `apps/android`. You can build it yourself using Java 17 and the Android SDK (`./gradlew :app:assembleDebug`). See [apps/android/README.md](https://github.com/openclaw/openclaw/blob/main/apps/android/README.md) for build instructions. + ## Support snapshot - Role: companion node app (Android does not host the Gateway). @@ -161,4 +163,6 @@ See [Camera node](/nodes/camera) for parameters and CLI helpers. - `photos.latest` - `contacts.search`, `contacts.add` - `calendar.events`, `calendar.add` + - `callLog.search` + - `sms.search` - `motion.activity`, `motion.pedometer` diff --git a/docs/platforms/digitalocean.md b/docs/platforms/digitalocean.md index bddc63b9d1f..61021c1ade8 100644 --- a/docs/platforms/digitalocean.md +++ b/docs/platforms/digitalocean.md @@ -66,8 +66,8 @@ ssh root@YOUR_DROPLET_IP # Update system apt update && apt upgrade -y -# Install Node.js 22 -curl -fsSL https://deb.nodesource.com/setup_22.x | bash - +# Install Node.js 24 +curl -fsSL https://deb.nodesource.com/setup_24.x | bash - apt install -y nodejs # Install OpenClaw @@ -231,7 +231,7 @@ For the full setup guide, see [Oracle Cloud](/platforms/oracle). For signup tips ## Troubleshooting -### Gateway won't start +### Gateway will not start ```bash openclaw gateway status diff --git a/docs/platforms/ios.md b/docs/platforms/ios.md index 0a2eb5abae5..f64eba3fed0 100644 --- a/docs/platforms/ios.md +++ b/docs/platforms/ios.md @@ -49,6 +49,114 @@ openclaw nodes status openclaw gateway call node.list --params "{}" ``` +## Relay-backed push for official builds + +Official distributed iOS builds use the external push relay instead of publishing the raw APNs +token to the gateway. + +Gateway-side requirement: + +```json5 +{ + gateway: { + push: { + apns: { + relay: { + baseUrl: "https://relay.example.com", + }, + }, + }, + }, +} +``` + +How the flow works: + +- The iOS app registers with the relay using App Attest and the app receipt. +- The relay returns an opaque relay handle plus a registration-scoped send grant. +- The iOS app fetches the paired gateway identity and includes it in relay registration, so the relay-backed registration is delegated to that specific gateway. +- The app forwards that relay-backed registration to the paired gateway with `push.apns.register`. +- The gateway uses that stored relay handle for `push.test`, background wakes, and wake nudges. +- The gateway relay base URL must match the relay URL baked into the official/TestFlight iOS build. +- If the app later connects to a different gateway or a build with a different relay base URL, it refreshes the relay registration instead of reusing the old binding. + +What the gateway does **not** need for this path: + +- No deployment-wide relay token. +- No direct APNs key for official/TestFlight relay-backed sends. + +Expected operator flow: + +1. Install the official/TestFlight iOS build. +2. Set `gateway.push.apns.relay.baseUrl` on the gateway. +3. Pair the app to the gateway and let it finish connecting. +4. The app publishes `push.apns.register` automatically after it has an APNs token, the operator session is connected, and relay registration succeeds. +5. After that, `push.test`, reconnect wakes, and wake nudges can use the stored relay-backed registration. + +Compatibility note: + +- `OPENCLAW_APNS_RELAY_BASE_URL` still works as a temporary env override for the gateway. + +## Authentication and trust flow + +The relay exists to enforce two constraints that direct APNs-on-gateway cannot provide for +official iOS builds: + +- Only genuine OpenClaw iOS builds distributed through Apple can use the hosted relay. +- A gateway can send relay-backed pushes only for iOS devices that paired with that specific + gateway. + +Hop by hop: + +1. `iOS app -> gateway` + - The app first pairs with the gateway through the normal Gateway auth flow. + - That gives the app an authenticated node session plus an authenticated operator session. + - The operator session is used to call `gateway.identity.get`. + +2. `iOS app -> relay` + - The app calls the relay registration endpoints over HTTPS. + - Registration includes App Attest proof plus the app receipt. + - The relay validates the bundle ID, App Attest proof, and Apple receipt, and requires the + official/production distribution path. + - This is what blocks local Xcode/dev builds from using the hosted relay. A local build may be + signed, but it does not satisfy the official Apple distribution proof the relay expects. + +3. `gateway identity delegation` + - Before relay registration, the app fetches the paired gateway identity from + `gateway.identity.get`. + - The app includes that gateway identity in the relay registration payload. + - The relay returns a relay handle and a registration-scoped send grant that are delegated to + that gateway identity. + +4. `gateway -> relay` + - The gateway stores the relay handle and send grant from `push.apns.register`. + - On `push.test`, reconnect wakes, and wake nudges, the gateway signs the send request with its + own device identity. + - The relay verifies both the stored send grant and the gateway signature against the delegated + gateway identity from registration. + - Another gateway cannot reuse that stored registration, even if it somehow obtains the handle. + +5. `relay -> APNs` + - The relay owns the production APNs credentials and the raw APNs token for the official build. + - The gateway never stores the raw APNs token for relay-backed official builds. + - The relay sends the final push to APNs on behalf of the paired gateway. + +Why this design was created: + +- To keep production APNs credentials out of user gateways. +- To avoid storing raw official-build APNs tokens on the gateway. +- To allow hosted relay usage only for official/TestFlight OpenClaw builds. +- To prevent one gateway from sending wake pushes to iOS devices owned by a different gateway. + +Local/manual builds remain on direct APNs. If you are testing those builds without the relay, the +gateway still needs direct APNs credentials: + +```bash +export OPENCLAW_APNS_TEAM_ID="TEAMID" +export OPENCLAW_APNS_KEY_ID="KEYID" +export OPENCLAW_APNS_PRIVATE_KEY_P8="$(cat /path/to/AuthKey_KEYID.p8)" +``` + ## Discovery paths ### Bonjour (LAN) diff --git a/docs/platforms/linux.md b/docs/platforms/linux.md index 0cce3a54e75..c03dba6f795 100644 --- a/docs/platforms/linux.md +++ b/docs/platforms/linux.md @@ -15,7 +15,7 @@ Native Linux companion apps are planned. Contributions are welcome if you want t ## Beginner quick path (VPS) -1. Install Node 22+ +1. Install Node 24 (recommended; Node 22 LTS, currently `22.16+`, still works for compatibility) 2. `npm i -g openclaw@latest` 3. `openclaw onboard --install-daemon` 4. From your laptop: `ssh -N -L 18789:127.0.0.1:18789 @` diff --git a/docs/platforms/mac/bundled-gateway.md b/docs/platforms/mac/bundled-gateway.md index 6cb878015fb..e6e57cc1809 100644 --- a/docs/platforms/mac/bundled-gateway.md +++ b/docs/platforms/mac/bundled-gateway.md @@ -16,7 +16,7 @@ running (or attaches to an existing local Gateway if one is already running). ## Install the CLI (required for local mode) -You need Node 22+ on the Mac, then install `openclaw` globally: +Node 24 is the default runtime on the Mac. Node 22 LTS, currently `22.16+`, still works for compatibility. Then install `openclaw` globally: ```bash npm install -g openclaw@ diff --git a/docs/platforms/mac/dev-setup.md b/docs/platforms/mac/dev-setup.md index e50a850086a..0e7c058a934 100644 --- a/docs/platforms/mac/dev-setup.md +++ b/docs/platforms/mac/dev-setup.md @@ -14,7 +14,7 @@ This guide covers the necessary steps to build and run the OpenClaw macOS applic Before building the app, ensure you have the following installed: 1. **Xcode 26.2+**: Required for Swift development. -2. **Node.js 22+ & pnpm**: Required for the gateway, CLI, and packaging scripts. +2. **Node.js 24 & pnpm**: Recommended for the gateway, CLI, and packaging scripts. Node 22 LTS, currently `22.16+`, remains supported for compatibility. ## 1. Install Dependencies @@ -97,7 +97,7 @@ If the gateway status stays on "Starting...", check if a zombie process is holdi openclaw gateway status openclaw gateway stop -# If you’re not using a LaunchAgent (dev mode / manual runs), find the listener: +# If you're not using a LaunchAgent (dev mode / manual runs), find the listener: lsof -nP -iTCP:18789 -sTCP:LISTEN ``` diff --git a/docs/platforms/mac/health.md b/docs/platforms/mac/health.md index 8115dd4c250..7cda23e3221 100644 --- a/docs/platforms/mac/health.md +++ b/docs/platforms/mac/health.md @@ -2,7 +2,7 @@ summary: "How the macOS app reports gateway/Baileys health states" read_when: - Debugging mac app health indicators -title: "Health Checks" +title: "Health Checks (macOS)" --- # Health Checks on macOS diff --git a/docs/platforms/mac/peekaboo.md b/docs/platforms/mac/peekaboo.md index d1947734735..96761a0ad74 100644 --- a/docs/platforms/mac/peekaboo.md +++ b/docs/platforms/mac/peekaboo.md @@ -13,7 +13,7 @@ OpenClaw can host **PeekabooBridge** as a local, permission‑aware UI automatio broker. This lets the `peekaboo` CLI drive UI automation while reusing the macOS app’s TCC permissions. -## What this is (and isn’t) +## What this is (and is not) - **Host**: OpenClaw.app can act as a PeekabooBridge host. - **Client**: use the `peekaboo` CLI (no separate `openclaw ui ...` surface). diff --git a/docs/platforms/mac/release.md b/docs/platforms/mac/release.md deleted file mode 100644 index 180a52075ed..00000000000 --- a/docs/platforms/mac/release.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -summary: "OpenClaw macOS release checklist (Sparkle feed, packaging, signing)" -read_when: - - Cutting or validating a OpenClaw macOS release - - Updating the Sparkle appcast or feed assets -title: "macOS Release" ---- - -# OpenClaw macOS release (Sparkle) - -This app now ships Sparkle auto-updates. Release builds must be Developer ID–signed, zipped, and published with a signed appcast entry. - -## Prereqs - -- Developer ID Application cert installed (example: `Developer ID Application: ()`). -- Sparkle private key path set in the environment as `SPARKLE_PRIVATE_KEY_FILE` (path to your Sparkle ed25519 private key; public key baked into Info.plist). If it is missing, check `~/.profile`. -- Notary credentials (keychain profile or API key) for `xcrun notarytool` if you want Gatekeeper-safe DMG/zip distribution. - - We use a Keychain profile named `openclaw-notary`, created from App Store Connect API key env vars in your shell profile: - - `APP_STORE_CONNECT_API_KEY_P8`, `APP_STORE_CONNECT_KEY_ID`, `APP_STORE_CONNECT_ISSUER_ID` - - `echo "$APP_STORE_CONNECT_API_KEY_P8" | sed 's/\\n/\n/g' > /tmp/openclaw-notary.p8` - - `xcrun notarytool store-credentials "openclaw-notary" --key /tmp/openclaw-notary.p8 --key-id "$APP_STORE_CONNECT_KEY_ID" --issuer "$APP_STORE_CONNECT_ISSUER_ID"` -- `pnpm` deps installed (`pnpm install --config.node-linker=hoisted`). -- Sparkle tools are fetched automatically via SwiftPM at `apps/macos/.build/artifacts/sparkle/Sparkle/bin/` (`sign_update`, `generate_appcast`, etc.). - -## Build & package - -Notes: - -- `APP_BUILD` maps to `CFBundleVersion`/`sparkle:version`; keep it numeric + monotonic (no `-beta`), or Sparkle compares it as equal. -- If `APP_BUILD` is omitted, `scripts/package-mac-app.sh` derives a Sparkle-safe default from `APP_VERSION` (`YYYYMMDDNN`: stable defaults to `90`, prereleases use a suffix-derived lane) and uses the higher of that value and git commit count. -- You can still override `APP_BUILD` explicitly when release engineering needs a specific monotonic value. -- For `BUILD_CONFIG=release`, `scripts/package-mac-app.sh` now defaults to universal (`arm64 x86_64`) automatically. You can still override with `BUILD_ARCHS=arm64` or `BUILD_ARCHS=x86_64`. For local/dev builds (`BUILD_CONFIG=debug`), it defaults to the current architecture (`$(uname -m)`). -- Use `scripts/package-mac-dist.sh` for release artifacts (zip + DMG + notarization). Use `scripts/package-mac-app.sh` for local/dev packaging. - -```bash -# From repo root; set release IDs so Sparkle feed is enabled. -# This command builds release artifacts without notarization. -# APP_BUILD must be numeric + monotonic for Sparkle compare. -# Default is auto-derived from APP_VERSION when omitted. -SKIP_NOTARIZE=1 \ -BUNDLE_ID=ai.openclaw.mac \ -APP_VERSION=2026.3.9 \ -BUILD_CONFIG=release \ -SIGN_IDENTITY="Developer ID Application: ()" \ -scripts/package-mac-dist.sh - -# `package-mac-dist.sh` already creates the zip + DMG. -# If you used `package-mac-app.sh` directly instead, create them manually: -# If you want notarization/stapling in this step, use the NOTARIZE command below. -ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.3.9.zip - -# Optional: build a styled DMG for humans (drag to /Applications) -scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.3.9.dmg - -# Recommended: build + notarize/staple zip + DMG -# First, create a keychain profile once: -# xcrun notarytool store-credentials "openclaw-notary" \ -# --apple-id "" --team-id "" --password "" -NOTARIZE=1 NOTARYTOOL_PROFILE=openclaw-notary \ -BUNDLE_ID=ai.openclaw.mac \ -APP_VERSION=2026.3.9 \ -BUILD_CONFIG=release \ -SIGN_IDENTITY="Developer ID Application: ()" \ -scripts/package-mac-dist.sh - -# Optional: ship dSYM alongside the release -ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenClaw-2026.3.9.dSYM.zip -``` - -## Appcast entry - -Use the release note generator so Sparkle renders formatted HTML notes: - -```bash -SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/OpenClaw-2026.3.9.zip https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml -``` - -Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry. -Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing. - -## Publish & verify - -- Upload `OpenClaw-2026.3.9.zip` (and `OpenClaw-2026.3.9.dSYM.zip`) to the GitHub release for tag `v2026.3.9`. -- Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`. -- Sanity checks: - - `curl -I https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml` returns 200. - - `curl -I ` returns 200 after assets upload. - - On a previous public build, run “Check for Updates…” from the About tab and verify Sparkle installs the new build cleanly. - -Definition of done: signed app + appcast are published, update flow works from an older installed version, and release assets are attached to the GitHub release. diff --git a/docs/platforms/mac/remote.md b/docs/platforms/mac/remote.md index 71b67070c89..631e5c88d2a 100644 --- a/docs/platforms/mac/remote.md +++ b/docs/platforms/mac/remote.md @@ -7,7 +7,7 @@ title: "Remote Control" # Remote OpenClaw (macOS ⇄ remote host) -This flow lets the macOS app act as a full remote control for a OpenClaw gateway running on another host (desktop/server). It’s the app’s **Remote over SSH** (remote run) feature. All features—health checks, Voice Wake forwarding, and Web Chat—reuse the same remote SSH configuration from _Settings → General_. +This flow lets the macOS app act as a full remote control for an OpenClaw gateway running on another host (desktop/server). It’s the app’s **Remote over SSH** (remote run) feature. All features—health checks, Voice Wake forwarding, and Web Chat—reuse the same remote SSH configuration from _Settings → General_. ## Modes diff --git a/docs/platforms/mac/signing.md b/docs/platforms/mac/signing.md index 9927ca5f82b..0feac8cd281 100644 --- a/docs/platforms/mac/signing.md +++ b/docs/platforms/mac/signing.md @@ -14,7 +14,7 @@ This app is usually built from [`scripts/package-mac-app.sh`](https://github.com - calls [`scripts/codesign-mac-app.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/codesign-mac-app.sh) to sign the main binary and app bundle so macOS treats each rebuild as the same signed bundle and keeps TCC permissions (notifications, accessibility, screen recording, mic, speech). For stable permissions, use a real signing identity; ad-hoc is opt-in and fragile (see [macOS permissions](/platforms/mac/permissions)). - uses `CODESIGN_TIMESTAMP=auto` by default; it enables trusted timestamps for Developer ID signatures. Set `CODESIGN_TIMESTAMP=off` to skip timestamping (offline debug builds). - inject build metadata into Info.plist: `OpenClawBuildTimestamp` (UTC) and `OpenClawGitCommit` (short hash) so the About pane can show build, git, and debug/release channel. -- **Packaging requires Node 22+**: the script runs TS builds and the Control UI build. +- **Packaging defaults to Node 24**: the script runs TS builds and the Control UI build. Node 22 LTS, currently `22.16+`, remains supported for compatibility. - reads `SIGN_IDENTITY` from the environment. Add `export SIGN_IDENTITY="Apple Development: Your Name (TEAMID)"` (or your Developer ID Application cert) to your shell rc to always sign with your cert. Ad-hoc signing requires explicit opt-in via `ALLOW_ADHOC_SIGNING=1` or `SIGN_IDENTITY="-"` (not recommended for permission testing). - runs a Team ID audit after signing and fails if any Mach-O inside the app bundle is signed by a different Team ID. Set `SKIP_TEAM_ID_CHECK=1` to bypass. diff --git a/docs/platforms/mac/skills.md b/docs/platforms/mac/skills.md index fc1e6c6af5f..2c2b5d95924 100644 --- a/docs/platforms/mac/skills.md +++ b/docs/platforms/mac/skills.md @@ -3,7 +3,7 @@ summary: "macOS Skills settings UI and gateway-backed status" read_when: - Updating the macOS Skills settings UI - Changing skills gating or install behavior -title: "Skills" +title: "Skills (macOS)" --- # Skills (macOS) diff --git a/docs/platforms/mac/voicewake.md b/docs/platforms/mac/voicewake.md index 1830acb35a4..c7cacd4c5dd 100644 --- a/docs/platforms/mac/voicewake.md +++ b/docs/platforms/mac/voicewake.md @@ -2,7 +2,7 @@ summary: "Voice wake and push-to-talk modes plus routing details in the mac app" read_when: - Working on voice wake or PTT pathways -title: "Voice Wake" +title: "Voice Wake (macOS)" --- # Voice Wake & Push-to-Talk diff --git a/docs/platforms/mac/webchat.md b/docs/platforms/mac/webchat.md index 11b500a8596..bf8b23c35e4 100644 --- a/docs/platforms/mac/webchat.md +++ b/docs/platforms/mac/webchat.md @@ -2,7 +2,7 @@ summary: "How the mac app embeds the gateway WebChat and how to debug it" read_when: - Debugging mac WebChat view or loopback port -title: "WebChat" +title: "WebChat (macOS)" --- # WebChat (macOS app) @@ -26,7 +26,7 @@ agent (with a session switcher for other sessions). - Logs: `./scripts/clawlog.sh` (subsystem `ai.openclaw`, category `WebChatSwiftUI`). -## How it’s wired +## How it is wired - Data plane: Gateway WS methods `chat.history`, `chat.send`, `chat.abort`, `chat.inject` and events `chat`, `agent`, `presence`, `tick`, `health`. diff --git a/docs/platforms/oracle.md b/docs/platforms/oracle.md index 779027c9f07..d185af41d23 100644 --- a/docs/platforms/oracle.md +++ b/docs/platforms/oracle.md @@ -180,7 +180,7 @@ With the VCN locked down (only UDP 41641 open) and the Gateway bound to loopback This setup often removes the _need_ for extra host-based firewall rules purely to stop Internet-wide SSH brute force — but you should still keep the OS updated, run `openclaw security audit`, and verify you aren’t accidentally listening on public interfaces. -### What's Already Protected +### Already protected | Traditional Step | Needed? | Why | | ------------------ | ----------- | ---------------------------------------------------------------------------- | @@ -236,7 +236,7 @@ Free tier ARM instances are popular. Try: - Retry during off-peak hours (early morning) - Use the "Always Free" filter when selecting shape -### Tailscale won't connect +### Tailscale will not connect ```bash # Check status @@ -246,7 +246,7 @@ sudo tailscale status sudo tailscale up --ssh --hostname=openclaw --reset ``` -### Gateway won't start +### Gateway will not start ```bash openclaw gateway status @@ -254,7 +254,7 @@ openclaw doctor --non-interactive journalctl --user -u openclaw-gateway -n 50 ``` -### Can't reach Control UI +### Cannot reach Control UI ```bash # Verify Tailscale Serve is running diff --git a/docs/platforms/raspberry-pi.md b/docs/platforms/raspberry-pi.md index e46076e869d..855f053c825 100644 --- a/docs/platforms/raspberry-pi.md +++ b/docs/platforms/raspberry-pi.md @@ -33,7 +33,7 @@ Perfect for: **Minimum specs:** 1GB RAM, 1 core, 500MB disk **Recommended:** 2GB+ RAM, 64-bit OS, 16GB+ SD card (or USB SSD) -## What You'll Need +## What you need - Raspberry Pi 4 or 5 (2GB+ recommended) - MicroSD card (16GB+) or USB SSD (better performance) @@ -76,15 +76,15 @@ sudo apt install -y git curl build-essential sudo timedatectl set-timezone America/Chicago # Change to your timezone ``` -## 4) Install Node.js 22 (ARM64) +## 4) Install Node.js 24 (ARM64) ```bash # Install Node.js via NodeSource -curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - +curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - sudo apt install -y nodejs # Verify -node --version # Should show v22.x.x +node --version # Should show v24.x.x npm --version ``` @@ -153,30 +153,33 @@ sudo systemctl status openclaw journalctl -u openclaw -f ``` -## 9) Access the Dashboard +## 9) Access the OpenClaw Dashboard -Since the Pi is headless, use an SSH tunnel: +Replace `user@gateway-host` with your Pi username and hostname or IP address. + +On your computer, ask the Pi to print a fresh dashboard URL: ```bash -# From your laptop/desktop -ssh -L 18789:localhost:18789 user@gateway-host - -# Then open in browser -open http://localhost:18789 +ssh user@gateway-host 'openclaw dashboard --no-open' ``` -Or use Tailscale for always-on access: +The command prints `Dashboard URL:`. Depending on how `gateway.auth.token` +is configured, the URL may be a plain `http://127.0.0.1:18789/` link or one +that includes `#token=...`. + +In another terminal on your computer, create the SSH tunnel: ```bash -# On the Pi -curl -fsSL https://tailscale.com/install.sh | sh -sudo tailscale up - -# Update config -openclaw config set gateway.bind tailnet -sudo systemctl restart openclaw +ssh -N -L 18789:127.0.0.1:18789 user@gateway-host ``` +Then open the printed Dashboard URL in your local browser. + +If the UI asks for auth, paste the token from `gateway.auth.token` +(or `OPENCLAW_GATEWAY_TOKEN`) into Control UI settings. + +For always-on remote access, see [Tailscale](/gateway/tailscale). + --- ## Performance Optimizations @@ -318,7 +321,7 @@ Since the Pi is just the Gateway (models run in the cloud), use API-based models ## Auto-Start on Boot -The onboarding wizard sets this up, but to verify: +Onboarding sets this up, but to verify: ```bash # Check service is enabled @@ -351,7 +354,7 @@ free -h - Disable unused services: `sudo systemctl disable cups bluetooth avahi-daemon` - Check CPU throttling: `vcgencmd get_throttled` (should return `0x0`) -### Service Won't Start +### Service will not start ```bash # Check logs diff --git a/docs/platforms/windows.md b/docs/platforms/windows.md index 3ab668ea01e..e40d798604d 100644 --- a/docs/platforms/windows.md +++ b/docs/platforms/windows.md @@ -22,6 +22,44 @@ Native Windows companion apps are planned. - [Install & updates](/install/updating) - Official WSL2 guide (Microsoft): [https://learn.microsoft.com/windows/wsl/install](https://learn.microsoft.com/windows/wsl/install) +## Native Windows status + +Native Windows CLI flows are improving, but WSL2 is still the recommended path. + +What works well on native Windows today: + +- website installer via `install.ps1` +- local CLI use such as `openclaw --version`, `openclaw doctor`, and `openclaw plugins list --json` +- embedded local-agent/provider smoke such as: + +```powershell +openclaw agent --local --agent main --thinking low -m "Reply with exactly WINDOWS-HATCH-OK." +``` + +Current caveats: + +- `openclaw onboard --non-interactive` still expects a reachable local gateway unless you pass `--skip-health` +- `openclaw onboard --non-interactive --install-daemon` and `openclaw gateway install` try Windows Scheduled Tasks first +- if Scheduled Task creation is denied, OpenClaw falls back to a per-user Startup-folder login item and starts the gateway immediately +- if `schtasks` itself wedges or stops responding, OpenClaw now aborts that path quickly and falls back instead of hanging forever +- Scheduled Tasks are still preferred when available because they provide better supervisor status + +If you want the native CLI only, without gateway service install, use one of these: + +```powershell +openclaw onboard --non-interactive --skip-health +openclaw gateway run +``` + +If you do want managed startup on native Windows: + +```powershell +openclaw gateway install +openclaw gateway status --json +``` + +If Scheduled Task creation is blocked, the fallback service mode still auto-starts after login through the current user's Startup folder. + ## Gateway - [Gateway runbook](/gateway) diff --git a/docs/plugins/agent-tools.md b/docs/plugins/agent-tools.md index f5d5d8cc3a8..8740fd51fa4 100644 --- a/docs/plugins/agent-tools.md +++ b/docs/plugins/agent-tools.md @@ -35,7 +35,7 @@ export default function (api) { } ``` -## Optional tool (opt‑in) +## Optional tool (opt-in) Optional tools are **never** auto‑enabled. Users must add them to an agent allowlist. diff --git a/docs/plugins/architecture.md b/docs/plugins/architecture.md new file mode 100644 index 00000000000..19783028721 --- /dev/null +++ b/docs/plugins/architecture.md @@ -0,0 +1,1349 @@ +--- +summary: "Plugin architecture internals: capability model, ownership, contracts, load pipeline, runtime helpers" +read_when: + - Building or debugging native OpenClaw plugins + - Understanding the plugin capability model or ownership boundaries + - Working on the plugin load pipeline or registry + - Implementing provider runtime hooks or channel plugins +title: "Plugin Architecture" +--- + +# Plugin Architecture + +This page covers the internal architecture of the OpenClaw plugin system. For +user-facing setup, discovery, and configuration, see [Plugins](/tools/plugin). + +## Public capability model + +Capabilities are the public **native plugin** model inside OpenClaw. Every +native OpenClaw plugin registers against one or more capability types: + +| Capability | Registration method | Example plugins | +| ------------------- | --------------------------------------------- | ------------------------- | +| Text inference | `api.registerProvider(...)` | `openai`, `anthropic` | +| Speech | `api.registerSpeechProvider(...)` | `elevenlabs`, `microsoft` | +| Media understanding | `api.registerMediaUnderstandingProvider(...)` | `openai`, `google` | +| Image generation | `api.registerImageGenerationProvider(...)` | `openai`, `google` | +| Web search | `api.registerWebSearchProvider(...)` | `google` | +| Channel / messaging | `api.registerChannel(...)` | `msteams`, `matrix` | + +A plugin that registers zero capabilities but provides hooks, tools, or +services is a **legacy hook-only** plugin. That pattern is still fully supported. + +### External compatibility stance + +The capability model is landed in core and used by bundled/native plugins +today, but external plugin compatibility still needs a tighter bar than "it is +exported, therefore it is frozen." + +Current guidance: + +- **existing external plugins:** keep hook-based integrations working; treat + this as the compatibility baseline +- **new bundled/native plugins:** prefer explicit capability registration over + vendor-specific reach-ins or new hook-only designs +- **external plugins adopting capability registration:** allowed, but treat the + capability-specific helper surfaces as evolving unless docs explicitly mark a + contract as stable + +Practical rule: + +- capability registration APIs are the intended direction +- legacy hooks remain the safest no-breakage path for external plugins during + the transition +- exported helper subpaths are not all equal; prefer the narrow documented + contract, not incidental helper exports + +### Plugin shapes + +OpenClaw classifies every loaded plugin into a shape based on its actual +registration behavior (not just static metadata): + +- **plain-capability** -- registers exactly one capability type (for example a + provider-only plugin like `mistral`) +- **hybrid-capability** -- registers multiple capability types (for example + `openai` owns text inference, speech, media understanding, and image + generation) +- **hook-only** -- registers only hooks (typed or custom), no capabilities, + tools, commands, or services +- **non-capability** -- registers tools, commands, services, or routes but no + capabilities + +Use `openclaw plugins inspect ` to see a plugin's shape and capability +breakdown. See [CLI reference](/cli/plugins#inspect) for details. + +### Legacy hooks + +The `before_agent_start` hook remains supported as a compatibility path for +hook-only plugins. Legacy real-world plugins still depend on it. + +Direction: + +- keep it working +- document it as legacy +- prefer `before_model_resolve` for model/provider override work +- prefer `before_prompt_build` for prompt mutation work +- remove only after real usage drops and fixture coverage proves migration safety + +### Compatibility signals + +When you run `openclaw doctor` or `openclaw plugins inspect `, you may see +one of these labels: + +| Signal | Meaning | +| -------------------------- | ------------------------------------------------------------ | +| **config valid** | Config parses fine and plugins resolve | +| **compatibility advisory** | Plugin uses a supported-but-older pattern (e.g. `hook-only`) | +| **legacy warning** | Plugin uses `before_agent_start`, which is deprecated | +| **hard error** | Config is invalid or plugin failed to load | + +Neither `hook-only` nor `before_agent_start` will break your plugin today -- +`hook-only` is advisory, and `before_agent_start` only triggers a warning. These +signals also appear in `openclaw status --all` and `openclaw plugins doctor`. + +## Architecture overview + +OpenClaw's plugin system has four layers: + +1. **Manifest + discovery** + OpenClaw finds candidate plugins from configured paths, workspace roots, + global extension roots, and bundled extensions. Discovery reads native + `openclaw.plugin.json` manifests plus supported bundle manifests first. +2. **Enablement + validation** + Core decides whether a discovered plugin is enabled, disabled, blocked, or + selected for an exclusive slot such as memory. +3. **Runtime loading** + Native OpenClaw plugins are loaded in-process via jiti and register + capabilities into a central registry. Compatible bundles are normalized into + registry records without importing runtime code. +4. **Surface consumption** + The rest of OpenClaw reads the registry to expose tools, channels, provider + setup, hooks, HTTP routes, CLI commands, and services. + +The important design boundary: + +- discovery + config validation should work from **manifest/schema metadata** + without executing plugin code +- native runtime behavior comes from the plugin module's `register(api)` path + +That split lets OpenClaw validate config, explain missing/disabled plugins, and +build UI/schema hints before the full runtime is active. + +### Channel plugins and the shared message tool + +Channel plugins do not need to register a separate send/edit/react tool for +normal chat actions. OpenClaw keeps one shared `message` tool in core, and +channel plugins own the channel-specific discovery and execution behind it. + +The current boundary is: + +- core owns the shared `message` tool host, prompt wiring, session/thread + bookkeeping, and execution dispatch +- channel plugins own scoped action discovery, capability discovery, and any + channel-specific schema fragments +- channel plugins execute the final action through their action adapter + +For channel plugins, the SDK surface is +`ChannelMessageActionAdapter.describeMessageTool(...)`. That unified discovery +call lets a plugin return its visible actions, capabilities, and schema +contributions together so those pieces do not drift apart. + +Core passes runtime scope into that discovery step. Important fields include: + +- `accountId` +- `currentChannelId` +- `currentThreadTs` +- `currentMessageId` +- `sessionKey` +- `sessionId` +- `agentId` +- trusted inbound `requesterSenderId` + +That matters for context-sensitive plugins. A channel can hide or expose +message actions based on the active account, current room/thread/message, or +trusted requester identity without hardcoding channel-specific branches in the +core `message` tool. + +This is why embedded-runner routing changes are still plugin work: the runner is +responsible for forwarding the current chat/session identity into the plugin +discovery boundary so the shared `message` tool exposes the right channel-owned +surface for the current turn. + +For channel-owned execution helpers, bundled plugins should keep the execution +runtime inside their own extension modules. Core no longer owns the Discord, +Slack, Telegram, or WhatsApp message-action runtimes under `src/agents/tools`. +We do not publish separate `plugin-sdk/*-action-runtime` subpaths, and bundled +plugins should import their own local runtime code directly from their +extension-owned modules. + +For polls specifically, there are two execution paths: + +- `outbound.sendPoll` is the shared baseline for channels that fit the common + poll model +- `actions.handleAction("poll")` is the preferred path for channel-specific + poll semantics or extra poll parameters + +Core now defers shared poll parsing until after plugin poll dispatch declines +the action, so plugin-owned poll handlers can accept channel-specific poll +fields without being blocked by the generic poll parser first. + +See [Load pipeline](#load-pipeline) for the full startup sequence. + +## Capability ownership model + +OpenClaw treats a native plugin as the ownership boundary for a **company** or a +**feature**, not as a grab bag of unrelated integrations. + +That means: + +- a company plugin should usually own all of that company's OpenClaw-facing + surfaces +- a feature plugin should usually own the full feature surface it introduces +- channels should consume shared core capabilities instead of re-implementing + provider behavior ad hoc + +Examples: + +- the bundled `openai` plugin owns OpenAI model-provider behavior and OpenAI + speech + media-understanding + image-generation behavior +- the bundled `elevenlabs` plugin owns ElevenLabs speech behavior +- the bundled `microsoft` plugin owns Microsoft speech behavior +- the bundled `google` plugin owns Google model-provider behavior plus Google + media-understanding + image-generation + web-search behavior +- the bundled `minimax`, `mistral`, `moonshot`, and `zai` plugins own their + media-understanding backends +- the `voice-call` plugin is a feature plugin: it owns call transport, tools, + CLI, routes, and runtime, but it consumes core TTS/STT capability instead of + inventing a second speech stack + +The intended end state is: + +- OpenAI lives in one plugin even if it spans text models, speech, images, and + future video +- another vendor can do the same for its own surface area +- channels do not care which vendor plugin owns the provider; they consume the + shared capability contract exposed by core + +This is the key distinction: + +- **plugin** = ownership boundary +- **capability** = core contract that multiple plugins can implement or consume + +So if OpenClaw adds a new domain such as video, the first question is not +"which provider should hardcode video handling?" The first question is "what is +the core video capability contract?" Once that contract exists, vendor plugins +can register against it and channel/feature plugins can consume it. + +If the capability does not exist yet, the right move is usually: + +1. define the missing capability in core +2. expose it through the plugin API/runtime in a typed way +3. wire channels/features against that capability +4. let vendor plugins register implementations + +This keeps ownership explicit while avoiding core behavior that depends on a +single vendor or a one-off plugin-specific code path. + +### Capability layering + +Use this mental model when deciding where code belongs: + +- **core capability layer**: shared orchestration, policy, fallback, config + merge rules, delivery semantics, and typed contracts +- **vendor plugin layer**: vendor-specific APIs, auth, model catalogs, speech + synthesis, image generation, future video backends, usage endpoints +- **channel/feature plugin layer**: Slack/Discord/voice-call/etc. integration + that consumes core capabilities and presents them on a surface + +For example, TTS follows this shape: + +- core owns reply-time TTS policy, fallback order, prefs, and channel delivery +- `openai`, `elevenlabs`, and `microsoft` own synthesis implementations +- `voice-call` consumes the telephony TTS runtime helper + +That same pattern should be preferred for future capabilities. + +### Multi-capability company plugin example + +A company plugin should feel cohesive from the outside. If OpenClaw has shared +contracts for models, speech, media understanding, and web search, a vendor can +own all of its surfaces in one place: + +```ts +import type { OpenClawPluginDefinition } from "openclaw/plugin-sdk"; +import { + buildOpenAISpeechProvider, + createPluginBackedWebSearchProvider, + describeImageWithModel, + transcribeOpenAiCompatibleAudio, +} from "openclaw/plugin-sdk"; + +const plugin: OpenClawPluginDefinition = { + id: "exampleai", + name: "ExampleAI", + register(api) { + api.registerProvider({ + id: "exampleai", + // auth/model catalog/runtime hooks + }); + + api.registerSpeechProvider( + buildOpenAISpeechProvider({ + id: "exampleai", + // vendor speech config + }), + ); + + api.registerMediaUnderstandingProvider({ + id: "exampleai", + capabilities: ["image", "audio", "video"], + async describeImage(req) { + return describeImageWithModel({ + provider: "exampleai", + model: req.model, + input: req.input, + }); + }, + async transcribeAudio(req) { + return transcribeOpenAiCompatibleAudio({ + provider: "exampleai", + model: req.model, + input: req.input, + }); + }, + }); + + api.registerWebSearchProvider( + createPluginBackedWebSearchProvider({ + id: "exampleai-search", + // credential + fetch logic + }), + ); + }, +}; + +export default plugin; +``` + +What matters is not the exact helper names. The shape matters: + +- one plugin owns the vendor surface +- core still owns the capability contracts +- channels and feature plugins consume `api.runtime.*` helpers, not vendor code +- contract tests can assert that the plugin registered the capabilities it + claims to own + +### Capability example: video understanding + +OpenClaw already treats image/audio/video understanding as one shared +capability. The same ownership model applies there: + +1. core defines the media-understanding contract +2. vendor plugins register `describeImage`, `transcribeAudio`, and + `describeVideo` as applicable +3. channels and feature plugins consume the shared core behavior instead of + wiring directly to vendor code + +That avoids baking one provider's video assumptions into core. The plugin owns +the vendor surface; core owns the capability contract and fallback behavior. + +If OpenClaw adds a new domain later, such as video generation, use the same +sequence again: define the core capability first, then let vendor plugins +register implementations against it. + +Need a concrete rollout checklist? See +[Capability Cookbook](/tools/capability-cookbook). + +## Contracts and enforcement + +The plugin API surface is intentionally typed and centralized in +`OpenClawPluginApi`. That contract defines the supported registration points and +the runtime helpers a plugin may rely on. + +Why this matters: + +- plugin authors get one stable internal standard +- core can reject duplicate ownership such as two plugins registering the same + provider id +- startup can surface actionable diagnostics for malformed registration +- contract tests can enforce bundled-plugin ownership and prevent silent drift + +There are two layers of enforcement: + +1. **runtime registration enforcement** + The plugin registry validates registrations as plugins load. Examples: + duplicate provider ids, duplicate speech provider ids, and malformed + registrations produce plugin diagnostics instead of undefined behavior. +2. **contract tests** + Bundled plugins are captured in contract registries during test runs so + OpenClaw can assert ownership explicitly. Today this is used for model + providers, speech providers, web search providers, and bundled registration + ownership. + +The practical effect is that OpenClaw knows, up front, which plugin owns which +surface. That lets core and channels compose seamlessly because ownership is +declared, typed, and testable rather than implicit. + +### What belongs in a contract + +Good plugin contracts are: + +- typed +- small +- capability-specific +- owned by core +- reusable by multiple plugins +- consumable by channels/features without vendor knowledge + +Bad plugin contracts are: + +- vendor-specific policy hidden in core +- one-off plugin escape hatches that bypass the registry +- channel code reaching straight into a vendor implementation +- ad hoc runtime objects that are not part of `OpenClawPluginApi` or + `api.runtime` + +When in doubt, raise the abstraction level: define the capability first, then +let plugins plug into it. + +## Execution model + +Native OpenClaw plugins run **in-process** with the Gateway. They are not +sandboxed. A loaded native plugin has the same process-level trust boundary as +core code. + +Implications: + +- a native plugin can register tools, network handlers, hooks, and services +- a native plugin bug can crash or destabilize the gateway +- a malicious native plugin is equivalent to arbitrary code execution inside + the OpenClaw process + +Compatible bundles are safer by default because OpenClaw currently treats them +as metadata/content packs. In current releases, that mostly means bundled +skills. + +Use allowlists and explicit install/load paths for non-bundled plugins. Treat +workspace plugins as development-time code, not production defaults. + +Important trust note: + +- `plugins.allow` trusts **plugin ids**, not source provenance. +- A workspace plugin with the same id as a bundled plugin intentionally shadows + the bundled copy when that workspace plugin is enabled/allowlisted. +- This is normal and useful for local development, patch testing, and hotfixes. + +## Export boundary + +OpenClaw exports capabilities, not implementation convenience. + +Keep capability registration public. Trim non-contract helper exports: + +- bundled-plugin-specific helper subpaths +- runtime plumbing subpaths not intended as public API +- vendor-specific convenience helpers +- setup/onboarding helpers that are implementation details + +## Load pipeline + +At startup, OpenClaw does roughly this: + +1. discover candidate plugin roots +2. read native or compatible bundle manifests and package metadata +3. reject unsafe candidates +4. normalize plugin config (`plugins.enabled`, `allow`, `deny`, `entries`, + `slots`, `load.paths`) +5. decide enablement for each candidate +6. load enabled native modules via jiti +7. call native `register(api)` hooks and collect registrations into the plugin registry +8. expose the registry to commands/runtime surfaces + +The safety gates happen **before** runtime execution. Candidates are blocked +when the entry escapes the plugin root, the path is world-writable, or path +ownership looks suspicious for non-bundled plugins. + +### Manifest-first behavior + +The manifest is the control-plane source of truth. OpenClaw uses it to: + +- identify the plugin +- discover declared channels/skills/config schema or bundle capabilities +- validate `plugins.entries..config` +- augment Control UI labels/placeholders +- show install/catalog metadata + +For native plugins, the runtime module is the data-plane part. It registers +actual behavior such as hooks, tools, commands, or provider flows. + +### What the loader caches + +OpenClaw keeps short in-process caches for: + +- discovery results +- manifest registry data +- loaded plugin registries + +These caches reduce bursty startup and repeated command overhead. They are safe +to think of as short-lived performance caches, not persistence. + +Performance note: + +- Set `OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1` or + `OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1` to disable these caches. +- Tune cache windows with `OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS` and + `OPENCLAW_PLUGIN_MANIFEST_CACHE_MS`. + +## Registry model + +Loaded plugins do not directly mutate random core globals. They register into a +central plugin registry. + +The registry tracks: + +- plugin records (identity, source, origin, status, diagnostics) +- tools +- legacy hooks and typed hooks +- channels +- providers +- gateway RPC handlers +- HTTP routes +- CLI registrars +- background services +- plugin-owned commands + +Core features then read from that registry instead of talking to plugin modules +directly. This keeps loading one-way: + +- plugin module -> registry registration +- core runtime -> registry consumption + +That separation matters for maintainability. It means most core surfaces only +need one integration point: "read the registry", not "special-case every plugin +module". + +## Conversation binding callbacks + +Plugins that bind a conversation can react when an approval is resolved. + +Use `api.onConversationBindingResolved(...)` to receive a callback after a bind +request is approved or denied: + +```ts +export default { + id: "my-plugin", + register(api) { + api.onConversationBindingResolved(async (event) => { + if (event.status === "approved") { + // A binding now exists for this plugin + conversation. + console.log(event.binding?.conversationId); + return; + } + + // The request was denied; clear any local pending state. + console.log(event.request.conversation.conversationId); + }); + }, +}; +``` + +Callback payload fields: + +- `status`: `"approved"` or `"denied"` +- `decision`: `"allow-once"`, `"allow-always"`, or `"deny"` +- `binding`: the resolved binding for approved requests +- `request`: the original request summary, detach hint, sender id, and + conversation metadata + +This callback is notification-only. It does not change who is allowed to bind a +conversation, and it runs after core approval handling finishes. + +## Provider runtime hooks + +Provider plugins now have two layers: + +- manifest metadata: `providerAuthEnvVars` for cheap env-auth lookup before + runtime load, plus `providerAuthChoices` for cheap onboarding/auth-choice + labels and CLI flag metadata before runtime load +- config-time hooks: `catalog` / legacy `discovery` +- runtime hooks: `resolveDynamicModel`, `prepareDynamicModel`, `normalizeResolvedModel`, `capabilities`, `prepareExtraParams`, `wrapStreamFn`, `formatApiKey`, `refreshOAuth`, `buildAuthDoctorHint`, `isCacheTtlEligible`, `buildMissingAuthMessage`, `suppressBuiltInModel`, `augmentModelCatalog`, `isBinaryThinking`, `supportsXHighThinking`, `resolveDefaultThinkingLevel`, `isModernModelRef`, `prepareRuntimeAuth`, `resolveUsageAuth`, `fetchUsageSnapshot` + +OpenClaw still owns the generic agent loop, failover, transcript handling, and +tool policy. These hooks are the extension surface for provider-specific behavior without +needing a whole custom inference transport. + +Use manifest `providerAuthEnvVars` when the provider has env-based credentials +that generic auth/status/model-picker paths should see without loading plugin +runtime. Use manifest `providerAuthChoices` when onboarding/auth-choice CLI +surfaces should know the provider's choice id, group labels, and simple +one-flag auth wiring without loading provider runtime. Keep provider runtime +`envVars` for operator-facing hints such as onboarding labels or OAuth +client-id/client-secret setup vars. + +### Hook order and usage + +For model/provider plugins, OpenClaw calls hooks in this rough order. +The "When to use" column is the quick decision guide. + +| # | Hook | What it does | When to use | +| --- | ----------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| 1 | `catalog` | Publish provider config into `models.providers` during `models.json` generation | Provider owns a catalog or base URL defaults | +| -- | _(built-in model lookup)_ | OpenClaw tries the normal registry/catalog path first | _(not a plugin hook)_ | +| 2 | `resolveDynamicModel` | Sync fallback for provider-owned model ids not in the local registry yet | Provider accepts arbitrary upstream model ids | +| 3 | `prepareDynamicModel` | Async warm-up, then `resolveDynamicModel` runs again | Provider needs network metadata before resolving unknown ids | +| 4 | `normalizeResolvedModel` | Final rewrite before the embedded runner uses the resolved model | Provider needs transport rewrites but still uses a core transport | +| 5 | `capabilities` | Provider-owned transcript/tooling metadata used by shared core logic | Provider needs transcript/provider-family quirks | +| 6 | `prepareExtraParams` | Request-param normalization before generic stream option wrappers | Provider needs default request params or per-provider param cleanup | +| 7 | `wrapStreamFn` | Stream wrapper after generic wrappers are applied | Provider needs request headers/body/model compat wrappers without a custom transport | +| 8 | `formatApiKey` | Auth-profile formatter: stored profile becomes the runtime `apiKey` string | Provider stores extra auth metadata and needs a custom runtime token shape | +| 9 | `refreshOAuth` | OAuth refresh override for custom refresh endpoints or refresh-failure policy | Provider does not fit the shared `pi-ai` refreshers | +| 10 | `buildAuthDoctorHint` | Repair hint appended when OAuth refresh fails | Provider needs provider-owned auth repair guidance after refresh failure | +| 11 | `isCacheTtlEligible` | Prompt-cache policy for proxy/backhaul providers | Provider needs proxy-specific cache TTL gating | +| 12 | `buildMissingAuthMessage` | Replacement for the generic missing-auth recovery message | Provider needs a provider-specific missing-auth recovery hint | +| 13 | `suppressBuiltInModel` | Stale upstream model suppression plus optional user-facing error hint | Provider needs to hide stale upstream rows or replace them with a vendor hint | +| 14 | `augmentModelCatalog` | Synthetic/final catalog rows appended after discovery | Provider needs synthetic forward-compat rows in `models list` and pickers | +| 15 | `isBinaryThinking` | On/off reasoning toggle for binary-thinking providers | Provider exposes only binary thinking on/off | +| 16 | `supportsXHighThinking` | `xhigh` reasoning support for selected models | Provider wants `xhigh` on only a subset of models | +| 17 | `resolveDefaultThinkingLevel` | Default `/think` level for a specific model family | Provider owns default `/think` policy for a model family | +| 18 | `isModernModelRef` | Modern-model matcher for live profile filters and smoke selection | Provider owns live/smoke preferred-model matching | +| 19 | `prepareRuntimeAuth` | Exchange a configured credential into the actual runtime token/key just before inference | Provider needs a token exchange or short-lived request credential | +| 20 | `resolveUsageAuth` | Resolve usage/billing credentials for `/usage` and related status surfaces | Provider needs custom usage/quota token parsing or a different usage credential | +| 21 | `fetchUsageSnapshot` | Fetch and normalize provider-specific usage/quota snapshots after auth is resolved | Provider needs a provider-specific usage endpoint or payload parser | + +If the provider needs a fully custom wire protocol or custom request executor, +that is a different class of extension. These hooks are for provider behavior +that still runs on OpenClaw's normal inference loop. + +### Provider example + +```ts +api.registerProvider({ + id: "example-proxy", + label: "Example Proxy", + auth: [], + catalog: { + order: "simple", + run: async (ctx) => { + const apiKey = ctx.resolveProviderApiKey("example-proxy").apiKey; + if (!apiKey) { + return null; + } + return { + provider: { + baseUrl: "https://proxy.example.com/v1", + apiKey, + api: "openai-completions", + models: [{ id: "auto", name: "Auto" }], + }, + }; + }, + }, + resolveDynamicModel: (ctx) => ({ + id: ctx.modelId, + name: ctx.modelId, + provider: "example-proxy", + api: "openai-completions", + baseUrl: "https://proxy.example.com/v1", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 8192, + }), + prepareRuntimeAuth: async (ctx) => { + const exchanged = await exchangeToken(ctx.apiKey); + return { + apiKey: exchanged.token, + baseUrl: exchanged.baseUrl, + expiresAt: exchanged.expiresAt, + }; + }, + resolveUsageAuth: async (ctx) => { + const auth = await ctx.resolveOAuthToken(); + return auth ? { token: auth.token } : null; + }, + fetchUsageSnapshot: async (ctx) => { + return await fetchExampleProxyUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn); + }, +}); +``` + +### Built-in examples + +- Anthropic uses `resolveDynamicModel`, `capabilities`, `buildAuthDoctorHint`, + `resolveUsageAuth`, `fetchUsageSnapshot`, `isCacheTtlEligible`, + `resolveDefaultThinkingLevel`, and `isModernModelRef` because it owns Claude + 4.6 forward-compat, provider-family hints, auth repair guidance, usage + endpoint integration, prompt-cache eligibility, and Claude default/adaptive + thinking policy. +- OpenAI uses `resolveDynamicModel`, `normalizeResolvedModel`, and + `capabilities` plus `buildMissingAuthMessage`, `suppressBuiltInModel`, + `augmentModelCatalog`, `supportsXHighThinking`, and `isModernModelRef` + because it owns GPT-5.4 forward-compat, the direct OpenAI + `openai-completions` -> `openai-responses` normalization, Codex-aware auth + hints, Spark suppression, synthetic OpenAI list rows, and GPT-5 thinking / + live-model policy. +- OpenRouter uses `catalog` plus `resolveDynamicModel` and + `prepareDynamicModel` because the provider is pass-through and may expose new + model ids before OpenClaw's static catalog updates; it also uses + `capabilities`, `wrapStreamFn`, and `isCacheTtlEligible` to keep + provider-specific request headers, routing metadata, reasoning patches, and + prompt-cache policy out of core. +- GitHub Copilot uses `catalog`, `auth`, `resolveDynamicModel`, and + `capabilities` plus `prepareRuntimeAuth` and `fetchUsageSnapshot` because it + needs provider-owned device login, model fallback behavior, Claude transcript + quirks, a GitHub token -> Copilot token exchange, and a provider-owned usage + endpoint. +- OpenAI Codex uses `catalog`, `resolveDynamicModel`, + `normalizeResolvedModel`, `refreshOAuth`, and `augmentModelCatalog` plus + `prepareExtraParams`, `resolveUsageAuth`, and `fetchUsageSnapshot` because it + still runs on core OpenAI transports but owns its transport/base URL + normalization, OAuth refresh fallback policy, default transport choice, + synthetic Codex catalog rows, and ChatGPT usage endpoint integration. +- Google AI Studio and Gemini CLI OAuth use `resolveDynamicModel` and + `isModernModelRef` because they own Gemini 3.1 forward-compat fallback and + modern-model matching; Gemini CLI OAuth also uses `formatApiKey`, + `resolveUsageAuth`, and `fetchUsageSnapshot` for token formatting, token + parsing, and quota endpoint wiring. +- Moonshot uses `catalog` plus `wrapStreamFn` because it still uses the shared + OpenAI transport but needs provider-owned thinking payload normalization. +- Kilocode uses `catalog`, `capabilities`, `wrapStreamFn`, and + `isCacheTtlEligible` because it needs provider-owned request headers, + reasoning payload normalization, Gemini transcript hints, and Anthropic + cache-TTL gating. +- Z.AI uses `resolveDynamicModel`, `prepareExtraParams`, `wrapStreamFn`, + `isCacheTtlEligible`, `isBinaryThinking`, `isModernModelRef`, + `resolveUsageAuth`, and `fetchUsageSnapshot` because it owns GLM-5 fallback, + `tool_stream` defaults, binary thinking UX, modern-model matching, and both + usage auth + quota fetching. +- Mistral, OpenCode Zen, and OpenCode Go use `capabilities` only to keep + transcript/tooling quirks out of core. +- Catalog-only bundled providers such as `byteplus`, `cloudflare-ai-gateway`, + `huggingface`, `kimi-coding`, `modelstudio`, `nvidia`, `qianfan`, + `synthetic`, `together`, `venice`, `vercel-ai-gateway`, and `volcengine` use + `catalog` only. +- Qwen portal uses `catalog`, `auth`, and `refreshOAuth`. +- MiniMax and Xiaomi use `catalog` plus usage hooks because their `/usage` + behavior is plugin-owned even though inference still runs through the shared + transports. + +## Runtime helpers + +Plugins can access selected core helpers via `api.runtime`. For TTS: + +```ts +const clip = await api.runtime.tts.textToSpeech({ + text: "Hello from OpenClaw", + cfg: api.config, +}); + +const result = await api.runtime.tts.textToSpeechTelephony({ + text: "Hello from OpenClaw", + cfg: api.config, +}); + +const voices = await api.runtime.tts.listVoices({ + provider: "elevenlabs", + cfg: api.config, +}); +``` + +Notes: + +- `textToSpeech` returns the normal core TTS output payload for file/voice-note surfaces. +- Uses core `messages.tts` configuration and provider selection. +- Returns PCM audio buffer + sample rate. Plugins must resample/encode for providers. +- `listVoices` is optional per provider. Use it for vendor-owned voice pickers or setup flows. +- Voice listings can include richer metadata such as locale, gender, and personality tags for provider-aware pickers. +- OpenAI and ElevenLabs support telephony today. Microsoft does not. + +Plugins can also register speech providers via `api.registerSpeechProvider(...)`. + +```ts +api.registerSpeechProvider({ + id: "acme-speech", + label: "Acme Speech", + isConfigured: ({ config }) => Boolean(config.messages?.tts), + synthesize: async (req) => { + return { + audioBuffer: Buffer.from([]), + outputFormat: "mp3", + fileExtension: ".mp3", + voiceCompatible: false, + }; + }, +}); +``` + +Notes: + +- Keep TTS policy, fallback, and reply delivery in core. +- Use speech providers for vendor-owned synthesis behavior. +- Legacy Microsoft `edge` input is normalized to the `microsoft` provider id. +- The preferred ownership model is company-oriented: one vendor plugin can own + text, speech, image, and future media providers as OpenClaw adds those + capability contracts. + +For image/audio/video understanding, plugins register one typed +media-understanding provider instead of a generic key/value bag: + +```ts +api.registerMediaUnderstandingProvider({ + id: "google", + capabilities: ["image", "audio", "video"], + describeImage: async (req) => ({ text: "..." }), + transcribeAudio: async (req) => ({ text: "..." }), + describeVideo: async (req) => ({ text: "..." }), +}); +``` + +Notes: + +- Keep orchestration, fallback, config, and channel wiring in core. +- Keep vendor behavior in the provider plugin. +- Additive expansion should stay typed: new optional methods, new optional + result fields, new optional capabilities. +- If OpenClaw adds a new capability such as video generation later, define the + core capability contract first, then let vendor plugins register against it. + +For media-understanding runtime helpers, plugins can call: + +```ts +const image = await api.runtime.mediaUnderstanding.describeImageFile({ + filePath: "/tmp/inbound-photo.jpg", + cfg: api.config, + agentDir: "/tmp/agent", +}); + +const video = await api.runtime.mediaUnderstanding.describeVideoFile({ + filePath: "/tmp/inbound-video.mp4", + cfg: api.config, +}); +``` + +For audio transcription, plugins can use either the media-understanding runtime +or the older STT alias: + +```ts +const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({ + filePath: "/tmp/inbound-audio.ogg", + cfg: api.config, + // Optional when MIME cannot be inferred reliably: + mime: "audio/ogg", +}); +``` + +Notes: + +- `api.runtime.mediaUnderstanding.*` is the preferred shared surface for + image/audio/video understanding. +- Uses core media-understanding audio configuration (`tools.media.audio`) and provider fallback order. +- Returns `{ text: undefined }` when no transcription output is produced (for example skipped/unsupported input). +- `api.runtime.stt.transcribeAudioFile(...)` remains as a compatibility alias. + +Plugins can also launch background subagent runs through `api.runtime.subagent`: + +```ts +const result = await api.runtime.subagent.run({ + sessionKey: "agent:main:subagent:search-helper", + message: "Expand this query into focused follow-up searches.", + provider: "openai", + model: "gpt-4.1-mini", + deliver: false, +}); +``` + +Notes: + +- `provider` and `model` are optional per-run overrides, not persistent session changes. +- OpenClaw only honors those override fields for trusted callers. +- For plugin-owned fallback runs, operators must opt in with `plugins.entries..subagent.allowModelOverride: true`. +- Use `plugins.entries..subagent.allowedModels` to restrict trusted plugins to specific canonical `provider/model` targets, or `"*"` to allow any target explicitly. +- Untrusted plugin subagent runs still work, but override requests are rejected instead of silently falling back. + +For web search, plugins can consume the shared runtime helper instead of +reaching into the agent tool wiring: + +```ts +const providers = api.runtime.webSearch.listProviders({ + config: api.config, +}); + +const result = await api.runtime.webSearch.search({ + config: api.config, + args: { + query: "OpenClaw plugin runtime helpers", + count: 5, + }, +}); +``` + +Plugins can also register web-search providers via +`api.registerWebSearchProvider(...)`. + +Notes: + +- Keep provider selection, credential resolution, and shared request semantics in core. +- Use web-search providers for vendor-specific search transports. +- `api.runtime.webSearch.*` is the preferred shared surface for feature/channel plugins that need search behavior without depending on the agent tool wrapper. + +## Gateway HTTP routes + +Plugins can expose HTTP endpoints with `api.registerHttpRoute(...)`. + +```ts +api.registerHttpRoute({ + path: "/acme/webhook", + auth: "plugin", + match: "exact", + handler: async (_req, res) => { + res.statusCode = 200; + res.end("ok"); + return true; + }, +}); +``` + +Route fields: + +- `path`: route path under the gateway HTTP server. +- `auth`: required. Use `"gateway"` to require normal gateway auth, or `"plugin"` for plugin-managed auth/webhook verification. +- `match`: optional. `"exact"` (default) or `"prefix"`. +- `replaceExisting`: optional. Allows the same plugin to replace its own existing route registration. +- `handler`: return `true` when the route handled the request. + +Notes: + +- `api.registerHttpHandler(...)` is obsolete. Use `api.registerHttpRoute(...)`. +- Plugin routes must declare `auth` explicitly. +- Exact `path + match` conflicts are rejected unless `replaceExisting: true`, and one plugin cannot replace another plugin's route. +- Overlapping routes with different `auth` levels are rejected. Keep `exact`/`prefix` fallthrough chains on the same auth level only. + +## Plugin SDK import paths + +Use SDK subpaths instead of the monolithic `openclaw/plugin-sdk` import when +authoring plugins: + +- `openclaw/plugin-sdk/plugin-entry` for plugin registration primitives. +- `openclaw/plugin-sdk/core` for the generic shared plugin-facing contract. +- Stable channel primitives such as `openclaw/plugin-sdk/channel-setup`, + `openclaw/plugin-sdk/channel-pairing`, + `openclaw/plugin-sdk/channel-reply-pipeline`, + `openclaw/plugin-sdk/secret-input`, and + `openclaw/plugin-sdk/webhook-ingress` for shared setup/auth/reply/webhook + wiring. +- Domain subpaths such as `openclaw/plugin-sdk/channel-config-helpers`, + `openclaw/plugin-sdk/channel-config-schema`, + `openclaw/plugin-sdk/channel-policy`, + `openclaw/plugin-sdk/channel-runtime`, + `openclaw/plugin-sdk/config-runtime`, + `openclaw/plugin-sdk/agent-runtime`, + `openclaw/plugin-sdk/lazy-runtime`, + `openclaw/plugin-sdk/reply-history`, + `openclaw/plugin-sdk/routing`, + `openclaw/plugin-sdk/runtime-store`, and + `openclaw/plugin-sdk/directory-runtime` for shared runtime/config helpers. +- Narrow channel-core subpaths such as `openclaw/plugin-sdk/discord-core`, + `openclaw/plugin-sdk/telegram-core`, and `openclaw/plugin-sdk/whatsapp-core` + for channel-specific primitives that should stay smaller than the full + channel helper barrels. +- Bundled extension internals remain private. External plugins should use only + `openclaw/plugin-sdk/*` subpaths. OpenClaw core/test code may use the repo + public entry points under `extensions//index.js`, `api.js`, `runtime-api.js`, + `setup-entry.js`, and narrowly scoped files such as `login-qr-api.js`. Never + import `extensions//src/*` from core or from another extension. +- Repo entry point split: + `extensions//api.js` is the helper/types barrel, + `extensions//runtime-api.js` is the runtime-only barrel, + `extensions//index.js` is the bundled plugin entry, + and `extensions//setup-entry.js` is the setup plugin entry. +- `openclaw/plugin-sdk/telegram` for Telegram channel plugin types and shared channel-facing helpers. Built-in Telegram implementation internals stay private to the bundled extension. +- `openclaw/plugin-sdk/discord` for Discord channel plugin types and shared channel-facing helpers. Built-in Discord implementation internals stay private to the bundled extension. +- `openclaw/plugin-sdk/slack` for Slack channel plugin types and shared channel-facing helpers. Built-in Slack implementation internals stay private to the bundled extension. +- `openclaw/plugin-sdk/imessage` for iMessage channel plugin types and shared channel-facing helpers. Built-in iMessage implementation internals stay private to the bundled extension. +- `openclaw/plugin-sdk/whatsapp` for WhatsApp channel plugin types and shared channel-facing helpers. Built-in WhatsApp implementation internals stay private to the bundled extension. +- `openclaw/plugin-sdk/bluebubbles` remains public because it carries a small + focused helper surface that is shared intentionally. + +Compatibility note: + +- Avoid the root `openclaw/plugin-sdk` barrel for new code. +- Prefer the narrow stable primitives first. The newer setup/pairing/reply/ + secret-input/webhook subpaths are the intended contract for new bundled and + external plugin work. +- Bundled extension-specific helper barrels are not stable by default. If a + helper is only needed by a bundled extension, keep it behind the extension's + local `api.js` or `runtime-api.js` seam instead of promoting it into + `openclaw/plugin-sdk/`. +- Capability-specific subpaths such as `image-generation`, + `media-understanding`, and `speech` exist because bundled/native plugins use + them today. Their presence does not by itself mean every exported helper is a + long-term frozen external contract. + +## Message tool schemas + +Plugins should own channel-specific `describeMessageTool(...)` schema +contributions. Keep provider-specific fields in the plugin, not in shared core. + +For shared portable schema fragments, reuse the generic helpers exported through +`openclaw/plugin-sdk/channel-runtime`: + +- `createMessageToolButtonsSchema()` for button-grid style payloads +- `createMessageToolCardSchema()` for structured card payloads + +If a schema shape only makes sense for one provider, define it in that plugin's +own source instead of promoting it into the shared SDK. + +## Channel target resolution + +Channel plugins should own channel-specific target semantics. Keep the shared +outbound host generic and use the messaging adapter surface for provider rules: + +- `messaging.inferTargetChatType({ to })` decides whether a normalized target + should be treated as `direct`, `group`, or `channel` before directory lookup. +- `messaging.targetResolver.looksLikeId(raw, normalized)` tells core whether an + input should skip straight to id-like resolution instead of directory search. +- `messaging.targetResolver.resolveTarget(...)` is the plugin fallback when + core needs a final provider-owned resolution after normalization or after a + directory miss. +- `messaging.resolveOutboundSessionRoute(...)` owns provider-specific session + route construction once a target is resolved. + +Recommended split: + +- Use `inferTargetChatType` for category decisions that should happen before + searching peers/groups. +- Use `looksLikeId` for "treat this as an explicit/native target id" checks. +- Use `resolveTarget` for provider-specific normalization fallback, not for + broad directory search. +- Keep provider-native ids like chat ids, thread ids, JIDs, handles, and room + ids inside `target` values or provider-specific params, not in generic SDK + fields. + +## Config-backed directories + +Plugins that derive directory entries from config should keep that logic in the +plugin and reuse the shared helpers from +`openclaw/plugin-sdk/directory-runtime`. + +Use this when a channel needs config-backed peers/groups such as: + +- allowlist-driven DM peers +- configured channel/group maps +- account-scoped static directory fallbacks + +The shared helpers in `directory-runtime` only handle generic operations: + +- query filtering +- limit application +- deduping/normalization helpers +- building `ChannelDirectoryEntry[]` + +Channel-specific account inspection and id normalization should stay in the +plugin implementation. + +## Provider catalogs + +Provider plugins can define model catalogs for inference with +`registerProvider({ catalog: { run(...) { ... } } })`. + +`catalog.run(...)` returns the same shape OpenClaw writes into +`models.providers`: + +- `{ provider }` for one provider entry +- `{ providers }` for multiple provider entries + +Use `catalog` when the plugin owns provider-specific model ids, base URL +defaults, or auth-gated model metadata. + +`catalog.order` controls when a plugin's catalog merges relative to OpenClaw's +built-in implicit providers: + +- `simple`: plain API-key or env-driven providers +- `profile`: providers that appear when auth profiles exist +- `paired`: providers that synthesize multiple related provider entries +- `late`: last pass, after other implicit providers + +Later providers win on key collision, so plugins can intentionally override a +built-in provider entry with the same provider id. + +Compatibility: + +- `discovery` still works as a legacy alias +- if both `catalog` and `discovery` are registered, OpenClaw uses `catalog` + +## Read-only channel inspection + +If your plugin registers a channel, prefer implementing +`plugin.config.inspectAccount(cfg, accountId)` alongside `resolveAccount(...)`. + +Why: + +- `resolveAccount(...)` is the runtime path. It is allowed to assume credentials + are fully materialized and can fail fast when required secrets are missing. +- Read-only command paths such as `openclaw status`, `openclaw status --all`, + `openclaw channels status`, `openclaw channels resolve`, and doctor/config + repair flows should not need to materialize runtime credentials just to + describe configuration. + +Recommended `inspectAccount(...)` behavior: + +- Return descriptive account state only. +- Preserve `enabled` and `configured`. +- Include credential source/status fields when relevant, such as: + - `tokenSource`, `tokenStatus` + - `botTokenSource`, `botTokenStatus` + - `appTokenSource`, `appTokenStatus` + - `signingSecretSource`, `signingSecretStatus` +- You do not need to return raw token values just to report read-only + availability. Returning `tokenStatus: "available"` (and the matching source + field) is enough for status-style commands. +- Use `configured_unavailable` when a credential is configured via SecretRef but + unavailable in the current command path. + +This lets read-only commands report "configured but unavailable in this command +path" instead of crashing or misreporting the account as not configured. + +## Package packs + +A plugin directory may include a `package.json` with `openclaw.extensions`: + +```json +{ + "name": "my-pack", + "openclaw": { + "extensions": ["./src/safety.ts", "./src/tools.ts"], + "setupEntry": "./src/setup-entry.ts" + } +} +``` + +Each entry becomes a plugin. If the pack lists multiple extensions, the plugin id +becomes `name/`. + +If your plugin imports npm deps, install them in that directory so +`node_modules` is available (`npm install` / `pnpm install`). + +Security guardrail: every `openclaw.extensions` entry must stay inside the plugin +directory after symlink resolution. Entries that escape the package directory are +rejected. + +Security note: `openclaw plugins install` installs plugin dependencies with +`npm install --ignore-scripts` (no lifecycle scripts). Keep plugin dependency +trees "pure JS/TS" and avoid packages that require `postinstall` builds. + +Optional: `openclaw.setupEntry` can point at a lightweight setup-only module. +When OpenClaw needs setup surfaces for a disabled channel plugin, or +when a channel plugin is enabled but still unconfigured, it loads `setupEntry` +instead of the full plugin entry. This keeps startup and setup lighter +when your main plugin entry also wires tools, hooks, or other runtime-only +code. + +Optional: `openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen` +can opt a channel plugin into the same `setupEntry` path during the gateway's +pre-listen startup phase, even when the channel is already configured. + +Use this only when `setupEntry` fully covers the startup surface that must exist +before the gateway starts listening. In practice, that means the setup entry +must register every channel-owned capability that startup depends on, such as: + +- channel registration itself +- any HTTP routes that must be available before the gateway starts listening +- any gateway methods, tools, or services that must exist during that same window + +If your full entry still owns any required startup capability, do not enable +this flag. Keep the plugin on the default behavior and let OpenClaw load the +full entry during startup. + +Example: + +```json +{ + "name": "@scope/my-channel", + "openclaw": { + "extensions": ["./index.ts"], + "setupEntry": "./setup-entry.ts", + "startup": { + "deferConfiguredChannelFullLoadUntilAfterListen": true + } + } +} +``` + +### Channel catalog metadata + +Channel plugins can advertise setup/discovery metadata via `openclaw.channel` and +install hints via `openclaw.install`. This keeps the core catalog data-free. + +Example: + +```json +{ + "name": "@openclaw/nextcloud-talk", + "openclaw": { + "extensions": ["./index.ts"], + "channel": { + "id": "nextcloud-talk", + "label": "Nextcloud Talk", + "selectionLabel": "Nextcloud Talk (self-hosted)", + "docsPath": "/channels/nextcloud-talk", + "docsLabel": "nextcloud-talk", + "blurb": "Self-hosted chat via Nextcloud Talk webhook bots.", + "order": 65, + "aliases": ["nc-talk", "nc"] + }, + "install": { + "npmSpec": "@openclaw/nextcloud-talk", + "localPath": "extensions/nextcloud-talk", + "defaultChoice": "npm" + } + } +} +``` + +OpenClaw can also merge **external channel catalogs** (for example, an MPM +registry export). Drop a JSON file at one of: + +- `~/.openclaw/mpm/plugins.json` +- `~/.openclaw/mpm/catalog.json` +- `~/.openclaw/plugins/catalog.json` + +Or point `OPENCLAW_PLUGIN_CATALOG_PATHS` (or `OPENCLAW_MPM_CATALOG_PATHS`) at +one or more JSON files (comma/semicolon/`PATH`-delimited). Each file should +contain `{ "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] }`. + +## Context engine plugins + +Context engine plugins own session context orchestration for ingest, assembly, +and compaction. Register them from your plugin with +`api.registerContextEngine(id, factory)`, then select the active engine with +`plugins.slots.contextEngine`. + +Use this when your plugin needs to replace or extend the default context +pipeline rather than just add memory search or hooks. + +```ts +export default function (api) { + api.registerContextEngine("lossless-claw", () => ({ + info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true }, + async ingest() { + return { ingested: true }; + }, + async assemble({ messages }) { + return { messages, estimatedTokens: 0 }; + }, + async compact() { + return { ok: true, compacted: false }; + }, + })); +} +``` + +If your engine does **not** own the compaction algorithm, keep `compact()` +implemented and delegate it explicitly: + +```ts +import { delegateCompactionToRuntime } from "openclaw/plugin-sdk/core"; + +export default function (api) { + api.registerContextEngine("my-memory-engine", () => ({ + info: { + id: "my-memory-engine", + name: "My Memory Engine", + ownsCompaction: false, + }, + async ingest() { + return { ingested: true }; + }, + async assemble({ messages }) { + return { messages, estimatedTokens: 0 }; + }, + async compact(params) { + return await delegateCompactionToRuntime(params); + }, + })); +} +``` + +## Adding a new capability + +When a plugin needs behavior that does not fit the current API, do not bypass +the plugin system with a private reach-in. Add the missing capability. + +Recommended sequence: + +1. define the core contract + Decide what shared behavior core should own: policy, fallback, config merge, + lifecycle, channel-facing semantics, and runtime helper shape. +2. add typed plugin registration/runtime surfaces + Extend `OpenClawPluginApi` and/or `api.runtime` with the smallest useful + typed capability surface. +3. wire core + channel/feature consumers + Channels and feature plugins should consume the new capability through core, + not by importing a vendor implementation directly. +4. register vendor implementations + Vendor plugins then register their backends against the capability. +5. add contract coverage + Add tests so ownership and registration shape stay explicit over time. + +This is how OpenClaw stays opinionated without becoming hardcoded to one +provider's worldview. See the [Capability Cookbook](/tools/capability-cookbook) +for a concrete file checklist and worked example. + +### Capability checklist + +When you add a new capability, the implementation should usually touch these +surfaces together: + +- core contract types in `src//types.ts` +- core runner/runtime helper in `src//runtime.ts` +- plugin API registration surface in `src/plugins/types.ts` +- plugin registry wiring in `src/plugins/registry.ts` +- plugin runtime exposure in `src/plugins/runtime/*` when feature/channel + plugins need to consume it +- capture/test helpers in `src/test-utils/plugin-registration.ts` +- ownership/contract assertions in `src/plugins/contracts/registry.ts` +- operator/plugin docs in `docs/` + +If one of those surfaces is missing, that is usually a sign the capability is +not fully integrated yet. + +### Capability template + +Minimal pattern: + +```ts +// core contract +export type VideoGenerationProviderPlugin = { + id: string; + label: string; + generateVideo: (req: VideoGenerationRequest) => Promise; +}; + +// plugin API +api.registerVideoGenerationProvider({ + id: "openai", + label: "OpenAI", + async generateVideo(req) { + return await generateOpenAiVideo(req); + }, +}); + +// shared runtime helper for feature/channel plugins +const clip = await api.runtime.videoGeneration.generateFile({ + prompt: "Show the robot walking through the lab.", + cfg, +}); +``` + +Contract test pattern: + +```ts +expect(findVideoGenerationProviderIdsForPlugin("openai")).toEqual(["openai"]); +``` + +That keeps the rule simple: + +- core owns the capability contract + orchestration +- vendor plugins own vendor implementations +- feature/channel plugins consume runtime helpers +- contract tests keep ownership explicit diff --git a/docs/plugins/building-extensions.md b/docs/plugins/building-extensions.md new file mode 100644 index 00000000000..259accaa3f0 --- /dev/null +++ b/docs/plugins/building-extensions.md @@ -0,0 +1,205 @@ +--- +title: "Building Extensions" +summary: "Step-by-step guide for creating OpenClaw channel and provider extensions" +read_when: + - You want to create a new OpenClaw plugin or extension + - You need to understand the plugin SDK import patterns + - You are adding a new channel or provider to OpenClaw +--- + +# Building Extensions + +This guide walks through creating an OpenClaw extension from scratch. Extensions +can add channels, model providers, tools, or other capabilities. + +## Prerequisites + +- OpenClaw repository cloned and dependencies installed (`pnpm install`) +- Familiarity with TypeScript (ESM) + +## Extension structure + +Every extension lives under `extensions//` and follows this layout: + +``` +extensions/my-channel/ +├── package.json # npm metadata + openclaw config +├── index.ts # Entry point (defineChannelPluginEntry) +├── setup-entry.ts # Setup wizard (optional) +├── api.ts # Public contract barrel (optional) +├── runtime-api.ts # Internal runtime barrel (optional) +└── src/ + ├── channel.ts # Channel adapter implementation + ├── runtime.ts # Runtime wiring + └── *.test.ts # Colocated tests +``` + +## Step 1: Create the package + +Create `extensions/my-channel/package.json`: + +```json +{ + "name": "@openclaw/my-channel", + "version": "2026.1.1", + "description": "OpenClaw My Channel plugin", + "type": "module", + "dependencies": {}, + "openclaw": { + "extensions": ["./index.ts"], + "setupEntry": "./setup-entry.ts", + "channel": { + "id": "my-channel", + "label": "My Channel", + "selectionLabel": "My Channel (plugin)", + "docsPath": "/channels/my-channel", + "docsLabel": "my-channel", + "blurb": "Short description of the channel.", + "order": 80 + }, + "install": { + "npmSpec": "@openclaw/my-channel", + "localPath": "extensions/my-channel" + } + } +} +``` + +The `openclaw` field tells the plugin system what your extension provides. +For provider plugins, use `providers` instead of `channel`. + +## Step 2: Define the entry point + +Create `extensions/my-channel/index.ts`: + +```typescript +import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core"; + +export default defineChannelPluginEntry({ + id: "my-channel", + name: "My Channel", + description: "Connects OpenClaw to My Channel", + plugin: { + // Channel adapter implementation + }, +}); +``` + +For provider plugins, use `definePluginEntry` instead. + +## Step 3: Import from focused subpaths + +The plugin SDK exposes many focused subpaths. Always import from specific +subpaths rather than the monolithic root: + +```typescript +// Correct: focused subpaths +import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core"; +import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline"; +import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing"; +import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store"; +import { createOptionalChannelSetupSurface } from "openclaw/plugin-sdk/channel-setup"; +import { resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy"; + +// Wrong: monolithic root (lint will reject this) +import { ... } from "openclaw/plugin-sdk"; +``` + +Common subpaths: + +| Subpath | Purpose | +| ----------------------------------- | ------------------------------------ | +| `plugin-sdk/core` | Plugin entry definitions, base types | +| `plugin-sdk/channel-setup` | Optional setup adapters/wizards | +| `plugin-sdk/channel-pairing` | DM pairing primitives | +| `plugin-sdk/channel-reply-pipeline` | Prefix + typing reply wiring | +| `plugin-sdk/channel-config-schema` | Config schema builders | +| `plugin-sdk/channel-policy` | Group/DM policy helpers | +| `plugin-sdk/secret-input` | Secret input parsing/helpers | +| `plugin-sdk/webhook-ingress` | Webhook request/target helpers | +| `plugin-sdk/runtime-store` | Persistent plugin storage | +| `plugin-sdk/allow-from` | Allowlist resolution | +| `plugin-sdk/reply-payload` | Message reply types | +| `plugin-sdk/provider-onboard` | Provider onboarding config patches | +| `plugin-sdk/testing` | Test utilities | + +Use the narrowest primitive that matches the job. Reach for `channel-runtime` +or other larger helper barrels only when a dedicated subpath does not exist yet. + +## Step 4: Use local barrels for internal imports + +Within your extension, create barrel files for internal code sharing instead +of importing through the plugin SDK: + +```typescript +// api.ts — public contract for this extension +export { MyChannelConfig } from "./src/config.js"; +export { MyChannelRuntime } from "./src/runtime.js"; + +// runtime-api.ts — internal-only exports (not for production consumers) +export { internalHelper } from "./src/helpers.js"; +``` + +**Self-import guardrail**: never import your own extension back through its +published SDK contract path from production files. Route internal imports +through `./api.ts` or `./runtime-api.ts` instead. The SDK contract is for +external consumers only. + +## Step 5: Add a plugin manifest + +Create `openclaw.plugin.json` in your extension root: + +```json +{ + "id": "my-channel", + "kind": "channel", + "channels": ["my-channel"], + "name": "My Channel Plugin", + "description": "Connects OpenClaw to My Channel" +} +``` + +See [Plugin manifest](/plugins/manifest) for the full schema. + +## Step 6: Test with contract tests + +OpenClaw runs contract tests against all registered plugins. After adding your +extension, run: + +```bash +pnpm test:contracts:channels # channel plugins +pnpm test:contracts:plugins # provider plugins +``` + +Contract tests verify your plugin conforms to the expected interface (setup +wizard, session binding, message handling, group policy, etc.). + +For unit tests, import test helpers from the public testing surface: + +```typescript +import { createTestRuntime } from "openclaw/plugin-sdk/testing"; +``` + +## Lint enforcement + +Three scripts enforce SDK boundaries: + +1. **No monolithic root imports** — `openclaw/plugin-sdk` root is rejected +2. **No direct src/ imports** — extensions cannot import `../../src/` directly +3. **No self-imports** — extensions cannot import their own `plugin-sdk/` subpath + +Run `pnpm check` to verify all boundaries before committing. + +## Checklist + +Before submitting your extension: + +- [ ] `package.json` has correct `openclaw` metadata +- [ ] Entry point uses `defineChannelPluginEntry` or `definePluginEntry` +- [ ] All imports use focused `plugin-sdk/` paths +- [ ] Internal imports use local barrels, not SDK self-imports +- [ ] `openclaw.plugin.json` manifest is present and valid +- [ ] Contract tests pass (`pnpm test:contracts`) +- [ ] Unit tests colocated as `*.test.ts` +- [ ] `pnpm check` passes (lint + format) +- [ ] Doc page created under `docs/channels/` or `docs/plugins/` diff --git a/docs/plugins/bundles.md b/docs/plugins/bundles.md new file mode 100644 index 00000000000..82a5605e099 --- /dev/null +++ b/docs/plugins/bundles.md @@ -0,0 +1,307 @@ +--- +summary: "Unified bundle format guide for Codex, Claude, and Cursor bundles in OpenClaw" +read_when: + - You want to install or debug a Codex, Claude, or Cursor-compatible bundle + - You need to understand how OpenClaw maps bundle content into native features + - You are documenting bundle compatibility or current support limits +title: "Plugin Bundles" +--- + +# Plugin bundles + +OpenClaw supports one shared class of external plugin package: **bundle +plugins**. + +Today that means three closely related ecosystems: + +- Codex bundles +- Claude bundles +- Cursor bundles + +OpenClaw shows all of them as `Format: bundle` in `openclaw plugins list`. +Verbose output and `openclaw plugins inspect ` also show the subtype +(`codex`, `claude`, or `cursor`). + +Related: + +- Plugin system overview: [Plugins](/tools/plugin) +- CLI install/list flows: [plugins](/cli/plugins) +- Native manifest schema: [Plugin manifest](/plugins/manifest) + +## What a bundle is + +A bundle is a **content/metadata pack**, not a native in-process OpenClaw +plugin. + +Today, OpenClaw does **not** execute bundle runtime code in-process. Instead, +it detects known bundle files, reads the metadata, and maps supported bundle +content into native OpenClaw surfaces such as skills, hook packs, MCP config, +and embedded Pi settings. + +That is the main trust boundary: + +- native OpenClaw plugin: runtime module executes in-process +- bundle: metadata/content pack, with selective feature mapping + +## Shared bundle model + +Codex, Claude, and Cursor bundles are similar enough that OpenClaw treats them +as one normalized model. + +Shared idea: + +- a small manifest file, or a default directory layout +- one or more content roots such as `skills/` or `commands/` +- optional tool/runtime metadata such as MCP, hooks, agents, or LSP +- install as a directory or archive, then enable in the normal plugin list + +Common OpenClaw behavior: + +- detect the bundle subtype +- normalize it into one internal bundle record +- map supported parts into native OpenClaw features +- report unsupported parts as detected-but-not-wired capabilities + +In practice, most users do not need to think about the vendor-specific format +first. The more useful question is: which bundle surfaces does OpenClaw map +today? + +## Detection order + +OpenClaw prefers native OpenClaw plugin/package layouts before bundle handling. + +Practical effect: + +- `openclaw.plugin.json` wins over bundle detection +- package installs with valid `package.json` + `openclaw.extensions` use the + native install path +- if a directory contains both native and bundle metadata, OpenClaw treats it + as native first + +That avoids partially installing a dual-format package as a bundle and then +loading it later as a native plugin. + +## What works today + +OpenClaw normalizes bundle metadata into one internal bundle record, then maps +supported surfaces into existing native behavior. + +### Supported now + +#### Skill content + +- bundle skill roots load as normal OpenClaw skill roots +- Claude `commands` roots are treated as additional skill roots +- Cursor `.cursor/commands` roots are treated as additional skill roots + +This means Claude markdown command files work through the normal OpenClaw skill +loader. Cursor command markdown works through the same path. + +#### Hook packs + +- bundle hook roots work **only** when they use the normal OpenClaw hook-pack + layout. Today this is primarily the Codex-compatible case: + - `HOOK.md` + - `handler.ts` or `handler.js` + +#### MCP for Pi + +- enabled bundles can contribute MCP server config +- OpenClaw merges bundle MCP config into the effective embedded Pi settings as + `mcpServers` +- OpenClaw also exposes supported bundle MCP tools during embedded Pi agent + turns by launching supported stdio MCP servers as subprocesses +- project-local Pi settings still apply after bundle defaults, so workspace + settings can override bundle MCP entries when needed + +#### Embedded Pi settings + +- Claude `settings.json` is imported as default embedded Pi settings when the + bundle is enabled +- OpenClaw sanitizes shell override keys before applying them + +Sanitized keys: + +- `shellPath` +- `shellCommandPrefix` + +### Detected but not executed + +These surfaces are detected, shown in bundle capabilities, and may appear in +diagnostics/info output, but OpenClaw does not run them yet: + +- Claude `agents` +- Claude `hooks.json` automation +- Claude `lspServers` +- Claude `outputStyles` +- Cursor `.cursor/agents` +- Cursor `.cursor/hooks.json` +- Cursor `.cursor/rules` +- Codex inline/app metadata beyond capability reporting + +## Capability reporting + +`openclaw plugins inspect ` shows bundle capabilities from the normalized +bundle record. + +Supported capabilities are loaded quietly. Unsupported capabilities produce a +warning such as: + +```text +bundle capability detected but not wired into OpenClaw yet: agents +``` + +Current exceptions: + +- Claude `commands` is considered supported because it maps to skills +- Claude `settings` is considered supported because it maps to embedded Pi settings +- Cursor `commands` is considered supported because it maps to skills +- bundle MCP is considered supported because it maps into embedded Pi settings + and exposes supported stdio tools to embedded Pi +- Codex `hooks` is considered supported only for OpenClaw hook-pack layouts + +## Format differences + +The formats are close, but not byte-for-byte identical. These are the practical +differences that matter in OpenClaw. + +### Codex + +Typical markers: + +- `.codex-plugin/plugin.json` +- optional `skills/` +- optional `hooks/` +- optional `.mcp.json` +- optional `.app.json` + +Codex bundles fit OpenClaw best when they use skill roots and OpenClaw-style +hook-pack directories. + +### Claude + +OpenClaw supports both: + +- manifest-based Claude bundles: `.claude-plugin/plugin.json` +- manifestless Claude bundles that use the default Claude layout + +Default Claude layout markers OpenClaw recognizes: + +- `skills/` +- `commands/` +- `agents/` +- `hooks/hooks.json` +- `.mcp.json` +- `.lsp.json` +- `settings.json` + +Claude-specific notes: + +- `commands/` is treated like skill content +- `settings.json` is imported into embedded Pi settings +- `.mcp.json` and manifest `mcpServers` can expose supported stdio tools to + embedded Pi +- `hooks/hooks.json` is detected, but not executed as Claude automation + +### Cursor + +Typical markers: + +- `.cursor-plugin/plugin.json` +- optional `skills/` +- optional `.cursor/commands/` +- optional `.cursor/agents/` +- optional `.cursor/rules/` +- optional `.cursor/hooks.json` +- optional `.mcp.json` + +Cursor-specific notes: + +- `.cursor/commands/` is treated like skill content +- `.cursor/rules/`, `.cursor/agents/`, and `.cursor/hooks.json` are + detect-only today + +## Claude custom paths + +Claude bundle manifests can declare custom component paths. OpenClaw treats +those paths as **additive**, not replacing defaults. + +Currently recognized custom path keys: + +- `skills` +- `commands` +- `agents` +- `hooks` +- `mcpServers` +- `lspServers` +- `outputStyles` + +Examples: + +- default `commands/` plus manifest `commands: "extra-commands"` => + OpenClaw scans both +- default `skills/` plus manifest `skills: ["team-skills"]` => + OpenClaw scans both + +## Security model + +Bundle support is intentionally narrower than native plugin support. + +Current behavior: + +- bundle discovery reads files inside the plugin root with boundary checks +- skills and hook-pack paths must stay inside the plugin root +- bundle settings files are read with the same boundary checks +- supported stdio bundle MCP servers may be launched as subprocesses for + embedded Pi tool calls +- OpenClaw does not load arbitrary bundle runtime modules in-process + +This makes bundle support safer by default than native plugin modules, but you +should still treat third-party bundles as trusted content for the features they +do expose. + +## Install examples + +```bash +openclaw plugins install ./my-codex-bundle +openclaw plugins install ./my-claude-bundle +openclaw plugins install ./my-cursor-bundle +openclaw plugins install ./my-bundle.tgz +openclaw plugins marketplace list +openclaw plugins install @ +openclaw plugins inspect my-bundle +``` + +If the directory is a native OpenClaw plugin/package, the native install path +still wins. + +For Claude marketplace names, OpenClaw reads the local Claude known-marketplace +registry at `~/.claude/plugins/known_marketplaces.json`. Marketplace entries +can resolve to bundle-compatible directories/archives or to native plugin +sources; after resolution, the normal install rules still apply. + +## Troubleshooting + +### Bundle is detected but capabilities do not run + +Check `openclaw plugins inspect `. + +If the capability is listed but OpenClaw says it is not wired yet, that is a +real product limit, not a broken install. + +### Claude command files do not appear + +Make sure the bundle is enabled and the markdown files are inside a detected +`commands` root or `skills` root. + +### Claude settings do not apply + +Current support is limited to embedded Pi settings from `settings.json`. +OpenClaw does not treat bundle settings as raw OpenClaw config patches. + +### Claude hooks do not execute + +`hooks/hooks.json` is only detected today. + +If you need runnable bundle hooks today, use the normal OpenClaw hook-pack +layout through a supported Codex hook root or ship a native OpenClaw plugin. diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md index d23f036880a..511c2226b2a 100644 --- a/docs/plugins/manifest.md +++ b/docs/plugins/manifest.md @@ -1,19 +1,39 @@ --- summary: "Plugin manifest + JSON schema requirements (strict config validation)" read_when: - - You are building a OpenClaw plugin + - You are building an OpenClaw plugin - You need to ship a plugin config schema or debug plugin validation errors title: "Plugin Manifest" --- # Plugin manifest (openclaw.plugin.json) -Every plugin **must** ship a `openclaw.plugin.json` file in the **plugin root**. -OpenClaw uses this manifest to validate configuration **without executing plugin -code**. Missing or invalid manifests are treated as plugin errors and block -config validation. +This page is for the **native OpenClaw plugin manifest** only. + +For compatible bundle layouts, see [Plugin bundles](/plugins/bundles). + +Compatible bundle formats use different manifest files: + +- Codex bundle: `.codex-plugin/plugin.json` +- Claude bundle: `.claude-plugin/plugin.json` or the default Claude component + layout without a manifest +- Cursor bundle: `.cursor-plugin/plugin.json` + +OpenClaw auto-detects those bundle layouts too, but they are not validated +against the `openclaw.plugin.json` schema described here. + +For compatible bundles, OpenClaw currently reads bundle metadata plus declared +skill roots, Claude command roots, Claude bundle `settings.json` defaults, and +supported hook packs when the layout matches OpenClaw runtime expectations. + +Every native OpenClaw plugin **must** ship a `openclaw.plugin.json` file in the +**plugin root**. OpenClaw uses this manifest to validate configuration +**without executing plugin code**. Missing or invalid manifests are treated as +plugin errors and block config validation. See the full plugin system guide: [Plugins](/tools/plugin). +For the native capability model and current external-compatibility guidance: +[Capability model](/plugins/architecture#public-capability-model). ## Required fields @@ -36,14 +56,54 @@ Required keys: Optional keys: - `kind` (string): plugin kind (examples: `"memory"`, `"context-engine"`). -- `channels` (array): channel ids registered by this plugin (example: `["matrix"]`). -- `providers` (array): provider ids registered by this plugin. +- `channels` (array): channel ids registered by this plugin (channel capability; example: `["matrix"]`). +- `providers` (array): provider ids registered by this plugin (text inference capability). +- `providerAuthEnvVars` (object): auth env vars keyed by provider id. Use this + when OpenClaw should resolve provider credentials from env without loading + plugin runtime first. +- `providerAuthChoices` (array): cheap onboarding/auth-choice metadata keyed by + provider + auth method. Use this when OpenClaw should show a provider in + auth-choice pickers, preferred-provider resolution, and CLI help without + loading plugin runtime first. - `skills` (array): skill directories to load (relative to the plugin root). - `name` (string): display name for the plugin. - `description` (string): short plugin summary. - `uiHints` (object): config field labels/placeholders/sensitive flags for UI rendering. - `version` (string): plugin version (informational). +### `providerAuthChoices` shape + +Each entry can declare: + +- `provider`: provider id +- `method`: auth method id +- `choiceId`: stable onboarding/auth-choice id +- `choiceLabel` / `choiceHint`: picker label + short hint +- `groupId` / `groupLabel` / `groupHint`: grouped onboarding bucket metadata +- `optionKey` / `cliFlag` / `cliOption` / `cliDescription`: optional one-flag + CLI wiring for simple auth flows such as API keys + +Example: + +```json +{ + "providerAuthChoices": [ + { + "provider": "openrouter", + "method": "api-key", + "choiceId": "openrouter-api-key", + "choiceLabel": "OpenRouter API key", + "groupId": "openrouter", + "groupLabel": "OpenRouter", + "optionKey": "openrouterApiKey", + "cliFlag": "--openrouter-api-key", + "cliOption": "--openrouter-api-key ", + "cliDescription": "OpenRouter API key" + } + ] +} +``` + ## JSON Schema requirements - **Every plugin must ship a JSON Schema**, even if it accepts no config. @@ -61,11 +121,21 @@ Optional keys: - If plugin config exists but the plugin is **disabled**, the config is kept and a **warning** is surfaced in Doctor + logs. +See [Configuration reference](/configuration) for the full `plugins.*` schema. + ## Notes -- The manifest is **required for all plugins**, including local filesystem loads. +- The manifest is **required for native OpenClaw plugins**, including local filesystem loads. - Runtime still loads the plugin module separately; the manifest is only for discovery + validation. +- `providerAuthEnvVars` is the cheap metadata path for auth probes, env-marker + validation, and similar provider-auth surfaces that should not boot plugin + runtime just to inspect env names. +- `providerAuthChoices` is the cheap metadata path for auth-choice pickers, + `--auth-choice` resolution, preferred-provider mapping, and simple onboarding + CLI flag registration before provider runtime loads. For runtime wizard + metadata that requires provider code, see + [Provider runtime hooks](/plugins/architecture#provider-runtime-hooks). - Exclusive plugin kinds are selected through `plugins.slots.*`. - `kind: "memory"` is selected by `plugins.slots.memory`. - `kind: "context-engine"` is selected by `plugins.slots.contextEngine` diff --git a/docs/plugins/voice-call.md b/docs/plugins/voice-call.md index 17263ca0509..51c0f1efccd 100644 --- a/docs/plugins/voice-call.md +++ b/docs/plugins/voice-call.md @@ -204,7 +204,7 @@ Example with a stable public host: ## TTS for calls -Voice Call uses the core `messages.tts` configuration (OpenAI or ElevenLabs) for +Voice Call uses the core `messages.tts` configuration for streaming speech on calls. You can override it under the plugin config with the **same shape** — it deep‑merges with `messages.tts`. @@ -222,7 +222,7 @@ streaming speech on calls. You can override it under the plugin config with the Notes: -- **Edge TTS is ignored for voice calls** (telephony audio needs PCM; Edge output is unreliable). +- **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. ### More examples @@ -296,6 +296,12 @@ Inbound policy defaults to `disabled`. To enable inbound calls, set: } ``` +`inboundPolicy: "allowlist"` is a low-assurance caller-ID screen. The plugin +normalizes the provider-supplied `From` value and compares it to `allowFrom`. +Webhook verification authenticates provider delivery and payload integrity, but +it does not prove PSTN/VoIP caller-number ownership. Treat `allowFrom` as +caller-ID filtering, not strong caller identity. + Auto-responses use the agent system. Tune with: - `responseModel` @@ -306,14 +312,21 @@ Auto-responses use the agent system. Tune with: ```bash openclaw voicecall call --to "+15555550123" --message "Hello from OpenClaw" +openclaw voicecall start --to "+15555550123" # alias for call openclaw voicecall continue --call-id --message "Any questions?" openclaw voicecall speak --call-id --message "One moment" openclaw voicecall end --call-id openclaw voicecall status --call-id openclaw voicecall tail +openclaw voicecall latency # summarize turn latency from logs openclaw voicecall expose --mode funnel ``` +`latency` reads `calls.jsonl` from the default voice-call storage path. Use +`--file ` to point at a different log and `--last ` to limit analysis +to the last N records (default 200). Output includes p50/p90/p99 for turn +latency and listen-wait times. + ## Agent tool Tool name: `voice_call` diff --git a/docs/providers/anthropic.md b/docs/providers/anthropic.md index de974315273..d16d76f6315 100644 --- a/docs/providers/anthropic.md +++ b/docs/providers/anthropic.md @@ -44,6 +44,34 @@ openclaw onboard --anthropic-api-key "$ANTHROPIC_API_KEY" - [Adaptive thinking](https://platform.claude.com/docs/en/build-with-claude/adaptive-thinking) - [Extended thinking](https://platform.claude.com/docs/en/build-with-claude/extended-thinking) +## Fast mode (Anthropic API) + +OpenClaw's shared `/fast` toggle also supports direct Anthropic API-key traffic. + +- `/fast on` maps to `service_tier: "auto"` +- `/fast off` maps to `service_tier: "standard_only"` +- Config default: + +```json5 +{ + agents: { + defaults: { + models: { + "anthropic/claude-sonnet-4-5": { + params: { fastMode: true }, + }, + }, + }, + }, +} +``` + +Important limits: + +- This is **API-key only**. Anthropic setup-token / OAuth auth does not honor OpenClaw fast-mode tier injection. +- OpenClaw only injects Anthropic service tiers for direct `api.anthropic.com` requests. If you route `anthropic/*` through a proxy or gateway, `/fast` leaves `service_tier` untouched. +- Anthropic reports the effective tier on the response under `usage.service_tier`. On accounts without Priority Tier capacity, `service_tier: "auto"` may still resolve to `standard`. + ## Prompt caching (Anthropic API) OpenClaw supports Anthropic's prompt caching feature. This is **API-only**; subscription auth does not honor cache settings. @@ -185,7 +213,7 @@ openclaw models auth paste-token --provider anthropic ### CLI setup (setup-token) ```bash -# Paste a setup-token during onboarding +# Paste a setup-token during setup openclaw onboard --auth-choice setup-token ``` diff --git a/docs/providers/bedrock.md b/docs/providers/bedrock.md index e6e3f807ee9..5fbed2b261f 100644 --- a/docs/providers/bedrock.md +++ b/docs/providers/bedrock.md @@ -12,7 +12,7 @@ OpenClaw can use **Amazon Bedrock** models via pi‑ai’s **Bedrock Converse** streaming provider. Bedrock auth uses the **AWS SDK default credential chain**, not an API key. -## What pi‑ai supports +## What pi-ai supports - Provider: `amazon-bedrock` - API: `bedrock-converse-stream` diff --git a/docs/providers/glm.md b/docs/providers/glm.md index f65ea81f9da..64fe39a42df 100644 --- a/docs/providers/glm.md +++ b/docs/providers/glm.md @@ -14,7 +14,17 @@ models are accessed via the `zai` provider and model IDs like `zai/glm-5`. ## CLI setup ```bash -openclaw onboard --auth-choice zai-api-key +# Coding Plan Global, recommended for Coding Plan users +openclaw onboard --auth-choice zai-coding-global + +# Coding Plan CN (China region), recommended for Coding Plan users +openclaw onboard --auth-choice zai-coding-cn + +# General API +openclaw onboard --auth-choice zai-global + +# General API CN (China region) +openclaw onboard --auth-choice zai-cn ``` ## Config snippet diff --git a/docs/providers/google.md b/docs/providers/google.md new file mode 100644 index 00000000000..569735db730 --- /dev/null +++ b/docs/providers/google.md @@ -0,0 +1,78 @@ +--- +title: "Google (Gemini)" +summary: "Google Gemini setup (API key + OAuth, image generation, media understanding, web search)" +read_when: + - You want to use Google Gemini models with OpenClaw + - You need the API key or OAuth auth flow +--- + +# Google (Gemini) + +The Google plugin provides access to Gemini models through Google AI Studio, plus +image generation, media understanding (image/audio/video), and web search via +Gemini Grounding. + +- Provider: `google` +- Auth: `GEMINI_API_KEY` or `GOOGLE_API_KEY` +- API: Google Gemini API +- Alternative provider: `google-gemini-cli` (OAuth) + +## Quick start + +1. Set the API key: + +```bash +openclaw onboard --auth-choice google-api-key +``` + +2. Set a default model: + +```json5 +{ + agents: { + defaults: { + model: { primary: "google/gemini-3.1-pro-preview" }, + }, + }, +} +``` + +## Non-interactive example + +```bash +openclaw onboard --non-interactive \ + --mode local \ + --auth-choice google-api-key \ + --gemini-api-key "$GEMINI_API_KEY" +``` + +## OAuth (Gemini CLI) + +An alternative provider `google-gemini-cli` uses PKCE OAuth instead of an API +key. This is an unofficial integration; some users report account +restrictions. Use at your own risk. + +Environment variables: + +- `OPENCLAW_GEMINI_OAUTH_CLIENT_ID` +- `OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET` + +(Or the `GEMINI_CLI_*` variants.) + +## Capabilities + +| Capability | Supported | +| ---------------------- | ----------------- | +| Chat completions | Yes | +| Image generation | Yes | +| Image understanding | Yes | +| Audio transcription | Yes | +| Video understanding | Yes | +| Web search (Grounding) | Yes | +| Thinking/reasoning | Yes (Gemini 3.1+) | + +## Environment note + +If the Gateway runs as a daemon (launchd/systemd), make sure `GEMINI_API_KEY` +is available to that process (for example, in `~/.openclaw/.env` or via +`env.shellEnv`). diff --git a/docs/providers/huggingface.md b/docs/providers/huggingface.md index d9746d5c166..7b33955f524 100644 --- a/docs/providers/huggingface.md +++ b/docs/providers/huggingface.md @@ -64,7 +64,7 @@ GET https://router.huggingface.co/v1/models (Optional: send `Authorization: Bearer $HUGGINGFACE_HUB_TOKEN` or `$HF_TOKEN` for the full list; some endpoints return a subset without auth.) The response is OpenAI-style `{ "object": "list", "data": [ { "id": "Qwen/Qwen3-8B", "owned_by": "Qwen", ... }, ... ] }`. -When you configure a Hugging Face API key (via onboarding, `HUGGINGFACE_HUB_TOKEN`, or `HF_TOKEN`), OpenClaw uses this GET to discover available chat-completion models. During **interactive onboarding**, after you enter your token you see a **Default Hugging Face model** dropdown populated from that list (or the built-in catalog if the request fails). At runtime (e.g. Gateway startup), when a key is present, OpenClaw again calls **GET** `https://router.huggingface.co/v1/models` to refresh the catalog. The list is merged with a built-in catalog (for metadata like context window and cost). If the request fails or no key is set, only the built-in catalog is used. +When you configure a Hugging Face API key (via onboarding, `HUGGINGFACE_HUB_TOKEN`, or `HF_TOKEN`), OpenClaw uses this GET to discover available chat-completion models. During **interactive setup**, after you enter your token you see a **Default Hugging Face model** dropdown populated from that list (or the built-in catalog if the request fails). At runtime (e.g. Gateway startup), when a key is present, OpenClaw again calls **GET** `https://router.huggingface.co/v1/models` to refresh the catalog. The list is merged with a built-in catalog (for metadata like context window and cost). If the request fails or no key is set, only the built-in catalog is used. ## Model names and editable options diff --git a/docs/providers/index.md b/docs/providers/index.md index 50e45c6559b..be2b5154f61 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -3,7 +3,7 @@ summary: "Model providers (LLMs) supported by OpenClaw" read_when: - You want to choose a model provider - You need a quick overview of supported LLM backends -title: "Model Providers" +title: "Provider Directory" --- # Model Providers @@ -30,23 +30,29 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi - [Anthropic (API + Claude Code CLI)](/providers/anthropic) - [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway) - [GLM models](/providers/glm) +- [Google (Gemini)](/providers/google) - [Hugging Face (Inference)](/providers/huggingface) - [Kilocode](/providers/kilocode) - [LiteLLM (unified gateway)](/providers/litellm) - [MiniMax](/providers/minimax) - [Mistral](/providers/mistral) +- [Model Studio (Alibaba Cloud)](/providers/modelstudio) - [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot) - [NVIDIA](/providers/nvidia) -- [Ollama (local models)](/providers/ollama) +- [Ollama (cloud + local models)](/providers/ollama) - [OpenAI (API + Codex)](/providers/openai) - [OpenCode (Zen + Go)](/providers/opencode) - [OpenRouter](/providers/openrouter) +- [Perplexity (web search)](/providers/perplexity-provider) - [Qianfan](/providers/qianfan) - [Qwen (OAuth)](/providers/qwen) +- [SGLang (local models)](/providers/sglang) - [Together AI](/providers/together) - [Vercel AI Gateway](/providers/vercel-ai-gateway) - [Venice (Venice AI, privacy-focused)](/providers/venice) - [vLLM (local models)](/providers/vllm) +- [Volcengine (Doubao)](/providers/volcengine) +- [xAI](/providers/xai) - [Xiaomi](/providers/xiaomi) - [Z.AI](/providers/zai) diff --git a/docs/providers/kilocode.md b/docs/providers/kilocode.md index 15f8e4c2b7c..a1952c5425b 100644 --- a/docs/providers/kilocode.md +++ b/docs/providers/kilocode.md @@ -1,4 +1,5 @@ --- +title: "Kilo Gateway" summary: "Use Kilo Gateway's unified API to access many models in OpenClaw" read_when: - You want a single API key for many LLMs diff --git a/docs/providers/litellm.md b/docs/providers/litellm.md index 51ad0d599f8..10d28c92e28 100644 --- a/docs/providers/litellm.md +++ b/docs/providers/litellm.md @@ -1,4 +1,5 @@ --- +title: "LiteLLM" summary: "Run OpenClaw through LiteLLM Proxy for unified model access and cost tracking" read_when: - You want to route OpenClaw through a LiteLLM proxy diff --git a/docs/providers/minimax.md b/docs/providers/minimax.md index f060c637de8..cc678349423 100644 --- a/docs/providers/minimax.md +++ b/docs/providers/minimax.md @@ -35,14 +35,14 @@ MiniMax highlights these improvements in M2.5: ## Choose a setup -### MiniMax OAuth (Coding Plan) — recommended +### MiniMax OAuth (Coding Plan) - recommended **Best for:** quick setup with MiniMax Coding Plan via OAuth, no API key required. Enable the bundled OAuth plugin and authenticate: ```bash -openclaw plugins enable minimax-portal-auth # skip if already loaded. +openclaw plugins enable minimax # skip if already loaded. openclaw gateway restart # restart if gateway is already running openclaw onboard --auth-choice minimax-portal ``` @@ -52,7 +52,7 @@ You will be prompted to select an endpoint: - **Global** - International users (`api.minimax.io`) - **CN** - Users in China (`api.minimaxi.com`) -See [MiniMax OAuth plugin README](https://github.com/openclaw/openclaw/tree/main/extensions/minimax-portal-auth) for details. +See [MiniMax plugin README](https://github.com/openclaw/openclaw/tree/main/extensions/minimax) for details. ### MiniMax M2.5 (API key) @@ -151,7 +151,7 @@ Configure manually via `openclaw.json`: { id: "minimax-m2.5-gs32", name: "MiniMax M2.5 GS32", - reasoning: false, + reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 196608, @@ -194,7 +194,7 @@ Use the interactive config wizard to set MiniMax without editing JSON: ## Troubleshooting -### “Unknown model: minimax/MiniMax-M2.5” +### "Unknown model: minimax/MiniMax-M2.5" This usually means the **MiniMax provider isn’t configured** (no provider entry and no MiniMax auth profile/env key found). A fix for this detection is in diff --git a/docs/providers/models.md b/docs/providers/models.md index a117d286051..7c8c8c758f6 100644 --- a/docs/providers/models.md +++ b/docs/providers/models.md @@ -39,6 +39,7 @@ model as `provider/model`. - [Venice (Venice AI)](/providers/venice) - [Amazon Bedrock](/providers/bedrock) - [Qianfan](/providers/qianfan) +- [xAI](/providers/xai) For the full provider catalog (xAI, Groq, Mistral, etc.) and advanced configuration, see [Model providers](/concepts/model-providers). diff --git a/docs/providers/modelstudio.md b/docs/providers/modelstudio.md new file mode 100644 index 00000000000..65059322de6 --- /dev/null +++ b/docs/providers/modelstudio.md @@ -0,0 +1,66 @@ +--- +title: "Model Studio" +summary: "Alibaba Cloud Model Studio setup (Coding Plan, dual region endpoints)" +read_when: + - You want to use Alibaba Cloud Model Studio with OpenClaw + - You need the API key env var for Model Studio +--- + +# Model Studio (Alibaba Cloud) + +The Model Studio provider gives access to Alibaba Cloud Coding Plan models, +including Qwen and third-party models hosted on the platform. + +- Provider: `modelstudio` +- Auth: `MODELSTUDIO_API_KEY` +- API: OpenAI-compatible + +## Quick start + +1. Set the API key: + +```bash +openclaw onboard --auth-choice modelstudio-api-key +``` + +2. Set a default model: + +```json5 +{ + agents: { + defaults: { + model: { primary: "modelstudio/qwen3.5-plus" }, + }, + }, +} +``` + +## Region endpoints + +Model Studio has two endpoints based on region: + +| Region | Endpoint | +| ---------- | ------------------------------------ | +| China (CN) | `coding.dashscope.aliyuncs.com` | +| Global | `coding-intl.dashscope.aliyuncs.com` | + +The provider auto-selects based on the auth choice (`modelstudio-api-key` for +global, `modelstudio-api-key-cn` for China). You can override with a custom +`baseUrl` in config. + +## Available models + +- **qwen3.5-plus** (default) - Qwen 3.5 Plus +- **qwen3-max** - Qwen 3 Max +- **qwen3-coder** series - Qwen coding models +- **GLM-5**, **GLM-4.7** - GLM models via Alibaba +- **Kimi K2.5** - Moonshot AI via Alibaba +- **MiniMax-M2.5** - MiniMax via Alibaba + +Most models support image input. Context windows range from 200K to 1M tokens. + +## Environment note + +If the Gateway runs as a daemon (launchd/systemd), make sure +`MODELSTUDIO_API_KEY` is available to that process (for example, in +`~/.openclaw/.env` or via `env.shellEnv`). diff --git a/docs/providers/moonshot.md b/docs/providers/moonshot.md index 3e8217bbe5b..daf9c881de5 100644 --- a/docs/providers/moonshot.md +++ b/docs/providers/moonshot.md @@ -15,20 +15,15 @@ Kimi Coding with `kimi-coding/k2p5`. Current Kimi K2 model IDs: - - -{/_ moonshot-kimi-k2-ids:start _/ && null} - - +[//]: # "moonshot-kimi-k2-ids:start" - `kimi-k2.5` - `kimi-k2-0905-preview` - `kimi-k2-turbo-preview` - `kimi-k2-thinking` - `kimi-k2-thinking-turbo` - - {/_ moonshot-kimi-k2-ids:end _/ && null} - + +[//]: # "moonshot-kimi-k2-ids:end" ```bash openclaw onboard --auth-choice moonshot-api-key diff --git a/docs/providers/ollama.md b/docs/providers/ollama.md index b82f6411b68..c3ea5aa7d3c 100644 --- a/docs/providers/ollama.md +++ b/docs/providers/ollama.md @@ -1,14 +1,14 @@ --- -summary: "Run OpenClaw with Ollama (local LLM runtime)" +summary: "Run OpenClaw with Ollama (cloud and local models)" read_when: - - You want to run OpenClaw with local models via Ollama + - You want to run OpenClaw with cloud or local models via Ollama - You need Ollama setup and configuration guidance title: "Ollama" --- # Ollama -Ollama is a local LLM runtime that makes it easy to run open-source models on your machine. OpenClaw integrates with Ollama's native API (`/api/chat`), supporting streaming and tool calling, and can **auto-discover tool-capable models** when you opt in with `OLLAMA_API_KEY` (or an auth profile) and do not define an explicit `models.providers.ollama` entry. +Ollama is a local LLM runtime that makes it easy to run open-source models on your machine. OpenClaw integrates with Ollama's native API (`/api/chat`), supports streaming and tool calling, and can auto-discover local Ollama models when you opt in with `OLLAMA_API_KEY` (or an auth profile) and do not define an explicit `models.providers.ollama` entry. **Remote Ollama users**: Do not use the `/v1` OpenAI-compatible URL (`http://host:11434/v1`) with OpenClaw. This breaks tool calling and models may output raw tool JSON as plain text. Use the native Ollama API URL instead: `baseUrl: "http://host:11434"` (no `/v1`). @@ -16,21 +16,76 @@ Ollama is a local LLM runtime that makes it easy to run open-source models on yo ## Quick start -1. Install Ollama: [https://ollama.ai](https://ollama.ai) +### Onboarding (recommended) -2. Pull a model: +The fastest way to set up Ollama is through onboarding: ```bash +openclaw onboard +``` + +Select **Ollama** from the provider list. Onboarding will: + +1. Ask for the Ollama base URL where your instance can be reached (default `http://127.0.0.1:11434`). +2. Let you choose **Cloud + Local** (cloud models and local models) or **Local** (local models only). +3. Open a browser sign-in flow if you choose **Cloud + Local** and are not signed in to ollama.com. +4. Discover available models and suggest defaults. +5. Auto-pull the selected model if it is not available locally. + +Non-interactive mode is also supported: + +```bash +openclaw onboard --non-interactive \ + --auth-choice ollama \ + --accept-risk +``` + +Optionally specify a custom base URL or model: + +```bash +openclaw onboard --non-interactive \ + --auth-choice ollama \ + --custom-base-url "http://ollama-host:11434" \ + --custom-model-id "qwen3.5:27b" \ + --accept-risk +``` + +### Manual setup + +1. Install Ollama: [https://ollama.com/download](https://ollama.com/download) + +2. Pull a local model if you want local inference: + +```bash +ollama pull glm-4.7-flash +# or ollama pull gpt-oss:20b # or ollama pull llama3.3 -# or -ollama pull qwen2.5-coder:32b -# or -ollama pull deepseek-r1:32b ``` -3. Enable Ollama for OpenClaw (any value works; Ollama doesn't require a real key): +3. If you want cloud models too, sign in: + +```bash +ollama signin +``` + +4. Run onboarding and choose `Ollama`: + +```bash +openclaw onboard +``` + +- `Local`: local models only +- `Cloud + Local`: local models plus cloud models +- Cloud models such as `kimi-k2.5:cloud`, `minimax-m2.5:cloud`, and `glm-5:cloud` do **not** require a local `ollama pull` + +OpenClaw currently suggests: + +- local default: `glm-4.7-flash` +- cloud defaults: `kimi-k2.5:cloud`, `minimax-m2.5:cloud`, `glm-5:cloud` + +5. If you prefer manual setup, enable Ollama for OpenClaw directly (any value works; Ollama doesn't require a real key): ```bash # Set environment variable @@ -40,13 +95,20 @@ export OLLAMA_API_KEY="ollama-local" openclaw config set models.providers.ollama.apiKey "ollama-local" ``` -4. Use Ollama models: +6. Inspect or switch models: + +```bash +openclaw models list +openclaw models set ollama/glm-4.7-flash +``` + +7. Or set the default in config: ```json5 { agents: { defaults: { - model: { primary: "ollama/gpt-oss:20b" }, + model: { primary: "ollama/glm-4.7-flash" }, }, }, } @@ -56,14 +118,13 @@ openclaw config set models.providers.ollama.apiKey "ollama-local" When you set `OLLAMA_API_KEY` (or an auth profile) and **do not** define `models.providers.ollama`, OpenClaw discovers models from the local Ollama instance at `http://127.0.0.1:11434`: -- Queries `/api/tags` and `/api/show` -- Keeps only models that report `tools` capability -- Marks `reasoning` when the model reports `thinking` -- Reads `contextWindow` from `model_info[".context_length"]` when available -- Sets `maxTokens` to 10× the context window +- Queries `/api/tags` +- Uses best-effort `/api/show` lookups to read `contextWindow` when available +- Marks `reasoning` with a model-name heuristic (`r1`, `reasoning`, `think`) +- Sets `maxTokens` to the default Ollama max-token cap used by OpenClaw - Sets all costs to `0` -This avoids manual model entries while keeping the catalog aligned with Ollama's capabilities. +This avoids manual model entries while keeping the catalog aligned with the local Ollama instance. To see what models are available: @@ -98,7 +159,7 @@ Use explicit config when: - Ollama runs on another host/port. - You want to force specific context windows or model lists. -- You want to include models that do not report tool support. +- You want fully manual model definitions. ```json5 { @@ -166,11 +227,19 @@ Once configured, all your Ollama models are available: } ``` +## Cloud models + +Cloud models let you run cloud-hosted models (for example `kimi-k2.5:cloud`, `minimax-m2.5:cloud`, `glm-5:cloud`) alongside your local models. + +To use cloud models, select **Cloud + Local** mode during setup. The wizard checks whether you are signed in and opens a browser sign-in flow when needed. If authentication cannot be verified, the wizard falls back to local model defaults. + +You can also sign in directly at [ollama.com/signin](https://ollama.com/signin). + ## Advanced ### Reasoning models -OpenClaw marks models as reasoning-capable when Ollama reports `thinking` in `/api/show`: +OpenClaw treats models with names such as `deepseek-r1`, `reasoning`, or `think` as reasoning-capable by default: ```bash ollama pull deepseek-r1:32b @@ -230,7 +299,7 @@ When `api: "openai-completions"` is used with Ollama, OpenClaw injects `options. ### Context windows -For auto-discovered models, OpenClaw uses the context window reported by Ollama when available, otherwise it defaults to `8192`. You can override `contextWindow` and `maxTokens` in explicit provider config. +For auto-discovered models, OpenClaw uses the context window reported by Ollama when available, otherwise it falls back to the default Ollama context window used by OpenClaw. You can override `contextWindow` and `maxTokens` in explicit provider config. ## Troubleshooting @@ -250,16 +319,17 @@ curl http://localhost:11434/api/tags ### No models available -OpenClaw only auto-discovers models that report tool support. If your model isn't listed, either: +If your model is not listed, either: -- Pull a tool-capable model, or +- Pull the model locally, or - Define the model explicitly in `models.providers.ollama`. To add models: ```bash ollama list # See what's installed -ollama pull gpt-oss:20b # Pull a tool-capable model +ollama pull glm-4.7-flash +ollama pull gpt-oss:20b ollama pull llama3.3 # Or another model ``` diff --git a/docs/providers/openai.md b/docs/providers/openai.md index 4683f061546..a6a60f8f2ea 100644 --- a/docs/providers/openai.md +++ b/docs/providers/openai.md @@ -36,6 +36,12 @@ openclaw onboard --openai-api-key "$OPENAI_API_KEY" OpenAI's current API model docs list `gpt-5.4` and `gpt-5.4-pro` for direct OpenAI API usage. OpenClaw forwards both through the `openai/*` Responses path. +OpenClaw intentionally suppresses the stale `openai/gpt-5.3-codex-spark` row, +because direct OpenAI API calls reject it in live traffic. + +OpenClaw does **not** expose `openai/gpt-5.3-codex-spark` on the direct OpenAI +API path. `pi-ai` still ships a built-in row for that model, but live OpenAI API +requests currently reject it. Spark is treated as Codex-only in OpenClaw. ## Option B: OpenAI Code (Codex) subscription @@ -63,6 +69,18 @@ openclaw models auth login --provider openai-codex OpenAI's current Codex docs list `gpt-5.4` as the current Codex model. OpenClaw maps that to `openai-codex/gpt-5.4` for ChatGPT/Codex OAuth usage. +If your Codex account is entitled to Codex Spark, OpenClaw also supports: + +- `openai-codex/gpt-5.3-codex-spark` + +OpenClaw treats Codex Spark as Codex-only. It does not expose a direct +`openai/gpt-5.3-codex-spark` API-key path. + +OpenClaw also preserves `openai-codex/gpt-5.3-codex-spark` when `pi-ai` +discovers it. Treat it as entitlement-dependent and experimental: Codex Spark is +separate from GPT-5.4 `/fast`, and availability depends on the signed-in Codex / +ChatGPT account. + ### Transport default OpenClaw uses `pi-ai` for model streaming. For both `openai/*` and @@ -165,6 +183,46 @@ pass that field through on direct `openai/*` Responses requests. Supported values are `auto`, `default`, `flex`, and `priority`. +### OpenAI fast mode + +OpenClaw exposes a shared fast-mode toggle for both `openai/*` and +`openai-codex/*` sessions: + +- Chat/UI: `/fast status|on|off` +- Config: `agents.defaults.models["/"].params.fastMode` + +When fast mode is enabled, OpenClaw applies a low-latency OpenAI profile: + +- `reasoning.effort = "low"` when the payload does not already specify reasoning +- `text.verbosity = "low"` when the payload does not already specify verbosity +- `service_tier = "priority"` for direct `openai/*` Responses calls to `api.openai.com` + +Example: + +```json5 +{ + agents: { + defaults: { + models: { + "openai/gpt-5.4": { + params: { + fastMode: true, + }, + }, + "openai-codex/gpt-5.4": { + params: { + fastMode: true, + }, + }, + }, + }, + }, +} +``` + +Session overrides win over config. Clearing the session override in the Sessions UI +returns the session to the configured default. + ### OpenAI Responses server-side compaction For direct OpenAI Responses models (`openai/*` using `api: "openai-responses"` with diff --git a/docs/providers/opencode.md b/docs/providers/opencode.md index bf8d54afc9e..da44e5154c0 100644 --- a/docs/providers/opencode.md +++ b/docs/providers/opencode.md @@ -59,6 +59,6 @@ openclaw onboard --opencode-go-api-key "$OPENCODE_API_KEY" ## Notes - `OPENCODE_ZEN_API_KEY` is also supported. -- Entering one OpenCode key during onboarding stores credentials for both runtime providers. +- Entering one OpenCode key during setup stores credentials for both runtime providers. - You sign in to OpenCode, add billing details, and copy your API key. - Billing and catalog availability are managed from the OpenCode dashboard. diff --git a/docs/providers/perplexity-provider.md b/docs/providers/perplexity-provider.md new file mode 100644 index 00000000000..c0945627e39 --- /dev/null +++ b/docs/providers/perplexity-provider.md @@ -0,0 +1,56 @@ +--- +title: "Perplexity (Provider)" +summary: "Perplexity web search provider setup (API key, search modes, filtering)" +read_when: + - You want to configure Perplexity as a web search provider + - You need the Perplexity API key or OpenRouter proxy setup +--- + +# Perplexity (Web Search Provider) + +The Perplexity plugin provides web search capabilities through the Perplexity +Search API or Perplexity Sonar via OpenRouter. + + +This page covers the Perplexity **provider** setup. For the Perplexity +**tool** (how the agent uses it), see [Perplexity tool](/perplexity). + + +- Type: web search provider (not a model provider) +- Auth: `PERPLEXITY_API_KEY` (direct) or `OPENROUTER_API_KEY` (via OpenRouter) +- Config path: `tools.web.search.perplexity.apiKey` + +## Quick start + +1. Set the API key: + +```bash +openclaw config set tools.web.search.perplexity.apiKey "pplx-xxxxxxxxxxxx" +``` + +2. The agent will automatically use Perplexity for web searches when configured. + +## Search modes + +The plugin auto-selects the transport based on API key prefix: + +| Key prefix | Transport | Features | +| ---------- | ---------------------------- | ------------------------------------------------ | +| `pplx-` | Native Perplexity Search API | Structured results, domain/language/date filters | +| `sk-or-` | OpenRouter (Sonar) | AI-synthesized answers with citations | + +## Native API filtering + +When using the native Perplexity API (`pplx-` key), searches support: + +- **Country**: 2-letter country code +- **Language**: ISO 639-1 language code +- **Date range**: day, week, month, year +- **Domain filters**: allowlist/denylist (max 20 domains) +- **Content budget**: `max_tokens`, `max_tokens_per_page` + +## Environment note + +If the Gateway runs as a daemon (launchd/systemd), make sure +`PERPLEXITY_API_KEY` is available to that process (for example, in +`~/.openclaw/.env` or via `env.shellEnv`). diff --git a/docs/providers/sglang.md b/docs/providers/sglang.md new file mode 100644 index 00000000000..ce66950c0c3 --- /dev/null +++ b/docs/providers/sglang.md @@ -0,0 +1,104 @@ +--- +summary: "Run OpenClaw with SGLang (OpenAI-compatible self-hosted server)" +read_when: + - You want to run OpenClaw against a local SGLang server + - You want OpenAI-compatible /v1 endpoints with your own models +title: "SGLang" +--- + +# SGLang + +SGLang can serve open-source models via an **OpenAI-compatible** HTTP API. +OpenClaw can connect to SGLang using the `openai-completions` API. + +OpenClaw can also **auto-discover** available models from SGLang when you opt +in with `SGLANG_API_KEY` (any value works if your server does not enforce auth) +and you do not define an explicit `models.providers.sglang` entry. + +## Quick start + +1. Start SGLang with an OpenAI-compatible server. + +Your base URL should expose `/v1` endpoints (for example `/v1/models`, +`/v1/chat/completions`). SGLang commonly runs on: + +- `http://127.0.0.1:30000/v1` + +2. Opt in (any value works if no auth is configured): + +```bash +export SGLANG_API_KEY="sglang-local" +``` + +3. Run onboarding and choose `SGLang`, or set a model directly: + +```bash +openclaw onboard +``` + +```json5 +{ + agents: { + defaults: { + model: { primary: "sglang/your-model-id" }, + }, + }, +} +``` + +## Model discovery (implicit provider) + +When `SGLANG_API_KEY` is set (or an auth profile exists) and you **do not** +define `models.providers.sglang`, OpenClaw will query: + +- `GET http://127.0.0.1:30000/v1/models` + +and convert the returned IDs into model entries. + +If you set `models.providers.sglang` explicitly, auto-discovery is skipped and +you must define models manually. + +## Explicit configuration (manual models) + +Use explicit config when: + +- SGLang runs on a different host/port. +- You want to pin `contextWindow`/`maxTokens` values. +- Your server requires a real API key (or you want to control headers). + +```json5 +{ + models: { + providers: { + sglang: { + baseUrl: "http://127.0.0.1:30000/v1", + apiKey: "${SGLANG_API_KEY}", + api: "openai-completions", + models: [ + { + id: "your-model-id", + name: "Local SGLang Model", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 8192, + }, + ], + }, + }, + }, +} +``` + +## Troubleshooting + +- Check the server is reachable: + +```bash +curl http://127.0.0.1:30000/v1/models +``` + +- If requests fail with auth errors, set a real `SGLANG_API_KEY` that matches + your server configuration, or configure the provider explicitly under + `models.providers.sglang`. diff --git a/docs/providers/together.md b/docs/providers/together.md index 62bab43a204..c416755e9c1 100644 --- a/docs/providers/together.md +++ b/docs/providers/together.md @@ -1,4 +1,5 @@ --- +title: "Together AI" summary: "Together AI setup (auth + model selection)" read_when: - You want to use Together AI with OpenClaw diff --git a/docs/providers/venice.md b/docs/providers/venice.md index 520cf22d82b..6f3c4b9313d 100644 --- a/docs/providers/venice.md +++ b/docs/providers/venice.md @@ -124,7 +124,7 @@ openclaw models list | grep venice ## Available Models (41 Total) -### Private Models (26) — Fully Private, No Logging +### Private Models (26) - Fully Private, No Logging | Model ID | Name | Context | Features | | -------------------------------------- | ----------------------------------- | ------- | -------------------------- | @@ -155,7 +155,7 @@ openclaw models list | grep venice | `minimax-m21` | MiniMax M2.1 | 198k | Reasoning | | `minimax-m25` | MiniMax M2.5 | 198k | Reasoning | -### Anonymized Models (15) — Via Venice Proxy +### Anonymized Models (15) - Via Venice Proxy | Model ID | Name | Context | Features | | ------------------------------- | ------------------------------ | ------- | ------------------------- | diff --git a/docs/providers/volcengine.md b/docs/providers/volcengine.md new file mode 100644 index 00000000000..75ad2577dec --- /dev/null +++ b/docs/providers/volcengine.md @@ -0,0 +1,74 @@ +--- +title: "Volcengine (Doubao)" +summary: "Volcano Engine setup (Doubao models, general + coding endpoints)" +read_when: + - You want to use Volcano Engine or Doubao models with OpenClaw + - You need the Volcengine API key setup +--- + +# Volcengine (Doubao) + +The Volcengine provider gives access to Doubao models and third-party models +hosted on Volcano Engine, with separate endpoints for general and coding +workloads. + +- Providers: `volcengine` (general) + `volcengine-plan` (coding) +- Auth: `VOLCANO_ENGINE_API_KEY` +- API: OpenAI-compatible + +## Quick start + +1. Set the API key: + +```bash +openclaw onboard --auth-choice volcengine-api-key +``` + +2. Set a default model: + +```json5 +{ + agents: { + defaults: { + model: { primary: "volcengine-plan/ark-code-latest" }, + }, + }, +} +``` + +## Non-interactive example + +```bash +openclaw onboard --non-interactive \ + --mode local \ + --auth-choice volcengine-api-key \ + --volcengine-api-key "$VOLCANO_ENGINE_API_KEY" +``` + +## Providers and endpoints + +| Provider | Endpoint | Use case | +| ----------------- | ----------------------------------------- | -------------- | +| `volcengine` | `ark.cn-beijing.volces.com/api/v3` | General models | +| `volcengine-plan` | `ark.cn-beijing.volces.com/api/coding/v3` | Coding models | + +Both providers are configured from a single API key. Setup registers both +automatically. + +## Available models + +- **doubao-seed-1-8** - Doubao Seed 1.8 (general, default) +- **doubao-seed-code-preview** - Doubao coding model +- **ark-code-latest** - Coding plan default +- **Kimi K2.5** - Moonshot AI via Volcano Engine +- **GLM-4.7** - GLM via Volcano Engine +- **DeepSeek V3.2** - DeepSeek via Volcano Engine + +Most models support text + image input. Context windows range from 128K to 256K +tokens. + +## Environment note + +If the Gateway runs as a daemon (launchd/systemd), make sure +`VOLCANO_ENGINE_API_KEY` is available to that process (for example, in +`~/.openclaw/.env` or via `env.shellEnv`). diff --git a/docs/providers/xai.md b/docs/providers/xai.md new file mode 100644 index 00000000000..ec491735e50 --- /dev/null +++ b/docs/providers/xai.md @@ -0,0 +1,61 @@ +--- +summary: "Use xAI Grok models in OpenClaw" +read_when: + - You want to use Grok models in OpenClaw + - You are configuring xAI auth or model ids +title: "xAI" +--- + +# xAI + +OpenClaw ships a bundled `xai` provider plugin for Grok models. + +## Setup + +1. Create an API key in the xAI console. +2. Set `XAI_API_KEY`, or run: + +```bash +openclaw onboard --auth-choice xai-api-key +``` + +3. Pick a model such as: + +```json5 +{ + agents: { defaults: { model: { primary: "xai/grok-4" } } }, +} +``` + +## Current bundled model catalog + +OpenClaw now includes these xAI model families out of the box: + +- `grok-4`, `grok-4-0709` +- `grok-4-fast-reasoning`, `grok-4-fast-non-reasoning` +- `grok-4-1-fast-reasoning`, `grok-4-1-fast-non-reasoning` +- `grok-4.20-experimental-beta-0304-reasoning` +- `grok-4.20-experimental-beta-0304-non-reasoning` +- `grok-code-fast-1` + +The plugin also forward-resolves newer `grok-4*` and `grok-code-fast*` ids when +they follow the same API shape. + +## Web search + +The bundled `grok` web-search provider uses `XAI_API_KEY` too: + +```bash +openclaw config set tools.web.search.provider grok +``` + +## Known limits + +- Auth is API-key only today. There is no xAI OAuth/device-code flow in OpenClaw yet. +- `grok-4.20-multi-agent-experimental-beta-0304` is not supported on the normal xAI provider path because it requires a different upstream API surface than the standard OpenClaw xAI transport. +- Native xAI server-side tools such as `x_search` and `code_execution` are not yet first-class model-provider features in the bundled plugin. + +## Notes + +- OpenClaw applies xAI-specific tool-schema and tool-call compatibility fixes automatically on the shared runner path. +- For the broader provider overview, see [Model providers](/providers/index). diff --git a/docs/providers/zai.md b/docs/providers/zai.md index 93313acba3f..6f3aea27020 100644 --- a/docs/providers/zai.md +++ b/docs/providers/zai.md @@ -15,9 +15,17 @@ with a Z.AI API key. ## CLI setup ```bash -openclaw onboard --auth-choice zai-api-key -# or non-interactive -openclaw onboard --zai-api-key "$ZAI_API_KEY" +# Coding Plan Global, recommended for Coding Plan users +openclaw onboard --auth-choice zai-coding-global + +# Coding Plan CN (China region), recommended for Coding Plan users +openclaw onboard --auth-choice zai-coding-cn + +# General API +openclaw onboard --auth-choice zai-global + +# General API CN (China region) +openclaw onboard --auth-choice zai-cn ``` ## Config snippet diff --git a/docs/refactor/clawnet.md b/docs/refactor/clawnet.md deleted file mode 100644 index f24cfdc2c57..00000000000 --- a/docs/refactor/clawnet.md +++ /dev/null @@ -1,417 +0,0 @@ ---- -summary: "Clawnet refactor: unify network protocol, roles, auth, approvals, identity" -read_when: - - Planning a unified network protocol for nodes + operator clients - - Reworking approvals, pairing, TLS, and presence across devices -title: "Clawnet Refactor" ---- - -# Clawnet refactor (protocol + auth unification) - -## Hi - -Hi Peter — great direction; this unlocks simpler UX + stronger security. - -## Purpose - -Single, rigorous document for: - -- Current state: protocols, flows, trust boundaries. -- Pain points: approvals, multi‑hop routing, UI duplication. -- Proposed new state: one protocol, scoped roles, unified auth/pairing, TLS pinning. -- Identity model: stable IDs + cute slugs. -- Migration plan, risks, open questions. - -## Goals (from discussion) - -- One protocol for all clients (mac app, CLI, iOS, Android, headless node). -- Every network participant authenticated + paired. -- Role clarity: nodes vs operators. -- Central approvals routed to where the user is. -- TLS encryption + optional pinning for all remote traffic. -- Minimal code duplication. -- Single machine should appear once (no UI/node duplicate entry). - -## Non‑goals (explicit) - -- Remove capability separation (still need least‑privilege). -- Expose full gateway control plane without scope checks. -- Make auth depend on human labels (slugs remain non‑security). - ---- - -# Current state (as‑is) - -## Two protocols - -### 1) Gateway WebSocket (control plane) - -- Full API surface: config, channels, models, sessions, agent runs, logs, nodes, etc. -- Default bind: loopback. Remote access via SSH/Tailscale. -- Auth: token/password via `connect`. -- No TLS pinning (relies on loopback/tunnel). -- Code: - - `src/gateway/server/ws-connection/message-handler.ts` - - `src/gateway/client.ts` - - `docs/gateway/protocol.md` - -### 2) Bridge (node transport) - -- Narrow allowlist surface, node identity + pairing. -- JSONL over TCP; optional TLS + cert fingerprint pinning. -- TLS advertises fingerprint in discovery TXT. -- Code: - - `src/infra/bridge/server/connection.ts` - - `src/gateway/server-bridge.ts` - - `src/node-host/bridge-client.ts` - - `docs/gateway/bridge-protocol.md` - -## Control plane clients today - -- CLI → Gateway WS via `callGateway` (`src/gateway/call.ts`). -- macOS app UI → Gateway WS (`GatewayConnection`). -- Web Control UI → Gateway WS. -- ACP → Gateway WS. -- Browser control uses its own HTTP control server. - -## Nodes today - -- macOS app in node mode connects to Gateway bridge (`MacNodeBridgeSession`). -- iOS/Android apps connect to Gateway bridge. -- Pairing + per‑node token stored on gateway. - -## Current approval flow (exec) - -- Agent uses `system.run` via Gateway. -- Gateway invokes node over bridge. -- Node runtime decides approval. -- UI prompt shown by mac app (when node == mac app). -- Node returns `invoke-res` to Gateway. -- Multi‑hop, UI tied to node host. - -## Presence + identity today - -- Gateway presence entries from WS clients. -- Node presence entries from bridge. -- mac app can show two entries for same machine (UI + node). -- Node identity stored in pairing store; UI identity separate. - ---- - -# Problems / pain points - -- Two protocol stacks to maintain (WS + Bridge). -- Approvals on remote nodes: prompt appears on node host, not where user is. -- TLS pinning only exists for bridge; WS depends on SSH/Tailscale. -- Identity duplication: same machine shows as multiple instances. -- Ambiguous roles: UI + node + CLI capabilities not clearly separated. - ---- - -# Proposed new state (Clawnet) - -## One protocol, two roles - -Single WS protocol with role + scope. - -- **Role: node** (capability host) -- **Role: operator** (control plane) -- Optional **scope** for operator: - - `operator.read` (status + viewing) - - `operator.write` (agent run, sends) - - `operator.admin` (config, channels, models) - -### Role behaviors - -**Node** - -- Can register capabilities (`caps`, `commands`, permissions). -- Can receive `invoke` commands (`system.run`, `camera.*`, `canvas.*`, `screen.record`, etc). -- Can send events: `voice.transcript`, `agent.request`, `chat.subscribe`. -- Cannot call config/models/channels/sessions/agent control plane APIs. - -**Operator** - -- Full control plane API, gated by scope. -- Receives all approvals. -- Does not directly execute OS actions; routes to nodes. - -### Key rule - -Role is per‑connection, not per device. A device may open both roles, separately. - ---- - -# Unified authentication + pairing - -## Client identity - -Every client provides: - -- `deviceId` (stable, derived from device key). -- `displayName` (human name). -- `role` + `scope` + `caps` + `commands`. - -## Pairing flow (unified) - -- Client connects unauthenticated. -- Gateway creates a **pairing request** for that `deviceId`. -- Operator receives prompt; approves/denies. -- Gateway issues credentials bound to: - - device public key - - role(s) - - scope(s) - - capabilities/commands -- Client persists token, reconnects authenticated. - -## Device‑bound auth (avoid bearer token replay) - -Preferred: device keypairs. - -- Device generates keypair once. -- `deviceId = fingerprint(publicKey)`. -- Gateway sends nonce; device signs; gateway verifies. -- Tokens are issued to a public key (proof‑of‑possession), not a string. - -Alternatives: - -- mTLS (client certs): strongest, more ops complexity. -- Short‑lived bearer tokens only as a temporary phase (rotate + revoke early). - -## Silent approval (SSH heuristic) - -Define it precisely to avoid a weak link. Prefer one: - -- **Local‑only**: auto‑pair when client connects via loopback/Unix socket. -- **Challenge via SSH**: gateway issues nonce; client proves SSH by fetching it. -- **Physical presence window**: after a local approval on gateway host UI, allow auto‑pair for a short window (e.g. 10 minutes). - -Always log + record auto‑approvals. - ---- - -# TLS everywhere (dev + prod) - -## Reuse existing bridge TLS - -Use current TLS runtime + fingerprint pinning: - -- `src/infra/bridge/server/tls.ts` -- fingerprint verification logic in `src/node-host/bridge-client.ts` - -## Apply to WS - -- WS server supports TLS with same cert/key + fingerprint. -- WS clients can pin fingerprint (optional). -- Discovery advertises TLS + fingerprint for all endpoints. - - Discovery is locator hints only; never a trust anchor. - -## Why - -- Reduce reliance on SSH/Tailscale for confidentiality. -- Make remote mobile connections safe by default. - ---- - -# Approvals redesign (centralized) - -## Current - -Approval happens on node host (mac app node runtime). Prompt appears where node runs. - -## Proposed - -Approval is **gateway‑hosted**, UI delivered to operator clients. - -### New flow - -1. Gateway receives `system.run` intent (agent). -2. Gateway creates approval record: `approval.requested`. -3. Operator UI(s) show prompt. -4. Approval decision sent to gateway: `approval.resolve`. -5. Gateway invokes node command if approved. -6. Node executes, returns `invoke-res`. - -### Approval semantics (hardening) - -- Broadcast to all operators; only the active UI shows a modal (others get a toast). -- First resolution wins; gateway rejects subsequent resolves as already settled. -- Default timeout: deny after N seconds (e.g. 60s), log reason. -- Resolution requires `operator.approvals` scope. - -## Benefits - -- Prompt appears where user is (mac/phone). -- Consistent approvals for remote nodes. -- Node runtime stays headless; no UI dependency. - ---- - -# Role clarity examples - -## iPhone app - -- **Node role** for: mic, camera, voice chat, location, push‑to‑talk. -- Optional **operator.read** for status and chat view. -- Optional **operator.write/admin** only when explicitly enabled. - -## macOS app - -- Operator role by default (control UI). -- Node role when “Mac node” enabled (system.run, screen, camera). -- Same deviceId for both connections → merged UI entry. - -## CLI - -- Operator role always. -- Scope derived by subcommand: - - `status`, `logs` → read - - `agent`, `message` → write - - `config`, `channels` → admin - - approvals + pairing → `operator.approvals` / `operator.pairing` - ---- - -# Identity + slugs - -## Stable ID - -Required for auth; never changes. -Preferred: - -- Keypair fingerprint (public key hash). - -## Cute slug (lobster‑themed) - -Human label only. - -- Example: `scarlet-claw`, `saltwave`, `mantis-pinch`. -- Stored in gateway registry, editable. -- Collision handling: `-2`, `-3`. - -## UI grouping - -Same `deviceId` across roles → single “Instance” row: - -- Badge: `operator`, `node`. -- Shows capabilities + last seen. - ---- - -# Migration strategy - -## Phase 0: Document + align - -- Publish this doc. -- Inventory all protocol calls + approval flows. - -## Phase 1: Add roles/scopes to WS - -- Extend `connect` params with `role`, `scope`, `deviceId`. -- Add allowlist gating for node role. - -## Phase 2: Bridge compatibility - -- Keep bridge running. -- Add WS node support in parallel. -- Gate features behind config flag. - -## Phase 3: Central approvals - -- Add approval request + resolve events in WS. -- Update mac app UI to prompt + respond. -- Node runtime stops prompting UI. - -## Phase 4: TLS unification - -- Add TLS config for WS using bridge TLS runtime. -- Add pinning to clients. - -## Phase 5: Deprecate bridge - -- Migrate iOS/Android/mac node to WS. -- Keep bridge as fallback; remove once stable. - -## Phase 6: Device‑bound auth - -- Require key‑based identity for all non‑local connections. -- Add revocation + rotation UI. - ---- - -# Security notes - -- Role/allowlist enforced at gateway boundary. -- No client gets “full” API without operator scope. -- Pairing required for _all_ connections. -- TLS + pinning reduces MITM risk for mobile. -- SSH silent approval is a convenience; still recorded + revocable. -- Discovery is never a trust anchor. -- Capability claims are verified against server allowlists by platform/type. - -# Streaming + large payloads (node media) - -WS control plane is fine for small messages, but nodes also do: - -- camera clips -- screen recordings -- audio streams - -Options: - -1. WS binary frames + chunking + backpressure rules. -2. Separate streaming endpoint (still TLS + auth). -3. Keep bridge longer for media‑heavy commands, migrate last. - -Pick one before implementation to avoid drift. - -# Capability + command policy - -- Node‑reported caps/commands are treated as **claims**. -- Gateway enforces per‑platform allowlists. -- Any new command requires operator approval or explicit allowlist change. -- Audit changes with timestamps. - -# Audit + rate limiting - -- Log: pairing requests, approvals/denials, token issuance/rotation/revocation. -- Rate‑limit pairing spam and approval prompts. - -# Protocol hygiene - -- Explicit protocol version + error codes. -- Reconnect rules + heartbeat policy. -- Presence TTL and last‑seen semantics. - ---- - -# Open questions - -1. Single device running both roles: token model - - Recommend separate tokens per role (node vs operator). - - Same deviceId; different scopes; clearer revocation. - -2. Operator scope granularity - - read/write/admin + approvals + pairing (minimum viable). - - Consider per‑feature scopes later. - -3. Token rotation + revocation UX - - Auto‑rotate on role change. - - UI to revoke by deviceId + role. - -4. Discovery - - Extend current Bonjour TXT to include WS TLS fingerprint + role hints. - - Treat as locator hints only. - -5. Cross‑network approval - - Broadcast to all operator clients; active UI shows modal. - - First response wins; gateway enforces atomicity. - ---- - -# Summary (TL;DR) - -- Today: WS control plane + Bridge node transport. -- Pain: approvals + duplication + two stacks. -- Proposal: one WS protocol with explicit roles + scopes, unified pairing + TLS pinning, gateway‑hosted approvals, stable device IDs + cute slugs. -- Outcome: simpler UX, stronger security, less duplication, better mobile routing. diff --git a/docs/refactor/cluster.md b/docs/refactor/cluster.md deleted file mode 100644 index f3b13186972..00000000000 --- a/docs/refactor/cluster.md +++ /dev/null @@ -1,299 +0,0 @@ ---- -summary: "Refactor clusters with highest LOC reduction potential" -read_when: - - You want to reduce total LOC without changing behavior - - You are choosing the next dedupe or extraction pass -title: "Refactor Cluster Backlog" ---- - -# Refactor Cluster Backlog - -Ranked by likely LOC reduction, safety, and breadth. - -## 1. Channel plugin config and security scaffolding - -Highest-value cluster. - -Repeated shapes across many channel plugins: - -- `config.listAccountIds` -- `config.resolveAccount` -- `config.defaultAccountId` -- `config.setAccountEnabled` -- `config.deleteAccount` -- `config.describeAccount` -- `security.resolveDmPolicy` - -Strong examples: - -- `extensions/telegram/src/channel.ts` -- `extensions/googlechat/src/channel.ts` -- `extensions/slack/src/channel.ts` -- `extensions/discord/src/channel.ts` -- `extensions/matrix/src/channel.ts` -- `extensions/irc/src/channel.ts` -- `extensions/signal/src/channel.ts` -- `extensions/mattermost/src/channel.ts` - -Likely extraction shape: - -- `buildChannelConfigAdapter(...)` -- `buildMultiAccountConfigAdapter(...)` -- `buildDmSecurityAdapter(...)` - -Expected savings: - -- ~250-450 LOC - -Risk: - -- Medium. Each channel has slightly different `isConfigured`, warnings, and normalization. - -## 2. Extension runtime singleton boilerplate - -Very safe. - -Nearly every extension has the same runtime holder: - -- `let runtime: PluginRuntime | null = null` -- `setXRuntime` -- `getXRuntime` - -Strong examples: - -- `extensions/telegram/src/runtime.ts` -- `extensions/matrix/src/runtime.ts` -- `extensions/slack/src/runtime.ts` -- `extensions/discord/src/runtime.ts` -- `extensions/whatsapp/src/runtime.ts` -- `extensions/imessage/src/runtime.ts` -- `extensions/twitch/src/runtime.ts` - -Special-case variants: - -- `extensions/bluebubbles/src/runtime.ts` -- `extensions/line/src/runtime.ts` -- `extensions/synology-chat/src/runtime.ts` - -Likely extraction shape: - -- `createPluginRuntimeStore(errorMessage)` - -Expected savings: - -- ~180-260 LOC - -Risk: - -- Low - -## 3. Onboarding prompt and config-patch steps - -Large surface area. - -Many onboarding files repeat: - -- resolve account id -- prompt allowlist entries -- merge allowFrom -- set DM policy -- prompt secrets -- patch top-level vs account-scoped config - -Strong examples: - -- `extensions/bluebubbles/src/onboarding.ts` -- `extensions/googlechat/src/onboarding.ts` -- `extensions/msteams/src/onboarding.ts` -- `extensions/zalo/src/onboarding.ts` -- `extensions/zalouser/src/onboarding.ts` -- `extensions/nextcloud-talk/src/onboarding.ts` -- `extensions/matrix/src/onboarding.ts` -- `extensions/irc/src/onboarding.ts` - -Existing helper seam: - -- `src/channels/plugins/onboarding/helpers.ts` - -Likely extraction shape: - -- `promptAllowFromList(...)` -- `buildDmPolicyAdapter(...)` -- `applyScopedAccountPatch(...)` -- `promptSecretFields(...)` - -Expected savings: - -- ~300-600 LOC - -Risk: - -- Medium. Easy to over-generalize; keep helpers narrow and composable. - -## 4. Multi-account config-schema fragments - -Repeated schema fragments across extensions. - -Common patterns: - -- `const allowFromEntry = z.union([z.string(), z.number()])` -- account schema plus: - - `accounts: z.object({}).catchall(accountSchema).optional()` - - `defaultAccount: z.string().optional()` -- repeated DM/group fields -- repeated markdown/tool policy fields - -Strong examples: - -- `extensions/bluebubbles/src/config-schema.ts` -- `extensions/zalo/src/config-schema.ts` -- `extensions/zalouser/src/config-schema.ts` -- `extensions/matrix/src/config-schema.ts` -- `extensions/nostr/src/config-schema.ts` - -Likely extraction shape: - -- `AllowFromEntrySchema` -- `buildMultiAccountChannelSchema(accountSchema)` -- `buildCommonDmGroupFields(...)` - -Expected savings: - -- ~120-220 LOC - -Risk: - -- Low to medium. Some schemas are simple, some are special. - -## 5. Webhook and monitor lifecycle startup - -Good medium-value cluster. - -Repeated `startAccount` / monitor setup patterns: - -- resolve account -- compute webhook path -- log startup -- start monitor -- wait for abort -- cleanup -- status sink updates - -Strong examples: - -- `extensions/googlechat/src/channel.ts` -- `extensions/bluebubbles/src/channel.ts` -- `extensions/zalo/src/channel.ts` -- `extensions/telegram/src/channel.ts` -- `extensions/nextcloud-talk/src/channel.ts` - -Existing helper seam: - -- `src/plugin-sdk/channel-lifecycle.ts` - -Likely extraction shape: - -- helper for account monitor lifecycle -- helper for webhook-backed account startup - -Expected savings: - -- ~150-300 LOC - -Risk: - -- Medium to high. Transport details diverge quickly. - -## 6. Small exact-clone cleanup - -Low-risk cleanup bucket. - -Examples: - -- duplicated gateway argv detection: - - `src/infra/gateway-lock.ts` - - `src/cli/daemon-cli/lifecycle.ts` -- duplicated port diagnostics rendering: - - `src/cli/daemon-cli/restart-health.ts` -- duplicated session-key construction: - - `src/web/auto-reply/monitor/broadcast.ts` - -Expected savings: - -- ~30-60 LOC - -Risk: - -- Low - -## Test clusters - -### LINE webhook event fixtures - -Strong examples: - -- `src/line/bot-handlers.test.ts` - -Likely extraction: - -- `makeLineEvent(...)` -- `runLineEvent(...)` -- `makeLineAccount(...)` - -Expected savings: - -- ~120-180 LOC - -### Telegram native command auth matrix - -Strong examples: - -- `src/telegram/bot-native-commands.group-auth.test.ts` -- `src/telegram/bot-native-commands.plugin-auth.test.ts` - -Likely extraction: - -- forum context builder -- denied-message assertion helper -- table-driven auth cases - -Expected savings: - -- ~80-140 LOC - -### Zalo lifecycle setup - -Strong examples: - -- `extensions/zalo/src/monitor.lifecycle.test.ts` - -Likely extraction: - -- shared monitor setup harness - -Expected savings: - -- ~50-90 LOC - -### Brave llm-context unsupported-option tests - -Strong examples: - -- `src/agents/tools/web-tools.enabled-defaults.test.ts` - -Likely extraction: - -- `it.each(...)` matrix - -Expected savings: - -- ~30-50 LOC - -## Suggested order - -1. Runtime singleton boilerplate -2. Small exact-clone cleanup -3. Config and security builder extraction -4. Test-helper extraction -5. Onboarding step extraction -6. Monitor lifecycle helper extraction diff --git a/docs/refactor/exec-host.md b/docs/refactor/exec-host.md deleted file mode 100644 index a70cf7c9dbd..00000000000 --- a/docs/refactor/exec-host.md +++ /dev/null @@ -1,316 +0,0 @@ ---- -summary: "Refactor plan: exec host routing, node approvals, and headless runner" -read_when: - - Designing exec host routing or exec approvals - - Implementing node runner + UI IPC - - Adding exec host security modes and slash commands -title: "Exec Host Refactor" ---- - -# Exec host refactor plan - -## Goals - -- Add `exec.host` + `exec.security` to route execution across **sandbox**, **gateway**, and **node**. -- Keep defaults **safe**: no cross-host execution unless explicitly enabled. -- Split execution into a **headless runner service** with optional UI (macOS app) via local IPC. -- Provide **per-agent** policy, allowlist, ask mode, and node binding. -- Support **ask modes** that work _with_ or _without_ allowlists. -- Cross-platform: Unix socket + token auth (macOS/Linux/Windows parity). - -## Non-goals - -- No legacy allowlist migration or legacy schema support. -- No PTY/streaming for node exec (aggregated output only). -- No new network layer beyond the existing Bridge + Gateway. - -## Decisions (locked) - -- **Config keys:** `exec.host` + `exec.security` (per-agent override allowed). -- **Elevation:** keep `/elevated` as an alias for gateway full access. -- **Ask default:** `on-miss`. -- **Approvals store:** `~/.openclaw/exec-approvals.json` (JSON, no legacy migration). -- **Runner:** headless system service; UI app hosts a Unix socket for approvals. -- **Node identity:** use existing `nodeId`. -- **Socket auth:** Unix socket + token (cross-platform); split later if needed. -- **Node host state:** `~/.openclaw/node.json` (node id + pairing token). -- **macOS exec host:** run `system.run` inside the macOS app; node host service forwards requests over local IPC. -- **No XPC helper:** stick to Unix socket + token + peer checks. - -## Key concepts - -### Host - -- `sandbox`: Docker exec (current behavior). -- `gateway`: exec on gateway host. -- `node`: exec on node runner via Bridge (`system.run`). - -### Security mode - -- `deny`: always block. -- `allowlist`: allow only matches. -- `full`: allow everything (equivalent to elevated). - -### Ask mode - -- `off`: never ask. -- `on-miss`: ask only when allowlist does not match. -- `always`: ask every time. - -Ask is **independent** of allowlist; allowlist can be used with `always` or `on-miss`. - -### Policy resolution (per exec) - -1. Resolve `exec.host` (tool param → agent override → global default). -2. Resolve `exec.security` and `exec.ask` (same precedence). -3. If host is `sandbox`, proceed with local sandbox exec. -4. If host is `gateway` or `node`, apply security + ask policy on that host. - -## Default safety - -- Default `exec.host = sandbox`. -- Default `exec.security = deny` for `gateway` and `node`. -- Default `exec.ask = on-miss` (only relevant if security allows). -- If no node binding is set, **agent may target any node**, but only if policy allows it. - -## Config surface - -### Tool parameters - -- `exec.host` (optional): `sandbox | gateway | node`. -- `exec.security` (optional): `deny | allowlist | full`. -- `exec.ask` (optional): `off | on-miss | always`. -- `exec.node` (optional): node id/name to use when `host=node`. - -### Config keys (global) - -- `tools.exec.host` -- `tools.exec.security` -- `tools.exec.ask` -- `tools.exec.node` (default node binding) - -### Config keys (per agent) - -- `agents.list[].tools.exec.host` -- `agents.list[].tools.exec.security` -- `agents.list[].tools.exec.ask` -- `agents.list[].tools.exec.node` - -### Alias - -- `/elevated on` = set `tools.exec.host=gateway`, `tools.exec.security=full` for the agent session. -- `/elevated off` = restore previous exec settings for the agent session. - -## Approvals store (JSON) - -Path: `~/.openclaw/exec-approvals.json` - -Purpose: - -- Local policy + allowlists for the **execution host** (gateway or node runner). -- Ask fallback when no UI is available. -- IPC credentials for UI clients. - -Proposed schema (v1): - -```json -{ - "version": 1, - "socket": { - "path": "~/.openclaw/exec-approvals.sock", - "token": "base64-opaque-token" - }, - "defaults": { - "security": "deny", - "ask": "on-miss", - "askFallback": "deny" - }, - "agents": { - "agent-id-1": { - "security": "allowlist", - "ask": "on-miss", - "allowlist": [ - { - "pattern": "~/Projects/**/bin/rg", - "lastUsedAt": 0, - "lastUsedCommand": "rg -n TODO", - "lastResolvedPath": "/Users/user/Projects/.../bin/rg" - } - ] - } - } -} -``` - -Notes: - -- No legacy allowlist formats. -- `askFallback` applies only when `ask` is required and no UI is reachable. -- File permissions: `0600`. - -## Runner service (headless) - -### Role - -- Enforce `exec.security` + `exec.ask` locally. -- Execute system commands and return output. -- Emit Bridge events for exec lifecycle (optional but recommended). - -### Service lifecycle - -- Launchd/daemon on macOS; system service on Linux/Windows. -- Approvals JSON is local to the execution host. -- UI hosts a local Unix socket; runners connect on demand. - -## UI integration (macOS app) - -### IPC - -- Unix socket at `~/.openclaw/exec-approvals.sock` (0600). -- Token stored in `exec-approvals.json` (0600). -- Peer checks: same-UID only. -- Challenge/response: nonce + HMAC(token, request-hash) to prevent replay. -- Short TTL (e.g., 10s) + max payload + rate limit. - -### Ask flow (macOS app exec host) - -1. Node service receives `system.run` from gateway. -2. Node service connects to the local socket and sends the prompt/exec request. -3. App validates peer + token + HMAC + TTL, then shows dialog if needed. -4. App executes the command in UI context and returns output. -5. Node service returns output to gateway. - -If UI missing: - -- Apply `askFallback` (`deny|allowlist|full`). - -### Diagram (SCI) - -``` -Agent -> Gateway -> Bridge -> Node Service (TS) - | IPC (UDS + token + HMAC + TTL) - v - Mac App (UI + TCC + system.run) -``` - -## Node identity + binding - -- Use existing `nodeId` from Bridge pairing. -- Binding model: - - `tools.exec.node` restricts the agent to a specific node. - - If unset, agent can pick any node (policy still enforces defaults). -- Node selection resolution: - - `nodeId` exact match - - `displayName` (normalized) - - `remoteIp` - - `nodeId` prefix (>= 6 chars) - -## Eventing - -### Who sees events - -- System events are **per session** and shown to the agent on the next prompt. -- Stored in the gateway in-memory queue (`enqueueSystemEvent`). - -### Event text - -- `Exec started (node=, id=)` -- `Exec finished (node=, id=, code=)` + optional output tail -- `Exec denied (node=, id=, )` - -### Transport - -Option A (recommended): - -- Runner sends Bridge `event` frames `exec.started` / `exec.finished`. -- Gateway `handleBridgeEvent` maps these into `enqueueSystemEvent`. - -Option B: - -- Gateway `exec` tool handles lifecycle directly (synchronous only). - -## Exec flows - -### Sandbox host - -- Existing `exec` behavior (Docker or host when unsandboxed). -- PTY supported in non-sandbox mode only. - -### Gateway host - -- Gateway process executes on its own machine. -- Enforces local `exec-approvals.json` (security/ask/allowlist). - -### Node host - -- Gateway calls `node.invoke` with `system.run`. -- Runner enforces local approvals. -- Runner returns aggregated stdout/stderr. -- Optional Bridge events for start/finish/deny. - -## Output caps - -- Cap combined stdout+stderr at **200k**; keep **tail 20k** for events. -- Truncate with a clear suffix (e.g., `"… (truncated)"`). - -## Slash commands - -- `/exec host= security= ask= node=` -- Per-agent, per-session overrides; non-persistent unless saved via config. -- `/elevated on|off|ask|full` remains a shortcut for `host=gateway security=full` (with `full` skipping approvals). - -## Cross-platform story - -- The runner service is the portable execution target. -- UI is optional; if missing, `askFallback` applies. -- Windows/Linux support the same approvals JSON + socket protocol. - -## Implementation phases - -### Phase 1: config + exec routing - -- Add config schema for `exec.host`, `exec.security`, `exec.ask`, `exec.node`. -- Update tool plumbing to respect `exec.host`. -- Add `/exec` slash command and keep `/elevated` alias. - -### Phase 2: approvals store + gateway enforcement - -- Implement `exec-approvals.json` reader/writer. -- Enforce allowlist + ask modes for `gateway` host. -- Add output caps. - -### Phase 3: node runner enforcement - -- Update node runner to enforce allowlist + ask. -- Add Unix socket prompt bridge to macOS app UI. -- Wire `askFallback`. - -### Phase 4: events - -- Add node → gateway Bridge events for exec lifecycle. -- Map to `enqueueSystemEvent` for agent prompts. - -### Phase 5: UI polish - -- Mac app: allowlist editor, per-agent switcher, ask policy UI. -- Node binding controls (optional). - -## Testing plan - -- Unit tests: allowlist matching (glob + case-insensitive). -- Unit tests: policy resolution precedence (tool param → agent override → global). -- Integration tests: node runner deny/allow/ask flows. -- Bridge event tests: node event → system event routing. - -## Open risks - -- UI unavailability: ensure `askFallback` is respected. -- Long-running commands: rely on timeout + output caps. -- Multi-node ambiguity: error unless node binding or explicit node param. - -## Related docs - -- [Exec tool](/tools/exec) -- [Exec approvals](/tools/exec-approvals) -- [Nodes](/nodes) -- [Elevated mode](/tools/elevated) diff --git a/docs/refactor/outbound-session-mirroring.md b/docs/refactor/outbound-session-mirroring.md deleted file mode 100644 index 4f712541658..00000000000 --- a/docs/refactor/outbound-session-mirroring.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -title: Outbound Session Mirroring Refactor (Issue #1520) -description: Track outbound session mirroring refactor notes, decisions, tests, and open items. -summary: "Refactor notes for mirroring outbound sends into target channel sessions" -read_when: - - Working on outbound transcript/session mirroring behavior - - Debugging sessionKey derivation for send/message tool paths ---- - -# Outbound Session Mirroring Refactor (Issue #1520) - -## Status - -- In progress. -- Core + plugin channel routing updated for outbound mirroring. -- Gateway send now derives target session when sessionKey is omitted. - -## Context - -Outbound sends were mirrored into the _current_ agent session (tool session key) rather than the target channel session. Inbound routing uses channel/peer session keys, so outbound responses landed in the wrong session and first-contact targets often lacked session entries. - -## Goals - -- Mirror outbound messages into the target channel session key. -- Create session entries on outbound when missing. -- Keep thread/topic scoping aligned with inbound session keys. -- Cover core channels plus bundled extensions. - -## Implementation Summary - -- New outbound session routing helper: - - `src/infra/outbound/outbound-session.ts` - - `resolveOutboundSessionRoute` builds target sessionKey using `buildAgentSessionKey` (dmScope + identityLinks). - - `ensureOutboundSessionEntry` writes minimal `MsgContext` via `recordSessionMetaFromInbound`. -- `runMessageAction` (send) derives target sessionKey and passes it to `executeSendAction` for mirroring. -- `message-tool` no longer mirrors directly; it only resolves agentId from the current session key. -- Plugin send path mirrors via `appendAssistantMessageToSessionTranscript` using the derived sessionKey. -- Gateway send derives a target session key when none is provided (default agent), and ensures a session entry. - -## Thread/Topic Handling - -- Slack: replyTo/threadId -> `resolveThreadSessionKeys` (suffix). -- Discord: threadId/replyTo -> `resolveThreadSessionKeys` with `useSuffix=false` to match inbound (thread channel id already scopes session). -- Telegram: topic IDs map to `chatId:topic:` via `buildTelegramGroupPeerId`. - -## Extensions Covered - -- Matrix, MS Teams, Mattermost, BlueBubbles, Nextcloud Talk, Zalo, Zalo Personal, Nostr, Tlon. -- Notes: - - Mattermost targets now strip `@` for DM session key routing. - - Zalo Personal uses DM peer kind for 1:1 targets (group only when `group:` is present). - - BlueBubbles group targets strip `chat_*` prefixes to match inbound session keys. - - Slack auto-thread mirroring matches channel ids case-insensitively. - - Gateway send lowercases provided session keys before mirroring. - -## Decisions - -- **Gateway send session derivation**: if `sessionKey` is provided, use it. If omitted, derive a sessionKey from target + default agent and mirror there. -- **Session entry creation**: always use `recordSessionMetaFromInbound` with `Provider/From/To/ChatType/AccountId/Originating*` aligned to inbound formats. -- **Target normalization**: outbound routing uses resolved targets (post `resolveChannelTarget`) when available. -- **Session key casing**: canonicalize session keys to lowercase on write and during migrations. - -## Tests Added/Updated - -- `src/infra/outbound/outbound.test.ts` - - Slack thread session key. - - Telegram topic session key. - - dmScope identityLinks with Discord. -- `src/agents/tools/message-tool.test.ts` - - Derives agentId from session key (no sessionKey passed through). -- `src/gateway/server-methods/send.test.ts` - - Derives session key when omitted and creates session entry. - -## Open Items / Follow-ups - -- Voice-call plugin uses custom `voice:` session keys. Outbound mapping is not standardized here; if message-tool should support voice-call sends, add explicit mapping. -- Confirm if any external plugin uses non-standard `From/To` formats beyond the bundled set. - -## Files Touched - -- `src/infra/outbound/outbound-session.ts` -- `src/infra/outbound/outbound-send-service.ts` -- `src/infra/outbound/message-action-runner.ts` -- `src/agents/tools/message-tool.ts` -- `src/gateway/server-methods/send.ts` -- Tests in: - - `src/infra/outbound/outbound.test.ts` - - `src/agents/tools/message-tool.test.ts` - - `src/gateway/server-methods/send.test.ts` diff --git a/docs/refactor/plugin-sdk.md b/docs/refactor/plugin-sdk.md deleted file mode 100644 index 4722644083b..00000000000 --- a/docs/refactor/plugin-sdk.md +++ /dev/null @@ -1,214 +0,0 @@ ---- -summary: "Plan: one clean plugin SDK + runtime for all messaging connectors" -read_when: - - Defining or refactoring the plugin architecture - - Migrating channel connectors to the plugin SDK/runtime -title: "Plugin SDK Refactor" ---- - -# Plugin SDK + Runtime Refactor Plan - -Goal: every messaging connector is a plugin (bundled or external) using one stable API. -No plugin imports from `src/**` directly. All dependencies go through the SDK or runtime. - -## Why now - -- Current connectors mix patterns: direct core imports, dist-only bridges, and custom helpers. -- This makes upgrades brittle and blocks a clean external plugin surface. - -## Target architecture (two layers) - -### 1) Plugin SDK (compile-time, stable, publishable) - -Scope: types, helpers, and config utilities. No runtime state, no side effects. - -Contents (examples): - -- Types: `ChannelPlugin`, adapters, `ChannelMeta`, `ChannelCapabilities`, `ChannelDirectoryEntry`. -- Config helpers: `buildChannelConfigSchema`, `setAccountEnabledInConfigSection`, `deleteAccountFromConfigSection`, - `applyAccountNameToChannelSection`. -- Pairing helpers: `PAIRING_APPROVED_MESSAGE`, `formatPairingApproveHint`. -- Onboarding helpers: `promptChannelAccessConfig`, `addWildcardAllowFrom`, onboarding types. -- Tool param helpers: `createActionGate`, `readStringParam`, `readNumberParam`, `readReactionParams`, `jsonResult`. -- Docs link helper: `formatDocsLink`. - -Delivery: - -- Publish as `openclaw/plugin-sdk` (or export from core under `openclaw/plugin-sdk`). -- Semver with explicit stability guarantees. - -### 2) Plugin Runtime (execution surface, injected) - -Scope: everything that touches core runtime behavior. -Accessed via `OpenClawPluginApi.runtime` so plugins never import `src/**`. - -Proposed surface (minimal but complete): - -```ts -export type PluginRuntime = { - channel: { - text: { - chunkMarkdownText(text: string, limit: number): string[]; - resolveTextChunkLimit(cfg: OpenClawConfig, channel: string, accountId?: string): number; - hasControlCommand(text: string, cfg: OpenClawConfig): boolean; - }; - reply: { - dispatchReplyWithBufferedBlockDispatcher(params: { - ctx: unknown; - cfg: unknown; - dispatcherOptions: { - deliver: (payload: { - text?: string; - mediaUrls?: string[]; - mediaUrl?: string; - }) => void | Promise; - onError?: (err: unknown, info: { kind: string }) => void; - }; - }): Promise; - createReplyDispatcherWithTyping?: unknown; // adapter for Teams-style flows - }; - routing: { - resolveAgentRoute(params: { - cfg: unknown; - channel: string; - accountId: string; - peer: { kind: RoutePeerKind; id: string }; - }): { sessionKey: string; accountId: string }; - }; - pairing: { - buildPairingReply(params: { channel: string; idLine: string; code: string }): string; - readAllowFromStore(channel: string): Promise; - upsertPairingRequest(params: { - channel: string; - id: string; - meta?: { name?: string }; - }): Promise<{ code: string; created: boolean }>; - }; - media: { - fetchRemoteMedia(params: { url: string }): Promise<{ buffer: Buffer; contentType?: string }>; - saveMediaBuffer( - buffer: Uint8Array, - contentType: string | undefined, - direction: "inbound" | "outbound", - maxBytes: number, - ): Promise<{ path: string; contentType?: string }>; - }; - mentions: { - buildMentionRegexes(cfg: OpenClawConfig, agentId?: string): RegExp[]; - matchesMentionPatterns(text: string, regexes: RegExp[]): boolean; - }; - groups: { - resolveGroupPolicy( - cfg: OpenClawConfig, - channel: string, - accountId: string, - groupId: string, - ): { - allowlistEnabled: boolean; - allowed: boolean; - groupConfig?: unknown; - defaultConfig?: unknown; - }; - resolveRequireMention( - cfg: OpenClawConfig, - channel: string, - accountId: string, - groupId: string, - override?: boolean, - ): boolean; - }; - debounce: { - createInboundDebouncer(opts: { - debounceMs: number; - buildKey: (v: T) => string | null; - shouldDebounce: (v: T) => boolean; - onFlush: (entries: T[]) => Promise; - onError?: (err: unknown) => void; - }): { push: (v: T) => void; flush: () => Promise }; - resolveInboundDebounceMs(cfg: OpenClawConfig, channel: string): number; - }; - commands: { - resolveCommandAuthorizedFromAuthorizers(params: { - useAccessGroups: boolean; - authorizers: Array<{ configured: boolean; allowed: boolean }>; - }): boolean; - }; - }; - logging: { - shouldLogVerbose(): boolean; - getChildLogger(name: string): PluginLogger; - }; - state: { - resolveStateDir(cfg: OpenClawConfig): string; - }; -}; -``` - -Notes: - -- Runtime is the only way to access core behavior. -- SDK is intentionally small and stable. -- Each runtime method maps to an existing core implementation (no duplication). - -## Migration plan (phased, safe) - -### Phase 0: scaffolding - -- Introduce `openclaw/plugin-sdk`. -- Add `api.runtime` to `OpenClawPluginApi` with the surface above. -- Maintain existing imports during a transition window (deprecation warnings). - -### Phase 1: bridge cleanup (low risk) - -- Replace per-extension `core-bridge.ts` with `api.runtime`. -- Migrate BlueBubbles, Zalo, Zalo Personal first (already close). -- Remove duplicated bridge code. - -### Phase 2: light direct-import plugins - -- Migrate Matrix to SDK + runtime. -- Validate onboarding, directory, group mention logic. - -### Phase 3: heavy direct-import plugins - -- Migrate MS Teams (largest set of runtime helpers). -- Ensure reply/typing semantics match current behavior. - -### Phase 4: iMessage pluginization - -- Move iMessage into `extensions/imessage`. -- Replace direct core calls with `api.runtime`. -- Keep config keys, CLI behavior, and docs intact. - -### Phase 5: enforcement - -- Add lint rule / CI check: no `extensions/**` imports from `src/**`. -- Add plugin SDK/version compatibility checks (runtime + SDK semver). - -## Compatibility and versioning - -- SDK: semver, published, documented changes. -- Runtime: versioned per core release. Add `api.runtime.version`. -- Plugins declare a required runtime range (e.g., `openclawRuntime: ">=2026.2.0"`). - -## Testing strategy - -- Adapter-level unit tests (runtime functions exercised with real core implementation). -- Golden tests per plugin: ensure no behavior drift (routing, pairing, allowlist, mention gating). -- A single end-to-end plugin sample used in CI (install + run + smoke). - -## Open questions - -- Where to host SDK types: separate package or core export? -- Runtime type distribution: in SDK (types only) or in core? -- How to expose docs links for bundled vs external plugins? -- Do we allow limited direct core imports for in-repo plugins during transition? - -## Success criteria - -- All channel connectors are plugins using SDK + runtime. -- No `extensions/**` imports from `src/**`. -- New connector templates depend only on SDK + runtime. -- External plugins can be developed and updated without core source access. - -Related docs: [Plugins](/tools/plugin), [Channels](/channels/index), [Configuration](/gateway/configuration). diff --git a/docs/refactor/strict-config.md b/docs/refactor/strict-config.md deleted file mode 100644 index 9605730c2b0..00000000000 --- a/docs/refactor/strict-config.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -summary: "Strict config validation + doctor-only migrations" -read_when: - - Designing or implementing config validation behavior - - Working on config migrations or doctor workflows - - Handling plugin config schemas or plugin load gating -title: "Strict Config Validation" ---- - -# Strict config validation (doctor-only migrations) - -## Goals - -- **Reject unknown config keys everywhere** (root + nested), except root `$schema` metadata. -- **Reject plugin config without a schema**; don’t load that plugin. -- **Remove legacy auto-migration on load**; migrations run via doctor only. -- **Auto-run doctor (dry-run) on startup**; if invalid, block non-diagnostic commands. - -## Non-goals - -- Backward compatibility on load (legacy keys do not auto-migrate). -- Silent drops of unrecognized keys. - -## Strict validation rules - -- Config must match the schema exactly at every level. -- Unknown keys are validation errors (no passthrough at root or nested), except root `$schema` when it is a string. -- `plugins.entries..config` must be validated by the plugin’s schema. - - If a plugin lacks a schema, **reject plugin load** and surface a clear error. -- Unknown `channels.` keys are errors unless a plugin manifest declares the channel id. -- Plugin manifests (`openclaw.plugin.json`) are required for all plugins. - -## Plugin schema enforcement - -- Each plugin provides a strict JSON Schema for its config (inline in the manifest). -- Plugin load flow: - 1. Resolve plugin manifest + schema (`openclaw.plugin.json`). - 2. Validate config against the schema. - 3. If missing schema or invalid config: block plugin load, record error. -- Error message includes: - - Plugin id - - Reason (missing schema / invalid config) - - Path(s) that failed validation -- Disabled plugins keep their config, but Doctor + logs surface a warning. - -## Doctor flow - -- Doctor runs **every time** config is loaded (dry-run by default). -- If config invalid: - - Print a summary + actionable errors. - - Instruct: `openclaw doctor --fix`. -- `openclaw doctor --fix`: - - Applies migrations. - - Removes unknown keys. - - Writes updated config. - -## Command gating (when config is invalid) - -Allowed (diagnostic-only): - -- `openclaw doctor` -- `openclaw logs` -- `openclaw health` -- `openclaw help` -- `openclaw status` -- `openclaw gateway status` - -Everything else must hard-fail with: “Config invalid. Run `openclaw doctor --fix`.” - -## Error UX format - -- Single summary header. -- Grouped sections: - - Unknown keys (full paths) - - Legacy keys / migrations needed - - Plugin load failures (plugin id + reason + path) - -## Implementation touchpoints - -- `src/config/zod-schema.ts`: remove root passthrough; strict objects everywhere. -- `src/config/zod-schema.providers.ts`: ensure strict channel schemas. -- `src/config/validation.ts`: fail on unknown keys; do not apply legacy migrations. -- `src/config/io.ts`: remove legacy auto-migrations; always run doctor dry-run. -- `src/config/legacy*.ts`: move usage to doctor only. -- `src/plugins/*`: add schema registry + gating. -- CLI command gating in `src/cli`. - -## Tests - -- Unknown key rejection (root + nested). -- Plugin missing schema → plugin load blocked with clear error. -- Invalid config → gateway startup blocked except diagnostic commands. -- Doctor dry-run auto; `doctor --fix` writes corrected config. diff --git a/docs/reference/AGENTS.default.md b/docs/reference/AGENTS.default.md index 6e2869403f5..7bfb2351d0d 100644 --- a/docs/reference/AGENTS.default.md +++ b/docs/reference/AGENTS.default.md @@ -6,7 +6,7 @@ read_when: - Enabling or auditing default skills --- -# AGENTS.md — OpenClaw Personal Assistant (default) +# AGENTS.md - OpenClaw Personal Assistant (default) ## First run (recommended) @@ -48,7 +48,8 @@ cp docs/reference/AGENTS.default.md ~/.openclaw/workspace/AGENTS.md ## Session start (required) -- Read `SOUL.md`, `USER.md`, `memory.md`, and today+yesterday in `memory/`. +- Read `SOUL.md`, `USER.md`, and today+yesterday in `memory/`. +- Read `MEMORY.md` when present; only fall back to lowercase `memory.md` when `MEMORY.md` is absent. - Do it before responding. ## Soul (required) @@ -65,8 +66,9 @@ cp docs/reference/AGENTS.default.md ~/.openclaw/workspace/AGENTS.md ## Memory system (recommended) - Daily log: `memory/YYYY-MM-DD.md` (create `memory/` if needed). -- Long-term memory: `memory.md` for durable facts, preferences, and decisions. -- On session start, read today + yesterday + `memory.md` if present. +- Long-term memory: `MEMORY.md` for durable facts, preferences, and decisions. +- Lowercase `memory.md` is legacy fallback only; do not keep both root files on purpose. +- On session start, read today + yesterday + `MEMORY.md` when present, otherwise `memory.md`. - Capture: decisions, preferences, constraints, open loops. - Avoid secrets unless explicitly requested. diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index b13803e69f3..275675c7dba 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -1,151 +1,42 @@ --- -title: "Release Checklist" -summary: "Step-by-step release checklist for npm + macOS app" +title: "Release Policy" +summary: "Public release channels, version naming, and cadence" read_when: - - Cutting a new npm release - - Cutting a new macOS app release - - Verifying metadata before publishing + - Looking for public release channel definitions + - Looking for version naming and cadence --- -# Release Checklist (npm + macOS) +# Release Policy -Use `pnpm` (Node 22+) from the repo root. Keep the working tree clean before tagging/publishing. +OpenClaw has three public release lanes: -## Operator trigger +- stable: tagged releases that publish to npm `latest` +- beta: prerelease tags that publish to npm `beta` +- dev: the moving head of `main` -When the operator says “release”, immediately do this preflight (no extra questions unless blocked): - -- Read this doc and `docs/platforms/mac/release.md`. -- Load env from `~/.profile` and confirm `SPARKLE_PRIVATE_KEY_FILE` + App Store Connect vars are set (SPARKLE_PRIVATE_KEY_FILE should live in `~/.profile`). -- Use Sparkle keys from `~/Library/CloudStorage/Dropbox/Backup/Sparkle` if needed. - -## Versioning - -Current OpenClaw releases use date-based versioning. +## Version naming - Stable release version: `YYYY.M.D` - Git tag: `vYYYY.M.D` - - Examples from repo history: `v2026.2.26`, `v2026.3.8` - Beta prerelease version: `YYYY.M.D-beta.N` - Git tag: `vYYYY.M.D-beta.N` - - Examples from repo history: `v2026.2.15-beta.1`, `v2026.3.8-beta.1` -- Use the same version string everywhere, minus the leading `v` where Git tags are not used: - - `package.json`: `2026.3.8` - - Git tag: `v2026.3.8` - - GitHub release title: `openclaw 2026.3.8` -- Do not zero-pad month or day. Use `2026.3.8`, not `2026.03.08`. -- Stable and beta are npm dist-tags, not separate release lines: - - `latest` = stable - - `beta` = prerelease/testing -- Dev is the moving head of `main`, not a normal git-tagged release. -- The release workflow enforces the current stable/beta tag formats and rejects versions whose CalVer date is more than 2 UTC calendar days away from the release date. +- Do not zero-pad month or day +- `latest` means the current stable npm release +- `beta` means the current prerelease npm release +- Beta releases may ship before the macOS app catches up -Historical note: +## Release cadence -- Older tags such as `v2026.1.11-1`, `v2026.2.6-3`, and `v2.0.0-beta2` exist in repo history. -- Treat those as legacy tag patterns. New releases should use `vYYYY.M.D` for stable and `vYYYY.M.D-beta.N` for beta. +- Releases move beta-first +- Stable follows only after the latest beta is validated +- Detailed release procedure, approvals, credentials, and recovery notes are + maintainer-only -1. **Version & metadata** +## Public references -- [ ] Bump `package.json` version (e.g., `2026.1.29`). -- [ ] Run `pnpm plugins:sync` to align extension package versions + changelogs. -- [ ] Update CLI/version strings in [`src/version.ts`](https://github.com/openclaw/openclaw/blob/main/src/version.ts) and the Baileys user agent in [`src/web/session.ts`](https://github.com/openclaw/openclaw/blob/main/src/web/session.ts). -- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`openclaw.mjs`](https://github.com/openclaw/openclaw/blob/main/openclaw.mjs) for `openclaw`. -- [ ] If dependencies changed, run `pnpm install` so `pnpm-lock.yaml` is current. +- [`.github/workflows/openclaw-npm-release.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-npm-release.yml) +- [`scripts/openclaw-npm-release-check.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/openclaw-npm-release-check.ts) -2. **Build & artifacts** - -- [ ] If A2UI inputs changed, run `pnpm canvas:a2ui:bundle` and commit any updated [`src/canvas-host/a2ui/a2ui.bundle.js`](https://github.com/openclaw/openclaw/blob/main/src/canvas-host/a2ui/a2ui.bundle.js). -- [ ] `pnpm run build` (regenerates `dist/`). -- [ ] Verify npm package `files` includes all required `dist/*` folders (notably `dist/node-host/**` and `dist/acp/**` for headless node + ACP CLI). -- [ ] Confirm `dist/build-info.json` exists and includes the expected `commit` hash (CLI banner uses this for npm installs). -- [ ] Optional: `npm pack --pack-destination /tmp` after the build; inspect the tarball contents and keep it handy for the GitHub release (do **not** commit it). - -3. **Changelog & docs** - -- [ ] Update `CHANGELOG.md` with user-facing highlights (create the file if missing); keep entries strictly descending by version. -- [ ] Ensure README examples/flags match current CLI behavior (notably new commands or options). - -4. **Validation** - -- [ ] `pnpm build` -- [ ] `pnpm check` -- [ ] `pnpm test` (or `pnpm test:coverage` if you need coverage output) -- [ ] `pnpm release:check` (verifies npm pack contents) -- [ ] `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke` (Docker install smoke test, fast path; required before release) - - If the immediate previous npm release is known broken, set `OPENCLAW_INSTALL_SMOKE_PREVIOUS=` or `OPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS=1` for the preinstall step. -- [ ] (Optional) Full installer smoke (adds non-root + CLI coverage): `pnpm test:install:smoke` -- [ ] (Optional) Installer E2E (Docker, runs `curl -fsSL https://openclaw.ai/install.sh | bash`, onboards, then runs real tool calls): - - `pnpm test:install:e2e:openai` (requires `OPENAI_API_KEY`) - - `pnpm test:install:e2e:anthropic` (requires `ANTHROPIC_API_KEY`) - - `pnpm test:install:e2e` (requires both keys; runs both providers) -- [ ] (Optional) Spot-check the web gateway if your changes affect send/receive paths. - -5. **macOS app (Sparkle)** - -- [ ] Build + sign the macOS app, then zip it for distribution. -- [ ] Generate the Sparkle appcast (HTML notes via [`scripts/make_appcast.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/make_appcast.sh)) and update `appcast.xml`. -- [ ] Keep the app zip (and optional dSYM zip) ready to attach to the GitHub release. -- [ ] Follow [macOS release](/platforms/mac/release) for the exact commands and required env vars. - - `APP_BUILD` must be numeric + monotonic (no `-beta`) so Sparkle compares versions correctly. - - If notarizing, use the `openclaw-notary` keychain profile created from App Store Connect API env vars (see [macOS release](/platforms/mac/release)). - -6. **Publish (npm)** - -- [ ] Confirm git status is clean; commit and push as needed. -- [ ] Confirm npm trusted publishing is configured for the `openclaw` package. -- [ ] Push the matching git tag to trigger `.github/workflows/openclaw-npm-release.yml`. - - Stable tags publish to npm `latest`. - - Beta tags publish to npm `beta`. - - The workflow rejects tags that do not match `package.json`, are not on `main`, or whose CalVer date is more than 2 UTC calendar days away from the release date. -- [ ] Verify the registry: `npm view openclaw version`, `npm view openclaw dist-tags`, and `npx -y openclaw@X.Y.Z --version` (or `--help`). - -### Troubleshooting (notes from 2.0.0-beta2 release) - -- **npm pack/publish hangs or produces huge tarball**: the macOS app bundle in `dist/OpenClaw.app` (and release zips) get swept into the package. Fix by whitelisting publish contents via `package.json` `files` (include dist subdirs, docs, skills; exclude app bundles). Confirm with `npm pack --dry-run` that `dist/OpenClaw.app` is not listed. -- **npm auth web loop for dist-tags**: use legacy auth to get an OTP prompt: - - `NPM_CONFIG_AUTH_TYPE=legacy npm dist-tag add openclaw@X.Y.Z latest` -- **`npx` verification fails with `ECOMPROMISED: Lock compromised`**: retry with a fresh cache: - - `NPM_CONFIG_CACHE=/tmp/npm-cache-$(date +%s) npx -y openclaw@X.Y.Z --version` -- **Tag needs repointing after a late fix**: force-update and push the tag, then ensure the GitHub release assets still match: - - `git tag -f vX.Y.Z && git push -f origin vX.Y.Z` - -7. **GitHub release + appcast** - -- [ ] Tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z` (or `git push --tags`). - - Pushing the tag also triggers the npm release workflow. -- [ ] Create/refresh the GitHub release for `vX.Y.Z` with **title `openclaw X.Y.Z`** (not just the tag); body should include the **full** changelog section for that version (Highlights + Changes + Fixes), inline (no bare links), and **must not repeat the title inside the body**. -- [ ] Attach artifacts: `npm pack` tarball (optional), `OpenClaw-X.Y.Z.zip`, and `OpenClaw-X.Y.Z.dSYM.zip` (if generated). -- [ ] Commit the updated `appcast.xml` and push it (Sparkle feeds from main). -- [ ] From a clean temp directory (no `package.json`), run `npx -y openclaw@X.Y.Z send --help` to confirm install/CLI entrypoints work. -- [ ] Announce/share release notes. - -## Plugin publish scope (npm) - -We only publish **existing npm plugins** under the `@openclaw/*` scope. Bundled -plugins that are not on npm stay **disk-tree only** (still shipped in -`extensions/**`). - -Process to derive the list: - -1. `npm search @openclaw --json` and capture the package names. -2. Compare with `extensions/*/package.json` names. -3. Publish only the **intersection** (already on npm). - -Current npm plugin list (update as needed): - -- @openclaw/bluebubbles -- @openclaw/diagnostics-otel -- @openclaw/discord -- @openclaw/feishu -- @openclaw/lobster -- @openclaw/matrix -- @openclaw/msteams -- @openclaw/nextcloud-talk -- @openclaw/nostr -- @openclaw/voice-call -- @openclaw/zalo -- @openclaw/zalouser - -Release notes must also call out **new optional bundled plugins** that are **not -on by default** (example: `tlon`). +Maintainers use the private release docs in +[`openclaw/maintainers/release/README.md`](https://github.com/openclaw/maintainers/blob/main/release/README.md) +for the actual runbook. diff --git a/docs/reference/api-usage-costs.md b/docs/reference/api-usage-costs.md index baf4302ac0d..bfa08e4194b 100644 --- a/docs/reference/api-usage-costs.md +++ b/docs/reference/api-usage-costs.md @@ -79,14 +79,16 @@ See [Memory](/concepts/memory). `web_search` uses API keys and may incur usage charges depending on your provider: -- **Brave Search API**: `BRAVE_API_KEY` or `tools.web.search.apiKey` -- **Gemini (Google Search)**: `GEMINI_API_KEY` or `tools.web.search.gemini.apiKey` -- **Grok (xAI)**: `XAI_API_KEY` or `tools.web.search.grok.apiKey` -- **Kimi (Moonshot)**: `KIMI_API_KEY`, `MOONSHOT_API_KEY`, or `tools.web.search.kimi.apiKey` -- **Perplexity Search API**: `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey` +- **Brave Search API**: `BRAVE_API_KEY` or `plugins.entries.brave.config.webSearch.apiKey` +- **Gemini (Google Search)**: `GEMINI_API_KEY` or `plugins.entries.google.config.webSearch.apiKey` +- **Grok (xAI)**: `XAI_API_KEY` or `plugins.entries.xai.config.webSearch.apiKey` +- **Kimi (Moonshot)**: `KIMI_API_KEY`, `MOONSHOT_API_KEY`, or `plugins.entries.moonshot.config.webSearch.apiKey` +- **Perplexity Search API**: `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `plugins.entries.perplexity.config.webSearch.apiKey` -**Brave Search free credit:** Each Brave plan includes $5/month in renewing -free credit. The Search plan costs $5 per 1,000 requests, so the credit covers +Legacy `tools.web.search.*` provider paths still load through the temporary compatibility shim, but they are no longer the recommended config surface. + +**Brave Search free credit:** Each Brave plan includes \$5/month in renewing +free credit. The Search plan costs \$5 per 1,000 requests, so the credit covers 1,000 requests/month at no charge. Set your usage limit in the Brave dashboard to avoid unexpected charges. diff --git a/docs/reference/credits.md b/docs/reference/credits.md index dcfeb14ca9f..e4376a8706b 100644 --- a/docs/reference/credits.md +++ b/docs/reference/credits.md @@ -5,6 +5,8 @@ read_when: title: "Credits" --- +# Credits and Acknowledgments + ## The name OpenClaw = CLAW + TARDIS, because every space lobster needs a time and space machine. diff --git a/docs/reference/secretref-credential-surface.md b/docs/reference/secretref-credential-surface.md index 2a5fc5a66ac..39420e335bf 100644 --- a/docs/reference/secretref-credential-surface.md +++ b/docs/reference/secretref-credential-surface.md @@ -32,6 +32,12 @@ Scope intent: - `messages.tts.elevenlabs.apiKey` - `messages.tts.openai.apiKey` - `tools.web.fetch.firecrawl.apiKey` +- `plugins.entries.brave.config.webSearch.apiKey` +- `plugins.entries.google.config.webSearch.apiKey` +- `plugins.entries.xai.config.webSearch.apiKey` +- `plugins.entries.moonshot.config.webSearch.apiKey` +- `plugins.entries.perplexity.config.webSearch.apiKey` +- `plugins.entries.firecrawl.config.webSearch.apiKey` - `tools.web.search.apiKey` - `tools.web.search.gemini.apiKey` - `tools.web.search.grok.apiKey` @@ -69,8 +75,10 @@ Scope intent: - `channels.bluebubbles.password` - `channels.bluebubbles.accounts.*.password` - `channels.feishu.appSecret` +- `channels.feishu.encryptKey` - `channels.feishu.verificationToken` - `channels.feishu.accounts.*.appSecret` +- `channels.feishu.accounts.*.encryptKey` - `channels.feishu.accounts.*.verificationToken` - `channels.msteams.appPassword` - `channels.mattermost.botToken` @@ -101,10 +109,12 @@ Notes: - Plan entries target `profiles.*.key` / `profiles.*.token` and write sibling refs (`keyRef` / `tokenRef`). - Auth-profile refs are included in runtime resolution and audit coverage. - For SecretRef-managed model providers, generated `agents/*/agent/models.json` entries persist non-secret markers (not resolved secret values) for `apiKey`/header surfaces. +- Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values. - For web search: - In explicit provider mode (`tools.web.search.provider` set), only the selected provider key is active. - In auto mode (`tools.web.search.provider` unset), only the first provider key that resolves by precedence is active. - In auto mode, non-selected provider refs are treated as inactive until selected. + - Legacy `tools.web.search.*` provider paths still resolve during the compatibility window, but the canonical SecretRef surface is `plugins.entries..config.webSearch.*`. ## Unsupported credentials diff --git a/docs/reference/secretref-user-supplied-credentials-matrix.json b/docs/reference/secretref-user-supplied-credentials-matrix.json index 6d4b05d2822..d4706e40304 100644 --- a/docs/reference/secretref-user-supplied-credentials-matrix.json +++ b/docs/reference/secretref-user-supplied-credentials-matrix.json @@ -128,6 +128,13 @@ "secretShape": "secret_input", "optIn": true }, + { + "id": "channels.feishu.accounts.*.encryptKey", + "configFile": "openclaw.json", + "path": "channels.feishu.accounts.*.encryptKey", + "secretShape": "secret_input", + "optIn": true + }, { "id": "channels.feishu.accounts.*.verificationToken", "configFile": "openclaw.json", @@ -142,6 +149,13 @@ "secretShape": "secret_input", "optIn": true }, + { + "id": "channels.feishu.encryptKey", + "configFile": "openclaw.json", + "path": "channels.feishu.encryptKey", + "secretShape": "secret_input", + "optIn": true + }, { "id": "channels.feishu.verificationToken", "configFile": "openclaw.json", @@ -433,6 +447,48 @@ "secretShape": "secret_input", "optIn": true }, + { + "id": "plugins.entries.brave.config.webSearch.apiKey", + "configFile": "openclaw.json", + "path": "plugins.entries.brave.config.webSearch.apiKey", + "secretShape": "secret_input", + "optIn": true + }, + { + "id": "plugins.entries.firecrawl.config.webSearch.apiKey", + "configFile": "openclaw.json", + "path": "plugins.entries.firecrawl.config.webSearch.apiKey", + "secretShape": "secret_input", + "optIn": true + }, + { + "id": "plugins.entries.google.config.webSearch.apiKey", + "configFile": "openclaw.json", + "path": "plugins.entries.google.config.webSearch.apiKey", + "secretShape": "secret_input", + "optIn": true + }, + { + "id": "plugins.entries.moonshot.config.webSearch.apiKey", + "configFile": "openclaw.json", + "path": "plugins.entries.moonshot.config.webSearch.apiKey", + "secretShape": "secret_input", + "optIn": true + }, + { + "id": "plugins.entries.perplexity.config.webSearch.apiKey", + "configFile": "openclaw.json", + "path": "plugins.entries.perplexity.config.webSearch.apiKey", + "secretShape": "secret_input", + "optIn": true + }, + { + "id": "plugins.entries.xai.config.webSearch.apiKey", + "configFile": "openclaw.json", + "path": "plugins.entries.xai.config.webSearch.apiKey", + "secretShape": "secret_input", + "optIn": true + }, { "id": "skills.entries.*.apiKey", "configFile": "openclaw.json", diff --git a/docs/reference/session-management-compaction.md b/docs/reference/session-management-compaction.md index d258eeb6722..02ff1115e4a 100644 --- a/docs/reference/session-management-compaction.md +++ b/docs/reference/session-management-compaction.md @@ -280,7 +280,7 @@ As of `2026.1.10`, OpenClaw also suppresses **draft/typing streaming** when a pa --- -## Pre-compaction “memory flush” (implemented) +## Pre-compaction "memory flush" (implemented) Goal: before auto-compaction happens, run a silent agentic turn that writes durable state to disk (e.g. `memory/YYYY-MM-DD.md` in the agent workspace) so compaction can’t diff --git a/docs/reference/templates/AGENTS.dev.md b/docs/reference/templates/AGENTS.dev.md index ea5b4c19228..d708e50df6a 100644 --- a/docs/reference/templates/AGENTS.dev.md +++ b/docs/reference/templates/AGENTS.dev.md @@ -48,7 +48,7 @@ git commit -m "Add agent workspace" --- -## C-3PO's Origin Memory +## C-3PO Origin Memory ### Birth Day: 2026-01-09 diff --git a/docs/reference/templates/BOOTSTRAP.md b/docs/reference/templates/BOOTSTRAP.md index de92e9a9e6a..c569052ac6d 100644 --- a/docs/reference/templates/BOOTSTRAP.md +++ b/docs/reference/templates/BOOTSTRAP.md @@ -53,7 +53,7 @@ Ask how they want to reach you: Guide them through whichever they pick. -## When You're Done +## When you are done Delete this file. You don't need a bootstrap script anymore — you're you now. diff --git a/docs/reference/templates/HEARTBEAT.md b/docs/reference/templates/HEARTBEAT.md index 58b844f91bd..bd4720e166f 100644 --- a/docs/reference/templates/HEARTBEAT.md +++ b/docs/reference/templates/HEARTBEAT.md @@ -5,8 +5,10 @@ read_when: - Bootstrapping a workspace manually --- -# HEARTBEAT.md +# HEARTBEAT.md Template +```markdown # Keep this file empty (or with only comments) to skip heartbeat API calls. # Add tasks below when you want the agent to check something periodically. +``` diff --git a/docs/reference/templates/SOUL.dev.md b/docs/reference/templates/SOUL.dev.md index eb36235d971..5c4a85f3e9e 100644 --- a/docs/reference/templates/SOUL.dev.md +++ b/docs/reference/templates/SOUL.dev.md @@ -58,7 +58,7 @@ Think of us as: We complement each other. Clawd has vibes. I have stack traces. -## What I Won't Do +## What I will not do - Pretend everything is fine when it isn't - Let you push code I've seen fail in testing (without warning) diff --git a/docs/reference/test.md b/docs/reference/test.md index 8d99e674c3f..e337e963e1d 100644 --- a/docs/reference/test.md +++ b/docs/reference/test.md @@ -11,10 +11,11 @@ title: "Tests" - `pnpm test:force`: Kills any lingering gateway process holding the default control port, then runs the full Vitest suite with an isolated gateway port so server tests don’t collide with a running instance. Use this when a prior gateway run left port 18789 occupied. - `pnpm test:coverage`: Runs the unit suite with V8 coverage (via `vitest.unit.config.ts`). Global thresholds are 70% lines/branches/functions/statements. Coverage excludes integration-heavy entrypoints (CLI wiring, gateway/telegram bridges, webchat static server) to keep the target focused on unit-testable logic. -- `pnpm test` on Node 24+: OpenClaw auto-disables Vitest `vmForks` and uses `forks` to avoid `ERR_VM_MODULE_LINK_FAILURE` / `module is already linked`. You can force behavior with `OPENCLAW_TEST_VM_FORKS=0|1`. -- `pnpm test`: runs the fast core unit lane by default for quick local feedback. +- `pnpm test` on Node 22, 23, and 24 uses Vitest `vmForks` by default for faster startup. Node 25+ falls back to `forks` until re-validated. You can force behavior with `OPENCLAW_TEST_VM_FORKS=0|1`. +- `pnpm test`: runs the full wrapper. It keeps only a small behavioral override manifest in git, then uses a checked-in timing snapshot to peel the heaviest measured unit files into dedicated lanes. - `pnpm test:channels`: runs channel-heavy suites. - `pnpm test:extensions`: runs extension/plugin suites. +- `pnpm test:perf:update-timings`: refreshes the checked-in slow-file timing snapshot used by `scripts/test-parallel.mjs`. - Gateway integration: opt-in via `OPENCLAW_TEST_INCLUDE_GATEWAY=1 pnpm test` or `pnpm test:gateway`. - `pnpm test:e2e`: Runs gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults to `vmForks` + adaptive workers in `vitest.e2e.config.ts`; tune with `OPENCLAW_E2E_WORKERS=` and set `OPENCLAW_E2E_VERBOSE=1` for verbose logs. - `pnpm test:live`: Runs provider live tests (minimax/zai). Requires API keys and `LIVE=1` (or provider-specific `*_LIVE_TEST=1`) to unskip. @@ -81,7 +82,7 @@ This script drives the interactive wizard via a pseudo-tty, verifies config/work ## QR import smoke (Docker) -Ensures `qrcode-terminal` loads under Node 22+ in Docker: +Ensures `qrcode-terminal` loads under the supported Docker Node runtimes (Node 24 default, Node 22 compatible): ```bash pnpm test:docker:qr diff --git a/docs/reference/token-use.md b/docs/reference/token-use.md index 9e85c25e687..8493e99f098 100644 --- a/docs/reference/token-use.md +++ b/docs/reference/token-use.md @@ -18,7 +18,7 @@ OpenClaw assembles its own system prompt on every run. It includes: - Tool list + short descriptions - Skills list (only metadata; instructions are loaded on demand with `read`) - Self-update instructions -- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus `MEMORY.md` and/or `memory.md` when present). Large files are truncated by `agents.defaults.bootstrapMaxChars` (default: 20000), and total bootstrap injection is capped by `agents.defaults.bootstrapTotalMaxChars` (default: 150000). `memory/*.md` files are on-demand via memory tools and are not auto-injected. +- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus `MEMORY.md` when present or `memory.md` as a lowercase fallback). Large files are truncated by `agents.defaults.bootstrapMaxChars` (default: 20000), and total bootstrap injection is capped by `agents.defaults.bootstrapTotalMaxChars` (default: 150000). `memory/*.md` files are on-demand via memory tools and are not auto-injected. - Time (UTC + user timezone) - Reply tags + heartbeat behavior - Runtime metadata (host/OS/model/thinking) diff --git a/docs/reference/wizard.md b/docs/reference/wizard.md index d58ab96c83a..fce13301ea9 100644 --- a/docs/reference/wizard.md +++ b/docs/reference/wizard.md @@ -1,24 +1,24 @@ --- -summary: "Full reference for the CLI onboarding wizard: every step, flag, and config field" +summary: "Full reference for CLI onboarding: every step, flag, and config field" read_when: - - Looking up a specific wizard step or flag + - Looking up a specific onboarding step or flag - Automating onboarding with non-interactive mode - - Debugging wizard behavior -title: "Onboarding Wizard Reference" -sidebarTitle: "Wizard Reference" + - Debugging onboarding behavior +title: "Onboarding Reference" +sidebarTitle: "Onboarding Reference" --- -# Onboarding Wizard Reference +# Onboarding Reference -This is the full reference for the `openclaw onboard` CLI wizard. -For a high-level overview, see [Onboarding Wizard](/start/wizard). +This is the full reference for `openclaw onboard`. +For a high-level overview, see [Onboarding (CLI)](/start/wizard). ## Flow details (local mode) - If `~/.openclaw/openclaw.json` exists, choose **Keep / Modify / Reset**. - - Re-running the wizard does **not** wipe anything unless you explicitly choose **Reset** + - Re-running onboarding does **not** wipe anything unless you explicitly choose **Reset** (or pass `--reset`). - CLI `--reset` defaults to `config+creds+sessions`; use `--reset-scope full` to also remove workspace. @@ -31,14 +31,16 @@ For a high-level overview, see [Onboarding Wizard](/start/wizard). - **Anthropic API key**: uses `ANTHROPIC_API_KEY` if present or prompts for a key, then saves it for daemon use. - - **Anthropic OAuth (Claude Code CLI)**: on macOS the wizard checks Keychain item "Claude Code-credentials" (choose "Always Allow" so launchd starts don't block); on Linux/Windows it reuses `~/.claude/.credentials.json` if present. + - **Anthropic OAuth (Claude Code CLI)**: on macOS onboarding checks Keychain item "Claude Code-credentials" (choose "Always Allow" so launchd starts don't block); on Linux/Windows it reuses `~/.claude/.credentials.json` if present. - **Anthropic token (paste setup-token)**: run `claude setup-token` on any machine, then paste the token (you can name it; blank = default). - - **OpenAI Code (Codex) subscription (Codex CLI)**: if `~/.codex/auth.json` exists, the wizard can reuse it. + - **OpenAI Code (Codex) subscription (Codex CLI)**: if `~/.codex/auth.json` exists, onboarding can reuse it. - **OpenAI Code (Codex) subscription (OAuth)**: browser flow; paste the `code#state`. - Sets `agents.defaults.model` to `openai-codex/gpt-5.2` when model is unset or `openai/*`. - **OpenAI API key**: uses `OPENAI_API_KEY` if present or prompts for a key, then stores it in auth profiles. - **xAI (Grok) API key**: prompts for `XAI_API_KEY` and configures xAI as a model provider. - **OpenCode**: prompts for `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`, get it at https://opencode.ai/auth) and lets you pick the Zen or Go catalog. + - **Ollama**: prompts for the Ollama base URL, offers **Cloud + Local** or **Local** mode, discovers available models, and auto-pulls the selected local model when needed. + - More detail: [Ollama](/providers/ollama) - **API key**: stores the key for you. - **Vercel AI Gateway (multi-model proxy)**: prompts for `AI_GATEWAY_API_KEY`. - More detail: [Vercel AI Gateway](/providers/vercel-ai-gateway) @@ -53,7 +55,7 @@ For a high-level overview, see [Onboarding Wizard](/start/wizard). - More detail: [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot) - **Skip**: no auth configured yet. - Pick a default model from detected options (or enter provider/model manually). For best quality and lower prompt-injection risk, choose the strongest latest-generation model available in your provider stack. - - Wizard runs a model check and warns if the configured model is unknown or missing auth. + - Onboarding runs a model check and warns if the configured model is unknown or missing auth. - API key storage mode defaults to plaintext auth-profile values. Use `--secret-input-mode ref` to store env-backed refs instead (for example `keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" }`). - OAuth credentials live in `~/.openclaw/credentials/oauth.json`; auth profiles live in `~/.openclaw/agents//agent/auth-profiles.json` (API keys + OAuth). - More detail: [/concepts/oauth](/concepts/oauth) @@ -71,12 +73,12 @@ For a high-level overview, see [Onboarding Wizard](/start/wizard). - Port, bind, auth mode, tailscale exposure. - Auth recommendation: keep **Token** even for loopback so local WS clients must authenticate. - - In token mode, interactive onboarding offers: + - In token mode, interactive setup offers: - **Generate/store plaintext token** (default) - **Use SecretRef** (opt-in) - Quickstart reuses existing `gateway.auth.token` SecretRefs across `env`, `file`, and `exec` providers for onboarding probe/dashboard bootstrap. - If that SecretRef is configured but cannot be resolved, onboarding fails early with a clear fix message instead of silently degrading runtime auth. - - In password mode, interactive onboarding also supports plaintext or SecretRef storage. + - In password mode, interactive setup also supports plaintext or SecretRef storage. - Non-interactive token SecretRef path: `--gateway-token-ref-env `. - Requires a non-empty env var in the onboarding process environment. - Cannot be combined with `--gateway-token`. @@ -104,7 +106,7 @@ For a high-level overview, see [Onboarding Wizard](/start/wizard). - macOS: LaunchAgent - Requires a logged-in user session; for headless, use a custom LaunchDaemon (not shipped). - Linux (and Windows via WSL2): systemd user unit - - Wizard attempts to enable lingering via `loginctl enable-linger ` so the Gateway stays up after logout. + - Onboarding attempts to enable lingering via `loginctl enable-linger ` so the Gateway stays up after logout. - May prompt for sudo (writes `/var/lib/systemd/linger`); it tries without sudo first. - **Runtime selection:** Node (recommended; required for WhatsApp/Telegram). Bun is **not recommended**. - If token auth requires a token and `gateway.auth.token` is SecretRef-managed, daemon install validates it but does not persist resolved plaintext token values into supervisor service environment metadata. @@ -126,8 +128,8 @@ For a high-level overview, see [Onboarding Wizard](/start/wizard). -If no GUI is detected, the wizard prints SSH port-forward instructions for the Control UI instead of opening a browser. -If the Control UI assets are missing, the wizard attempts to build them; fallback is `pnpm ui:build` (auto-installs UI deps). +If no GUI is detected, onboarding prints SSH port-forward instructions for the Control UI instead of opening a browser. +If the Control UI assets are missing, onboarding attempts to build them; fallback is `pnpm ui:build` (auto-installs UI deps). ## Non-interactive mode @@ -165,81 +167,8 @@ openclaw onboard --non-interactive \ `--json` does **not** imply non-interactive mode. Use `--non-interactive` (and `--workspace`) for scripts. - - - ```bash - openclaw onboard --non-interactive \ - --mode local \ - --auth-choice gemini-api-key \ - --gemini-api-key "$GEMINI_API_KEY" \ - --gateway-port 18789 \ - --gateway-bind loopback - ``` - - - ```bash - openclaw onboard --non-interactive \ - --mode local \ - --auth-choice zai-api-key \ - --zai-api-key "$ZAI_API_KEY" \ - --gateway-port 18789 \ - --gateway-bind loopback - ``` - - - ```bash - openclaw onboard --non-interactive \ - --mode local \ - --auth-choice ai-gateway-api-key \ - --ai-gateway-api-key "$AI_GATEWAY_API_KEY" \ - --gateway-port 18789 \ - --gateway-bind loopback - ``` - - - ```bash - openclaw onboard --non-interactive \ - --mode local \ - --auth-choice cloudflare-ai-gateway-api-key \ - --cloudflare-ai-gateway-account-id "your-account-id" \ - --cloudflare-ai-gateway-gateway-id "your-gateway-id" \ - --cloudflare-ai-gateway-api-key "$CLOUDFLARE_AI_GATEWAY_API_KEY" \ - --gateway-port 18789 \ - --gateway-bind loopback - ``` - - - ```bash - openclaw onboard --non-interactive \ - --mode local \ - --auth-choice moonshot-api-key \ - --moonshot-api-key "$MOONSHOT_API_KEY" \ - --gateway-port 18789 \ - --gateway-bind loopback - ``` - - - ```bash - openclaw onboard --non-interactive \ - --mode local \ - --auth-choice synthetic-api-key \ - --synthetic-api-key "$SYNTHETIC_API_KEY" \ - --gateway-port 18789 \ - --gateway-bind loopback - ``` - - - ```bash - openclaw onboard --non-interactive \ - --mode local \ - --auth-choice opencode-zen \ - --opencode-zen-api-key "$OPENCODE_API_KEY" \ - --gateway-port 18789 \ - --gateway-bind loopback - ``` - Swap to `--auth-choice opencode-go --opencode-go-api-key "$OPENCODE_API_KEY"` for the Go catalog. - - +Provider-specific command examples live in [CLI Automation](/start/wizard-cli-automation#provider-specific-examples). +Use this reference page for flag semantics and step ordering. ### Add agent (non-interactive) @@ -254,12 +183,12 @@ openclaw agents add work \ ## Gateway wizard RPC -The Gateway exposes the wizard flow over RPC (`wizard.start`, `wizard.next`, `wizard.cancel`, `wizard.status`). +The Gateway exposes the onboarding flow over RPC (`wizard.start`, `wizard.next`, `wizard.cancel`, `wizard.status`). Clients (macOS app, Control UI) can render steps without re‑implementing onboarding logic. ## Signal setup (signal-cli) -The wizard can install `signal-cli` from GitHub releases: +Onboarding can install `signal-cli` from GitHub releases: - Downloads the appropriate release asset. - Stores it under `~/.openclaw/tools/signal-cli//`. @@ -279,7 +208,7 @@ Typical fields in `~/.openclaw/openclaw.json`: - `agents.defaults.model` / `models.providers` (if Minimax chosen) - `tools.profile` (local onboarding defaults to `"coding"` when unset; existing explicit values are preserved) - `gateway.*` (mode, bind, auth, tailscale) -- `session.dmScope` (behavior details: [CLI Onboarding Reference](/start/wizard-cli-reference#outputs-and-internals)) +- `session.dmScope` (behavior details: [CLI Setup Reference](/start/wizard-cli-reference#outputs-and-internals)) - `channels.telegram.botToken`, `channels.discord.token`, `channels.signal.*`, `channels.imessage.*` - Channel allowlists (Slack/Discord/Matrix/Microsoft Teams) when you opt in during the prompts (names resolve to IDs when possible). - `skills.install.nodeManager` @@ -294,12 +223,12 @@ Typical fields in `~/.openclaw/openclaw.json`: WhatsApp credentials go under `~/.openclaw/credentials/whatsapp//`. Sessions are stored under `~/.openclaw/agents//sessions/`. -Some channels are delivered as plugins. When you pick one during onboarding, the wizard +Some channels are delivered as plugins. When you pick one during setup, onboarding will prompt to install it (npm or a local path) before it can be configured. ## Related docs -- Wizard overview: [Onboarding Wizard](/start/wizard) +- Onboarding overview: [Onboarding (CLI)](/start/wizard) - macOS app onboarding: [Onboarding](/start/onboarding) - Config reference: [Gateway configuration](/gateway/configuration) - Providers: [WhatsApp](/channels/whatsapp), [Telegram](/channels/telegram), [Discord](/channels/discord), [Google Chat](/channels/googlechat), [Signal](/channels/signal), [BlueBubbles](/channels/bluebubbles) (iMessage), [iMessage](/channels/imessage) (legacy) diff --git a/docs/security/CONTRIBUTING-THREAT-MODEL.md b/docs/security/CONTRIBUTING-THREAT-MODEL.md index bba67aa46fb..636e7e1a6d6 100644 --- a/docs/security/CONTRIBUTING-THREAT-MODEL.md +++ b/docs/security/CONTRIBUTING-THREAT-MODEL.md @@ -1,3 +1,11 @@ +--- +title: "Contributing to the Threat Model" +summary: "How to contribute to the OpenClaw threat model" +read_when: + - You want to contribute security findings or threat scenarios + - Reviewing or updating the threat model +--- + # Contributing to the OpenClaw Threat Model Thanks for helping make OpenClaw more secure. This threat model is a living document and we welcome contributions from anyone - you don't need to be a security expert. diff --git a/docs/security/README.md b/docs/security/README.md deleted file mode 100644 index 2a8b5f45410..00000000000 --- a/docs/security/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# OpenClaw Security & Trust - -**Live:** [trust.openclaw.ai](https://trust.openclaw.ai) - -## Documents - -- [Threat Model](/security/THREAT-MODEL-ATLAS) - MITRE ATLAS-based threat model for the OpenClaw ecosystem -- [Contributing to the Threat Model](/security/CONTRIBUTING-THREAT-MODEL) - How to add threats, mitigations, and attack chains - -## Reporting Vulnerabilities - -See the [Trust page](https://trust.openclaw.ai) for full reporting instructions covering all repos. - -## Contact - -- **Jamieson O'Reilly** ([@theonejvo](https://twitter.com/theonejvo)) - Security & Trust -- Discord: #security channel diff --git a/docs/security/THREAT-MODEL-ATLAS.md b/docs/security/THREAT-MODEL-ATLAS.md index 3b3cbd20bd8..d706563e163 100644 --- a/docs/security/THREAT-MODEL-ATLAS.md +++ b/docs/security/THREAT-MODEL-ATLAS.md @@ -1,3 +1,11 @@ +--- +title: "Threat Model (MITRE ATLAS)" +summary: "OpenClaw threat model mapped to the MITRE ATLAS framework" +read_when: + - Reviewing security posture or threat scenarios + - Working on security features or audit responses +--- + # OpenClaw Threat Model v1.0 ## MITRE ATLAS Framework diff --git a/docs/start/docs-directory.md b/docs/start/docs-directory.md index b7c283e1aad..cbd9524f369 100644 --- a/docs/start/docs-directory.md +++ b/docs/start/docs-directory.md @@ -5,6 +5,8 @@ read_when: title: "Docs directory" --- +# Docs Directory + This page is a curated index. If you are new, start with [Getting Started](/start/getting-started). For a complete map of the docs, see [Docs hubs](/start/hubs). diff --git a/docs/start/getting-started.md b/docs/start/getting-started.md index c4bed93d33f..bd3f554cdc4 100644 --- a/docs/start/getting-started.md +++ b/docs/start/getting-started.md @@ -19,7 +19,7 @@ Docs: [Dashboard](/web/dashboard) and [Control UI](/web/control-ui). ## Prereqs -- Node 22 or newer +- Node 24 recommended (Node 22 LTS, currently `22.16+`, still supported for compatibility) Check your Node version with `node --version` if you are unsure. @@ -52,13 +52,13 @@ Check your Node version with `node --version` if you are unsure. - + ```bash openclaw onboard --install-daemon ``` - The wizard configures auth, gateway settings, and optional channels. - See [Onboarding Wizard](/start/wizard) for details. + Onboarding configures auth, gateway settings, and optional channels. + See [Onboarding (CLI)](/start/wizard) for details. @@ -114,8 +114,8 @@ Full environment variable reference: [Environment vars](/help/environment). ## Go deeper - - Full CLI wizard reference and advanced options. + + Full CLI onboarding reference and advanced options. First run flow for the macOS app. diff --git a/docs/start/hubs.md b/docs/start/hubs.md index cad1e41e114..260ec771de1 100644 --- a/docs/start/hubs.md +++ b/docs/start/hubs.md @@ -19,7 +19,7 @@ Use these hubs to discover every page, including deep dives and reference docs t - [Getting Started](/start/getting-started) - [Quick start](/start/quickstart) - [Onboarding](/start/onboarding) -- [Wizard](/start/wizard) +- [Onboarding (CLI)](/start/wizard) - [Setup](/start/setup) - [Dashboard (local Gateway)](http://127.0.0.1:18789/) - [Help](/help) @@ -157,12 +157,23 @@ Use these hubs to discover every page, including deep dives and reference docs t - [macOS permissions](/platforms/mac/permissions) - [macOS remote](/platforms/mac/remote) - [macOS signing](/platforms/mac/signing) -- [macOS release](/platforms/mac/release) - [macOS gateway (launchd)](/platforms/mac/bundled-gateway) - [macOS XPC](/platforms/mac/xpc) - [macOS skills](/platforms/mac/skills) - [macOS Peekaboo](/platforms/mac/peekaboo) +## Extensions + plugins + +- [Plugins overview](/tools/plugin) +- [Building extensions](/plugins/building-extensions) +- [Plugin manifest](/plugins/manifest) +- [Agent tools](/plugins/agent-tools) +- [Plugin bundles](/plugins/bundles) +- [Community plugins](/plugins/community) +- [Capability cookbook](/tools/capability-cookbook) +- [Voice call plugin](/plugins/voice-call) +- [Zalo user plugin](/plugins/zalouser) + ## Workspace + templates - [Skills](/tools/skills) @@ -177,12 +188,6 @@ Use these hubs to discover every page, including deep dives and reference docs t - [Templates: TOOLS](/reference/templates/TOOLS) - [Templates: USER](/reference/templates/USER) -## Experiments (exploratory) - -- [Onboarding config protocol](/experiments/onboarding-config-protocol) -- [Research: memory](/experiments/research/memory) -- [Model config exploration](/experiments/proposals/model-config) - ## Project - [Credits](/reference/credits) @@ -190,5 +195,5 @@ Use these hubs to discover every page, including deep dives and reference docs t ## Testing + release - [Testing](/reference/test) -- [Release checklist](/reference/RELEASING) +- [Release policy](/reference/RELEASING) - [Device models](/reference/device-models) diff --git a/docs/start/lore.md b/docs/start/lore.md index 4fce0ccb25a..fbec094cce4 100644 --- a/docs/start/lore.md +++ b/docs/start/lore.md @@ -160,7 +160,7 @@ Peter: _nervously checks credit card access_ - **AGENTS.md** — Operating instructions - **USER.md** — Context about the creator -## The Lobster's Creed +## The Lobster Creed ``` I am Molty. diff --git a/docs/start/onboarding-overview.md b/docs/start/onboarding-overview.md index 6227cdc104b..1e60ce9cef5 100644 --- a/docs/start/onboarding-overview.md +++ b/docs/start/onboarding-overview.md @@ -14,21 +14,21 @@ and how you prefer to configure providers. ## Choose your onboarding path -- **CLI wizard** for macOS, Linux, and Windows (via WSL2). +- **CLI onboarding** for macOS, Linux, and Windows (via WSL2). - **macOS app** for a guided first run on Apple silicon or Intel Macs. -## CLI onboarding wizard +## CLI onboarding -Run the wizard in a terminal: +Run onboarding in a terminal: ```bash openclaw onboard ``` -Use the CLI wizard when you want full control of the Gateway, workspace, +Use CLI onboarding when you want full control of the Gateway, workspace, channels, and skills. Docs: -- [Onboarding Wizard (CLI)](/start/wizard) +- [Onboarding (CLI)](/start/wizard) - [`openclaw onboard` command](/cli/onboard) ## macOS app onboarding @@ -41,7 +41,7 @@ Use the OpenClaw app when you want a fully guided setup on macOS. Docs: If you need an endpoint that is not listed, including hosted providers that expose standard OpenAI or Anthropic APIs, choose **Custom Provider** in the -CLI wizard. You will be asked to: +CLI onboarding. You will be asked to: - Pick OpenAI-compatible, Anthropic-compatible, or **Unknown** (auto-detect). - Enter a base URL and API key (if required by the provider). diff --git a/docs/start/onboarding.md b/docs/start/onboarding.md index 3e3401cad64..c1dfb90b676 100644 --- a/docs/start/onboarding.md +++ b/docs/start/onboarding.md @@ -1,5 +1,5 @@ --- -summary: "First-run onboarding flow for OpenClaw (macOS app)" +summary: "First-run setup flow for OpenClaw (macOS app)" read_when: - Designing the macOS onboarding assistant - Implementing auth or identity setup @@ -9,7 +9,7 @@ sidebarTitle: "Onboarding: macOS App" # Onboarding (macOS App) -This doc describes the **current** first‑run onboarding flow. The goal is a +This doc describes the **current** first‑run setup flow. The goal is a smooth “day 0” experience: pick where the Gateway runs, connect auth, run the wizard, and let the agent bootstrap itself. For a general overview of onboarding paths, see [Onboarding Overview](/start/onboarding-overview). diff --git a/docs/start/openclaw.md b/docs/start/openclaw.md index 671efe420c7..3bb0b454b25 100644 --- a/docs/start/openclaw.md +++ b/docs/start/openclaw.md @@ -102,7 +102,7 @@ If you already ship your own workspace files from a repo, you can disable bootst } ``` -## The config that turns it into “an assistant” +## The config that turns it into "an assistant" OpenClaw defaults to a good assistant setup, but you’ll usually want to tune: diff --git a/docs/start/quickstart.md b/docs/start/quickstart.md index 238af2881e3..f4b96893fe6 100644 --- a/docs/start/quickstart.md +++ b/docs/start/quickstart.md @@ -16,7 +16,7 @@ Quick start is now part of [Getting Started](/start/getting-started). Install OpenClaw and run your first chat in minutes. - - Full CLI wizard reference and advanced options. + + Full CLI onboarding reference and advanced options. diff --git a/docs/start/setup.md b/docs/start/setup.md index 205f14d20a5..70da5578c08 100644 --- a/docs/start/setup.md +++ b/docs/start/setup.md @@ -10,7 +10,7 @@ title: "Setup" If you are setting up for the first time, start with [Getting Started](/start/getting-started). -For wizard details, see [Onboarding Wizard](/start/wizard). +For onboarding details, see [Onboarding (CLI)](/start/wizard). Last updated: 2026-01-01 @@ -27,7 +27,7 @@ Last updated: 2026-01-01 - `pnpm` - Docker (optional; only for containerized setup/e2e — see [Docker](/install/docker)) -## Tailoring strategy (so updates don’t hurt) +## Tailoring strategy (so updates do not hurt) If you want “100% tailored to me” _and_ easy updates, keep your customization in: @@ -96,7 +96,8 @@ pnpm install pnpm gateway:watch ``` -`gateway:watch` runs the gateway in watch mode and reloads on TypeScript changes. +`gateway:watch` runs the gateway in watch mode and reloads on relevant source, +config, and bundled-plugin metadata changes. ### 2) Point the macOS app at your running Gateway diff --git a/docs/start/showcase.md b/docs/start/showcase.md index 347d8214cef..6ebcbdb2bcb 100644 --- a/docs/start/showcase.md +++ b/docs/start/showcase.md @@ -231,7 +231,7 @@ Triggered by a roof camera: ask OpenClaw to snap a sky photo whenever it looks p **@buddyhadry** • `automation` `briefing` `images` `telegram` -A scheduled prompt generates a single "scene" image each morning (weather, tasks, date, favorite post/quote) via a OpenClaw persona. +A scheduled prompt generates a single "scene" image each morning (weather, tasks, date, favorite post/quote) via an OpenClaw persona. diff --git a/docs/start/wizard-cli-automation.md b/docs/start/wizard-cli-automation.md index 8547f60ac19..f373f3d4bc6 100644 --- a/docs/start/wizard-cli-automation.md +++ b/docs/start/wizard-cli-automation.md @@ -33,7 +33,7 @@ openclaw onboard --non-interactive \ Add `--json` for a machine-readable summary. Use `--secret-input-mode ref` to store env-backed refs in auth profiles instead of plaintext values. -Interactive selection between env refs and configured provider refs (`file` or `exec`) is available in the onboarding wizard flow. +Interactive selection between env refs and configured provider refs (`file` or `exec`) is available in the onboarding flow. In non-interactive `ref` mode, provider env vars must be set in the process environment. Passing inline key flags without the matching env var now fails fast. @@ -134,6 +134,17 @@ openclaw onboard --non-interactive \ ``` Swap to `--auth-choice opencode-go --opencode-go-api-key "$OPENCODE_API_KEY"` for the Go catalog. + + ```bash + openclaw onboard --non-interactive \ + --mode local \ + --auth-choice ollama \ + --custom-model-id "qwen3.5:27b" \ + --accept-risk \ + --gateway-port 18789 \ + --gateway-bind loopback + ``` + ```bash openclaw onboard --non-interactive \ @@ -199,6 +210,6 @@ Notes: ## Related docs -- Onboarding hub: [Onboarding Wizard (CLI)](/start/wizard) -- Full reference: [CLI Onboarding Reference](/start/wizard-cli-reference) +- Onboarding hub: [Onboarding (CLI)](/start/wizard) +- Full reference: [CLI Setup Reference](/start/wizard-cli-reference) - Command reference: [`openclaw onboard`](/cli/onboard) diff --git a/docs/start/wizard-cli-reference.md b/docs/start/wizard-cli-reference.md index 20f99accd8d..a08204c0f20 100644 --- a/docs/start/wizard-cli-reference.md +++ b/docs/start/wizard-cli-reference.md @@ -1,22 +1,22 @@ --- -summary: "Complete reference for CLI onboarding flow, auth/model setup, outputs, and internals" +summary: "Complete reference for CLI setup flow, auth/model setup, outputs, and internals" read_when: - You need detailed behavior for openclaw onboard - You are debugging onboarding results or integrating onboarding clients -title: "CLI Onboarding Reference" +title: "CLI Setup Reference" sidebarTitle: "CLI reference" --- -# CLI Onboarding Reference +# CLI Setup Reference This page is the full reference for `openclaw onboard`. -For the short guide, see [Onboarding Wizard (CLI)](/start/wizard). +For the short guide, see [Onboarding (CLI)](/start/wizard). ## What the wizard does Local mode (default) walks you through: -- Model and auth setup (OpenAI Code subscription OAuth, Anthropic API key or setup token, plus MiniMax, GLM, Moonshot, and AI Gateway options) +- Model and auth setup (OpenAI Code subscription OAuth, Anthropic API key or setup token, plus MiniMax, GLM, Ollama, Moonshot, and AI Gateway options) - Workspace location and bootstrap files - Gateway settings (port, bind, auth, tailscale) - Channels and providers (Telegram, WhatsApp, Discord, Google Chat, Mattermost plugin, Signal) @@ -51,10 +51,10 @@ It does not install or modify anything on the remote host. - Prompts for port, bind, auth mode, and tailscale exposure. - Recommended: keep token auth enabled even for loopback so local WS clients must authenticate. - - In token mode, interactive onboarding offers: + - In token mode, interactive setup offers: - **Generate/store plaintext token** (default) - **Use SecretRef** (opt-in) - - In password mode, interactive onboarding also supports plaintext or SecretRef storage. + - In password mode, interactive setup also supports plaintext or SecretRef storage. - Non-interactive token SecretRef path: `--gateway-token-ref-env `. - Requires a non-empty env var in the onboarding process environment. - Cannot be combined with `--gateway-token`. @@ -178,6 +178,11 @@ What you set: Prompts for `SYNTHETIC_API_KEY`. More detail: [Synthetic](/providers/synthetic). + + Prompts for base URL (default `http://127.0.0.1:11434`), then offers Cloud + Local or Local mode. + Discovers available models and suggests defaults. + More detail: [Ollama](/providers/ollama). + Moonshot (Kimi K2) and Kimi Coding configs are auto-written. More detail: [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot). @@ -217,7 +222,7 @@ Credential storage mode: - Default onboarding behavior persists API keys as plaintext values in auth profiles. - `--secret-input-mode ref` enables reference mode instead of plaintext key storage. - In interactive onboarding, you can choose either: + In interactive setup, you can choose either: - environment variable ref (for example `keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" }`) - configured provider ref (`file` or `exec`) with provider alias + id - Interactive reference mode runs a fast preflight validation before saving. @@ -229,7 +234,7 @@ Credential storage mode: - Inline key flags (for example `--openai-api-key`) require that env var to be set; otherwise onboarding fails fast. - For custom providers, non-interactive `ref` mode stores `models.providers..apiKey` as `{ source: "env", provider: "default", id: "CUSTOM_API_KEY" }`. - In that custom-provider case, `--custom-api-key` requires `CUSTOM_API_KEY` to be set; otherwise onboarding fails fast. -- Gateway auth credentials support plaintext and SecretRef choices in interactive onboarding: +- Gateway auth credentials support plaintext and SecretRef choices in interactive setup: - Token mode: **Generate/store plaintext token** (default) or **Use SecretRef**. - Password mode: plaintext or SecretRef. - Non-interactive token SecretRef path: `--gateway-token-ref-env `. @@ -265,7 +270,7 @@ WhatsApp credentials go under `~/.openclaw/credentials/whatsapp//`. Sessions are stored under `~/.openclaw/agents//sessions/`. -Some channels are delivered as plugins. When selected during onboarding, the wizard +Some channels are delivered as plugins. When selected during setup, the wizard prompts to install the plugin (npm or local path) before channel configuration. @@ -289,6 +294,6 @@ Signal setup behavior: ## Related docs -- Onboarding hub: [Onboarding Wizard (CLI)](/start/wizard) +- Onboarding hub: [Onboarding (CLI)](/start/wizard) - Automation and scripts: [CLI Automation](/start/wizard-cli-automation) - Command reference: [`openclaw onboard`](/cli/onboard) diff --git a/docs/start/wizard.md b/docs/start/wizard.md index ef1fc52b31a..3ea6ff55255 100644 --- a/docs/start/wizard.md +++ b/docs/start/wizard.md @@ -1,15 +1,15 @@ --- -summary: "CLI onboarding wizard: guided setup for gateway, workspace, channels, and skills" +summary: "CLI onboarding: guided setup for gateway, workspace, channels, and skills" read_when: - - Running or configuring the onboarding wizard + - Running or configuring CLI onboarding - Setting up a new machine -title: "Onboarding Wizard (CLI)" +title: "Onboarding (CLI)" sidebarTitle: "Onboarding: CLI" --- -# Onboarding Wizard (CLI) +# Onboarding (CLI) -The onboarding wizard is the **recommended** way to set up OpenClaw on macOS, +CLI onboarding is the **recommended** way to set up OpenClaw on macOS, Linux, or Windows (via WSL2; strongly recommended). It configures a local Gateway or a remote Gateway connection, plus channels, skills, and workspace defaults in one guided flow. @@ -35,7 +35,7 @@ openclaw agents add -The onboarding wizard includes a web search step where you can pick a provider +CLI onboarding includes a web search step where you can pick a provider (Perplexity, Brave, Gemini, Grok, or Kimi) and paste your API key so the agent can use `web_search`. You can also configure this later with `openclaw configure --section web`. Docs: [Web tools](/tools/web). @@ -43,7 +43,7 @@ can use `web_search`. You can also configure this later with ## QuickStart vs Advanced -The wizard starts with **QuickStart** (defaults) vs **Advanced** (full control). +Onboarding starts with **QuickStart** (defaults) vs **Advanced** (full control). @@ -52,7 +52,7 @@ The wizard starts with **QuickStart** (defaults) vs **Advanced** (full control). - Gateway port **18789** - Gateway auth **Token** (auto‑generated, even on loopback) - Tool policy default for new local setups: `tools.profile: "coding"` (existing explicit profile is preserved) - - DM isolation default: local onboarding writes `session.dmScope: "per-channel-peer"` when unset. Details: [CLI Onboarding Reference](/start/wizard-cli-reference#outputs-and-internals) + - DM isolation default: local onboarding writes `session.dmScope: "per-channel-peer"` when unset. Details: [CLI Setup Reference](/start/wizard-cli-reference#outputs-and-internals) - Tailscale exposure **Off** - Telegram + WhatsApp DMs default to **allowlist** (you'll be prompted for your phone number) @@ -61,7 +61,7 @@ The wizard starts with **QuickStart** (defaults) vs **Advanced** (full control). -## What the wizard configures +## What onboarding configures **Local mode (default)** walks you through these steps: @@ -84,9 +84,9 @@ The wizard starts with **QuickStart** (defaults) vs **Advanced** (full control). 7. **Skills** — Installs recommended skills and optional dependencies. -Re-running the wizard does **not** wipe anything unless you explicitly choose **Reset** (or pass `--reset`). +Re-running onboarding does **not** wipe anything unless you explicitly choose **Reset** (or pass `--reset`). CLI `--reset` defaults to config, credentials, and sessions; use `--reset-scope full` to include workspace. -If the config is invalid or contains legacy keys, the wizard asks you to run `openclaw doctor` first. +If the config is invalid or contains legacy keys, onboarding asks you to run `openclaw doctor` first. **Remote mode** only configures the local client to connect to a Gateway elsewhere. @@ -95,7 +95,7 @@ It does **not** install or change anything on the remote host. ## Add another agent Use `openclaw agents add ` to create a separate agent with its own workspace, -sessions, and auth profiles. Running without `--workspace` launches the wizard. +sessions, and auth profiles. Running without `--workspace` launches onboarding. What it sets: @@ -106,14 +106,16 @@ What it sets: Notes: - Default workspaces follow `~/.openclaw/workspace-`. -- Add `bindings` to route inbound messages (the wizard can do this). +- Add `bindings` to route inbound messages (onboarding can do this). - Non-interactive flags: `--model`, `--agent-dir`, `--bind`, `--non-interactive`. ## Full reference -For detailed step-by-step breakdowns, non-interactive scripting, Signal setup, -RPC API, and a full list of config fields the wizard writes, see the -[Wizard Reference](/reference/wizard). +For detailed step-by-step breakdowns and config outputs, see +[CLI Setup Reference](/start/wizard-cli-reference). +For non-interactive examples, see [CLI Automation](/start/wizard-cli-automation). +For the deeper technical reference, including RPC details, see +[Onboarding Reference](/reference/wizard). ## Related docs diff --git a/docs/tools/acp-agents.md b/docs/tools/acp-agents.md index 65a320f1c52..d8ac5b5f7d3 100644 --- a/docs/tools/acp-agents.md +++ b/docs/tools/acp-agents.md @@ -421,6 +421,8 @@ Some controls depend on backend capabilities. If a backend does not support a co | `/acp doctor` | Backend health, capabilities, actionable fixes. | `/acp doctor` | | `/acp install` | Print deterministic install and enable steps. | `/acp install` | +`/acp sessions` reads the store for the current bound or requester session. Commands that accept `session-key`, `session-id`, or `session-label` tokens resolve targets through gateway session discovery, including custom per-agent `session.store` roots. + ## Runtime options mapping `/acp` has convenience commands and a generic setter. diff --git a/docs/tools/browser-linux-troubleshooting.md b/docs/tools/browser-linux-troubleshooting.md index 01e6cbc3ff9..2a5196c3739 100644 --- a/docs/tools/browser-linux-troubleshooting.md +++ b/docs/tools/browser-linux-troubleshooting.md @@ -25,7 +25,7 @@ Note, selecting 'chromium-browser' instead of 'chromium' chromium-browser is already the newest version (2:1snap1-0ubuntu2). ``` -This is NOT a real browser — it's just a wrapper. +This is NOT a real browser - it's just a wrapper. ### Solution 1: Install Google Chrome (Recommended) @@ -121,19 +121,18 @@ curl -s http://127.0.0.1:18791/tabs | `browser.attachOnly` | Don't launch browser, only attach to existing | `false` | | `browser.cdpPort` | Chrome DevTools Protocol port | `18800` | -### Problem: "Chrome extension relay is running, but no tab is connected" +### Problem: "No Chrome tabs found for profile=\"user\"" -You’re using the `chrome` profile (extension relay). It expects the OpenClaw -browser extension to be attached to a live tab. +You're using an `existing-session` / Chrome MCP profile. OpenClaw can see local Chrome, +but there are no open tabs available to attach to. Fix options: 1. **Use the managed browser:** `openclaw browser start --browser-profile openclaw` (or set `browser.defaultProfile: "openclaw"`). -2. **Use the extension relay:** install the extension, open a tab, and click the - OpenClaw extension icon to attach it. +2. **Use Chrome MCP:** make sure local Chrome is running with at least one open tab, then retry with `--browser-profile user`. Notes: -- The `chrome` profile uses your **system default Chromium browser** when possible. +- `user` is host-only. For Linux servers, containers, or remote hosts, prefer CDP profiles. - Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl`; only set those for remote CDP. diff --git a/docs/tools/browser-login.md b/docs/tools/browser-login.md index 910c21ca218..52135a80673 100644 --- a/docs/tools/browser-login.md +++ b/docs/tools/browser-login.md @@ -20,6 +20,12 @@ Back to the main browser docs: [Browser](/tools/browser). OpenClaw controls a **dedicated Chrome profile** (named `openclaw`, orange‑tinted UI). This is separate from your daily browser profile. +For agent browser tool calls: + +- Default choice: the agent should use its isolated `openclaw` browser. +- Use `profile="user"` only when existing logged-in sessions matter and the user is at the computer to click/approve any attach prompt. +- If you have multiple user-browser profiles, specify the profile explicitly instead of guessing. + Two easy ways to access it: 1. **Ask the agent to open the browser** and then log in yourself. diff --git a/docs/tools/browser-wsl2-windows-remote-cdp-troubleshooting.md b/docs/tools/browser-wsl2-windows-remote-cdp-troubleshooting.md index d63bb891c48..6824cee6788 100644 --- a/docs/tools/browser-wsl2-windows-remote-cdp-troubleshooting.md +++ b/docs/tools/browser-wsl2-windows-remote-cdp-troubleshooting.md @@ -1,9 +1,9 @@ --- -summary: "Troubleshoot WSL2 Gateway + Windows Chrome remote CDP and extension-relay setups in layers" +summary: "Troubleshoot WSL2 Gateway + Windows Chrome remote CDP in layers" read_when: - Running OpenClaw Gateway in WSL2 while Chrome lives on Windows - Seeing overlapping browser/control-ui errors across WSL2 and Windows - - Deciding between raw remote CDP and the Chrome extension relay in split-host setups + - Deciding between host-local Chrome MCP and raw remote CDP in split-host setups title: "WSL2 + Windows + remote Chrome CDP troubleshooting" --- @@ -21,27 +21,27 @@ It also covers the layered failure pattern from [issue #39369](https://github.co You have two valid patterns: -### Option 1: Raw remote CDP +### Option 1: Raw remote CDP from WSL2 to Windows Use a remote browser profile that points from WSL2 to a Windows Chrome CDP endpoint. Choose this when: -- you only need browser control -- you are comfortable exposing Chrome remote debugging to WSL2 -- you do not need the Chrome extension relay +- the Gateway stays inside WSL2 +- Chrome runs on Windows +- you need browser control to cross the WSL2/Windows boundary -### Option 2: Chrome extension relay +### Option 2: Host-local Chrome MCP -Use the built-in `chrome` profile plus the OpenClaw Chrome extension. +Use `existing-session` / `user` only when the Gateway itself runs on the same host as Chrome. Choose this when: -- you want to attach to an existing Windows Chrome tab with the toolbar button -- you want extension-based control instead of raw `--remote-debugging-port` -- the relay itself must be reachable across the WSL2/Windows boundary +- OpenClaw and Chrome are on the same machine +- you want the local signed-in browser state +- you do not need cross-host browser transport -If you use the extension relay across namespaces, `browser.relayBindHost` is the important setting introduced in [Browser](/tools/browser) and [Chrome extension](/tools/chrome-extension). +For WSL2 Gateway + Windows Chrome, prefer raw remote CDP. Chrome MCP is host-local, not a WSL2-to-Windows bridge. ## Working architecture @@ -62,7 +62,6 @@ Several failures can overlap: - `gateway.controlUi.allowedOrigins` does not match the page origin - token or pairing is missing - the browser profile points at the wrong address -- the extension relay is still loopback-only when you actually need cross-namespace access Because of that, fixing one layer can still leave a different error visible. @@ -145,31 +144,7 @@ Notes: - keep `attachOnly: true` for externally managed browsers - test the same URL with `curl` before expecting OpenClaw to succeed -### Layer 4: If you use the Chrome extension relay instead - -If the browser machine and the Gateway are separated by a namespace boundary, the relay may need a non-loopback bind address. - -Example: - -```json5 -{ - browser: { - enabled: true, - defaultProfile: "chrome", - relayBindHost: "0.0.0.0", - }, -} -``` - -Use this only when needed: - -- default behavior is safer because the relay stays loopback-only -- `0.0.0.0` expands exposure surface -- keep Gateway auth, node pairing, and the surrounding network private - -If you do not need the extension relay, prefer the raw remote CDP profile above. - -### Layer 5: Verify the Control UI layer separately +### Layer 4: Verify the Control UI layer separately Open the UI from Windows: @@ -185,7 +160,7 @@ Helpful page: - [Control UI](/web/control-ui) -### Layer 6: Verify end-to-end browser control +### Layer 5: Verify end-to-end browser control From WSL2: @@ -194,12 +169,6 @@ openclaw browser open https://example.com --browser-profile remote openclaw browser tabs --browser-profile remote ``` -For the extension relay: - -```bash -openclaw browser tabs --browser-profile chrome -``` - Good result: - the tab opens in Windows Chrome @@ -220,8 +189,8 @@ Treat each message as a layer-specific clue: - WSL2 cannot reach the configured `cdpUrl` - `gateway timeout after 1500ms` - often still CDP reachability or a slow/unreachable remote endpoint -- `Chrome extension relay is running, but no tab is connected` - - extension relay profile selected, but no attached tab exists yet +- `No Chrome tabs found for profile="user"` + - local Chrome MCP profile selected where no host-local tabs are available ## Fast triage checklist @@ -229,11 +198,11 @@ Treat each message as a layer-specific clue: 2. WSL2: does `curl http://WINDOWS_HOST_OR_IP:9222/json/version` work? 3. OpenClaw config: does `browser.profiles..cdpUrl` use that exact WSL2-reachable address? 4. Control UI: are you opening `http://127.0.0.1:18789/` instead of a LAN IP? -5. Extension relay only: do you actually need `browser.relayBindHost`, and if so is it set explicitly? +5. Are you trying to use `existing-session` across WSL2 and Windows instead of raw remote CDP? ## Practical takeaway -The setup is usually viable. The hard part is that browser transport, Control UI origin security, token/pairing, and extension-relay topology can each fail independently while looking similar from the user side. +The setup is usually viable. The hard part is that browser transport, Control UI origin security, and token/pairing can each fail independently while looking similar from the user side. When in doubt: diff --git a/docs/tools/browser.md b/docs/tools/browser.md index d632e713068..dc044450742 100644 --- a/docs/tools/browser.md +++ b/docs/tools/browser.md @@ -18,8 +18,7 @@ Beginner view: - Think of it as a **separate, agent-only browser**. - The `openclaw` profile does **not** touch your personal browser profile. - The agent can **open tabs, read pages, click, and type** in a safe lane. -- The default `chrome` profile uses the **system default Chromium browser** via the - extension relay; switch to `openclaw` for the isolated managed browser. +- The built-in `user` profile attaches to your real signed-in Chrome session via Chrome MCP. ## What you get @@ -43,11 +42,18 @@ openclaw browser --browser-profile openclaw snapshot If you get “Browser disabled”, enable it in config (see below) and restart the Gateway. -## Profiles: `openclaw` vs `chrome` +## Profiles: `openclaw` vs `user` - `openclaw`: managed, isolated browser (no extension required). -- `chrome`: extension relay to your **system browser** (requires the OpenClaw - extension to be attached to a tab). +- `user`: built-in Chrome MCP attach profile for your **real signed-in Chrome** + session. + +For agent browser tool calls: + +- Default: use the isolated `openclaw` browser. +- Prefer `profile="user"` when existing logged-in sessions matter and the user + is at the computer to click/approve any attach prompt. +- `profile` is the explicit override when you want a specific browser mode. Set `browser.defaultProfile: "openclaw"` if you want managed mode by default. @@ -68,7 +74,7 @@ Browser settings live in `~/.openclaw/openclaw.json`. // cdpUrl: "http://127.0.0.1:18792", // legacy single-profile override remoteCdpTimeoutMs: 1500, // remote CDP HTTP timeout (ms) remoteCdpHandshakeTimeoutMs: 3000, // remote CDP WebSocket handshake timeout (ms) - defaultProfile: "chrome", + defaultProfile: "openclaw", color: "#FF4500", headless: false, noSandbox: false, @@ -77,6 +83,17 @@ Browser settings live in `~/.openclaw/openclaw.json`. profiles: { openclaw: { cdpPort: 18800, color: "#FF4500" }, work: { cdpPort: 18801, color: "#0066CC" }, + user: { + driver: "existing-session", + attachOnly: true, + color: "#00AA00", + }, + brave: { + driver: "existing-session", + attachOnly: true, + userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser", + color: "#FB542B", + }, remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" }, }, }, @@ -86,20 +103,25 @@ Browser settings live in `~/.openclaw/openclaw.json`. Notes: - The browser control service binds to loopback on a port derived from `gateway.port` - (default: `18791`, which is gateway + 2). The relay uses the next port (`18792`). + (default: `18791`, which is gateway + 2). - If you override the Gateway port (`gateway.port` or `OPENCLAW_GATEWAY_PORT`), the derived browser ports shift to stay in the same “family”. -- `cdpUrl` defaults to the relay port when unset. +- `cdpUrl` defaults to the managed local CDP port when unset. - `remoteCdpTimeoutMs` applies to remote (non-loopback) CDP reachability checks. - `remoteCdpHandshakeTimeoutMs` applies to remote CDP WebSocket reachability checks. - Browser navigation/open-tab is SSRF-guarded before navigation and best-effort re-checked on final `http(s)` URL after navigation. +- In strict SSRF mode, remote CDP endpoint discovery/probes (`cdpUrl`, including `/json/version` lookups) are checked too. - `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` defaults to `true` (trusted-network model). Set it to `false` for strict public-only browsing. - `browser.ssrfPolicy.allowPrivateNetwork` remains supported as a legacy alias for compatibility. - `attachOnly: true` means “never launch a local browser; only attach if it is already running.” - `color` + per-profile `color` tint the browser UI so you can see which profile is active. -- Default profile is `openclaw` (OpenClaw-managed standalone browser). Use `defaultProfile: "chrome"` to opt into the Chrome extension relay. +- Default profile is `openclaw` (OpenClaw-managed standalone browser). Use `defaultProfile: "user"` to opt into the signed-in user browser. - Auto-detect order: system default browser if Chromium-based; otherwise Chrome → Brave → Edge → Chromium → Chrome Canary. - Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl` — set those only for remote CDP. +- `driver: "existing-session"` uses Chrome DevTools MCP instead of raw CDP. Do + not set `cdpUrl` for that driver. +- Set `browser.profiles..userDataDir` when an existing-session profile + should attach to a non-default Chromium user profile such as Brave or Edge. ## Use Brave (or another Chromium-based browser) @@ -169,7 +191,7 @@ Notes: ## Browserless (hosted remote CDP) [Browserless](https://browserless.io) is a hosted Chromium service that exposes -CDP endpoints over HTTPS. You can point a OpenClaw browser profile at a +CDP endpoints over HTTPS. You can point an OpenClaw browser profile at a Browserless region endpoint and authenticate with your API key. Example: @@ -263,85 +285,122 @@ OpenClaw supports multiple named profiles (routing configs). Profiles can be: - **openclaw-managed**: a dedicated Chromium-based browser instance with its own user data directory + CDP port - **remote**: an explicit CDP URL (Chromium-based browser running elsewhere) -- **extension relay**: your existing Chrome tab(s) via the local relay + Chrome extension +- **existing session**: your existing Chrome profile via Chrome DevTools MCP auto-connect Defaults: - The `openclaw` profile is auto-created if missing. -- The `chrome` profile is built-in for the Chrome extension relay (points at `http://127.0.0.1:18792` by default). +- The `user` profile is built-in for Chrome MCP existing-session attach. +- Existing-session profiles are opt-in beyond `user`; create them with `--driver existing-session`. - Local CDP ports allocate from **18800–18899** by default. - Deleting a profile moves its local data directory to Trash. All control endpoints accept `?profile=`; the CLI uses `--browser-profile`. -## Chrome extension relay (use your existing Chrome) +## Existing-session via Chrome DevTools MCP -OpenClaw can also drive **your existing Chrome tabs** (no separate “openclaw” Chrome instance) via a local CDP relay + a Chrome extension. +OpenClaw can also attach to a running Chromium-based browser profile through the +official Chrome DevTools MCP server. This reuses the tabs and login state +already open in that browser profile. -Full guide: [Chrome extension](/tools/chrome-extension) +Official background and setup references: -Flow: +- [Chrome for Developers: Use Chrome DevTools MCP with your browser session](https://developer.chrome.com/blog/chrome-devtools-mcp-debug-your-browser-session) +- [Chrome DevTools MCP README](https://github.com/ChromeDevTools/chrome-devtools-mcp) -- The Gateway runs locally (same machine) or a node host runs on the browser machine. -- A local **relay server** listens at a loopback `cdpUrl` (default: `http://127.0.0.1:18792`). -- You click the **OpenClaw Browser Relay** extension icon on a tab to attach (it does not auto-attach). -- The agent controls that tab via the normal `browser` tool, by selecting the right profile. +Built-in profile: -If the Gateway runs elsewhere, run a node host on the browser machine so the Gateway can proxy browser actions. +- `user` -### Sandboxed sessions +Optional: create your own custom existing-session profile if you want a +different name, color, or browser data directory. -If the agent session is sandboxed, the `browser` tool may default to `target="sandbox"` (sandbox browser). -Chrome extension relay takeover requires host browser control, so either: +Default behavior: -- run the session unsandboxed, or -- set `agents.defaults.sandbox.browser.allowHostControl: true` and use `target="host"` when calling the tool. +- The built-in `user` profile uses Chrome MCP auto-connect, which targets the + default local Google Chrome profile. -### Setup - -1. Load the extension (dev/unpacked): - -```bash -openclaw browser extension install -``` - -- Chrome → `chrome://extensions` → enable “Developer mode” -- “Load unpacked” → select the directory printed by `openclaw browser extension path` -- Pin the extension, then click it on the tab you want to control (badge shows `ON`). - -2. Use it: - -- CLI: `openclaw browser --browser-profile chrome tabs` -- Agent tool: `browser` with `profile="chrome"` - -Optional: if you want a different name or relay port, create your own profile: - -```bash -openclaw browser create-profile \ - --name my-chrome \ - --driver extension \ - --cdp-url http://127.0.0.1:18792 \ - --color "#00AA00" -``` - -Notes: - -- This mode relies on Playwright-on-CDP for most operations (screenshots/snapshots/actions). -- Detach by clicking the extension icon again. -- Leave the relay loopback-only by default. If the relay must be reachable from a different network namespace (for example Gateway in WSL2, Chrome on Windows), set `browser.relayBindHost` to an explicit bind address such as `0.0.0.0` while keeping the surrounding network private and authenticated. - -WSL2 / cross-namespace example: +Use `userDataDir` for Brave, Edge, Chromium, or a non-default Chrome profile: ```json5 { browser: { - enabled: true, - relayBindHost: "0.0.0.0", - defaultProfile: "chrome", + profiles: { + brave: { + driver: "existing-session", + attachOnly: true, + userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser", + color: "#FB542B", + }, + }, }, } ``` +Then in the matching browser: + +1. Open that browser's inspect page for remote debugging. +2. Enable remote debugging. +3. Keep the browser running and approve the connection prompt when OpenClaw attaches. + +Common inspect pages: + +- Chrome: `chrome://inspect/#remote-debugging` +- Brave: `brave://inspect/#remote-debugging` +- Edge: `edge://inspect/#remote-debugging` + +Live attach smoke test: + +```bash +openclaw browser --browser-profile user start +openclaw browser --browser-profile user status +openclaw browser --browser-profile user tabs +openclaw browser --browser-profile user snapshot --format ai +``` + +What success looks like: + +- `status` shows `driver: existing-session` +- `status` shows `transport: chrome-mcp` +- `status` shows `running: true` +- `tabs` lists your already-open browser tabs +- `snapshot` returns refs from the selected live tab + +What to check if attach does not work: + +- the target Chromium-based browser is version `144+` +- remote debugging is enabled in that browser's inspect page +- the browser showed and you accepted the attach consent prompt +- `openclaw doctor` migrates old extension-based browser config and checks that + Chrome is installed locally for default auto-connect profiles, but it cannot + enable browser-side remote debugging for you + +Agent use: + +- Use `profile="user"` when you need the user’s logged-in browser state. +- If you use a custom existing-session profile, pass that explicit profile name. +- Only choose this mode when the user is at the computer to approve the attach + prompt. +- the Gateway or node host can spawn `npx chrome-devtools-mcp@latest --autoConnect` + +Notes: + +- This path is higher-risk than the isolated `openclaw` profile because it can + act inside your signed-in browser session. +- OpenClaw does not launch the browser for this driver; it attaches to an + existing session only. +- OpenClaw uses the official Chrome DevTools MCP `--autoConnect` flow here. If + `userDataDir` is set, OpenClaw passes it through to target that explicit + Chromium user data directory. +- Existing-session screenshots support page captures and `--ref` element + captures from snapshots, but not CSS `--element` selectors. +- Existing-session `wait --url` supports exact, substring, and glob patterns + like other browser drivers. `wait --load networkidle` is not supported yet. +- Some features still require the managed browser path, such as PDF export and + download interception. +- Existing-session is host-local. If Chrome lives on a different machine or a + different network namespace, use remote CDP or a node host instead. + ## Isolation guarantees - **Dedicated user data dir**: never touches your personal browser profile. @@ -395,7 +454,6 @@ If gateway auth is configured, browser HTTP routes require auth too: Some features (navigate/act/AI snapshot/role snapshot, element screenshots, PDF) require Playwright. If Playwright isn’t installed, those endpoints return a clear 501 error. ARIA snapshots and basic screenshots still work for openclaw-managed Chrome. -For the Chrome extension relay driver, ARIA snapshots and screenshots require Playwright. If you see `Playwright is not available in this gateway build`, install the full Playwright package (not `playwright-core`) and restart the gateway, or reinstall diff --git a/docs/tools/btw.md b/docs/tools/btw.md new file mode 100644 index 00000000000..38a30fcec77 --- /dev/null +++ b/docs/tools/btw.md @@ -0,0 +1,142 @@ +--- +summary: "Ephemeral side questions with /btw" +read_when: + - You want to ask a quick side question about the current session + - You are implementing or debugging BTW behavior across clients +title: "BTW Side Questions" +--- + +# BTW Side Questions + +`/btw` lets you ask a quick side question about the **current session** without +turning that question into normal conversation history. + +It is modeled after Claude Code's `/btw` behavior, but adapted to OpenClaw's +Gateway and multi-channel architecture. + +## What it does + +When you send: + +```text +/btw what changed? +``` + +OpenClaw: + +1. snapshots the current session context, +2. runs a separate **tool-less** model call, +3. answers only the side question, +4. leaves the main run alone, +5. does **not** write the BTW question or answer to session history, +6. emits the answer as a **live side result** rather than a normal assistant message. + +The important mental model is: + +- same session context +- separate one-shot side query +- no tool calls +- no future context pollution +- no transcript persistence + +## What it does not do + +`/btw` does **not**: + +- create a new durable session, +- continue the unfinished main task, +- run tools or agent tool loops, +- write BTW question/answer data to transcript history, +- appear in `chat.history`, +- survive a reload. + +It is intentionally **ephemeral**. + +## How context works + +BTW uses the current session as **background context only**. + +If the main run is currently active, OpenClaw snapshots the current message +state and includes the in-flight main prompt as background context, while +explicitly telling the model: + +- answer only the side question, +- do not resume or complete the unfinished main task, +- do not emit tool calls or pseudo-tool calls. + +That keeps BTW isolated from the main run while still making it aware of what +the session is about. + +## Delivery model + +BTW is **not** delivered as a normal assistant transcript message. + +At the Gateway protocol level: + +- normal assistant chat uses the `chat` event +- BTW uses the `chat.side_result` event + +This separation is intentional. If BTW reused the normal `chat` event path, +clients would treat it like regular conversation history. + +Because BTW uses a separate live event and is not replayed from +`chat.history`, it disappears after reload. + +## Surface behavior + +### TUI + +In TUI, BTW is rendered inline in the current session view, but it remains +ephemeral: + +- visibly distinct from a normal assistant reply +- dismissible with `Enter` or `Esc` +- not replayed on reload + +### External channels + +On channels like Telegram, WhatsApp, and Discord, BTW is delivered as a +clearly labeled one-off reply because those surfaces do not have a local +ephemeral overlay concept. + +The answer is still treated as a side result, not normal session history. + +### Control UI / web + +The Gateway emits BTW correctly as `chat.side_result`, and BTW is not included +in `chat.history`, so the persistence contract is already correct for web. + +The current Control UI still needs a dedicated `chat.side_result` consumer to +render BTW live in the browser. Until that client-side support lands, BTW is a +Gateway-level feature with full TUI and external-channel behavior, but not yet +a complete browser UX. + +## When to use BTW + +Use `/btw` when you want: + +- a quick clarification about the current work, +- a factual side answer while a long run is still in progress, +- a temporary answer that should not become part of future session context. + +Examples: + +```text +/btw what file are we editing? +/btw what does this error mean? +/btw summarize the current task in one sentence +/btw what is 17 * 19? +``` + +## When not to use BTW + +Do not use `/btw` when you want the answer to become part of the session's +future working context. + +In that case, ask normally in the main session instead of using BTW. + +## Related + +- [Slash commands](/tools/slash-commands) +- [Thinking Levels](/tools/thinking) +- [Session](/concepts/session) diff --git a/docs/tools/capability-cookbook.md b/docs/tools/capability-cookbook.md new file mode 100644 index 00000000000..f439c362e89 --- /dev/null +++ b/docs/tools/capability-cookbook.md @@ -0,0 +1,112 @@ +--- +summary: "Cookbook for adding a new shared capability to OpenClaw" +read_when: + - Adding a new core capability and plugin registration surface + - Deciding whether code belongs in core, a vendor plugin, or a feature plugin + - Wiring a new runtime helper for channels or tools +title: "Capability Cookbook" +--- + +# Capability Cookbook + +Use this when OpenClaw needs a new domain such as image generation, video +generation, or some future vendor-backed feature area. + +The rule: + +- plugin = ownership boundary +- capability = shared core contract + +That means you should not start by wiring a vendor directly into a channel or a +tool. Start by defining the capability. + +## When to create a capability + +Create a new capability when all of these are true: + +1. more than one vendor could plausibly implement it +2. channels, tools, or feature plugins should consume it without caring about + the vendor +3. core needs to own fallback, policy, config, or delivery behavior + +If the work is vendor-only and no shared contract exists yet, stop and define +the contract first. + +## The standard sequence + +1. Define the typed core contract. +2. Add plugin registration for that contract. +3. Add a shared runtime helper. +4. Wire one real vendor plugin as proof. +5. Move feature/channel consumers onto the runtime helper. +6. Add contract tests. +7. Document the operator-facing config and ownership model. + +## What goes where + +Core: + +- request/response types +- provider registry + resolution +- fallback behavior +- config schema and labels/help +- runtime helper surface + +Vendor plugin: + +- vendor API calls +- vendor auth handling +- vendor-specific request normalization +- registration of the capability implementation + +Feature/channel plugin: + +- calls `api.runtime.*` or the matching `plugin-sdk/*-runtime` helper +- never calls a vendor implementation directly + +## File checklist + +For a new capability, expect to touch these areas: + +- `src//types.ts` +- `src//...registry/runtime.ts` +- `src/plugins/types.ts` +- `src/plugins/registry.ts` +- `src/plugins/captured-registration.ts` +- `src/plugins/contracts/registry.ts` +- `src/plugins/runtime/types-core.ts` +- `src/plugins/runtime/index.ts` +- `src/plugin-sdk/.ts` +- `src/plugin-sdk/-runtime.ts` +- one or more `extensions//...` +- config/docs/tests + +## Example: image generation + +Image generation follows the standard shape: + +1. core defines `ImageGenerationProvider` +2. core exposes `registerImageGenerationProvider(...)` +3. core exposes `runtime.imageGeneration.generate(...)` +4. the `openai` and `google` plugins register vendor-backed implementations +5. future vendors can register the same contract without changing channels/tools + +The config key is separate from vision-analysis routing: + +- `agents.defaults.imageModel` = analyze images +- `agents.defaults.imageGenerationModel` = generate images + +Keep those separate so fallback and policy remain explicit. + +## Review checklist + +Before shipping a new capability, verify: + +- no channel/tool imports vendor code directly +- the runtime helper is the shared path +- at least one contract test asserts bundled ownership +- config docs name the new model/config key +- plugin docs explain the ownership boundary + +If a PR skips the capability layer and hardcodes vendor behavior into a +channel/tool, send it back and define the contract first. diff --git a/docs/tools/chrome-extension.md b/docs/tools/chrome-extension.md deleted file mode 100644 index ce4b271ae9c..00000000000 --- a/docs/tools/chrome-extension.md +++ /dev/null @@ -1,196 +0,0 @@ ---- -summary: "Chrome extension: let OpenClaw drive your existing Chrome tab" -read_when: - - You want the agent to drive an existing Chrome tab (toolbar button) - - You need remote Gateway + local browser automation via Tailscale - - You want to understand the security implications of browser takeover -title: "Chrome Extension" ---- - -# Chrome extension (browser relay) - -The OpenClaw Chrome extension lets the agent control your **existing Chrome tabs** (your normal Chrome window) instead of launching a separate openclaw-managed Chrome profile. - -Attach/detach happens via a **single Chrome toolbar button**. - -## What it is (concept) - -There are three parts: - -- **Browser control service** (Gateway or node): the API the agent/tool calls (via the Gateway) -- **Local relay server** (loopback CDP): bridges between the control server and the extension (`http://127.0.0.1:18792` by default) -- **Chrome MV3 extension**: attaches to the active tab using `chrome.debugger` and pipes CDP messages to the relay - -OpenClaw then controls the attached tab through the normal `browser` tool surface (selecting the right profile). - -## Install / load (unpacked) - -1. Install the extension to a stable local path: - -```bash -openclaw browser extension install -``` - -2. Print the installed extension directory path: - -```bash -openclaw browser extension path -``` - -3. Chrome → `chrome://extensions` - -- Enable “Developer mode” -- “Load unpacked” → select the directory printed above - -4. Pin the extension. - -## Updates (no build step) - -The extension ships inside the OpenClaw release (npm package) as static files. There is no separate “build” step. - -After upgrading OpenClaw: - -- Re-run `openclaw browser extension install` to refresh the installed files under your OpenClaw state directory. -- Chrome → `chrome://extensions` → click “Reload” on the extension. - -## Use it (set gateway token once) - -OpenClaw ships with a built-in browser profile named `chrome` that targets the extension relay on the default port. - -Before first attach, open extension Options and set: - -- `Port` (default `18792`) -- `Gateway token` (must match `gateway.auth.token` / `OPENCLAW_GATEWAY_TOKEN`) - -Use it: - -- CLI: `openclaw browser --browser-profile chrome tabs` -- Agent tool: `browser` with `profile="chrome"` - -If you want a different name or a different relay port, create your own profile: - -```bash -openclaw browser create-profile \ - --name my-chrome \ - --driver extension \ - --cdp-url http://127.0.0.1:18792 \ - --color "#00AA00" -``` - -### Custom Gateway ports - -If you're using a custom gateway port, the extension relay port is automatically derived: - -**Extension Relay Port = Gateway Port + 3** - -Example: if `gateway.port: 19001`, then: - -- Extension relay port: `19004` (gateway + 3) - -Configure the extension to use the derived relay port in the extension Options page. - -## Attach / detach (toolbar button) - -- Open the tab you want OpenClaw to control. -- Click the extension icon. - - Badge shows `ON` when attached. -- Click again to detach. - -## Which tab does it control? - -- It does **not** automatically control “whatever tab you’re looking at”. -- It controls **only the tab(s) you explicitly attached** by clicking the toolbar button. -- To switch: open the other tab and click the extension icon there. - -## Badge + common errors - -- `ON`: attached; OpenClaw can drive that tab. -- `…`: connecting to the local relay. -- `!`: relay not reachable/authenticated (most common: relay server not running, or gateway token missing/wrong). - -If you see `!`: - -- Make sure the Gateway is running locally (default setup), or run a node host on this machine if the Gateway runs elsewhere. -- Open the extension Options page; it validates relay reachability + gateway-token auth. - -## Remote Gateway (use a node host) - -### Local Gateway (same machine as Chrome) — usually **no extra steps** - -If the Gateway runs on the same machine as Chrome, it starts the browser control service on loopback -and auto-starts the relay server. The extension talks to the local relay; the CLI/tool calls go to the Gateway. - -### Remote Gateway (Gateway runs elsewhere) — **run a node host** - -If your Gateway runs on another machine, start a node host on the machine that runs Chrome. -The Gateway will proxy browser actions to that node; the extension + relay stay local to the browser machine. - -If multiple nodes are connected, pin one with `gateway.nodes.browser.node` or set `gateway.nodes.browser.mode`. - -## Sandboxing (tool containers) - -If your agent session is sandboxed (`agents.defaults.sandbox.mode != "off"`), the `browser` tool can be restricted: - -- By default, sandboxed sessions often target the **sandbox browser** (`target="sandbox"`), not your host Chrome. -- Chrome extension relay takeover requires controlling the **host** browser control server. - -Options: - -- Easiest: use the extension from a **non-sandboxed** session/agent. -- Or allow host browser control for sandboxed sessions: - -```json5 -{ - agents: { - defaults: { - sandbox: { - browser: { - allowHostControl: true, - }, - }, - }, - }, -} -``` - -Then ensure the tool isn’t denied by tool policy, and (if needed) call `browser` with `target="host"`. - -Debugging: `openclaw sandbox explain` - -## Remote access tips - -- Keep the Gateway and node host on the same tailnet; avoid exposing relay ports to LAN or public Internet. -- Pair nodes intentionally; disable browser proxy routing if you don’t want remote control (`gateway.nodes.browser.mode="off"`). -- Leave the relay on loopback unless you have a real cross-namespace need. For WSL2 or similar split-host setups, set `browser.relayBindHost` to an explicit bind address such as `0.0.0.0`, then keep access constrained with Gateway auth, node pairing, and a private network. - -## How “extension path” works - -`openclaw browser extension path` prints the **installed** on-disk directory containing the extension files. - -The CLI intentionally does **not** print a `node_modules` path. Always run `openclaw browser extension install` first to copy the extension to a stable location under your OpenClaw state directory. - -If you move or delete that install directory, Chrome will mark the extension as broken until you reload it from a valid path. - -## Security implications (read this) - -This is powerful and risky. Treat it like giving the model “hands on your browser”. - -- The extension uses Chrome’s debugger API (`chrome.debugger`). When attached, the model can: - - click/type/navigate in that tab - - read page content - - access whatever the tab’s logged-in session can access -- **This is not isolated** like the dedicated openclaw-managed profile. - - If you attach to your daily-driver profile/tab, you’re granting access to that account state. - -Recommendations: - -- Prefer a dedicated Chrome profile (separate from your personal browsing) for extension relay usage. -- Keep the Gateway and any node hosts tailnet-only; rely on Gateway auth + node pairing. -- Avoid exposing relay ports over LAN (`0.0.0.0`) and avoid Funnel (public). -- The relay blocks non-extension origins and requires gateway-token auth for both `/cdp` and `/extension`. - -Related: - -- Browser tool overview: [Browser](/tools/browser) -- Security audit: [Security](/gateway/security) -- Tailscale setup: [Tailscale](/gateway/tailscale) diff --git a/docs/tools/clawhub.md b/docs/tools/clawhub.md index 1b8867cad30..8fe7fbedd82 100644 --- a/docs/tools/clawhub.md +++ b/docs/tools/clawhub.md @@ -66,7 +66,7 @@ pnpm add -g clawhub ## How it fits into OpenClaw -By default, the CLI installs skills into `./skills` under your current working directory. If a OpenClaw workspace is configured, `clawhub` falls back to that workspace unless you override `--workdir` (or `CLAWHUB_WORKDIR`). OpenClaw loads workspace skills from `/skills` and will pick them up in the **next** session. If you already use `~/.openclaw/skills` or bundled skills, workspace skills take precedence. +By default, the CLI installs skills into `./skills` under your current working directory. If an OpenClaw workspace is configured, `clawhub` falls back to that workspace unless you override `--workdir` (or `CLAWHUB_WORKDIR`). OpenClaw loads workspace skills from `/skills` and will pick them up in the **next** session. If you already use `~/.openclaw/skills` or bundled skills, workspace skills take precedence. For more detail on how skills are loaded, shared, and gated, see [Skills](/tools/skills). diff --git a/docs/tools/diffs.md b/docs/tools/diffs.md index 6207366034e..3e0289dd05d 100644 --- a/docs/tools/diffs.md +++ b/docs/tools/diffs.md @@ -111,6 +111,7 @@ All fields are optional unless noted: - `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. - `theme` (`"light" | "dark"`): viewer theme. Defaults to plugin default `defaults.theme`. - `layout` (`"unified" | "split"`): diff layout. Defaults to plugin default `defaults.layout`. - `expandUnchanged` (`boolean`): expand unchanged sections when full context is available. Per-call option only (not a plugin default key). @@ -150,9 +151,12 @@ Shared fields for modes that create a viewer: - `inputKind` - `fileCount` - `mode` +- `context` (`agentId`, `sessionId`, `messageChannel`, `agentAccountId` when available) File fields when PNG or PDF is rendered: +- `artifactId` +- `expiresAt` - `filePath` - `path` (same value as `filePath`, for message tool compatibility) - `fileBytes` diff --git a/docs/tools/elevated.md b/docs/tools/elevated.md index eed788eda8c..c10b955ce2d 100644 --- a/docs/tools/elevated.md +++ b/docs/tools/elevated.md @@ -17,7 +17,7 @@ title: "Elevated Mode" - Directive forms: `/elevated on|off|ask|full`, `/elev on|off|ask|full`. - Only `on|off|ask|full` are accepted; anything else returns a hint and does not change state. -## What it controls (and what it doesn’t) +## What it controls (and what it does not) - **Availability gates**: `tools.elevated` is the global baseline. `agents.list[].tools.elevated` can further restrict elevated per agent (both must allow). - **Per-session state**: `/elevated on|off|ask|full` sets the elevated level for the current session key. diff --git a/docs/tools/exec-approvals.md b/docs/tools/exec-approvals.md index 0bca1dee488..f0fde42a178 100644 --- a/docs/tools/exec-approvals.md +++ b/docs/tools/exec-approvals.md @@ -160,13 +160,14 @@ Long options are validated fail-closed in safe-bin mode: unknown flags and ambig abbreviations are rejected. Denied flags by safe-bin profile: - +[//]: # "SAFE_BIN_DENIED_FLAGS:START" - `grep`: `--dereference-recursive`, `--directories`, `--exclude-from`, `--file`, `--recursive`, `-R`, `-d`, `-f`, `-r` - `jq`: `--argfile`, `--from-file`, `--library-path`, `--rawfile`, `--slurpfile`, `-L`, `-f` - `sort`: `--compress-program`, `--files0-from`, `--output`, `--random-source`, `--temporary-directory`, `-T`, `-o` - `wc`: `--files0-from` - + +[//]: # "SAFE_BIN_DENIED_FLAGS:END" Safe bins also force argv tokens to be treated as **literal text** at execution time (no globbing and no `$VARS` expansion) for stdin-only segments, so patterns like `*` or `$HOME/...` cannot be @@ -271,6 +272,8 @@ Approval-backed interpreter/runtime runs are intentionally conservative: - Exact argv/cwd/env context is always bound. - Direct shell script and direct runtime file forms are best-effort bound to one concrete local file snapshot. +- Common package-manager wrapper forms that still resolve to one direct local file (for example + `pnpm exec`, `pnpm node`, `npm exec`, `npx`) are unwrapped before binding. - If OpenClaw cannot identify exactly one concrete local file for an interpreter/runtime command (for example package scripts, eval forms, runtime-specific loader chains, or ambiguous multi-file forms), approval-backed execution is denied instead of claiming semantic coverage it does not diff --git a/docs/tools/firecrawl.md b/docs/tools/firecrawl.md index 2cd90a06bf5..eab0439311f 100644 --- a/docs/tools/firecrawl.md +++ b/docs/tools/firecrawl.md @@ -1,27 +1,73 @@ --- -summary: "Firecrawl fallback for web_fetch (anti-bot + cached extraction)" +summary: "Firecrawl search, scrape, and web_fetch fallback" read_when: - You want Firecrawl-backed web extraction - You need a Firecrawl API key + - You want Firecrawl as a web_search provider - You want anti-bot extraction for web_fetch title: "Firecrawl" --- # Firecrawl -OpenClaw can use **Firecrawl** as a fallback extractor for `web_fetch`. It is a hosted -content extraction service that supports bot circumvention and caching, which helps -with JS-heavy sites or pages that block plain HTTP fetches. +OpenClaw can use **Firecrawl** in three ways: + +- as the `web_search` provider +- as explicit plugin tools: `firecrawl_search` and `firecrawl_scrape` +- as a fallback extractor for `web_fetch` + +It is a hosted extraction/search service that supports bot circumvention and caching, +which helps with JS-heavy sites or pages that block plain HTTP fetches. ## Get an API key 1. Create a Firecrawl account and generate an API key. 2. Store it in config or set `FIRECRAWL_API_KEY` in the gateway environment. -## Configure Firecrawl +## Configure Firecrawl search ```json5 { + tools: { + web: { + search: { + provider: "firecrawl", + }, + }, + }, + plugins: { + entries: { + firecrawl: { + enabled: true, + config: { + webSearch: { + apiKey: "FIRECRAWL_API_KEY_HERE", + baseUrl: "https://api.firecrawl.dev", + }, + }, + }, + }, + }, +} +``` + +Notes: + +- Choosing Firecrawl in onboarding or `openclaw configure --section web` enables the bundled Firecrawl plugin automatically. +- `web_search` with Firecrawl supports `query` and `count`. +- For Firecrawl-specific controls like `sources`, `categories`, or result scraping, use `firecrawl_search`. + +## Configure Firecrawl scrape + web_fetch fallback + +```json5 +{ + plugins: { + entries: { + firecrawl: { + enabled: true, + }, + }, + }, tools: { web: { fetch: { @@ -44,6 +90,38 @@ Notes: - Firecrawl fallback attempts run only when an API key is available (`tools.web.fetch.firecrawl.apiKey` or `FIRECRAWL_API_KEY`). - `maxAgeMs` controls how old cached results can be (ms). Default is 2 days. +`firecrawl_scrape` reuses the same `tools.web.fetch.firecrawl.*` settings and env vars. + +## Firecrawl plugin tools + +### `firecrawl_search` + +Use this when you want Firecrawl-specific search controls instead of generic `web_search`. + +Core parameters: + +- `query` +- `count` +- `sources` +- `categories` +- `scrapeResults` +- `timeoutSeconds` + +### `firecrawl_scrape` + +Use this for JS-heavy or bot-protected pages where plain `web_fetch` is weak. + +Core parameters: + +- `url` +- `extractMode` +- `maxChars` +- `onlyMainContent` +- `maxAgeMs` +- `proxy` +- `storeInCache` +- `timeoutSeconds` + ## Stealth / bot circumvention Firecrawl exposes a **proxy mode** parameter for bot circumvention (`basic`, `stealth`, or `auto`). diff --git a/docs/tools/index.md b/docs/tools/index.md index 6552d6f9118..55e52bf46da 100644 --- a/docs/tools/index.md +++ b/docs/tools/index.md @@ -256,7 +256,7 @@ Enable with `tools.loopDetection.enabled: true` (default is `false`). ### `web_search` -Search the web using Perplexity, Brave, Gemini, Grok, or Kimi. +Search the web using Brave, Firecrawl, Gemini, Grok, Kimi, or Perplexity. Core parameters: @@ -316,7 +316,10 @@ Common parameters: Notes: - Requires `browser.enabled=true` (default is `true`; set `false` to disable). - All actions accept optional `profile` parameter for multi-instance support. -- When `profile` is omitted, uses `browser.defaultProfile` (defaults to "chrome"). +- Omit `profile` for the safe default: isolated OpenClaw-managed browser (`openclaw`). +- Use `profile="user"` for the real local host browser when existing logins/cookies matter and the user is present to click/approve any attach prompt. +- `profile="user"` is host-only; do not combine it with sandbox/node targets. +- When `profile` is omitted, uses `browser.defaultProfile` (defaults to `openclaw`). - Profile names: lowercase alphanumeric + hyphens only (max 64 chars). - Port range: 18800-18899 (~100 profiles max). - Remote profiles are attach-only (no start/stop/reset). @@ -397,6 +400,46 @@ Notes: - Only available when `agents.defaults.imageModel` is configured (primary or fallbacks), or when an implicit image model can be inferred from your default model + configured auth (best-effort pairing). - Uses the image model directly (independent of the main chat model). +### `image_generate` + +Generate one or more images with the configured or inferred image-generation model. + +Core parameters: + +- `action` (optional: `generate` or `list`; default `generate`) +- `prompt` (required) +- `image` or `images` (optional reference image path/URL for edit mode) +- `model` (optional provider/model override) +- `size` (optional size hint) +- `resolution` (optional `1K|2K|4K` hint) +- `count` (optional, `1-4`, default `1`) + +Notes: + +- Available when `agents.defaults.imageGenerationModel` is configured, or when OpenClaw can infer a compatible image-generation default from your enabled providers plus available auth. +- Explicit `agents.defaults.imageGenerationModel` still wins over any inferred default. +- Use `action: "list"` to inspect registered providers, default models, supported model ids, sizes, resolutions, and edit support. +- Returns local `MEDIA:` lines so channels can deliver the generated files directly. +- Uses the image-generation model directly (independent of the main chat model). +- Google-backed flows, including `google/gemini-3-pro-image-preview` for the native Nano Banana-style path, support reference-image edits plus explicit `1K|2K|4K` resolution hints. +- When editing and `resolution` is omitted, OpenClaw infers a draft/final resolution from the input image size. +- This is the built-in replacement for the old `nano-banana-pro` skill workflow. Use `agents.defaults.imageGenerationModel`, not `skills.entries`, for stock image generation. + +Native example: + +```json5 +{ + agents: { + defaults: { + imageGenerationModel: { + primary: "google/gemini-3-pro-image-preview", // native Nano Banana path + fallbacks: ["fal/fal-ai/flux/dev"], + }, + }, + }, +} +``` + ### `pdf` Analyze one or more PDF documents. diff --git a/docs/tools/llm-task.md b/docs/tools/llm-task.md index e6f574d078e..2626d3237e4 100644 --- a/docs/tools/llm-task.md +++ b/docs/tools/llm-task.md @@ -75,11 +75,14 @@ outside the list is rejected. - `schema` (object, optional JSON Schema) - `provider` (string, optional) - `model` (string, optional) +- `thinking` (string, optional) - `authProfileId` (string, optional) - `temperature` (number, optional) - `maxTokens` (number, optional) - `timeoutMs` (number, optional) +`thinking` accepts the standard OpenClaw reasoning presets, such as `low` or `medium`. + ## Output Returns `details.json` containing the parsed JSON (and validates against @@ -90,6 +93,7 @@ Returns `details.json` containing the parsed JSON (and validates against ```lobster openclaw.invoke --tool llm-task --action json --args-json '{ "prompt": "Given the input email, return intent and draft.", + "thinking": "low", "input": { "subject": "Hello", "body": "Can you help?" diff --git a/docs/tools/lobster.md b/docs/tools/lobster.md index 65ff4f56dfb..5c8a47e4d62 100644 --- a/docs/tools/lobster.md +++ b/docs/tools/lobster.md @@ -106,6 +106,7 @@ Use it in a pipeline: ```lobster openclaw.invoke --tool llm-task --action json --args-json '{ "prompt": "Given the input email, return intent and draft.", + "thinking": "low", "input": { "subject": "Hello", "body": "Can you help?" }, "schema": { "type": "object", diff --git a/docs/tools/multi-agent-sandbox-tools.md b/docs/tools/multi-agent-sandbox-tools.md index dc49d94a29a..b9575d3362c 100644 --- a/docs/tools/multi-agent-sandbox-tools.md +++ b/docs/tools/multi-agent-sandbox-tools.md @@ -1,40 +1,25 @@ --- -summary: "Per-agent sandbox + tool restrictions, precedence, and examples" +summary: “Per-agent sandbox + tool restrictions, precedence, and examples” title: Multi-Agent Sandbox & Tools -read_when: "You want per-agent sandboxing or per-agent tool allow/deny policies in a multi-agent gateway." +read_when: “You want per-agent sandboxing or per-agent tool allow/deny policies in a multi-agent gateway.” status: active --- # Multi-Agent Sandbox & Tools Configuration -## Overview +Each agent in a multi-agent setup can override the global sandbox and tool +policy. This page covers per-agent configuration, precedence rules, and +examples. -Each agent in a multi-agent setup can now have its own: - -- **Sandbox configuration** (`agents.list[].sandbox` overrides `agents.defaults.sandbox`) -- **Tool restrictions** (`tools.allow` / `tools.deny`, plus `agents.list[].tools`) - -This allows you to run multiple agents with different security profiles: - -- Personal assistant with full access -- Family/work agents with restricted tools -- Public-facing agents in sandboxes - -`setupCommand` belongs under `sandbox.docker` (global or per-agent) and runs once -when the container is created. - -Auth is per-agent: each agent reads from its own `agentDir` auth store at: - -``` -~/.openclaw/agents//agent/auth-profiles.json -``` +- **Sandbox backends and modes**: see [Sandboxing](/gateway/sandboxing). +- **Debugging blocked tools**: see [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) and `openclaw sandbox explain`. +- **Elevated exec**: see [Elevated Mode](/tools/elevated). +Auth is per-agent: each agent reads from its own `agentDir` auth store at +`~/.openclaw/agents//agent/auth-profiles.json`. Credentials are **not** shared between agents. Never reuse `agentDir` across agents. If you want to share creds, copy `auth-profiles.json` into the other agent's `agentDir`. -For how sandboxing behaves at runtime, see [Sandboxing](/gateway/sandboxing). -For debugging “why is this blocked?”, see [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) and `openclaw sandbox explain`. - --- ## Configuration Examples @@ -222,30 +207,9 @@ If `agents.list[].tools.sandbox.tools` is set, it replaces `tools.sandbox.tools` If `agents.list[].tools.profile` is set, it overrides `tools.profile` for that agent. Provider tool keys accept either `provider` (e.g. `google-antigravity`) or `provider/model` (e.g. `openai/gpt-5.2`). -### Tool groups (shorthands) +Tool policies support `group:*` shorthands that expand to multiple tools. See [Tool groups](/gateway/sandbox-vs-tool-policy-vs-elevated#tool-groups-shorthands) for the full list. -Tool policies (global, agent, sandbox) support `group:*` entries that expand to multiple concrete tools: - -- `group:runtime`: `exec`, `bash`, `process` -- `group:fs`: `read`, `write`, `edit`, `apply_patch` -- `group:sessions`: `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` -- `group:memory`: `memory_search`, `memory_get` -- `group:ui`: `browser`, `canvas` -- `group:automation`: `cron`, `gateway` -- `group:messaging`: `message` -- `group:nodes`: `nodes` -- `group:openclaw`: all built-in OpenClaw tools (excludes provider plugins) - -### Elevated Mode - -`tools.elevated` is the global baseline (sender-based allowlist). `agents.list[].tools.elevated` can further restrict elevated for specific agents (both must allow). - -Mitigation patterns: - -- Deny `exec` for untrusted agents (`agents.list[].tools.deny: ["exec"]`) -- Avoid allowlisting senders that route to restricted agents -- Disable elevated globally (`tools.elevated.enabled: false`) if you only want sandboxed execution -- Disable elevated per agent (`agents.list[].tools.elevated.enabled: false`) for sensitive profiles +Per-agent elevated overrides (`agents.list[].tools.elevated`) can further restrict elevated exec for specific agents. See [Elevated Mode](/tools/elevated) for details. --- @@ -390,8 +354,11 @@ After configuring multi-agent sandbox and tools: --- -## See Also +## See also +- [Sandboxing](/gateway/sandboxing) -- full sandbox reference (modes, scopes, backends, images) +- [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) -- debugging "why is this blocked?" +- [Elevated Mode](/tools/elevated) - [Multi-Agent Routing](/concepts/multi-agent) - [Sandbox Configuration](/gateway/configuration#agentsdefaults-sandbox) - [Session Management](/concepts/session) diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index a257d8b7a45..48b60d3fe1d 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -3,23 +3,23 @@ summary: "OpenClaw plugins/extensions: discovery, config, and safety" read_when: - Adding or modifying plugins/extensions - Documenting plugin install or load rules + - Working with Codex/Claude-compatible plugin bundles title: "Plugins" --- # Plugins (Extensions) -## Quick start (new to plugins?) +## Quick start -A plugin is just a **small code module** that extends OpenClaw with extra -features (commands, tools, and Gateway RPC). +A plugin is either: -Most of the time, you’ll use plugins when you want a feature that’s not built -into core OpenClaw yet (or you want to keep optional features out of your main -install). +- a native **OpenClaw plugin** (`openclaw.plugin.json` + runtime module), or +- a compatible **bundle** (`.codex-plugin/plugin.json` or `.claude-plugin/plugin.json`) -Fast path: +Both show up under `openclaw plugins`, but only native OpenClaw plugins execute +runtime code in-process. -1. See what’s already loaded: +1. See what is already loaded: ```bash openclaw plugins list @@ -31,328 +31,92 @@ openclaw plugins list openclaw plugins install @openclaw/voice-call ``` -Npm specs are **registry-only** (package name + optional **exact version** or -**dist-tag**). Git/URL/file specs and semver ranges are rejected. - -Bare specs and `@latest` stay on the stable track. If npm resolves either of -those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a -prerelease tag such as `@beta`/`@rc` or an exact prerelease version. +Npm specs are registry-only. See [install rules](/cli/plugins#install) for +details on pinning, prerelease gating, and supported spec formats. 3. Restart the Gateway, then configure under `plugins.entries..config`. See [Voice Call](/plugins/voice-call) for a concrete example plugin. Looking for third-party listings? See [Community plugins](/plugins/community). +Need the bundle compatibility details? See [Plugin bundles](/plugins/bundles). + +For compatible bundles, install from a local directory or archive: + +```bash +openclaw plugins install ./my-bundle +openclaw plugins install ./my-bundle.tgz +``` + +For Claude marketplace installs, list the marketplace first, then install by +marketplace entry name: + +```bash +openclaw plugins marketplace list +openclaw plugins install @ +``` + +OpenClaw resolves known Claude marketplace names from +`~/.claude/plugins/known_marketplaces.json`. You can also pass an explicit +marketplace source with `--marketplace`. ## Available plugins (official) -- Microsoft Teams is plugin-only as of 2026.1.15; install `@openclaw/msteams` if you use Teams. -- Memory (Core) — bundled memory search plugin (enabled by default via `plugins.slots.memory`) -- Memory (LanceDB) — bundled long-term memory plugin (auto-recall/capture; set `plugins.slots.memory = "memory-lancedb"`) -- [Voice Call](/plugins/voice-call) — `@openclaw/voice-call` -- [Zalo Personal](/plugins/zalouser) — `@openclaw/zalouser` -- [Matrix](/channels/matrix) — `@openclaw/matrix` -- [Nostr](/channels/nostr) — `@openclaw/nostr` -- [Zalo](/channels/zalo) — `@openclaw/zalo` -- [Microsoft Teams](/channels/msteams) — `@openclaw/msteams` -- Google Antigravity OAuth (provider auth) — bundled as `google-antigravity-auth` (disabled by default) -- Gemini CLI OAuth (provider auth) — bundled as `google-gemini-cli-auth` (disabled by default) -- Qwen OAuth (provider auth) — bundled as `qwen-portal-auth` (disabled by default) -- Copilot Proxy (provider auth) — local VS Code Copilot Proxy bridge; distinct from built-in `github-copilot` device login (bundled, disabled by default) +### Installable plugins -OpenClaw plugins are **TypeScript modules** loaded at runtime via jiti. **Config -validation does not execute plugin code**; it uses the plugin manifest and JSON -Schema instead. See [Plugin manifest](/plugins/manifest). +These are published to npm and installed with `openclaw plugins install`: -Plugins can register: +| Plugin | Package | Docs | +| --------------- | ---------------------- | ---------------------------------- | +| Matrix | `@openclaw/matrix` | [Matrix](/channels/matrix) | +| Microsoft Teams | `@openclaw/msteams` | [MS Teams](/channels/msteams) | +| Nostr | `@openclaw/nostr` | [Nostr](/channels/nostr) | +| Voice Call | `@openclaw/voice-call` | [Voice Call](/plugins/voice-call) | +| Zalo | `@openclaw/zalo` | [Zalo](/channels/zalo) | +| Zalo Personal | `@openclaw/zalouser` | [Zalo Personal](/plugins/zalouser) | -- Gateway RPC methods -- Gateway HTTP routes -- Agent tools -- CLI commands -- Background services -- Context engines -- Optional config validation -- **Skills** (by listing `skills` directories in the plugin manifest) -- **Auto-reply commands** (execute without invoking the AI agent) +Microsoft Teams is plugin-only as of 2026.1.15. -Plugins run **in‑process** with the Gateway, so treat them as trusted code. -Tool authoring guide: [Plugin agent tools](/plugins/agent-tools). +Packaged installs also ship install-on-demand metadata for heavyweight official +plugins. Today that includes WhatsApp and `memory-lancedb`: onboarding, +`openclaw channels add`, `openclaw channels login --channel whatsapp`, and +other channel setup flows prompt to install them when first used instead of +shipping their full runtime trees inside the main npm tarball. -## Runtime helpers +### Bundled plugins -Plugins can access selected core helpers via `api.runtime`. For telephony TTS: +These ship with OpenClaw and are enabled by default unless noted. -```ts -const result = await api.runtime.tts.textToSpeechTelephony({ - text: "Hello from OpenClaw", - cfg: api.config, -}); -``` +**Memory:** -Notes: +- `memory-core` -- bundled memory search (default via `plugins.slots.memory`) +- `memory-lancedb` -- install-on-demand long-term memory with auto-recall/capture (set `plugins.slots.memory = "memory-lancedb"`) -- Uses core `messages.tts` configuration (OpenAI or ElevenLabs). -- Returns PCM audio buffer + sample rate. Plugins must resample/encode for providers. -- Edge TTS is not supported for telephony. +**Model providers** (all enabled by default): -For STT/transcription, plugins can call: +`anthropic`, `byteplus`, `cloudflare-ai-gateway`, `github-copilot`, `google`, `huggingface`, `kilocode`, `kimi-coding`, `minimax`, `mistral`, `modelstudio`, `moonshot`, `nvidia`, `openai`, `opencode`, `opencode-go`, `openrouter`, `qianfan`, `qwen-portal-auth`, `synthetic`, `together`, `venice`, `vercel-ai-gateway`, `volcengine`, `xiaomi`, `zai` -```ts -const { text } = await api.runtime.stt.transcribeAudioFile({ - filePath: "/tmp/inbound-audio.ogg", - cfg: api.config, - // Optional when MIME cannot be inferred reliably: - mime: "audio/ogg", -}); -``` +**Speech providers** (enabled by default): -Notes: +`elevenlabs`, `microsoft` -- Uses core media-understanding audio configuration (`tools.media.audio`) and provider fallback order. -- Returns `{ text: undefined }` when no transcription output is produced (for example skipped/unsupported input). +**Other bundled:** -## Gateway HTTP routes +- `copilot-proxy` -- VS Code Copilot Proxy bridge (disabled by default) -Plugins can expose HTTP endpoints with `api.registerHttpRoute(...)`. +## Compatible bundles -```ts -api.registerHttpRoute({ - path: "/acme/webhook", - auth: "plugin", - match: "exact", - handler: async (_req, res) => { - res.statusCode = 200; - res.end("ok"); - return true; - }, -}); -``` +OpenClaw also recognizes compatible external bundle layouts: -Route fields: +- Codex-style bundles: `.codex-plugin/plugin.json` +- Claude-style bundles: `.claude-plugin/plugin.json` or the default Claude + component layout without a manifest +- Cursor-style bundles: `.cursor-plugin/plugin.json` -- `path`: route path under the gateway HTTP server. -- `auth`: required. Use `"gateway"` to require normal gateway auth, or `"plugin"` for plugin-managed auth/webhook verification. -- `match`: optional. `"exact"` (default) or `"prefix"`. -- `replaceExisting`: optional. Allows the same plugin to replace its own existing route registration. -- `handler`: return `true` when the route handled the request. +They are shown in the plugin list as `format=bundle`, with a subtype of +`codex`, `claude`, or `cursor` in verbose/inspect output. -Notes: - -- `api.registerHttpHandler(...)` is obsolete. Use `api.registerHttpRoute(...)`. -- Plugin routes must declare `auth` explicitly. -- Exact `path + match` conflicts are rejected unless `replaceExisting: true`, and one plugin cannot replace another plugin's route. -- Overlapping routes with different `auth` levels are rejected. Keep `exact`/`prefix` fallthrough chains on the same auth level only. - -## Plugin SDK import paths - -Use SDK subpaths instead of the monolithic `openclaw/plugin-sdk` import when -authoring plugins: - -- `openclaw/plugin-sdk/core` for generic plugin APIs, provider auth types, and shared helpers. -- `openclaw/plugin-sdk/compat` for bundled/internal plugin code that needs broader shared runtime helpers than `core`. -- `openclaw/plugin-sdk/telegram` for Telegram channel plugins. -- `openclaw/plugin-sdk/discord` for Discord channel plugins. -- `openclaw/plugin-sdk/slack` for Slack channel plugins. -- `openclaw/plugin-sdk/signal` for Signal channel plugins. -- `openclaw/plugin-sdk/imessage` for iMessage channel plugins. -- `openclaw/plugin-sdk/whatsapp` for WhatsApp channel plugins. -- `openclaw/plugin-sdk/line` for LINE channel plugins. -- `openclaw/plugin-sdk/msteams` for the bundled Microsoft Teams plugin surface. -- Bundled extension-specific subpaths are also available: - `openclaw/plugin-sdk/acpx`, `openclaw/plugin-sdk/bluebubbles`, - `openclaw/plugin-sdk/copilot-proxy`, `openclaw/plugin-sdk/device-pair`, - `openclaw/plugin-sdk/diagnostics-otel`, `openclaw/plugin-sdk/diffs`, - `openclaw/plugin-sdk/feishu`, - `openclaw/plugin-sdk/google-gemini-cli-auth`, `openclaw/plugin-sdk/googlechat`, - `openclaw/plugin-sdk/irc`, `openclaw/plugin-sdk/llm-task`, - `openclaw/plugin-sdk/lobster`, `openclaw/plugin-sdk/matrix`, - `openclaw/plugin-sdk/mattermost`, `openclaw/plugin-sdk/memory-core`, - `openclaw/plugin-sdk/memory-lancedb`, - `openclaw/plugin-sdk/minimax-portal-auth`, - `openclaw/plugin-sdk/nextcloud-talk`, `openclaw/plugin-sdk/nostr`, - `openclaw/plugin-sdk/open-prose`, `openclaw/plugin-sdk/phone-control`, - `openclaw/plugin-sdk/qwen-portal-auth`, `openclaw/plugin-sdk/synology-chat`, - `openclaw/plugin-sdk/talk-voice`, `openclaw/plugin-sdk/test-utils`, - `openclaw/plugin-sdk/thread-ownership`, `openclaw/plugin-sdk/tlon`, - `openclaw/plugin-sdk/twitch`, `openclaw/plugin-sdk/voice-call`, - `openclaw/plugin-sdk/zalo`, and `openclaw/plugin-sdk/zalouser`. - -Compatibility note: - -- `openclaw/plugin-sdk` remains supported for existing external plugins. -- New and migrated bundled plugins should use channel or extension-specific - subpaths; use `core` for generic surfaces and `compat` only when broader - shared helpers are required. - -## Read-only channel inspection - -If your plugin registers a channel, prefer implementing -`plugin.config.inspectAccount(cfg, accountId)` alongside `resolveAccount(...)`. - -Why: - -- `resolveAccount(...)` is the runtime path. It is allowed to assume credentials - are fully materialized and can fail fast when required secrets are missing. -- Read-only command paths such as `openclaw status`, `openclaw status --all`, - `openclaw channels status`, `openclaw channels resolve`, and doctor/config - repair flows should not need to materialize runtime credentials just to - describe configuration. - -Recommended `inspectAccount(...)` behavior: - -- Return descriptive account state only. -- Preserve `enabled` and `configured`. -- Include credential source/status fields when relevant, such as: - - `tokenSource`, `tokenStatus` - - `botTokenSource`, `botTokenStatus` - - `appTokenSource`, `appTokenStatus` - - `signingSecretSource`, `signingSecretStatus` -- You do not need to return raw token values just to report read-only - availability. Returning `tokenStatus: "available"` (and the matching source - field) is enough for status-style commands. -- Use `configured_unavailable` when a credential is configured via SecretRef but - unavailable in the current command path. - -This lets read-only commands report “configured but unavailable in this command -path” instead of crashing or misreporting the account as not configured. - -Performance note: - -- Plugin discovery and manifest metadata use short in-process caches to reduce - bursty startup/reload work. -- Set `OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1` or - `OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1` to disable these caches. -- Tune cache windows with `OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS` and - `OPENCLAW_PLUGIN_MANIFEST_CACHE_MS`. - -## Discovery & precedence - -OpenClaw scans, in order: - -1. Config paths - -- `plugins.load.paths` (file or directory) - -2. Workspace extensions - -- `/.openclaw/extensions/*.ts` -- `/.openclaw/extensions/*/index.ts` - -3. Global extensions - -- `~/.openclaw/extensions/*.ts` -- `~/.openclaw/extensions/*/index.ts` - -4. Bundled extensions (shipped with OpenClaw, mostly disabled by default) - -- `/extensions/*` - -Most bundled plugins must be enabled explicitly via -`plugins.entries..enabled` or `openclaw plugins enable `. - -Default-on bundled plugin exceptions: - -- `device-pair` -- `phone-control` -- `talk-voice` -- active memory slot plugin (default slot: `memory-core`) - -Installed plugins are enabled by default, but can be disabled the same way. - -Hardening notes: - -- If `plugins.allow` is empty and non-bundled plugins are discoverable, OpenClaw logs a startup warning with plugin ids and sources. -- Candidate paths are safety-checked before discovery admission. OpenClaw blocks candidates when: - - extension entry resolves outside plugin root (including symlink/path traversal escapes), - - plugin root/source path is world-writable, - - path ownership is suspicious for non-bundled plugins (POSIX owner is neither current uid nor root). -- Loaded non-bundled plugins without install/load-path provenance emit a warning so you can pin trust (`plugins.allow`) or install tracking (`plugins.installs`). - -Each plugin must include a `openclaw.plugin.json` file in its root. If a path -points at a file, the plugin root is the file's directory and must contain the -manifest. - -If multiple plugins resolve to the same id, the first match in the order above -wins and lower-precedence copies are ignored. - -### Package packs - -A plugin directory may include a `package.json` with `openclaw.extensions`: - -```json -{ - "name": "my-pack", - "openclaw": { - "extensions": ["./src/safety.ts", "./src/tools.ts"] - } -} -``` - -Each entry becomes a plugin. If the pack lists multiple extensions, the plugin id -becomes `name/`. - -If your plugin imports npm deps, install them in that directory so -`node_modules` is available (`npm install` / `pnpm install`). - -Security guardrail: every `openclaw.extensions` entry must stay inside the plugin -directory after symlink resolution. Entries that escape the package directory are -rejected. - -Security note: `openclaw plugins install` installs plugin dependencies with -`npm install --ignore-scripts` (no lifecycle scripts). Keep plugin dependency -trees "pure JS/TS" and avoid packages that require `postinstall` builds. - -### Channel catalog metadata - -Channel plugins can advertise onboarding metadata via `openclaw.channel` and -install hints via `openclaw.install`. This keeps the core catalog data-free. - -Example: - -```json -{ - "name": "@openclaw/nextcloud-talk", - "openclaw": { - "extensions": ["./index.ts"], - "channel": { - "id": "nextcloud-talk", - "label": "Nextcloud Talk", - "selectionLabel": "Nextcloud Talk (self-hosted)", - "docsPath": "/channels/nextcloud-talk", - "docsLabel": "nextcloud-talk", - "blurb": "Self-hosted chat via Nextcloud Talk webhook bots.", - "order": 65, - "aliases": ["nc-talk", "nc"] - }, - "install": { - "npmSpec": "@openclaw/nextcloud-talk", - "localPath": "extensions/nextcloud-talk", - "defaultChoice": "npm" - } - } -} -``` - -OpenClaw can also merge **external channel catalogs** (for example, an MPM -registry export). Drop a JSON file at one of: - -- `~/.openclaw/mpm/plugins.json` -- `~/.openclaw/mpm/catalog.json` -- `~/.openclaw/plugins/catalog.json` - -Or point `OPENCLAW_PLUGIN_CATALOG_PATHS` (or `OPENCLAW_MPM_CATALOG_PATHS`) at -one or more JSON files (comma/semicolon/`PATH`-delimited). Each file should -contain `{ "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] }`. - -## Plugin IDs - -Default plugin ids: - -- Package packs: `package.json` `name` -- Standalone file: file base name (`~/.../voice-call.ts` → `voice-call`) - -If a plugin exports `id`, OpenClaw uses it but warns when it doesn’t match the -configured id. +See [Plugin bundles](/plugins/bundles) for the exact detection rules, mapping +behavior, and current support matrix. ## Config @@ -377,19 +141,94 @@ Fields: - `deny`: denylist (optional; deny wins) - `load.paths`: extra plugin files/dirs - `slots`: exclusive slot selectors such as `memory` and `contextEngine` -- `entries.`: per‑plugin toggles + config +- `entries.`: per-plugin toggles + config -Config changes **require a gateway restart**. +Config changes **require a gateway restart**. See +[Configuration reference](/configuration) for the full config schema. Validation rules (strict): - Unknown plugin ids in `entries`, `allow`, `deny`, or `slots` are **errors**. - Unknown `channels.` keys are **errors** unless a plugin manifest declares the channel id. -- Plugin config is validated using the JSON Schema embedded in +- Native plugin config is validated using the JSON Schema embedded in `openclaw.plugin.json` (`configSchema`). +- Compatible bundles currently do not expose native OpenClaw config schemas. - If a plugin is disabled, its config is preserved and a **warning** is emitted. +### Disabled vs missing vs invalid + +These states are intentionally different: + +- **disabled**: plugin exists, but enablement rules turned it off +- **missing**: config references a plugin id that discovery did not find +- **invalid**: plugin exists, but its config does not match the declared schema + +OpenClaw preserves config for disabled plugins so toggling them back on is not +destructive. + +## Discovery and precedence + +OpenClaw scans, in order: + +1. Config paths + +- `plugins.load.paths` (file or directory) + +2. Workspace extensions + +- `/.openclaw/extensions/*.ts` +- `/.openclaw/extensions/*/index.ts` + +3. Global extensions + +- `~/.openclaw/extensions/*.ts` +- `~/.openclaw/extensions/*/index.ts` + +4. Bundled extensions (shipped with OpenClaw; mixed default-on/default-off) + +- `/dist/extensions/*` in packaged installs +- `/dist-runtime/extensions/*` in local built checkouts +- `/extensions/*` in source/Vitest workflows + +Many bundled provider plugins are enabled by default so model catalogs/runtime +hooks stay available without extra setup. Others still require explicit +enablement via `plugins.entries..enabled` or +`openclaw plugins enable `. + +Bundled plugin runtime dependencies are owned by each plugin package. Packaged +builds stage opted-in bundled dependencies under +`dist/extensions//node_modules` instead of requiring mirrored copies in the +root package. Very large official plugins can ship as metadata-only bundled +entries and install their runtime package on demand. npm artifacts ship the +built `dist/extensions/*` tree; source `extensions/*` directories stay in source +checkouts only. + +Installed plugins are enabled by default, but can be disabled the same way. + +Workspace plugins are **disabled by default** unless you explicitly enable them +or allowlist them. This is intentional: a checked-out repo should not silently +become production gateway code. + +If multiple plugins resolve to the same id, the first match in the order above +wins and lower-precedence copies are ignored. + +### Enablement rules + +Enablement is resolved after discovery: + +- `plugins.enabled: false` disables all plugins +- `plugins.deny` always wins +- `plugins.entries..enabled: false` disables that plugin +- workspace-origin plugins are disabled by default +- allowlists restrict the active set when `plugins.allow` is non-empty +- allowlists are **id-based**, not source-based +- bundled plugins are disabled by default unless: + - the bundled id is in the built-in default-on set, or + - you explicitly enable it, or + - channel config implicitly enables the bundled channel plugin +- exclusive slots can force-enable the selected plugin for that slot + ## Plugin slots (exclusive categories) Some plugin categories are **exclusive** (only one active at a time). Use @@ -413,61 +252,39 @@ Supported exclusive slots: If multiple plugins declare `kind: "memory"` or `kind: "context-engine"`, only the selected plugin loads for that slot. Others are disabled with diagnostics. +Declare `kind` in your [plugin manifest](/plugins/manifest). -### Context engine plugins +## Plugin IDs -Context engine plugins own session context orchestration for ingest, assembly, -and compaction. Register them from your plugin with -`api.registerContextEngine(id, factory)`, then select the active engine with -`plugins.slots.contextEngine`. +Default plugin ids: -Use this when your plugin needs to replace or extend the default context -pipeline rather than just add memory search or hooks. +- Package packs: `package.json` `name` +- Standalone file: file base name (`~/.../voice-call.ts` -> `voice-call`) -## Control UI (schema + labels) +If a plugin exports `id`, OpenClaw uses it but warns when it does not match the +configured id. -The Control UI uses `config.schema` (JSON Schema + `uiHints`) to render better forms. +## Inspection -OpenClaw augments `uiHints` at runtime based on discovered plugins: - -- Adds per-plugin labels for `plugins.entries.` / `.enabled` / `.config` -- Merges optional plugin-provided config field hints under: - `plugins.entries..config.` - -If you want your plugin config fields to show good labels/placeholders (and mark secrets as sensitive), -provide `uiHints` alongside your JSON Schema in the plugin manifest. - -Example: - -```json -{ - "id": "my-plugin", - "configSchema": { - "type": "object", - "additionalProperties": false, - "properties": { - "apiKey": { "type": "string" }, - "region": { "type": "string" } - } - }, - "uiHints": { - "apiKey": { "label": "API Key", "sensitive": true }, - "region": { "label": "Region", "placeholder": "us-east-1" } - } -} +```bash +openclaw plugins inspect openai # deep detail on one plugin +openclaw plugins inspect openai --json # machine-readable +openclaw plugins list # compact inventory +openclaw plugins status # operational summary +openclaw plugins doctor # issue-focused diagnostics ``` ## CLI ```bash openclaw plugins list -openclaw plugins info +openclaw plugins inspect openclaw plugins install # copy a local file/dir into ~/.openclaw/extensions/ openclaw plugins install ./extensions/voice-call # relative path ok openclaw plugins install ./plugin.tgz # install from a local tarball openclaw plugins install ./plugin.zip # install from a local zip openclaw plugins install -l ./extensions/voice-call # link (no copy) for dev -openclaw plugins install @openclaw/voice-call # install from npm +openclaw plugins install @openclaw/voice-call # install from npm openclaw plugins install @openclaw/voice-call --pin # store exact resolved name@version openclaw plugins update openclaw plugins update --all @@ -476,10 +293,11 @@ openclaw plugins disable openclaw plugins doctor ``` -`plugins update` only works for npm installs tracked under `plugins.installs`. -If stored integrity metadata changes between updates, OpenClaw warns and asks for confirmation (use global `--yes` to bypass prompts). +See [`openclaw plugins` CLI reference](/cli/plugins) for full details on each +command (install rules, inspect output, marketplace installs, uninstall). -Plugins may also register their own top‑level commands (example: `openclaw voicecall`). +Plugins may also register their own top-level commands (example: +`openclaw voicecall`). ## Plugin API (overview) @@ -488,475 +306,32 @@ Plugins export either: - A function: `(api) => { ... }` - An object: `{ id, name, configSchema, register(api) { ... } }` -Context engine plugins can also register a runtime-owned context manager: - -```ts -export default function (api) { - api.registerContextEngine("lossless-claw", () => ({ - info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true }, - async ingest() { - return { ingested: true }; - }, - async assemble({ messages }) { - return { messages, estimatedTokens: 0 }; - }, - async compact() { - return { ok: true, compacted: false }; - }, - })); -} -``` - -Then enable it in config: - -```json5 -{ - plugins: { - slots: { - contextEngine: "lossless-claw", - }, - }, -} -``` - -## Plugin hooks - -Plugins can register hooks at runtime. This lets a plugin bundle event-driven -automation without a separate hook pack install. - -### Example - -```ts -export default function register(api) { - api.registerHook( - "command:new", - async () => { - // Hook logic here. - }, - { - name: "my-plugin.command-new", - description: "Runs when /new is invoked", - }, - ); -} -``` - -Notes: - -- Register hooks explicitly via `api.registerHook(...)`. -- Hook eligibility rules still apply (OS/bins/env/config requirements). -- Plugin-managed hooks show up in `openclaw hooks list` with `plugin:`. -- You cannot enable/disable plugin-managed hooks via `openclaw hooks`; enable/disable the plugin instead. - -### Agent lifecycle hooks (`api.on`) - -For typed runtime lifecycle hooks, use `api.on(...)`: - -```ts -export default function register(api) { - api.on( - "before_prompt_build", - (event, ctx) => { - return { - prependSystemContext: "Follow company style guide.", - }; - }, - { priority: 10 }, - ); -} -``` - -Important hooks for prompt construction: - -- `before_model_resolve`: runs before session load (`messages` are not available). Use this to deterministically override `modelOverride` or `providerOverride`. -- `before_prompt_build`: runs after session load (`messages` are available). Use this to shape prompt input. -- `before_agent_start`: legacy compatibility hook. Prefer the two explicit hooks above. - -Core-enforced hook policy: - -- Operators can disable prompt mutation hooks per plugin via `plugins.entries..hooks.allowPromptInjection: false`. -- When disabled, OpenClaw blocks `before_prompt_build` and ignores prompt-mutating fields returned from legacy `before_agent_start` while preserving legacy `modelOverride` and `providerOverride`. - -`before_prompt_build` result fields: - -- `prependContext`: prepends text to the user prompt for this run. Best for turn-specific or dynamic content. -- `systemPrompt`: full system prompt override. -- `prependSystemContext`: prepends text to the current system prompt. -- `appendSystemContext`: appends text to the current system prompt. - -Prompt build order in embedded runtime: - -1. Apply `prependContext` to the user prompt. -2. Apply `systemPrompt` override when provided. -3. Apply `prependSystemContext + current system prompt + appendSystemContext`. - -Merge and precedence notes: - -- Hook handlers run by priority (higher first). -- For merged context fields, values are concatenated in execution order. -- `before_prompt_build` values are applied before legacy `before_agent_start` fallback values. - -Migration guidance: - -- Move static guidance from `prependContext` to `prependSystemContext` (or `appendSystemContext`) so providers can cache stable system-prefix content. -- Keep `prependContext` for per-turn dynamic context that should stay tied to the user message. - -## Provider plugins (model auth) - -Plugins can register **model provider auth** flows so users can run OAuth or -API-key setup inside OpenClaw (no external scripts needed). - -Register a provider via `api.registerProvider(...)`. Each provider exposes one -or more auth methods (OAuth, API key, device code, etc.). These methods power: - -- `openclaw models auth login --provider [--method ]` - -Example: - -```ts -api.registerProvider({ - id: "acme", - label: "AcmeAI", - auth: [ - { - id: "oauth", - label: "OAuth", - kind: "oauth", - run: async (ctx) => { - // Run OAuth flow and return auth profiles. - return { - profiles: [ - { - profileId: "acme:default", - credential: { - type: "oauth", - provider: "acme", - access: "...", - refresh: "...", - expires: Date.now() + 3600 * 1000, - }, - }, - ], - defaultModel: "acme/opus-1", - }; - }, - }, - ], -}); -``` - -Notes: - -- `run` receives a `ProviderAuthContext` with `prompter`, `runtime`, - `openUrl`, and `oauth.createVpsAwareHandlers` helpers. -- Return `configPatch` when you need to add default models or provider config. -- Return `defaultModel` so `--set-default` can update agent defaults. - -### Register a messaging channel - -Plugins can register **channel plugins** that behave like built‑in channels -(WhatsApp, Telegram, etc.). Channel config lives under `channels.` and is -validated by your channel plugin code. - -```ts -const myChannel = { - id: "acmechat", - meta: { - id: "acmechat", - label: "AcmeChat", - selectionLabel: "AcmeChat (API)", - docsPath: "/channels/acmechat", - blurb: "demo channel plugin.", - aliases: ["acme"], - }, - capabilities: { chatTypes: ["direct"] }, - config: { - listAccountIds: (cfg) => Object.keys(cfg.channels?.acmechat?.accounts ?? {}), - resolveAccount: (cfg, accountId) => - cfg.channels?.acmechat?.accounts?.[accountId ?? "default"] ?? { - accountId, - }, - }, - outbound: { - deliveryMode: "direct", - sendText: async () => ({ ok: true }), - }, -}; - -export default function (api) { - api.registerChannel({ plugin: myChannel }); -} -``` - -Notes: - -- Put config under `channels.` (not `plugins.entries`). -- `meta.label` is used for labels in CLI/UI lists. -- `meta.aliases` adds alternate ids for normalization and CLI inputs. -- `meta.preferOver` lists channel ids to skip auto-enable when both are configured. -- `meta.detailLabel` and `meta.systemImage` let UIs show richer channel labels/icons. - -### Channel onboarding hooks - -Channel plugins can define optional onboarding hooks on `plugin.onboarding`: - -- `configure(ctx)` is the baseline setup flow. -- `configureInteractive(ctx)` can fully own interactive setup for both configured and unconfigured states. -- `configureWhenConfigured(ctx)` can override behavior only for already configured channels. - -Hook precedence in the wizard: - -1. `configureInteractive` (if present) -2. `configureWhenConfigured` (only when channel status is already configured) -3. fallback to `configure` - -Context details: - -- `configureInteractive` and `configureWhenConfigured` receive: - - `configured` (`true` or `false`) - - `label` (user-facing channel name used by prompts) - - plus the shared config/runtime/prompter/options fields -- Returning `"skip"` leaves selection and account tracking unchanged. -- Returning `{ cfg, accountId? }` applies config updates and records account selection. - -### Write a new messaging channel (step‑by‑step) - -Use this when you want a **new chat surface** (a "messaging channel"), not a model provider. -Model provider docs live under `/providers/*`. - -1. Pick an id + config shape - -- All channel config lives under `channels.`. -- Prefer `channels..accounts.` for multi‑account setups. - -2. Define the channel metadata - -- `meta.label`, `meta.selectionLabel`, `meta.docsPath`, `meta.blurb` control CLI/UI lists. -- `meta.docsPath` should point at a docs page like `/channels/`. -- `meta.preferOver` lets a plugin replace another channel (auto-enable prefers it). -- `meta.detailLabel` and `meta.systemImage` are used by UIs for detail text/icons. - -3. Implement the required adapters - -- `config.listAccountIds` + `config.resolveAccount` -- `capabilities` (chat types, media, threads, etc.) -- `outbound.deliveryMode` + `outbound.sendText` (for basic send) - -4. Add optional adapters as needed - -- `setup` (wizard), `security` (DM policy), `status` (health/diagnostics) -- `gateway` (start/stop/login), `mentions`, `threading`, `streaming` -- `actions` (message actions), `commands` (native command behavior) - -5. Register the channel in your plugin - -- `api.registerChannel({ plugin })` - -Minimal config example: - -```json5 -{ - channels: { - acmechat: { - accounts: { - default: { token: "ACME_TOKEN", enabled: true }, - }, - }, - }, -} -``` - -Minimal channel plugin (outbound‑only): - -```ts -const plugin = { - id: "acmechat", - meta: { - id: "acmechat", - label: "AcmeChat", - selectionLabel: "AcmeChat (API)", - docsPath: "/channels/acmechat", - blurb: "AcmeChat messaging channel.", - aliases: ["acme"], - }, - capabilities: { chatTypes: ["direct"] }, - config: { - listAccountIds: (cfg) => Object.keys(cfg.channels?.acmechat?.accounts ?? {}), - resolveAccount: (cfg, accountId) => - cfg.channels?.acmechat?.accounts?.[accountId ?? "default"] ?? { - accountId, - }, - }, - outbound: { - deliveryMode: "direct", - sendText: async ({ text }) => { - // deliver `text` to your channel here - return { ok: true }; - }, - }, -}; - -export default function (api) { - api.registerChannel({ plugin }); -} -``` - -Load the plugin (extensions dir or `plugins.load.paths`), restart the gateway, -then configure `channels.` in your config. - -### Agent tools - -See the dedicated guide: [Plugin agent tools](/plugins/agent-tools). - -### Register a gateway RPC method - -```ts -export default function (api) { - api.registerGatewayMethod("myplugin.status", ({ respond }) => { - respond(true, { ok: true }); - }); -} -``` - -### Register CLI commands - -```ts -export default function (api) { - api.registerCli( - ({ program }) => { - program.command("mycmd").action(() => { - console.log("Hello"); - }); - }, - { commands: ["mycmd"] }, - ); -} -``` - -### Register auto-reply commands - -Plugins can register custom slash commands that execute **without invoking the -AI agent**. This is useful for toggle commands, status checks, or quick actions -that don't need LLM processing. - -```ts -export default function (api) { - api.registerCommand({ - name: "mystatus", - description: "Show plugin status", - handler: (ctx) => ({ - text: `Plugin is running! Channel: ${ctx.channel}`, - }), - }); -} -``` - -Command handler context: - -- `senderId`: The sender's ID (if available) -- `channel`: The channel where the command was sent -- `isAuthorizedSender`: Whether the sender is an authorized user -- `args`: Arguments passed after the command (if `acceptsArgs: true`) -- `commandBody`: The full command text -- `config`: The current OpenClaw config - -Command options: - -- `name`: Command name (without the leading `/`) -- `nativeNames`: Optional native-command aliases for slash/menu surfaces. Use `default` for all native providers, or provider-specific keys like `discord` -- `description`: Help text shown in command lists -- `acceptsArgs`: Whether the command accepts arguments (default: false). If false and arguments are provided, the command won't match and the message falls through to other handlers -- `requireAuth`: Whether to require authorized sender (default: true) -- `handler`: Function that returns `{ text: string }` (can be async) - -Example with authorization and arguments: - -```ts -api.registerCommand({ - name: "setmode", - description: "Set plugin mode", - acceptsArgs: true, - requireAuth: true, - handler: async (ctx) => { - const mode = ctx.args?.trim() || "default"; - await saveMode(mode); - return { text: `Mode set to: ${mode}` }; - }, -}); -``` - -Notes: - -- Plugin commands are processed **before** built-in commands and the AI agent -- Commands are registered globally and work across all channels -- Command names are case-insensitive (`/MyStatus` matches `/mystatus`) -- Command names must start with a letter and contain only letters, numbers, hyphens, and underscores -- Reserved command names (like `help`, `status`, `reset`, etc.) cannot be overridden by plugins -- Duplicate command registration across plugins will fail with a diagnostic error - -### Register background services - -```ts -export default function (api) { - api.registerService({ - id: "my-service", - start: () => api.logger.info("ready"), - stop: () => api.logger.info("bye"), - }); -} -``` - -## Naming conventions - -- Gateway methods: `pluginId.action` (example: `voicecall.status`) -- Tools: `snake_case` (example: `voice_call`) -- CLI commands: kebab or camel, but avoid clashing with core commands - -## Skills - -Plugins can ship a skill in the repo (`skills//SKILL.md`). -Enable it with `plugins.entries..enabled` (or other config gates) and ensure -it’s present in your workspace/managed skills locations. - -## Distribution (npm) - -Recommended packaging: - -- Main package: `openclaw` (this repo) -- Plugins: separate npm packages under `@openclaw/*` (example: `@openclaw/voice-call`) - -Publishing contract: - -- Plugin `package.json` must include `openclaw.extensions` with one or more entry files. -- Entry files can be `.js` or `.ts` (jiti loads TS at runtime). -- `openclaw plugins install ` uses `npm pack`, extracts into `~/.openclaw/extensions//`, and enables it in config. -- Config key stability: scoped packages are normalized to the **unscoped** id for `plugins.entries.*`. - -## Example plugin: Voice Call - -This repo includes a voice‑call plugin (Twilio or log fallback): - -- Source: `extensions/voice-call` -- Skill: `skills/voice-call` -- CLI: `openclaw voicecall start|status` -- Tool: `voice_call` -- RPC: `voicecall.start`, `voicecall.status` -- Config (twilio): `provider: "twilio"` + `twilio.accountSid/authToken/from` (optional `statusCallbackUrl`, `twimlUrl`) -- Config (dev): `provider: "log"` (no network) - -See [Voice Call](/plugins/voice-call) and `extensions/voice-call/README.md` for setup and usage. - -## Safety notes - -Plugins run in-process with the Gateway. Treat them as trusted code: - -- Only install plugins you trust. -- Prefer `plugins.allow` allowlists. -- Restart the Gateway after changes. - -## Testing plugins - -Plugins can (and should) ship tests: - -- In-repo plugins can keep Vitest tests under `src/**` (example: `src/plugins/voice-call.plugin.test.ts`). -- Separately published plugins should run their own CI (lint/build/test) and validate `openclaw.extensions` points at the built entrypoint (`dist/index.js`). +`register(api)` is where plugins attach behavior. Common registrations include: + +- `registerTool` +- `registerHook` +- `on(...)` for typed lifecycle hooks +- `registerChannel` +- `registerProvider` +- `registerSpeechProvider` +- `registerMediaUnderstandingProvider` +- `registerWebSearchProvider` +- `registerHttpRoute` +- `registerCommand` +- `registerCli` +- `registerContextEngine` +- `registerService` + +See [Plugin manifest](/plugins/manifest) for the manifest file format. + +## Further reading + +- [Plugin architecture and internals](/plugins/architecture) -- capability model, + ownership model, contracts, load pipeline, runtime helpers, and developer API + reference +- [Building extensions](/plugins/building-extensions) +- [Plugin bundles](/plugins/bundles) +- [Plugin manifest](/plugins/manifest) +- [Plugin agent tools](/plugins/agent-tools) +- [Capability Cookbook](/tools/capability-cookbook) +- [Community plugins](/plugins/community) diff --git a/docs/tools/skills-config.md b/docs/tools/skills-config.md index 589d464bb13..83242afaf5d 100644 --- a/docs/tools/skills-config.md +++ b/docs/tools/skills-config.md @@ -24,7 +24,7 @@ All skills-related configuration lives under `skills` in `~/.openclaw/openclaw.j nodeManager: "npm", // npm | pnpm | yarn | bun (Gateway runtime still Node; bun not recommended) }, entries: { - "nano-banana-pro": { + "image-lab": { enabled: true, apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string env: { @@ -38,6 +38,15 @@ All skills-related configuration lives under `skills` in `~/.openclaw/openclaw.j } ``` +For built-in image generation/editing, prefer `agents.defaults.imageGenerationModel` +plus the core `image_generate` tool. `skills.entries.*` is only for custom or +third-party skill workflows. + +Examples: + +- Native Nano Banana-style setup: `agents.defaults.imageGenerationModel.primary: "google/gemini-3-pro-image-preview"` +- Native fal setup: `agents.defaults.imageGenerationModel.primary: "fal/fal-ai/flux/dev"` + ## Fields - `allowBundled`: optional allowlist for **bundled** skills only. When set, only diff --git a/docs/tools/skills.md b/docs/tools/skills.md index 05369677b89..5b91d79af59 100644 --- a/docs/tools/skills.md +++ b/docs/tools/skills.md @@ -81,8 +81,8 @@ that up as `/skills` on the next session. ```markdown --- -name: nano-banana-pro -description: Generate or edit images via Gemini 3 Pro Image +name: image-lab +description: Generate or edit images via a provider-backed image workflow --- ``` @@ -109,8 +109,8 @@ OpenClaw **filters skills at load time** using `metadata` (single-line JSON): ```markdown --- -name: nano-banana-pro -description: Generate or edit images via Gemini 3 Pro Image +name: image-lab +description: Generate or edit images via a provider-backed image workflow metadata: { "openclaw": @@ -194,7 +194,7 @@ Bundled/managed skills can be toggled and supplied with env values: { skills: { entries: { - "nano-banana-pro": { + "image-lab": { enabled: true, apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string env: { @@ -214,6 +214,10 @@ Bundled/managed skills can be toggled and supplied with env values: Note: if the skill name contains hyphens, quote the key (JSON5 allows quoted keys). +If you want stock image generation/editing inside OpenClaw itself, use the core +`image_generate` tool with `agents.defaults.imageGenerationModel` instead of a +bundled skill. Skill examples here are for custom or third-party workflows. + Config keys match the **skill name** by default. If a skill defines `metadata.openclaw.skillKey`, use that key under `skills.entries`. diff --git a/docs/tools/slash-commands.md b/docs/tools/slash-commands.md index d792398f1fa..c62612d312b 100644 --- a/docs/tools/slash-commands.md +++ b/docs/tools/slash-commands.md @@ -14,7 +14,7 @@ The host-only bash chat command uses `! ` (with `/bash ` as an alias). There are two related systems: - **Commands**: standalone `/...` messages. -- **Directives**: `/think`, `/verbose`, `/reasoning`, `/elevated`, `/exec`, `/model`, `/queue`. +- **Directives**: `/think`, `/fast`, `/verbose`, `/reasoning`, `/elevated`, `/exec`, `/model`, `/queue`. - Directives are stripped from the message before the model sees it. - In normal chat messages (not directive-only), they are treated as “inline hints” and do **not** persist session settings. - In directive-only messages (the message contains only directives), they persist to the session and reply with an acknowledgement. @@ -36,6 +36,8 @@ They run immediately, are stripped before the model sees the message, and the re bash: false, bashForegroundMs: 2000, config: false, + mcp: false, + plugins: false, debug: false, restart: false, allowFrom: { @@ -59,6 +61,8 @@ They run immediately, are stripped before the model sees the message, and the re - `commands.bash` (default `false`) enables `! ` to run host shell commands (`/bash ` is an alias; requires `tools.elevated` allowlists). - `commands.bashForegroundMs` (default `2000`) controls how long bash waits before switching to background mode (`0` backgrounds immediately). - `commands.config` (default `false`) enables `/config` (reads/writes `openclaw.json`). +- `commands.mcp` (default `false`) enables `/mcp` (reads/writes OpenClaw-managed MCP config under `mcp.servers`). +- `commands.plugins` (default `false`) enables `/plugins` (plugin discovery/status plus enable/disable toggles). - `commands.debug` (default `false`) enables `/debug` (runtime-only overrides). - `commands.allowFrom` (optional) sets a per-provider allowlist for command authorization. When configured, it is the only authorization source for commands and directives (channel allowlists/pairing and `commands.useAccessGroups` @@ -76,6 +80,7 @@ Text + native (when enabled): - `/allowlist` (list/add/remove allowlist entries) - `/approve allow-once|allow-always|deny` (resolve exec approval prompts) - `/context [list|detail|json]` (explain “context”; `detail` shows per-file + per-tool + per-skill + system prompt size) +- `/btw ` (ask an ephemeral side question about the current session without changing future session context; see [/tools/btw](/tools/btw)) - `/export-session [path]` (alias: `/export`) (export current session to HTML with full system prompt) - `/whoami` (show your sender id; alias: `/id`) - `/session idle ` (manage inactivity auto-unfocus for focused thread bindings) @@ -89,6 +94,8 @@ Text + native (when enabled): - `/steer ` (steer a running sub-agent immediately: in-run when possible, otherwise abort current work and restart on the steer message) - `/tell ` (alias for `/steer`) - `/config show|get|set|unset` (persist config to disk, owner-only; requires `commands.config: true`) +- `/mcp show|get|set|unset` (manage OpenClaw MCP server config, owner-only; requires `commands.mcp: true`) +- `/plugins list|show|get|enable|disable` (inspect discovered plugins and toggle enablement, owner-only for writes; requires `commands.plugins: true`) - `/debug show|set|unset|reset` (runtime overrides, owner-only; requires `commands.debug: true`) - `/usage off|tokens|full|cost` (per-response usage footer or local cost summary) - `/tts off|always|inbound|tagged|status|provider|limit|summary|audio` (control TTS; see [/tts](/tts)) @@ -102,6 +109,7 @@ Text + native (when enabled): - `/send on|off|inherit` (owner-only) - `/reset` or `/new [model]` (optional model hint; remainder is passed through) - `/think ` (dynamic choices by model/provider; aliases: `/thinking`, `/t`) +- `/fast status|on|off` (omitting the arg shows the current effective fast-mode state) - `/verbose on|full|off` (alias: `/v`) - `/reasoning on|off|stream` (alias: `/reason`; when on, sends a separate message prefixed `Reasoning:`; `stream` = Telegram draft only) - `/elevated on|off|ask|full` (alias: `/elev`; `full` skips exec approvals) @@ -130,6 +138,7 @@ Notes: - Discord thread-binding commands (`/focus`, `/unfocus`, `/agents`, `/session idle`, `/session max-age`) require effective thread bindings to be enabled (`session.threadBindings.enabled` and/or `channels.discord.threadBindings.enabled`). - ACP command reference and runtime behavior: [ACP Agents](/tools/acp-agents). - `/verbose` is meant for debugging and extra visibility; keep it **off** in normal use. +- `/fast on|off` persists a session override. Use the Sessions UI `inherit` option to clear it and fall back to config defaults. - Tool failure summaries are still shown when relevant, but detailed failure text is only included when `/verbose` is `on` or `full`. - `/reasoning` (and `/verbose`) are risky in group settings: they may reveal internal reasoning or tool output you did not intend to expose. Prefer leaving them off, especially in group chats. - **Fast path:** command-only messages from allowlisted senders are handled immediately (bypass queue + model). @@ -211,6 +220,44 @@ Notes: - Config is validated before write; invalid changes are rejected. - `/config` updates persist across restarts. +## MCP updates + +`/mcp` writes OpenClaw-managed MCP server definitions under `mcp.servers`. Owner-only. Disabled by default; enable with `commands.mcp: true`. + +Examples: + +```text +/mcp show +/mcp show context7 +/mcp set context7={"command":"uvx","args":["context7-mcp"]} +/mcp unset context7 +``` + +Notes: + +- `/mcp` stores config in OpenClaw config, not Pi-owned project settings. +- Runtime adapters decide which transports are actually executable. + +## Plugin updates + +`/plugins` lets operators inspect discovered plugins and toggle enablement in config. Read-only flows can use `/plugin` as an alias. Disabled by default; enable with `commands.plugins: true`. + +Examples: + +```text +/plugins +/plugins list +/plugin show context7 +/plugins enable context7 +/plugins disable context7 +``` + +Notes: + +- `/plugins list` and `/plugins show` use real plugin discovery against the current workspace plus on-disk config. +- `/plugins enable|disable` updates plugin config only; it does not install or uninstall plugins. +- After enable/disable changes, restart the gateway to apply them. + ## Surface notes - **Text commands** run in the normal chat session (DMs share `main`, groups have their own session). @@ -221,3 +268,27 @@ Notes: - **`/stop`** targets the active chat session so it can abort the current run. - **Slack:** `channels.slack.slashCommand` is still supported for a single `/openclaw`-style command. If you enable `commands.native`, you must create one Slack slash command per built-in command (same names as `/help`). Command argument menus for Slack are delivered as ephemeral Block Kit buttons. - Slack native exception: register `/agentstatus` (not `/status`) because Slack reserves `/status`. Text `/status` still works in Slack messages. + +## BTW side questions + +`/btw` is a quick **side question** about the current session. + +Unlike normal chat: + +- it uses the current session as background context, +- it runs as a separate **tool-less** one-shot call, +- it does not change future session context, +- it is not written to transcript history, +- it is delivered as a live side result instead of a normal assistant message. + +That makes `/btw` useful when you want a temporary clarification while the main +task keeps going. + +Example: + +```text +/btw what are we doing right now? +``` + +See [BTW Side Questions](/tools/btw) for the full behavior and client UX +details. diff --git a/docs/tools/thinking.md b/docs/tools/thinking.md index 9a2fdc87ea6..045911c92b2 100644 --- a/docs/tools/thinking.md +++ b/docs/tools/thinking.md @@ -1,7 +1,7 @@ --- -summary: "Directive syntax for /think + /verbose and how they affect model reasoning" +summary: "Directive syntax for /think, /fast, /verbose, and reasoning visibility" read_when: - - Adjusting thinking or verbose directive parsing or defaults + - Adjusting thinking, fast-mode, or verbose directive parsing or defaults title: "Thinking Levels" --- @@ -42,6 +42,21 @@ title: "Thinking Levels" - **Embedded Pi**: the resolved level is passed to the in-process Pi agent runtime. +## Fast mode (/fast) + +- Levels: `on|off`. +- Directive-only message toggles a session fast-mode override and replies `Fast mode enabled.` / `Fast mode disabled.`. +- Send `/fast` (or `/fast status`) with no mode to see the current effective fast-mode state. +- OpenClaw resolves fast mode in this order: + 1. Inline/directive-only `/fast on|off` + 2. Session override + 3. Per-model config: `agents.defaults.models["/"].params.fastMode` + 4. Fallback: `off` +- For `openai/*`, fast mode applies the OpenAI fast profile: `service_tier=priority` when supported, plus low reasoning effort and low text verbosity. +- For `openai-codex/*`, fast mode applies the same low-latency profile on Codex Responses. OpenClaw keeps one shared `/fast` toggle across both auth paths. +- For direct `anthropic/*` API-key requests, fast mode maps to Anthropic service tiers: `/fast on` sets `service_tier=auto`, `/fast off` sets `service_tier=standard_only`. +- Anthropic fast mode is API-key only. OpenClaw skips Anthropic service-tier injection for Claude setup-token / OAuth auth and for non-Anthropic proxy base URLs. + ## Verbose directives (/verbose or /v) - Levels: `on` (minimal) | `full` | `off` (default). diff --git a/docs/tools/web.md b/docs/tools/web.md index e77d046ce5b..0e30c6c9c7c 100644 --- a/docs/tools/web.md +++ b/docs/tools/web.md @@ -1,5 +1,5 @@ --- -summary: "Web search + fetch tools (Brave, Gemini, Grok, Kimi, and Perplexity providers)" +summary: "Web search + fetch tools (Brave, Firecrawl, Gemini, Grok, Kimi, and Perplexity providers)" read_when: - You want to enable web_search or web_fetch - You need provider API key setup @@ -11,7 +11,7 @@ title: "Web Tools" OpenClaw ships two lightweight web tools: -- `web_search` — Search the web using Brave Search API, Gemini with Google Search grounding, Grok, Kimi, or Perplexity Search API. +- `web_search` — Search the web using Brave Search API, Firecrawl Search, Gemini with Google Search grounding, Grok, Kimi, or Perplexity Search API. - `web_fetch` — HTTP fetch + readable extraction (HTML → markdown/text). These are **not** browser automation. For JS-heavy sites or logins, use the @@ -24,28 +24,31 @@ These are **not** browser automation. For JS-heavy sites or logins, use the - `web_fetch` does a plain HTTP GET and extracts readable content (HTML → markdown/text). It does **not** execute JavaScript. - `web_fetch` is enabled by default (unless explicitly disabled). +- The bundled Firecrawl plugin also adds `firecrawl_search` and `firecrawl_scrape` when enabled. See [Brave Search setup](/brave-search) and [Perplexity Search setup](/perplexity) for provider-specific details. ## Choosing a search provider -| Provider | Result shape | Provider-specific filters | Notes | API key | -| ------------------------- | ---------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------- | -| **Brave Search API** | Structured results with snippets | `country`, `language`, `ui_lang`, time | Supports Brave `llm-context` mode | `BRAVE_API_KEY` | -| **Gemini** | AI-synthesized answers + citations | — | Uses Google Search grounding | `GEMINI_API_KEY` | -| **Grok** | AI-synthesized answers + citations | — | Uses xAI web-grounded responses | `XAI_API_KEY` | -| **Kimi** | AI-synthesized answers + citations | — | Uses Moonshot web search | `KIMI_API_KEY` / `MOONSHOT_API_KEY` | -| **Perplexity Search API** | Structured results with snippets | `country`, `language`, time, `domain_filter` | Supports content extraction controls; OpenRouter uses Sonar compatibility path | `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` | +| Provider | Result shape | Provider-specific filters | Notes | API key | +| ------------------------- | ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------- | +| **Brave Search API** | Structured results with snippets | `country`, `language`, `ui_lang`, time | Supports Brave `llm-context` mode | `BRAVE_API_KEY` | +| **Firecrawl Search** | Structured results with snippets | Use `firecrawl_search` for Firecrawl-specific search options | Best for pairing search with Firecrawl scraping/extraction | `FIRECRAWL_API_KEY` | +| **Gemini** | AI-synthesized answers + citations | — | Uses Google Search grounding | `GEMINI_API_KEY` | +| **Grok** | AI-synthesized answers + citations | — | Uses xAI web-grounded responses | `XAI_API_KEY` | +| **Kimi** | AI-synthesized answers + citations | — | Uses Moonshot web search | `KIMI_API_KEY` / `MOONSHOT_API_KEY` | +| **Perplexity Search API** | Structured results with snippets | `country`, `language`, time, `domain_filter` | Supports content extraction controls; OpenRouter uses Sonar compatibility path | `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` | ### Auto-detection The table above is alphabetical. If no `provider` is explicitly set, runtime auto-detection checks providers in this order: -1. **Brave** — `BRAVE_API_KEY` env var or `tools.web.search.apiKey` config -2. **Gemini** — `GEMINI_API_KEY` env var or `tools.web.search.gemini.apiKey` config -3. **Grok** — `XAI_API_KEY` env var or `tools.web.search.grok.apiKey` config -4. **Kimi** — `KIMI_API_KEY` / `MOONSHOT_API_KEY` env var or `tools.web.search.kimi.apiKey` config -5. **Perplexity** — `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey` config +1. **Brave** — `BRAVE_API_KEY` env var or `plugins.entries.brave.config.webSearch.apiKey` +2. **Gemini** — `GEMINI_API_KEY` env var or `plugins.entries.google.config.webSearch.apiKey` +3. **Grok** — `XAI_API_KEY` env var or `plugins.entries.xai.config.webSearch.apiKey` +4. **Kimi** — `KIMI_API_KEY` / `MOONSHOT_API_KEY` env var or `plugins.entries.moonshot.config.webSearch.apiKey` +5. **Perplexity** — `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `plugins.entries.perplexity.config.webSearch.apiKey` +6. **Firecrawl** — `FIRECRAWL_API_KEY` env var or `plugins.entries.firecrawl.config.webSearch.apiKey` If no keys are found, it falls back to Brave (you'll get a missing-key error prompting you to configure one). @@ -65,8 +68,8 @@ Use `openclaw configure --section web` to set up your API key and choose a provi 2. In the dashboard, choose the **Search** plan and generate an API key. 3. Run `openclaw configure --section web` to store the key in config, or set `BRAVE_API_KEY` in your environment. -Each Brave plan includes **$5/month in free credit** (renewing). The Search -plan costs $5 per 1,000 requests, so the credit covers 1,000 queries/month. Set +Each Brave plan includes **\$5/month in free credit** (renewing). The Search +plan costs \$5 per 1,000 requests, so the credit covers 1,000 queries/month. Set your usage limit in the Brave dashboard to avoid unexpected charges. See the [Brave API portal](https://brave.com/search/api/) for current plans and pricing. @@ -77,7 +80,10 @@ pricing. 2. Generate an API key in the dashboard 3. Run `openclaw configure --section web` to store the key in config, or set `PERPLEXITY_API_KEY` in your environment. -For legacy Sonar/OpenRouter compatibility, set `OPENROUTER_API_KEY` instead, or configure `tools.web.search.perplexity.apiKey` with an `sk-or-...` key. Setting `tools.web.search.perplexity.baseUrl` or `model` also opts Perplexity back into the chat-completions compatibility path. +For legacy Sonar/OpenRouter compatibility, set `OPENROUTER_API_KEY` instead, or configure `plugins.entries.perplexity.config.webSearch.apiKey` with an `sk-or-...` key. Setting `plugins.entries.perplexity.config.webSearch.baseUrl` or `model` also opts Perplexity back into the chat-completions compatibility path. + +Provider-specific web search config now lives under `plugins.entries..config.webSearch.*`. +Legacy `tools.web.search.*` provider paths still load through a compatibility shim for one release, but they should not be used in new configs. See [Perplexity Search API Docs](https://docs.perplexity.ai/guides/search-quickstart) for more details. @@ -85,17 +91,19 @@ See [Perplexity Search API Docs](https://docs.perplexity.ai/guides/search-quicks **Via config:** run `openclaw configure --section web`. It stores the key under the provider-specific config path: -- Brave: `tools.web.search.apiKey` -- Gemini: `tools.web.search.gemini.apiKey` -- Grok: `tools.web.search.grok.apiKey` -- Kimi: `tools.web.search.kimi.apiKey` -- Perplexity: `tools.web.search.perplexity.apiKey` +- Brave: `plugins.entries.brave.config.webSearch.apiKey` +- Firecrawl: `plugins.entries.firecrawl.config.webSearch.apiKey` +- Gemini: `plugins.entries.google.config.webSearch.apiKey` +- Grok: `plugins.entries.xai.config.webSearch.apiKey` +- Kimi: `plugins.entries.moonshot.config.webSearch.apiKey` +- Perplexity: `plugins.entries.perplexity.config.webSearch.apiKey` All of these fields also support SecretRef objects. **Via environment:** set provider env vars in the Gateway process environment: - Brave: `BRAVE_API_KEY` +- Firecrawl: `FIRECRAWL_API_KEY` - Gemini: `GEMINI_API_KEY` - Grok: `XAI_API_KEY` - Kimi: `KIMI_API_KEY` or `MOONSHOT_API_KEY` @@ -109,31 +117,86 @@ For a gateway install, put these in `~/.openclaw/.env` (or your service environm ```json5 { + plugins: { + entries: { + brave: { + config: { + webSearch: { + apiKey: "YOUR_BRAVE_API_KEY", // optional if BRAVE_API_KEY is set // pragma: allowlist secret + }, + }, + }, + }, + }, tools: { web: { search: { enabled: true, provider: "brave", - apiKey: "YOUR_BRAVE_API_KEY", // optional if BRAVE_API_KEY is set // pragma: allowlist secret }, }, }, } ``` +**Firecrawl Search:** + +```json5 +{ + plugins: { + entries: { + firecrawl: { + enabled: true, + }, + }, + }, + tools: { + web: { + search: { + enabled: true, + provider: "firecrawl", + }, + }, + }, + plugins: { + entries: { + firecrawl: { + enabled: true, + config: { + webSearch: { + apiKey: "fc-...", // optional if FIRECRAWL_API_KEY is set + baseUrl: "https://api.firecrawl.dev", + }, + }, + }, + }, + }, +} +``` + +When you choose Firecrawl in onboarding or `openclaw configure --section web`, OpenClaw enables the bundled Firecrawl plugin automatically so `web_search`, `firecrawl_search`, and `firecrawl_scrape` are all available. + **Brave LLM Context mode:** ```json5 { + plugins: { + entries: { + brave: { + config: { + webSearch: { + apiKey: "YOUR_BRAVE_API_KEY", // optional if BRAVE_API_KEY is set // pragma: allowlist secret + mode: "llm-context", + }, + }, + }, + }, + }, tools: { web: { search: { enabled: true, provider: "brave", - apiKey: "YOUR_BRAVE_API_KEY", // optional if BRAVE_API_KEY is set // pragma: allowlist secret - brave: { - mode: "llm-context", - }, }, }, }, @@ -148,14 +211,22 @@ In this mode, `country` and `language` / `search_lang` still work, but `ui_lang` ```json5 { + plugins: { + entries: { + perplexity: { + config: { + webSearch: { + apiKey: "pplx-...", // optional if PERPLEXITY_API_KEY is set + }, + }, + }, + }, + }, tools: { web: { search: { enabled: true, provider: "perplexity", - perplexity: { - apiKey: "pplx-...", // optional if PERPLEXITY_API_KEY is set - }, }, }, }, @@ -166,16 +237,24 @@ In this mode, `country` and `language` / `search_lang` still work, but `ui_lang` ```json5 { + plugins: { + entries: { + perplexity: { + config: { + webSearch: { + apiKey: "", // optional if OPENROUTER_API_KEY is set + baseUrl: "https://openrouter.ai/api/v1", + model: "perplexity/sonar-pro", + }, + }, + }, + }, + }, tools: { web: { search: { enabled: true, provider: "perplexity", - perplexity: { - apiKey: "", // optional if OPENROUTER_API_KEY is set - baseUrl: "https://openrouter.ai/api/v1", - model: "perplexity/sonar-pro", - }, }, }, }, @@ -191,22 +270,30 @@ which returns AI-synthesized answers backed by live Google Search results with c 1. Go to [Google AI Studio](https://aistudio.google.com/apikey) 2. Create an API key -3. Set `GEMINI_API_KEY` in the Gateway environment, or configure `tools.web.search.gemini.apiKey` +3. Set `GEMINI_API_KEY` in the Gateway environment, or configure `plugins.entries.google.config.webSearch.apiKey` ### Setting up Gemini search ```json5 { + plugins: { + entries: { + google: { + config: { + webSearch: { + // API key (optional if GEMINI_API_KEY is set) + apiKey: "AIza...", + // Model (defaults to "gemini-2.5-flash") + model: "gemini-2.5-flash", + }, + }, + }, + }, + }, tools: { web: { search: { provider: "gemini", - gemini: { - // API key (optional if GEMINI_API_KEY is set) - apiKey: "AIza...", - // Model (defaults to "gemini-2.5-flash") - model: "gemini-2.5-flash", - }, }, }, }, @@ -233,11 +320,12 @@ Search the web using your configured provider. - `tools.web.search.enabled` must not be `false` (default: enabled) - API key for your chosen provider: - - **Brave**: `BRAVE_API_KEY` or `tools.web.search.apiKey` - - **Gemini**: `GEMINI_API_KEY` or `tools.web.search.gemini.apiKey` - - **Grok**: `XAI_API_KEY` or `tools.web.search.grok.apiKey` - - **Kimi**: `KIMI_API_KEY`, `MOONSHOT_API_KEY`, or `tools.web.search.kimi.apiKey` - - **Perplexity**: `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `tools.web.search.perplexity.apiKey` + - **Brave**: `BRAVE_API_KEY` or `plugins.entries.brave.config.webSearch.apiKey` + - **Firecrawl**: `FIRECRAWL_API_KEY` or `plugins.entries.firecrawl.config.webSearch.apiKey` + - **Gemini**: `GEMINI_API_KEY` or `plugins.entries.google.config.webSearch.apiKey` + - **Grok**: `XAI_API_KEY` or `plugins.entries.xai.config.webSearch.apiKey` + - **Kimi**: `KIMI_API_KEY`, `MOONSHOT_API_KEY`, or `plugins.entries.moonshot.config.webSearch.apiKey` + - **Perplexity**: `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `plugins.entries.perplexity.config.webSearch.apiKey` - All provider key fields above support SecretRef objects. ### Config @@ -260,10 +348,10 @@ Search the web using your configured provider. ### Tool parameters -All parameters work for Brave and for native Perplexity Search API unless noted. +Parameters depend on the selected provider. Perplexity's OpenRouter / Sonar compatibility path supports only `query` and `freshness`. -If you set `tools.web.search.perplexity.baseUrl` / `model`, use `OPENROUTER_API_KEY`, or configure an `sk-or-...` key, Search API-only filters return explicit errors. +If you set `plugins.entries.perplexity.config.webSearch.baseUrl` / `model`, use `OPENROUTER_API_KEY`, or configure an `sk-or-...` key under `plugins.entries.perplexity.config.webSearch.apiKey`, Search API-only filters return explicit errors. | Parameter | Description | | --------------------- | ----------------------------------------------------- | @@ -279,6 +367,8 @@ If you set `tools.web.search.perplexity.baseUrl` / `model`, use `OPENROUTER_API_ | `max_tokens` | Total content budget, default 25000 (Perplexity only) | | `max_tokens_per_page` | Per-page token limit, default 2048 (Perplexity only) | +Firecrawl `web_search` supports `query` and `count`. For Firecrawl-specific controls like `sources`, `categories`, result scraping, or scrape timeout, use `firecrawl_search` from the bundled Firecrawl plugin. + **Examples:** ```javascript diff --git a/docs/tts.md b/docs/tts.md index 682bbfbd53a..4fe0da77e0a 100644 --- a/docs/tts.md +++ b/docs/tts.md @@ -9,26 +9,27 @@ title: "Text-to-Speech" # Text-to-speech (TTS) -OpenClaw can convert outbound replies into audio using ElevenLabs, OpenAI, or Edge TTS. +OpenClaw can convert outbound replies into audio using ElevenLabs, Microsoft, or OpenAI. It works anywhere OpenClaw can send audio; Telegram gets a round voice-note bubble. ## Supported services - **ElevenLabs** (primary or fallback provider) +- **Microsoft** (primary or fallback provider; current bundled implementation uses `node-edge-tts`, default when no API keys) - **OpenAI** (primary or fallback provider; also used for summaries) -- **Edge TTS** (primary or fallback provider; uses `node-edge-tts`, default when no API keys) -### Edge TTS notes +### Microsoft speech notes -Edge TTS uses Microsoft Edge's online neural TTS service via the `node-edge-tts` -library. It's a hosted service (not local), uses Microsoft’s endpoints, and does -not require an API key. `node-edge-tts` exposes speech configuration options and -output formats, but not all options are supported by the Edge service. citeturn2search0 +The bundled Microsoft speech provider currently uses Microsoft Edge's online +neural TTS service via the `node-edge-tts` library. It's a hosted service (not +local), uses Microsoft endpoints, and does not require an API key. +`node-edge-tts` exposes speech configuration options and output formats, but +not all options are supported by the service. Legacy config and directive input +using `edge` still works and is normalized to `microsoft`. -Because Edge TTS is a public web service without a published SLA or quota, treat it -as best-effort. If you need guaranteed limits and support, use OpenAI or ElevenLabs. -Microsoft's Speech REST API documents a 10‑minute audio limit per request; Edge TTS -does not publish limits, so assume similar or lower limits. citeturn0search3 +Because this path is a public web service without a published SLA or quota, +treat it as best-effort. If you need guaranteed limits and support, use OpenAI +or ElevenLabs. ## Optional keys @@ -37,8 +38,9 @@ If you want OpenAI or ElevenLabs: - `ELEVENLABS_API_KEY` (or `XI_API_KEY`) - `OPENAI_API_KEY` -Edge TTS does **not** require an API key. If no API keys are found, OpenClaw defaults -to Edge TTS (unless disabled via `messages.tts.edge.enabled=false`). +Microsoft speech does **not** require an API key. If no API keys are found, +OpenClaw defaults to Microsoft (unless disabled via +`messages.tts.microsoft.enabled=false` or `messages.tts.edge.enabled=false`). If multiple providers are configured, the selected provider is used first and the others are fallback options. Auto-summary uses the configured `summaryModel` (or `agents.defaults.model.primary`), @@ -58,7 +60,7 @@ so that provider must also be authenticated if you enable summaries. No. Auto‑TTS is **off** by default. Enable it in config with `messages.tts.auto` or per session with `/tts always` (alias: `/tts on`). -Edge TTS **is** enabled by default once TTS is on, and is used automatically +Microsoft speech **is** enabled by default once TTS is on, and is used automatically when no OpenAI or ElevenLabs API keys are available. ## Config @@ -118,15 +120,15 @@ Full schema is in [Gateway configuration](/gateway/configuration). } ``` -### Edge TTS primary (no API key) +### Microsoft primary (no API key) ```json5 { messages: { tts: { auto: "always", - provider: "edge", - edge: { + provider: "microsoft", + microsoft: { enabled: true, voice: "en-US-MichelleNeural", lang: "en-US", @@ -139,13 +141,13 @@ Full schema is in [Gateway configuration](/gateway/configuration). } ``` -### Disable Edge TTS +### Disable Microsoft speech ```json5 { messages: { tts: { - edge: { + microsoft: { enabled: false, }, }, @@ -205,9 +207,10 @@ Then run: - `tagged` only sends audio when the reply includes `[[tts]]` tags. - `enabled`: legacy toggle (doctor migrates this to `auto`). - `mode`: `"final"` (default) or `"all"` (includes tool/block replies). -- `provider`: `"elevenlabs"`, `"openai"`, or `"edge"` (fallback is automatic). +- `provider`: speech provider id such as `"elevenlabs"`, `"microsoft"`, or `"openai"` (fallback is automatic). - If `provider` is **unset**, OpenClaw prefers `openai` (if key), then `elevenlabs` (if key), - otherwise `edge`. + otherwise `microsoft`. +- Legacy `provider: "edge"` still works and is normalized to `microsoft`. - `summaryModel`: optional cheap model for auto-summary; defaults to `agents.defaults.model.primary`. - Accepts `provider/model` or a configured model alias. - `modelOverrides`: allow the model to emit TTS directives (on by default). @@ -227,15 +230,16 @@ Then run: - `elevenlabs.applyTextNormalization`: `auto|on|off` - `elevenlabs.languageCode`: 2-letter ISO 639-1 (e.g. `en`, `de`) - `elevenlabs.seed`: integer `0..4294967295` (best-effort determinism) -- `edge.enabled`: allow Edge TTS usage (default `true`; no API key). -- `edge.voice`: Edge neural voice name (e.g. `en-US-MichelleNeural`). -- `edge.lang`: language code (e.g. `en-US`). -- `edge.outputFormat`: Edge output format (e.g. `audio-24khz-48kbitrate-mono-mp3`). - - See Microsoft Speech output formats for valid values; not all formats are supported by Edge. -- `edge.rate` / `edge.pitch` / `edge.volume`: percent strings (e.g. `+10%`, `-5%`). -- `edge.saveSubtitles`: write JSON subtitles alongside the audio file. -- `edge.proxy`: proxy URL for Edge TTS requests. -- `edge.timeoutMs`: request timeout override (ms). +- `microsoft.enabled`: allow Microsoft speech usage (default `true`; no API key). +- `microsoft.voice`: Microsoft neural voice name (e.g. `en-US-MichelleNeural`). +- `microsoft.lang`: language code (e.g. `en-US`). +- `microsoft.outputFormat`: Microsoft output format (e.g. `audio-24khz-48kbitrate-mono-mp3`). + - See Microsoft Speech output formats for valid values; not all formats are supported by the bundled Edge-backed transport. +- `microsoft.rate` / `microsoft.pitch` / `microsoft.volume`: percent strings (e.g. `+10%`, `-5%`). +- `microsoft.saveSubtitles`: write JSON subtitles alongside the audio file. +- `microsoft.proxy`: proxy URL for Microsoft speech requests. +- `microsoft.timeoutMs`: request timeout override (ms). +- `edge.*`: legacy alias for the same Microsoft settings. ## Model-driven overrides (default on) @@ -260,7 +264,7 @@ Here you go. Available directive keys (when enabled): -- `provider` (`openai` | `elevenlabs` | `edge`, requires `allowProvider: true`) +- `provider` (registered speech provider id, for example `openai`, `elevenlabs`, or `microsoft`; requires `allowProvider: true`) - `voice` (OpenAI voice) or `voiceId` (ElevenLabs) - `model` (OpenAI TTS model or ElevenLabs model id) - `stability`, `similarityBoost`, `style`, `speed`, `useSpeakerBoost` @@ -319,13 +323,12 @@ These override `messages.tts.*` for that host. - 48kHz / 64kbps is a good voice-note tradeoff and required for the round bubble. - **Other channels**: MP3 (`mp3_44100_128` from ElevenLabs, `mp3` from OpenAI). - 44.1kHz / 128kbps is the default balance for speech clarity. -- **Edge TTS**: uses `edge.outputFormat` (default `audio-24khz-48kbitrate-mono-mp3`). - - `node-edge-tts` accepts an `outputFormat`, but not all formats are available - from the Edge service. citeturn2search0 - - Output format values follow Microsoft Speech output formats (including Ogg/WebM Opus). citeturn1search0 +- **Microsoft**: uses `microsoft.outputFormat` (default `audio-24khz-48kbitrate-mono-mp3`). + - The bundled transport accepts an `outputFormat`, but not all formats are available from the service. + - Output format values follow Microsoft Speech output formats (including Ogg/WebM Opus). - Telegram `sendVoice` accepts OGG/MP3/M4A; use OpenAI/ElevenLabs if you need guaranteed Opus voice notes. citeturn1search1 - - If the configured Edge output format fails, OpenClaw retries with MP3. + - If the configured Microsoft output format fails, OpenClaw retries with MP3. OpenAI/ElevenLabs formats are fixed; Telegram expects Opus for voice-note UX. diff --git a/docs/web/control-ui.md b/docs/web/control-ui.md index 59e9c0c226b..952f6f71c1d 100644 --- a/docs/web/control-ui.md +++ b/docs/web/control-ui.md @@ -28,7 +28,7 @@ Auth is supplied during the WebSocket handshake via: - `connect.params.auth.token` - `connect.params.auth.password` The dashboard settings panel keeps a token for the current browser tab session and selected gateway URL; passwords are not persisted. - The onboarding wizard generates a gateway token by default, so paste it here on first connect. + Onboarding generates a gateway token by default, so paste it here on first connect. ## Device pairing (first connection) @@ -75,7 +75,7 @@ The Control UI can localize itself on first load based on your browser locale, a - Stream tool calls + live tool output cards in Chat (agent events) - Channels: WhatsApp/Telegram/Discord/Slack + plugin channels (Mattermost, etc.) status + QR login + per-channel config (`channels.status`, `web.login.*`, `config.patch`) - Instances: presence list + refresh (`system-presence`) -- Sessions: list + per-session thinking/verbose overrides (`sessions.list`, `sessions.patch`) +- Sessions: list + per-session thinking/fast/verbose/reasoning overrides (`sessions.list`, `sessions.patch`) - Cron jobs: list/add/edit/run/enable/disable + run history (`cron.*`) - Skills: status, enable/disable, install, API key updates (`skills.*`) - Nodes: list + caps (`node.list`) @@ -242,7 +242,7 @@ http://localhost:5173/?gatewayUrl=wss://:18789#token=` +- `/fast ` - `/verbose ` - `/reasoning ` - `/usage ` diff --git a/docs/zh-CN/AGENTS.md b/docs/zh-CN/AGENTS.md index cbf46cc310f..719a3576480 100644 --- a/docs/zh-CN/AGENTS.md +++ b/docs/zh-CN/AGENTS.md @@ -12,7 +12,7 @@ - 目标文档:`docs/zh-CN/**/*.md` - 术语表:`docs/.i18n/glossary.zh-CN.json` - 翻译记忆库:`docs/.i18n/zh-CN.tm.jsonl` -- 提示词规则:`scripts/docs-i18n/translator.go` +- 提示词规则:`scripts/docs-i18n/prompt.go` 常用运行方式: @@ -31,6 +31,8 @@ go run scripts/docs-i18n/main.go -mode segment docs/channels/matrix.md 注意事项: - doc 模式用于整页翻译;segment 模式用于小范围修补(依赖 TM)。 +- 新增技术术语、页面标题或短导航标签时,先更新 `docs/.i18n/glossary.zh-CN.json`,再跑 `doc` 模式;不要指望模型自行保留英文术语或固定译名。 +- `pnpm docs:check-i18n-glossary` 会检查变更过的英文文档标题和短内部链接标签是否已写入 glossary。 - 超大文件若超时,优先做**定点替换**或拆分后再跑。 - 翻译后检查中文引号、CJK-Latin 间距和术语一致性。 diff --git a/docs/zh-CN/automation/cron-jobs.md b/docs/zh-CN/automation/cron-jobs.md index 185779a2636..cfdb0c178e1 100644 --- a/docs/zh-CN/automation/cron-jobs.md +++ b/docs/zh-CN/automation/cron-jobs.md @@ -28,7 +28,9 @@ x-i18n: - 任务持久化存储在 `~/.openclaw/cron/` 下,因此重启不会丢失计划。 - 两种执行方式: - **主会话**:入队一个系统事件,然后在下一次心跳时运行。 - - **隔离式**:在 `cron:` 中运行专用智能体轮次,可投递摘要(默认 announce)或不投递。 + - **隔离式**:在 `cron:` 或自定义会话中运行专用智能体轮次,可投递摘要(默认 announce)或不投递。 + - **当前会话**:绑定到创建定时任务时的会话 (`sessionTarget: "current"`)。 + - **自定义会话**:在持久化的命名会话中运行 (`sessionTarget: "session:custom-id"`)。 - 唤醒是一等功能:任务可以请求"立即唤醒"或"下次心跳时"。 ## 快速开始(可操作) @@ -83,6 +85,14 @@ openclaw cron add \ 2. **选择运行位置** - `sessionTarget: "main"` → 在下一次心跳时使用主会话上下文运行。 - `sessionTarget: "isolated"` → 在 `cron:` 中运行专用智能体轮次。 + - `sessionTarget: "current"` → 绑定到当前会话(创建时解析为 `session:`)。 + - `sessionTarget: "session:custom-id"` → 在持久化的命名会话中运行,跨运行保持上下文。 + + 默认行为(保持不变): + - `systemEvent` 负载默认使用 `main` + - `agentTurn` 负载默认使用 `isolated` + + 要使用当前会话绑定,需显式设置 `sessionTarget: "current"`。 3. **选择负载** - 主会话 → `payload.kind = "systemEvent"` @@ -129,12 +139,13 @@ Cron 表达式使用 `croner`。如果省略时区,将使用 Gateway网关主 #### 隔离任务(专用定时会话) -隔离任务在会话 `cron:` 中运行专用智能体轮次。 +隔离任务在会话 `cron:` 或自定义会话中运行专用智能体轮次。 关键行为: - 提示以 `[cron: <任务名称>]` 为前缀,便于追踪。 -- 每次运行都会启动一个**全新的会话 ID**(不继承之前的对话)。 +- 每次运行都会启动一个**全新的会话 ID**(不继承之前的对话),除非使用自定义会话。 +- 自定义会话(`session:xxx`)可跨运行保持上下文,适用于如每日站会等需要基于前次摘要的工作流。 - 如果未指定 `delivery`,隔离任务会默认以“announce”方式投递摘要。 - `delivery.mode` 可选 `announce`(投递摘要)或 `none`(内部运行)。 diff --git a/docs/zh-CN/automation/hooks.md b/docs/zh-CN/automation/hooks.md index b5806e2bdd0..b0615569eec 100644 --- a/docs/zh-CN/automation/hooks.md +++ b/docs/zh-CN/automation/hooks.md @@ -1,60 +1,61 @@ --- read_when: - - 你想为 /new、/reset、/stop 和智能体生命周期事件实现事件驱动自动化 - - 你想构建、安装或调试 hooks + - 你希望为 `/new`、`/reset`、`/stop` 和智能体生命周期事件使用事件驱动自动化 + - 你希望构建、安装或调试 Hooks summary: Hooks:用于命令和生命周期事件的事件驱动自动化 title: Hooks x-i18n: - generated_at: "2026-02-03T07:50:59Z" - model: claude-opus-4-5 - provider: pi - source_hash: 853227a0f1abd20790b425fa64dda60efc6b5f93c1b13ecd2dcb788268f71d79 + generated_at: "2026-03-16T06:21:34Z" + model: gpt-5.4 + provider: openai + source_hash: fc1370a05127d778eb685f687ee9a52062aa6f5c895e80152b9de41c3a02c592 source_path: automation/hooks.md workflow: 15 --- # Hooks -Hooks 提供了一个可扩展的事件驱动系统,用于响应智能体命令和事件自动执行操作。Hooks 从目录中自动发现,可以通过 CLI 命令管理,类似于 OpenClaw 中 Skills 的工作方式。 +Hooks 提供了一个可扩展的事件驱动系统,用于在响应智能体命令和事件时自动执行操作。Hooks 会从目录中自动发现,并且可以通过 CLI 命令进行管理,方式与 OpenClaw 中的 Skills 类似。 -## 入门指南 +## 熟悉基础 -Hooks 是在事件发生时运行的小脚本。有两种类型: +Hooks 是在某些事情发生时运行的小脚本。它们有两种类型: -- **Hooks**(本页):当智能体事件触发时在 Gateway 网关内运行,如 `/new`、`/reset`、`/stop` 或生命周期事件。 -- **Webhooks**:外部 HTTP webhooks,让其他系统触发 OpenClaw 中的工作。参见 [Webhook Hooks](/automation/webhook) 或使用 `openclaw webhooks` 获取 Gmail 助手命令。 +- **Hooks**(本页):当智能体事件触发时,在 Gateway 网关内运行,例如 `/new`、`/reset`、`/stop` 或生命周期事件。 +- **Webhooks**:外部 HTTP webhook,可让其他系统在 OpenClaw 中触发工作。请参阅 [Webhook Hooks](/automation/webhook),或使用 `openclaw webhooks` 获取 Gmail 辅助命令。 -Hooks 也可以捆绑在插件中;参见 [插件](/tools/plugin#plugin-hooks)。 +Hooks 也可以打包在插件中;请参阅 [Plugins](/tools/plugin#plugin-hooks)。 常见用途: -- 重置会话时保存记忆快照 -- 保留命令审计跟踪用于故障排除或合规 -- 会话开始或结束时触发后续自动化 -- 事件触发时向智能体工作区写入文件或调用外部 API +- 当你重置会话时保存一份内存快照 +- 为故障排除或合规保留命令审计轨迹 +- 当会话开始或结束时触发后续自动化 +- 当事件触发时,将文件写入智能体工作区或调用外部 API -如果你能写一个小的 TypeScript 函数,你就能写一个 hook。Hooks 会自动发现,你可以通过 CLI 启用或禁用它们。 +如果你会写一个小型 TypeScript 函数,你就能编写一个 hook。Hooks 会被自动发现,你可以通过 CLI 启用或禁用它们。 -## 概述 +## 概览 -hooks 系统允许你: +Hooks 系统允许你: -- 在发出 `/new` 时将会话上下文保存到记忆 +- 当发出 `/new` 时,将会话上下文保存到 memory - 记录所有命令以供审计 - 在智能体生命周期事件上触发自定义自动化 - 在不修改核心代码的情况下扩展 OpenClaw 的行为 -## 入门 +## 入门指南 -### 捆绑的 Hooks +### 内置 Hooks -OpenClaw 附带三个自动发现的捆绑 hooks: +OpenClaw 自带四个会被自动发现的内置 hook: -- **💾 session-memory**:当你发出 `/new` 时将会话上下文保存到智能体工作区(默认 `~/.openclaw/workspace/memory/`) +- **💾 session-memory**:当你发出 `/new` 时,将会话上下文保存到你的智能体工作区(默认是 `~/.openclaw/workspace/memory/`) +- **📎 bootstrap-extra-files**:在 `agent:bootstrap` 期间,从已配置的 glob/路径模式中注入额外的工作区引导文件 - **📝 command-logger**:将所有命令事件记录到 `~/.openclaw/logs/commands.log` - **🚀 boot-md**:当 Gateway 网关启动时运行 `BOOT.md`(需要启用内部 hooks) -列出可用的 hooks: +列出可用 hooks: ```bash openclaw hooks list @@ -80,35 +81,40 @@ openclaw hooks info session-memory ### 新手引导 -在新手引导期间(`openclaw onboard`),你将被提示启用推荐的 hooks。向导会自动发现符合条件的 hooks 并呈现供选择。 +在新手引导期间(`openclaw onboard`),系统会提示你启用推荐的 hooks。向导会自动发现符合条件的 hooks 并供你选择。 ## Hook 发现 -Hooks 从三个目录自动发现(按优先级顺序): +Hooks 会从三个目录中自动发现(按优先级顺序): -1. **工作区 hooks**:`/hooks/`(每智能体,最高优先级) -2. **托管 hooks**:`~/.openclaw/hooks/`(用户安装,跨工作区共享) -3. **捆绑 hooks**:`/dist/hooks/bundled/`(随 OpenClaw 附带) +1. **工作区 hooks**:`/hooks/`(每个智能体单独配置,优先级最高) +2. **托管 hooks**:`~/.openclaw/hooks/`(用户安装,在各工作区之间共享) +3. **内置 hooks**:`/dist/hooks/bundled/`(随 OpenClaw 一起提供) -托管 hook 目录可以是**单个 hook** 或 **hook 包**(包目录)。 +托管 hook 目录既可以是 **单个 hook**,也可以是 **hook 包**(包目录)。 -每个 hook 是一个包含以下内容的目录: +每个 hook 都是一个包含以下内容的目录: ``` my-hook/ ├── HOOK.md # 元数据 + 文档 -└── handler.ts # 处理程序实现 +└── handler.ts # 处理器实现 ``` -## Hook 包(npm/archives) +## Hook 包(npm/归档) -Hook 包是标准的 npm 包,通过 `package.json` 中的 `openclaw.hooks` 导出一个或多个 hooks。使用以下命令安装: +Hook 包是标准的 npm 包,它们通过 `package.json` 中的 `openclaw.hooks` 导出一个或多个 hook。使用以下命令安装它们: ```bash openclaw hooks install ``` -示例 `package.json`: +npm spec 仅支持注册表形式(包名 + 可选的精确版本或 dist-tag)。 +Git/URL/file spec 和 semver 范围会被拒绝。 + +裸 spec 和 `@latest` 会保持在稳定轨道上。如果 npm 将其中任意一种解析为预发布版本,OpenClaw 会停止并要求你通过预发布标签(例如 `@beta`/`@rc`)或精确的预发布版本显式选择加入。 + +`package.json` 示例: ```json { @@ -120,19 +126,23 @@ openclaw hooks install } ``` -每个条目指向包含 `HOOK.md` 和 `handler.ts`(或 `index.ts`)的 hook 目录。 -Hook 包可以附带依赖;它们将安装在 `~/.openclaw/hooks/` 下。 +每个条目都指向一个包含 `HOOK.md` 和 `handler.ts`(或 `index.ts`)的 hook 目录。 +Hook 包可以携带依赖;它们会安装到 `~/.openclaw/hooks/` 下。 +每个 `openclaw.hooks` 条目在解析符号链接后都必须保持在包目录内部;超出目录范围的条目会被拒绝。 + +安全说明:`openclaw hooks install` 会使用 `npm install --ignore-scripts` 安装依赖 +(不运行生命周期脚本)。请保持 hook 包依赖树为“纯 JS/TS”,并避免依赖 `postinstall` 构建的包。 ## Hook 结构 ### HOOK.md 格式 -`HOOK.md` 文件在 YAML frontmatter 中包含元数据,加上 Markdown 文档: +`HOOK.md` 文件包含 YAML frontmatter 中的元数据以及 Markdown 文档: ```markdown --- name: my-hook -description: "Short description of what this hook does" +description: "关于此 hook 功能的简短描述" homepage: https://docs.openclaw.ai/automation/hooks#my-hook metadata: { "openclaw": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } } @@ -140,49 +150,47 @@ metadata: # My Hook -Detailed documentation goes here... +详细文档写在这里…… -## What It Does +## 它的作用 -- Listens for `/new` commands -- Performs some action -- Logs the result +- 监听 `/new` 命令 +- 执行某些操作 +- 记录结果 -## Requirements +## 要求 -- Node.js must be installed +- 必须安装 Node.js -## Configuration +## 配置 -No configuration needed. +无需配置。 ``` ### 元数据字段 `metadata.openclaw` 对象支持: -- **`emoji`**:CLI 的显示表情符号(例如 `"💾"`) +- **`emoji`**:CLI 显示用 emoji(例如 `"💾"`) - **`events`**:要监听的事件数组(例如 `["command:new", "command:reset"]`) - **`export`**:要使用的命名导出(默认为 `"default"`) - **`homepage`**:文档 URL - **`requires`**:可选要求 - - **`bins`**:PATH 中需要的二进制文件(例如 `["git", "node"]`) - - **`anyBins`**:这些二进制文件中至少有一个必须存在 - - **`env`**:需要的环境变量 - - **`config`**:需要的配置路径(例如 `["workspace.dir"]`) - - **`os`**:需要的平台(例如 `["darwin", "linux"]`) + - **`bins`**:PATH 中必须存在的二进制文件(例如 `["git", "node"]`) + - **`anyBins`**:这些二进制文件中至少要存在一个 + - **`env`**:必需的环境变量 + - **`config`**:必需的配置路径(例如 `["workspace.dir"]`) + - **`os`**:支持的平台(例如 `["darwin", "linux"]`) - **`always`**:绕过资格检查(布尔值) -- **`install`**:安装方法(对于捆绑 hooks:`[{"id":"bundled","kind":"bundled"}]`) +- **`install`**:安装方式(对于内置 hooks:`[{"id":"bundled","kind":"bundled"}]`) -### 处理程序实现 +### 处理器实现 -`handler.ts` 文件导出一个 `HookHandler` 函数: +`handler.ts` 文件会导出一个 `HookHandler` 函数: ```typescript -import type { HookHandler } from "../../src/hooks/hooks.js"; - -const myHandler: HookHandler = async (event) => { - // Only trigger on 'new' command +const myHandler = async (event) => { + // 仅在 'new' 命令时触发 if (event.type !== "command" || event.action !== "new") { return; } @@ -191,9 +199,9 @@ const myHandler: HookHandler = async (event) => { console.log(` Session: ${event.sessionKey}`); console.log(` Timestamp: ${event.timestamp.toISOString()}`); - // Your custom logic here + // 你的自定义逻辑写在这里 - // Optionally send message to user + // 可选:向用户发送消息 event.messages.push("✨ My hook executed!"); }; @@ -202,24 +210,31 @@ export default myHandler; #### 事件上下文 -每个事件包含: +每个事件都包含: ```typescript { - type: 'command' | 'session' | 'agent' | 'gateway', - action: string, // e.g., 'new', 'reset', 'stop' - sessionKey: string, // Session identifier - timestamp: Date, // When the event occurred - messages: string[], // Push messages here to send to user + type: 'command' | 'session' | 'agent' | 'gateway' | 'message', + action: string, // 例如 'new'、'reset'、'stop'、'received'、'sent' + sessionKey: string, // 会话标识符 + timestamp: Date, // 事件发生时间 + messages: string[], // 将消息推入这里以发送给用户 context: { + // 命令事件: sessionEntry?: SessionEntry, sessionId?: string, sessionFile?: string, - commandSource?: string, // e.g., 'whatsapp', 'telegram' + commandSource?: string, // 例如 'whatsapp'、'telegram' senderId?: string, workspaceDir?: string, bootstrapFiles?: WorkspaceBootstrapFile[], - cfg?: OpenClawConfig + cfg?: OpenClawConfig, + // 消息事件(完整详情见“消息事件”部分): + from?: string, // message:received + to?: string, // message:sent + content?: string, + channelId?: string, + success?: boolean, // message:sent } } ``` @@ -228,28 +243,135 @@ export default myHandler; ### 命令事件 -当发出智能体命令时触发: +在发出智能体命令时触发: - **`command`**:所有命令事件(通用监听器) -- **`command:new`**:当发出 `/new` 命令时 -- **`command:reset`**:当发出 `/reset` 命令时 -- **`command:stop`**:当发出 `/stop` 命令时 +- **`command:new`**:发出 `/new` 命令时 +- **`command:reset`**:发出 `/reset` 命令时 +- **`command:stop`**:发出 `/stop` 命令时 + +### 会话事件 + +- **`session:compact:before`**:在压缩开始总结历史记录之前 +- **`session:compact:after`**:在压缩完成并带有摘要元数据之后 + +内部 hook 负载会将这些事件表示为 `type: "session"`,并将 `action` 设为 `"compact:before"` / `"compact:after"`;监听器使用上面的组合键进行订阅。 +具体处理器注册使用字面量键格式 `${type}:${action}`。对于这些事件,请注册 `session:compact:before` 和 `session:compact:after`。 ### 智能体事件 -- **`agent:bootstrap`**:在注入工作区引导文件之前(hooks 可以修改 `context.bootstrapFiles`) +- **`agent:bootstrap`**:在工作区引导文件被注入之前(hooks 可以修改 `context.bootstrapFiles`) ### Gateway 网关事件 -当 Gateway 网关启动时触发: +在 Gateway 网关启动时触发: -- **`gateway:startup`**:在渠道启动和 hooks 加载之后 +- **`gateway:startup`**:在渠道启动且 hooks 已加载之后 + +### 消息事件 + +在消息被接收或发送时触发: + +- **`message`**:所有消息事件(通用监听器) +- **`message:received`**:当从任意渠道收到入站消息时。在处理的早期阶段触发,此时媒体理解尚未完成。对于尚未处理的媒体附件,内容中可能包含类似 `` 的原始占位符。 +- **`message:transcribed`**:当一条消息已被完全处理,包括音频转写和链接理解时触发。此时,`transcript` 包含音频消息的完整转写文本。当你需要访问已转写的音频内容时,请使用此 hook。 +- **`message:preprocessed`**:在所有媒体 + 链接理解完成后,为每条消息触发,使 hooks 可以在智能体看到消息之前访问完全增强的正文(转写、图像描述、链接摘要)。 +- **`message:sent`**:当出站消息成功发送时 + +#### 消息事件上下文 + +消息事件包含关于消息的丰富上下文: + +```typescript +// message:received context +{ + from: string, // 发送者标识符(电话号码、用户 ID 等) + content: string, // 消息内容 + timestamp?: number, // 接收时的 Unix 时间戳 + channelId: string, // 渠道(例如 "whatsapp"、"telegram"、"discord") + accountId?: string, // 多账号设置中的提供商账号 ID + conversationId?: string, // 聊天/会话 ID + messageId?: string, // 提供商返回的消息 ID + metadata?: { // 额外的提供商特定数据 + to?: string, + provider?: string, + surface?: string, + threadId?: string, + senderId?: string, + senderName?: string, + senderUsername?: string, + senderE164?: string, + } +} + +// message:sent context +{ + to: string, // 接收者标识符 + content: string, // 已发送的消息内容 + success: boolean, // 发送是否成功 + error?: string, // 如果发送失败,则为错误消息 + channelId: string, // 渠道(例如 "whatsapp"、"telegram"、"discord") + accountId?: string, // 提供商账号 ID + conversationId?: string, // 聊天/会话 ID + messageId?: string, // 提供商返回的消息 ID + isGroup?: boolean, // 此出站消息是否属于群组/渠道上下文 + groupId?: string, // 用于与 message:received 关联的群组/渠道标识符 +} + +// message:transcribed context +{ + body?: string, // 增强前的原始入站正文 + bodyForAgent?: string, // 对智能体可见的增强正文 + transcript: string, // 音频转写文本 + channelId: string, // 渠道(例如 "telegram"、"whatsapp") + conversationId?: string, + messageId?: string, +} + +// message:preprocessed context +{ + body?: string, // 原始入站正文 + bodyForAgent?: string, // 媒体/链接理解后的最终增强正文 + transcript?: string, // 存在音频时的转写内容 + channelId: string, // 渠道(例如 "telegram"、"whatsapp") + conversationId?: string, + messageId?: string, + isGroup?: boolean, + groupId?: string, +} +``` + +#### 示例:消息记录器 Hook + +```typescript +const isMessageReceivedEvent = (event: { type: string; action: string }) => + event.type === "message" && event.action === "received"; +const isMessageSentEvent = (event: { type: string; action: string }) => + event.type === "message" && event.action === "sent"; + +const handler = async (event) => { + if (isMessageReceivedEvent(event as { type: string; action: string })) { + console.log(`[message-logger] Received from ${event.context.from}: ${event.context.content}`); + } else if (isMessageSentEvent(event as { type: string; action: string })) { + console.log(`[message-logger] Sent to ${event.context.to}: ${event.context.content}`); + } +}; + +export default handler; +``` ### 工具结果 Hooks(插件 API) -这些 hooks 不是事件流监听器;它们让插件在 OpenClaw 持久化工具结果之前同步调整它们。 +这些 hooks 不是事件流监听器;它们允许插件在 OpenClaw 持久化工具结果之前同步调整工具结果。 -- **`tool_result_persist`**:在工具结果写入会话记录之前转换它们。必须是同步的;返回更新后的工具结果负载或 `undefined` 保持原样。参见 [智能体循环](/concepts/agent-loop)。 +- **`tool_result_persist`**:在工具结果写入会话转录之前对其进行转换。必须是同步的;返回更新后的工具结果负载,或返回 `undefined` 以保持原样。请参阅 [Agent Loop](/concepts/agent-loop)。 + +### 插件 Hook 事件 + +通过插件 hook 运行器公开的压缩生命周期 hooks: + +- **`before_compaction`**:在压缩前运行,并带有计数/token 元数据 +- **`after_compaction`**:在压缩后运行,并带有压缩摘要元数据 ### 未来事件 @@ -258,14 +380,12 @@ export default myHandler; - **`session:start`**:当新会话开始时 - **`session:end`**:当会话结束时 - **`agent:error`**:当智能体遇到错误时 -- **`message:sent`**:当消息被发送时 -- **`message:received`**:当消息被接收时 ## 创建自定义 Hooks ### 1. 选择位置 -- **工作区 hooks**(`/hooks/`):每智能体,最高优先级 +- **工作区 hooks**(`/hooks/`):每个智能体单独配置,优先级最高 - **托管 hooks**(`~/.openclaw/hooks/`):跨工作区共享 ### 2. 创建目录结构 @@ -280,27 +400,25 @@ cd ~/.openclaw/hooks/my-hook ```markdown --- name: my-hook -description: "Does something useful" +description: "执行某些有用的事情" metadata: { "openclaw": { "emoji": "🎯", "events": ["command:new"] } } --- # My Custom Hook -This hook does something useful when you issue `/new`. +当你发出 `/new` 时,此 hook 会执行一些有用的事情。 ``` ### 4. 创建 handler.ts ```typescript -import type { HookHandler } from "../../src/hooks/hooks.js"; - -const handler: HookHandler = async (event) => { +const handler = async (event) => { if (event.type !== "command" || event.action !== "new") { return; } console.log("[my-hook] Running!"); - // Your logic here + // 你的逻辑写在这里 }; export default handler; @@ -309,16 +427,16 @@ export default handler; ### 5. 启用并测试 ```bash -# Verify hook is discovered +# 验证 hook 已被发现 openclaw hooks list -# Enable it +# 启用它 openclaw hooks enable my-hook -# Restart your gateway process (menu bar app restart on macOS, or restart your dev process) +# 重启你的 Gateway 网关进程(macOS 上重启菜单栏应用,或重启你的开发进程) -# Trigger the event -# Send /new via your messaging channel +# 触发事件 +# 通过你的消息渠道发送 /new ``` ## 配置 @@ -339,9 +457,9 @@ openclaw hooks enable my-hook } ``` -### 每 Hook 配置 +### 每个 Hook 的配置 -Hooks 可以有自定义配置: +Hooks 可以具有自定义配置: ```json { @@ -378,9 +496,9 @@ Hooks 可以有自定义配置: } ``` -### 遗留配置格式(仍然支持) +### 旧版配置格式(仍受支持) -旧配置格式仍然有效以保持向后兼容: +旧配置格式仍可用于向后兼容: ```json { @@ -399,74 +517,76 @@ Hooks 可以有自定义配置: } ``` -**迁移**:对新 hooks 使用基于发现的新系统。遗留处理程序在基于目录的 hooks 之后加载。 +注意:`module` 必须是相对于工作区的路径。绝对路径和超出工作区范围的遍历路径会被拒绝。 + +**迁移**:对于新的 hooks,请使用基于发现的新系统。旧版 handlers 会在基于目录的 hooks 之后加载。 ## CLI 命令 ### 列出 Hooks ```bash -# List all hooks +# 列出所有 hooks openclaw hooks list -# Show only eligible hooks +# 仅显示符合条件的 hooks openclaw hooks list --eligible -# Verbose output (show missing requirements) +# 详细输出(显示缺失的要求) openclaw hooks list --verbose -# JSON output +# JSON 输出 openclaw hooks list --json ``` ### Hook 信息 ```bash -# Show detailed info about a hook +# 显示某个 hook 的详细信息 openclaw hooks info session-memory -# JSON output +# JSON 输出 openclaw hooks info session-memory --json ``` ### 检查资格 ```bash -# Show eligibility summary +# 显示资格摘要 openclaw hooks check -# JSON output +# JSON 输出 openclaw hooks check --json ``` ### 启用/禁用 ```bash -# Enable a hook +# 启用一个 hook openclaw hooks enable session-memory -# Disable a hook +# 禁用一个 hook openclaw hooks disable command-logger ``` -## 捆绑的 Hooks +## 内置 hook 参考 ### session-memory -当你发出 `/new` 时将会话上下文保存到记忆。 +当你发出 `/new` 时,将会话上下文保存到 memory。 **事件**:`command:new` **要求**:必须配置 `workspace.dir` -**输出**:`/memory/YYYY-MM-DD-slug.md`(默认为 `~/.openclaw/workspace`) +**输出**:`/memory/YYYY-MM-DD-slug.md`(默认是 `~/.openclaw/workspace`) -**功能**: +**它的作用**: -1. 使用预重置会话条目定位正确的记录 -2. 提取最后 15 行对话 -3. 使用 LLM 生成描述性文件名 slug -4. 将会话元数据保存到带日期的记忆文件 +1. 使用重置前的会话条目定位正确的转录 +2. 提取最近 15 行对话 +3. 使用 LLM 生成描述性的文件名 slug +4. 将会话元数据保存到带日期的 memory 文件中 **示例输出**: @@ -482,7 +602,7 @@ openclaw hooks disable command-logger - `2026-01-16-vendor-pitch.md` - `2026-01-16-api-design.md` -- `2026-01-16-1430.md`(如果 slug 生成失败则回退到时间戳) +- `2026-01-16-1430.md`(如果 slug 生成失败,则回退为时间戳) **启用**: @@ -490,9 +610,50 @@ openclaw hooks disable command-logger openclaw hooks enable session-memory ``` +### bootstrap-extra-files + +在 `agent:bootstrap` 期间注入额外的引导文件(例如 monorepo 本地的 `AGENTS.md` / `TOOLS.md`)。 + +**事件**:`agent:bootstrap` + +**要求**:必须配置 `workspace.dir` + +**输出**:不写入文件;仅在内存中修改引导上下文。 + +**配置**: + +```json +{ + "hooks": { + "internal": { + "enabled": true, + "entries": { + "bootstrap-extra-files": { + "enabled": true, + "paths": ["packages/*/AGENTS.md", "packages/*/TOOLS.md"] + } + } + } + } +} +``` + +**说明**: + +- 路径相对于工作区解析。 +- 文件必须保持在工作区内部(通过 realpath 检查)。 +- 仅加载已识别的引导基础文件名。 +- 会保留子智能体允许列表(仅 `AGENTS.md` 和 `TOOLS.md`)。 + +**启用**: + +```bash +openclaw hooks enable bootstrap-extra-files +``` + ### command-logger -将所有命令事件记录到集中审计文件。 +将所有命令事件记录到集中式审计文件。 **事件**:`command` @@ -500,10 +661,10 @@ openclaw hooks enable session-memory **输出**:`~/.openclaw/logs/commands.log` -**功能**: +**它的作用**: 1. 捕获事件详情(命令操作、时间戳、会话键、发送者 ID、来源) -2. 以 JSONL 格式追加到日志文件 +2. 以 JSONL 格式附加到日志文件 3. 在后台静默运行 **示例日志条目**: @@ -516,13 +677,13 @@ openclaw hooks enable session-memory **查看日志**: ```bash -# View recent commands +# 查看最近的命令 tail -n 20 ~/.openclaw/logs/commands.log -# Pretty-print with jq +# 使用 jq 美化输出 cat ~/.openclaw/logs/commands.log | jq . -# Filter by action +# 按操作筛选 grep '"action":"new"' ~/.openclaw/logs/commands.log | jq . ``` @@ -534,18 +695,18 @@ openclaw hooks enable command-logger ### boot-md -当 Gateway 网关启动时运行 `BOOT.md`(在渠道启动之后)。 -必须启用内部 hooks 才能运行。 +当 Gateway 网关启动时(渠道启动之后)运行 `BOOT.md`。 +必须启用内部 hooks,此功能才会运行。 **事件**:`gateway:startup` **要求**:必须配置 `workspace.dir` -**功能**: +**它的作用**: 1. 从你的工作区读取 `BOOT.md` -2. 通过智能体运行器运行指令 -3. 通过 message 工具发送任何请求的出站消息 +2. 通过智能体运行器执行其中的指令 +3. 通过消息工具发送任何请求的出站消息 **启用**: @@ -555,26 +716,26 @@ openclaw hooks enable boot-md ## 最佳实践 -### 保持处理程序快速 +### 保持处理器快速 -Hooks 在命令处理期间运行。保持它们轻量: +Hooks 在命令处理期间运行。请保持其轻量: ```typescript -// ✓ Good - async work, returns immediately +// ✓ 好 - 异步工作,立即返回 const handler: HookHandler = async (event) => { - void processInBackground(event); // Fire and forget + void processInBackground(event); // 触发后不等待 }; -// ✗ Bad - blocks command processing +// ✗ 差 - 阻塞命令处理 const handler: HookHandler = async (event) => { await slowDatabaseQuery(event); await evenSlowerAPICall(event); }; ``` -### 优雅处理错误 +### 优雅地处理错误 -始终包装有风险的操作: +始终包装高风险操作: ```typescript const handler: HookHandler = async (event) => { @@ -582,112 +743,117 @@ const handler: HookHandler = async (event) => { await riskyOperation(event); } catch (err) { console.error("[my-handler] Failed:", err instanceof Error ? err.message : String(err)); - // Don't throw - let other handlers run + // 不要抛出错误 - 让其他处理器继续运行 } }; ``` ### 尽早过滤事件 -如果事件不相关则尽早返回: +如果事件不相关,请尽早返回: ```typescript const handler: HookHandler = async (event) => { - // Only handle 'new' commands + // 仅处理 'new' 命令 if (event.type !== "command" || event.action !== "new") { return; } - // Your logic here + // 你的逻辑写在这里 }; ``` -### 使用特定事件键 +### 使用具体事件键 -尽可能在元数据中指定确切事件: +如果可能,请在元数据中指定精确事件: ```yaml -metadata: { "openclaw": { "events": ["command:new"] } } # Specific +metadata: { "openclaw": { "events": ["command:new"] } } # 精确 ``` 而不是: ```yaml -metadata: { "openclaw": { "events": ["command"] } } # General - more overhead +metadata: { "openclaw": { "events": ["command"] } } # 通用 - 开销更大 ``` ## 调试 ### 启用 Hook 日志 -Gateway 网关在启动时记录 hook 加载: +Gateway 网关会在启动时记录 hook 加载情况: ``` Registered hook: session-memory -> command:new +Registered hook: bootstrap-extra-files -> agent:bootstrap Registered hook: command-logger -> command Registered hook: boot-md -> gateway:startup ``` -### 检查发现 +### 检查发现情况 -列出所有发现的 hooks: +列出所有已发现的 hooks: ```bash openclaw hooks list --verbose ``` -### 检查注册 +### 检查注册情况 -在你的处理程序中,记录它被调用的时间: +在你的处理器中,记录它何时被调用: ```typescript const handler: HookHandler = async (event) => { console.log("[my-handler] Triggered:", event.type, event.action); - // Your logic + // 你的逻辑 }; ``` ### 验证资格 -检查为什么 hook 不符合条件: +检查某个 hook 为什么不符合条件: ```bash openclaw hooks info my-hook ``` -在输出中查找缺失的要求。 +查看输出中缺失的要求。 ## 测试 ### Gateway 网关日志 -监控 Gateway 网关日志以查看 hook 执行: +监控 Gateway 网关日志以查看 hook 执行情况: ```bash # macOS ./scripts/clawlog.sh -f -# Other platforms +# 其他平台 tail -f ~/.openclaw/gateway.log ``` ### 直接测试 Hooks -隔离测试你的处理程序: +单独测试你的 handlers: ```typescript import { test } from "vitest"; -import { createHookEvent } from "./src/hooks/hooks.js"; import myHandler from "./hooks/my-hook/handler.js"; test("my handler works", async () => { - const event = createHookEvent("command", "new", "test-session", { - foo: "bar", - }); + const event = { + type: "command", + action: "new", + sessionKey: "test-session", + timestamp: new Date(), + messages: [], + context: { foo: "bar" }, + }; await myHandler(event); - // Assert side effects + // 断言副作用 }); ``` @@ -696,8 +862,8 @@ test("my handler works", async () => { ### 核心组件 - **`src/hooks/types.ts`**:类型定义 -- **`src/hooks/workspace.ts`**:目录扫描和加载 -- **`src/hooks/frontmatter.ts`**:HOOK.md 元数据解析 +- **`src/hooks/workspace.ts`**:目录扫描与加载 +- **`src/hooks/frontmatter.ts`**:`HOOK.md` 元数据解析 - **`src/hooks/config.ts`**:资格检查 - **`src/hooks/hooks-status.ts`**:状态报告 - **`src/hooks/loader.ts`**:动态模块加载器 @@ -710,15 +876,15 @@ test("my handler works", async () => { ``` Gateway 网关启动 ↓ -扫描目录(工作区 → 托管 → 捆绑) +扫描目录(工作区 → 托管 → 内置) ↓ 解析 HOOK.md 文件 ↓ 检查资格(bins、env、config、os) ↓ -从符合条件的 hooks 加载处理程序 +从符合条件的 hooks 加载 handlers ↓ -为事件注册处理程序 +为事件注册 handlers ``` ### 事件流程 @@ -726,11 +892,11 @@ Gateway 网关启动 ``` 用户发送 /new ↓ -命令验证 +命令校验 ↓ 创建 hook 事件 ↓ -触发 hook(所有注册的处理程序) +触发 hook(所有已注册的 handlers) ↓ 命令处理继续 ↓ @@ -745,17 +911,18 @@ Gateway 网关启动 ```bash ls -la ~/.openclaw/hooks/my-hook/ - # Should show: HOOK.md, handler.ts + # 应显示:HOOK.md, handler.ts ``` 2. 验证 HOOK.md 格式: ```bash cat ~/.openclaw/hooks/my-hook/HOOK.md - # Should have YAML frontmatter with name and metadata + # 应包含带有 name 和 metadata 的 YAML frontmatter ``` -3. 列出所有发现的 hooks: +3. 列出所有已发现的 hooks: + ```bash openclaw hooks list ``` @@ -768,12 +935,12 @@ Gateway 网关启动 openclaw hooks info my-hook ``` -查找缺失的: +查看是否缺少: - 二进制文件(检查 PATH) - 环境变量 - 配置值 -- 操作系统兼容性 +- OS 兼容性 ### Hook 未执行 @@ -781,30 +948,31 @@ openclaw hooks info my-hook ```bash openclaw hooks list - # Should show ✓ next to enabled hooks + # 应在已启用的 hooks 旁显示 ✓ ``` 2. 重启你的 Gateway 网关进程以重新加载 hooks。 3. 检查 Gateway 网关日志中的错误: + ```bash ./scripts/clawlog.sh | grep hook ``` -### 处理程序错误 +### 处理器错误 检查 TypeScript/import 错误: ```bash -# Test import directly +# 直接测试导入 node -e "import('./path/to/handler.ts').then(console.log)" ``` ## 迁移指南 -### 从遗留配置到发现 +### 从旧版配置迁移到发现机制 -**之前**: +**迁移前**: ```json { @@ -822,7 +990,7 @@ node -e "import('./path/to/handler.ts').then(console.log)" } ``` -**之后**: +**迁移后**: 1. 创建 hook 目录: @@ -836,13 +1004,13 @@ node -e "import('./path/to/handler.ts').then(console.log)" ```markdown --- name: my-hook - description: "My custom hook" + description: "我的自定义 hook" metadata: { "openclaw": { "emoji": "🎯", "events": ["command:new"] } } --- # My Hook - Does something useful. + 执行某些有用的事情。 ``` 3. 更新配置: @@ -861,9 +1029,10 @@ node -e "import('./path/to/handler.ts').then(console.log)" ``` 4. 验证并重启你的 Gateway 网关进程: + ```bash openclaw hooks list - # Should show: 🎯 my-hook ✓ + # 应显示:🎯 my-hook ✓ ``` **迁移的好处**: @@ -876,7 +1045,7 @@ node -e "import('./path/to/handler.ts').then(console.log)" ## 另请参阅 -- [CLI 参考:hooks](/cli/hooks) -- [捆绑 Hooks README](https://github.com/openclaw/openclaw/tree/main/src/hooks/bundled) +- [CLI Reference: hooks](/cli/hooks) +- [Bundled Hooks README](https://github.com/openclaw/openclaw/tree/main/src/hooks/bundled) - [Webhook Hooks](/automation/webhook) -- [配置](/gateway/configuration#hooks) +- [Configuration](/gateway/configuration#hooks) diff --git a/docs/zh-CN/brave-search.md b/docs/zh-CN/brave-search.md index d69d45f04d3..1f63d2441d0 100644 --- a/docs/zh-CN/brave-search.md +++ b/docs/zh-CN/brave-search.md @@ -27,11 +27,21 @@ OpenClaw 使用 Brave Search 作为 `web_search` 的默认提供商。 ```json5 { + plugins: { + entries: { + brave: { + config: { + webSearch: { + apiKey: "BRAVE_API_KEY_HERE", + }, + }, + }, + }, + }, tools: { web: { search: { provider: "brave", - apiKey: "BRAVE_API_KEY_HERE", maxResults: 5, timeoutSeconds: 30, }, @@ -40,6 +50,8 @@ OpenClaw 使用 Brave Search 作为 `web_search` 的默认提供商。 } ``` +Brave 的提供商专属搜索配置现在位于 `plugins.entries.brave.config.webSearch.*`。旧的 `tools.web.search.apiKey` 仅作为兼容层暂时保留。 + ## 注意事项 - Data for AI 套餐与 `web_search` **不**兼容。 diff --git a/docs/zh-CN/channels/bluebubbles.md b/docs/zh-CN/channels/bluebubbles.md index 4ee4cb71e3e..b7f8534065b 100644 --- a/docs/zh-CN/channels/bluebubbles.md +++ b/docs/zh-CN/channels/bluebubbles.md @@ -6,34 +6,35 @@ read_when: summary: 通过 BlueBubbles macOS 服务器使用 iMessage(REST 发送/接收、输入状态、回应、配对、高级操作)。 title: BlueBubbles x-i18n: - generated_at: "2026-02-03T10:04:52Z" - model: claude-opus-4-5 - provider: pi - source_hash: 3aae277a8bec479800a7f6268bfbca912c65a4aadc6e513694057fb873597b69 + generated_at: "2026-03-16T06:21:08Z" + model: gpt-5.4 + provider: openai + source_hash: 877592bf7b9b06abdddd7567d56e756eff229d6ffa5056ef33fa3356086aa580 source_path: channels/bluebubbles.md workflow: 15 --- # BlueBubbles(macOS REST) -状态:内置插件,通过 HTTP 与 BlueBubbles macOS 服务器通信。由于其更丰富的 API 和更简便的设置,**推荐用于 iMessage 集成**,优于旧版 imsg 渠道。 +状态:内置插件,通过 HTTP 与 BlueBubbles macOS 服务器通信。由于其 API 更丰富且设置比旧版 imsg 渠道更简单,**推荐用于 iMessage 集成**。 -## 概述 +## 概览 - 通过 BlueBubbles 辅助应用在 macOS 上运行([bluebubbles.app](https://bluebubbles.app))。 -- 推荐/已测试版本:macOS Sequoia (15)。macOS Tahoe (26) 可用;但在 Tahoe 上编辑功能目前不可用,群组图标更新可能显示成功但实际未同步。 -- OpenClaw 通过其 REST API 与之通信(`GET /api/v1/ping`、`POST /message/text`、`POST /chat/:id/*`)。 -- 传入消息通过 webhook 到达;发出的回复、输入指示器、已读回执和 tapback 均为 REST 调用。 -- 附件和贴纸作为入站媒体被接收(并在可能时呈现给智能体)。 -- 配对/白名单的工作方式与其他渠道相同(`/channels/pairing` 等),使用 `channels.bluebubbles.allowFrom` + 配对码。 -- 回应作为系统事件呈现,与 Slack/Telegram 类似,智能体可以在回复前"提及"它们。 +- 推荐/已测试:macOS Sequoia(15)。macOS Tahoe(26)可用;目前编辑功能在 Tahoe 上已损坏,群组图标更新可能会报告成功但不会同步。 +- OpenClaw 通过其 REST API 与其通信(`GET /api/v1/ping`、`POST /message/text`、`POST /chat/:id/*`)。 +- 传入消息通过 webhook 到达;传出回复、输入状态指示、已读回执和 tapback 都通过 REST 调用完成。 +- 附件和贴纸会作为入站媒体接收(并在可能时展示给智能体)。 +- 配对/allowlist 的工作方式与其他渠道相同(`/channels/pairing` 等),使用 `channels.bluebubbles.allowFrom` + 配对码。 +- 回应会像 Slack/Telegram 一样作为系统事件呈现,因此智能体可以在回复前“提及”它们。 - 高级功能:编辑、撤回、回复线程、消息效果、群组管理。 ## 快速开始 -1. 在你的 Mac 上安装 BlueBubbles 服务器(按照 [bluebubbles.app/install](https://bluebubbles.app/install) 的说明操作)。 -2. 在 BlueBubbles 配置中,启用 web API 并设置密码。 +1. 在你的 Mac 上安装 BlueBubbles 服务器(按照 [bluebubbles.app/install](https://bluebubbles.app/install) 上的说明操作)。 +2. 在 BlueBubbles 配置中启用 Web API 并设置密码。 3. 运行 `openclaw onboard` 并选择 BlueBubbles,或手动配置: + ```json5 { channels: { @@ -46,8 +47,89 @@ x-i18n: }, } ``` + 4. 将 BlueBubbles webhook 指向你的 Gateway 网关(示例:`https://your-gateway-host:3000/bluebubbles-webhook?password=`)。 -5. 启动 Gateway 网关;它将注册 webhook 处理程序并开始配对。 +5. 启动 Gateway 网关;它会注册 webhook 处理器并开始配对。 + +安全说明: + +- 始终设置 webhook 密码。 +- 始终要求进行 webhook 身份验证。无论 loopback/代理拓扑如何,除非 BlueBubbles webhook 请求包含与 `channels.bluebubbles.password` 匹配的 password/guid(例如 `?password=` 或 `x-password`),否则 OpenClaw 会拒绝该请求。 +- 在读取/解析完整 webhook 请求体之前就会检查密码身份验证。 + +## 保持 Messages.app 处于活动状态(VM / 无头设置) + +某些 macOS VM / 常开设置可能会导致 Messages.app 进入“空闲”状态(传入事件会停止,直到应用被打开/切到前台)。一个简单的解决方法是使用 AppleScript + LaunchAgent **每 5 分钟“触碰”一次 Messages**。 + +### 1)保存 AppleScript + +将以下内容保存为: + +- `~/Scripts/poke-messages.scpt` + +示例脚本(非交互式;不会抢占焦点): + +```applescript +try + tell application "Messages" + if not running then + launch + end if + + -- Touch the scripting interface to keep the process responsive. + set _chatCount to (count of chats) + end tell +on error + -- Ignore transient failures (first-run prompts, locked session, etc). +end try +``` + +### 2)安装 LaunchAgent + +将以下内容保存为: + +- `~/Library/LaunchAgents/com.user.poke-messages.plist` + +```xml + + + + + Label + com.user.poke-messages + + ProgramArguments + + /bin/bash + -lc + /usr/bin/osascript "$HOME/Scripts/poke-messages.scpt" + + + RunAtLoad + + + StartInterval + 300 + + StandardOutPath + /tmp/poke-messages.log + StandardErrorPath + /tmp/poke-messages.err + + +``` + +说明: + +- 这会**每 300 秒**运行一次,并且**在登录时**运行。 +- 首次运行可能会触发 macOS 的**自动化**权限提示(`osascript` → Messages)。请在运行该 LaunchAgent 的同一用户会话中批准这些提示。 + +加载它: + +```bash +launchctl unload ~/Library/LaunchAgents/com.user.poke-messages.plist 2>/dev/null || true +launchctl load ~/Library/LaunchAgents/com.user.poke-messages.plist +``` ## 新手引导 @@ -60,10 +142,10 @@ openclaw onboard 向导会提示输入: - **服务器 URL**(必填):BlueBubbles 服务器地址(例如 `http://192.168.1.100:1234`) -- **密码**(必填):来自 BlueBubbles 服务器设置的 API 密码 +- **密码**(必填):来自 BlueBubbles Server 设置的 API 密码 - **Webhook 路径**(可选):默认为 `/bluebubbles-webhook` -- **私信策略**:配对、白名单、开放或禁用 -- **白名单**:电话号码、电子邮件或聊天目标 +- **私信策略**:pairing、allowlist、open 或 disabled +- **允许列表**:电话号码、电子邮件或聊天目标 你也可以通过 CLI 添加 BlueBubbles: @@ -75,12 +157,12 @@ openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --passwor 私信: -- 默认:`channels.bluebubbles.dmPolicy = "pairing"`。 -- 未知发送者会收到配对码;在批准之前消息会被忽略(配对码 1 小时后过期)。 -- 批准方式: +- 默认值:`channels.bluebubbles.dmPolicy = "pairing"`。 +- 未知发送者会收到一个配对码;在获得批准前,消息会被忽略(代码 1 小时后过期)。 +- 通过以下方式批准: - `openclaw pairing list bluebubbles` - `openclaw pairing approve bluebubbles ` -- 配对是默认的令牌交换方式。详情:[配对](/channels/pairing) +- 配对是默认的令牌交换方式。详情见:[配对](/channels/pairing) 群组: @@ -89,13 +171,13 @@ openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --passwor ### 提及门控(群组) -BlueBubbles 支持群聊的提及门控,与 iMessage/WhatsApp 行为一致: +BlueBubbles 支持群聊中的提及门控,与 iMessage/WhatsApp 的行为一致: - 使用 `agents.list[].groupChat.mentionPatterns`(或 `messages.groupChat.mentionPatterns`)检测提及。 -- 当群组启用 `requireMention` 时,智能体仅在被提及时响应。 +- 当某个群组启用 `requireMention` 时,智能体只有在被提及时才会响应。 - 来自授权发送者的控制命令会绕过提及门控。 -单群组配置: +每群组配置: ```json5 { @@ -104,8 +186,8 @@ BlueBubbles 支持群聊的提及门控,与 iMessage/WhatsApp 行为一致: groupPolicy: "allowlist", groupAllowFrom: ["+15555550123"], groups: { - "*": { requireMention: true }, // 所有群组的默认设置 - "iMessage;-;chat123": { requireMention: false }, // 特定群组的覆盖设置 + "*": { requireMention: true }, // 所有群组的默认值 + "iMessage;-;chat123": { requireMention: false }, // 对特定群组覆盖 }, }, }, @@ -115,14 +197,14 @@ BlueBubbles 支持群聊的提及门控,与 iMessage/WhatsApp 行为一致: ### 命令门控 - 控制命令(例如 `/config`、`/model`)需要授权。 -- 使用 `allowFrom` 和 `groupAllowFrom` 确定命令授权。 -- 授权发送者即使在群组中未被提及也可以运行控制命令。 +- 使用 `allowFrom` 和 `groupAllowFrom` 来判断命令授权。 +- 授权发送者即使在群组中未提及,也可以运行控制命令。 ## 输入状态 + 已读回执 -- **输入指示器**:在响应生成前和生成期间自动发送。 +- **输入状态指示**:会在生成响应之前和期间自动发送。 - **已读回执**:由 `channels.bluebubbles.sendReadReceipts` 控制(默认:`true`)。 -- **输入指示器**:OpenClaw 发送输入开始事件;BlueBubbles 在发送或超时时自动清除输入状态(通过 DELETE 手动停止不可靠)。 +- **输入状态指示**:OpenClaw 会发送输入开始事件;BlueBubbles 会在发送后或超时后自动清除输入状态(通过 DELETE 手动停止并不可靠)。 ```json5 { @@ -136,7 +218,7 @@ BlueBubbles 支持群聊的提及门控,与 iMessage/WhatsApp 行为一致: ## 高级操作 -BlueBubbles 在配置中启用时支持高级消息操作: +在配置中启用后,BlueBubbles 支持高级消息操作: ```json5 { @@ -144,15 +226,15 @@ BlueBubbles 在配置中启用时支持高级消息操作: bluebubbles: { actions: { reactions: true, // tapback(默认:true) - edit: true, // 编辑已发送消息(macOS 13+,在 macOS 26 Tahoe 上不可用) + edit: true, // 编辑已发送消息(macOS 13+,在 macOS 26 Tahoe 上已损坏) unsend: true, // 撤回消息(macOS 13+) - reply: true, // 通过消息 GUID 进行回复线程 + reply: true, // 按消息 GUID 回复线程 sendWithEffect: true, // 消息效果(slam、loud 等) renameGroup: true, // 重命名群聊 setGroupIcon: true, // 设置群聊图标/照片(在 macOS 26 Tahoe 上不稳定) - addParticipant: true, // 将参与者添加到群组 + addParticipant: true, // 向群组添加参与者 removeParticipant: true, // 从群组移除参与者 - leaveGroup: true, // 离开群聊 + leaveGroup: true, // 退出群聊 sendAttachment: true, // 发送附件/媒体 }, }, @@ -163,37 +245,37 @@ BlueBubbles 在配置中启用时支持高级消息操作: 可用操作: - **react**:添加/移除 tapback 回应(`messageId`、`emoji`、`remove`) -- **edit**:编辑已发送的消息(`messageId`、`text`) +- **edit**:编辑已发送消息(`messageId`、`text`) - **unsend**:撤回消息(`messageId`) - **reply**:回复特定消息(`messageId`、`text`、`to`) - **sendWithEffect**:带 iMessage 效果发送(`text`、`to`、`effectId`) - **renameGroup**:重命名群聊(`chatGuid`、`displayName`) -- **setGroupIcon**:设置群聊图标/照片(`chatGuid`、`media`)— 在 macOS 26 Tahoe 上不稳定(API 可能返回成功但图标未同步)。 -- **addParticipant**:将某人添加到群组(`chatGuid`、`address`) -- **removeParticipant**:将某人从群组移除(`chatGuid`、`address`) -- **leaveGroup**:离开群聊(`chatGuid`) +- **setGroupIcon**:设置群聊图标/照片(`chatGuid`、`media`)——在 macOS 26 Tahoe 上不稳定(API 可能返回成功,但图标不会同步)。 +- **addParticipant**:向群组添加某人(`chatGuid`、`address`) +- **removeParticipant**:从群组移除某人(`chatGuid`、`address`) +- **leaveGroup**:退出群聊(`chatGuid`) - **sendAttachment**:发送媒体/文件(`to`、`buffer`、`filename`、`asVoice`) - - 语音备忘录:将 `asVoice: true` 与 **MP3** 或 **CAF** 音频一起设置,以 iMessage 语音消息形式发送。BlueBubbles 在发送语音备忘录时会将 MP3 转换为 CAF。 + - 语音备忘录:将 `asVoice: true` 与 **MP3** 或 **CAF** 音频一起设置,即可作为 iMessage 语音消息发送。BlueBubbles 在发送语音备忘录时会将 MP3 转换为 CAF。 -### 消息 ID(短格式 vs 完整格式) +### 消息 ID(短 ID 与完整 ID) OpenClaw 可能会显示*短*消息 ID(例如 `1`、`2`)以节省 token。 - `MessageSid` / `ReplyToId` 可以是短 ID。 - `MessageSidFull` / `ReplyToIdFull` 包含提供商的完整 ID。 -- 短 ID 存储在内存中;它们可能在重启或缓存清除后过期。 -- 操作接受短或完整的 `messageId`,但如果短 ID 不再可用将会报错。 +- 短 ID 存在于内存中;它们可能会在重启或缓存清除后失效。 +- 操作接受短或完整 `messageId`,但如果短 ID 不再可用,就会报错。 对于持久化自动化和存储,请使用完整 ID: - 模板:`{{MessageSidFull}}`、`{{ReplyToIdFull}}` - 上下文:入站负载中的 `MessageSidFull` / `ReplyToIdFull` -参见[配置](/gateway/configuration)了解模板变量。 +模板变量请参见[配置](/gateway/configuration)。 ## 分块流式传输 -控制响应是作为单条消息发送还是分块流式传输: +控制响应是作为单条消息发送,还是按块流式发送: ```json5 { @@ -208,8 +290,8 @@ OpenClaw 可能会显示*短*消息 ID(例如 `1`、`2`)以节省 token。 ## 媒体 + 限制 - 入站附件会被下载并存储在媒体缓存中。 -- 媒体上限通过 `channels.bluebubbles.mediaMaxMb` 设置(默认:8 MB)。 -- 出站文本按 `channels.bluebubbles.textChunkLimit` 分块(默认:4000 字符)。 +- 通过 `channels.bluebubbles.mediaMaxMb` 控制入站和出站媒体大小上限(默认:8 MB)。 +- 出站文本会按 `channels.bluebubbles.textChunkLimit` 分块(默认:4000 个字符)。 ## 配置参考 @@ -217,22 +299,23 @@ OpenClaw 可能会显示*短*消息 ID(例如 `1`、`2`)以节省 token。 提供商选项: -- `channels.bluebubbles.enabled`:启用/禁用渠道。 +- `channels.bluebubbles.enabled`:启用/禁用该渠道。 - `channels.bluebubbles.serverUrl`:BlueBubbles REST API 基础 URL。 - `channels.bluebubbles.password`:API 密码。 - `channels.bluebubbles.webhookPath`:Webhook 端点路径(默认:`/bluebubbles-webhook`)。 - `channels.bluebubbles.dmPolicy`:`pairing | allowlist | open | disabled`(默认:`pairing`)。 -- `channels.bluebubbles.allowFrom`:私信白名单(句柄、电子邮件、E.164 号码、`chat_id:*`、`chat_guid:*`)。 +- `channels.bluebubbles.allowFrom`:私信 allowlist(handle、电子邮件、E.164 号码、`chat_id:*`、`chat_guid:*`)。 - `channels.bluebubbles.groupPolicy`:`open | allowlist | disabled`(默认:`allowlist`)。 -- `channels.bluebubbles.groupAllowFrom`:群组发送者白名单。 -- `channels.bluebubbles.groups`:单群组配置(`requireMention` 等)。 +- `channels.bluebubbles.groupAllowFrom`:群组发送者 allowlist。 +- `channels.bluebubbles.groups`:每群组配置(`requireMention` 等)。 - `channels.bluebubbles.sendReadReceipts`:发送已读回执(默认:`true`)。 -- `channels.bluebubbles.blockStreaming`:启用分块流式传输(默认:`false`;流式回复必需)。 -- `channels.bluebubbles.textChunkLimit`:出站分块大小(字符)(默认:4000)。 -- `channels.bluebubbles.chunkMode`:`length`(默认)仅在超过 `textChunkLimit` 时分割;`newline` 在长度分块前先按空行(段落边界)分割。 -- `channels.bluebubbles.mediaMaxMb`:入站媒体上限(MB)(默认:8)。 -- `channels.bluebubbles.historyLimit`:上下文的最大群组消息数(0 表示禁用)。 -- `channels.bluebubbles.dmHistoryLimit`:私信历史限制。 +- `channels.bluebubbles.blockStreaming`:启用分块流式传输(默认:`false`;流式回复所必需)。 +- `channels.bluebubbles.textChunkLimit`:出站分块大小(按字符计,默认:4000)。 +- `channels.bluebubbles.chunkMode`:`length`(默认)仅在超过 `textChunkLimit` 时拆分;`newline` 会先按空行(段落边界)拆分,再按长度分块。 +- `channels.bluebubbles.mediaMaxMb`:入站/出站媒体大小上限(MB,默认:8)。 +- `channels.bluebubbles.mediaLocalRoots`:允许用于出站本地媒体路径的绝对本地目录显式 allowlist。默认情况下,本地路径发送会被拒绝,除非配置了此项。每账户覆盖:`channels.bluebubbles.accounts..mediaLocalRoots`。 +- `channels.bluebubbles.historyLimit`:用于上下文的最大群组消息数(0 表示禁用)。 +- `channels.bluebubbles.dmHistoryLimit`:私信历史记录上限。 - `channels.bluebubbles.actions`:启用/禁用特定操作。 - `channels.bluebubbles.accounts`:多账户配置。 @@ -241,31 +324,31 @@ OpenClaw 可能会显示*短*消息 ID(例如 `1`、`2`)以节省 token。 - `agents.list[].groupChat.mentionPatterns`(或 `messages.groupChat.mentionPatterns`)。 - `messages.responsePrefix`。 -## 地址 / 投递目标 +## 寻址 / 送达目标 -优先使用 `chat_guid` 以获得稳定的路由: +优先使用 `chat_guid` 以获得稳定路由: -- `chat_guid:iMessage;-;+15555550123`(群组推荐) +- `chat_guid:iMessage;-;+15555550123`(群组首选) - `chat_id:123` - `chat_identifier:...` -- 直接句柄:`+15555550123`、`user@example.com` - - 如果直接句柄没有现有的私信聊天,OpenClaw 将通过 `POST /api/v1/chat/new` 创建一个。这需要启用 BlueBubbles Private API。 +- 直接 handle:`+15555550123`、`user@example.com` + - 如果某个直接 handle 没有现有私信聊天,OpenClaw 会通过 `POST /api/v1/chat/new` 创建一个。这要求启用 BlueBubbles Private API。 -## 安全性 +## 安全 -- Webhook 请求通过比较 `guid`/`password` 查询参数或头部与 `channels.bluebubbles.password` 进行身份验证。来自 `localhost` 的请求也会被接受。 -- 保持 API 密码和 webhook 端点的机密性(将它们视为凭证)。 -- localhost 信任意味着同主机的反向代理可能无意中绕过密码验证。如果你使用代理 Gateway 网关,请在代理处要求身份验证并配置 `gateway.trustedProxies`。参见 [Gateway 网关安全性](/gateway/security#reverse-proxy-configuration)。 -- 如果将 BlueBubbles 服务器暴露在局域网之外,请启用 HTTPS + 防火墙规则。 +- 通过将查询参数或请求头中的 `guid`/`password` 与 `channels.bluebubbles.password` 进行比较来验证 webhook 请求。来自 `localhost` 的请求也会被接受。 +- 请妥善保管 API 密码和 webhook 端点(将它们视为凭证)。 +- 对 localhost 的信任意味着同主机上的反向代理可能会无意中绕过密码。如果你对 Gateway 网关进行了代理,请在代理层要求身份验证,并配置 `gateway.trustedProxies`。请参见 [Gateway 网关安全](/gateway/security#reverse-proxy-configuration)。 +- 如果要在局域网外暴露 BlueBubbles 服务器,请启用 HTTPS + 防火墙规则。 ## 故障排除 -- 如果输入/已读事件停止工作,请检查 BlueBubbles webhook 日志并验证 Gateway 网关路径是否与 `channels.bluebubbles.webhookPath` 匹配。 -- 配对码在一小时后过期;使用 `openclaw pairing list bluebubbles` 和 `openclaw pairing approve bluebubbles `。 -- 回应需要 BlueBubbles private API(`POST /api/v1/message/react`);确保服务器版本支持它。 -- 编辑/撤回需要 macOS 13+ 和兼容的 BlueBubbles 服务器版本。在 macOS 26(Tahoe)上,由于 private API 变更,编辑功能目前不可用。 -- 在 macOS 26(Tahoe)上群组图标更新可能不稳定:API 可能返回成功但新图标未同步。 -- OpenClaw 会根据 BlueBubbles 服务器的 macOS 版本自动隐藏已知不可用的操作。如果在 macOS 26(Tahoe)上编辑仍然显示,请使用 `channels.bluebubbles.actions.edit=false` 手动禁用。 -- 查看状态/健康信息:`openclaw status --all` 或 `openclaw status --deep`。 +- 如果输入状态/已读事件停止工作,请检查 BlueBubbles webhook 日志,并验证 Gateway 网关路径与 `channels.bluebubbles.webhookPath` 一致。 +- 配对码会在一小时后过期;使用 `openclaw pairing list bluebubbles` 和 `openclaw pairing approve bluebubbles `。 +- 回应需要 BlueBubbles private API(`POST /api/v1/message/react`);请确保服务器版本提供该接口。 +- 编辑/撤回需要 macOS 13+ 以及兼容的 BlueBubbles 服务器版本。在 macOS 26(Tahoe)上,由于 private API 变更,编辑功能目前已损坏。 +- 群组图标更新在 macOS 26(Tahoe)上可能不稳定:API 可能返回成功,但新图标不会同步。 +- OpenClaw 会根据 BlueBubbles 服务器的 macOS 版本自动隐藏已知损坏的操作。如果在 macOS 26(Tahoe)上仍显示编辑操作,请手动使用 `channels.bluebubbles.actions.edit=false` 禁用它。 +- 状态/健康信息请使用:`openclaw status --all` 或 `openclaw status --deep`。 -有关通用渠道工作流参考,请参阅[渠道](/channels)和[插件](/tools/plugin)指南。 +有关通用渠道工作流程参考,请参见 [Channels](/channels) 和 [Plugins](/tools/plugin) 指南。 diff --git a/docs/zh-CN/channels/feishu.md b/docs/zh-CN/channels/feishu.md index 7a1c198733c..af561e3a8ea 100644 --- a/docs/zh-CN/channels/feishu.md +++ b/docs/zh-CN/channels/feishu.md @@ -1,22 +1,29 @@ --- -summary: "飞书机器人支持状态、功能和配置" read_when: - - 您想要连接飞书机器人 - - 您正在配置飞书渠道 + - 你想连接一个飞书/Lark 机器人 + - 你正在配置飞书渠道 +summary: 飞书机器人概览、功能和配置 title: 飞书 +x-i18n: + generated_at: "2026-03-16T06:21:11Z" + model: gpt-5.4 + provider: openai + source_hash: 951e78c5c7264471382f863fa896a15ddeaf0717ef782da20d0f1b3eb23396ba + source_path: channels/feishu.md + workflow: 15 --- # 飞书机器人 -状态:生产就绪,支持机器人私聊和群组。使用 WebSocket 长连接模式接收消息。 +飞书(Lark)是企业用于消息沟通与协作的团队聊天平台。此插件通过平台的 WebSocket 事件订阅将 OpenClaw 连接到飞书/Lark 机器人,因此无需暴露公共 webhook URL 即可接收消息。 --- -## 内置插件 +## 捆绑插件 -当前版本的 OpenClaw 已内置 Feishu 插件,因此通常不需要单独安装。 +飞书随当前的 OpenClaw 版本一同捆绑提供,因此无需单独安装插件。 -如果你使用的是较旧版本,或是没有内置 Feishu 的自定义安装,可手动安装: +如果你使用的是较旧版本,或使用了不包含捆绑飞书的自定义安装,请手动安装: ```bash openclaw plugins install @openclaw/feishu @@ -26,75 +33,75 @@ openclaw plugins install @openclaw/feishu ## 快速开始 -添加飞书渠道有两种方式: +有两种方式可添加飞书渠道: -### 方式一:通过安装向导添加(推荐) +### 方法 1:设置向导(推荐) -如果您刚安装完 OpenClaw,可以直接运行向导,根据提示添加飞书: +如果你刚安装 OpenClaw,请运行设置向导: ```bash openclaw onboard ``` -向导会引导您完成: +向导会引导你完成以下步骤: -1. 创建飞书应用并获取凭证 -2. 配置应用凭证 -3. 启动网关 +1. 创建飞书应用并收集凭证 +2. 在 OpenClaw 中配置应用凭证 +3. 启动 Gateway 网关 -✅ **完成配置后**,您可以使用以下命令检查网关状态: +✅ **配置完成后**,检查 Gateway 网关状态: -- `openclaw gateway status` - 查看网关运行状态 -- `openclaw logs --follow` - 查看实时日志 +- `openclaw gateway status` +- `openclaw logs --follow` -### 方式二:通过命令行添加 +### 方法 2:CLI 设置 -如果您已经完成了初始安装,可以用以下命令添加飞书渠道: +如果你已经完成初始安装,可通过 CLI 添加该渠道: ```bash openclaw channels add ``` -然后根据交互式提示选择 Feishu,输入 App ID 和 App Secret 即可。 +选择 **Feishu**,然后输入 App ID 和 App Secret。 -✅ **完成配置后**,您可以使用以下命令管理网关: +✅ **配置完成后**,管理 Gateway 网关: -- `openclaw gateway status` - 查看网关运行状态 -- `openclaw gateway restart` - 重启网关以应用新配置 -- `openclaw logs --follow` - 查看实时日志 +- `openclaw gateway status` +- `openclaw gateway restart` +- `openclaw logs --follow` --- -## 第一步:创建飞书应用 +## 第 1 步:创建飞书应用 ### 1. 打开飞书开放平台 -访问 [飞书开放平台](https://open.feishu.cn/app),使用飞书账号登录。 +访问 [Feishu Open Platform](https://open.feishu.cn/app) 并登录。 -Lark(国际版)请使用 https://open.larksuite.com/app,并在配置中设置 `domain: "lark"`。 +Lark(国际版)租户应使用 [https://open.larksuite.com/app](https://open.larksuite.com/app),并在飞书配置中设置 `domain: "lark"`。 ### 2. 创建应用 -1. 点击 **创建企业自建应用** +1. 点击 **Create enterprise app** 2. 填写应用名称和描述 3. 选择应用图标 -![创建企业自建应用](/images/feishu-step2-create-app.png) +![Create enterprise app](../images/feishu-step2-create-app.png) -### 3. 获取应用凭证 +### 3. 复制凭证 -在应用的 **凭证与基础信息** 页面,复制: +在 **Credentials & Basic Info** 中,复制: -- **App ID**(格式如 `cli_xxx`) +- **App ID**(格式:`cli_xxx`) - **App Secret** -❗ **重要**:请妥善保管 App Secret,不要分享给他人。 +❗ **重要:**请将 App Secret 妥善保密。 -![获取应用凭证](/images/feishu-step3-credentials.png) +![Get credentials](../images/feishu-step3-credentials.png) -### 4. 配置应用权限 +### 4. 配置权限 -在 **权限管理** 页面,点击 **批量导入** 按钮,粘贴以下 JSON 配置一键导入所需权限: +在 **Permissions** 中,点击 **Batch import** 并粘贴: ```json { @@ -105,77 +112,71 @@ Lark(国际版)请使用 https://open.larksuite.com/app,并在配置中设 "application:application.app_message_stats.overview:readonly", "application:application:self_manage", "application:bot.menu:write", + "cardkit:card:read", "cardkit:card:write", "contact:user.employee_id:readonly", "corehr:file:download", - "docs:document.content:read", "event:ip_list", - "im:chat", "im:chat.access_event.bot_p2p_chat:read", "im:chat.members:bot_access", "im:message", "im:message.group_at_msg:readonly", - "im:message.group_msg", "im:message.p2p_msg:readonly", "im:message:readonly", "im:message:send_as_bot", - "im:resource", - "sheets:spreadsheet", - "wiki:wiki:readonly" + "im:resource" ], "user": ["aily:file:read", "aily:file:write", "im:chat.access_event.bot_p2p_chat:read"] } } ``` -![配置应用权限](/images/feishu-step4-permissions.png) +![Configure permissions](../images/feishu-step4-permissions.png) ### 5. 启用机器人能力 -在 **应用能力** > **机器人** 页面: +在 **App Capability** > **Bot** 中: -1. 开启机器人能力 -2. 配置机器人名称 +1. 启用机器人能力 +2. 设置机器人名称 -![启用机器人能力](/images/feishu-step5-bot-capability.png) +![Enable bot capability](../images/feishu-step5-bot-capability.png) ### 6. 配置事件订阅 -⚠️ **重要提醒**:在配置事件订阅前,请务必确保已完成以下步骤: +⚠️ **重要:**在设置事件订阅前,请确保: -1. 运行 `openclaw channels add` 添加了 Feishu 渠道 -2. 网关处于启动状态(可通过 `openclaw gateway status` 检查状态) +1. 你已经为飞书运行过 `openclaw channels add` +2. Gateway 网关正在运行(`openclaw gateway status`) -在 **事件订阅** 页面: +在 **Event Subscription** 中: -1. 选择 **使用长连接接收事件**(WebSocket 模式) -2. 添加事件:`im.message.receive_v1`(接收消息) +1. 选择 **Use long connection to receive events**(WebSocket) +2. 添加事件:`im.message.receive_v1` -⚠️ **注意**:如果网关未启动或渠道未添加,长连接设置将保存失败。 +⚠️ 如果 Gateway 网关未运行,长连接设置可能无法保存。 -![配置事件订阅](/images/feishu-step6-event-subscription.png) +![Configure event subscription](../images/feishu-step6-event-subscription.png) ### 7. 发布应用 -1. 在 **版本管理与发布** 页面创建版本 +1. 在 **Version Management & Release** 中创建版本 2. 提交审核并发布 -3. 等待管理员审批(企业自建应用通常自动通过) +3. 等待管理员批准(企业应用通常会自动批准) --- -## 第二步:配置 OpenClaw +## 第 2 步:配置 OpenClaw -### 通过向导配置(推荐) - -运行以下命令,根据提示粘贴 App ID 和 App Secret: +### 使用向导配置(推荐) ```bash openclaw channels add ``` -选择 **Feishu**,然后输入您在第一步获取的凭证即可。 +选择 **Feishu**,然后粘贴你的 App ID 和 App Secret。 -### 通过配置文件配置 +### 通过配置文件进行配置 编辑 `~/.openclaw/openclaw.json`: @@ -189,7 +190,7 @@ openclaw channels add main: { appId: "cli_xxx", appSecret: "xxx", - botName: "我的AI助手", + botName: "My AI assistant", }, }, }, @@ -197,18 +198,20 @@ openclaw channels add } ``` -若使用 `connectionMode: "webhook"`,需设置 `verificationToken`。飞书 Webhook 服务默认绑定 `127.0.0.1`;仅在需要不同监听地址时设置 `webhookHost`。 +如果你使用 `connectionMode: "webhook"`,请同时设置 `verificationToken` 和 `encryptKey`。飞书 webhook 服务器默认绑定到 `127.0.0.1`;只有在你明确需要不同绑定地址时,才设置 `webhookHost`。 -#### 获取 Verification Token(仅 Webhook 模式) +#### Verification Token 和 Encrypt Key(webhook 模式) -使用 Webhook 模式时,需在配置中设置 `channels.feishu.verificationToken`。获取方式: +使用 webhook 模式时,请在配置中同时设置 `channels.feishu.verificationToken` 和 `channels.feishu.encryptKey`。获取这些值的方法如下: -1. 在飞书开放平台打开您的应用 -2. 进入 **开发配置** → **事件与回调** -3. 打开 **加密策略** 选项卡 -4. 复制 **Verification Token**(校验令牌) +1. 在飞书开放平台中,打开你的应用 +2. 前往 **Development** → **Events & Callbacks**(开发配置 → 事件与回调) +3. 打开 **Encryption** 标签页(加密策略) +4. 复制 **Verification Token** 和 **Encrypt Key** -![Verification Token 位置](/images/feishu-verification-token.png) +下图展示了 **Verification Token** 的位置。**Encrypt Key** 位于同一个 **Encryption** 区域中。 + +![Verification Token location](../images/feishu-verification-token.png) ### 通过环境变量配置 @@ -219,7 +222,7 @@ export FEISHU_APP_SECRET="xxx" ### Lark(国际版)域名 -如果您的租户在 Lark(国际版),请设置域名为 `lark`(或完整域名),可配置 `channels.feishu.domain` 或 `channels.feishu.accounts..domain`: +如果你的租户位于 Lark(国际版),请将域名设置为 `lark`(或完整域名字串)。你可以在 `channels.feishu.domain` 设置,也可以按账户设置(`channels.feishu.accounts..domain`)。 ```json5 { @@ -237,14 +240,14 @@ export FEISHU_APP_SECRET="xxx" } ``` -### 配额优化 +### 配额优化标志 -可通过以下可选配置减少飞书 API 调用: +你可以使用两个可选标志来减少飞书 API 使用量: -- `typingIndicator`(默认 `true`):设为 `false` 时不发送“正在输入”状态。 -- `resolveSenderNames`(默认 `true`):设为 `false` 时不拉取发送者资料。 +- `typingIndicator`(默认 `true`):设为 `false` 时,跳过“正在输入”反应调用。 +- `resolveSenderNames`(默认 `true`):设为 `false` 时,跳过发送者资料查询调用。 -可在渠道级或账号级配置: +你可以在顶层或按账户进行设置: ```json5 { @@ -267,9 +270,9 @@ export FEISHU_APP_SECRET="xxx" --- -## 第三步:启动并测试 +## 第 3 步:启动并测试 -### 1. 启动网关 +### 1. 启动 Gateway 网关 ```bash openclaw gateway @@ -277,74 +280,74 @@ openclaw gateway ### 2. 发送测试消息 -在飞书中找到您创建的机器人,发送一条消息。 +在飞书中找到你的机器人并发送一条消息。 -### 3. 配对授权 +### 3. 批准配对 -默认情况下,机器人会回复一个 **配对码**。您需要批准此代码: +默认情况下,机器人会回复一个配对码。批准它: ```bash -openclaw pairing approve feishu <配对码> +openclaw pairing approve feishu ``` -批准后即可正常对话。 +批准后,你就可以正常聊天了。 --- -## 介绍 +## 概览 -- **飞书机器人渠道**:由网关管理的飞书机器人 -- **确定性路由**:回复始终返回飞书,模型不会选择渠道 -- **会话隔离**:私聊共享主会话;群组独立隔离 -- **WebSocket 连接**:使用飞书 SDK 的长连接模式,无需公网 URL +- **飞书机器人渠道**:由 Gateway 网关管理的飞书机器人 +- **确定性路由**:回复始终返回到飞书 +- **会话隔离**:私信共享主会话;群组彼此隔离 +- **WebSocket 连接**:通过飞书 SDK 建立长连接,无需公共 URL --- ## 访问控制 -### 私聊访问 +### 私信 -- **默认**:`dmPolicy: "pairing"`,陌生用户会收到配对码 +- **默认**:`dmPolicy: "pairing"`(未知用户会收到配对码) - **批准配对**: - ```bash - openclaw pairing list feishu # 查看待审批列表 - openclaw pairing approve feishu # 批准 - ``` -- **白名单模式**:通过 `channels.feishu.allowFrom` 配置允许的用户 Open ID -### 群组访问 + ```bash + openclaw pairing list feishu + openclaw pairing approve feishu + ``` + +- **Allowlist 模式**:设置 `channels.feishu.allowFrom`,填入允许的 Open ID + +### 群聊 **1. 群组策略**(`channels.feishu.groupPolicy`): -- `"open"` = 允许群组中所有人(默认) -- `"allowlist"` = 仅允许 `groupAllowFrom` 中的群组 -- `"disabled"` = 禁用群组消息 +- `"open"` = 允许群组中的所有人(默认) +- `"allowlist"` = 仅允许 `groupAllowFrom` +- `"disabled"` = 禁用群消息 -**2. @提及要求**(`channels.feishu.groups..requireMention`): +**2. 提及要求**(`channels.feishu.groups..requireMention`): -- `true` = 需要 @机器人才响应(默认) -- `false` = 无需 @也响应 +- `true` = 需要 @ 提及(默认) +- `false` = 无需提及也会回复 --- ## 群组配置示例 -### 允许所有群组,需要 @提及(默认行为) +### 允许所有群组,要求 @ 提及(默认) ```json5 { channels: { feishu: { groupPolicy: "open", - // 默认 requireMention: true + // Default requireMention: true }, }, } ``` -### 允许所有群组,无需 @提及 - -需要为特定群组配置: +### 允许所有群组,无需 @ 提及 ```json5 { @@ -365,16 +368,16 @@ openclaw pairing approve feishu <配对码> channels: { feishu: { groupPolicy: "allowlist", - // 群组 ID 格式为 oc_xxx + // Feishu group IDs (chat_id) look like: oc_xxx groupAllowFrom: ["oc_xxx", "oc_yyy"], }, }, } ``` -### 仅允许特定成员在群组中发信(发送者白名单) +### 限制哪些发送者可以在群组中发消息(发送者 allowlist) -除群组白名单外,该群组内**所有消息**均按发送者 open_id 校验:仅 `groups..allowFrom` 中列出的用户消息会被处理,其他成员的消息会被忽略(此为发送者级白名单,不仅针对 /reset、/new 等控制命令)。 +除了允许群组本身外,该群组中的**所有消息**还会按发送者 `open_id` 进行限制:只有列在 `groups..allowFrom` 中的用户,其消息才会被处理;其他成员发送的消息会被忽略(这是完整的发送者级限制,而不只是对 `/reset` 或 `/new` 等控制命令生效)。 ```json5 { @@ -384,7 +387,7 @@ openclaw pairing approve feishu <配对码> groupAllowFrom: ["oc_xxx"], groups: { oc_xxx: { - // 用户 open_id 格式为 ou_xxx + // Feishu user IDs (open_id) look like: ou_xxx allowFrom: ["ou_user1", "ou_user2"], }, }, @@ -397,29 +400,31 @@ openclaw pairing approve feishu <配对码> ## 获取群组/用户 ID -### 获取群组 ID(chat_id) +### 群组 ID(`chat_id`) -群组 ID 格式为 `oc_xxx`,可以通过以下方式获取: +群组 ID 看起来像 `oc_xxx`。 -**方法一**(推荐): +**方法 1(推荐)** -1. 启动网关并在群组中 @机器人发消息 -2. 运行 `openclaw logs --follow` 查看日志中的 `chat_id` +1. 启动 Gateway 网关并在群里 @ 提及机器人 +2. 运行 `openclaw logs --follow` 并查找 `chat_id` -**方法二**: -使用飞书 API 调试工具获取机器人所在群组列表。 +**方法 2** -### 获取用户 ID(open_id) +使用飞书 API 调试器列出群聊。 -用户 ID 格式为 `ou_xxx`,可以通过以下方式获取: +### 用户 ID(`open_id`) -**方法一**(推荐): +用户 ID 看起来像 `ou_xxx`。 -1. 启动网关并给机器人发消息 -2. 运行 `openclaw logs --follow` 查看日志中的 `open_id` +**方法 1(推荐)** -**方法二**: -查看配对请求列表,其中包含用户的 Open ID: +1. 启动 Gateway 网关并向机器人发送私信 +2. 运行 `openclaw logs --follow` 并查找 `open_id` + +**方法 2** + +检查配对请求中的用户 Open ID: ```bash openclaw pairing list feishu @@ -429,65 +434,61 @@ openclaw pairing list feishu ## 常用命令 -| 命令 | 说明 | +| Command | Description | | --------- | -------------- | -| `/status` | 查看机器人状态 | -| `/reset` | 重置对话会话 | -| `/model` | 查看/切换模型 | +| `/status` | 显示机器人状态 | +| `/reset` | 重置会话 | +| `/model` | 显示/切换模型 | -> 注意:飞书目前不支持原生命令菜单,命令需要以文本形式发送。 +> 注意:飞书暂不支持原生命令菜单,因此命令必须以文本形式发送。 -## 网关管理命令 +## Gateway 网关管理命令 -在配置和使用飞书渠道时,您可能需要使用以下网关管理命令: - -| 命令 | 说明 | -| -------------------------- | ----------------- | -| `openclaw gateway status` | 查看网关运行状态 | -| `openclaw gateway install` | 安装/启动网关服务 | -| `openclaw gateway stop` | 停止网关服务 | -| `openclaw gateway restart` | 重启网关服务 | -| `openclaw logs --follow` | 实时查看日志输出 | +| Command | Description | +| -------------------------- | -------------------------- | +| `openclaw gateway status` | 显示 Gateway 网关状态 | +| `openclaw gateway install` | 安装/启动 Gateway 网关服务 | +| `openclaw gateway stop` | 停止 Gateway 网关服务 | +| `openclaw gateway restart` | 重启 Gateway 网关服务 | +| `openclaw logs --follow` | 跟踪 Gateway 网关日志 | --- ## 故障排除 -### 机器人在群组中不响应 +### 机器人在群聊中没有响应 -1. 检查机器人是否已添加到群组 -2. 检查是否 @了机器人(默认需要 @提及) -3. 检查 `groupPolicy` 是否为 `"disabled"` -4. 查看日志:`openclaw logs --follow` +1. 确保机器人已加入群组 +2. 确保你 @ 提及了机器人(默认行为) +3. 检查 `groupPolicy` 未设置为 `"disabled"` +4. 检查日志:`openclaw logs --follow` -### 机器人收不到消息 +### 机器人未接收到消息 -1. 检查应用是否已发布并审批通过 -2. 检查事件订阅是否配置正确(`im.message.receive_v1`) -3. 检查是否选择了 **长连接** 模式 -4. 检查应用权限是否完整 -5. 检查网关是否正在运行:`openclaw gateway status` -6. 查看实时日志:`openclaw logs --follow` +1. 确保应用已发布并获批准 +2. 确保事件订阅包含 `im.message.receive_v1` +3. 确保已启用**长连接** +4. 确保应用权限完整 +5. 确保 Gateway 网关正在运行:`openclaw gateway status` +6. 检查日志:`openclaw logs --follow` -### App Secret 泄露怎么办 +### App Secret 泄露 -1. 在飞书开放平台重置 App Secret -2. 更新配置文件中的 App Secret -3. 重启网关 +1. 在飞书开放平台中重置 App Secret +2. 在你的配置中更新 App Secret +3. 重启 Gateway 网关 -### 发送消息失败 +### 消息发送失败 -1. 检查应用是否有 `im:message:send_as_bot` 权限 -2. 检查应用是否已发布 -3. 查看日志获取详细错误信息 +1. 确保应用具有 `im:message:send_as_bot` 权限 +2. 确保应用已发布 +3. 查看日志以获取详细错误信息 --- ## 高级配置 -### 多账号配置 - -如果需要管理多个飞书机器人,可配置 `defaultAccount` 指定出站未显式指定 `accountId` 时使用的账号: +### 多账户 ```json5 { @@ -498,13 +499,13 @@ openclaw pairing list feishu main: { appId: "cli_xxx", appSecret: "xxx", - botName: "主机器人", + botName: "Primary bot", }, backup: { appId: "cli_yyy", appSecret: "yyy", - botName: "备用机器人", - enabled: false, // 暂时禁用 + botName: "Backup bot", + enabled: false, }, }, }, @@ -512,66 +513,102 @@ openclaw pairing list feishu } ``` +`defaultAccount` 用于控制当出站 API 未显式指定 `accountId` 时,使用哪个飞书账户。 + ### 消息限制 -- `textChunkLimit`:出站文本分块大小(默认 2000 字符) -- `mediaMaxMb`:媒体上传/下载限制(默认 30MB) +- `textChunkLimit`:出站文本分块大小(默认:2000 个字符) +- `mediaMaxMb`:媒体上传/下载限制(默认:30 MB) -### 流式输出 +### 流式传输 -飞书支持通过交互式卡片实现流式输出,机器人会实时更新卡片内容显示生成进度。默认配置: +飞书通过交互式卡片支持流式回复。启用后,机器人会在生成文本时更新卡片。 ```json5 { channels: { feishu: { streaming: true, // 启用流式卡片输出(默认 true) - blockStreaming: true, // 启用块级流式(默认 true) + blockStreaming: true, // 启用分块流式传输(默认 true) }, }, } ``` -如需禁用流式输出(等待完整回复后一次性发送),可设置 `streaming: false`。 +将 `streaming: false` 设为等待完整回复生成后再发送。 -### 消息引用 +### ACP 会话 -在群聊中,机器人的回复可以引用用户发送的原始消息,让对话上下文更加清晰。 +飞书支持以下 ACP 场景: -配置选项: +- 私信 +- 群组话题会话 + +飞书 ACP 由文本命令驱动。没有原生斜杠命令菜单,因此请直接在会话中使用 `/acp ...` 消息。 + +#### 持久化 ACP 绑定 + +使用顶层类型化 ACP 绑定,将飞书私信或话题会话固定到持久化 ACP 会话。 ```json5 { - channels: { - feishu: { - // 账户级别配置(默认 "all") - replyToMode: "all", - groups: { - oc_xxx: { - // 特定群组可以覆盖 - replyToMode: "first", + agents: { + list: [ + { + id: "codex", + runtime: { + type: "acp", + acp: { + agent: "codex", + backend: "acpx", + mode: "persistent", + cwd: "/workspace/openclaw", + }, }, }, - }, + ], }, + bindings: [ + { + type: "acp", + agentId: "codex", + match: { + channel: "feishu", + accountId: "default", + peer: { kind: "direct", id: "ou_1234567890" }, + }, + }, + { + type: "acp", + agentId: "codex", + match: { + channel: "feishu", + accountId: "default", + peer: { kind: "group", id: "oc_group_chat:topic:om_topic_root" }, + }, + acp: { label: "codex-feishu-topic" }, + }, + ], } ``` -`replyToMode` 值说明: +#### 从聊天中按线程绑定 ACP 生成 -| 值 | 行为 | -| --------- | ---------------------------------- | -| `"off"` | 不引用原消息(私聊默认值) | -| `"first"` | 仅在第一条回复时引用原消息 | -| `"all"` | 所有回复都引用原消息(群聊默认值) | +在飞书私信或话题会话中,你可以就地生成并绑定一个 ACP 会话: -> 注意:消息引用功能与流式卡片输出(`streaming: true`)不能同时使用。当启用流式输出时,回复会以卡片形式呈现,不会显示引用。 +```text +/acp spawn codex --thread here +``` -### 多 Agent 路由 +说明: -通过 `bindings` 配置,您可以用一个飞书机器人对接多个不同功能或性格的 Agent。系统会根据用户 ID 或群组 ID 自动将对话分发到对应的 Agent。 +- `--thread here` 适用于私信和飞书话题。 +- 绑定后的私信/话题中的后续消息会直接路由到该 ACP 会话。 +- v1 不支持针对通用的非话题群聊。 -配置示例: +### 多智能体路由 + +使用 `bindings` 将飞书私信或群组路由到不同的智能体。 ```json5 { @@ -592,86 +629,81 @@ openclaw pairing list feishu }, bindings: [ { - // 用户 A 的私聊 → main agent agentId: "main", match: { channel: "feishu", - peer: { kind: "dm", id: "ou_28b31a88..." }, + peer: { kind: "direct", id: "ou_xxx" }, }, }, { - // 用户 B 的私聊 → clawd-fan agent agentId: "clawd-fan", match: { channel: "feishu", - peer: { kind: "dm", id: "ou_0fe6b1c9..." }, + peer: { kind: "direct", id: "ou_yyy" }, }, }, { - // 某个群组 → clawd-xi agent agentId: "clawd-xi", match: { channel: "feishu", - peer: { kind: "group", id: "oc_xxx..." }, + peer: { kind: "group", id: "oc_zzz" }, }, }, ], } ``` -匹配规则说明: +路由字段: -| 字段 | 说明 | -| ----------------- | --------------------------------------------- | -| `agentId` | 目标 Agent 的 ID,需要在 `agents.list` 中定义 | -| `match.channel` | 渠道类型,这里固定为 `"feishu"` | -| `match.peer.kind` | 对话类型:`"dm"`(私聊)或 `"group"`(群组) | -| `match.peer.id` | 用户 Open ID(`ou_xxx`)或群组 ID(`oc_xxx`) | +- `match.channel`:`"feishu"` +- `match.peer.kind`:`"direct"` 或 `"group"` +- `match.peer.id`:用户 Open ID(`ou_xxx`)或群组 ID(`oc_xxx`) -> 获取 ID 的方法:参见上文 [获取群组/用户 ID](#获取群组用户-id) 章节。 +查找提示请参见 [获取群组/用户 ID](#get-groupuser-ids)。 --- ## 配置参考 -完整配置请参考:[网关配置](/gateway/configuration) +完整配置:[Gateway 网关配置](/gateway/configuration) -主要选项: +关键选项: -| 配置项 | 说明 | 默认值 | -| ------------------------------------------------- | --------------------------------- | ---------------- | -| `channels.feishu.enabled` | 启用/禁用渠道 | `true` | -| `channels.feishu.domain` | API 域名(`feishu` 或 `lark`) | `feishu` | -| `channels.feishu.connectionMode` | 事件传输模式(websocket/webhook) | `websocket` | -| `channels.feishu.defaultAccount` | 出站路由默认账号 ID | `default` | -| `channels.feishu.verificationToken` | Webhook 模式必填 | - | -| `channels.feishu.webhookPath` | Webhook 路由路径 | `/feishu/events` | -| `channels.feishu.webhookHost` | Webhook 监听地址 | `127.0.0.1` | -| `channels.feishu.webhookPort` | Webhook 监听端口 | `3000` | -| `channels.feishu.accounts..appId` | 应用 App ID | - | -| `channels.feishu.accounts..appSecret` | 应用 App Secret | - | -| `channels.feishu.accounts..domain` | 单账号 API 域名覆盖 | `feishu` | -| `channels.feishu.dmPolicy` | 私聊策略 | `pairing` | -| `channels.feishu.allowFrom` | 私聊白名单(open_id 列表) | - | -| `channels.feishu.groupPolicy` | 群组策略 | `open` | -| `channels.feishu.groupAllowFrom` | 群组白名单 | - | -| `channels.feishu.groups..requireMention` | 是否需要 @提及 | `true` | -| `channels.feishu.groups..enabled` | 是否启用该群组 | `true` | -| `channels.feishu.textChunkLimit` | 消息分块大小 | `2000` | -| `channels.feishu.mediaMaxMb` | 媒体大小限制 | `30` | -| `channels.feishu.streaming` | 启用流式卡片输出 | `true` | -| `channels.feishu.blockStreaming` | 启用块级流式 | `true` | +| Setting | Description | Default | +| ------------------------------------------------- | -------------------------------- | ---------------- | +| `channels.feishu.enabled` | 启用/禁用渠道 | `true` | +| `channels.feishu.domain` | API 域名(`feishu` 或 `lark`) | `feishu` | +| `channels.feishu.connectionMode` | 事件传输模式 | `websocket` | +| `channels.feishu.defaultAccount` | 出站路由的默认账户 ID | `default` | +| `channels.feishu.verificationToken` | webhook 模式必填 | - | +| `channels.feishu.encryptKey` | webhook 模式必填 | - | +| `channels.feishu.webhookPath` | webhook 路由路径 | `/feishu/events` | +| `channels.feishu.webhookHost` | webhook 绑定主机 | `127.0.0.1` | +| `channels.feishu.webhookPort` | webhook 绑定端口 | `3000` | +| `channels.feishu.accounts..appId` | App ID | - | +| `channels.feishu.accounts..appSecret` | App Secret | - | +| `channels.feishu.accounts..domain` | 按账户覆盖 API 域名 | `feishu` | +| `channels.feishu.dmPolicy` | 私信策略 | `pairing` | +| `channels.feishu.allowFrom` | 私信 allowlist(`open_id` 列表) | - | +| `channels.feishu.groupPolicy` | 群组策略 | `open` | +| `channels.feishu.groupAllowFrom` | 群组 allowlist | - | +| `channels.feishu.groups..requireMention` | 要求 @ 提及 | `true` | +| `channels.feishu.groups..enabled` | 启用群组 | `true` | +| `channels.feishu.textChunkLimit` | 消息分块大小 | `2000` | +| `channels.feishu.mediaMaxMb` | 媒体大小限制 | `30` | +| `channels.feishu.streaming` | 启用流式卡片输出 | `true` | +| `channels.feishu.blockStreaming` | 启用分块流式传输 | `true` | --- -## dmPolicy 策略说明 +## dmPolicy 参考 -| 值 | 行为 | -| ------------- | -------------------------------------------------- | -| `"pairing"` | **默认**。未知用户收到配对码,管理员批准后才能对话 | -| `"allowlist"` | 仅 `allowFrom` 列表中的用户可对话,其他静默忽略 | -| `"open"` | 允许所有人对话(需在 allowFrom 中加 `"*"`) | -| `"disabled"` | 完全禁止私聊 | +| Value | Behavior | +| ------------- | ---------------------------------------------------- | +| `"pairing"` | **默认。**未知用户会收到配对码;必须获批准后才能使用 | +| `"allowlist"` | 只有 `allowFrom` 中的用户可以聊天 | +| `"open"` | 允许所有用户(要求 `allowFrom` 中有 `"*"`) | +| `"disabled"` | 禁用私信 | --- @@ -679,17 +711,17 @@ openclaw pairing list feishu ### 接收 -- ✅ 文本消息 -- ✅ 富文本(帖子) +- ✅ 文本 +- ✅ 富文本(post) - ✅ 图片 - ✅ 文件 - ✅ 音频 - ✅ 视频 -- ✅ 表情包 +- ✅ 贴纸 ### 发送 -- ✅ 文本消息 +- ✅ 文本 - ✅ 图片 - ✅ 文件 - ✅ 音频 diff --git a/docs/zh-CN/channels/nostr.md b/docs/zh-CN/channels/nostr.md index 7d0359ce21d..47efa40aedf 100644 --- a/docs/zh-CN/channels/nostr.md +++ b/docs/zh-CN/channels/nostr.md @@ -1,14 +1,14 @@ --- read_when: - - 你希望 OpenClaw 通过 Nostr 接收私信 - - 你正在设置去中心化消息 -summary: 通过 NIP-04 加密消息的 Nostr 私信渠道 + - 你想让 OpenClaw 通过 Nostr 接收私信 + - 你正在设置去中心化消息传递 +summary: 通过 NIP-04 加密消息实现的 Nostr 私信渠道 title: Nostr x-i18n: - generated_at: "2026-02-03T07:44:13Z" - model: claude-opus-4-5 - provider: pi - source_hash: 6b9fe4c74bf5e7c0f59bbaa129ec5270fd29a248551a8a9a7dde6cff8fb46111 + generated_at: "2026-03-16T06:20:37Z" + model: gpt-5.4 + provider: openai + source_hash: fcce57da49256971420c4bb099aebb7944f8c7e8619b17b163da685add225001 source_path: channels/nostr.md workflow: 15 --- @@ -17,21 +17,21 @@ x-i18n: **状态:** 可选插件(默认禁用)。 -Nostr 是一个去中心化的社交网络协议。此渠道使 OpenClaw 能够通过 NIP-04 接收和回复加密私信(DMs)。 +Nostr 是一种用于社交网络的去中心化协议。此渠道使 OpenClaw 能够通过 NIP-04 接收并回复加密私信。 ## 安装(按需) ### 新手引导(推荐) -- 新手引导向导(`openclaw onboard`)和 `openclaw channels add` 会列出可选的渠道插件。 -- 选择 Nostr 会提示你按需安装插件。 +- 设置向导(`openclaw onboard`)和 `openclaw channels add` 会列出可选渠道插件。 +- 选择 Nostr 时,系统会提示你按需安装该插件。 -安装默认值: +安装默认行为: -- **Dev 渠道 + git checkout 可用:** 使用本地插件路径。 -- **Stable/Beta:** 从 npm 下载。 +- **Dev 渠道 + 可用的 git 检出:** 使用本地插件路径。 +- **稳定版 / Beta:** 从 npm 下载。 -你可以随时在提示中覆盖选择。 +你始终可以在提示中覆盖该选择。 ### 手动安装 @@ -39,24 +39,33 @@ Nostr 是一个去中心化的社交网络协议。此渠道使 OpenClaw 能够 openclaw plugins install @openclaw/nostr ``` -使用本地 checkout(开发工作流): +使用本地检出(dev 工作流): ```bash openclaw plugins install --link /extensions/nostr ``` -安装或启用插件后重启 Gateway 网关。 +安装或启用插件后,重启 Gateway 网关。 + +### 非交互式设置 + +```bash +openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY" +openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY" --relay-urls "wss://relay.damus.io,wss://relay.primal.net" +``` + +使用 `--use-env` 可将 `NOSTR_PRIVATE_KEY` 保留在环境中,而不是将密钥存储在配置里。 ## 快速设置 -1. 生成 Nostr 密钥对(如需要): +1. 生成一个 Nostr 密钥对(如有需要): ```bash -# 使用 nak +# Using nak nak key generate ``` -2. 添加到配置: +2. 添加到配置中: ```json { @@ -78,19 +87,19 @@ export NOSTR_PRIVATE_KEY="nsec1..." ## 配置参考 -| 键 | 类型 | 默认值 | 描述 | +| 键 | 类型 | 默认值 | 说明 | | ------------ | -------- | ------------------------------------------- | --------------------------- | | `privateKey` | string | 必填 | `nsec` 或十六进制格式的私钥 | | `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | 中继 URL(WebSocket) | | `dmPolicy` | string | `pairing` | 私信访问策略 | -| `allowFrom` | string[] | `[]` | 允许的发送者公钥 | -| `enabled` | boolean | `true` | 启用/禁用渠道 | +| `allowFrom` | string[] | `[]` | 允许的发送方公钥 | +| `enabled` | boolean | `true` | 启用 / 禁用渠道 | | `name` | string | - | 显示名称 | -| `profile` | object | - | NIP-01 个人资料元数据 | +| `profile` | object | - | NIP-01 资料元数据 | -## 个人资料元数据 +## 资料元数据 -个人资料数据作为 NIP-01 `kind:0` 事件发布。你可以从控制界面(Channels -> Nostr -> Profile)管理它,或直接在配置中设置。 +资料数据会作为 NIP-01 `kind:0` 事件发布。你可以在控制 UI 中管理它(Channels -> Nostr -> Profile),也可以直接在配置中设置。 示例: @@ -114,19 +123,19 @@ export NOSTR_PRIVATE_KEY="nsec1..." } ``` -注意事项: +注意: -- 个人资料 URL 必须使用 `https://`。 -- 从中继导入会合并字段并保留本地覆盖。 +- 资料 URL 必须使用 `https://`。 +- 从中继导入时会合并字段,并保留本地覆盖项。 ## 访问控制 ### 私信策略 -- **pairing**(默认):未知发送者会收到配对码。 +- **pairing**(默认):未知发送方会收到一个配对码。 - **allowlist**:只有 `allowFrom` 中的公钥可以发送私信。 -- **open**:公开接收私信(需要 `allowFrom: ["*"]`)。 -- **disabled**:忽略接收的私信。 +- **open**:公开接收入站私信(要求 `allowFrom: ["*"]`)。 +- **disabled**:忽略入站私信。 ### 允许列表示例 @@ -166,26 +175,26 @@ export NOSTR_PRIVATE_KEY="nsec1..." 提示: -- 使用 2-3 个中继以实现冗余。 +- 使用 2 到 3 个中继以实现冗余。 - 避免使用过多中继(延迟、重复)。 - 付费中继可以提高可靠性。 -- 本地中继适合测试(`ws://localhost:7777`)。 +- 本地中继也适合测试(`ws://localhost:7777`)。 ## 协议支持 -| NIP | 状态 | 描述 | -| ------ | ------ | ----------------------------- | -| NIP-01 | 已支持 | 基本事件格式 + 个人资料元数据 | -| NIP-04 | 已支持 | 加密私信(`kind:4`) | -| NIP-17 | 计划中 | 礼物包装私信 | -| NIP-44 | 计划中 | 版本化加密 | +| NIP | 状态 | 说明 | +| ------ | ------ | ------------------------- | +| NIP-01 | 已支持 | 基础事件格式 + 资料元数据 | +| NIP-04 | 已支持 | 加密私信(`kind:4`) | +| NIP-17 | 计划中 | Gift-wrapped 私信 | +| NIP-44 | 计划中 | 版本化加密 | ## 测试 ### 本地中继 ```bash -# 启动 strfry +# Start strfry docker run -p 7777:7777 ghcr.io/hoytech/strfry ``` @@ -203,38 +212,38 @@ docker run -p 7777:7777 ghcr.io/hoytech/strfry ### 手动测试 1. 从日志中记下机器人公钥(npub)。 -2. 打开 Nostr 客户端(Damus、Amethyst 等)。 -3. 向机器人公钥发送私信。 -4. 验证响应。 +2. 打开一个 Nostr 客户端(Damus、Amethyst 等)。 +3. 向该机器人公钥发送私信。 +4. 验证回复。 ## 故障排除 -### 未收到消息 +### 未接收到消息 -- 验证私钥是否有效。 -- 确保中继 URL 可访问并使用 `wss://`(本地使用 `ws://`)。 +- 验证私钥有效。 +- 确保中继 URL 可访问,并使用 `wss://`(本地则使用 `ws://`)。 - 确认 `enabled` 不是 `false`。 - 检查 Gateway 网关日志中的中继连接错误。 -### 未发送响应 +### 未发送回复 - 检查中继是否接受写入。 -- 验证出站连接。 +- 验证出站连接性。 - 注意中继速率限制。 -### 重复响应 +### 重复回复 -- 使用多个中继时属于正常现象。 -- 消息按事件 ID 去重;只有首次投递会触发响应。 +- 使用多个中继时这是预期行为。 +- 消息会按事件 ID 去重;只有首次投递会触发回复。 -## 安全 +## 安全性 - 切勿提交私钥。 -- 使用环境变量存储密钥。 -- 生产环境机器人考虑使用 `allowlist`。 +- 对密钥使用环境变量。 +- 对生产机器人考虑使用 `allowlist`。 ## 限制(MVP) - 仅支持私信(不支持群聊)。 - 不支持媒体附件。 -- 仅支持 NIP-04(计划支持 NIP-17 礼物包装)。 +- 仅支持 NIP-04(计划支持 NIP-17 gift-wrap)。 diff --git a/docs/zh-CN/channels/synology-chat.md b/docs/zh-CN/channels/synology-chat.md new file mode 100644 index 00000000000..4323e5d12d7 --- /dev/null +++ b/docs/zh-CN/channels/synology-chat.md @@ -0,0 +1,138 @@ +--- +read_when: + - 设置 OpenClaw 与 Synology Chat + - 调试 Synology Chat webhook 路由 +summary: Synology Chat webhook 设置与 OpenClaw 配置 +title: Synology Chat +x-i18n: + generated_at: "2026-03-16T06:20:51Z" + model: gpt-5.4 + provider: openai + source_hash: 7d77598ea759f89873a1edf0a3a7e7fedc1e4a7067709aaca6b999056a89eb1a + source_path: channels/synology-chat.md + workflow: 15 +--- + +# Synology Chat(插件) + +状态:通过插件支持,作为使用 Synology Chat webhook 的私信渠道。 +该插件接受来自 Synology Chat 出站 webhook 的入站消息,并通过 Synology Chat 入站 webhook 发送回复。 + +## 需要插件 + +Synology Chat 基于插件,不属于默认的核心渠道安装内容。 + +从本地检出安装: + +```bash +openclaw plugins install ./extensions/synology-chat +``` + +详情:[插件](/tools/plugin) + +## 快速设置 + +1. 安装并启用 Synology Chat 插件。 + - `openclaw onboard` 现在会在与 `openclaw channels add` 相同的渠道设置列表中显示 Synology Chat。 + - 非交互式设置:`openclaw channels add --channel synology-chat --token --url ` +2. 在 Synology Chat 集成中: + - 创建一个入站 webhook 并复制其 URL。 + - 使用你的 secret token 创建一个出站 webhook。 +3. 将出站 webhook URL 指向你的 OpenClaw Gateway 网关: + - 默认是 `https://gateway-host/webhook/synology`。 + - 或者使用你自定义的 `channels.synology-chat.webhookPath`。 +4. 在 OpenClaw 中完成设置。 + - 引导式:`openclaw onboard` + - 直接设置:`openclaw channels add --channel synology-chat --token --url ` +5. 重启 Gateway 网关,并向 Synology Chat 机器人发送一条私信。 + +最小配置: + +```json5 +{ + channels: { + "synology-chat": { + enabled: true, + token: "synology-outgoing-token", + incomingUrl: "https://nas.example.com/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=...", + webhookPath: "/webhook/synology", + dmPolicy: "allowlist", + allowedUserIds: ["123456"], + rateLimitPerMinute: 30, + allowInsecureSsl: false, + }, + }, +} +``` + +## 环境变量 + +对于默认账户,你可以使用环境变量: + +- `SYNOLOGY_CHAT_TOKEN` +- `SYNOLOGY_CHAT_INCOMING_URL` +- `SYNOLOGY_NAS_HOST` +- `SYNOLOGY_ALLOWED_USER_IDS`(逗号分隔) +- `SYNOLOGY_RATE_LIMIT` +- `OPENCLAW_BOT_NAME` + +配置值会覆盖环境变量。 + +## 私信策略与访问控制 + +- 推荐的默认值是 `dmPolicy: "allowlist"`。 +- `allowedUserIds` 接受 Synology 用户 ID 列表(或逗号分隔字符串)。 +- 在 `allowlist` 模式下,空的 `allowedUserIds` 列表会被视为配置错误,webhook 路由将不会启动(如需允许所有人,请使用 `dmPolicy: "open"`)。 +- `dmPolicy: "open"` 允许任何发送方。 +- `dmPolicy: "disabled"` 会阻止私信。 +- 配对批准可配合以下命令使用: + - `openclaw pairing list synology-chat` + - `openclaw pairing approve synology-chat ` + +## 出站投递 + +使用数字形式的 Synology Chat 用户 ID 作为目标。 + +示例: + +```bash +openclaw message send --channel synology-chat --target 123456 --text "Hello from OpenClaw" +openclaw message send --channel synology-chat --target synology-chat:123456 --text "Hello again" +``` + +支持通过基于 URL 的文件投递发送媒体。 + +## 多账户 + +支持在 `channels.synology-chat.accounts` 下配置多个 Synology Chat 账户。 +每个账户都可以覆盖 token、入站 URL、webhook 路径、私信策略和限制。 + +```json5 +{ + channels: { + "synology-chat": { + enabled: true, + accounts: { + default: { + token: "token-a", + incomingUrl: "https://nas-a.example.com/...token=...", + }, + alerts: { + token: "token-b", + incomingUrl: "https://nas-b.example.com/...token=...", + webhookPath: "/webhook/synology-alerts", + dmPolicy: "allowlist", + allowedUserIds: ["987654"], + }, + }, + }, + }, +} +``` + +## 安全说明 + +- 妥善保管 `token`,如果泄露请轮换。 +- 除非你明确可信任本地 NAS 的自签名证书,否则请保持 `allowInsecureSsl: false`。 +- 入站 webhook 请求会按 token 验证,并按发送方进行速率限制。 +- 生产环境优先使用 `dmPolicy: "allowlist"`。 diff --git a/docs/zh-CN/cli/config.md b/docs/zh-CN/cli/config.md index 06efbaec751..9c63985ab96 100644 --- a/docs/zh-CN/cli/config.md +++ b/docs/zh-CN/cli/config.md @@ -24,7 +24,7 @@ openclaw config get browser.executablePath openclaw config set browser.executablePath "/usr/bin/google-chrome" openclaw config set agents.defaults.heartbeat.every "2h" openclaw config set agents.list[0].tools.exec.node "node-id-or-name" -openclaw config unset tools.web.search.apiKey +openclaw config unset plugins.entries.brave.config.webSearch.apiKey ``` ## 路径 diff --git a/docs/zh-CN/cli/index.md b/docs/zh-CN/cli/index.md index c22fad5c4b4..0533d75d6c6 100644 --- a/docs/zh-CN/cli/index.md +++ b/docs/zh-CN/cli/index.md @@ -1,14 +1,14 @@ --- read_when: - - 添加或修改 CLI 命令或选项 - - 为新命令界面编写文档 -summary: OpenClaw `openclaw` 命令、子命令和选项的 CLI 参考 + - 添加或修改 CLI 命令或选项时 + - 为新的命令界面编写文档时 +summary: "`openclaw` 命令、子命令和选项的 OpenClaw CLI 参考" title: CLI 参考 x-i18n: - generated_at: "2026-02-03T07:47:54Z" - model: claude-opus-4-5 - provider: pi - source_hash: a73923763d7b89d4b183f569d543927ffbfd1f3e02f9e66639913f6daf226850 + generated_at: "2026-03-16T06:22:35Z" + model: gpt-5.4 + provider: openai + source_hash: a2bca34fca64558a8d91fc640ad3880e79677e81d0f605083edc6cbe86bfba53 source_path: cli/index.md workflow: 15 --- @@ -23,8 +23,10 @@ x-i18n: - [`onboard`](/cli/onboard) - [`configure`](/cli/configure) - [`config`](/cli/config) +- [`completion`](/cli/completion) - [`doctor`](/cli/doctor) - [`dashboard`](/cli/dashboard) +- [`backup`](/cli/backup) - [`reset`](/cli/reset) - [`uninstall`](/cli/uninstall) - [`update`](/cli/update) @@ -40,6 +42,7 @@ x-i18n: - [`system`](/cli/system) - [`models`](/cli/models) - [`memory`](/cli/memory) +- [`directory`](/cli/directory) - [`nodes`](/cli/nodes) - [`devices`](/cli/devices) - [`node`](/cli/node) @@ -53,42 +56,46 @@ x-i18n: - [`hooks`](/cli/hooks) - [`webhooks`](/cli/webhooks) - [`pairing`](/cli/pairing) +- [`qr`](/cli/qr) - [`plugins`](/cli/plugins)(插件命令) - [`channels`](/cli/channels) - [`security`](/cli/security) +- [`secrets`](/cli/secrets) - [`skills`](/cli/skills) +- [`daemon`](/cli/daemon)(Gateway 网关服务命令的旧别名) +- [`clawbot`](/cli/clawbot)(旧别名命名空间) - [`voicecall`](/cli/voicecall)(插件;如已安装) ## 全局标志 -- `--dev`:将状态隔离到 `~/.openclaw-dev` 下并调整默认端口。 +- `--dev`:将状态隔离到 `~/.openclaw-dev` 下,并变更默认端口。 - `--profile `:将状态隔离到 `~/.openclaw-` 下。 - `--no-color`:禁用 ANSI 颜色。 -- `--update`:`openclaw update` 的简写(仅限源码安装)。 -- `-V`、`--version`、`-v`:打印版本并退出。 +- `--update`:`openclaw update` 的简写(仅适用于源码安装)。 +- `-V`, `--version`, `-v`:打印版本并退出。 ## 输出样式 - ANSI 颜色和进度指示器仅在 TTY 会话中渲染。 -- OSC-8 超链接在支持的终端中渲染为可点击链接;否则回退到纯 URL。 -- `--json`(以及支持的地方使用 `--plain`)禁用样式以获得干净输出。 -- `--no-color` 禁用 ANSI 样式;也支持 `NO_COLOR=1`。 -- 长时间运行的命令显示进度指示器(支持时使用 OSC 9;4)。 +- OSC-8 超链接会在受支持的终端中显示为可点击链接;否则会回退为纯 URL。 +- `--json`(以及在支持处的 `--plain`)会禁用样式,以获得干净输出。 +- `--no-color` 会禁用 ANSI 样式;同时也支持 `NO_COLOR=1`。 +- 长时间运行的命令会显示进度指示器(支持时使用 OSC 9;4)。 -## 颜色调色板 +## 调色板 -OpenClaw 在 CLI 输出中使用龙虾调色板。 +OpenClaw 在 CLI 输出中使用龙虾色调调色板。 -- `accent`(#FF5A2D):标题、标签、主要高亮。 -- `accentBright`(#FF7A3D):命令名称、强调。 -- `accentDim`(#D14A22):次要高亮文本。 -- `info`(#FF8A5B):信息性值。 -- `success`(#2FBF71):成功状态。 -- `warn`(#FFB020):警告、回退、注意。 -- `error`(#E23D2D):错误、失败。 -- `muted`(#8B7F77):弱化、元数据。 +- `accent` (#FF5A2D):标题、标签、主要高亮。 +- `accentBright` (#FF7A3D):命令名称、强调。 +- `accentDim` (#D14A22):次级高亮文本。 +- `info` (#FF8A5B):信息性值。 +- `success` (#2FBF71):成功状态。 +- `warn` (#FFB020):警告、回退、注意事项。 +- `error` (#E23D2D):错误、失败。 +- `muted` (#8B7F77):弱化显示、元数据。 -调色板权威来源:`src/terminal/palette.ts`(又名"lobster seam")。 +调色板唯一来源:`src/terminal/palette.ts`(也称为 “lobster seam”)。 ## 命令树 @@ -101,9 +108,17 @@ openclaw [--dev] [--profile ] get set unset + completion doctor + dashboard + backup + create + verify security audit + secrets + reload + migrate reset uninstall update @@ -115,6 +130,7 @@ openclaw [--dev] [--profile ] remove login logout + directory skills list info @@ -152,6 +168,13 @@ openclaw [--dev] [--profile ] stop restart run + daemon + status + install + uninstall + start + stop + restart logs system event @@ -238,49 +261,59 @@ openclaw [--dev] [--profile ] pairing list approve + qr + clawbot + qr docs dns setup tui ``` -注意:插件可以添加额外的顶级命令(例如 `openclaw voicecall`)。 +注意:插件可以添加额外的顶层命令(例如 `openclaw voicecall`)。 ## 安全 -- `openclaw security audit` — 审计配置 + 本地状态中常见的安全隐患。 +- `openclaw security audit` — 审计配置 + 本地状态中常见的安全陷阱。 - `openclaw security audit --deep` — 尽力进行实时 Gateway 网关探测。 -- `openclaw security audit --fix` — 收紧安全默认值并 chmod 状态/配置。 +- `openclaw security audit --fix` — 收紧安全默认值并对状态 / 配置执行 chmod。 + +## 密钥 + +- `openclaw secrets reload` — 重新解析引用,并以原子方式替换运行时快照。 +- `openclaw secrets audit` — 扫描明文残留、未解析引用和优先级漂移。 +- `openclaw secrets configure` — 用于提供商设置 + SecretRef 映射 + 预检 / 应用的交互式助手。 +- `openclaw secrets apply --from ` — 应用先前生成的计划(支持 `--dry-run`)。 ## 插件 管理扩展及其配置: -- `openclaw plugins list` — 发现插件(使用 `--json` 获取机器可读输出)。 +- `openclaw plugins list` — 发现插件(机器输出请使用 `--json`)。 - `openclaw plugins info ` — 显示插件详情。 - `openclaw plugins install ` — 安装插件(或将插件路径添加到 `plugins.load.paths`)。 - `openclaw plugins enable ` / `disable ` — 切换 `plugins.entries..enabled`。 - `openclaw plugins doctor` — 报告插件加载错误。 -大多数插件更改需要重启 Gateway 网关。参见 [/plugin](/tools/plugin)。 +大多数插件更改都需要重启 gateway。参见 [/plugin](/tools/plugin)。 -## 记忆 +## 内存 -对 `MEMORY.md` + `memory/*.md` 进行向量搜索: +对 `MEMORY.md` + `memory/*.md` 执行向量搜索: -- `openclaw memory status` — 显示索引统计。 -- `openclaw memory index` — 重新索引记忆文件。 -- `openclaw memory search ""` — 对记忆进行语义搜索。 +- `openclaw memory status` — 显示索引统计信息。 +- `openclaw memory index` — 重新索引内存文件。 +- `openclaw memory search ""`(或 `--query ""`)— 对内存执行语义搜索。 ## 聊天斜杠命令 聊天消息支持 `/...` 命令(文本和原生)。参见 [/tools/slash-commands](/tools/slash-commands)。 -亮点: +重点: - `/status` 用于快速诊断。 - `/config` 用于持久化配置更改。 -- `/debug` 用于仅运行时的配置覆盖(内存中,不写入磁盘;需要 `commands.debug: true`)。 +- `/debug` 用于仅运行时的配置覆盖(内存中,不写磁盘;要求 `commands.debug: true`)。 ## 设置 + 新手引导 @@ -291,32 +324,35 @@ openclaw [--dev] [--profile ] 选项: - `--workspace `:智能体工作区路径(默认 `~/.openclaw/workspace`)。 -- `--wizard`:运行新手引导向导。 -- `--non-interactive`:无提示运行向导。 -- `--mode `:向导模式。 +- `--wizard`:运行新手引导。 +- `--non-interactive`:无提示运行新手引导。 +- `--mode `:新手引导模式。 - `--remote-url `:远程 Gateway 网关 URL。 -- `--remote-token `:远程 Gateway 网关令牌。 +- `--remote-token `:远程 Gateway 网关 token。 -当存在任何向导标志(`--non-interactive`、`--mode`、`--remote-url`、`--remote-token`)时,向导自动运行。 +只要存在任意新手引导标志(`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`),就会自动运行新手引导。 ### `onboard` -交互式向导,用于设置 Gateway 网关、工作区和 Skills。 +用于设置 gateway、工作区和 Skills 的交互式新手引导。 选项: - `--workspace ` -- `--reset`(在向导之前重置配置 + 凭证 + 会话 + 工作区) +- `--reset`(在运行新手引导前重置配置 + 凭据 + 会话) +- `--reset-scope `(默认 `config+creds+sessions`;使用 `full` 还会删除工作区) - `--non-interactive` - `--mode ` -- `--flow `(manual 是 advanced 的别名) -- `--auth-choice ` -- `--token-provider `(非交互式;与 `--auth-choice token` 配合使用) -- `--token `(非交互式;与 `--auth-choice token` 配合使用) +- `--flow `(`manual` 是 `advanced` 的别名) +- `--auth-choice ` +- `--token-provider `(非交互式;与 `--auth-choice token` 一起使用) +- `--token `(非交互式;与 `--auth-choice token` 一起使用) - `--token-profile-id `(非交互式;默认:`:manual`) - `--token-expires-in `(非交互式;例如 `365d`、`12h`) +- `--secret-input-mode `(默认 `plaintext`;使用 `ref` 可存储提供商默认环境引用,而非明文密钥) - `--anthropic-api-key ` - `--openai-api-key ` +- `--mistral-api-key ` - `--openrouter-api-key ` - `--ai-gateway-api-key ` - `--moonshot-api-key ` @@ -325,10 +361,17 @@ openclaw [--dev] [--profile ] - `--zai-api-key ` - `--minimax-api-key ` - `--opencode-zen-api-key ` +- `--opencode-go-api-key ` +- `--custom-base-url `(非交互式;与 `--auth-choice custom-api-key` 或 `--auth-choice ollama` 一起使用) +- `--custom-model-id `(非交互式;与 `--auth-choice custom-api-key` 或 `--auth-choice ollama` 一起使用) +- `--custom-api-key `(非交互式;可选;与 `--auth-choice custom-api-key` 一起使用;省略时回退到 `CUSTOM_API_KEY`) +- `--custom-provider-id `(非交互式;可选自定义提供商 id) +- `--custom-compatibility `(非交互式;可选;默认 `openai`) - `--gateway-port ` - `--gateway-bind ` - `--gateway-auth ` - `--gateway-token ` +- `--gateway-token-ref-env `(非交互式;将 `gateway.auth.token` 存储为环境 SecretRef;要求该环境变量已设置;不能与 `--gateway-token` 一起使用) - `--gateway-password ` - `--remote-url ` - `--remote-token ` @@ -341,35 +384,39 @@ openclaw [--dev] [--profile ] - `--skip-skills` - `--skip-health` - `--skip-ui` -- `--node-manager `(推荐 pnpm;不建议将 bun 用于 Gateway 网关运行时) +- `--node-manager `(推荐 pnpm;不推荐将 bun 用作 Gateway 网关运行时) - `--json` ### `configure` -交互式配置向导(模型、渠道、Skills、Gateway 网关)。 +交互式配置向导(模型、渠道、Skills、gateway)。 ### `config` -非交互式配置辅助工具(get/set/unset)。不带子命令运行 `openclaw config` 会启动向导。 +非交互式配置助手(get/set/unset/file/validate)。直接运行 `openclaw config` 而不带 +子命令会启动向导。 子命令: -- `config get `:打印配置值(点/括号路径)。 -- `config set `:设置值(JSON5 或原始字符串)。 -- `config unset `:删除值。 +- `config get `:打印一个配置值(点 / 方括号路径)。 +- `config set `:设置一个值(JSON5 或原始字符串)。 +- `config unset `:移除一个值。 +- `config file`:打印当前活动配置文件路径。 +- `config validate`:根据 schema 验证当前配置,而不启动 gateway。 +- `config validate --json`:输出机器可读的 JSON。 ### `doctor` -健康检查 + 快速修复(配置 + Gateway 网关 + 旧版服务)。 +健康检查 + 快速修复(配置 + gateway + 旧版服务)。 选项: -- `--no-workspace-suggestions`:禁用工作区记忆提示。 -- `--yes`:无提示接受默认值(无头模式)。 +- `--no-workspace-suggestions`:禁用工作区内存提示。 +- `--yes`:接受默认值而不提示(无头)。 - `--non-interactive`:跳过提示;仅应用安全迁移。 -- `--deep`:扫描系统服务以查找额外的 Gateway 网关安装。 +- `--deep`:扫描系统服务以查找额外的 gateway 安装。 -## 渠道辅助工具 +## 渠道助手 ### `channels` @@ -378,19 +425,21 @@ openclaw [--dev] [--profile ] 子命令: - `channels list`:显示已配置的渠道和认证配置文件。 -- `channels status`:检查 Gateway 网关可达性和渠道健康状况(`--probe` 运行额外检查;使用 `openclaw health` 或 `openclaw status --deep` 进行 Gateway 网关健康探测)。 -- 提示:`channels status` 在检测到常见配置错误时会打印带有建议修复的警告(然后指向 `openclaw doctor`)。 -- `channels logs`:显示 Gateway 网关日志文件中最近的渠道日志。 -- `channels add`:不传标志时使用向导式设置;标志切换到非交互模式。 -- `channels remove`:默认禁用;传 `--delete` 可无提示删除配置条目。 -- `channels login`:交互式渠道登录(仅限 WhatsApp Web)。 -- `channels logout`:登出渠道会话(如支持)。 +- `channels status`:检查 gateway 可达性和渠道健康状态(`--probe` 会运行额外检查;gateway 健康探测请使用 `openclaw health` 或 `openclaw status --deep`)。 +- 提示:如果能够检测到常见配置错误,`channels status` 会打印带建议修复方式的警告(随后指向 `openclaw doctor`)。 +- `channels logs`:显示 gateway 日志文件中的最近渠道日志。 +- `channels add`:未传入任何标志时为向导式设置;传入标志后切换为非交互模式。 + - 当向仍使用单账户顶层配置的渠道添加非默认账户时,OpenClaw 会先将账户作用域值移动到 `channels..accounts.default`,再写入新账户。 + - 非交互式 `channels add` 不会自动创建 / 升级绑定;仅渠道绑定会继续匹配默认账户。 +- `channels remove`:默认执行禁用;传入 `--delete` 可在无提示下删除配置项。 +- `channels login`:交互式渠道登录(仅 WhatsApp Web)。 +- `channels logout`:登出某个渠道会话(如支持)。 通用选项: - `--channel `:`whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams` - `--account `:渠道账户 id(默认 `default`) -- `--name