Compare commits

..

1 Commits

Author SHA1 Message Date
Vincent Koc
2afd1c0077 fix(types): unblock changed gate checks 2026-05-28 17:48:14 +02:00
1787 changed files with 20245 additions and 87768 deletions

View File

@@ -29,11 +29,6 @@ actions:
- openclaw
runnerVersion: latest
ephemeral: true
blacksmith:
org: openclaw
workflow: .github/workflows/ci-check-testbox.yml
job: check
ref: main
aws:
region: eu-west-1
rootGB: 400

8
.github/CODEOWNERS vendored
View File

@@ -11,10 +11,8 @@
/.github/workflows/codeql.yml @openclaw/openclaw-secops
/.github/workflows/codeql-android-critical-security.yml @openclaw/openclaw-secops
/.github/workflows/codeql-critical-quality.yml @openclaw/openclaw-secops
/.github/workflows/dependency-guard.yml @openclaw/openclaw-secops
/test/scripts/dependency-guard-workflow.test.ts @openclaw/openclaw-secops
/test/scripts/dependency-guard-script.test.ts @openclaw/openclaw-secops
/scripts/github/dependency-guard.mjs @openclaw/openclaw-secops
/.github/workflows/dependency-change-awareness.yml @openclaw/openclaw-secops
/test/scripts/dependency-change-awareness-workflow.test.ts @openclaw/openclaw-secops
/package-lock.json @openclaw/openclaw-secops
/npm-shrinkwrap.json @openclaw/openclaw-secops
/extensions/*/package-lock.json @openclaw/openclaw-secops
@@ -31,7 +29,7 @@
/src/gateway/**/*secret*.ts @openclaw/openclaw-secops
/src/gateway/security-path*.ts @openclaw/openclaw-secops
/src/gateway/resolve-configured-secret-input-string*.ts @openclaw/openclaw-secops
/packages/gateway-protocol/src/**/*secret*.ts @openclaw/openclaw-secops
/src/gateway/protocol/**/*secret*.ts @openclaw/openclaw-secops
/src/gateway/server-methods/secrets*.ts @openclaw/openclaw-secops
/src/agents/*auth*.ts @openclaw/openclaw-secops
/src/agents/**/*auth*.ts @openclaw/openclaw-secops

View File

@@ -14,10 +14,6 @@ self-hosted-runner:
- blacksmith-16vcpu-ubuntu-2404-arm
- blacksmith-6vcpu-macos-latest
- blacksmith-12vcpu-macos-latest
- blacksmith-6vcpu-macos-15
- blacksmith-12vcpu-macos-15
- blacksmith-6vcpu-macos-26
- blacksmith-12vcpu-macos-26
# Ignore patterns for known issues
paths:

View File

@@ -35,29 +35,17 @@ runs:
exit 0
fi
docs_changed=false
non_docs=false
while IFS= read -r changed_path; do
case "$changed_path" in
test/fixtures/*)
non_docs=true
;;
docs/* | *.md | *.mdx)
docs_changed=true
;;
*)
non_docs=true
;;
esac
done <<< "$CHANGED"
if [ "$docs_changed" = "true" ]; then
# Check if any changed file is a doc
DOCS=$(echo "$CHANGED" | grep -E '^docs/|\.md$|\.mdx$' || true)
if [ -n "$DOCS" ]; then
echo "docs_changed=true" >> "$GITHUB_OUTPUT"
else
echo "docs_changed=false" >> "$GITHUB_OUTPUT"
fi
if [ "$non_docs" = "false" ]; then
# Check if all changed files are docs or markdown
NON_DOCS=$(echo "$CHANGED" | grep -vE '^docs/|\.md$|\.mdx$' || true)
if [ -z "$NON_DOCS" ]; then
echo "docs_only=true" >> "$GITHUB_OUTPUT"
echo "Docs-only change detected — skipping heavy jobs"
else

View File

@@ -20,13 +20,9 @@ inputs:
required: false
default: "true"
use-actions-cache:
description: Whether to restore the pnpm store with actions/cache.
description: Whether to restore and save the pnpm store with actions/cache.
required: false
default: "true"
save-actions-cache:
description: Whether to save the pnpm store with actions/cache after install when no exact cache restored.
required: false
default: "false"
runs:
using: composite
steps:
@@ -49,7 +45,6 @@ runs:
openclaw_ensure_node "$REQUESTED_NODE_VERSION"
- name: Setup pnpm
id: setup-pnpm
uses: ./.github/actions/setup-pnpm-store-cache
with:
node-version: ${{ inputs.node-version }}
@@ -135,10 +130,3 @@ runs:
ln -sfn "$PNPM_CONFIG_MODULES_DIR" node_modules
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
fi
- name: Save pnpm store cache
if: ${{ inputs.install-deps == 'true' && inputs.use-actions-cache == 'true' && inputs.save-actions-cache == 'true' && runner.os != 'Windows' && steps.setup-pnpm.outputs.store-cache-hit != 'true' }}
uses: actions/cache/save@v5
with:
path: ${{ steps.setup-pnpm.outputs.store-path }}
key: ${{ steps.setup-pnpm.outputs.store-cache-primary-key }}

View File

@@ -14,7 +14,7 @@ inputs:
required: false
default: ""
use-actions-cache:
description: Whether actions/cache should restore the pnpm store.
description: Whether actions/cache should cache the pnpm store.
required: false
default: "true"
outputs:
@@ -24,15 +24,6 @@ outputs:
project-dir:
description: Directory containing the packageManager file used for pnpm resolution.
value: ${{ steps.setup-pnpm.outputs.project-dir }}
store-cache-hit:
description: Whether the pnpm store cache restored an exact key.
value: ${{ steps.pnpm-store-cache.outputs.cache-hit }}
store-cache-primary-key:
description: Exact pnpm store cache key used for restore/save.
value: ${{ steps.pnpm-store-cache.outputs.cache-primary-key }}
store-path:
description: Resolved pnpm store path.
value: ${{ steps.pnpm-store.outputs.path }}
runs:
using: composite
steps:
@@ -90,15 +81,14 @@ runs:
echo "path=$store_path" >> "$GITHUB_OUTPUT"
- name: Restore pnpm store cache
id: pnpm-store-cache
if: ${{ inputs.use-actions-cache == 'true' && runner.os != 'Windows' }}
uses: actions/cache/restore@v5
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-store-${{ runner.os }}-${{ runner.arch }}-${{ inputs.node-version }}-${{ hashFiles(inputs.package-manager-file) }}-${{ hashFiles(inputs.lockfile-path) }}
key: pnpm-store-${{ runner.os }}-${{ inputs.node-version }}-${{ hashFiles(inputs.lockfile-path) }}
restore-keys: |
pnpm-store-${{ runner.os }}-${{ runner.arch }}-${{ inputs.node-version }}-${{ hashFiles(inputs.package-manager-file) }}-
pnpm-store-${{ runner.os }}-${{ runner.arch }}-${{ inputs.node-version }}-
pnpm-store-${{ runner.os }}-${{ inputs.node-version }}-
pnpm-store-${{ runner.os }}-
- name: Record pnpm version
id: pnpm-version

View File

@@ -95,7 +95,7 @@ openclaw_find_toolcache_node() {
done
local node_root candidate candidate_version
for node_root in ${roots[@]+"${roots[@]}"}; do
for node_root in "${roots[@]}"; do
while IFS= read -r candidate; do
candidate_version="$("$candidate" -p 'process.versions.node' 2>/dev/null || true)"
if openclaw_node_version_matches "$candidate_version" "$requested_node"; then

View File

@@ -19,7 +19,7 @@ paths:
- src/config/types.channel*.ts
- src/gateway/server-channel*.ts
- src/gateway/server-methods/channels.ts
- packages/gateway-protocol/src/schema/channels.ts
- src/gateway/protocol/schema/channels.ts
- src/infra/channel-*.ts
- src/infra/exec-approval-channel-runtime.ts
- src/infra/outbound/channel-*.ts

View File

@@ -30,7 +30,7 @@ paths:
- src/gateway/**/*auth*.ts
- src/gateway/*secret*.ts
- src/gateway/**/*secret*.ts
- packages/gateway-protocol/src/**/*secret*.ts
- src/gateway/protocol/**/*secret*.ts
- src/gateway/resolve-configured-secret-input-string*.ts
- src/gateway/security-path*.ts
- src/gateway/server-methods/secrets*.ts

View File

@@ -30,7 +30,7 @@ paths:
- src/gateway/**/*auth*.ts
- src/gateway/*secret*.ts
- src/gateway/**/*secret*.ts
- packages/gateway-protocol/src/**/*secret*.ts
- src/gateway/protocol/**/*secret*.ts
- src/gateway/resolve-configured-secret-input-string*.ts
- src/gateway/security-path*.ts
- src/gateway/server-methods/secrets*.ts

View File

@@ -15,7 +15,7 @@ query-filters:
paths:
- src/gateway/method-scopes.ts
- packages/gateway-protocol/src
- src/gateway/protocol
- src/gateway/server-methods
- src/gateway/server-methods.ts
- src/gateway/server-methods-list.ts

View File

@@ -76,8 +76,6 @@ predicate allowedRawSocketClientCall(Expr call) {
or
allowedOwnerScope(call, "src/proxy-capture/proxy-server.ts", "startDebugProxyServer")
or
allowedOwnerScope(call, "extensions/codex-supervisor/src/json-rpc-client.ts", "connectCodexSupervisorUnixSocket")
or
allowedOwnerScope(call, "extensions/irc/src/client.ts", "connectIrcClient")
or
allowedOwnerScope(call, "extensions/qa-lab/src/lab-server-capture.ts", "probeTcpReachability")

14
.github/labeler.yml vendored
View File

@@ -188,7 +188,7 @@
- "ui/**"
- "src/gateway/control-ui.ts"
- "src/gateway/control-ui-shared.ts"
- "packages/gateway-protocol/src/**"
- "src/gateway/protocol/**"
- "src/gateway/server-methods/chat.ts"
- "src/infra/control-ui-assets.ts"
@@ -196,7 +196,6 @@
- changed-files:
- any-glob-to-any-file:
- "src/gateway/**"
- "packages/gateway-protocol/src/**"
- "src/daemon/**"
- "docs/gateway/**"
@@ -399,17 +398,6 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/codex/**"
"extensions: codex-supervisor":
- changed-files:
- any-glob-to-any-file:
- "extensions/codex-supervisor/**"
- "docs/plugins/reference/codex-supervisor.md"
- "docs/specs/claw-supervisor.md"
"extensions: copilot":
- changed-files:
- any-glob-to-any-file:
- "extensions/copilot/**"
- "docs/plugins/copilot.md"
"extensions: kimi-coding":
- changed-files:
- any-glob-to-any-file:

View File

@@ -414,73 +414,13 @@ jobs:
- name: Audit production dependencies
run: node scripts/pre-commit/pnpm-audit-prod.mjs --audit-level=high
# Warm the lockfile- and pnpm-pinned store once before Linux Node shards fan out.
# On a cold key this job owns the save, so later shards restore the exact key.
pnpm-store-warmup:
permissions:
contents: read
needs: [preflight]
if: needs.preflight.outputs.run_node == 'true' || needs.preflight.outputs.run_check_docs == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
steps:
- name: Checkout
shell: bash
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
run: |
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
}
checkout_attempt() {
local attempt="$1"
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
echo "checkout attempt ${attempt}/5 succeeded"
}
for attempt in 1 2 3 4 5; do
if checkout_attempt "$attempt"; then
exit 0
fi
echo "checkout attempt ${attempt}/5 failed"
sleep $((attempt * 5))
done
echo "checkout failed after 5 attempts" >&2
exit 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
save-actions-cache: "true"
# Build dist once for Node-relevant changes and share it with downstream jobs.
# Keep this overlapping with the fast correctness lanes so green PRs get heavy
# test/build feedback sooner instead of waiting behind a full `check` pass.
build-artifacts:
permissions:
contents: read
needs: [preflight, pnpm-store-warmup]
needs: [preflight]
if: needs.preflight.outputs.run_build_artifacts == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
@@ -712,7 +652,7 @@ jobs:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight, pnpm-store-warmup]
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast_core == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
@@ -801,7 +741,7 @@ jobs:
permissions:
contents: read
name: ${{ matrix.checkName }}
needs: [preflight, pnpm-store-warmup]
needs: [preflight]
if: needs.preflight.outputs.run_plugin_contracts_shards == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
@@ -881,7 +821,7 @@ jobs:
permissions:
contents: read
name: ${{ matrix.checkName }}
needs: [preflight, pnpm-store-warmup]
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
@@ -1033,9 +973,9 @@ jobs:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight, pnpm-store-warmup]
needs: [preflight]
if: needs.preflight.outputs.run_checks_node_core_nondist == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (matrix.runner || 'blacksmith-8vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (matrix.runner || 'ubuntu-24.04') || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1139,9 +1079,9 @@ jobs:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight, pnpm-store-warmup]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' && needs.pnpm-store-warmup.result == 'success' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && matrix.runner || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -1232,7 +1172,7 @@ jobs:
pnpm lint:auth:pairing-account-scope
pnpm check:import-cycles
# build-artifacts already runs the tsdown/runtime build for the same Node-relevant changes.
NODE_OPTIONS=--max-old-space-size=8192 pnpm build:plugin-sdk:strict-smoke
pnpm build:plugin-sdk:strict-smoke
;;
prod-types)
pnpm tsgo:prod
@@ -1270,8 +1210,8 @@ jobs:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight, pnpm-store-warmup]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' && needs.pnpm-store-warmup.result == 'success' }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
@@ -1437,7 +1377,7 @@ jobs:
check-docs:
permissions:
contents: read
needs: [preflight, pnpm-store-warmup]
needs: [preflight]
if: needs.preflight.outputs.run_check_docs == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
@@ -1494,44 +1434,11 @@ jobs:
- name: Checkout ClawHub docs source
run: |
set -euo pipefail
workdir="$GITHUB_WORKSPACE/clawhub-source"
started_at="$(date +%s)"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
}
checkout_attempt() {
local attempt="$1"
reset_checkout_dir
git init "$workdir" >/dev/null
git -C "$workdir" config gc.auto 0
git -C "$workdir" remote add origin "https://github.com/openclaw/clawhub.git"
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+refs/heads/main:refs/remotes/origin/checkout" || return 1
git -C "$workdir" checkout --force --detach refs/remotes/origin/checkout || return 1
echo "ClawHub checkout attempt ${attempt}/5 succeeded"
}
for attempt in 1 2 3 4 5; do
if checkout_attempt "$attempt"; then
elapsed="$(( $(date +%s) - started_at ))"
echo "ClawHub checkout completed in ${elapsed}s"
exit 0
fi
echo "ClawHub checkout attempt ${attempt}/5 failed"
sleep $((attempt * 5))
done
echo "ClawHub checkout failed after 5 attempts" >&2
exit 1
git init clawhub-source
git -C clawhub-source config gc.auto 0
git -C clawhub-source remote add origin "https://github.com/openclaw/clawhub.git"
git -C clawhub-source fetch --no-tags --depth=1 origin "+HEAD:refs/remotes/origin/checkout"
git -C clawhub-source checkout --detach refs/remotes/origin/checkout
- name: Check docs
env:
@@ -1688,7 +1595,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_macos_node == 'true' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'macos-15' || (github.repository == 'openclaw/openclaw' && 'blacksmith-6vcpu-macos-15' || 'macos-15') }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'macos-latest' || (github.repository == 'openclaw/openclaw' && 'blacksmith-6vcpu-macos-latest' || 'macos-latest') }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -1737,7 +1644,7 @@ jobs:
name: "macos-swift"
needs: [preflight]
if: needs.preflight.outputs.run_macos_swift == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'macos-26' || (github.repository == 'openclaw/openclaw' && 'blacksmith-12vcpu-macos-26' || 'macos-26') }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'macos-26' || (github.repository == 'openclaw/openclaw' && 'blacksmith-12vcpu-macos-latest' || 'macos-26') }}
timeout-minutes: 20
steps:
- name: Checkout
@@ -1960,53 +1867,3 @@ jobs:
exit 1
;;
esac
ci-timings-summary:
permissions:
actions: read
contents: read
name: ci-timings-summary
needs:
- preflight
- security-fast
- pnpm-store-warmup
- build-artifacts
- checks-fast-core
- checks-fast-plugin-contracts-shard
- checks-fast-channel-contracts-shard
- checks-node-compat
- checks-node-core-test-nondist-shard
- check-shard
- check-additional-shard
- check-docs
- skills-python
- checks-windows
- macos-node
- macos-swift
- android
if: ${{ !cancelled() && always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Checkout timing summary helper
uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || needs.preflight.outputs.checkout_revision || github.sha }}
fetch-depth: 1
fetch-tags: false
persist-credentials: false
submodules: false
- name: Write CI timing summary
env:
GH_TOKEN: ${{ github.token }}
run: |
node scripts/ci-run-timings.mjs "$GITHUB_RUN_ID" --limit 25 > ci-timings-summary.txt
cat ci-timings-summary.txt >> "$GITHUB_STEP_SUMMARY"
- name: Upload CI timing summary
uses: actions/upload-artifact@v7
with:
name: ci-timings-summary
path: ci-timings-summary.txt
retention-days: 14

View File

@@ -106,13 +106,13 @@ on:
- "src/gateway/**/*auth*.ts"
- "src/gateway/*secret*.ts"
- "src/gateway/**/*secret*.ts"
- "packages/gateway-protocol/src/**/*secret*.ts"
- "src/gateway/protocol/**/*secret*.ts"
- "src/gateway/resolve-configured-secret-input-string*.ts"
- "src/gateway/security-path*.ts"
- "src/gateway/server-methods/secrets*.ts"
- "src/gateway/server-startup-memory.ts"
- "src/gateway/method-scopes.ts"
- "packages/gateway-protocol/src/**"
- "src/gateway/protocol/**"
- "src/gateway/server-methods/**"
- "src/gateway/server-methods.ts"
- "src/gateway/server-methods-list.ts"
@@ -244,14 +244,14 @@ jobs:
src/config/*)
config=true
;;
packages/gateway-protocol/src/*secret*.ts|packages/gateway-protocol/src/**/*secret*.ts|src/gateway/server-methods/secrets*.ts)
src/gateway/protocol/*secret*.ts|src/gateway/server-methods/secrets*.ts)
core_auth_secrets=true
gateway=true
;;
src/agents/*auth*.ts|src/agents/auth-health*.ts|src/agents/auth-profiles|src/agents/auth-profiles/*|src/agents/bash-tools.exec-host-shared.ts|src/agents/sandbox|src/agents/sandbox.ts|src/agents/sandbox-*.ts|src/agents/sandbox/*|src/cron/service/jobs.ts|src/cron/stagger.ts|src/gateway/*auth*.ts|src/gateway/*secret*.ts|src/gateway/resolve-configured-secret-input-string*.ts|src/gateway/security-path*.ts|src/infra/secret-file*.ts|src/secrets/*|src/security/*)
core_auth_secrets=true
;;
packages/gateway-protocol/src/*|packages/gateway-protocol/src/**/*|src/gateway/method-scopes.ts|src/gateway/server-methods/*|src/gateway/server-methods.ts|src/gateway/server-methods-list.ts)
src/gateway/method-scopes.ts|src/gateway/protocol/*|src/gateway/server-methods/*|src/gateway/server-methods.ts|src/gateway/server-methods-list.ts)
gateway=true
;;
packages/memory-host-sdk/*|src/commands/doctor-cron-dreaming-payload-migration.ts|src/commands/doctor-memory-search.ts|src/gateway/server-startup-memory.ts|src/memory/*|src/memory-host-sdk/*)

View File

@@ -20,7 +20,7 @@ permissions:
jobs:
macos:
name: Critical Security (macOS)
runs-on: blacksmith-6vcpu-macos-15
runs-on: blacksmith-6vcpu-macos-latest
timeout-minutes: 45
steps:
- name: Checkout

View File

@@ -85,21 +85,10 @@ jobs:
config_file: ./.github/codeql/codeql-actions-critical-security.yml
steps:
- name: Checkout
if: ${{ matrix.category != 'actions' }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
submodules: false
- name: Checkout Actions security sources
if: ${{ matrix.category == 'actions' }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
submodules: false
sparse-checkout: |
.github/actions
.github/workflows
.github/codeql
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
with:

View File

@@ -0,0 +1,176 @@
name: Dependency Change Awareness
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] metadata-only workflow; no checkout or untrusted code execution
types: [opened, reopened, synchronize, ready_for_review]
permissions:
pull-requests: write
issues: write
concurrency:
group: dependency-change-awareness-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
dependency-change-awareness:
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Label and comment on dependency changes
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const marker = "<!-- openclaw:dependency-change-awareness -->";
const labelName = "dependencies-changed";
const maxListedFiles = 25;
const pullRequest = context.payload.pull_request;
if (!pullRequest) {
core.info("No pull_request payload found; skipping.");
return;
}
const isDependencyFile = (filename) =>
filename === "package.json" ||
filename === "package-lock.json" ||
filename === "npm-shrinkwrap.json" ||
filename === "pnpm-lock.yaml" ||
filename === "pnpm-workspace.yaml" ||
filename === "ui/package.json" ||
filename.startsWith("patches/") ||
/^packages\/[^/]+\/package\.json$/u.test(filename) ||
/^extensions\/[^/]+\/package-lock\.json$/u.test(filename) ||
/^extensions\/[^/]+\/npm-shrinkwrap\.json$/u.test(filename) ||
/^extensions\/[^/]+\/package\.json$/u.test(filename);
const sanitizeDisplayValue = (value) =>
String(value)
.replace(/[\u0000-\u001f\u007f]/gu, "?")
.slice(0, 240);
const markdownCode = (value) =>
`\`${sanitizeDisplayValue(value).replaceAll("`", "\\`")}\``;
const ignoreUnavailableWritePermission = (action) => (error) => {
if (error?.status === 403) {
core.warning(
`Skipping dependency change ${action}; token does not have issue write permission.`,
);
return;
}
if (error?.status === 404 || error?.status === 422) {
core.warning(`Dependency change ${action} is unavailable.`);
return;
}
throw error;
};
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pullRequest.number,
per_page: 100,
});
const dependencyFiles = files
.map((file) => file.filename)
.filter((filename) => typeof filename === "string" && isDependencyFile(filename))
.sort((left, right) => left.localeCompare(right));
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
per_page: 100,
});
const existingComment = comments.find(
(comment) =>
comment.user?.login === "github-actions[bot]" && comment.body?.includes(marker),
);
const labels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
per_page: 100,
});
const hasLabel = labels.some((label) => label.name === labelName);
if (dependencyFiles.length === 0) {
if (hasLabel) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
name: labelName,
}).catch(ignoreUnavailableWritePermission("label removal"));
}
if (existingComment) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
}).catch(ignoreUnavailableWritePermission("comment deletion"));
}
await core.summary
.addHeading("Dependency Change Awareness")
.addRaw("No dependency-related file changes detected.")
.write();
core.info("No dependency-related file changes detected.");
return;
}
if (!hasLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
labels: [labelName],
}).catch(ignoreUnavailableWritePermission(`label "${labelName}" update`));
}
const listedFiles = dependencyFiles.slice(0, maxListedFiles);
const omittedCount = dependencyFiles.length - listedFiles.length;
const fileLines = listedFiles.map((filename) => `- ${markdownCode(filename)}`);
if (omittedCount > 0) {
fileLines.push(`- ${omittedCount} additional dependency-related files not shown`);
}
const body = [
marker,
"",
"### Dependency Changes Detected",
"",
"This PR changes dependency-related files. Maintainers should confirm these changes are intentional.",
"",
"Changed files:",
...fileLines,
"",
"Maintainer follow-up:",
"- Review whether the dependency changes are intentional.",
"- Inspect resolved package deltas when lockfile, shrinkwrap, or workspace dependency policy changes are present.",
"- Treat `package-lock.json` and `npm-shrinkwrap.json` diffs as security-review surfaces.",
"- Run `pnpm deps:changes:report -- --base-ref origin/main --markdown /tmp/dependency-changes.md --json /tmp/dependency-changes.json` locally for detailed release-style evidence.",
].join("\n");
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body,
}).catch(ignoreUnavailableWritePermission("comment update"));
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
body,
}).catch(ignoreUnavailableWritePermission("comment creation"));
}
await core.summary
.addHeading("Dependency Change Awareness")
.addRaw(`Detected ${dependencyFiles.length} dependency-related file change(s).`)
.addList(dependencyFiles.map((filename) => markdownCode(filename)))
.write();
core.notice(`Detected ${dependencyFiles.length} dependency-related file change(s).`);

View File

@@ -1,33 +0,0 @@
name: Dependency Guard
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] checks trusted base script only; never checks out PR head
types: [opened, reopened, synchronize, ready_for_review, labeled]
permissions:
contents: read
pull-requests: write
issues: write
concurrency:
group: dependency-guard-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
dependency-guard:
if: ${{ !github.event.pull_request.draft && (github.event.action != 'labeled' || github.event.label.name == 'dependency-guard-backfill') }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Check out trusted base workflow scripts
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.base.sha }}
persist-credentials: false
- name: Label, comment, and guard dependency changes
env:
GITHUB_TOKEN: ${{ github.token }}
OPENCLAW_SECURITY_APPROVERS: vincentkoc,steipete,joshavant
OPENCLAW_SECURITY_TEAM_SLUG: openclaw-secops
run: node scripts/github/dependency-guard.mjs

View File

@@ -143,7 +143,7 @@ jobs:
for (const [dep, rel] of Object.entries(workspace.patchedDependencies ?? {})) {
const absolute = path.join(\"/app\", rel);
if (!fs.existsSync(absolute)) {
throw new Error(\"missing patch for \" + dep + \": \" + rel);
throw new Error(`missing patch for ${dep}: ${rel}`);
}
}
"
@@ -337,7 +337,7 @@ jobs:
for (const [dep, rel] of Object.entries(workspace.patchedDependencies ?? {})) {
const absolute = path.join(\"/app\", rel);
if (!fs.existsSync(absolute)) {
throw new Error(\"missing patch for \" + dep + \": \" + rel);
throw new Error(`missing patch for ${dep}: ${rel}`);
}
}
"

View File

@@ -377,7 +377,6 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT: "1"
CRABBOX_COORDINATOR: ${{ secrets.CRABBOX_COORDINATOR }}

View File

@@ -218,7 +218,6 @@ jobs:
OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE: ci
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT: "1"
INPUT_SCENARIO: ${{ inputs.scenario }}

View File

@@ -451,7 +451,7 @@ jobs:
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline
run: |
mkdir -p "${OUTPUT_DIR}"
timeout --preserve-status 300s npm pack --ignore-scripts --json "${BASELINE_SPEC}" --pack-destination "${OUTPUT_DIR}" > "${OUTPUT_DIR}/pack.json"
npm pack --ignore-scripts --json "${BASELINE_SPEC}" --pack-destination "${OUTPUT_DIR}" > "${OUTPUT_DIR}/pack.json"
- name: Capture candidate metadata
id: candidate_metadata

View File

@@ -480,35 +480,6 @@ jobs:
fi
exit 1
plan_release_workflow_matrices:
needs: validate_selected_ref
runs-on: ubuntu-24.04
outputs:
docker_e2e_count: ${{ steps.plan.outputs.docker_e2e_count }}
docker_e2e_matrix: ${{ steps.plan.outputs.docker_e2e_matrix }}
docker_e2e_omitted_json: ${{ steps.plan.outputs.docker_e2e_omitted_json }}
live_models_count: ${{ steps.plan.outputs.live_models_count }}
live_models_matrix: ${{ steps.plan.outputs.live_models_matrix }}
live_models_omitted_json: ${{ steps.plan.outputs.live_models_omitted_json }}
steps:
- name: Checkout trusted release harness
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ github.sha }}
fetch-depth: 1
- name: Plan release workflow matrices
id: plan
env:
DOCKER_LANES: ${{ inputs.docker_lanes }}
INCLUDE_LIVE_SUITES: ${{ inputs.include_live_suites }}
INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include_release_path_suites }}
LIVE_MODEL_PROVIDERS: ${{ inputs.live_model_providers }}
LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
RELEASE_TEST_PROFILE: ${{ inputs.release_test_profile }}
run: node scripts/plan-release-workflow-matrix.mjs >> "$GITHUB_OUTPUT"
validate_release_live_cache:
needs: validate_selected_ref
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'live-cache')
@@ -665,15 +636,72 @@ jobs:
run: ${{ matrix.command }}
validate_docker_e2e:
needs: [validate_selected_ref, prepare_docker_e2e_image, plan_release_workflow_matrices]
if: inputs.include_release_path_suites && inputs.docker_lanes == '' && needs.plan_release_workflow_matrices.outputs.docker_e2e_count != '0'
needs: [validate_selected_ref, prepare_docker_e2e_image]
if: inputs.include_release_path_suites && inputs.docker_lanes == ''
name: Docker E2E (${{ matrix.label }})
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.plan_release_workflow_matrices.outputs.docker_e2e_matrix) }}
matrix:
include:
- chunk_id: core
label: core
timeout_minutes: 60
profiles: stable full
- chunk_id: package-update-openai
label: package/update OpenAI install
timeout_minutes: 45
profiles: beta minimum stable full
- chunk_id: package-update-anthropic
label: package/update Anthropic install
timeout_minutes: 60
profiles: beta minimum stable full
- chunk_id: package-update-core
label: package/update core
timeout_minutes: 60
profiles: beta minimum stable full
- chunk_id: plugins-runtime-plugins
label: plugins/runtime plugins
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-services
label: plugins/runtime services
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-a
label: plugins/runtime install A
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-b
label: plugins/runtime install B
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-c
label: plugins/runtime install C
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-d
label: plugins/runtime install D
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-e
label: plugins/runtime install E
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-f
label: plugins/runtime install F
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-g
label: plugins/runtime install G
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-h
label: plugins/runtime install H
timeout_minutes: 60
profiles: stable full
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
@@ -1603,14 +1631,42 @@ jobs:
validate_live_models_docker:
name: Docker live models (${{ matrix.provider_label }})
needs: [validate_selected_ref, prepare_live_test_image, plan_release_workflow_matrices]
if: inputs.include_live_suites && inputs.live_model_providers == '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models') && needs.plan_release_workflow_matrices.outputs.live_models_count != '0'
needs: [validate_selected_ref, prepare_live_test_image]
if: inputs.include_live_suites && inputs.live_model_providers == '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models')
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.plan_release_workflow_matrices.outputs.live_models_matrix) }}
matrix:
include:
- provider_label: Anthropic
providers: anthropic
profiles: stable full
- provider_label: Google
providers: google
profiles: stable full
- provider_label: MiniMax
providers: minimax
profiles: stable full
- provider_label: OpenAI
providers: openai
profiles: beta minimum stable full
- provider_label: OpenCode
providers: opencode-go
profiles: full
- provider_label: OpenRouter
providers: openrouter
profiles: full
- provider_label: xAI
providers: xai
profiles: full
- provider_label: Z.ai
providers: zai
profiles: full
- provider_label: Fireworks
providers: fireworks
profiles: full
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
@@ -1688,8 +1744,6 @@ jobs:
- name: Validate provider credential
if: contains(matrix.profiles, inputs.release_test_profile)
shell: bash
env:
LIVE_MODEL_PROVIDERS: ${{ matrix.providers }}
run: |
set -euo pipefail
@@ -1706,7 +1760,7 @@ jobs:
exit 1
}
case "${LIVE_MODEL_PROVIDERS}" in
case "${{ matrix.providers }}" in
anthropic) require_any Anthropic ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;;
google) require_any Google GEMINI_API_KEY GOOGLE_API_KEY ;;
minimax) require_any MiniMax MINIMAX_API_KEY ;;
@@ -1717,7 +1771,7 @@ jobs:
zai) require_any Z.ai ZAI_API_KEY Z_AI_API_KEY ;;
fireworks) require_any Fireworks FIREWORKS_API_KEY ;;
*)
echo "Unhandled live model provider shard: ${LIVE_MODEL_PROVIDERS}" >&2
echo "Unhandled live model provider shard: ${{ matrix.providers }}" >&2
exit 1
;;
esac
@@ -1959,7 +2013,7 @@ jobs:
profiles: stable full
- suite_id: native-live-src-gateway-profiles-openai
label: Native live gateway profiles OpenAI
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.5 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=180000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=600000 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.5 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 60
profile_env_only: false
profiles: beta minimum stable full

View File

@@ -1207,7 +1207,6 @@ jobs:
env:
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT: "1"
run: |

View File

@@ -32,11 +32,11 @@ jobs:
- name: Install opengrep
env:
# Pin both the install script (by commit SHA) and the binary version.
# The script SHA must match the v1.22.0 release tag in opengrep/opengrep
# The script SHA must match the v1.19.0 release tag in opengrep/opengrep
# so a compromised or force-pushed `main` cannot RCE in our CI runner.
# Bump both together when upgrading.
OPENGREP_VERSION: v1.22.0
OPENGREP_INSTALL_SHA: f458d7f0d52cc58eae1ca3cf3d5caf101e637519
OPENGREP_VERSION: v1.19.0
OPENGREP_INSTALL_SHA: 9a4c0a68220618441608cd2bad4ff2eddccf8113
run: |
curl -fsSL "https://raw.githubusercontent.com/opengrep/opengrep/${OPENGREP_INSTALL_SHA}/install.sh" \
| bash -s -- -v "$OPENGREP_VERSION"

View File

@@ -44,7 +44,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
fetch-depth: 1
fetch-depth: 0
fetch-tags: false
persist-credentials: false
submodules: false
@@ -58,11 +58,11 @@ jobs:
- name: Install opengrep
env:
# Pin both the install script (by commit SHA) and the binary version.
# The script SHA must match the v1.22.0 release tag in opengrep/opengrep
# The script SHA must match the v1.19.0 release tag in opengrep/opengrep
# so a compromised or force-pushed `main` cannot RCE in our CI runner.
# Bump both together when upgrading.
OPENGREP_VERSION: v1.22.0
OPENGREP_INSTALL_SHA: f458d7f0d52cc58eae1ca3cf3d5caf101e637519
OPENGREP_VERSION: v1.19.0
OPENGREP_INSTALL_SHA: 9a4c0a68220618441608cd2bad4ff2eddccf8113
run: |
curl -fsSL "https://raw.githubusercontent.com/opengrep/opengrep/${OPENGREP_INSTALL_SHA}/install.sh" \
| bash -s -- -v "$OPENGREP_VERSION"

View File

@@ -530,7 +530,6 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT: "1"
INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.scenario || '' }}

View File

@@ -90,7 +90,7 @@ jobs:
bash -lc 'apt-get update -y && apt-get install -y curl && bash /tmp/install-cli.sh --prefix /tmp/openclaw --no-onboard --version latest && /tmp/openclaw/bin/openclaw --version'
macos-installer:
runs-on: macos-15
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v6

1
.gitignore vendored
View File

@@ -178,7 +178,6 @@ mantis/
/local/
/client_secret_*.json
package-lock.json
!src/commands/copilot-sdk-install-manifest/package-lock.json
.claude/
.agent/
skills-lock.json

View File

@@ -35,9 +35,9 @@ Skills own workflows; root owns hard policy and routing.
## Map
- Core TS: `src/`, `ui/`, `packages/`; plugins: `extensions/`; SDK: `src/plugin-sdk/*`; channels: `src/channels/*`; loader: `src/plugins/*`; protocol: `packages/gateway-protocol/*`; docs/apps: `docs/`, `apps/`.
- Core TS: `src/`, `ui/`, `packages/`; plugins: `extensions/`; SDK: `src/plugin-sdk/*`; channels: `src/channels/*`; loader: `src/plugins/*`; protocol: `src/gateway/protocol/*`; docs/apps: `docs/`, `apps/`.
- Installers: sibling `../openclaw.ai`.
- Scoped guides: `extensions/`, `src/{plugin-sdk,channels,plugins,gateway,agents}/`, `packages/`, `test/helpers*/`, `docs/`, `ui/`, `scripts/`.
- Scoped guides: `extensions/`, `src/{plugin-sdk,channels,plugins,gateway,gateway/protocol,agents}/`, `test/helpers*/`, `docs/`, `ui/`, `scripts/`.
## Docs

View File

@@ -2,19 +2,12 @@
Docs: https://docs.openclaw.ai
## Unreleased
### Fixes
- Cron: retry recurring jobs after transient model rate limits before waiting for the next scheduled slot.
## 2026.5.28
### Highlights
- Agent and Codex runtime recovery is steadier: subagents keep cwd/workspace separation, hook context stays prompt-local, session locks release on timeout abort, stale restart continuations are avoided, and Codex app-server/helper failures no longer tear down shared runtime state. (#87218, #86875, #87409, #87399, #87375)
- Channel delivery and session identity got safer across outbound plugin hooks, Matrix room ids, iMessage reactions/approvals, Slack final replies, Discord recovered tool warnings, and Microsoft Teams service URL trust checks. (#73706, #75670, #87366, #87451, #87334)
- Mobile and chat surfaces got a broader refresh: the iOS Pro UI, Gateway chat transport, onboarding, Talk permissions, WebChat reconnect delivery, and session picker behavior now preserve more state across reconnects and empty searches. (#87367, #87531, #87682)
- CLI, auth, doctor, and provider paths fail faster and recover more clearly: malformed numeric/version options are rejected, OAuth and local service startup requests are bounded, legacy `api_key` auth profiles migrate to canonical form, and restart guidance is actionable. (#87398, #86281, #87361)
- Plugin and Gateway hot paths do less repeated work while preserving cache correctness for install records, config JSON parsing, tool search catalogs, session stores, manifest model rows, auto-enabled plugin config, browser tokens, and viewer assets. (#86699)
- Release, QA, and E2E validation now bound more log, artifact, harness, and cross-OS waits so failing lanes produce proof instead of hanging or false-greening.
@@ -26,19 +19,15 @@ Docs: https://docs.openclaw.ai
- ClawHub: add plugin display names plus skill verification and trust surfaces. (#87354, #86699) Thanks @thewilloftheshadow and @Patrick-Erichsen.
- iOS: refresh the dev app with Pro Command, Chat, Agents, and Settings tabs wired to gateway sessions, diagnostics, chat, and realtime Talk. (#87367) Thanks @Solvely-Colin.
- Docs: clarify Codex computer-use setup, paste-token stdin auth setup, macOS gateway sleep troubleshooting, native Codex hook relay recovery, container model auth, install deployment cards, device-token admin gating, and backport targets. (#87313, #63050) Thanks @bdjben, @liaoandi, and @thewilloftheshadow.
- PDF/tools: use ClawPDF for PDF extraction and surface MCP structured content in agent tool results. (#87670)
### Fixes
- Agents: fall back to local config pruning when the optional `agents delete` Gateway probe cannot authenticate, so offline installs can still delete agents without removing shared workspaces.
- Tighten phone-control mutation authorization [AI]. (#87150) Thanks @pgondhi987.
- Clarify directive persistence authorization policy [AI]. (#86369) Thanks @pgondhi987.
- Agents/Codex: keep spawned agent cwd/workspace state separated, keep hook context prompt-local, release session locks on timeout abort, avoid session event queue self-wait, preserve shared app-server state across startup or helper failures, keep native hook relay alive across restarts, route workspace memory through tools, resolve Codex runtime models first, report quarantined dynamic tools, format `skills` command output, and bound compaction/steering retries. (#87218, #86875, #86123, #87399, #87375, #87383, #87400) Thanks @mbelinky, @Alix-007, @luoyanglang, @yetval, and @sjf.
- Channels: thread canonical session keys into outbound hooks, preserve Matrix room-id case, keep fallback tool warnings mention-inert, retain delivered Slack final replies during late cleanup, continue iMessage polling after denied reactions, suppress duplicate native exec approvals, preserve Telegram SecretRef prompt config, suppress Discord recovered tool warnings, and block untrusted Teams service URLs. (#73706, #75670, #87366, #87451, #87334) Thanks @zeroaltitude, @lukeboyett, @xiaotian, and @eleqtrizit.
- CLI/auth/doctor/providers: reject malformed numeric/timeout/subcommand-version inputs, wait for respawn child shutdown, bound Codex and GitHub Copilot OAuth/token requests, warm provider auth off the main thread, honor Codex response timeouts, bound local service startup, resolve GPT-5.5 without cached catalog, migrate legacy memory auto-provider config, rewrite non-canonical `api_key` auth profiles, and make doctor restart follow-ups actionable. (#87398, #86281, #87361) Thanks @Patrick-Erichsen, @samzong, @giodl73-repo, and @alkor2000.
- Gateway/security/session state: expire browser tokens after auth rotation, scope assistant idempotency dedupe, drain probe client closes, avoid stale restart continuation reuse, preserve retry-after fallbacks, bound webchat image and artifact transcript scans, include seconds in inbound metadata timestamps, and evict current plugin-state namespaces at row caps.
- Config/parsing/network: reject partial numeric parsing, parse provider/Discord retry headers and dates strictly, honor IPv6 and bare IPv6 `no_proxy` entries, canonicalize secret target array indexes, and reject malformed media content lengths, inspected TCP ports, marketplace content lengths, cron epochs, and sandbox stat fields.
- Providers/agents: preserve seeded Anthropic signatures, concatenate signature-delta chunks, preserve DeepSeek `reasoning_content` replay across tier suffixes, apply OpenRouter strict9 ids to Mistral routes, promote Ollama plain-text tool calls, and recover empty preflight compaction. (#87593)
- File transfer: handle late tar stdin pipe errors after archive validation or unpacking has already settled.
- Performance: trust install-record caches between reloads, prefer native JSON parsing, reuse unchanged tool-search catalogs, skip unchanged store serialization, add precomputed session patch writers, reduce store clone allocations, cache manifest model catalog rows and auto-enabled plugin config, and slim current metadata identity caches.
- Docker/release/QA: package runtime workspace templates, stream cross-OS served artifacts, preserve sparse Crabbox run artifacts, bound OpenClaw instance logs, plugin gauntlet relay logs, MCP channel buffers, kitchen-sink scans, agent-turn assertions, and release scenario logs, and keep release/google live guards current.

View File

@@ -48,7 +48,6 @@ RUN --mount=type=bind,source=packages,target=/tmp/packages,readonly \
FROM ${OPENCLAW_BUN_IMAGE} AS bun-binary
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS build
ARG OPENCLAW_BUNDLED_PLUGIN_DIR
ARG OPENCLAW_EXTENSIONS
# Copy pinned Bun binary from the official image instead of fetching via curl.
COPY --from=bun-binary /usr/local/bin/bun /usr/local/bin/bun
@@ -78,12 +77,7 @@ RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/sto
# pnpm v10+ may append peer-resolution hashes to virtual-store folder names; do not hardcode `.pnpm/...`
# paths. Matrix's native downloader can hit transient release CDN errors while
# still exiting successfully, so retry the package downloader before failing.
# Skip the entire check when matrix is not a bundled extension (e.g. msteams-only builds).
RUN set -eux; \
if ! printf '%s\n' "$OPENCLAW_EXTENSIONS" | tr ',' ' ' | tr ' ' '\n' | grep -qx 'matrix'; then \
echo "==> matrix not bundled, skipping matrix-sdk-crypto check"; \
exit 0; \
fi; \
echo "==> Verifying critical native addons..."; \
for attempt in 1 2 3 4 5; do \
if find /app/node_modules -name "matrix-sdk-crypto*.node" 2>/dev/null | grep -q .; then \

View File

@@ -170,8 +170,6 @@ final class AppState {
}
}
var voiceWakeMeterActive = false
var talkEnabled: Bool {
didSet {
self.ifNotPreview {

View File

@@ -63,14 +63,6 @@ extension CritterStatusLabel {
.frame(width: 6, height: 6)
.padding(1)
}
if self.voiceWakeMeterActive {
Circle()
.fill(.orange)
.frame(width: 5, height: 5)
.padding(2)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
}
}
.frame(width: 18, height: 18)
}
@@ -247,8 +239,7 @@ extension CritterStatusLabel {
sendCelebrationTick: 1,
gatewayStatus: .running(details: nil),
animationsEnabled: true,
iconState: .workingMain(.tool(.bash)),
voiceWakeMeterActive: true)
iconState: .workingMain(.tool(.bash)))
_ = label.body
_ = label.iconImage
@@ -284,8 +275,7 @@ extension CritterStatusLabel {
sendCelebrationTick: 0,
gatewayStatus: .failed("boom"),
animationsEnabled: false,
iconState: .idle,
voiceWakeMeterActive: false)
iconState: .idle)
_ = failed.gatewayNeedsAttention
_ = failed.gatewayBadgeColor
@@ -298,8 +288,7 @@ extension CritterStatusLabel {
sendCelebrationTick: 0,
gatewayStatus: .stopped,
animationsEnabled: false,
iconState: .idle,
voiceWakeMeterActive: false)
iconState: .idle)
_ = stopped.gatewayNeedsAttention
_ = stopped.gatewayBadgeColor

View File

@@ -10,7 +10,6 @@ struct CritterStatusLabel: View {
var gatewayStatus: GatewayProcessManager.Status
var animationsEnabled: Bool
var iconState: IconState
var voiceWakeMeterActive: Bool = false
@State var blinkAmount: CGFloat = 0
@State var nextBlink = Date().addingTimeInterval(Double.random(in: 3.5...8.5))

View File

@@ -50,8 +50,7 @@ struct OpenClawApp: App {
sendCelebrationTick: self.state.sendCelebrationTick,
gatewayStatus: self.gatewayManager.status,
animationsEnabled: self.state.iconAnimationsEnabled && !self.isGatewaySleeping,
iconState: self.effectiveIconState,
voiceWakeMeterActive: self.state.voiceWakeMeterActive)
iconState: self.effectiveIconState)
.background(SettingsWindowOpenRegistrar())
}
.menuBarExtraAccess(isPresented: self.$isMenuPresented) { item in
@@ -76,9 +75,6 @@ struct OpenClawApp: App {
.onChange(of: self.gatewayManager.status) { _, _ in
self.applyStatusItemAppearance(paused: self.state.isPaused, sleeping: self.isGatewaySleeping)
}
.onChange(of: self.state.voiceWakeMeterActive) { _, _ in
self.applyStatusItemAppearance(paused: self.state.isPaused, sleeping: self.isGatewaySleeping)
}
.onChange(of: self.state.connectionMode) { _, mode in
Task { await ConnectionModeCoordinator.shared.apply(mode: mode, paused: self.state.isPaused) }
CLIInstallPrompter.shared.checkAndPromptIfNeeded(reason: "connection-mode")
@@ -111,9 +107,6 @@ struct OpenClawApp: App {
// The SwiftUI label already renders those states; AppKit's disabled appearance can
// leak into menu item validation and grey out app-level commands like Settings.
self.statusItem?.button?.appearsDisabled = false
self.statusItem?.button?.toolTip = self.state.voiceWakeMeterActive
? "OpenClaw - Voice Wake live meter active"
: "OpenClaw"
}
private static func applyAttachOnlyOverrideIfNeeded() {

View File

@@ -8,18 +8,12 @@ actor MicLevelMonitor {
private var update: (@Sendable (Double) -> Void)?
private var running = false
private var smoothedLevel: Double = 0
private var lastUpdate = ContinuousClock.now
private var lastPublishedLevel: Double = 0
private let minimumUpdateInterval: Duration = .milliseconds(125)
private let minimumLevelDelta = 0.02
func start(onLevel: @Sendable @escaping (Double) -> Void) async throws {
self.update = onLevel
if self.running { return }
self.logger.info(
"mic level monitor start (\(AudioInputDeviceObserver.defaultInputDeviceSummary(), privacy: .public))")
self.lastUpdate = .now
self.lastPublishedLevel = self.smoothedLevel
guard AudioInputDeviceObserver.hasUsableDefaultInputDevice() else {
self.engine = nil
throw NSError(
@@ -62,13 +56,7 @@ actor MicLevelMonitor {
private func push(level: Double) {
self.smoothedLevel = (self.smoothedLevel * 0.45) + (level * 0.55)
guard let update else { return }
let now = ContinuousClock.now
guard now - self.lastUpdate >= self.minimumUpdateInterval ||
abs(self.smoothedLevel - self.lastPublishedLevel) >= self.minimumLevelDelta
else { return }
self.lastUpdate = now
let value = self.smoothedLevel
self.lastPublishedLevel = value
Task { @MainActor in update(value) }
}

View File

@@ -3,12 +3,12 @@ import Foundation
enum SoundEffectCatalog {
/// All discoverable system sound names, with "Glass" pinned first.
static let systemOptions: [String] = {
static var systemOptions: [String] {
var names = Set(Self.discoveredSoundMap.keys).union(Self.fallbackNames)
names.remove("Glass")
let sorted = names.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
return ["Glass"] + sorted
}()
}
static func displayName(for raw: String) -> String {
raw

View File

@@ -20,7 +20,6 @@ struct VoiceWakeSettings: View {
private let meter = MicLevelMonitor()
@State private var micObserver = AudioInputDeviceObserver()
@State private var micRefreshTask: Task<Void, Never>?
@State private var meterStartupTask: Task<Void, Never>?
@State private var availableLocales: [Locale] = []
@State private var triggerEntries: [TriggerEntry] = []
private let fieldLabelWidth: CGFloat = 140
@@ -189,68 +188,59 @@ struct VoiceWakeSettings: View {
}
.settingsDetailContent()
}
.task {
guard !self.isPreview else { return }
await self.loadMicsIfNeeded()
}
.task {
guard !self.isPreview else { return }
await self.loadLocalesIfNeeded()
}
.task {
guard !self.isPreview else { return }
await self.restartMeter()
}
.onAppear {
guard !self.isPreview else { return }
guard self.isActive else { return }
self.activateLivePreview()
self.startMicObserver()
self.loadTriggerEntries()
}
.onChange(of: self.state.voiceWakeMicID) { _, _ in
guard !self.isPreview else { return }
self.updateSelectedMicName()
guard self.isActive else { return }
self.scheduleMeterRestart()
Task { await self.restartMeter() }
}
.onChange(of: self.isActive) { _, active in
guard !self.isPreview else { return }
if !active {
self.deactivateLivePreview()
self.tester.stop()
self.isTesting = false
self.testState = .idle
self.testTimeoutTask?.cancel()
self.micRefreshTask?.cancel()
self.micRefreshTask = nil
Task { await self.meter.stop() }
self.micObserver.stop()
self.syncTriggerEntriesToState()
} else {
self.activateLivePreview()
self.startMicObserver()
self.loadTriggerEntries()
}
}
.onDisappear {
guard !self.isPreview else { return }
self.deactivateLivePreview()
self.tester.stop()
self.isTesting = false
self.testState = .idle
self.testTimeoutTask?.cancel()
self.micRefreshTask?.cancel()
self.micRefreshTask = nil
self.micObserver.stop()
Task { await self.meter.stop() }
self.syncTriggerEntriesToState()
}
}
private func activateLivePreview() {
self.meterStartupTask?.cancel()
self.startMicObserver()
self.loadTriggerEntries()
self.meterStartupTask = Task { @MainActor in
await self.loadMicsIfNeeded()
guard !Task.isCancelled, self.isActive else { return }
await self.loadLocalesIfNeeded()
guard !Task.isCancelled, self.isActive else { return }
await self.restartMeter()
}
}
private func deactivateLivePreview() {
self.tester.stop()
self.isTesting = false
self.testState = .idle
self.testTimeoutTask?.cancel()
self.micRefreshTask?.cancel()
self.micRefreshTask = nil
self.meterStartupTask?.cancel()
self.meterStartupTask = nil
self.micObserver.stop()
self.state.voiceWakeMeterActive = false
Task { await self.meter.stop() }
}
private func scheduleMeterRestart() {
self.meterStartupTask?.cancel()
self.meterStartupTask = Task { @MainActor in
guard !Task.isCancelled, self.isActive else { return }
await self.restartMeter()
}
}
private func loadTriggerEntries() {
self.triggerEntries = self.state.swabbleTriggerWords.map { TriggerEntry(id: UUID(), value: $0) }
}
@@ -662,7 +652,6 @@ struct VoiceWakeSettings: View {
@MainActor
private func scheduleMicRefresh() {
guard self.isActive else { return }
MicRefreshSupport.schedule(refreshTask: &self.micRefreshTask) {
await self.loadMicsIfNeeded(force: true)
await self.restartMeter()
@@ -724,17 +713,8 @@ struct VoiceWakeSettings: View {
@MainActor
private func restartMeter() async {
guard self.isActive else {
self.state.voiceWakeMeterActive = false
await self.meter.stop()
return
}
self.meterError = nil
await self.meter.stop()
guard !Task.isCancelled, self.isActive else {
self.state.voiceWakeMeterActive = false
return
}
do {
try await self.meter.start { [weak state] level in
Task { @MainActor in
@@ -742,14 +722,7 @@ struct VoiceWakeSettings: View {
self.meterLevel = level
}
}
guard !Task.isCancelled, self.isActive else {
self.state.voiceWakeMeterActive = false
await self.meter.stop()
return
}
self.state.voiceWakeMeterActive = true
} catch {
self.state.voiceWakeMeterActive = false
self.meterError = error.localizedDescription
}
}

View File

@@ -2379,7 +2379,6 @@ public struct SessionsCompactParams: Codable, Sendable {
public struct SessionsUsageParams: Codable, Sendable {
public let key: String?
public let agentid: String?
public let agentscope: String?
public let startdate: String?
public let enddate: String?
public let mode: AnyCodable?
@@ -2393,7 +2392,6 @@ public struct SessionsUsageParams: Codable, Sendable {
public init(
key: String?,
agentid: String? = nil,
agentscope: String? = nil,
startdate: String?,
enddate: String?,
mode: AnyCodable?,
@@ -2406,7 +2404,6 @@ public struct SessionsUsageParams: Codable, Sendable {
{
self.key = key
self.agentid = agentid
self.agentscope = agentscope
self.startdate = startdate
self.enddate = enddate
self.mode = mode
@@ -2421,7 +2418,6 @@ public struct SessionsUsageParams: Codable, Sendable {
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
case agentscope = "agentScope"
case startdate = "startDate"
case enddate = "endDate"
case mode
@@ -5462,8 +5458,6 @@ public struct CronListParams: Codable, Sendable {
public let offset: Int?
public let query: String?
public let enabled: AnyCodable?
public let schedulekind: AnyCodable?
public let lastrunstatus: AnyCodable?
public let sortby: AnyCodable?
public let sortdir: AnyCodable?
public let agentid: String?
@@ -5474,8 +5468,6 @@ public struct CronListParams: Codable, Sendable {
offset: Int?,
query: String?,
enabled: AnyCodable?,
schedulekind: AnyCodable?,
lastrunstatus: AnyCodable?,
sortby: AnyCodable?,
sortdir: AnyCodable?,
agentid: String?)
@@ -5485,8 +5477,6 @@ public struct CronListParams: Codable, Sendable {
self.offset = offset
self.query = query
self.enabled = enabled
self.schedulekind = schedulekind
self.lastrunstatus = lastrunstatus
self.sortby = sortby
self.sortdir = sortdir
self.agentid = agentid
@@ -5498,8 +5488,6 @@ public struct CronListParams: Codable, Sendable {
case offset
case query
case enabled
case schedulekind = "scheduleKind"
case lastrunstatus = "lastRunStatus"
case sortby = "sortBy"
case sortdir = "sortDir"
case agentid = "agentId"

View File

@@ -7,7 +7,7 @@ on:
jobs:
build-and-test:
runs-on: macos-15
runs-on: macos-latest
defaults:
run:
shell: bash

View File

@@ -27,7 +27,7 @@ const bundledPluginEntries = [
"setup-entry.ts!",
"{api,contract-api,helper-api,runtime-api,light-runtime-api,update-offset-runtime-api,channel-plugin-api,provider-plugin-api,setup-api}.ts!",
"subagent-hooks-api.ts!",
"src/{api,runtime-api,light-runtime-api,update-offset-runtime-api,channel-plugin-api,provider-plugin-api,doctor-contract,setup-surface,mcp-serve}.ts!",
"src/{api,runtime-api,light-runtime-api,update-offset-runtime-api,channel-plugin-api,provider-plugin-api,doctor-contract,setup-surface}.ts!",
"src/subagent-hooks-api.ts!",
] as const;
@@ -50,7 +50,7 @@ const bundledPluginIgnoredRuntimeDependencies = [
"lit",
"linkedom",
"openclaw",
"clawpdf",
"pdfjs-dist",
] as const;
const rootBundledPluginRuntimeDependencies = [
@@ -70,7 +70,7 @@ const rootBundledPluginRuntimeDependencies = [
"minimatch",
"node-edge-tts",
"openshell",
"clawpdf",
"pdfjs-dist",
"tokenjuice",
] as const;
@@ -168,19 +168,6 @@ const config = {
entry: ["src/index.ts!", "src/*.ts!", "src/harness/**/*.ts!"],
project: ["src/**/*.ts!"],
},
"packages/gateway-client": {
entry: ["src/index.ts!"],
project: ["src/**/*.ts!"],
},
"packages/gateway-protocol": {
entry: ["src/index.ts!", "src/schema.ts!"],
project: ["src/**/*.ts!"],
},
"packages/speech-core": {
entry: ["api.ts!", "runtime-api.ts!", "speaker.ts!", "voice-models.ts!"],
project: ["**/*.ts!"],
ignoreDependencies: ["openclaw"],
},
"packages/*": {
entry: ["index.js!", "scripts/postinstall.js!"],
project: ["index.js!", "scripts/**/*.js!"],

View File

@@ -63,7 +63,6 @@ services:
ports:
- "${OPENCLAW_GATEWAY_PORT:-18789}:18789"
- "${OPENCLAW_BRIDGE_PORT:-18790}:18790"
- "${OPENCLAW_MSTEAMS_PORT:-3978}:3978"
init: true
restart: unless-stopped
command:

View File

@@ -1,4 +1,4 @@
feec5d92420f2008f02beed1f09c373a9392bf54fec547087bfa0581d8c5fe85 config-baseline.json
e51fb382c686637ba6c413648fead48950982b72595c9c6aa1a50da109f4024d config-baseline.core.json
6e64ca2148297da39348568c1faf8c6c431efe3c7b53fb29914905f5b88322a4 config-baseline.channel.json
a69acd971a7d54d3086f26c52fde4084eaeef350f71b918fb8e7338f329bff95 config-baseline.json
ee4c0f0fb15cda02268f2e83d0c5e1c8d0ec0a2c1b2fdb89cdfce308dadb2b8b config-baseline.core.json
b901fb766edfd9df630690281476fc4032c64772f69d1d8f7b2e0e913a90f229 config-baseline.channel.json
1b763a5524aca2d7ecf1eea38f845ad1ffed5c1b37e85e62f6a7902a3ee0f920 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
28bbd7e0a05747ef3d17ae25e6dac5002d6cc9ad3256f1c4e58ee8e45014e397 plugin-sdk-api-baseline.json
d1d3fe6599e6cbc64f069737d08099d6b2586bc2b9d8d2ddb00d9f6e35c87cc7 plugin-sdk-api-baseline.jsonl
7039b60f2cea732a90db633328952faaddd919f0d098b303b29d554e64184073 plugin-sdk-api-baseline.json
1a78f4df81562af070c5379c6369a8bea9c704f985b5382a463364757b26db0d plugin-sdk-api-baseline.jsonl

View File

@@ -175,26 +175,6 @@
"source": "Agent harness plugins",
"target": "Agent harness plugins"
},
{
"source": "Agent harness plugins (SDK reference)",
"target": "Agent harness plugins (SDK reference)"
},
{
"source": "Copilot SDK harness",
"target": "Copilot SDK harness"
},
{
"source": "Copilot plugin",
"target": "Copilot plugin"
},
{
"source": "GitHub Copilot agent runtime",
"target": "GitHub Copilot agent runtime"
},
{
"source": "copilot",
"target": "copilot"
},
{
"source": "Agent loop",
"target": "Agent loop"

View File

@@ -15,8 +15,9 @@ Cron is the Gateway's built-in scheduler. It persists jobs, wakes the agent at t
<Steps>
<Step title="Add a one-shot reminder">
```bash
openclaw cron create "2026-02-01T16:00:00Z" \
openclaw cron add \
--name "Reminder" \
--at "2026-02-01T16:00:00Z" \
--session main \
--system-event "Reminder: check the cron docs draft" \
--wake now \
@@ -214,11 +215,12 @@ Failure notifications follow a separate destination path:
</Tab>
<Tab title="Recurring isolated job">
```bash
openclaw cron create "0 7 * * *" \
"Summarize overnight updates." \
openclaw cron add \
--name "Morning brief" \
--cron "0 7 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Summarize overnight updates." \
--announce \
--channel slack \
--to "channel:C1234567890"
@@ -237,14 +239,6 @@ Failure notifications follow a separate destination path:
--announce
```
</Tab>
<Tab title="Webhook output">
```bash
openclaw cron create "0 18 * * 1-5" \
"Summarize today's deploys as JSON." \
--name "Deploy digest" \
--webhook "https://example.invalid/openclaw/cron"
```
</Tab>
</Tabs>
## Webhooks
@@ -417,14 +411,12 @@ openclaw cron runs --id <jobId> --run-id <runId>
openclaw cron remove <jobId>
# Agent selection (multi-agent setups)
openclaw cron create "0 6 * * *" "Check ops queue" --name "Ops sweep" --session isolated --agent ops
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
openclaw cron edit <jobId> --clear-agent
```
`openclaw cron run <jobId>` returns after enqueueing the manual run. Use `--wait` for shutdown hooks, maintenance scripts, or other automation that must block until the queued run finishes. Wait mode polls the exact returned `runId`; it exits `0` for status `ok` and non-zero for `error`, `skipped`, or a wait timeout.
`openclaw cron create` is an alias for `openclaw cron add`, and new jobs can use a positional schedule (`"0 9 * * 1"`, `"every 1h"`, `"20m"`, or an ISO timestamp) followed by a positional agent prompt. Use `--webhook <url>` on `cron add|create` or `cron edit` to POST the finished run payload to an HTTP endpoint. Webhook delivery cannot be combined with chat delivery flags such as `--announce`, `--channel`, `--to`, `--thread-id`, or `--account`.
<Note>
Model override note:

View File

@@ -696,7 +696,6 @@ Default slash command settings:
maxLines: 8,
maxLineChars: 120,
toolProgress: true,
commentary: false,
},
},
},
@@ -709,7 +708,6 @@ Default slash command settings:
- Media, error, and explicit-reply finals cancel pending preview edits.
- `streaming.preview.toolProgress` (default `true`) controls whether tool/progress updates reuse the preview message.
- Tool/progress rows render as compact emoji + title + detail when available, for example `🛠️ Bash: run tests` or `🔎 Web Search: for "query"`.
- `streaming.progress.commentary` (default `false`) opts into assistant commentary/preamble text in the temporary progress draft. Commentary is cleaned before display, stays transient, and does not change final answer delivery.
- `streaming.progress.maxLineChars` controls the per-line progress preview budget. Prose is shortened on word boundaries; command and path details keep useful suffixes.
- `streaming.preview.commandText` / `streaming.progress.commandText` controls command/exec detail in compact progress lines: `raw` (default) or `status` (tool label only).
@@ -1217,7 +1215,7 @@ Auto-join example:
realtime: {
provider: "openai",
model: "gpt-realtime-2",
speakerVoice: "cedar",
voice: "cedar",
},
},
},
@@ -1227,20 +1225,20 @@ Auto-join example:
Notes:
- `voice.tts` overrides `messages.tts` for `stt-tts` voice playback only. Realtime modes use `voice.realtime.speakerVoice`.
- `voice.tts` overrides `messages.tts` for `stt-tts` voice playback only. Realtime modes use `voice.realtime.voice`.
- `voice.mode` controls the conversation path. The default is `agent-proxy`: a realtime voice front end handles turn timing, interruption, and playback, delegates substantive work to the routed OpenClaw agent through `openclaw_agent_consult`, and treats the result like a typed Discord prompt from that speaker. `stt-tts` keeps the older batch STT plus TTS flow. `bidi` lets the realtime model converse directly while exposing `openclaw_agent_consult` for the OpenClaw brain.
- `voice.agentSession` controls which OpenClaw conversation receives voice turns. Leave it unset for the voice channel's own session, or set `{ mode: "target", target: "channel:<text-channel-id>" }` to make the voice channel act as the microphone/speaker extension of an existing Discord text channel session such as `#maintainers`.
- `voice.model` overrides the OpenClaw agent brain for Discord voice responses and realtime consults. Leave it unset to inherit the routed agent model. It is separate from `voice.realtime.model`.
- `voice.followUsers` lets the bot join, move, and leave Discord voice with selected users. See [Follow users in voice](#follow-users-in-voice) for behavior rules and examples.
- `agent-proxy` routes speech through `discord-voice`, which preserves normal owner/tool authorization for the speaker and target session but hides the agent `tts` tool because Discord voice owns playback. By default, `agent-proxy` gives the consult full owner-equivalent tool access for owner speakers (`voice.realtime.toolPolicy: "owner"`) and strongly prefers consulting the OpenClaw agent before substantive answers (`voice.realtime.consultPolicy: "always"`). In that default `always` mode, the realtime layer does not auto-speak filler before the consult answer; it captures and transcribes speech, then speaks the routed OpenClaw answer. If multiple forced consult answers finish while Discord is still playing the first answer, later exact-speech answers are queued until playback idles instead of replacing speech mid-sentence.
- In `stt-tts` mode, STT uses `tools.media.audio`; `voice.model` does not affect transcription.
- In realtime modes, `voice.realtime.provider`, `voice.realtime.model`, and `voice.realtime.speakerVoice` configure the realtime audio session. For OpenAI Realtime 2 plus the Codex brain, use `voice.realtime.model: "gpt-realtime-2"` and `voice.model: "openai-codex/gpt-5.5"`.
- In realtime modes, `voice.realtime.provider`, `voice.realtime.model`, and `voice.realtime.voice` configure the realtime audio session. For OpenAI Realtime 2 plus the Codex brain, use `voice.realtime.model: "gpt-realtime-2"` and `voice.model: "openai-codex/gpt-5.5"`.
- Realtime voice modes include small `IDENTITY.md`, `USER.md`, and `SOUL.md` profile files in the realtime provider instructions by default so fast direct turns keep the same identity, user grounding, and persona as the routed OpenClaw agent. Set `voice.realtime.bootstrapContextFiles` to a subset to customize this, or `[]` to disable it. The supported realtime bootstrap files are limited to those profile files; `AGENTS.md` stays in the normal agent context. The injected profile context does not replace `openclaw_agent_consult` for workspace work, current facts, memory lookup, or tool-backed actions.
- In OpenAI `agent-proxy` realtime mode, set `voice.realtime.requireWakeName: true` to keep Discord realtime voice silent until a transcript starts or ends with a wake name. Configured wake names must be one or two words. If `voice.realtime.wakeNames` is unset, OpenClaw uses the routed agent `name` plus `OpenClaw`, falling back to the agent id plus `OpenClaw`. Wake-name gating disables realtime provider auto-response, routes accepted turns through the OpenClaw agent consult path, and gives a short spoken acknowledgement when a leading wake name is recognized from partial transcription before the final transcript arrives.
- The OpenAI realtime provider accepts current Realtime 2 event names and legacy Codex-compatible aliases for output audio and transcript events, so compatible provider snapshots can drift without dropping assistant audio.
- `voice.realtime.bargeIn` controls whether Discord speaker-start events interrupt active realtime playback. If unset, it follows the realtime provider's input-audio interruption setting.
- `voice.realtime.minBargeInAudioEndMs` controls the minimum assistant playback duration before an OpenAI realtime barge-in truncates audio. Default: `250`. Set `0` for immediate interruption in low-echo rooms, or raise it for echo-heavy speaker setups.
- For an OpenAI voice on Discord playback, set `voice.tts.provider: "openai"` and choose a Text-to-speech voice under `voice.tts.providers.openai.speakerVoice`. `cedar` is a good masculine-sounding choice on the current OpenAI TTS model.
- For an OpenAI voice on Discord playback, set `voice.tts.provider: "openai"` and choose a Text-to-speech voice under `voice.tts.openai.voice` or `voice.tts.providers.openai.voice`. `cedar` is a good masculine-sounding choice on the current OpenAI TTS model.
- Per-channel Discord `systemPrompt` overrides apply to voice transcript turns for that voice channel.
- Voice transcript turns derive owner status from Discord `allowFrom` (or `dm.allowFrom`) for owner-gated commands and channel actions. Agent tool visibility follows the configured tool policy for the routed session.
- Discord voice is opt-in for text-only configs; set `channels.discord.voice.enabled=true` (or keep an existing `channels.discord.voice` block) to enable `/vc` commands, the voice runtime, and the `GuildVoiceStates` gateway intent.
@@ -1331,7 +1329,7 @@ Default agent-proxy voice-channel session example:
realtime: {
provider: "openai",
model: "gpt-realtime-2",
speakerVoice: "cedar",
voice: "cedar",
},
},
},
@@ -1353,11 +1351,9 @@ Legacy STT plus TTS example:
model: "openai/gpt-5.4-mini",
tts: {
provider: "openai",
providers: {
openai: {
model: "gpt-4o-mini-tts",
speakerVoice: "cedar",
},
openai: {
model: "gpt-4o-mini-tts",
voice: "cedar",
},
},
},
@@ -1379,7 +1375,7 @@ Realtime bidi example:
realtime: {
provider: "openai",
model: "gpt-realtime-2",
speakerVoice: "cedar",
voice: "cedar",
toolPolicy: "safe-read-only",
consultPolicy: "always",
},
@@ -1406,7 +1402,7 @@ Voice as an extension of an existing Discord channel session:
realtime: {
provider: "openai",
model: "gpt-realtime-2",
speakerVoice: "cedar",
voice: "cedar",
},
},
},
@@ -1437,7 +1433,7 @@ Echo-heavy OpenAI Realtime example:
realtime: {
provider: "openai",
model: "gpt-realtime-2",
speakerVoice: "cedar",
voice: "cedar",
bargeIn: true,
minBargeInAudioEndMs: 500,
consultPolicy: "always",

View File

@@ -666,58 +666,6 @@ Teams delivers messages via HTTP webhook. If processing takes too long (e.g., sl
OpenClaw handles this by returning quickly and sending replies proactively, but very slow responses may still cause issues.
### Teams cloud and service URL support
This SDK-backed Teams path is live-validated for Microsoft Teams public cloud.
Inbound replies use the incoming Teams SDK turn context. Out-of-context proactive operations - sends, edits, deletes, cards, polls, file-consent messages, and queued long-running replies - use the stored conversation reference `serviceUrl`. Public cloud defaults to the Teams SDK public cloud environment and allows stored references on the public Teams Connector host: `https://smba.trafficmanager.net/`.
Public cloud is the default. You do not need to set `channels.msteams.cloud` or `channels.msteams.serviceUrl` for normal public-cloud bots.
For non-public Teams clouds, set `cloud` and the matching proactive boundary when Microsoft publishes one:
- `channels.msteams.cloud` selects the Teams SDK cloud preset for authentication, JWT validation, token services, and Graph scope.
- `channels.msteams.serviceUrl` selects the Bot Connector endpoint boundary used to validate stored conversation references before proactive sends, edits, deletes, cards, polls, file-consent messages, and queued long-running replies. It is required for USGov and DoD SDK clouds. For China/21Vianet, OpenClaw uses the SDK `China` preset and accepts stored/configured service URLs only on Azure China Bot Framework channel hosts.
Microsoft publishes the global proactive Bot Connector endpoints in the [Create the conversation](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/send-proactive-messages?tabs=dotnet#create-the-conversation) section of the Teams proactive messaging docs. Use the incoming activity's `serviceUrl` when available; if you need a global proactive endpoint, use Microsoft's table.
| Teams environment | OpenClaw config | Proactive `serviceUrl` |
| ----------------- | ----------------------------------------------------------- | -------------------------------------------------- |
| Public | no cloud/serviceUrl config needed | `https://smba.trafficmanager.net/teams` |
| GCC | set `serviceUrl`; no separate Teams SDK cloud preset exists | `https://smba.infra.gcc.teams.microsoft.com/teams` |
| GCC High | `cloud: "USGov"` + `serviceUrl` | `https://smba.infra.gov.teams.microsoft.us/teams` |
| DoD | `cloud: "USGovDoD"` + `serviceUrl` | `https://smba.infra.dod.teams.microsoft.us/teams` |
| China/21Vianet | `cloud: "China"` | use the incoming activity's `serviceUrl` |
Example for GCC, where Microsoft documents a separate proactive service URL but the Teams SDK does not expose a separate GCC cloud preset:
```json
{
"channels": {
"msteams": {
"serviceUrl": "https://smba.infra.gcc.teams.microsoft.com/teams"
}
}
}
```
Example for GCC High:
```json
{
"channels": {
"msteams": {
"cloud": "USGov",
"serviceUrl": "https://smba.infra.gov.teams.microsoft.us/teams"
}
}
}
```
`channels.msteams.serviceUrl` is restricted to supported Microsoft Teams Bot Connector hosts. When a service URL is configured, OpenClaw checks that the stored conversation `serviceUrl` uses the same host before proactive sends, edits, deletes, cards, polls, or queued long-running replies run. With the default public-cloud config, OpenClaw fails closed if a stored conversation points outside the public Teams Connector host. Receive a fresh message from the conversation after changing cloud/service URL settings so the stored conversation reference is current.
China/21Vianet does not have a separate global proactive `smba` URL in Microsoft's Teams proactive endpoint table. Configure `cloud: "China"` so the Teams SDK uses Azure China auth, token, and JWT endpoints. Proactive sends then require a stored conversation reference from an incoming China Teams activity, or an explicitly configured service URL, on the Azure China Bot Framework channel boundary (`*.botframework.azure.cn`). Graph-backed Teams helpers are currently disabled for `cloud: "China"` until OpenClaw routes Graph requests through the Azure China Graph endpoint.
### Formatting
Teams markdown is more limited than Slack or Discord:
@@ -732,8 +680,6 @@ Key settings (see `/gateway/configuration` for shared channel patterns):
- `channels.msteams.enabled`: enable/disable the channel.
- `channels.msteams.appId`, `channels.msteams.appPassword`, `channels.msteams.tenantId`: bot credentials.
- `channels.msteams.cloud`: Teams SDK cloud environment (`Public`, `USGov`, `USGovDoD`, or `China`; default `Public`). Set this with `serviceUrl` for USGov/DoD SDK clouds; China uses the SDK preset and stored Azure China Bot Framework conversation references, with Graph-backed helpers disabled until Azure China Graph routing is implemented.
- `channels.msteams.serviceUrl`: Bot Connector service URL boundary for SDK proactive operations. Public cloud uses the SDK default; set this for GCC (`https://smba.infra.gcc.teams.microsoft.com/teams`), GCC High, or DoD. China accepts Azure China Bot Framework channel hosts when the stored conversation reference comes from Teams operated by 21Vianet.
- `channels.msteams.webhook.port` (default `3978`)
- `channels.msteams.webhook.path` (default `/api/messages`)
- `channels.msteams.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing)

View File

@@ -1120,8 +1120,6 @@ Hide raw command/exec text while keeping compact progress lines:
`channels.slack.streaming.nativeTransport` controls Slack native text streaming when `channels.slack.streaming.mode` is `partial` (default: `true`).
Slack native progress task cards are opt-in for progress mode. Set `channels.slack.streaming.progress.nativeTaskCards` to `true` with `channels.slack.streaming.mode="progress"` to send a Slack-native plan/task card while work is running, then update the same task card at completion. Without this flag, progress mode keeps the portable draft-preview behavior.
- A reply thread must be available for native text streaming and Slack assistant thread status to appear. Thread selection still follows `replyToMode`.
- Channel, group-chat, and top-level DM roots can still use the normal draft preview when native streaming is unavailable or no reply thread exists.
- Top-level Slack DMs stay off-thread by default, so they do not show Slack's thread-style native stream/status preview; OpenClaw posts and edits a draft preview in the DM instead.
@@ -1144,24 +1142,6 @@ Use draft preview instead of Slack native text streaming:
}
```
Opt in to Slack native progress task cards:
```json5
{
channels: {
slack: {
streaming: {
mode: "progress",
progress: {
nativeTaskCards: true,
render: "rich",
},
},
},
},
}
```
Legacy keys:
- `channels.slack.streamMode` (`replace | status_final | append`) is a legacy runtime alias for `channels.slack.streaming.mode`.

View File

@@ -43,9 +43,7 @@ OpenClaw CI runs on every push to `main` and every pull request. The `preflight`
GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. Matrix jobs use `fail-fast: false`, and `build-artifacts` reports embedded channel, core-support-boundary, and gateway-watch failures directly instead of queuing tiny verifier jobs. The automatic CI concurrency key is versioned (`CI-v7-*`) so a GitHub-side zombie in an old queue group cannot indefinitely block newer main runs. Manual full-suite runs use `CI-manual-v1-*` and do not cancel in-progress runs.
Use `pnpm ci:timings`, `pnpm ci:timings:recent`, or `node scripts/ci-run-timings.mjs <run-id>` to summarize wall time, queue time, slowest jobs, failures, and the `pnpm-store-warmup` fanout barrier from GitHub Actions. CI also uploads the same run summary as a `ci-timings-summary` artifact. For build timing, check the `build-artifacts` job's `Build dist` step: `pnpm build:ci-artifacts` prints `[build-all] phase timings:` and includes `ui:build`; the job also uploads the `startup-memory` artifact.
For pull request runs, the terminal timing-summary job runs the helper from the trusted base revision before passing `GH_TOKEN` to `gh run view`. That keeps the tokened query out of branch-controlled code while still summarizing the pull request's current CI run.
The `ci-timings-summary` job uploads a compact `ci-timings-summary` artifact for each non-draft CI run. It records wall time, queue time, slowest jobs, and failed jobs for the current run, so CI health checks do not need to scrape the full Actions payload repeatedly. The `build-artifacts` job also runs the blocking startup-memory smoke and uploads a `startup-memory` artifact with per-command RSS values for `--help`, `status --json`, and `gateway status`.
## Real behavior proof
@@ -122,17 +120,17 @@ gh workflow run full-release-validation.yml --ref main -f ref=<branch-or-sha>
## Runners
| Runner | Jobs |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ubuntu-24.04` | Manual CI dispatch and non-canonical repository fallbacks, workflow-sanity, labeler, auto-response, docs workflows outside CI, and install-smoke preflight so the Blacksmith matrix can queue earlier |
| `blacksmith-4vcpu-ubuntu-2404` | `CodeQL Critical Quality`, `preflight`, `security-fast`, lower-weight extension shards, `checks-fast-core`, plugin/channel contract shards, `checks-node-compat-node22`, `check-guards`, `check-prod-types`, and `check-test-types` |
| `blacksmith-8vcpu-ubuntu-2404` | Linux Node test shards, bundled plugin test shards, `check-additional-*` shards, `check-dependencies`, and `android` |
| `blacksmith-16vcpu-ubuntu-2404` | `build-artifacts`, `check-lint` (CPU-sensitive enough that 8 vCPU cost more than they saved); install-smoke Docker builds (32-vCPU queue time cost more than it saved) |
| `blacksmith-16vcpu-windows-2025` | `checks-windows` |
| `blacksmith-6vcpu-macos-15` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-15` |
| `blacksmith-12vcpu-macos-26` | `macos-swift` on `openclaw/openclaw`; forks fall back to `macos-26` |
| Runner | Jobs |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ubuntu-24.04` | `preflight`, docs checks, Python skills, workflow-sanity, labeler, auto-response; install-smoke preflight also uses GitHub-hosted Ubuntu so the Blacksmith matrix can queue earlier |
| `blacksmith-4vcpu-ubuntu-2404` | `CodeQL Critical Quality`, `security-fast`, lower-weight extension shards, `checks-fast-core`, plugin/channel contract shards, `checks-node-compat-node22`, `check-guards`, `check-prod-types`, and `check-test-types` |
| `blacksmith-8vcpu-ubuntu-2404` | Linux Node test shards, bundled plugin test shards, `check-additional-*` shards, `android` |
| `blacksmith-16vcpu-ubuntu-2404` | `build-artifacts`, `check-lint` (CPU-sensitive enough that 8 vCPU cost more than they saved); install-smoke Docker builds (32-vCPU queue time cost more than it saved) |
| `blacksmith-16vcpu-windows-2025` | `checks-windows` |
| `blacksmith-6vcpu-macos-latest` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-latest` |
| `blacksmith-12vcpu-macos-latest` | `macos-swift` on `openclaw/openclaw`; forks fall back to `macos-latest` |
Canonical-repo CI keeps Blacksmith as the default runner path for normal push and pull-request runs. `workflow_dispatch` and non-canonical repository runs use GitHub-hosted runners, but normal canonical runs do not currently probe Blacksmith queue health or automatically fall back to GitHub-hosted labels when Blacksmith is unavailable.
Canonical-repo CI keeps Blacksmith as the default runner path. During `preflight`, `scripts/ci-runner-labels.mjs` checks recent queued and in-progress Actions runs for queued Blacksmith jobs. If a specific Blacksmith label already has queued jobs, downstream jobs that would use that exact label fall back to the matching GitHub-hosted runner (`ubuntu-24.04`, `windows-2025`, or `macos-latest`) for that run only. Other Blacksmith sizes in the same OS family stay on their primary labels. If the API probe fails, no fallback is applied.
## Local equivalents

View File

@@ -14,26 +14,6 @@ Manage cron jobs for the Gateway scheduler.
Run `openclaw cron --help` for the full command surface. See [Cron jobs](/automation/cron-jobs) for the conceptual guide.
</Tip>
## Create jobs quickly
`openclaw cron create` is an alias for `openclaw cron add`. For new jobs, put the schedule first and the prompt second:
```bash
openclaw cron create "0 7 * * *" \
"Summarize overnight updates." \
--name "Morning brief" \
--agent ops
```
Use `--webhook <url>` when the job should POST the finished payload instead of delivering to a chat target:
```bash
openclaw cron create "0 18 * * 1-5" \
"Summarize today's deploys as JSON." \
--name "Deploy digest" \
--webhook "https://example.invalid/openclaw/cron"
```
## Sessions
`--session` accepts `main`, `isolated`, `current`, or `session:<id>`.
@@ -70,8 +50,6 @@ Isolated cron chat delivery is shared between the agent and the runner:
- `webhook` posts the finished payload to a URL.
- `none` disables runner fallback delivery.
Use `cron add|create --webhook <url>` or `cron edit <job-id> --webhook <url>` to set webhook delivery. Do not combine `--webhook` with chat delivery flags such as `--announce`, `--no-deliver`, `--channel`, `--to`, `--thread-id`, or `--account`.
`--announce` is runner fallback delivery for the final reply. `--no-deliver` disables that fallback but does not remove the agent's `message` tool when a chat route is available.
Reminders created from an active chat preserve the live chat delivery target for fallback announce delivery. Internal session keys may be lowercase; do not use them as a source of truth for case-sensitive provider IDs such as Matrix room IDs.
@@ -241,10 +219,11 @@ openclaw cron edit <job-id> --announce --channel telegram --to "-1001234567890"
Create an isolated job with lightweight bootstrap context:
```bash
openclaw cron create "0 7 * * *" \
"Summarize overnight updates." \
openclaw cron add \
--name "Lightweight morning brief" \
--cron "0 7 * * *" \
--session isolated \
--message "Summarize overnight updates." \
--light-context \
--no-deliver
```
@@ -291,7 +270,6 @@ Delivery tweaks:
```bash
openclaw cron edit <job-id> --announce --channel slack --to "channel:C1234567890"
openclaw cron edit <job-id> --webhook "https://example.invalid/openclaw/cron"
openclaw cron edit <job-id> --best-effort-deliver
openclaw cron edit <job-id> --no-best-effort-deliver
openclaw cron edit <job-id> --no-deliver

View File

@@ -40,7 +40,6 @@ openclaw doctor
openclaw doctor --lint
openclaw doctor --lint --json
openclaw doctor --lint --severity-min warning
openclaw doctor --lint --allow-exec
openclaw doctor --deep
openclaw doctor --fix
openclaw doctor --fix --non-interactive
@@ -65,7 +64,6 @@ The targeted Discord capabilities probe reports the bot's effective channel perm
- `--force`: apply aggressive repairs, including overwriting custom service config when needed
- `--non-interactive`: run without prompts; safe migrations and non-service repairs only
- `--generate-gateway-token`: generate and configure a gateway token
- `--allow-exec`: allow doctor to execute configured exec SecretRefs while verifying secrets
- `--deep`: scan system services for extra gateway installs and report recent Gateway supervisor restart handoffs
- `--lint`: run modernized health checks in read-only mode and emit diagnostic findings
- `--json`: with `--lint`, emit JSON findings instead of human output
@@ -86,7 +84,6 @@ are only accepted with `--lint`.
openclaw doctor --lint
openclaw doctor --lint --severity-min warning
openclaw doctor --lint --json
openclaw doctor --lint --allow-exec
openclaw doctor --lint --only core/doctor/gateway-config --json
```
@@ -194,7 +191,6 @@ Notes:
- Interactive prompts (like keychain/OAuth fixes) only run when stdin is a TTY and `--non-interactive` is **not** set. Headless runs (cron, Telegram, no terminal) will skip prompts.
- Performance: non-interactive `doctor` runs skip eager plugin loading so headless health checks stay fast. Interactive doctor sessions still load the plugin surfaces needed by the legacy health and repair flow.
- `--lint` is stricter than `--non-interactive`: it is always read-only, never prompts, and never applies safe migrations. Run `doctor --fix` or `doctor --repair` when you want doctor to make changes.
- By default, doctor does not execute `exec` SecretRefs while checking secrets. Use `openclaw doctor --allow-exec` or `openclaw doctor --lint --allow-exec` only when you intentionally want doctor to run those configured secret resolvers.
- `--fix` (alias for `--repair`) writes a backup to `~/.openclaw/openclaw.json.bak` and drops unknown config keys, listing each removal.
- Modernized health checks can expose a `repair()` path for `doctor --fix`; checks that do not expose one continue through the existing doctor repair flow.
- `doctor --fix --non-interactive` reports missing or stale gateway service definitions but does not install or rewrite them outside update repair mode. Run `openclaw gateway install` for a missing service, or `openclaw gateway install --force` when you intentionally want to replace the launcher.
@@ -218,7 +214,7 @@ Notes:
- Doctor warns when skills allowed for the default agent are unavailable in the current runtime environment because bins, env vars, config, or OS requirements are missing. `doctor --fix` can disable those unavailable skills with `skills.entries.<skill>.enabled=false`; install/configure the missing requirement instead when you want to keep the skill active.
- 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 legacy sandbox registry files (`~/.openclaw/sandbox/containers.json` or `~/.openclaw/sandbox/browsers.json`) are present, doctor reports them; `openclaw doctor --fix` migrates valid entries into sharded registry directories and quarantines invalid legacy files.
- 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. For exec-backed SecretRefs, doctor skips execution unless `--allow-exec` is present.
- 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.
- After state-directory migrations, doctor warns when enabled default Telegram or Discord accounts depend on env fallback and `TELEGRAM_BOT_TOKEN` or `DISCORD_BOT_TOKEN` is unavailable to the doctor process.
- 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.

View File

@@ -170,7 +170,7 @@ Notes:
- `memory status` includes any extra paths configured via `memorySearch.extraPaths`.
- If effectively active memory remote API key fields are configured as SecretRefs, the command resolves those values from the active gateway snapshot. If gateway is unavailable, the command fails fast.
- Gateway version skew note: this command path requires a gateway that supports `secrets.resolve`; older gateways return an unknown-method error.
- Tune scheduled sweep cadence with `dreaming.frequency`. Deep promotion policy is otherwise internal except for `dreaming.phases.deep.maxPromotedSnippetTokens`, which bounds promoted snippet length while keeping provenance visible. Use CLI flags on `memory promote` when you need one-off manual threshold overrides.
- Tune scheduled sweep cadence with `dreaming.frequency`. Deep promotion policy is otherwise internal; use CLI flags on `memory promote` when you need one-off manual overrides.
- `memory rem-harness --path <file-or-dir> --grounded` previews grounded `What Happened`, `Reflections`, and `Possible Lasting Updates` from historical daily notes without writing anything.
- `memory rem-backfill --path <file-or-dir>` writes reversible grounded diary entries into `DREAMS.md` for UI review.
- `memory rem-backfill --path <file-or-dir> --stage-short-term` also seeds grounded durable candidates into the live short-term promotion store so the normal deep phase can rank them.

View File

@@ -169,7 +169,7 @@ is available, then fall back to `latest`.
<Accordion title="Hook packs and npm specs">
`plugins install` is also the install surface for hook packs that expose `openclaw.hooks` in `package.json`. Use `openclaw hooks` for filtered hook visibility and per-hook enablement, not package installation.
Npm specs are **registry-only** (package name + optional **exact version** or **dist-tag**). Git/URL/file specs and semver ranges are rejected. Dependency installs run in one managed npm project per plugin with `--ignore-scripts` for safety, even when your shell has global npm install settings. Managed plugin npm projects inherit OpenClaw's package-level npm `overrides`, so host security pins apply to hoisted plugin dependencies too.
Npm specs are **registry-only** (package name + optional **exact version** or **dist-tag**). Git/URL/file specs and semver ranges are rejected. Dependency installs run project-local with `--ignore-scripts` for safety, even when your shell has global npm install settings. Managed plugin npm roots inherit OpenClaw's package-level npm `overrides`, so host security pins apply to hoisted plugin dependencies too.
Use `npm:<package>` when you want to make npm resolution explicit. Bare package specs also install directly from npm during the launch cutover unless they match an official plugin id.
@@ -177,8 +177,6 @@ is available, then fall back to `latest`.
Bare specs and `@latest` stay on the stable track. OpenClaw date-stamped correction versions such as `2026.5.3-1` are stable releases for this check. 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 such as `@1.2.3-beta.4`.
For npm installs without an exact version (`npm:<package>` or `npm:<package>@latest`), OpenClaw checks the resolved package metadata before install. If the latest stable package requires a newer OpenClaw plugin API or minimum host version, OpenClaw inspects older stable versions and installs the newest compatible release instead. Exact versions and explicit dist-tags such as `@beta` remain strict: if the selected package is incompatible, the command fails and asks you to upgrade OpenClaw or choose a compatible version.
If a bare install spec matches an official plugin id (for example `diffs`), OpenClaw installs the catalog entry directly. To install an npm package with the same name, use an explicit scoped spec (for example `@scope/diffs`).
</Accordion>
@@ -194,10 +192,10 @@ is available, then fall back to `latest`.
Supported archives: `.zip`, `.tgz`, `.tar.gz`, `.tar`. Native OpenClaw plugin archives must contain a valid `openclaw.plugin.json` at the extracted plugin root; archives that only contain `package.json` are rejected before OpenClaw writes install records.
Use `npm-pack:<path.tgz>` when the file is an npm-pack tarball and you want
to test the same per-plugin managed npm project path used by registry
installs, including `package-lock.json` verification, hoisted dependency
scanning, and npm install records. Plain archive paths still install as local
archives under the plugin extensions root.
to test the same managed npm-root install path used by registry installs,
including `package-lock.json` verification, hoisted dependency scanning, and
npm install records. Plain archive paths still install as local archives
under the plugin extensions root.
Claude marketplace installs are also supported.
@@ -437,7 +435,7 @@ The local plugin registry is OpenClaw's persisted cold read model for installed
Use `plugins registry` to inspect whether the persisted registry is present, current, or stale. Use `--refresh` to rebuild it from the persisted plugin index, config policy, and manifest/package metadata. This is a repair path, not a runtime activation path.
`openclaw doctor --fix` also repairs registry-adjacent managed npm drift: if an orphaned or recovered `@openclaw/*` package under a managed plugin npm project or the legacy flat managed npm root shadows a bundled plugin, doctor removes that stale package and rebuilds the registry so startup validates against the bundled manifest. Doctor also relinks the host `openclaw` package into managed npm plugins that declare `peerDependencies.openclaw`, so package-local runtime imports such as `openclaw/plugin-sdk/*` resolve after updates or npm repairs.
`openclaw doctor --fix` also repairs registry-adjacent managed npm drift: if an orphaned or recovered `@openclaw/*` package under the managed plugin npm root shadows a bundled plugin, doctor removes that stale package and rebuilds the registry so startup validates against the bundled manifest. Doctor also relinks the host `openclaw` package into managed npm plugins that declare `peerDependencies.openclaw`, so package-local runtime imports such as `openclaw/plugin-sdk/*` resolve after updates or npm repairs.
<Warning>
`OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY=1` is a deprecated break-glass compatibility switch for registry read failures. Prefer `plugins registry --refresh` or `openclaw doctor --fix`; the env fallback is only for emergency startup recovery while the migration rolls out.

View File

@@ -172,87 +172,21 @@ present in `policy.jsonc`. The observed state is existing OpenClaw config or
workspace metadata; policy reports drift but does not rewrite runtime behavior
unless a repair path is explicitly available and enabled.
Policy overlays keep broad top-level rules global, then let named scope blocks
add stricter normal policy sections for explicit selectors. A scope name is a
descriptive bucket only; matching uses the selector values inside the scope.
The overlay is additive: global claims still run, and a scoped claim can emit
its own finding against the same observed config.
#### Scoped overlays
Use `scopes.<scopeName>` when one set of agents needs stricter policy than the
top-level baseline. Scopes require the `agentIds` selector, which supports
`tools.*`, `agents.workspace.*`, and `sandbox.*`. Unsupported sections are
rejected instead of being ignored. If an `agentIds` entry is not present in
`agents.list[]`, OpenClaw evaluates the scoped rule against inherited
global/default posture for that runtime agent id.
```jsonc
{
"tools": {
"exec": {
"allowHosts": ["sandbox", "node"],
},
},
"sandbox": {
"requireMode": ["all", "non-main"],
},
"scopes": {
"release-workspace": {
"agentIds": ["release-agent", "review-agent"],
"agents": {
"workspace": {
"allowedAccess": ["none", "ro"],
},
},
},
"release-lockdown": {
"agentIds": ["release-agent"],
"tools": {
"exec": {
"allowHosts": ["sandbox"],
"allowSecurity": ["deny", "allowlist"],
"requireAsk": ["always"],
},
"denyTools": ["exec", "process", "write", "edit", "apply_patch"],
},
"sandbox": {
"requireMode": ["all"],
"allowBackends": ["docker"],
},
},
"shell-sandbox": {
"agentIds": ["shell-agent"],
"sandbox": {
"allowBackends": ["openshell"],
"containers": {
"requireReadOnlyMounts": false,
},
},
},
},
}
```
The same agent can appear in multiple scopes when each scope governs different
fields, as shown above. A repeated scoped field for the same agent must be
equally or more restrictive according to policy metadata; weaker duplicate
claims are rejected. Strictness metadata treats allow-lists as subsets,
deny-lists as supersets, and required booleans as fixed requirements.
Container posture policy is evaluated only against evidence OpenClaw can
observe for the matched agent. If an enabled `sandbox.containers.*` rule applies
to an agent whose sandbox backend cannot expose that field, policy reports
`policy/sandbox-container-posture-unobservable` instead of treating the claim as
passing. Use separate `agentIds` scopes for agent groups that use different
sandbox backends, and leave unsupported container rules unset or false for the
groups where those fields cannot be observed.
| Selector | Supported sections | Use when |
| ---------- | ------------------------------------------ | ----------------------------------------------- |
| `agentIds` | `tools`, `agents.workspace`, and `sandbox` | One or more runtime agents need stricter rules. |
Every scope present in `policy.jsonc` must be valid and enforceable.
Agent-specific policy overlays keep broad `tools.*` and `agents.workspace`
posture global, then let named scope blocks add stricter normal policy sections
for explicit `agentIds` under `scopes.<scopeName>`. The initial scoped
sections are `tools` and `agents.workspace`; sandbox and ingress can use the
same container once their evidence is attributable to an agent. Scoped fields
carry strictness metadata such as allowlist subset, denylist superset, required
boolean, and exact-list semantics so future policy-file conformance can reuse
the same rule inventory instead of guessing. The overlay is additive: global
claims still run, and a scoped claim can emit its own finding against the same
observed config. See [Agent-scoped policy overlays](/plan/policy-agent-scoped-overlays).
Every scope present in `policy.jsonc` must be valid and enforceable. Scopes
currently require `agentIds`, and that selector supports only `tools.*` and
`agents.workspace.*`. If an `agentIds` entry is not present in `agents.list[]`,
the scoped rule is evaluated against the inherited global/default posture for
that runtime agent id instead of being skipped.
#### Channels
@@ -301,23 +235,6 @@ Every scope present in `policy.jsonc` must be valid and enforceable.
| `agents.workspace.allowedAccess` | `agents.defaults.sandbox.workspaceAccess` and `agents.list[].sandbox.workspaceAccess` | Allow only sandbox workspace access values such as `none` or `ro`. |
| `agents.workspace.denyTools` | Global and per-agent tool deny config | Require workspace/runtime mutation tools such as `exec`, `process`, `write`, `edit`, or `apply_patch` to be denied. |
#### Sandbox posture
| Policy field | Observed state | Use when |
| ----------------------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------------- |
| `sandbox.requireMode` | `agents.defaults.sandbox.mode` and per-agent mode | Allow only reviewed sandbox modes such as `all` or `non-main`. |
| `sandbox.allowBackends` | `agents.defaults.sandbox.backend` and per-agent backend | Allow only reviewed sandbox backends such as `docker`. |
| `sandbox.containers.denyHostNetwork` | Container-backed sandbox/browser network mode | Deny host network mode. |
| `sandbox.containers.denyContainerNamespaceJoin` | Container-backed sandbox/browser network mode | Deny joining another container network namespace. |
| `sandbox.containers.requireReadOnlyMounts` | Container-backed sandbox/browser mount mode | Require mounts to be read-only. |
| `sandbox.containers.denyContainerRuntimeSocketMounts` | Container-backed sandbox/browser mount targets | Deny container runtime socket mounts. |
| `sandbox.containers.denyUnconfinedProfiles` | Container security profile posture | Deny unconfined container security profiles. |
| `sandbox.browser.requireCdpSourceRange` | Sandbox browser CDP source range | Require browser CDP exposure to declare a source range. |
Policy treats missing `sandbox.mode` as the implicit default `off`, so
`sandbox.requireMode` reports a fresh or unconfigured sandbox as outside an
allowlist such as `["all"]`.
#### Secrets
| Policy field | Observed state | Use when |
@@ -605,56 +522,47 @@ choose a different interval.
Policy currently verifies:
| Check id | Finding |
| ------------------------------------------------- | --------------------------------------------------------------------------------- |
| `policy/policy-jsonc-missing` | Policy is enabled but `policy.jsonc` is missing. |
| `policy/policy-jsonc-invalid` | Policy cannot be parsed or contains malformed rule entries. |
| `policy/policy-hash-mismatch` | Policy does not match configured `expectedHash`. |
| `policy/attestation-hash-mismatch` | Current policy evidence no longer matches the accepted attestation. |
| `policy/channels-denied-provider` | An enabled channel matches a channel deny rule. |
| `policy/mcp-denied-server` | A configured MCP server is denied by policy. |
| `policy/mcp-unapproved-server` | A configured MCP server is outside the allowlist. |
| `policy/models-denied-provider` | A configured model provider or model ref uses a denied provider. |
| `policy/models-unapproved-provider` | A configured model provider or model ref is outside the allowlist. |
| `policy/network-private-access-enabled` | A private-network SSRF escape hatch is enabled when policy denies it. |
| `policy/gateway-non-loopback-bind` | Gateway bind posture permits non-loopback exposure when policy denies it. |
| `policy/gateway-auth-disabled` | Gateway authentication is disabled when policy requires auth. |
| `policy/gateway-rate-limit-missing` | Gateway auth rate-limit posture is not explicit when policy requires it. |
| `policy/gateway-control-ui-insecure` | Gateway Control UI insecure exposure toggles are enabled. |
| `policy/gateway-tailscale-funnel` | Gateway Tailscale Funnel exposure is enabled when policy denies it. |
| `policy/gateway-remote-enabled` | Gateway remote mode is active when policy denies it. |
| `policy/gateway-http-endpoint-enabled` | A Gateway HTTP API endpoint is enabled while denied by policy. |
| `policy/gateway-http-url-fetch-unrestricted` | Gateway HTTP URL-fetch input lacks a required URL allowlist. |
| `policy/agents-workspace-access-denied` | Agent sandbox mode or workspace access is outside the policy allowlist. |
| `policy/agents-tool-not-denied` | An agent or default config does not deny a tool required by policy. |
| `policy/tools-profile-unapproved` | A configured global or per-agent tool profile is outside the allowlist. |
| `policy/tools-fs-workspace-only-required` | Filesystem tools are not configured with workspace-only path posture. |
| `policy/tools-exec-security-unapproved` | Exec security mode is outside the policy allowlist. |
| `policy/tools-exec-ask-unapproved` | Exec ask mode is outside the policy allowlist. |
| `policy/tools-exec-host-unapproved` | Exec host routing is outside the policy allowlist. |
| `policy/tools-elevated-enabled` | Elevated tool mode is enabled when policy denies it. |
| `policy/tools-also-allow-missing` | A configured `alsoAllow` list is missing an entry required by policy. |
| `policy/tools-also-allow-unexpected` | A configured `alsoAllow` list includes an entry not expected by policy. |
| `policy/tools-required-deny-missing` | A global or per-agent tool deny list does not include a required denied tool. |
| `policy/sandbox-mode-unapproved` | Sandbox mode is outside the policy allowlist. |
| `policy/sandbox-backend-unapproved` | Sandbox backend is outside the policy allowlist. |
| `policy/sandbox-container-posture-unobservable` | A container posture rule is enabled for a backend that cannot observe it. |
| `policy/sandbox-container-host-network-denied` | A container-backed sandbox or browser uses host network mode. |
| `policy/sandbox-container-namespace-join-denied` | A container-backed sandbox or browser joins another container namespace. |
| `policy/sandbox-container-mount-mode-required` | A container-backed sandbox or browser mount is not read-only. |
| `policy/sandbox-container-runtime-socket-mount` | A container-backed sandbox or browser mount exposes the container runtime socket. |
| `policy/sandbox-container-unconfined-profile` | Container sandbox profile is unconfined when policy denies it. |
| `policy/sandbox-browser-cdp-source-range-missing` | Sandbox browser CDP source range is missing when policy requires one. |
| `policy/secrets-unmanaged-provider` | A config SecretRef references a provider not declared under `secrets.providers`. |
| `policy/secrets-denied-provider-source` | A config secret provider or SecretRef uses a source denied by policy. |
| `policy/secrets-insecure-provider` | A secret provider opts into insecure posture when policy denies it. |
| `policy/auth-profile-invalid-metadata` | A config auth profile is missing valid provider or mode metadata. |
| `policy/auth-profile-unapproved-mode` | A config auth profile mode is outside the policy allowlist. |
| `policy/tools-missing-risk-level` | A governed tool declaration is missing risk metadata. |
| `policy/tools-unknown-risk-level` | A governed tool declaration uses an unknown risk value. |
| `policy/tools-missing-sensitivity-token` | A governed tool declaration is missing sensitivity metadata. |
| `policy/tools-missing-owner` | A governed tool declaration is missing owner metadata. |
| `policy/tools-unknown-sensitivity-token` | A governed tool declaration uses an unknown sensitivity value. |
| Check id | Finding |
| -------------------------------------------- | -------------------------------------------------------------------------------- |
| `policy/policy-jsonc-missing` | Policy is enabled but `policy.jsonc` is missing. |
| `policy/policy-jsonc-invalid` | Policy cannot be parsed or contains malformed rule entries. |
| `policy/policy-hash-mismatch` | Policy does not match configured `expectedHash`. |
| `policy/attestation-hash-mismatch` | Current policy evidence no longer matches the accepted attestation. |
| `policy/channels-denied-provider` | An enabled channel matches a channel deny rule. |
| `policy/mcp-denied-server` | A configured MCP server is denied by policy. |
| `policy/mcp-unapproved-server` | A configured MCP server is outside the allowlist. |
| `policy/models-denied-provider` | A configured model provider or model ref uses a denied provider. |
| `policy/models-unapproved-provider` | A configured model provider or model ref is outside the allowlist. |
| `policy/network-private-access-enabled` | A private-network SSRF escape hatch is enabled when policy denies it. |
| `policy/gateway-non-loopback-bind` | Gateway bind posture permits non-loopback exposure when policy denies it. |
| `policy/gateway-auth-disabled` | Gateway authentication is disabled when policy requires auth. |
| `policy/gateway-rate-limit-missing` | Gateway auth rate-limit posture is not explicit when policy requires it. |
| `policy/gateway-control-ui-insecure` | Gateway Control UI insecure exposure toggles are enabled. |
| `policy/gateway-tailscale-funnel` | Gateway Tailscale Funnel exposure is enabled when policy denies it. |
| `policy/gateway-remote-enabled` | Gateway remote mode is active when policy denies it. |
| `policy/gateway-http-endpoint-enabled` | A Gateway HTTP API endpoint is enabled while denied by policy. |
| `policy/gateway-http-url-fetch-unrestricted` | Gateway HTTP URL-fetch input lacks a required URL allowlist. |
| `policy/agents-workspace-access-denied` | Agent sandbox mode or workspace access is outside the policy allowlist. |
| `policy/agents-tool-not-denied` | An agent or default config does not deny a tool required by policy. |
| `policy/tools-profile-unapproved` | A configured global or per-agent tool profile is outside the allowlist. |
| `policy/tools-fs-workspace-only-required` | Filesystem tools are not configured with workspace-only path posture. |
| `policy/tools-exec-security-unapproved` | Exec security mode is outside the policy allowlist. |
| `policy/tools-exec-ask-unapproved` | Exec ask mode is outside the policy allowlist. |
| `policy/tools-exec-host-unapproved` | Exec host routing is outside the policy allowlist. |
| `policy/tools-elevated-enabled` | Elevated tool mode is enabled when policy denies it. |
| `policy/tools-also-allow-missing` | A configured `alsoAllow` list is missing an entry required by policy. |
| `policy/tools-also-allow-unexpected` | A configured `alsoAllow` list includes an entry not expected by policy. |
| `policy/tools-required-deny-missing` | A global or per-agent tool deny list does not include a required denied tool. |
| `policy/secrets-unmanaged-provider` | A config SecretRef references a provider not declared under `secrets.providers`. |
| `policy/secrets-denied-provider-source` | A config secret provider or SecretRef uses a source denied by policy. |
| `policy/secrets-insecure-provider` | A secret provider opts into insecure posture when policy denies it. |
| `policy/auth-profile-invalid-metadata` | A config auth profile is missing valid provider or mode metadata. |
| `policy/auth-profile-unapproved-mode` | A config auth profile mode is outside the policy allowlist. |
| `policy/tools-missing-risk-level` | A governed tool declaration is missing risk metadata. |
| `policy/tools-unknown-risk-level` | A governed tool declaration uses an unknown risk value. |
| `policy/tools-missing-sensitivity-token` | A governed tool declaration is missing sensitivity metadata. |
| `policy/tools-missing-owner` | A governed tool declaration is missing owner metadata. |
| `policy/tools-unknown-sensitivity-token` | A governed tool declaration uses an unknown sensitivity value. |
Policy findings can include both `target` and `requirement`. `target` is the
observed workspace thing that does not conform. `requirement` is the authored

View File

@@ -110,7 +110,7 @@ openclaw sessions cleanup --json
- `--dry-run`: preview how many entries would be pruned/capped without writing.
- In text mode, dry-run prints a per-session action table (`Action`, `Key`, `Age`, `Model`, `Flags`) so you can see what would be kept vs removed.
- `--enforce`: apply maintenance even when `session.maintenance.mode` is `warn`.
- `--fix-missing`: remove entries whose transcript files are missing or header-only/empty, even if they would not normally age/count out yet.
- `--fix-missing`: remove entries whose transcript files are missing, even if they would not normally age/count out yet.
- `--fix-dm-scope`: when `session.dmScope` is `main`, retire stale peer-keyed direct-DM rows left behind by earlier `per-peer`, `per-channel-peer`, or `per-account-channel-peer` routing. Use `--dry-run` first; applying the cleanup removes those rows from `sessions.json` and preserves their transcripts as deleted archives.
- `--active-key <key>`: protect a specific active key from disk-budget eviction. Durable external conversation pointers, such as group sessions and thread-scoped chat sessions, are also kept by age/count/disk-budget maintenance.
- `--agent <id>`: run cleanup for one configured agent store.

View File

@@ -14,12 +14,12 @@ the finished turn to OpenClaw.
Runtimes are easy to confuse with providers because both show up near model
configuration. They are different layers:
| Layer | Examples | What it means |
| ------------- | -------------------------------------------- | ------------------------------------------------------------------- |
| Provider | `openai`, `anthropic`, `openai-codex` | How OpenClaw authenticates, discovers models, and names model refs. |
| Model | `gpt-5.5`, `claude-opus-4-6` | The model selected for the agent turn. |
| Agent runtime | `openclaw`, `codex`, `copilot`, `claude-cli` | The low level loop or backend that executes the prepared turn. |
| Channel | Telegram, Discord, Slack, WhatsApp | Where messages enter and leave OpenClaw. |
| Layer | Examples | What it means |
| ------------- | ------------------------------------- | ------------------------------------------------------------------- |
| Provider | `openai`, `anthropic`, `openai-codex` | How OpenClaw authenticates, discovers models, and names model refs. |
| Model | `gpt-5.5`, `claude-opus-4-6` | The model selected for the agent turn. |
| Agent runtime | `openclaw`, `codex`, `claude-cli` | The low level loop or backend that executes the prepared turn. |
| Channel | Telegram, Discord, Slack, WhatsApp | Where messages enter and leave OpenClaw. |
You will also see the word **harness** in code. A harness is the implementation
that provides an agent runtime. For example, the bundled Codex harness
@@ -33,17 +33,13 @@ There are two runtime families:
- **Embedded harnesses** run inside OpenClaw's prepared agent loop. Today this
is the built-in `openclaw` runtime plus registered plugin harnesses such as
`codex` and `copilot`.
`codex`.
- **CLI backends** run a local CLI process while keeping the model ref
canonical. For example, `anthropic/claude-opus-4-7` with
a model-scoped `agentRuntime.id: "claude-cli"` means "select the Anthropic
model, execute through Claude CLI." `claude-cli` is not an embedded harness id
and must not be passed to AgentHarness selection.
The `copilot` harness is a separate, opt-in plugin harness for the
GitHub Copilot CLI; see [GitHub Copilot agent runtime](/plugins/copilot)
for the user-facing decision between PI, Codex, and GitHub Copilot agent runtime.
## Codex surfaces
Most confusion comes from several different surfaces sharing the Codex name:
@@ -205,34 +201,6 @@ If `openclaw doctor` warns that the `codex` plugin is enabled while
`openai-codex/*` remains in config, treat that as legacy route state. Run
`openclaw doctor --fix` to rewrite it to `openai/*` with the Codex runtime.
## GitHub Copilot agent runtime
The bundled `copilot` extension registers an opt-in `copilot` runtime
backed by the GitHub Copilot CLI (`@github/copilot-sdk`). It claims the
canonical subscription `github-copilot` provider and is **never** selected by
`auto`. Opt in per-model or per-provider via `agentRuntime.id`:
```json5
{
agents: {
defaults: {
model: "github-copilot/gpt-5.5",
models: {
"github-copilot/gpt-5.5": {
agentRuntime: { id: "copilot" },
},
},
},
},
}
```
The harness claims its provider, runtime, CLI session key, and auth profile
prefix in `extensions/copilot/doctor-contract-api.ts`, which
`openclaw doctor` auto-loads. For configuration, auth, transcript mirroring,
compaction, the doctor probe surface, and the broader PI vs Codex vs Copilot
SDK decision, see [GitHub Copilot agent runtime](/plugins/copilot).
## Compatibility contract
When a runtime is not OpenClaw, it should document what OpenClaw surfaces it supports.
@@ -268,7 +236,6 @@ runtime policy first. Legacy session runtime pins no longer decide routing.
- [Codex harness](/plugins/codex-harness)
- [Codex harness runtime](/plugins/codex-harness-runtime)
- [GitHub Copilot agent runtime](/plugins/copilot)
- [OpenAI](/providers/openai)
- [Agent harness plugins](/plugins/sdk-agent-harness)
- [Agent loop](/concepts/agent-loop)

View File

@@ -65,10 +65,6 @@ OpenClaw loads skills from these locations (highest precedence first):
- Bundled (shipped with the install)
- Extra skill folders: `skills.load.extraDirs`
Skill roots can contain grouped folders such as
`<workspace>/skills/personal/foo/SKILL.md`; the skill is still exposed by its
flat frontmatter name, for example `foo`.
Skills can be gated by config/env (see `skills` in [Gateway configuration](/gateway/configuration)).
## Runtime boundaries

View File

@@ -251,20 +251,6 @@ Native Codex and OpenClaw embedded agent runs satisfy `assemble-before-prompt`.
Generic CLI backends do not, so engines that require it are rejected before the
CLI process starts.
### Failure isolation
OpenClaw isolates the selected plugin engine from the core reply path. If a
non-legacy engine is missing, fails contract validation, throws during factory
creation, or throws from a lifecycle method, OpenClaw quarantines that engine
for the current Gateway process and downgrades context-engine work to the
built-in `legacy` engine. The error is logged with the failed operation so the
operator can repair, update, or disable the plugin without the agent going
silent.
Host requirement failures are different: when an engine declares that a runtime
lacks a required capability, OpenClaw fails closed before starting the run. That
protects engines that would corrupt state if they ran in an unsupported host.
### ownsCompaction
`ownsCompaction` controls whether OpenClaw runtime's built-in in-attempt auto-compaction stays enabled for the run:
@@ -335,7 +321,7 @@ The slot is exclusive at run time - only one registered context engine is resolv
- 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 the selected plugin engine is quarantined for the current Gateway process. OpenClaw falls back to `legacy` for user turns so replies can continue, but you should still repair, update, disable, or uninstall the broken plugin.
- 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.
## Related

View File

@@ -229,16 +229,13 @@ All settings live under `plugins.entries.memory-core.config.dreaming`.
<ParamField path="model" type="string">
Optional Dream Diary subagent model override. Use a canonical `provider/model` value when also setting a subagent `allowedModels` allowlist.
</ParamField>
<ParamField path="phases.deep.maxPromotedSnippetTokens" type="number" default="160">
Maximum estimated token count kept from each short-term recall snippet promoted into `MEMORY.md`. Ranking provenance remains visible.
</ParamField>
<Warning>
`dreaming.model` requires `plugins.entries.memory-core.subagent.allowModelOverride: true`. To restrict it, also set `plugins.entries.memory-core.subagent.allowedModels`. Trust or allowlist failures stay visible instead of falling back silently; the retry only covers model-unavailable errors.
</Warning>
<Note>
Most phase policy, thresholds, and storage behavior are internal implementation details. See [Memory configuration reference](/reference/memory-config#dreaming) for the full key list.
Phase policy, thresholds, and storage behavior are internal implementation details (not user-facing config). See [Memory configuration reference](/reference/memory-config#dreaming) for the full key list.
</Note>
## Dreams UI

View File

@@ -290,32 +290,32 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
### Other bundled provider plugins
| Provider | Id | Auth env | Example model |
| ----------------------- | -------------------------------- | ------------------------------------------------------------ | -------------------------------------------------- |
| BytePlus | `byteplus` / `byteplus-plan` | `BYTEPLUS_API_KEY` | `byteplus-plan/ark-code-latest` |
| Cerebras | `cerebras` | `CEREBRAS_API_KEY` | `cerebras/zai-glm-4.7` |
| Cloudflare AI Gateway | `cloudflare-ai-gateway` | `CLOUDFLARE_AI_GATEWAY_API_KEY` | - |
| DeepInfra | `deepinfra` | `DEEPINFRA_API_KEY` | `deepinfra/deepseek-ai/DeepSeek-V4-Flash` |
| DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-v4-flash` |
| GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` | - |
| Groq | `groq` | `GROQ_API_KEY` | - |
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |
| Kilo Gateway | `kilocode` | `KILOCODE_API_KEY` | `kilocode/kilo/auto` |
| Kimi Coding | `kimi` | `KIMI_API_KEY` or `KIMICODE_API_KEY` | `kimi/kimi-for-coding` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M2.7` |
| Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` |
| Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` |
| NVIDIA | `nvidia` | `NVIDIA_API_KEY` | `nvidia/nvidia/nemotron-3-super-120b-a12b` |
| OpenRouter | `openrouter` | `OPENROUTER_API_KEY` | `openrouter/auto` |
| Qianfan | `qianfan` | `QIANFAN_API_KEY` | `qianfan/deepseek-v3.2` |
| Qwen Cloud | `qwen` | `QWEN_API_KEY` / `MODELSTUDIO_API_KEY` / `DASHSCOPE_API_KEY` | `qwen/qwen3.5-plus` |
| StepFun | `stepfun` / `stepfun-plan` | `STEPFUN_API_KEY` | `stepfun/step-3.5-flash` |
| Together | `together` | `TOGETHER_API_KEY` | `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` |
| Venice | `venice` | `VENICE_API_KEY` | - |
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | `xai/grok-4.3` |
| Xiaomi | `xiaomi` | `XIAOMI_API_KEY` | `xiaomi/mimo-v2-flash` |
| Provider | Id | Auth env | Example model |
| ----------------------- | -------------------------------- | ------------------------------------------------------------ | --------------------------------------------- |
| BytePlus | `byteplus` / `byteplus-plan` | `BYTEPLUS_API_KEY` | `byteplus-plan/ark-code-latest` |
| Cerebras | `cerebras` | `CEREBRAS_API_KEY` | `cerebras/zai-glm-4.7` |
| Cloudflare AI Gateway | `cloudflare-ai-gateway` | `CLOUDFLARE_AI_GATEWAY_API_KEY` | - |
| DeepInfra | `deepinfra` | `DEEPINFRA_API_KEY` | `deepinfra/deepseek-ai/DeepSeek-V4-Flash` |
| DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-v4-flash` |
| GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` | - |
| Groq | `groq` | `GROQ_API_KEY` | - |
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |
| Kilo Gateway | `kilocode` | `KILOCODE_API_KEY` | `kilocode/kilo/auto` |
| Kimi Coding | `kimi` | `KIMI_API_KEY` or `KIMICODE_API_KEY` | `kimi/kimi-for-coding` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M2.7` |
| Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` |
| Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` |
| NVIDIA | `nvidia` | `NVIDIA_API_KEY` | `nvidia/nvidia/nemotron-3-super-120b-a12b` |
| OpenRouter | `openrouter` | `OPENROUTER_API_KEY` | `openrouter/auto` |
| Qianfan | `qianfan` | `QIANFAN_API_KEY` | `qianfan/deepseek-v3.2` |
| Qwen Cloud | `qwen` | `QWEN_API_KEY` / `MODELSTUDIO_API_KEY` / `DASHSCOPE_API_KEY` | `qwen/qwen3.5-plus` |
| StepFun | `stepfun` / `stepfun-plan` | `STEPFUN_API_KEY` | `stepfun/step-3.5-flash` |
| Together | `together` | `TOGETHER_API_KEY` | `together/moonshotai/Kimi-K2.5` |
| Venice | `venice` | `VENICE_API_KEY` | - |
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | `xai/grok-4.3` |
| Xiaomi | `xiaomi` | `XIAOMI_API_KEY` | `xiaomi/mimo-v2-flash` |
#### Quirks worth knowing

View File

@@ -23,7 +23,7 @@ sidebarTitle: "Models CLI"
</Card>
</CardGroup>
Model refs choose a provider and model. They do not usually choose the low-level agent runtime. OpenAI agent refs are the main exception: `openai/gpt-5.5` runs through the Codex app-server runtime by default on the official OpenAI provider. Subscription Copilot refs (`github-copilot/*`) can additionally be opted into the bundled GitHub Copilot agent runtime — that path stays explicit (no `auto` fallback). Explicit runtime overrides belong on provider/model policy, not on the whole agent or session. In Codex runtime mode, the `openai/gpt-*` ref does not imply API-key billing; auth can come from a Codex account or `openai-codex` auth profile. See [Agent runtimes](/concepts/agent-runtimes) and [GitHub Copilot agent runtime](/plugins/copilot).
Model refs choose a provider and model. They do not usually choose the low-level agent runtime. OpenAI agent refs are the main exception: `openai/gpt-5.5` runs through the Codex app-server runtime by default on the official OpenAI provider. Explicit runtime overrides belong on provider/model policy, not on the whole agent or session. In Codex runtime mode, the `openai/gpt-*` ref does not imply API-key billing; auth can come from a Codex account or `openai-codex` auth profile. See [Agent runtimes](/concepts/agent-runtimes).
## How model selection works
@@ -340,7 +340,7 @@ When live probes run in a TTY, you can select fallbacks interactively. In non-in
## Models registry (`models.json`)
Custom providers in `models.providers` are written into `models.json` under the agent directory (default `~/.openclaw/agents/<agentId>/agent/models.json`). Provider-plugin catalogs are stored as generated plugin-owned catalog shards under the agent's plugin state and loaded automatically. This file is merged by default unless `models.mode` is set to `replace`.
Custom providers in `models.providers` are written into `models.json` under the agent directory (default `~/.openclaw/agents/<agentId>/agent/models.json`). This file is merged by default unless `models.mode` is set to `replace`.
<AccordionGroup>
<Accordion title="Merge mode precedence">

View File

@@ -665,48 +665,6 @@ pnpm openclaw qa slack \
A green run completes in well under 30 seconds and `slack-qa-report.md` shows both `slack-canary` and `slack-mention-gating` at status `pass`. If the lane hangs for ~90 seconds and exits with `Convex credential pool exhausted for kind "slack"`, either the pool is empty or every row is leased - `qa credentials list --kind slack --status all --json` will tell you which.
### WhatsApp QA
```bash
pnpm openclaw qa whatsapp
```
Targets two dedicated WhatsApp Web accounts: a driver account controlled by
the harness and a SUT account started by the child OpenClaw gateway through the
bundled WhatsApp plugin.
Required env when `--credential-source env`:
- `OPENCLAW_QA_WHATSAPP_DRIVER_PHONE_E164`
- `OPENCLAW_QA_WHATSAPP_SUT_PHONE_E164`
- `OPENCLAW_QA_WHATSAPP_DRIVER_AUTH_ARCHIVE_BASE64`
- `OPENCLAW_QA_WHATSAPP_SUT_AUTH_ARCHIVE_BASE64`
Optional:
- `OPENCLAW_QA_WHATSAPP_GROUP_JID` enables `whatsapp-mention-gating`.
- `OPENCLAW_QA_WHATSAPP_CAPTURE_CONTENT=1` keeps message bodies in
observed-message artifacts.
Scenarios (`extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.runtime.ts`):
- `whatsapp-canary`
- `whatsapp-pairing-block`
- `whatsapp-mention-gating`
- `whatsapp-approval-exec-native` - opt-in native WhatsApp exec approval
scenario. Requests an exec approval through the gateway, verifies the
WhatsApp message has native reaction approval affordances, resolves it, and
verifies the resolved WhatsApp follow-up.
- `whatsapp-approval-plugin-native` - opt-in native WhatsApp plugin approval
scenario. Enables exec and plugin approval forwarding together, then verifies
the same pending/resolved native WhatsApp path.
Output artifacts:
- `whatsapp-qa-report.md`
- `whatsapp-qa-summary.json`
- `whatsapp-qa-observed-messages.json` - bodies redacted unless `OPENCLAW_QA_WHATSAPP_CAPTURE_CONTENT=1`.
### Convex credential pool
Telegram, Discord, Slack, and WhatsApp lanes can lease credentials from a shared Convex pool instead of reading the env vars above. Pass `--credential-source convex` (or set `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`); QA Lab acquires an exclusive lease, heartbeats it for the duration of the run, and releases it on shutdown. Pool kinds are `"telegram"`, `"discord"`, `"slack"`, and `"whatsapp"`.

View File

@@ -258,10 +258,6 @@ prompt instructs the model to use `read` to load the SKILL.md at the listed
location (workspace, managed, or bundled). If no skills are eligible, the
Skills section is omitted.
The location can point at a nested skill, such as
`skills/personal/foo/SKILL.md`. Nesting is only organizational; the prompt still
uses the flat skill name from `SKILL.md` frontmatter.
Eligibility includes skill metadata gates, runtime environment/config checks,
and the effective agent skill allowlist when `agents.defaults.skills` or
`agents.list[].skills` is configured.

View File

@@ -53,8 +53,8 @@ Authoritative advertised **discovery** inventory lives in
## Where the schemas live
- Source: `packages/gateway-protocol/src/schema.ts`
- Runtime validators (AJV): `packages/gateway-protocol/src/index.ts`
- Source: `src/gateway/protocol/schema.ts`
- Runtime validators (AJV): `src/gateway/protocol/index.ts`
- Advertised feature/discovery registry: `src/gateway/server-methods-list.ts`
- Server handshake + method dispatch: `src/gateway/server.impl.ts`
- Node client: `src/gateway/client.ts`
@@ -195,7 +195,7 @@ Example: add a new `system.echo` request that returns `{ ok: true, text }`.
1. **Schema (source of truth)**
Add to `packages/gateway-protocol/src/schema.ts`:
Add to `src/gateway/protocol/schema.ts`:
```ts
export const SystemEchoParamsSchema = Type.Object(
@@ -223,7 +223,7 @@ export type SystemEchoResult = Static<typeof SystemEchoResultSchema>;
2. **Validation**
In `packages/gateway-protocol/src/index.ts`, export an AJV validator:
In `src/gateway/protocol/index.ts`, export an AJV validator:
```ts
export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);
@@ -272,7 +272,7 @@ Unknown frame types are preserved as raw payloads for forward compatibility.
## Versioning + compatibility
- `PROTOCOL_VERSION` lives in `packages/gateway-protocol/src/version.ts`.
- `PROTOCOL_VERSION` lives in `src/gateway/protocol/version.ts`.
- Clients send `minProtocol` + `maxProtocol`; the server rejects ranges that
do not include its current protocol.
- The Swift models keep unknown frame types to avoid breaking older clients.

View File

@@ -1554,7 +1554,6 @@
"gateway/security/index",
"gateway/security/exposure-runbook",
"gateway/security/secure-file-operations",
"gateway/security/shrinkwrap",
"gateway/security/audit-checks",
"gateway/operator-scopes",
"gateway/sandboxing",
@@ -1816,7 +1815,6 @@
"pages": [
"reference/RELEASING",
"reference/full-release-validation",
"reference/release-performance-sweep",
"reference/test",
"ci",
"help/scripts"

View File

@@ -562,6 +562,20 @@ Optional CLI backends for text-only fallback runs (no tool calls). Useful as a b
first compaction summary exists. Auth profile or credential-epoch changes
still never raw-reseed.
### `agents.defaults.systemPromptOverride`
Replace the entire OpenClaw-assembled system prompt with a fixed string. Set at the default level (`agents.defaults.systemPromptOverride`) or per agent (`agents.list[].systemPromptOverride`). Per-agent values take precedence; an empty or whitespace-only value is ignored. Useful for controlled prompt experiments.
```json5
{
agents: {
defaults: {
systemPromptOverride: "You are a helpful assistant.",
},
},
}
```
### `agents.defaults.promptOverlays`
Provider-independent prompt overlays applied by model family on OpenClaw-assembled prompt surfaces. GPT-5-family model ids receive the shared behavior contract across OpenClaw/provider routes; `personality` controls only the friendly interaction-style layer. Native Codex app-server routes keep Codex-owned base/model instructions instead of this OpenClaw GPT-5 overlay, and OpenClaw disables Codex's built-in personality for native threads.
@@ -1056,7 +1070,7 @@ for provider examples and precedence.
params: { cacheRetention: "none" }, // overrides matching defaults.models params by key
tts: {
providers: {
elevenlabs: { speakerVoiceId: "EXAVITQu4vr4xnSDxMaL" },
elevenlabs: { voiceId: "EXAVITQu4vr4xnSDxMaL" },
},
},
skills: ["docs-search"], // replaces agents.defaults.skills when set
@@ -1415,7 +1429,7 @@ Batches rapid text-only messages from the same sender into a single agent turn.
elevenlabs: {
apiKey: "elevenlabs_api_key",
baseUrl: "https://api.elevenlabs.io",
speakerVoiceId: "voice_id",
voiceId: "voice_id",
modelId: "eleven_multilingual_v2",
seed: 42,
applyTextNormalization: "auto",
@@ -1429,7 +1443,7 @@ Batches rapid text-only messages from the same sender into a single agent turn.
},
},
microsoft: {
speakerVoice: "en-US-AvaMultilingualNeural",
voice: "en-US-AvaMultilingualNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
},
@@ -1437,7 +1451,7 @@ Batches rapid text-only messages from the same sender into a single agent turn.
apiKey: "openai_api_key",
baseUrl: "https://api.openai.com/v1",
model: "gpt-4o-mini-tts",
speakerVoice: "alloy",
voice: "alloy",
},
},
},
@@ -1465,7 +1479,7 @@ Defaults for Talk mode (macOS/iOS/Android).
provider: "elevenlabs",
providers: {
elevenlabs: {
speakerVoiceId: "elevenlabs_voice_id",
voiceId: "elevenlabs_voice_id",
voiceAliases: {
Clawd: "EXAVITQu4vr4xnSDxMaL",
Roger: "CwhRBWXzGAHq8TQ4Fs17",
@@ -1489,7 +1503,7 @@ Defaults for Talk mode (macOS/iOS/Android).
providers: {
openai: {
model: "gpt-realtime-2",
speakerVoice: "cedar",
voice: "cedar",
},
},
instructions: "Speak warmly and keep answers brief.",

View File

@@ -488,8 +488,7 @@ Configuring a custom/local provider `baseUrl` is also the narrow network trust d
- 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.
- Matching model `contextTokens` preserves an explicit runtime cap when present; use it to limit effective context without changing native model metadata.
- Provider-plugin catalogs are stored as generated plugin-owned catalog shards under the agent's plugin state.
- Use `models.mode: "replace"` when you want config to fully rewrite `models.json` and active plugin catalog shards.
- 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.
</Accordion>

View File

@@ -1268,11 +1268,11 @@ Current builds no longer include the TCP bridge. Nodes connect over the Gateway
}
```
- `maxAttempts`: maximum retries for cron jobs on transient errors (default: `3`; range: `0`-`10`).
- `maxAttempts`: maximum retries for one-shot jobs on transient errors (default: `3`; range: `0`-`10`).
- `backoffMs`: array of backoff delays in ms for each retry attempt (default: `[30000, 60000, 300000]`; 1-10 entries).
- `retryOn`: error types that trigger retries - `"rate_limit"`, `"overloaded"`, `"network"`, `"timeout"`, `"server_error"`. Omit to retry all transient types.
One-shot jobs stay enabled until retry attempts are exhausted, then disable while keeping the final error state. Recurring jobs use the same transient retry policy to run again after backoff before their next scheduled slot; permanent errors or exhausted transient retries fall back to the normal recurring schedule with error backoff.
Applies only to one-shot cron jobs. Recurring jobs use separate failure handling.
### `cron.failureAlert`

View File

@@ -264,7 +264,6 @@ That stages grounded durable candidates into the short-term dreaming store while
- `routing.transcribeAudio` → `tools.media.audio.models`
- `messages.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `messages.tts.providers.<provider>`
- `messages.tts.provider: "edge"` and `messages.tts.providers.edge` → `messages.tts.provider: "microsoft"` and `messages.tts.providers.microsoft`
- TTS speaker selection fields (`voice`/`voiceName`/`voiceId`) → `speakerVoice`/`speakerVoiceId`
- `channels.discord.voice.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `channels.discord.voice.tts.providers.<provider>`
- `channels.discord.accounts.<id>.voice.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `channels.discord.accounts.<id>.voice.tts.providers.<provider>`
- `plugins.entries.voice-call.config.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `plugins.entries.voice-call.config.tts.providers.<provider>`

View File

@@ -175,9 +175,9 @@ Current behavior:
rasterized into images and passed to the model, and the injected file block uses
the placeholder `[PDF content rendered to images]`.
PDF parsing is provided by the bundled `document-extract` plugin, which uses
`clawpdf` and its packaged PDFium WebAssembly runtime for text extraction and
page rendering.
PDF parsing is provided by the bundled `document-extract` plugin, which uses the
Node-friendly `pdfjs-dist` legacy build (no worker). The modern PDF.js build
expects browser workers/DOM globals, so it is not used in the Gateway.
URL fetch defaults:

View File

@@ -104,7 +104,7 @@ within their overall connection budget instead of surfacing it as a terminal
handshake failure.
`server`, `features`, `snapshot`, and `policy` are all required by the schema
(`packages/gateway-protocol/src/schema/frames.ts`). `auth` is also required and reports
(`src/gateway/protocol/schema/frames.ts`). `auth` is also required and reports
the negotiated role/scopes. `pluginSurfaceUrls` is optional and maps plugin
surface names, such as `canvas`, to scoped hosted URLs.
@@ -347,11 +347,9 @@ enumeration of `src/gateway/server-methods/*.ts`.
- `models.list` returns the runtime-allowed model catalog. Pass `{ "view": "configured" }` for picker-sized configured models (`agents.defaults.models` first, then `models.providers.*.models`), or `{ "view": "all" }` for the full catalog.
- `usage.status` returns provider usage windows/remaining quota summaries.
- `usage.cost` returns aggregated cost usage summaries for a date range.
Pass `agentId` for one agent, or `agentScope: "all"` to aggregate configured agents.
- `doctor.memory.status` returns vector-memory / cached embedding readiness for the active default agent workspace. Pass `{ "probe": true }` or `{ "deep": true }` only when the caller explicitly wants a live embedding provider ping.
- `doctor.memory.remHarness` returns a bounded, read-only REM harness preview for remote control-plane clients. It can include workspace paths, memory snippets, rendered grounded markdown, and deep promotion candidates, so callers need `operator.read`.
- `sessions.usage` returns per-session usage summaries. Pass `agentId` for one
agent, or `agentScope: "all"` to list configured agents together.
- `sessions.usage` returns per-session usage summaries.
- `sessions.usage.timeseries` returns timeseries usage for one session.
- `sessions.usage.logs` returns usage log entries for one session.
@@ -564,14 +562,8 @@ terminal summary, and sanitized error text.
- `sessionKey` is required.
- The gateway derives trusted runtime context from the session server-side instead of accepting
caller-supplied auth or delivery context.
- The response is a session-scoped server-derived projection of the active inventory,
including core, plugin, channel, and already-discovered MCP server tools.
- `tools.effective` is read-only for MCP: it may project a warm session MCP catalog through the
final tool policy, but it does not create MCP runtimes, connect transports, or issue
`tools/list`. If no matching warm catalog exists, the response may include a notice such as
`mcp-not-yet-connected`, `mcp-not-yet-listed`, or `mcp-stale-catalog`.
- Effective tool entries use `source="core"`, `source="plugin"`, `source="channel"`, or
`source="mcp"`.
- The response is session-scoped and reflects what the active conversation can use right now,
including core, plugin, and channel tools.
- Operators may call `tools.invoke` (`operator.write`) to invoke one available tool through the
same gateway policy path as `/tools/invoke`.
- `name` is required. `args`, `sessionKey`, `agentId`, `confirm`, and
@@ -648,7 +640,7 @@ terminal summary, and sanitized error text.
## Versioning
- `PROTOCOL_VERSION` lives in `packages/gateway-protocol/src/version.ts`.
- `PROTOCOL_VERSION` lives in `src/gateway/protocol/version.ts`.
- Clients send `minProtocol` + `maxProtocol`; the server rejects ranges that
do not include its current protocol. Current clients and servers require
protocol v4.
@@ -664,8 +656,8 @@ stable across protocol v4 and are the expected baseline for third-party clients.
| Constant | Default | Source |
| ----------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `PROTOCOL_VERSION` | `4` | `packages/gateway-protocol/src/version.ts` |
| `MIN_CLIENT_PROTOCOL_VERSION` | `4` | `packages/gateway-protocol/src/version.ts` |
| `PROTOCOL_VERSION` | `4` | `src/gateway/protocol/version.ts` |
| `MIN_CLIENT_PROTOCOL_VERSION` | `4` | `src/gateway/protocol/version.ts` |
| Request timeout (per RPC) | `30_000` ms | `src/gateway/client.ts` (`requestTimeoutMs`) |
| Preauth / connect-challenge timeout | `15_000` ms | `src/gateway/handshake-timeouts.ts` (config/env can raise the paired server/client budget) |
| Initial reconnect backoff | `1_000` ms | `src/gateway/client.ts` (`backoffMs`) |
@@ -818,7 +810,7 @@ Migration target:
This protocol exposes the **full gateway API** (status, channels, models, chat,
agent, sessions, nodes, approvals, etc.). The exact surface is defined by the
TypeBox schemas in `packages/gateway-protocol/src/schema.ts`.
TypeBox schemas in `src/gateway/protocol/schema.ts`.
## Related

View File

@@ -63,11 +63,67 @@ OpenClaw source checkouts use `pnpm-lock.yaml`. The published `openclaw` npm
package and OpenClaw-owned npm plugin packages include `npm-shrinkwrap.json`,
npm's publishable dependency lockfile, so package installs use the reviewed
transitive dependency graph from the release instead of resolving a fresh graph
at install time.
at install time. Suitable OpenClaw-owned npm plugin packages can also publish
with explicit `bundledDependencies`, so their runtime dependency files are
carried in the plugin tarball instead of depending only on install-time
resolution.
Shrinkwrap is a supply-chain hardening and release reproducibility boundary,
not a sandbox. For the plain-English model, maintainer commands, and package
inspection checks, see [npm shrinkwrap](/gateway/security/shrinkwrap).
This is a supply-chain hardening measure:
- release installs are more reproducible;
- transitive dependency updates become visible review surfaces;
- the package tarball contains the dependency graph that release validators
checked;
- suitable OpenClaw-owned plugin tarballs contain the dependency files from
that graph;
- `package-lock.json` stays out of the published package, because npm does not
treat it as the publishable lock contract.
Shrinkwrap is not a sandbox and does not make every dependency trustworthy. It
does not replace `openclaw security audit`, host isolation, npm provenance,
signature/audit checks, or `--ignore-scripts` install smoke tests when those are
appropriate. Treat it as a release reproducibility and review-control boundary.
Maintainers should update and verify shrinkwrap whenever the root package or an
OpenClaw-owned published plugin package changes its published dependency graph:
```bash
pnpm deps:shrinkwrap:generate
pnpm deps:shrinkwrap:check
```
The generator resolves npm's publishable lock format but rejects generated
package versions that are not already present in `pnpm-lock.yaml`, preserving
the pnpm dependency age, override, and patch review boundary.
Use `pnpm deps:shrinkwrap:root:generate` and
`pnpm deps:shrinkwrap:root:check` only when you intentionally want to refresh
the root `openclaw` package without touching plugin packages.
Review `pnpm-lock.yaml`, `npm-shrinkwrap.json`, bundled plugin dependency
payloads, and any `package-lock.json` diff as security-sensitive. The package
validators require shrinkwrap in new root package tarballs and the plugin npm
publish path checks plugin-local shrinkwrap, installs package-local bundled
dependencies, and then packs or publishes. Package validators reject
`package-lock.json`.
To inspect a published package:
```bash
npm pack openclaw@<version> --json --pack-destination /tmp/openclaw-pack
tar -tf /tmp/openclaw-pack/openclaw-<version>.tgz | grep '^package/npm-shrinkwrap.json$'
```
To inspect an OpenClaw-owned plugin package, replace the package spec and check
the same tar entry:
```bash
npm pack @openclaw/discord@<version> --json --pack-destination /tmp/openclaw-plugin-pack
tar -tf /tmp/openclaw-plugin-pack/openclaw-discord-<version>.tgz | grep '^package/npm-shrinkwrap.json$'
tar -tf /tmp/openclaw-plugin-pack/openclaw-discord-<version>.tgz | grep '^package/node_modules/'
```
Background: [npm-shrinkwrap.json](https://docs.npmjs.com/cli/v11/configuring-npm/npm-shrinkwrap-json).
### Deployment and host trust

View File

@@ -1,111 +0,0 @@
---
summary: "Plain-English and technical explanation of npm shrinkwrap in OpenClaw releases"
read_when:
- You want to know what npm shrinkwrap means in an OpenClaw release
- You are reviewing package lockfiles, dependency changes, or supply-chain risk
- You are validating root or plugin npm packages before publishing
title: "npm shrinkwrap"
---
OpenClaw source checkouts use `pnpm-lock.yaml`. Published OpenClaw npm
packages use `npm-shrinkwrap.json`, npm's publishable dependency lockfile, so
package installs use the dependency graph reviewed during release.
## The easy version
Shrinkwrap is a receipt for the dependency tree that ships with an npm package.
It tells npm which exact transitive package versions to install.
For OpenClaw releases, that means:
- the published package does not ask npm to invent a fresh dependency graph at
install time;
- dependency changes become easier to review because they appear in a lockfile;
- release validation can test the same graph users will install;
- package-size or native-dependency surprises are easier to spot before
publishing.
Shrinkwrap is not a sandbox. It does not make a dependency safe by itself, and
it does not replace host isolation, `openclaw security audit`, package
provenance, or install smoke tests.
The short mental model:
| File | Where it matters | What it means |
| --------------------- | ------------------------ | --------------------------------- |
| `pnpm-lock.yaml` | OpenClaw source checkout | Maintainer dependency graph |
| `npm-shrinkwrap.json` | Published npm package | npm install graph for users |
| `package-lock.json` | Local npm apps | Not the OpenClaw publish contract |
## Why OpenClaw uses it
OpenClaw is a gateway, plugin host, model router, and agent runtime. A default
install can affect startup time, disk use, native package downloads, and
supply-chain exposure.
Shrinkwrap gives release review a stable boundary:
- reviewers can see transitive dependency movement;
- package validators can reject unexpected lockfile drift;
- package acceptance can test installs with the graph that will ship;
- plugin packages can carry their own locked dependency graph instead of
relying on the root package to own plugin-only dependencies.
The goal is not "more lockfiles." The goal is reproducible release installs
with clear ownership.
## Technical details
The root `openclaw` npm package and OpenClaw-owned npm plugin packages include
`npm-shrinkwrap.json` when they publish. Suitable OpenClaw-owned plugin
packages can also publish with explicit `bundledDependencies`, so their runtime
dependency files are carried in the plugin tarball instead of depending only on
install-time resolution.
Maintain the boundary like this:
```bash
pnpm deps:shrinkwrap:generate
pnpm deps:shrinkwrap:check
```
The generator resolves npm's publishable lock format but rejects generated
package versions that are not already present in `pnpm-lock.yaml`. That keeps
the pnpm dependency age, override, and patch-review boundary intact.
Use root-only commands only when intentionally refreshing the root package
without touching plugin packages:
```bash
pnpm deps:shrinkwrap:root:generate
pnpm deps:shrinkwrap:root:check
```
Review these files as security-sensitive:
- `pnpm-lock.yaml`
- `npm-shrinkwrap.json`
- bundled plugin dependency payloads
- any `package-lock.json` diff
OpenClaw package validators require shrinkwrap in new root package tarballs.
The plugin npm publish path checks plugin-local shrinkwrap, installs
package-local bundled dependencies, and then packs or publishes. Package
validators reject `package-lock.json` for published OpenClaw packages.
To inspect a published root package:
```bash
npm pack openclaw@<version> --json --pack-destination /tmp/openclaw-pack
tar -tf /tmp/openclaw-pack/openclaw-<version>.tgz | grep '^package/npm-shrinkwrap.json$'
```
To inspect an OpenClaw-owned plugin package:
```bash
npm pack @openclaw/discord@<version> --json --pack-destination /tmp/openclaw-plugin-pack
tar -tf /tmp/openclaw-plugin-pack/openclaw-discord-<version>.tgz | grep '^package/npm-shrinkwrap.json$'
tar -tf /tmp/openclaw-plugin-pack/openclaw-discord-<version>.tgz | grep '^package/node_modules/'
```
Background: [npm-shrinkwrap.json](https://docs.npmjs.com/cli/v11/configuring-npm/npm-shrinkwrap-json).

View File

@@ -71,13 +71,12 @@ Live tests are split into two layers so we can isolate failures:
- Run a small completion per model (and targeted regressions where needed)
- How to enable:
- `pnpm test:live` (or `OPENCLAW_LIVE_TEST=1` if invoking Vitest directly)
- Set `OPENCLAW_LIVE_MODELS=modern`, `small`, or `all` (alias for modern) to actually run this suite; otherwise it skips to keep `pnpm test:live` focused on gateway smoke
- Set `OPENCLAW_LIVE_MODELS=modern` (or `all`, alias for modern) to actually run this suite; otherwise it skips to keep `pnpm test:live` focused on gateway smoke
- How to select models:
- `OPENCLAW_LIVE_MODELS=modern` to run the modern allowlist (Opus/Sonnet 4.6+, GPT-5.2 + Codex, Gemini 3, DeepSeek V4, GLM 4.7, MiniMax M2.7, Grok 4.3)
- `OPENCLAW_LIVE_MODELS=small` to run the constrained small-model allowlist (Qwen 8B/9B local-compatible routes, OpenRouter Qwen/GLM, and Z.AI GLM)
- `OPENCLAW_LIVE_MODELS=all` is an alias for the modern allowlist
- or `OPENCLAW_LIVE_MODELS="openai/gpt-5.5,openai-codex/gpt-5.5,anthropic/claude-opus-4-6,..."` (comma allowlist)
- Modern/all and small sweeps default to their curated caps; set `OPENCLAW_LIVE_MAX_MODELS=0` for an exhaustive selected-profile sweep or a positive number for a smaller cap.
- Modern/all sweeps default to a curated high-signal cap; set `OPENCLAW_LIVE_MAX_MODELS=0` for an exhaustive modern sweep or a positive number for a smaller cap.
- Exhaustive sweeps use `OPENCLAW_LIVE_TEST_TIMEOUT_MS` for the whole direct-model test timeout. Default: 60 minutes.
- Direct-model probes run with 20-way parallelism by default; set `OPENCLAW_LIVE_MODEL_CONCURRENCY` to override.
- How to select providers:
@@ -340,12 +339,6 @@ Narrow, explicit allowlists are fastest and least flaky:
- Single model, direct (no gateway):
- `OPENCLAW_LIVE_MODELS="openai/gpt-5.5" pnpm test:live src/agents/models.profiles.live.test.ts`
- Small-model direct profile:
- `OPENCLAW_LIVE_MODELS=small pnpm test:live src/agents/models.profiles.live.test.ts`
- Ollama Cloud API smoke:
- `OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_OLLAMA=1 OPENCLAW_LIVE_OLLAMA_BASE_URL=https://ollama.com OPENCLAW_LIVE_OLLAMA_MODEL=glm-5.1:cloud OPENCLAW_LIVE_OLLAMA_WEB_SEARCH=0 pnpm test:live -- extensions/ollama/ollama.live.test.ts`
- Single model, gateway smoke:
- `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`

View File

@@ -30,9 +30,9 @@ Update and plugin tests protect these contracts:
plugin state.
- Plugin installs work from local directories, git repos, npm packages, and the
ClawHub registry path.
- Plugin npm dependencies are installed in one managed npm project per plugin,
scanned before trust, and removed through npm during uninstall so hoisted
dependencies do not linger.
- Plugin npm dependencies are installed in the managed npm root, scanned before
trust, and removed through npm during uninstall so hoisted dependencies do not
linger.
- Plugin update is stable when nothing changed: install records, resolved
source, installed dependency layout, and enabled state stay intact.
@@ -276,9 +276,9 @@ can fail for the right reason:
- Registry/package source behavior: `test:docker:plugins` fixture or ClawHub
fixture server.
- Dependency layout or cleanup behavior: assert both runtime execution and the
filesystem boundary. npm dependencies may be hoisted inside the plugin's
managed npm project, so tests should prove that project is scanned/cleaned
instead of assuming only the plugin package-local `node_modules` tree.
filesystem boundary. npm dependencies may be hoisted under the managed npm
root, so tests should prove the root is scanned/cleaned instead of assuming a
package-local `node_modules` tree.
Keep new Docker fixtures hermetic by default. Use local fixture registries and
fake packages unless the point of the test is live registry behavior.

View File

@@ -84,12 +84,11 @@ When debugging real providers/models (requires real creds):
- Codex on-demand install smoke: `pnpm test:docker:codex-on-demand`
- Installs the packaged OpenClaw tarball in Docker, runs OpenAI API-key
onboarding, and verifies the Codex plugin plus `@openai/codex` dependency
were downloaded into the managed npm project root on demand.
were downloaded into the managed npm root on demand.
- Live plugin tool dependency smoke: `pnpm test:docker:live-plugin-tool`
- Packs a fixture plugin with a real `slugify` dependency, installs it through
`npm-pack:`, verifies the dependency under the managed npm project root,
then asks a live OpenAI model to call the plugin tool and return the hidden
slug.
`npm-pack:`, verifies the dependency under the managed npm root, then asks a
live OpenAI model to call the plugin tool and return the hidden slug.
- Crestodian rescue command smoke: `pnpm test:live:crestodian-rescue-channel`
- Opt-in belt-and-suspenders check for the message-channel rescue command
surface. It exercises `/crestodian status`, queues a persistent model
@@ -739,13 +738,13 @@ plugin validation checklist, see
These Docker runners split into two buckets:
- Live-model runners: `test:docker:live-models` and `test:docker:live-gateway` run only their matching profile-key live file inside the repo Docker image (`src/agents/models.profiles.live.test.ts` and `src/gateway/gateway-models.profiles.live.test.ts`), mounting your local config dir, workspace, and optional profile env file. The matching local entrypoints are `test:live:models-profiles` and `test:live:gateway-profiles`.
- Docker live runners keep their own practical caps where needed:
`test:docker:live-models` defaults to the curated supported high-signal set, and
- Docker live runners default to a smaller smoke cap so a full Docker sweep stays practical:
`test:docker:live-models` defaults to `OPENCLAW_LIVE_MAX_MODELS=12`, and
`test:docker:live-gateway` defaults to `OPENCLAW_LIVE_GATEWAY_SMOKE=1`,
`OPENCLAW_LIVE_GATEWAY_MAX_MODELS=8`,
`OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=45000`, and
`OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=90000`. Set `OPENCLAW_LIVE_MAX_MODELS`
or the gateway env vars when you explicitly want a smaller cap or larger scan.
`OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=90000`. Override those env vars when you
explicitly want the larger exhaustive scan.
- `test:docker:all` builds the live Docker image once via `test:docker:live-build`, packs OpenClaw once as an npm tarball through `scripts/package-openclaw-for-docker.mjs`, then builds/reuses two `scripts/e2e/Dockerfile` images. The bare image is only the Node/Git runner for install/update/plugin-dependency lanes; those lanes mount the prebuilt tarball. The functional image installs the same tarball into `/app` for built-app functionality lanes. Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mjs`; planner logic lives in `scripts/lib/docker-e2e-plan.mjs`; `scripts/test-docker-all.mjs` executes the selected plan. The aggregate uses a weighted local scheduler: `OPENCLAW_DOCKER_ALL_PARALLELISM` controls process slots, while resource caps keep heavy live, npm-install, and multi-service lanes from all starting at once. If a single lane is heavier than the active caps, the scheduler can still start it when the pool is empty and then keeps it running alone until capacity is available again. Defaults are 10 slots, `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=10`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; tune `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` only when the Docker host has more headroom. The runner performs a Docker preflight by default, removes stale OpenClaw E2E containers, prints status every 30 seconds, stores successful lane timings in `.artifacts/docker-tests/lane-timings.json`, and uses those timings to start longer lanes first on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the weighted lane manifest without building or running Docker, or `node scripts/test-docker-all.mjs --plan-json` to print the CI plan for selected lanes, package/image needs, and credentials.
- `Package Acceptance` is the GitHub-native package gate for "does this installable tarball work as a product?" It resolves one candidate package from `source=npm`, `source=ref`, `source=url`, or `source=artifact`, uploads it as `package-under-test`, then runs the reusable Docker E2E lanes against that exact tarball instead of repacking the selected ref. Profiles are ordered by breadth: `smoke`, `package`, `product`, and `full`. See [Testing updates and plugins](/help/testing-updates-plugins) for the package/update/plugin contract, published-upgrade survivor matrix, release defaults, and failure triage.
- Build and release checks run `scripts/check-cli-bootstrap-imports.mjs` after tsdown. The guard walks the static built graph from `dist/entry.js` and `dist/cli/run-main.js` and fails if pre-dispatch startup imports package dependencies such as Commander, prompt UI, undici, or logging before command dispatch; it also keeps the bundled gateway run chunk under budget and rejects static imports of known cold gateway paths. Packaged CLI smoke also covers root help, onboard help, doctor help, status, config schema, and a model-list command.

View File

@@ -0,0 +1,205 @@
---
summary: "Per-agent Policy plugin overlays layered on top of global policy rules."
read_when:
- You are designing per-agent policy requirements
- You need to distinguish tool posture policy from workspace policy
- You are configuring stricter policy for one named agent
title: "Agent-scoped policy overlays"
---
# Agent-scoped policy overlays
OpenClaw policy supports global requirements and stricter requirements for
explicit runtime agent ids. Some deployments need one agent to use a tighter
workspace and tool posture than other agents, but deployment-wide rules should
not force every agent to use the same posture.
This page describes the agent-scoped overlay model. The field reference remains
[`openclaw policy`](/cli/policy).
## Design goals
- Keep global policy as the deployment baseline.
- Let a named agent add stricter requirements without weakening global rules.
- Reuse existing policy section shapes where the evidence can be attributed to
an agent.
- Avoid making `agents.workspace` a second tool-permission system.
- Leave global-only checks global until their evidence can be mapped to an
agent.
## Shape
Use `scopes.<scopeName>` for purpose-named agent policy scopes. Each
scope lists the runtime `agentIds` it applies to, then reuses the normal
top-level policy section grammar where the section evidence can be attributed to
those agents. The initial shipped scoped sections are `tools` and
`agents.workspace`; sandbox and ingress stay out of this PR and can join the
same container once those policy PRs land and their evidence carries agent
identity. The scoped field inventory is backed by policy rule metadata that
records each field's strictness semantics for later policy-file conformance.
```jsonc
{
"tools": {
"denyTools": ["process"],
},
"agents": {
"workspace": {
"allowedAccess": ["none", "ro"],
},
},
"scopes": {
"release-agent-lockdown": {
"agentIds": ["release-agent"],
"agents": {
"workspace": {
"allowedAccess": ["none", "ro"],
},
},
"tools": {
"profiles": { "allow": ["minimal", "messaging"] },
"fs": { "requireWorkspaceOnly": true },
"exec": {
"allowSecurity": ["deny", "allowlist"],
"requireAsk": ["always"],
"allowHosts": ["sandbox"],
},
"elevated": { "allow": false },
"alsoAllow": { "expected": ["message", "read"] },
"denyTools": ["exec", "process", "write", "edit", "apply_patch"],
},
},
},
}
```
`agents.workspace` remains the existing all-agent workspace baseline.
`scopes.<scopeName>` is a scoped overlay, not a replacement for global
policy. The scope name is descriptive only; matching uses `agentIds`, not
display names. It deliberately contains normal section names instead of a
bespoke per-agent mini-grammar.
Every scope present in `policy.jsonc` must be valid and enforceable. In this
PR, the only supported selector is `agentIds`, and it supports only `tools.*`
and `agents.workspace.*`.
## Layering semantics
Policy evaluation is additive:
1. Top-level policy applies to all matching evidence.
2. Existing `agents.workspace` applies to defaults and every listed agent.
3. `scopes.<scopeName>` applies to evidence for each normalized runtime
id in `agentIds`.
4. Multiple scope blocks may target the same agent when they govern
different fields, or when a later value for the same field is equally or
more restrictive according to policy metadata.
5. A named-agent overlay can tighten policy, but it cannot make a global
violation acceptable.
If both global and agent-scoped rules fail, findings should point at the rule
that was violated:
```text
oc://policy.jsonc/tools/denyTools
oc://policy.jsonc/scopes/release-agent-lockdown/tools/denyTools
oc://policy.jsonc/scopes/release-agent-lockdown/agents/workspace/allowedAccess
```
That keeps broad tool posture, named-agent tool posture, and workspace posture
auditable as separate requirements even when they observe the same config
fields.
Exact-list claims such as `tools.alsoAllow.expected` compare the configured list
to the expected list and report both missing expected entries and unexpected
extra entries. This is intended for additive posture such as `alsoAllow`, where
one extra entry can widen an agent beyond its reviewed role.
## Policy and config layering
The overlay model separates where policy is authored from where OpenClaw config
is observed:
| Policy scope | Observed config | Applies to | Example result |
| --------------------------------------- | ---------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------- |
| Top-level `tools.*` | Global `tools.*` and inherited agent tool posture | All agents using matching posture | Deny `gateway` exec host for every agent unless the global policy allows it. |
| Top-level `tools.*` | `agents.list[].tools.*` overrides | Any agent with an override | Flag one agent that overrides `tools.exec.host` to an unapproved value. |
| `scopes.<scopeName>.tools.*` | Matching `agents.list[]` entry and inherited posture | Only that named agent | Let most agents use `node` exec host while one agent must use only `sandbox`. |
| `agents.workspace` | Defaults and every listed agent workspace posture | Defaults and all listed agents | Require every agent workspace access to be `none` or `ro`. |
| `scopes.<scopeName>.agents.workspace.*` | Matching `agents.list[]` workspace posture | Only that named agent | Require one agent to be read-only without requiring the same for `main`. |
Per-agent overlays are additive. A named-agent rule can be stricter than the
top-level rule, but it cannot make a global violation acceptable. For allow-list
rules, the effective allowed set is the intersection of the global rule and the
named-agent overlay when both are present.
For example, if top-level `tools.exec.allowHosts` permits `["sandbox", "node"]`
and `scopes.release-agent-lockdown.tools.exec.allowHosts` permits only
`["sandbox"]`, `release-agent` fails when its effective exec host is `node`;
another agent can still pass
with `node`.
## Tool posture versus workspace posture
Tool posture belongs under `tools` because it describes what tool behavior a
configuration may expose. The existing `tools.*` policy observes both global
`tools.*` config and per-agent `agents.list[].tools.*` overrides.
Workspace posture belongs under `workspace` because it describes sandbox mode
and workspace access. The workspace section should not grow into a general tool
policy namespace. If one agent needs stricter tool restrictions to make its
workspace posture meaningful, put those restrictions in the same agent overlay
under `scopes.<scopeName>.tools`.
For a restricted release agent, the intended split is:
```jsonc
{
"scopes": {
"release-agent-lockdown": {
"agentIds": ["release-agent"],
"agents": {
"workspace": { "allowedAccess": ["none", "ro"] },
},
"tools": {
"denyTools": ["exec", "process", "write", "edit", "apply_patch"],
},
},
},
}
```
## Section eligibility
An agent-scoped section should be added only when policy evidence carries an
agent id or can be attributed to one without guessing.
| Section | Initial agent-scoped status | Reason |
| ----------- | --------------------------- | ------------------------------------------------------------------------ |
| `workspace` | Include | Agent sandbox/workspace evidence already has agent identity. |
| `tools` | Include | Tool posture evidence includes global and per-agent tool config. |
| `sandbox` | Pipeline follow-up | Keep out until the sandbox posture PR lands and evidence can be scoped. |
| `ingress` | Pipeline follow-up | Keep out until ingress/channel posture lands with agent attribution. |
| `models` | Include when mapped | Selected model refs can be agent-specific. |
| `mcp` | Include when mapped | Use only when MCP server evidence is attributable to an agent. |
| `auth` | Defer | Auth profile metadata is a config catalog unless agent binding is clear. |
| `channels` | Defer | Channel provider posture is deployment-level until routing is scoped. |
| `gateway` | Keep global | Gateway exposure/auth/http posture is process-level. |
| `network` | Keep global | Private-network SSRF posture is runtime-level. |
| `secrets` | Keep global first | Secret provider posture is shared unless refs are agent-attributed. |
## Compatibility
The implementation is additive:
- keep all existing top-level policy fields valid;
- keep `agents.workspace` semantics unchanged;
- validate `scopes` before evaluating scoped rules;
- reject unsupported scoped sections clearly until their evidence and policy
contracts are implemented;
- do not reinterpret top-level `tools.requireMetadata` as agent-scoped, because
tool metadata describes the declared workspace tool catalog;
- include agent-scoped evidence in the attestation hash when any scoped rule is
present.
This lets broad tool posture remain a top-level policy contract while named
agents add stricter observable claims without weakening the global baseline.

View File

@@ -281,9 +281,8 @@ fresh OpenClaw session.
**A Computer Use tool says `Native hook relay unavailable`.** The Codex-native
tool hook could not reach an active OpenClaw relay through the local bridge or
Gateway fallback. Start a fresh OpenClaw session with `/new` or `/reset`. If it
works once and then fails again on a later tool call, `/new` is only clearing the
current attempt; restart the Codex app-server or OpenClaw Gateway so old threads
and hook registrations are dropped, then retry in a fresh session.
keeps happening, restart the gateway so old app-server threads and hook
registrations are dropped, then retry.
**Turn-start auto-install refuses a source.** This is intentional. Add the
source with explicit `/codex computer-use install --source <marketplace-source>`

View File

@@ -99,16 +99,6 @@ OpenClaw can mirror selected events, but it cannot rewrite the native Codex
thread unless Codex exposes that operation through app-server or native hook
callbacks.
Codex app-server report-mode `PreToolUse` events defer plugin approval requests
to the matching app-server approval. If an OpenClaw `before_tool_call` hook
returns `requireApproval` while the native payload sets report approval mode
(`openclaw_approval_mode` is `"report"`), the native hook relay records the
plugin approval requirement and returns no native decision. When Codex sends the
app-server approval request for the same tool use, OpenClaw opens the plugin
approval prompt and maps the decision back to Codex. Codex `PermissionRequest`
events are a separate approval path and can still route through OpenClaw
approvals when the runtime is configured for that bridge.
Codex app-server item notifications also provide async `after_tool_call`
observations for native tool completions that are not already covered by the
native `PostToolUse` relay. These observations are for telemetry and plugin

View File

@@ -738,11 +738,9 @@ protocol version.
the Codex thread is still trying to use a native hook relay id that OpenClaw no
longer has registered. This is a native Codex hook transport problem, not an ACP
backend, provider, GitHub, or shell-command failure. Start a fresh session in
the affected chat with `/new` or `/reset`, then retry a harmless command. If that
works once but the next native tool call fails again, treat `/new` as a temporary
workaround only: copy the prompt into a fresh session after restarting the Codex
app-server or OpenClaw Gateway so old threads are dropped and native hook
registrations are recreated.
the affected chat with `/new` or `/reset`, then retry a harmless command. If the
same fresh session still fails, restart the Codex app-server or OpenClaw Gateway
so native hook registrations are recreated.
**A non-Codex model uses the built-in harness:** that is expected unless
provider or model runtime policy routes it to another harness. Plain non-OpenAI

View File

@@ -1,374 +0,0 @@
---
summary: "Run OpenClaw embedded agent turns through the bundled GitHub Copilot SDK harness"
title: "Copilot SDK harness"
read_when:
- You want to use the bundled GitHub Copilot SDK harness for an agent
- You need configuration examples for the `copilot` runtime
- You are wiring an agent to subscription Copilot (github / openclaw / copilot) and want it to run through the Copilot CLI
---
The bundled `copilot` extension lets OpenClaw run embedded subscription
Copilot agent turns through the GitHub Copilot CLI (`@github/copilot-sdk`)
instead of the built-in PI harness.
Use the Copilot SDK harness when you want the Copilot CLI session to own the
low-level agent loop: native tool execution, native compaction
(`infiniteSessions`), and CLI-managed thread state under `copilotHome`.
OpenClaw still owns chat channels, session files, model selection, OpenClaw
dynamic tools (bridged), approvals, media delivery, the visible transcript
mirror, `/btw` side questions (handled by the in-tree PI fallback — see
[Side questions (`/btw`)](#side-questions-btw)), and `openclaw doctor`.
For the broader model/provider/runtime split, start with
[Agent runtimes](/concepts/agent-runtimes).
## Requirements
- OpenClaw with the bundled `copilot` extension available.
- If your config uses `plugins.allow`, include `copilot` (the manifest
id in `extensions/copilot/openclaw.plugin.json`). A restrictive
allowlist that uses the npm-style `@openclaw/copilot` package name
will leave the bundled plugin blocked and the runtime will not load
even with `agentRuntime.id: "copilot"`.
- A GitHub Copilot subscription that can drive the Copilot CLI (or a
`gitHubToken` env / auth-profile entry for headless / cron runs).
- A writable `copilotHome` directory. The harness defaults to
`~/.openclaw/agents/<agentId>/copilot` for full per-agent isolation. The
platform default (`%APPDATA%\copilot` on Windows, `$XDG_CONFIG_HOME/copilot`
or `~/.config/copilot` elsewhere) is used as the doctor probe fallback when
no explicit home is set.
`openclaw doctor` runs the bundled
[doctor contract](#doctor-and-probes) for the extension; failures there are
the canonical way to confirm the environment is ready before opting an agent
in.
## On-demand SDK install
The Copilot agent runtime ships its small TypeScript code bundled inside
the openclaw tarball, but the underlying `@github/copilot-sdk` package
(and its platform-specific `@github/copilot-<platform>-<arch>` CLI
binary) is **not** installed by default — together they add ~260 MB to
your openclaw install footprint, and most openclaw users do not select
a Copilot model.
The wizard offers to install the SDK the first time you select a
`github-copilot/*` model **and** your config opts the model (or its
provider) into the Copilot agent runtime via
`agentRuntime: { id: "copilot" }` (see [Quickstart](#quickstart) below).
Without the opt-in, openclaw uses its built-in GitHub Copilot provider
and never prompts for the SDK install:
```
The Copilot agent runtime needs @github/copilot-sdk (~260 MB on first
install, downloads the @github/copilot CLI binary for your platform).
Install now? [Y/n]
```
If you accept, the SDK is installed into
`~/.openclaw/npm-runtime/copilot/` and detected on subsequent runs. The
install runs `npm ci` against a checked-in `package-lock.json` shipped
with openclaw at
`src/commands/copilot-sdk-install-manifest/package-lock.json`, so the
exact transitive graph reviewed for this release lands on disk on every
user machine.
If you decline, the runtime will fail at first invocation with an
actionable install message; re-run `openclaw setup` to retry the install
(or copy the pinned manifest into `~/.openclaw/npm-runtime/copilot/` and
run `npm ci` yourself if you need to install offline).
The runtime resolves the SDK in this order:
1. `import("@github/copilot-sdk")` against the host openclaw install
(covers source/dev checkouts and any environment that pre-installs
the SDK alongside openclaw).
2. The well-known fallback dir `~/.openclaw/npm-runtime/copilot/` (the
wizard install target).
A missing SDK surfaces a single error with code `COPILOT_SDK_MISSING`
and the manual install command above.
## Quickstart
Pin one model (or one provider) to the harness:
```json5
{
agents: {
defaults: {
model: "github-copilot/gpt-5.5",
models: {
"github-copilot/gpt-5.5": {
agentRuntime: { id: "copilot" },
},
},
},
},
}
```
Both routes are equivalent. Use `agentRuntime.id` on a single model entry
when only that model should be routed through the harness; set
`agentRuntime.id` on a provider when every model under that provider should
use it.
## Supported providers
The harness advertises support for the canonical `github-copilot` provider
(the same id owned by `extensions/github-copilot`):
- `github-copilot`
Anything outside that set falls through `selection.ts`'s `auto_pi` branch back
to PI.
## Auth
Per-agent precedence, applied during `runCopilotAttempt`:
1. **Explicit `useLoggedInUser: true`** on the attempt input. Uses the Copilot
CLI's logged-in user resolved under the agent's `copilotHome`.
2. **Explicit `gitHubToken`** on the attempt input (with `profileId` +
`profileVersion`). Useful for direct CLI invocations and tests where the
caller wants to bypass auth-profile resolution.
3. **Contract-resolved `resolvedApiKey` + `authProfileId`** from the
`EmbeddedRunAttemptParams` shape. This is the **production main path**:
core resolves the agent's configured `github-copilot` auth profile
(via `src/infra/provider-usage.auth.ts:resolveProviderAuths`) before
invoking the harness, and the harness consumes both fields directly.
This makes a `github-copilot:<profile>` auth profile work end-to-end
for headless / cron / multi-profile setups without env vars.
4. **Env-var fallback** for direct CLI / dogfood runs where no auth
profile is configured. The runtime checks the following vars in
precedence order, mirroring the shipped `github-copilot` provider
(`extensions/github-copilot/auth.ts`) and the documented Copilot SDK
setup:
1. `OPENCLAW_GITHUB_TOKEN` -- harness-specific override; set this
to pin a token for the OpenClaw harness without disturbing
system-wide `gh` / Copilot CLI config.
2. `COPILOT_GITHUB_TOKEN` -- standard Copilot SDK / CLI env var.
3. `GH_TOKEN` -- standard `gh` CLI env var (matches the existing
`github-copilot` provider precedence).
4. `GITHUB_TOKEN` -- generic GitHub token fallback.
The first non-empty value wins; empty strings are treated as
absent. The synthesised pool profile id is `env:<NAME>` and the
profileVersion is a non-reversible sha256 fingerprint of the
token, so rotating the env value cleanly busts the client pool.
5. **Default `useLoggedInUser`** when no token signal is available.
Each agent gets a dedicated `copilotHome` so Copilot CLI tokens, sessions, and
config do not leak between agents on the same machine. The default is
`<agentDir>/copilot` when the host hands the harness an agent directory
(isolating SDK state from OpenClaw's `models.json` / `auth-profiles.json` in
the same directory), or `~/.openclaw/agents/<agentId>/copilot` otherwise.
Override with `copilotHome: <path>` on the attempt input when you need a
custom location (for example, a shared mount for migration).
`probeCopilotAuthShape` (see [Doctor and probes](#doctor-and-probes)) is the
pure shape check that validates which of the modes above will be used.
It does not perform a live SDK handshake.
## Configuration surface
The harness reads its config from per-attempt input
(`runCopilotAttempt({...})`) plus a small set of env defaults inside
`extensions/copilot/src/`:
- `copilotHome` — per-agent CLI state directory (defaults documented above).
- `model` — string or `{ provider, id, api? }`. When omitted, OpenClaw uses
the agent's normal model selection and the harness verifies the resolved
provider is in the supported set.
- `reasoningEffort``"low" | "medium" | "high" | "xhigh"`. Maps from
OpenClaw's `ThinkLevel` / `ReasoningLevel` resolution in
`auto-reply/thinking.ts`.
- `infiniteSessionConfig` — optional override for the SDK
`infiniteSessions` block driven by `harness.compact`. Defaults are safe to
leave as-is.
- `hooksConfig` — optional bridge config exposing OpenClaw
before/after-message-write hooks to the SDK loop.
- `permissionPolicy` — optional override for the SDK's
`onPermissionRequest` handler used for built-in SDK tool kinds
(`shell`, `write`, `read`, `url`, `mcp`, `memory`, `hook`). Defaults
to `rejectAllPolicy` as a safety net; in practice the SDK never
invokes any of those kinds because every bridged OpenClaw tool is
registered with `overridesBuiltInTool: true` and
`skipPermission: true` so 100% of tool calls flow through OpenClaw's
wrapped `execute()`. See [Permissions and ask_user](#permissions-and-ask_user).
- `enableSessionTelemetry` — opt-in OpenTelemetry routing via
`telemetry-bridge.ts`.
Nothing in the rest of OpenClaw needs to know about these fields. Other
plugins, channels, and core code only see the standard
`AgentHarnessAttemptParams` / `AgentHarnessAttemptResult` shape.
## Compaction
When `harness.compact` runs, the Copilot SDK harness:
1. Enables `infiniteSessions` on the SDK session.
2. Lets the SDK perform its native compaction.
3. Writes an OpenClaw-shaped marker at
`workspacePath/files/openclaw-compaction-<ts>.json` so existing OpenClaw
transcript readers still see a familiar artifact.
The OpenClaw side transcript mirror (see below) continues to receive the
post-compaction messages, so user-facing chat history stays consistent.
## Transcript mirroring
`runCopilotAttempt` dual-writes each turn's mirrorable messages into the
OpenClaw audit transcript via
`extensions/copilot/src/dual-write-transcripts.ts`. The mirror is
per-session scoped (`copilot:${sessionId}`) and uses a per-message
identity (`${role}:${sha256_16(role,content)}`) so re-emits of prior-turn
entries collide with existing on-disk keys and do not duplicate.
The mirror is wrapped in two layers of failure containment so a transcript
write failure cannot fail the attempt: an internal best-effort wrapper and a
defense-in-depth `.catch(...)` at the attempt level. Failures are logged but
not surfaced.
## Side questions (`/btw`)
`/btw` is **not** native on this harness. `createCopilotAgentHarness()`
deliberately leaves `harness.runSideQuestion` undefined, so OpenClaw's `/btw`
dispatcher (`src/agents/btw.ts`) falls through to the same in-tree PI fallback
path it uses for every non-Codex runtime: the configured model provider is
called directly with a short side-question prompt and streamed back via
`streamSimple` (no CLI session, no extra pool slot).
This keeps Copilot CLI sessions reserved for the agent's main turn loop, and
keeps `/btw` behavior identical to other PI-backed runtimes. The contract is
asserted in
[`extensions/copilot/harness.test.ts`](https://github.com/openclaw/openclaw/blob/main/extensions/copilot/harness.test.ts)
under `describe("runSideQuestion")`.
## Doctor and probes
`extensions/copilot/doctor-contract-api.ts` is auto-loaded by
`src/plugins/doctor-contract-registry.ts`. It contributes:
- An empty `legacyConfigRules` (no retired fields at MVP).
- A no-op `normalizeCompatibilityConfig` (kept so future field retirements
have a stable in-tree home).
- One `sessionRouteStateOwners` entry claiming provider `github-copilot`;
runtime `copilot`; CLI session key `copilot`; auth profile
prefix `github-copilot:`.
`extensions/copilot/src/doctor-probes.ts` exports three imperative probes
that hosts (including `openclaw doctor`) can call to verify the environment:
| Probe | What it checks | Reasons it can fail |
| -------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `probeCopilotCliVersion` | `copilot --version` exits 0 with a non-empty version string | `non-zero-exit`, `empty-version`, `spawn-failed`, `spawn-error`, `probe-timeout` |
| `probeCopilotHomeWritable` | `mkdir -p copilotHome` + write + rm a marker file | `copilothome-not-writable` (with the underlying fs error in `details.rawError`) |
| `probeCopilotAuthShape` | At least one of `useLoggedInUser`, `gitHubToken`, or `profileId`+`profileVersion` | `no-auth-source` |
Each probe accepts a DI seam (`spawnFn`, `fsApi`) so tests do not spawn the
real Copilot CLI or touch the host fs.
## Limitations
- The harness only claims the canonical `github-copilot` provider at MVP.
Additional providers (BYOK or otherwise) should land in follow-up PRs that
ship the adapter alongside the wire-up.
- The harness does not deliver TUI; PI's TUI is unaffected and remains the
fallback for whatever runtimes do not have a peer surface.
- PI session state is not migrated when an agent switches to `copilot`.
Selection is per attempt; existing PI sessions remain valid.
- **Interactive `ask_user` is not yet wired.** The SDK's
`onUserInputRequest` handler is intentionally not registered, which
per the SDK contract hides the `ask_user` tool from the model
entirely. Agents running under this harness make best-judgment
decisions from the initial prompt rather than asking clarifying
questions mid-turn. A follow-up will port the codex pattern at
`extensions/codex/src/app-server/user-input-bridge.ts` to route SDK
`UserInputRequest`s through the OpenClaw channel/TUI prompt path; the
dormant scaffolding in `extensions/copilot/src/user-input-bridge.ts`
is the surface that follow-up will wire.
## Permissions and ask_user
Permission enforcement for bridged OpenClaw tools happens **inside the
tool wrapper**, not via the SDK's `onPermissionRequest` callback. The
same `wrapToolWithBeforeToolCallHook` that PI uses
(`src/agents/pi-tools.before-tool-call.ts`) is applied by
`createOpenClawCodingTools` to every coding tool: loop detection,
trusted plugin policies, before-tool-call hooks, and two-phase plugin
approvals via the gateway (`plugin.approval.request`) all run with the
exact same code path as native PI attempts.
To let that wrapper own the decision, the SDK Tool returned by
`convertOpenClawToolToSdkTool` is marked with:
- `overridesBuiltInTool: true` — replaces the Copilot CLI's built-in
tool of the same name (edit, read, write, bash, …) so every tool
invocation routes back to OpenClaw.
- `skipPermission: true` — tells the SDK not to fire
`onPermissionRequest({kind: "custom-tool"})` before invoking the tool.
The wrapped `execute()` performs the richer OpenClaw policy check
internally; an SDK-level prompt would either short-circuit OpenClaw's
enforcement (if we allow-all) or block every tool call (if we
reject-all) — neither matches PI parity.
The in-tree codex harness uses the same split: bridged OpenClaw tools
are wrapped (`extensions/codex/src/app-server/dynamic-tools.ts`) and
the codex-app-server's _own_ native approval kinds
(`item/commandExecution/requestApproval`,
`item/fileChange/requestApproval`,
`item/permissions/requestApproval`) are routed through
`plugin.approval.request`
(`extensions/codex/src/app-server/approval-bridge.ts`). The Copilot SDK
equivalent — fail-closed `rejectAllPolicy` for any non-`custom-tool`
kind that ever reaches `onPermissionRequest` — is the same safety net,
and it does not fire in practice because `overridesBuiltInTool: true`
displaces every built-in.
For the wrapped-tool layer to make policy decisions equivalent to PI,
the harness forwards the full PI attempt-tool context to
`createOpenClawCodingTools` — identity (`senderIsOwner`,
`memberRoleIds`, `ownerOnlyToolAllowlist`, …), channel/routing
(`groupId`, `currentChannelId`, `replyToMode`, message-tool toggles),
auth (`authProfileStore`), run identity
(`sessionKey`/`runSessionKey` derived from `sandboxSessionKey`,
`runId`), model context (`modelApi`, `modelContextWindowTokens`,
`modelCompat`, `modelHasVision`), and run hooks (`onToolOutcome`,
`onYield`). Without those fields, owner-only allowlists silently
behave as deny-by-default, plugin-trust policies cannot resolve to the
right scope, and `session_status: "current"` resolves to a stale
sandbox key. The bridge builder is in
`extensions/copilot/src/tool-bridge.ts` and mirrors the PI
authoritative call at
`src/agents/pi-embedded-runner/run/attempt.ts:1029-1117`. Two PI fields
are intentionally **not** forwarded at MVP and tracked as follow-ups:
`sandbox` (the harness does not yet route through `resolveSandboxContext`)
and the PI tool-search/code-mode machinery
(`toolSearchCatalogRef`, `includeCoreTools`,
`includeToolSearchControls`, `toolSearchCatalogExecutor`,
`toolConstructionPlan`), which has no analog at the SDK boundary.
### Session-level GitHub token
The Copilot SDK contract distinguishes the **client-level** GitHub
token (`CopilotClientOptions.gitHubToken`, used to authenticate the
CLI process itself) from the **session-level** token
(`SessionConfig.gitHubToken`, which determines content exclusion,
model routing, and quota for that session and is honored on both
`createSession` and `resumeSession`). The harness resolves auth once
via `resolveCopilotAuth` and sets both fields when the auth mode is
`gitHubToken` (an explicit `auth.gitHubToken` or a contract-resolved
`resolvedApiKey` from a configured `github-copilot` auth profile).
When the resolved mode is `useLoggedInUser`, the session-level field
is omitted so the SDK keeps deriving identity from the logged-in
identity.
`ask_user` is intentionally hidden — see Limitations above.
## Related
- [Agent runtimes](/concepts/agent-runtimes)
- [Codex harness](/plugins/codex-harness)
- [Agent harness plugins (SDK reference)](/plugins/sdk-agent-harness)

View File

@@ -34,36 +34,34 @@ OpenClaw owns only the plugin lifecycle:
OpenClaw uses stable per-source roots:
- npm packages install into per-plugin projects under
`~/.openclaw/npm/projects/<encoded-package>`
- npm packages install under `~/.openclaw/npm`
- git packages clone under `~/.openclaw/git`
- local/path/archive installs are copied or referenced without dependency repair
npm installs run in that per-plugin project root with:
npm installs run in the npm root with:
```bash
cd ~/.openclaw/npm/projects/<encoded-package>
cd ~/.openclaw/npm
npm install --omit=dev --omit=peer --legacy-peer-deps --ignore-scripts --no-audit --no-fund
```
`openclaw plugins install npm-pack:<path.tgz>` uses that same per-plugin npm
project root for a local npm-pack tarball. OpenClaw reads the tarball's npm
metadata, adds it to the managed project as a copied `file:` dependency, runs
the normal npm install, and then verifies the installed lockfile metadata before
trusting the plugin.
`openclaw plugins install npm-pack:<path.tgz>` uses that same managed npm root
for a local npm-pack tarball. OpenClaw reads the tarball's npm metadata, adds it
to the managed root as a copied `file:` dependency, runs the normal npm install,
and then verifies the installed lockfile metadata before trusting the plugin.
This is intended for package-acceptance and release-candidate proof where a
local pack artifact should behave like the registry artifact it simulates.
npm may hoist transitive dependencies to the per-plugin project's
`node_modules` beside the plugin package. OpenClaw scans the managed project
root before trusting the install and removes that project during uninstall, so
hoisted runtime dependencies stay inside that plugin's cleanup boundary.
npm may hoist transitive dependencies to `~/.openclaw/npm/node_modules` beside
the plugin package. OpenClaw scans the managed npm root before trusting the
install and uses npm to remove npm-managed packages during uninstall, so hoisted
runtime dependencies stay inside the managed cleanup boundary.
Published npm plugin packages can ship `npm-shrinkwrap.json`. npm uses that
publishable lockfile during install, and OpenClaw's managed npm project root
supports it through the normal npm install path. OpenClaw-owned publishable
plugin packages must include a package-local shrinkwrap generated from that
plugin package's published dependency graph:
publishable lockfile during install, and OpenClaw's managed npm root supports it
through the normal npm install path. OpenClaw-owned publishable plugin packages
must include a package-local shrinkwrap generated from that plugin package's
published dependency graph:
```bash
pnpm deps:shrinkwrap:generate
@@ -89,11 +87,11 @@ instead of embedding every platform binary in the plugin tarball. The root
Plugins that import `openclaw/plugin-sdk/*` declare `openclaw` as a peer
dependency. OpenClaw does not let npm install a separate registry copy of the
host package into a managed project, because stale host packages can affect npm
peer resolution inside that plugin. Managed npm installs skip npm peer
resolution/materialization and OpenClaw reasserts plugin-local
`node_modules/openclaw` links for installed packages that declare the host peer
after install or update.
host package into the managed root, because stale host packages can affect npm
peer resolution during later plugin installs. Managed npm installs skip npm peer
resolution/materialization for the shared root and OpenClaw reasserts
plugin-local `node_modules/openclaw` links for installed packages that declare
the host peer after install, update, or uninstall.
git installs clone or refresh the repository, then run:
@@ -157,7 +155,7 @@ not a supported way to prepare bundled plugin dependencies.
| -------------------------------- | ------------------------------------- | -------------------------------------------------------------------- |
| `npm install -g openclaw` | Built runtime tree inside the package | OpenClaw package and explicit plugin install/update/doctor flows |
| Git checkout plus `pnpm install` | `extensions/<id>` workspace packages | The pnpm workspace, including each plugin package's own dependencies |
| `openclaw plugins install ...` | Managed npm project/git/ClawHub root | The plugin install/update flow |
| `openclaw plugins install ...` | Managed npm/git/ClawHub plugin root | The plugin install/update flow |
## Legacy cleanup
@@ -170,7 +168,4 @@ stage directories, and package-local pnpm stores. Packaged postinstall also
removes those global symlinks before pruning the legacy target roots so upgrades
do not leave dangling ESM package imports.
Older npm installs also used a shared `~/.openclaw/npm/node_modules` root.
Current install, update, uninstall, and doctor flows still recognize that legacy
flat root only for recovery and cleanup. New npm installs should create
per-plugin project roots instead.
These paths are legacy debris only. New installs should not create them.

View File

@@ -1124,7 +1124,7 @@ Optional overrides:
introMessage: "Say exactly: I'm here.",
providers: {
google: {
speakerVoice: "Kore",
voice: "Kore",
},
},
},
@@ -1141,7 +1141,7 @@ ElevenLabs for both agent-mode listening and speaking:
providers: {
elevenlabs: {
modelId: "eleven_v3",
speakerVoiceId: "pMsXgVXv3BLzUgSXRplE",
voiceId: "pMsXgVXv3BLzUgSXRplE",
},
},
},
@@ -1169,11 +1169,11 @@ ElevenLabs for both agent-mode listening and speaking:
```
The persistent Meet voice comes from
`messages.tts.providers.elevenlabs.speakerVoiceId`. Agent replies can also use
per-reply `[[tts:speakerVoiceId=... model=eleven_v3]]` directives when TTS model
`messages.tts.providers.elevenlabs.voiceId`. Agent replies can also use
per-reply `[[tts:voiceId=... model=eleven_v3]]` directives when TTS model
overrides are enabled, but config is the deterministic default for meetings.
On join, the logs should show `transcriptionProvider=elevenlabs` and each
spoken reply should log `provider=elevenlabs model=eleven_v3 speakerVoiceId=<voiceId>`.
spoken reply should log `provider=elevenlabs model=eleven_v3 voice=<voiceId>`.
Twilio-only config:

View File

@@ -211,8 +211,6 @@ Hook guard behavior for typed lifecycle hooks:
- `params` rewrites the tool parameters for execution.
- `requireApproval` pauses the agent run and asks the user through plugin
approvals. The `/approve` command can approve both exec and plugin approvals.
In Codex app-server report-mode native `PreToolUse` relays, this is deferred
to the matching app-server approval request; see [Codex harness runtime](/plugins/codex-harness-runtime#hook-boundaries).
- A lower-priority `block: true` can still block after a higher-priority hook
requested approval.
- `onResolution` receives the resolved approval decision - `allow-once`,

View File

@@ -71,8 +71,8 @@ pnpm openclaw onboard --mode local
Verify the installed package under the state directory:
```bash
find "$OPENCLAW_STATE_DIR/npm/projects" -path '*/node_modules/@openclaw/codex/package.json' -print
grep -R '"@openclaw/codex"' "$OPENCLAW_STATE_DIR/npm/projects"/*/package-lock.json
find "$OPENCLAW_STATE_DIR/npm/node_modules" -maxdepth 3 -name package.json -print
grep -R '"@openclaw/codex"' "$OPENCLAW_STATE_DIR/npm/package-lock.json"
```
For live provider E2E, source the real API key from a trusted shell or CI secret

View File

@@ -1197,10 +1197,9 @@ Important examples:
| `openclaw.install.clawhubSpec` / `openclaw.install.npmSpec` / `openclaw.install.localPath` | Install/update hints for bundled and externally published plugins. |
| `openclaw.install.defaultChoice` | Preferred install path when multiple install sources are available. |
| `openclaw.install.minHostVersion` | Minimum supported OpenClaw host version, using a semver floor like `>=2026.3.22` or `>=2026.5.1-beta.1`. |
| `openclaw.compat.pluginApi` | Minimum OpenClaw plugin API range required by this package, using a semver floor like `>=2026.5.27`. |
| `openclaw.install.expectedIntegrity` | Expected npm dist integrity string such as `sha512-...`; install and update flows verify the fetched artifact against it. |
| `openclaw.install.allowInvalidConfigRecovery` | Allows a narrow bundled-plugin reinstall recovery path when config is invalid. |
| `openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen` | Lets setup-runtime channel surfaces load before listen, then defers the full configured channel plugin until post-listen activation. |
| `openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen` | Lets setup-only channel surfaces load before the full channel plugin during startup. |
Manifest metadata decides which provider/channel/setup choices appear in
onboarding before runtime loads. `package.json#openclaw.install` tells
@@ -1212,17 +1211,6 @@ registry loading for non-bundled plugin sources. Invalid values are rejected;
newer-but-valid values skip external plugins on older hosts. Bundled source
plugins are assumed to be co-versioned with the host checkout.
`openclaw.compat.pluginApi` is enforced during package install for non-bundled
plugin sources. Use it for the OpenClaw plugin SDK/runtime API floor that the
package was built against. It can be stricter than `minHostVersion` when a
plugin package needs a newer API but still keeps a lower install hint for other
flows. Official OpenClaw release sync bumps existing official plugin API floors
to the OpenClaw release version by default, but plugin-only releases can keep a
lower floor when the package intentionally supports older hosts. Do not use the
package version alone as the compatibility contract. `peerDependencies.openclaw`
remains npm package metadata; OpenClaw uses the `openclaw.compat.pluginApi`
contract for install compatibility decisions.
Official install-on-demand metadata should use `clawhubSpec` when the plugin is
published on ClawHub; onboarding treats that as the preferred remote source and
records ClawHub artifact facts after install. `npmSpec` remains the compatibility

View File

@@ -64,9 +64,7 @@ commands.
| [chutes](/plugins/reference/chutes) | Adds Chutes model provider support to OpenClaw. | `@openclaw/chutes-provider`<br />included in OpenClaw | providers: chutes |
| [clickclack](/plugins/reference/clickclack) | Adds the Clickclack channel surface for sending and receiving OpenClaw messages. | `@openclaw/clickclack`<br />included in OpenClaw | channels: clickclack |
| [cloudflare-ai-gateway](/plugins/reference/cloudflare-ai-gateway) | Adds Cloudflare AI Gateway model provider support to OpenClaw. | `@openclaw/cloudflare-ai-gateway-provider`<br />included in OpenClaw | providers: cloudflare-ai-gateway |
| [codex-supervisor](/plugins/reference/codex-supervisor) | Supervise Codex app-server sessions from OpenClaw. | `@openclaw/codex-supervisor`<br />included in OpenClaw | contracts: tools |
| [comfy](/plugins/reference/comfy) | Adds ComfyUI model provider support to OpenClaw. | `@openclaw/comfy-provider`<br />included in OpenClaw | providers: comfy; contracts: imageGenerationProviders, musicGenerationProviders, videoGenerationProviders |
| [copilot](/plugins/reference/copilot) | Registers the GitHub Copilot agent runtime. | `@openclaw/copilot`<br />included in OpenClaw | plugin |
| [copilot-proxy](/plugins/reference/copilot-proxy) | Adds Copilot Proxy model provider support to OpenClaw. | `@openclaw/copilot-proxy`<br />included in OpenClaw | providers: copilot-proxy |
| [deepgram](/plugins/reference/deepgram) | Adds media understanding provider support. Adds realtime transcription provider support. | `@openclaw/deepgram-provider`<br />included in OpenClaw | contracts: mediaUnderstandingProviders, realtimeTranscriptionProviders |
| [deepinfra](/plugins/reference/deepinfra) | Adds DeepInfra model provider support to OpenClaw. | `@openclaw/deepinfra-provider`<br />included in OpenClaw | providers: deepinfra; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, speechProviders, videoGenerationProviders |

View File

@@ -36,9 +36,7 @@ pnpm plugins:inventory:gen
| [clickclack](/plugins/reference/clickclack) | Adds the Clickclack channel surface for sending and receiving OpenClaw messages. | `@openclaw/clickclack`<br />included in OpenClaw | channels: clickclack |
| [cloudflare-ai-gateway](/plugins/reference/cloudflare-ai-gateway) | Adds Cloudflare AI Gateway model provider support to OpenClaw. | `@openclaw/cloudflare-ai-gateway-provider`<br />included in OpenClaw | providers: cloudflare-ai-gateway |
| [codex](/plugins/reference/codex) | OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog. | `@openclaw/codex`<br />npm; ClawHub | providers: codex; contracts: mediaUnderstandingProviders, migrationProviders |
| [codex-supervisor](/plugins/reference/codex-supervisor) | Supervise Codex app-server sessions from OpenClaw. | `@openclaw/codex-supervisor`<br />included in OpenClaw | contracts: tools |
| [comfy](/plugins/reference/comfy) | Adds ComfyUI model provider support to OpenClaw. | `@openclaw/comfy-provider`<br />included in OpenClaw | providers: comfy; contracts: imageGenerationProviders, musicGenerationProviders, videoGenerationProviders |
| [copilot](/plugins/reference/copilot) | Registers the GitHub Copilot agent runtime. | `@openclaw/copilot`<br />included in OpenClaw | plugin |
| [copilot-proxy](/plugins/reference/copilot-proxy) | Adds Copilot Proxy model provider support to OpenClaw. | `@openclaw/copilot-proxy`<br />included in OpenClaw | providers: copilot-proxy |
| [deepgram](/plugins/reference/deepgram) | Adds media understanding provider support. Adds realtime transcription provider support. | `@openclaw/deepgram-provider`<br />included in OpenClaw | contracts: mediaUnderstandingProviders, realtimeTranscriptionProviders |
| [deepinfra](/plugins/reference/deepinfra) | Adds DeepInfra model provider support to OpenClaw. | `@openclaw/deepinfra-provider`<br />included in OpenClaw | providers: deepinfra; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, speechProviders, videoGenerationProviders |

View File

@@ -1,19 +0,0 @@
---
summary: "Supervise Codex app-server sessions from OpenClaw."
read_when:
- You are installing, configuring, or auditing the codex-supervisor plugin
title: "Codex Supervisor plugin"
---
# Codex Supervisor plugin
Supervise Codex app-server sessions from OpenClaw.
## Distribution
- Package: `@openclaw/codex-supervisor`
- Install route: included in OpenClaw
## Surface
contracts: tools

View File

@@ -1,23 +0,0 @@
---
summary: "Registers the GitHub Copilot agent runtime."
read_when:
- You are installing, configuring, or auditing the copilot plugin
title: "Copilot plugin"
---
# Copilot plugin
Registers the GitHub Copilot agent runtime.
## Distribution
- Package: `@openclaw/copilot`
- Install route: included in OpenClaw
## Surface
plugin
## Related docs
- [copilot](/plugins/copilot)

View File

@@ -18,45 +18,6 @@ Adds policy-backed doctor checks for workspace conformance.
plugin
## Behavior
The Policy plugin contributes doctor health checks for policy-managed OpenClaw
settings and governed workspace declarations. Policy currently covers channel
conformance, governed tool metadata, MCP server posture, model-provider posture,
private-network access posture, Gateway exposure posture, agent workspace/tool
posture, configured global/per-agent tool posture, configured sandbox runtime
posture, and OpenClaw config secret provider/auth profile posture.
Policy stores authored requirements in `policy.jsonc`, observes existing
OpenClaw settings and workspace declarations as evidence, and reports drift
through `openclaw policy check` and `openclaw doctor --lint`. A clean policy
check emits policy, evidence, findings, and attestation hashes that operators
can record for audit.
Tool posture rules can require approved profiles, workspace-only filesystem
tools, bounded exec security/ask/host settings, disabled elevated mode, exact
`alsoAllow` entries, and required tool deny entries. The evidence records
additive `alsoAllow` entries because they can widen effective tool posture.
These checks observe config conformance only; they do not read runtime approval
state or add runtime enforcement.
Sandbox posture rules can require approved sandbox modes/backends, deny host
container networking, deny container namespace joins, require read-only container
mounts, deny container runtime socket mounts and unconfined container profiles,
and require sandbox browser CDP source ranges.
These checks observe config conformance only; they do not read runtime approval
state, inspect live containers, or add runtime enforcement.
Named agent policy scopes under `scopes.<scopeName>` can add stricter
normal policy sections for the runtime agent ids listed in `agentIds`. The
supported scoped sections are `tools`, `agents.workspace`, and `sandbox`.
Every scope present in `policy.jsonc` must be valid and
enforceable for its selector. Overlay rules are additional claims, so they do
not weaken top-level policy and can produce their own findings when the same
observed config violates both scopes. Runtime agent ids that are not explicitly
listed in `agents.list[]` are checked against inherited global/default posture
rather than silently passing with no evidence.
## Related docs
- [policy](/cli/policy)

View File

@@ -246,22 +246,11 @@ export default defineBundledChannelSetupEntry({
specifier: "./runtime-api.js",
exportName: "setMyChannelRuntime",
},
registerSetupRuntime(api) {
api.registerHttpRoute({
path: "/my-channel/events",
auth: "plugin",
handler: async (req, res) => {
/* setup-safe route */
},
});
},
});
```
Use that bundled contract only when setup flows truly need a lightweight runtime
setter or setup-safe gateway surface before the full channel entry loads.
`registerSetupRuntime` runs only for `"setup-runtime"` loads; keep it limited to
config-only routes or methods that must exist before deferred full activation.
setter before the full channel entry loads.
## Registration mode

View File

@@ -534,7 +534,7 @@ openclaw plugins install <package-name>
```
<Info>
For npm-sourced installs, `openclaw plugins install` installs the package into a per-plugin project under `~/.openclaw/npm/projects` with lifecycle scripts disabled. Keep plugin dependency trees pure JS/TS and avoid packages that require `postinstall` builds.
For npm-sourced installs, `openclaw plugins install` installs the package under `~/.openclaw/npm` with lifecycle scripts disabled. Keep plugin dependency trees pure JS/TS and avoid packages that require `postinstall` builds.
</Info>
<Note>

View File

@@ -115,7 +115,7 @@ Voice-call credentials accept SecretRefs. `plugins.entries.voice-call.config.twi
responseSystemPrompt: "You are a concise baseball card specialist.",
tts: {
providers: {
openai: { speakerVoice: "alloy" },
openai: { voice: "alloy" },
},
},
},
@@ -230,7 +230,7 @@ Current runtime behaviour:
- Provider-owned raw config lives under `realtime.providers.<providerId>`.
- Voice Call exposes the shared `openclaw_agent_consult` realtime tool by default. The realtime model can call it when the caller asks for deeper reasoning, current information, or normal OpenClaw tools.
- `realtime.consultPolicy` optionally adds guidance for when the realtime model should call `openclaw_agent_consult`.
- `realtime.agentContext.enabled` is default-off. When enabled, Voice Call injects a bounded agent identity and selected workspace-file capsule into the realtime provider instructions at session setup.
- `realtime.agentContext.enabled` is default-off. When enabled, Voice Call injects a bounded agent identity, system prompt override, and selected workspace-file capsule into the realtime provider instructions at session setup.
- `realtime.fastContext.enabled` is default-off. When enabled, Voice Call first searches indexed memory/session context for the consult question and returns those snippets to the realtime model within `realtime.fastContext.timeoutMs` before falling back to the full consult agent only if `realtime.fastContext.fallbackToConsult` is true.
- If `realtime.provider` points at an unregistered provider, or no realtime voice provider is registered at all, Voice Call logs a warning and skips realtime media instead of failing the whole plugin.
- Consult session keys reuse the stored call session when available, then fall back to the configured `sessionScope` (`per-phone` by default, or `per-call` for isolated calls).
@@ -278,6 +278,7 @@ for tool work, current information, memory lookups, or workspace state.
enabled: true,
maxChars: 6000,
includeIdentity: true,
includeSystemPrompt: true,
includeWorkspaceFiles: true,
files: ["SOUL.md", "IDENTITY.md", "USER.md"],
},
@@ -322,7 +323,7 @@ for tool work, current information, memory lookups, or workspace state.
google: {
apiKey: "${GEMINI_API_KEY}",
model: "gemini-2.5-flash-native-audio-preview-12-2025",
speakerVoice: "Kore",
voice: "Kore",
silenceDurationMs: 500,
startSensitivity: "high",
},
@@ -455,7 +456,7 @@ speech on calls. You can override it under the plugin config with the
provider: "elevenlabs",
providers: {
elevenlabs: {
speakerVoiceId: "pMsXgVXv3BLzUgSXRplE",
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},
@@ -486,7 +487,7 @@ Behavior notes:
tts: {
provider: "openai",
providers: {
openai: { speakerVoice: "alloy" },
openai: { voice: "alloy" },
},
},
},
@@ -505,7 +506,7 @@ Behavior notes:
providers: {
elevenlabs: {
apiKey: "elevenlabs_key",
speakerVoiceId: "pMsXgVXv3BLzUgSXRplE",
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},
@@ -528,7 +529,7 @@ Behavior notes:
providers: {
openai: {
model: "gpt-4o-mini-tts",
speakerVoice: "marin",
voice: "marin",
},
},
},
@@ -599,7 +600,7 @@ you can usually override only the provider voice:
tts: {
provider: "openai",
providers: {
openai: { speakerVoice: "coral" },
openai: { voice: "coral" },
},
},
numbers: {
@@ -608,7 +609,7 @@ you can usually override only the provider voice:
responseSystemPrompt: "You are a concise baseball card specialist.",
tts: {
providers: {
openai: { speakerVoice: "alloy" },
openai: { voice: "alloy" },
},
},
},

View File

@@ -45,7 +45,7 @@ provider-owned output format through `X-Microsoft-OutputFormat`.
provider: "azure-speech",
providers: {
"azure-speech": {
speakerVoice: "en-US-JennyNeural",
voice: "en-US-JennyNeural",
lang: "en-US",
},
},
@@ -69,7 +69,7 @@ provider-owned output format through `X-Microsoft-OutputFormat`.
| `region` | `messages.tts.providers.azure-speech.region` | Azure Speech resource region. Falls back to `AZURE_SPEECH_REGION` or `SPEECH_REGION`. |
| `endpoint` | `messages.tts.providers.azure-speech.endpoint` | Optional Azure Speech endpoint/base URL override. |
| `baseUrl` | `messages.tts.providers.azure-speech.baseUrl` | Optional Azure Speech base URL override. |
| `speakerVoice` | `messages.tts.providers.azure-speech.speakerVoice` | Azure voice ShortName (default `en-US-JennyNeural`). Legacy alias: `voice`. |
| `voice` | `messages.tts.providers.azure-speech.voice` | Azure voice ShortName (default `en-US-JennyNeural`). |
| `lang` | `messages.tts.providers.azure-speech.lang` | SSML language code (default `en-US`). |
| `outputFormat` | `messages.tts.providers.azure-speech.outputFormat` | Audio-file output format (default `audio-24khz-48kbitrate-mono-mp3`). |
| `voiceNoteOutputFormat` | `messages.tts.providers.azure-speech.voiceNoteOutputFormat` | Voice-note output format (default `ogg-24khz-16bit-mono-opus`). |

View File

@@ -34,7 +34,7 @@ export ELEVENLABS_API_KEY="..."
providers: {
elevenlabs: {
apiKey: "${ELEVENLABS_API_KEY}",
speakerVoiceId: "pMsXgVXv3BLzUgSXRplE",
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},

View File

@@ -44,46 +44,24 @@ generation.
The bundled `fal` image-generation provider defaults to
`fal/fal-ai/flux/dev`.
| Capability | Value |
| -------------- | ------------------------------------------------------------------ |
| Max images | 4 per request; Krea 2: 1 per request |
| Edit mode | Flux: 1 reference image; GPT Image 2: 10; Nano Banana 2: 14 |
| Style refs | Krea 2: up to 10 style references via `image` / `images` |
| Size overrides | Supported |
| Aspect ratio | Supported for generate, Krea 2, and GPT Image 2/Nano Banana 2 edit |
| Resolution | Supported |
| Output format | `png` or `jpeg` |
| Capability | Value |
| -------------- | ----------------------------------------------------------- |
| Max images | 4 per request |
| Edit mode | Flux: 1 reference image; GPT Image 2: 10; Nano Banana 2: 14 |
| Size overrides | Supported |
| Aspect ratio | Supported for generate and GPT Image 2/Nano Banana 2 edit |
| Resolution | Supported |
| Output format | `png` or `jpeg` |
<Warning>
Flux image-to-image requests do **not** support `aspectRatio` overrides. GPT
Image 2 and Nano Banana 2 edit requests use fal's `/edit` endpoint and accept
aspect-ratio hints. Nano Banana 2 also accepts extra-native wide/tall ratios
such as `4:1`, `1:4`, `8:1`, and `1:8`; Krea 2 validates its own smaller
aspect-ratio subset.
aspect-ratio hints.
</Warning>
Krea 2 models use fal's native Krea payload schema. OpenClaw sends
`aspect_ratio`, `creativity`, and `image_style_references` instead of the
generic `image_size` / edit-endpoint payload used by Flux. The model refs are:
- `fal/krea/v2/medium/text-to-image`
- `fal/krea/v2/large/text-to-image`
Use Medium for faster expressive illustration, anime, painting, and artistic
styles. Use Large for slower photoreal, raw texture, film grain, and detailed
looks. Krea defaults to `fal.creativity: "medium"`; supported values are
`raw`, `low`, `medium`, and `high`.
Krea 2 exposes aspect ratio, not `image_size`, in fal's request schema. Prefer
`aspectRatio`; OpenClaw maps `size` to the closest supported Krea aspect ratio
and rejects `resolution` for Krea rather than dropping it.
Use `outputFormat: "png"` when you want PNG output from fal models that expose
`output_format`. fal does not declare an explicit transparent-background
control in OpenClaw, so `background: "transparent"` is reported as an ignored
override for fal models.
Krea 2 endpoints do not expose an `output_format` request field through fal, so
OpenClaw rejects `outputFormat` overrides for Krea requests.
Use `outputFormat: "png"` when you want PNG output. fal does not declare an
explicit transparent-background control in OpenClaw, so `background:
"transparent"` is reported as an ignored override for fal models.
To use fal as the default image provider:
@@ -99,20 +77,6 @@ To use fal as the default image provider:
}
```
To use Krea 2 Medium:
```json5
{
agents: {
defaults: {
imageGenerationModel: {
primary: "fal/krea/v2/medium/text-to-image",
},
},
},
}
```
## Video generation
The bundled `fal` video-generation provider defaults to

Some files were not shown because too many files have changed in this diff Show More