Compare commits

..

1 Commits

Author SHA1 Message Date
Vincent Koc
6d460a4584 fix(agents): cap compaction budgets for small contexts 2026-05-31 23:20:20 +02:00
2817 changed files with 24242 additions and 82401 deletions

View File

@@ -4,11 +4,11 @@ profile: openclaw-check
provider: azure
class: standard
capacity:
market: on-demand
market: spot
strategy: most-available
# The Azure-backed billing account carries the OpenClaw runner credits; use
# explicit on-demand capacity instead of low-priority spot, whose regional
# quota is too small for broad maintainer proof or parallel Crabbox lanes.
# Fail closed instead of silently falling back to on-demand while the
# Azure-backed billing account is the default runner path.
fallback: spot-only
hints: true
actions:
workflow: .github/workflows/crabbox-hydrate.yml
@@ -49,8 +49,8 @@ aws:
region: eu-west-1
rootGB: 400
azure:
# The OpenClaw Azure subscription is reliable in eastus2; eastus rejects the
# same SKUs and can stall provisioning.
# The OpenClaw Azure subscription has reliable D2 spot capacity in eastus2;
# eastus rejects the same SKUs and can stall provisioning.
location: eastus2
sync:
delete: true
@@ -71,16 +71,14 @@ env:
- OPENCLAW_*
ssh:
user: crabbox
# Azure coordinator leases expose SSH on 22. The run wrapper can fall back
# from 2222, but `crabbox job run` hydrates via the configured port directly.
port: "22"
port: "2222"
jobs:
prewarm:
provider: azure
target: linux
class: standard
type: Standard_D4ads_v6
market: on-demand
type: Standard_D2ads_v6
market: spot
idleTimeout: 90m
hydrate:
actions: true
@@ -97,8 +95,8 @@ jobs:
provider: azure
target: linux
class: standard
type: Standard_D4ads_v6
market: on-demand
type: Standard_D2ads_v6
market: spot
idleTimeout: 90m
hydrate:
actions: true
@@ -107,18 +105,7 @@ jobs:
workflow: .github/workflows/crabbox-hydrate.yml
job: hydrate
ref: main
shell: true
command: |
set -euo pipefail
if ! git status --short >/dev/null 2>&1; then
rm -rf .git
git init -q
git add -A
if ! git diff --cached --quiet; then
git -c user.name=OpenClaw -c user.email=ci@openclaw.local commit -q --no-gpg-sign -m remote-check-tree
fi
fi
env CI=1 corepack pnpm check --timed
command: env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 CI=1 corepack pnpm check:changed
stop: always
testbox-changed:
provider: blacksmith-testbox

View File

@@ -139,139 +139,3 @@ jobs:
if: success()
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
check-arm:
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
permissions:
contents: read
name: "check-arm"
runs-on: blacksmith-16vcpu-ubuntu-2404-arm
timeout-minutes: 120
steps:
- name: Begin Testbox
uses: useblacksmith/begin-testbox@d0e04585c26905fdd92c94a09c159544c7ee1b67
with:
testbox_id: ${{ inputs.testbox_id }}
- name: Verify ARM runner
shell: bash
run: |
set -euo pipefail
runner_arch="$(uname -m)"
echo "check-arm runner architecture: ${runner_arch}"
case "$runner_arch" in
aarch64 | arm64)
;;
*)
echo "check-arm requires an ARM64 runner; got ${runner_arch}" >&2
exit 1
;;
esac
- name: Checkout
shell: bash
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ github.sha }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
if [[ -z "$CHECKOUT_TOKEN" ]]; then
echo "checkout token is missing" >&2
exit 1
fi
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
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 -C "$workdir" config gc.auto 0
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.extraheader=AUTHORIZATION: basic ${auth_header}" \
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"
- name: Prepare Testbox shell
shell: bash
run: |
set -euo pipefail
timeout --signal=TERM --kill-after=10s 30s git \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=50 origin \
"+refs/heads/main:refs/remotes/origin/main"
node_bin="$(dirname "$(node -p 'process.execPath')")"
sudo ln -sf "$node_bin/node" /usr/local/bin/node
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
#!/usr/bin/env bash
exec /usr/local/bin/corepack pnpm "$@"
PNPM
sudo chmod 0755 /usr/local/bin/pnpm
- name: Hydrate Testbox provider env helper
shell: bash
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
run: bash scripts/ci-hydrate-testbox-env.sh
- name: Run Testbox
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc
if: success()
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

View File

@@ -358,8 +358,8 @@ jobs:
$env:COREPACK_HOME = Join-Path $env:XDG_CACHE_HOME "corepack"
$env:PNPM_HOME = Join-Path $cacheRoot "pnpm-home"
$env:PNPM_CONFIG_STORE_DIR = Join-Path $cacheRoot "openclaw-pnpm-store"
$env:PNPM_CONFIG_MODULES_DIR = Join-Path $cacheRoot "openclaw-pnpm-node-modules"
$env:PNPM_CONFIG_VIRTUAL_STORE_DIR = Join-Path $env:PNPM_CONFIG_MODULES_DIR ".pnpm"
$env:PNPM_CONFIG_MODULES_DIR = Join-Path $workspace "node_modules"
$env:PNPM_CONFIG_VIRTUAL_STORE_DIR = Join-Path $workspace "node_modules\.pnpm"
$env:PNPM_CONFIG_CHILD_CONCURRENCY = "4"
$env:PNPM_CONFIG_NETWORK_CONCURRENCY = "8"
$env:PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN = "false"
@@ -431,25 +431,6 @@ jobs:
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$workspaceNodeModules = Join-Path $workspace "node_modules"
if (Test-Path $workspaceNodeModules) {
$workspaceNodeModulesItem = Get-Item $workspaceNodeModules -Force
if (($workspaceNodeModulesItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -eq 0) {
$nodeModulesChildren = @(Get-ChildItem -LiteralPath $workspaceNodeModules -Force)
$hasOnlyPnpmWorkspaceState = $nodeModulesChildren.Count -eq 1 -and $nodeModulesChildren[0].Name -eq ".pnpm-workspace-state-v1.json"
if ($nodeModulesChildren.Count -ne 0 -and -not $hasOnlyPnpmWorkspaceState) {
throw "workspace node_modules exists and is not a link: $workspaceNodeModules"
}
foreach ($nodeModulesChild in $nodeModulesChildren) {
Remove-Item -LiteralPath $nodeModulesChild.FullName -Force
}
Remove-Item -LiteralPath $workspaceNodeModules -Force
New-Item -ItemType Junction -Path $workspaceNodeModules -Target $env:PNPM_CONFIG_MODULES_DIR | Out-Null
}
} else {
New-Item -ItemType Junction -Path $workspaceNodeModules -Target $env:PNPM_CONFIG_MODULES_DIR | Out-Null
}
$corepackShimDir = Join-Path $nodeBin "node_modules\corepack\shims"
if (Test-Path $corepackShimDir) {
$env:PNPM_HOME = $corepackShimDir

View File

@@ -1953,7 +1953,7 @@ jobs:
profiles: stable full
- suite_id: native-live-src-gateway-profiles-minimax
label: Native live gateway profiles MiniMax
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M3,minimax-portal/MiniMax-M3 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 60
profile_env_only: false
profiles: stable full
@@ -2252,7 +2252,7 @@ jobs:
profiles: stable full
- suite_id: live-gateway-minimax-docker
label: Docker live gateway MiniMax
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M3,minimax-portal/MiniMax-M3 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: stable full

View File

@@ -798,7 +798,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: node scripts/build-all.mjs qaRuntime
run: pnpm build
- name: Run parity lane
env:
@@ -876,7 +876,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: node scripts/build-all.mjs qaRuntime
run: pnpm build
- name: Generate parity report
run: |
@@ -934,7 +934,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: node scripts/build-all.mjs qaRuntime
run: pnpm build
- name: Run runtime parity lane
id: runtime_parity_lane
@@ -1101,7 +1101,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: node scripts/build-all.mjs qaRuntime
run: pnpm build
- name: Run Matrix live lane
id: run_lane
@@ -1199,7 +1199,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: node scripts/build-all.mjs qaRuntime
run: pnpm build
- name: Run Telegram live lane
id: run_lane
@@ -1295,7 +1295,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: node scripts/build-all.mjs qaRuntime
run: pnpm build
- name: Run Discord live lane
id: run_lane
@@ -1393,7 +1393,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: node scripts/build-all.mjs qaRuntime
run: pnpm build
- name: Run WhatsApp live lane
id: run_lane
@@ -1488,7 +1488,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: node scripts/build-all.mjs qaRuntime
run: pnpm build
- name: Run Slack live lane
id: run_lane

View File

@@ -22,7 +22,6 @@
"eslint/no-object-constructor": "error",
"eslint/no-param-reassign": "error",
"eslint/no-proto": "error",
"eslint/no-promise-executor-return": "error",
"eslint/no-regex-spaces": "error",
"eslint/no-return-assign": "error",
"eslint/no-sequences": "error",
@@ -36,7 +35,6 @@
"eslint/no-useless-constructor": "error",
"eslint/no-useless-rename": "error",
"eslint/no-useless-return": "error",
"eslint/no-useless-assignment": "error",
"eslint/no-unused-vars": "error",
"eslint/no-warning-comments": "error",
"eslint/no-unmodified-loop-condition": "error",
@@ -82,10 +80,7 @@
"typescript/no-meaningless-void-operator": "error",
"typescript/no-misused-promises": "error",
"typescript/no-inferrable-types": "error",
"typescript/only-throw-error": "error",
"typescript/no-non-null-asserted-nullish-coalescing": "error",
"typescript/prefer-promise-reject-errors": "error",
"typescript/restrict-plus-operands": "error",
"typescript/no-unnecessary-qualifier": "error",
"typescript/no-unnecessary-type-assertion": "error",
"typescript/no-unnecessary-type-arguments": "error",
@@ -112,8 +107,6 @@
"typescript/require-array-sort-compare": "error",
"typescript/restrict-template-expressions": "error",
"typescript/triple-slash-reference": "error",
"typescript/unbound-method": "error",
"typescript/use-unknown-in-catch-callback-variable": "error",
"unicorn/consistent-date-clone": "error",
"unicorn/consistent-empty-array-spread": "error",
"unicorn/consistent-function-scoping": "off",
@@ -133,7 +126,6 @@
"unicorn/no-unnecessary-slice-end": "error",
"unicorn/no-useless-error-capture-stack-trace": "error",
"unicorn/no-useless-promise-resolve-reject": "error",
"unicorn/no-useless-switch-case": "error",
"unicorn/no-zero-fractions": "error",
"unicorn/prefer-date-now": "error",
"unicorn/prefer-dom-node-text-content": "error",

View File

@@ -2,7 +2,7 @@
Docs: https://docs.openclaw.ai
## 2026.6.1
## 2026.5.31
### Highlights
@@ -12,11 +12,7 @@ Docs: https://docs.openclaw.ai
- Skills, session metadata, gateway runtime state, plugin metadata, and store writes do less repeated work on hot paths while keeping config and dispatch behavior stable.
- Skills and plugin loading now handle stale disabled snapshots and loader failures more clearly, so channel turns avoid disabled SecretRefs and operators get better recovery guidance. (#79072, #79173) Thanks @zeus1959.
- Workboard, SecretRef plugin manifests, hosted iOS push relay, and external Copilot/Tokenjuice packaging add broader orchestration, integration, and plugin delivery surfaces. (#82326, #87469, #87796, #88107, #88117)
- Skill Workshop now has a fuller Control UI flow with proposal lists, today actions, revision handoff, searchable file previews, review states, locale coverage, and reusable session routing.
- Chat and Control UI startup paths keep sends alive through history loading, stream deltas incrementally, skip markdown work while streaming, keep drafts local while typing, trace first-output latency, and expose calmer composer controls. (#88772, #88825, #88998) Thanks @vincentkoc.
- Provider coverage and model metadata now include MiniMax M3, account OAuth endpoints, Google/Vertex catalog fixes, OpenRouter SQLite model caching, Copilot Claude 1M capabilities, Foundry reasoning alignment, and OpenAI response replay guards. (#88480, #88512, #88851, #88860)
- iMessage monitor state, inbound queues, and plugin install ledgers moved toward SQLite-backed state so restarts and local monitors recover with less duplicate filesystem scanning. (#88794, #88797)
- Release, CI, Docker, E2E, plugin install, and diagnostics lanes now cap more logs, response bodies, readiness probes, artifact checks, status polling, and rollback snapshots so failures report bounded proof instead of stalling.
- Release, CI, Docker, E2E, and diagnostics lanes now cap more logs, response bodies, readiness probes, artifact checks, and status polling so failures report bounded proof instead of stalling.
### Changes
@@ -25,46 +21,29 @@ Docs: https://docs.openclaw.ai
- Skills: let proposals carry approved support files under standard skill folders, with scanner, hash, and rollback safeguards. Thanks @shakkernerd.
- Skills: let pending proposals be revised in place with versioned, dated proposal frontmatter before approval. Thanks @shakkernerd.
- Skills: add Skill Workshop with pending proposals, CLI/Gateway review actions, rollback metadata, and the `skill_workshop` agent tool. Thanks @shakkernerd.
- Skill Workshop: add the Control UI navigation, styled dashboard, proposal today view, revision dialog, file preview modal, searchable preview files, reusable session handoff, and localized strings.
- Plugins: externalize Tokenjuice as the official `@openclaw/tokenjuice` plugin with npm and ClawHub publish metadata.
- Plugins: externalize the GitHub Copilot agent runtime as the official `@openclaw/copilot` plugin with npm and ClawHub publish metadata.
- iOS: add hosted push relay defaults, realtime Talk playback, and a guarded WebSocket ping path for more reliable mobile sessions. (#88096, #88105, #88231)
- iOS: support native iPad display layouts.
- Workboard: add orchestration primitives and agent coordination tools for multi-agent planning and run tracking. (#87469)
- Workboard: wire task-backed board runs and show task comments in the edit modal.
- Code mode: add internal namespaces for scoped agent/global sessions and exact namespace tool dispatch. (#88043)
- Code mode: add MCP API files and docs for code-mode integrations.
- Control UI: add a Dreaming-tab agent selector and propagate the selected agent through Dreaming status, diary, and diary actions. (#78748) Thanks @stevenepalmer.
- Control UI: add calmer chat composer controls, local draft typing state, and first-output latency instrumentation for active chat entry. (#88772, #88998) Thanks @vincentkoc.
- Plugins: add a SecretRef provider integration manifest contract and extract shared LLM core packages for provider/plugin reuse. (#82326, #88117)
- Plugins: persist the plugin install index in SQLite so installed package lookup survives reloads with less filesystem scanning. (#88794)
- Providers: add MiniMax M3 model support. (#88860)
- Doctor: add disk space health checks and stabilize post-upgrade JSON probes.
- Channels: store inbound queues in SQLite and migrate iMessage monitor state to SQLite-backed tracking. (#88797)
- Skills: add the core skills index and centralize skills runtime loading, status, filtering, and prompt formatting.
### Fixes
- Agents/TUI: keep local custom provider runs from loading plugin runtime and auth alias metadata when plugins are disabled.
- Agents/TUI: restore in-flight TUI run switch-back behavior, keep no-policy native hook fallback available, guard vanished workspaces, and keep lightweight isolated subagents lightweight.
- Agents/media: keep async image, music, and video generation starts from ending the Codex turn, so mixed requests can continue with summaries or other work while media renders in the background.
- Agents/Codex: keep public OpenAI API-key profiles from being treated as native Codex app-server auth while preserving persisted Codex OAuth sessions.
- Agents/Codex: stream Codex app-server final-answer partials to live reply previews, preserve ACP metadata in SQLite, prefer real tool results over synthetic repair output, prevent aborted app-server turn handles from lingering, migrate legacy OpenAI Codex `lastGood` auth state, and preserve workspace/session metadata through ACP runtime refactors. (#88405, #88724, #88730) Thanks @vincentkoc.
- Control UI: keep collapsed tool cards labeled with the tool name and action instead of generic output text. Thanks @shakkernerd.
- Agents/Codex: surface Skill Workshop guidance in Codex app-server prompts when `skill_workshop` is available. Thanks @shakkernerd.
- Agents/auth: write auth profiles atomically, add force re-login recovery, preserve workspaces during state-only uninstall, and compact before oversized turns so recovery paths avoid partial state.
- Skills: skip disabled skill env overrides from stale persisted snapshots so disabled skill `apiKey` SecretRefs cannot abort embedded or channel turns. (#79072, #79173) Thanks @zeus1959.
- CLI: avoid live catalog validation during `openclaw agents add`, so adding a secondary agent no longer depends on provider catalog availability. (#76284, #88314) Thanks @zhangguiping-xydt.
- CLI: keep `plugins list --json` on the snapshot-only path so plugin sweeps avoid loading the full runtime status graph.
- CLI/desktop: bridge WSL clipboard operations through the shell and recognize manual-update launchd jobs. (#88764)
- Plugins: make PixVerse external-plugin ClawHub metadata explicit and keep it out of bundled dist builds.
- Plugins: clarify plugin loader failure guidance so missing or incompatible plugin packages point operators at the right repair path.
- Plugins: preserve npm plugin roots after blocked installs, skip plugin-local `openclaw` peer symlinks during rollback snapshots, relink those peers after restore, isolate cached tool runtime siblings, and isolate web-provider factory failures so one bad plugin does not poison sibling runtime paths. (#77237, #88807)
- Cron: keep SQLite cron migrations compatible with legacy run-log tables, archived job stores, diagnostic cron names, and legacy one-shot delete-after-run behavior. (#88285)
- Cron: keep update delivery validation scoped, harden restart state, and retire MCP runtimes on isolated cron cleanup.
- Memory: serialize QMD update/embed writes per store, preserve phase signals on read errors, harden envelope metadata sanitization, and rewrite generated transcript paths on rollover so memory/search state survives concurrent gateway and CLI activity. (#66339, #85931) Thanks @openperf and @amittell.
- Providers: bound generated media downloads from OpenAI, Runway, xAI, MiniMax, BytePlus, DashScope-compatible, FAL, OpenRouter, Google, Vydra, and Comfy providers.
- Providers: resolve Google defaults to `google-generative-ai`, register Vertex static catalog rows, align Foundry reasoning metadata, skip DeepSeek V4 thinking params on Foundry fallback, use MiniMax account OAuth endpoints, preserve Copilot Claude 1M capabilities, suppress disabled Ollama reasoning output, keep OpenAI stop-finished tool calls, and avoid replay ids when the Responses store is disabled. (#88480, #88512)
- Providers: cap GitHub Copilot OAuth request timeouts before creating abort signals.
- Cron: retry recurring jobs after transient model rate limits before waiting for the next scheduled slot.
- Agents/Codex: keep live session locks during cleanup, recover interrupted CLI tool transcripts, preserve Codex auth and compaction session identity, clear orphan tool state, cap app-server idle timers, and keep media completion delivery retryable. (#88129, #88136, #88141, #88162, #88182)
@@ -72,19 +51,13 @@ Docs: https://docs.openclaw.ai
- Channels: cap Telegram, Discord, WhatsApp, Signal, Feishu, Google Chat, Microsoft Teams, QQBot, Nostr, Zalo, Zalouser, and Nextcloud-style request/retry timers; preserve SMS approval reply routes; and retry WhatsApp QR login 408 timeouts. (#88183)
- Security/config parsing: reject unsafe OAuth/token lifetimes, retry-after delays, inbound timestamps, response body sizes, command timeout config, sandbox observer token TTLs, and gateway WebSocket calls after close.
- Providers/media: cap local service, model, usage, queue, generated media, TTS, music, workflow polling, and provider OAuth request timers across hosted and local providers.
- Release/CI/E2E: bound release candidate reads, beta smoke REST calls, plugin npm verification commands, changelog restore, cross-OS process groups, kitchen-sink and bundled plugin readiness probes, secret-provider probes, Telegram credential timeouts, Control UI i18n and CLI startup metadata generation, Vitest routing, and mainline test flakes. (#88127, #88137, #88155, #88160)
- Release/CI/E2E: keep Kitchen Sink live plugin MCP probes resolving source-checkout workspace packages and align the live gauntlet with current Kitchen Sink diagnostics.
- Release/CI/E2E: bound release candidate reads, beta smoke REST calls, changelog restore, kitchen-sink and bundled plugin readiness probes, secret-provider probes, Vitest routing, and mainline test flakes. (#88127, #88137, #88155, #88160)
- Release/CI/E2E: run the secret-provider integration proof through the repo pnpm runner so native macOS and Windows validation use the hydrated package-manager shim.
- Release/CI/E2E: run the Telegram desktop proof gateway through the repo pnpm runner so native macOS proof uses the hydrated package-manager shim.
- Docs/CI: run Mintlify anchor checks through the repo pnpm runner so docs link validation works when pnpm is only available through the hydrated package-manager shim.
- Agents: keep configured fallback model metadata typed so provider params, context-token caps, and media input limits do not break changed-gate typechecks.
- Agents: accept hidden `sessions_send` body aliases before validation while keeping the model-facing `message` schema canonical. (#88229) Thanks @zhangguiping-xydt.
- Chat/UI: preserve startup chat sends during history loading, unblock the initial Control UI chat send, stream chat deltas incrementally, skip markdown parsing while streaming, keep drafts local while typing, guard composer rerenders, honor Chromium executable overrides, and detect system Chromium for E2E. (#88998) Thanks @vincentkoc.
- Channels: preserve long Feishu streaming replies, send visible fallbacks when accepted Feishu turns produce no final reply, tolerate iMessage self-chat timestamp skew, preserve colon-prefixed slash commands in mention parsing, decode Nostr `npub` allowlists correctly, and suppress raw provider errors during channel delivery. (#87896)
- Config/status/doctor: skip unresolved shell references in state-dir dotenv files, resolve gateway auth secrets during deep status audits, respect explicit PI runtime policy, report runtime tool-schema errors, and keep post-upgrade JSON stable. (#88288)
- Gateway/session state: list commands from the Gateway plugin registry, harden MCP loopback tool schemas, hide phantom agent-store rows from `sessions.list`, make task persistence failures explicit, and carry session UUIDs on interactive dispatch events.
- OpenAI/TTS: handle speed directives for OpenAI TTS voices. (#74089)
- CI/Crabbox: keep default runner capacity on the Azure credit-backed on-demand D4 lane with the Azure SSH port and a Git-independent full check job, so broad validation avoids low-priority spot quota stalls, hydrate port mismatches, non-Git hydrated workspaces, and stale AWS region hints.
- CI/Crabbox: keep default runner capacity spot-only and provider-neutral so OpenClaw remote validation does not silently fall back to on-demand leases or stale AWS region hints.
- CI/Crabbox: route Crabbox wrapper and Testbox workflow edits to their regression tests so changed-test gates do not silently run zero specs.
- CI/workflows: route workflow sanity helper edits to their guard tests and cover composite-action input interpolation checks.
- CI/tooling: route CI scope, dependency, changelog, and docs helper edits to their owner tests instead of silently skipping changed-test coverage.

View File

@@ -66,7 +66,7 @@ android {
minSdk = 31
targetSdk = 36
versionCode = 2026053101
versionName = "2026.6.1"
versionName = "2026.5.31"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")

View File

@@ -1,82 +0,0 @@
package ai.openclaw.app.ui
import ai.openclaw.app.node.DeviceNotificationListenerService
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
/** App entry shown in the notification-forwarding package picker. */
data class InstalledApp(
val label: String,
val packageName: String,
val isSystemApp: Boolean,
)
/** Reads launcher, recent-notification, and configured packages for the picker. */
internal fun queryInstalledApps(
context: Context,
configuredPackages: Set<String>,
): List<InstalledApp> {
val packageManager = context.packageManager
val launcherIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
val launcherPackages =
packageManager
.queryIntentActivities(launcherIntent, PackageManager.MATCH_ALL)
.asSequence()
.mapNotNull {
it.activityInfo
?.packageName
?.trim()
?.takeIf(String::isNotEmpty)
}.toMutableSet()
val recentNotificationPackages =
DeviceNotificationListenerService
.recentPackages(context)
.asSequence()
.map { it.trim() }
.filter { it.isNotEmpty() }
.toList()
val candidatePackages =
resolveNotificationCandidatePackages(
launcherPackages = launcherPackages,
recentPackages = recentNotificationPackages,
configuredPackages = configuredPackages,
appPackageName = context.packageName,
)
return candidatePackages
.asSequence()
.mapNotNull { packageName ->
runCatching {
val appInfo = packageManager.getApplicationInfo(packageName, 0)
val label = packageManager.getApplicationLabel(appInfo).toString().trim()
InstalledApp(
label = if (label.isEmpty()) packageName else label,
packageName = packageName,
isSystemApp = (appInfo.flags and android.content.pm.ApplicationInfo.FLAG_SYSTEM) != 0,
)
}.getOrNull()
}.sortedWith(compareBy<InstalledApp> { it.label.lowercase() }.thenBy { it.packageName })
.toList()
}
/** Merges package sources while excluding OpenClaw from its own forwarding filter. */
internal fun resolveNotificationCandidatePackages(
launcherPackages: Set<String>,
recentPackages: List<String>,
configuredPackages: Set<String>,
appPackageName: String,
): Set<String> {
val blockedPackage = appPackageName.trim()
return sequenceOf(
configuredPackages.asSequence(),
launcherPackages.asSequence(),
recentPackages.asSequence(),
).flatten()
.map { it.trim() }
.filter { it.isNotEmpty() && it != blockedPackage }
.toSet()
}

View File

@@ -493,8 +493,6 @@ private fun playVoiceSetupTone() {
Handler(Looper.getMainLooper()).postDelayed({ tone.release() }, 300L)
}
private const val NOTIFICATION_PICKER_RESULT_LIMIT = 40
@Composable
private fun NotificationSettingsScreen(
viewModel: MainViewModel,
@@ -509,19 +507,6 @@ private fun NotificationSettingsScreen(
val quietEnd by viewModel.notificationForwardingQuietEnd.collectAsState()
val maxEventsPerMinute by viewModel.notificationForwardingMaxEventsPerMinute.collectAsState()
val modeLabel = if (mode == NotificationPackageFilterMode.Blocklist) "Blocklist" else "Allowlist"
val installedApps = remember(context, packages) { queryInstalledApps(context, packages) }
var notificationPickerExpanded by remember { mutableStateOf(false) }
var notificationAppSearch by remember { mutableStateOf("") }
var notificationShowSystemApps by remember { mutableStateOf(false) }
val filteredApps =
remember(installedApps, packages, notificationAppSearch, notificationShowSystemApps) {
filterNotificationAppsForPicker(
apps = installedApps,
selectedPackages = packages,
query = notificationAppSearch,
showSystemApps = notificationShowSystemApps,
)
}
var listenerEnabled by remember { mutableStateOf(DeviceNotificationListenerService.isAccessEnabled(context)) }
val notificationPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
@@ -582,124 +567,6 @@ private fun NotificationSettingsScreen(
)
}
}
NotificationPackagePickerPanel(
mode = mode,
selectedPackages = packages,
apps = filteredApps,
search = notificationAppSearch,
showSystemApps = notificationShowSystemApps,
expanded = notificationPickerExpanded,
onSearchChange = { notificationAppSearch = it },
onShowSystemAppsChange = { notificationShowSystemApps = it },
onExpandedChange = { notificationPickerExpanded = it },
onPackageSelectionChange = { packageName, selected ->
val next = packages.toMutableSet()
if (selected) {
next.add(packageName)
} else {
next.remove(packageName)
}
viewModel.setNotificationForwardingPackagesCsv(next.sorted().joinToString(","))
},
)
}
}
@Composable
private fun NotificationPackagePickerPanel(
mode: NotificationPackageFilterMode,
selectedPackages: Set<String>,
apps: List<InstalledApp>,
search: String,
showSystemApps: Boolean,
expanded: Boolean,
onSearchChange: (String) -> Unit,
onShowSystemAppsChange: (Boolean) -> Unit,
onExpandedChange: (Boolean) -> Unit,
onPackageSelectionChange: (String, Boolean) -> Unit,
) {
val visibleApps = apps.take(NOTIFICATION_PICKER_RESULT_LIMIT)
ClawPanel {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text(text = "App Filter", style = ClawTheme.type.section, color = ClawTheme.colors.text)
Text(
text = notificationPackageSelectionSummary(mode = mode, selectedCount = selectedPackages.size),
style = ClawTheme.type.body,
color = ClawTheme.colors.textMuted,
)
ClawSecondaryButton(
text = if (expanded) "Close App Picker" else "Open App Picker",
onClick = { onExpandedChange(!expanded) },
modifier = Modifier.fillMaxWidth(),
)
if (expanded) {
ClawTextField(value = search, onValueChange = onSearchChange, placeholder = "Search apps")
SettingsToggleListRow(
SettingsToggleRow(
title = "Show System Apps",
subtitle = "Include Android and background packages.",
icon = Icons.Default.Storage,
checked = showSystemApps,
onCheckedChange = onShowSystemAppsChange,
),
)
if (visibleApps.isEmpty()) {
Text(text = "No matching apps.", style = ClawTheme.type.body, color = ClawTheme.colors.textMuted)
} else {
ClawSeparatedColumn(items = visibleApps) { app ->
NotificationPackageAppRow(
app = app,
selected = selectedPackages.contains(app.packageName),
onSelectedChange = { selected -> onPackageSelectionChange(app.packageName, selected) },
)
}
if (apps.size > visibleApps.size) {
Text(
text = "Showing ${visibleApps.size} of ${apps.size}. Refine search for more.",
style = ClawTheme.type.caption,
color = ClawTheme.colors.textMuted,
)
}
}
}
}
}
}
@Composable
private fun NotificationPackageAppRow(
app: InstalledApp,
selected: Boolean,
onSelectedChange: (Boolean) -> Unit,
) {
Row(
modifier =
Modifier
.fillMaxWidth()
.heightIn(min = 58.dp)
.clickable { onSelectedChange(!selected) }
.padding(vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(9.dp),
) {
ClawTextBadge(text = notificationAppBadge(app.label))
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
Text(
text = app.label,
style = ClawTheme.type.body,
color = ClawTheme.colors.text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = app.packageName,
style = ClawTheme.type.caption,
color = ClawTheme.colors.textMuted,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Switch(checked = selected, onCheckedChange = onSelectedChange)
}
}
@@ -1245,55 +1112,6 @@ private fun cronJobStatus(job: GatewayCronJobSummary): ClawStatus {
}
}
internal fun filterNotificationAppsForPicker(
apps: List<InstalledApp>,
selectedPackages: Set<String>,
query: String,
showSystemApps: Boolean,
): List<InstalledApp> {
val normalizedQuery = query.trim().lowercase()
return apps.filter { app ->
val selected = app.packageName in selectedPackages
val visibleByType = showSystemApps || !app.isSystemApp || selected
val visibleBySearch =
normalizedQuery.isEmpty() ||
app.label.lowercase().contains(normalizedQuery) ||
app.packageName.lowercase().contains(normalizedQuery)
visibleByType && visibleBySearch
}
}
private fun notificationPackageSelectionSummary(
mode: NotificationPackageFilterMode,
selectedCount: Int,
): String =
when (mode) {
NotificationPackageFilterMode.Allowlist ->
if (selectedCount == 0) {
"No apps selected. Nothing forwards until you add apps."
} else {
"$selectedCount ${if (selectedCount == 1) "app" else "apps"} allowed to forward."
}
NotificationPackageFilterMode.Blocklist ->
if (selectedCount == 0) {
"No apps blocked. Apps can forward unless you add blocks."
} else {
"$selectedCount ${if (selectedCount == 1) "app" else "apps"} blocked from forwarding."
}
}
private fun notificationAppBadge(label: String): String {
val initials =
label
.split(' ', '-', '_', '.')
.asSequence()
.filter { it.isNotBlank() }
.take(2)
.mapNotNull { it.firstOrNull()?.uppercaseChar()?.toString() }
.joinToString("")
return initials.ifBlank { "A" }
}
/**
* Converts cron wake times into short relative labels for scheduled-work rows.
*/

View File

@@ -1222,6 +1222,82 @@ fun SettingsSheet(viewModel: MainViewModel) {
}
}
/** App entry shown in the notification-forwarding package picker. */
data class InstalledApp(
val label: String,
val packageName: String,
val isSystemApp: Boolean,
)
/** Reads launcher, recent-notification, and configured packages for the picker. */
private fun queryInstalledApps(
context: Context,
configuredPackages: Set<String>,
): List<InstalledApp> {
val packageManager = context.packageManager
val launcherIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
val launcherPackages =
packageManager
.queryIntentActivities(launcherIntent, PackageManager.MATCH_ALL)
.asSequence()
.mapNotNull {
it.activityInfo
?.packageName
?.trim()
?.takeIf(String::isNotEmpty)
}.toMutableSet()
val recentNotificationPackages =
DeviceNotificationListenerService
.recentPackages(context)
.asSequence()
.map { it.trim() }
.filter { it.isNotEmpty() }
.toList()
val candidatePackages =
resolveNotificationCandidatePackages(
launcherPackages = launcherPackages,
recentPackages = recentNotificationPackages,
configuredPackages = configuredPackages,
appPackageName = context.packageName,
)
return candidatePackages
.asSequence()
.mapNotNull { packageName ->
runCatching {
val appInfo = packageManager.getApplicationInfo(packageName, 0)
val label = packageManager.getApplicationLabel(appInfo).toString().trim()
InstalledApp(
label = if (label.isEmpty()) packageName else label,
packageName = packageName,
isSystemApp = (appInfo.flags and android.content.pm.ApplicationInfo.FLAG_SYSTEM) != 0,
)
}.getOrNull()
}.sortedWith(compareBy<InstalledApp> { it.label.lowercase() }.thenBy { it.packageName })
.toList()
}
/** Merges package sources while excluding OpenClaw from its own forwarding filter. */
internal fun resolveNotificationCandidatePackages(
launcherPackages: Set<String>,
recentPackages: List<String>,
configuredPackages: Set<String>,
appPackageName: String,
): Set<String> {
val blockedPackage = appPackageName.trim()
return sequenceOf(
configuredPackages.asSequence(),
launcherPackages.asSequence(),
recentPackages.asSequence(),
).flatten()
.map { it.trim() }
.filter { it.isNotEmpty() && it != blockedPackage }
.toSet()
}
/** Shared Material text-field colors for the legacy mobile settings sheet. */
@Composable
private fun settingsTextFieldColors() =

View File

@@ -32,46 +32,4 @@ class SettingsSheetNotificationAppsTest {
assertEquals(setOf("com.example.recent", "com.example.configured"), packages)
}
@Test
fun filterNotificationAppsForPicker_keepsSelectedSystemPackagesVisible() {
val apps =
listOf(
InstalledApp(label = "Android System", packageName = "android", isSystemApp = true),
InstalledApp(label = "Phone Services", packageName = "com.android.phone", isSystemApp = true),
InstalledApp(label = "Gmail", packageName = "com.google.android.gm", isSystemApp = false),
)
val filtered =
filterNotificationAppsForPicker(
apps = apps,
selectedPackages = setOf("com.android.phone"),
query = "",
showSystemApps = false,
)
assertEquals(
listOf("com.android.phone", "com.google.android.gm"),
filtered.map { it.packageName },
)
}
@Test
fun filterNotificationAppsForPicker_matchesLabelsAndPackageNames() {
val apps =
listOf(
InstalledApp(label = "Gmail", packageName = "com.google.android.gm", isSystemApp = false),
InstalledApp(label = "Calendar", packageName = "com.google.android.calendar", isSystemApp = false),
)
val filtered =
filterNotificationAppsForPicker(
apps = apps,
selectedPackages = emptySet(),
query = "gm",
showSystemApps = false,
)
assertEquals(listOf("com.google.android.gm"), filtered.map { it.packageName })
}
}

View File

@@ -1,6 +1,6 @@
# OpenClaw iOS Changelog
## 2026.6.1 - 2026-06-01
## 2026.5.31 - 2026-05-31
Maintenance update for the current OpenClaw release.

View File

@@ -2,8 +2,8 @@
// Source of truth: apps/ios/version.json
// Generated by scripts/ios-sync-versioning.ts.
OPENCLAW_IOS_VERSION = 2026.6.1
OPENCLAW_MARKETING_VERSION = 2026.6.1
OPENCLAW_IOS_VERSION = 2026.5.31
OPENCLAW_MARKETING_VERSION = 2026.5.31
OPENCLAW_BUILD_VERSION = 1
#include? "../build/Version.xcconfig"

View File

@@ -1,6 +1,6 @@
# OpenClaw iOS (Super Alpha)
This iOS app is super-alpha and internal-use only. It connects to an OpenClaw Gateway as a `role: node` on iPhone and iPad.
This iPhone app is super-alpha and internal-use only. It connects to an OpenClaw Gateway as a `role: node`.
## Distribution Status
@@ -34,7 +34,7 @@ open OpenClaw.xcodeproj
3. In Xcode:
- Scheme: `OpenClaw`
- Destination: connected iPhone or iPad (recommended for real behavior)
- Destination: connected iPhone (recommended for real behavior)
- Build configuration: `Debug`
- Run (`Product` -> `Run`)
4. If signing fails on a personal team:
@@ -245,13 +245,13 @@ gateway can only send pushes for iOS devices that paired with that gateway.
- Pairing via QR or setup code flow (`/pair qr` or `/pair`, then `/pair approve` in Telegram).
- Gateway connection via discovery or manual host/port with TLS fingerprint trust prompt.
- Chat + Talk surfaces through the operator gateway session.
- iOS node commands in foreground: camera snap/clip, canvas present/navigate/eval/snapshot, screen record, location, contacts, calendar, reminders, photos, motion, local notifications.
- iPhone node commands in foreground: camera snap/clip, canvas present/navigate/eval/snapshot, screen record, location, contacts, calendar, reminders, photos, motion, local notifications.
- Authenticated background `node.presence.alive` beacons that update gateway last-seen metadata when the app moves between foreground and background, without treating suspended sockets as connected.
- Share extension deep-link forwarding into the connected gateway session.
## Computer Use Relationship
The iOS app is not a Codex Computer Use backend. Computer Use and `cua-driver mcp` are macOS desktop-control paths; iOS exposes device capabilities as OpenClaw node commands through the gateway. Agents can drive the iPhone or iPad canvas, camera, screen, location, voice, and other node capabilities with `node.invoke`, subject to iOS foreground/background limits.
The iOS app is not a Codex Computer Use backend. Computer Use and `cua-driver mcp` are macOS desktop-control paths; iOS exposes device capabilities as OpenClaw node commands through the gateway. Agents can drive the iPhone canvas, camera, screen, location, voice, and other node capabilities with `node.invoke`, subject to iOS foreground/background limits.
## Location Automation Use Case (Testing)

View File

@@ -50,11 +50,6 @@ struct ChatProTab: View {
.onChange(of: self.appModel.chatSessionKey) { _, _ in
self.syncChatViewModel()
}
.onChange(of: self.appModel.isOperatorGatewayConnected) { _, connected in
guard connected else { return }
self.syncChatViewModel()
self.viewModel?.refresh()
}
}
private var header: some View {
@@ -156,8 +151,7 @@ struct ChatProTab: View {
}
private var gatewayConnected: Bool {
GatewayStatusBuilder.build(appModel: self.appModel) == .connected &&
self.appModel.isOperatorGatewayConnected
GatewayStatusBuilder.build(appModel: self.appModel) == .connected
}
private var chatUserAccent: Color {

View File

@@ -45,7 +45,6 @@ struct SettingsProTab: View {
@State var gatewayPassword = ""
@State var manualGatewayPortText = ""
@State var setupStatusText: String?
@State var stagedGatewaySetupLink: GatewayConnectDeepLink?
@State var pendingManualAuthOverride: GatewayConnectionController.ManualAuthOverride?
@State var defaultShareInstruction = ""
@State var showGatewayProblemDetails = false
@@ -83,7 +82,6 @@ struct SettingsProTab: View {
self.previousLocationModeRaw = self.locationModeRaw
self.syncSettingsState()
self.refreshNotificationSettings()
self.applyPendingGatewaySetupLinkIfNeeded()
}
.onChange(of: self.scenePhase) { _, phase in
if phase == .active {
@@ -109,17 +107,9 @@ struct SettingsProTab: View {
.onChange(of: self.gatewayPassword) { _, newValue in
self.persistGatewayPassword(newValue)
}
.onChange(of: self.setupCode) { _, newValue in
if !newValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
self.stagedGatewaySetupLink = nil
}
}
.onChange(of: self.defaultShareInstruction) { _, newValue in
ShareToAgentSettings.saveDefaultInstruction(newValue)
}
.onChange(of: self.appModel.gatewaySetupRequestID) { _, _ in
self.applyPendingGatewaySetupLinkIfNeeded()
}
}
.sheet(isPresented: self.$showGatewayProblemDetails) {
if let gatewayProblem = self.appModel.lastGatewayProblem {

View File

@@ -202,29 +202,17 @@ extension SettingsProTab {
await self.connectManual()
}
func applyPendingGatewaySetupLinkIfNeeded() {
guard let link = self.appModel.consumePendingGatewaySetupLink() else { return }
self.setupCode = ""
self.setupStatusText = nil
self.stagedGatewaySetupLink = link
let security = link.tls ? "TLS" : "plain"
self.setupStatusText = "Setup link loaded for \(link.host):\(link.port) (\(security)). Tap Connect to apply."
}
@discardableResult
func applySetupCode() -> Bool {
let raw = self.setupCode.trimmingCharacters(in: .whitespacesAndNewlines)
let stagedLink = self.stagedGatewaySetupLink
guard !raw.isEmpty || stagedLink != nil else {
guard !raw.isEmpty else {
self.setupStatusText = "Paste a setup code to continue."
return false
}
guard let link = raw.isEmpty ? stagedLink : GatewayConnectDeepLink.fromSetupInput(raw) else {
guard let link = GatewayConnectDeepLink.fromSetupInput(raw) else {
self.setupStatusText = "Setup code not recognized or uses an insecure ws:// gateway URL."
return false
}
self.stagedGatewaySetupLink = nil
self.applyGatewayLink(link)
return true
}
@@ -311,7 +299,7 @@ extension SettingsProTab {
let trimmed = host.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return false }
if Self.isTailnetHostOrIP(trimmed), !Self.hasTailnetIPv4() {
self.setupStatusText = "Tailscale is off on this device. Turn it on, then try again."
self.setupStatusText = "Tailscale is off on this iPhone. Turn it on, then try again."
return false
}
self.setupStatusText = "Checking gateway reachability..."
@@ -522,15 +510,10 @@ extension SettingsProTab {
return gatewayStatus
}
var canApplyGatewaySetup: Bool {
!self.setupCode.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|| self.stagedGatewaySetupLink != nil
}
var tailnetWarningText: String? {
let host = self.manualGatewayHost.trimmingCharacters(in: .whitespacesAndNewlines)
guard !host.isEmpty, Self.isTailnetHostOrIP(host), !Self.hasTailnetIPv4() else { return nil }
return "This gateway is on your tailnet. Turn on Tailscale on this device, then tap Connect."
return "This gateway is on your tailnet. Turn on Tailscale on this iPhone, then tap Connect."
}
func friendlyGatewayMessage(from raw: String) -> String? {

View File

@@ -542,7 +542,7 @@ extension SettingsProTab {
{
Task { await self.applySetupCodeAndConnect() }
}
.disabled(!self.canApplyGatewaySetup)
.disabled(self.setupCode.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
if let status = self.setupStatusLine {
Text(status)

View File

@@ -111,7 +111,7 @@ struct GatewayProblemBanner: View {
case .gateway:
"Fix on gateway"
case .iphone:
"Fix on this device"
"Fix on iPhone"
case .both:
"Check both"
case .network:
@@ -227,9 +227,9 @@ struct GatewayProblemDetailsSheet: View {
case .gateway:
"Primary fix: gateway"
case .iphone:
"Primary fix: this device"
"Primary fix: this iPhone"
case .both:
"Primary fix: check both this device and the gateway"
"Primary fix: check both this iPhone and the gateway"
case .network:
"Primary fix: network or remote access"
case .unknown:

View File

@@ -138,9 +138,7 @@ final class NodeAppModel {
var homeCanvasRevision: Int = 0
var lastShareEventText: String = "No share events yet."
var openChatRequestID: Int = 0
var gatewaySetupRequestID: Int = 0
private(set) var pendingAgentDeepLinkPrompt: AgentDeepLinkPrompt?
private var pendingGatewaySetupLink: GatewayConnectDeepLink?
private(set) var pendingExecApprovalPrompt: ExecApprovalPrompt?
private(set) var pendingExecApprovalPromptResolving: Bool = false
private(set) var pendingExecApprovalPromptErrorText: String?
@@ -4136,23 +4134,11 @@ extension NodeAppModel {
switch route {
case let .agent(link):
await self.handleAgentDeepLink(link, originalURL: url)
case let .gateway(link):
self.stageGatewaySetupLink(link)
case .dashboard:
case .gateway, .dashboard:
break
}
}
func stageGatewaySetupLink(_ link: GatewayConnectDeepLink) {
self.pendingGatewaySetupLink = link
self.gatewaySetupRequestID &+= 1
}
func consumePendingGatewaySetupLink() -> GatewayConnectDeepLink? {
defer { self.pendingGatewaySetupLink = nil }
return self.pendingGatewaySetupLink
}
private func handleAgentDeepLink(_ link: AgentDeepLink, originalURL: URL) async {
let message = link.message.trimmingCharacters(in: .whitespacesAndNewlines)
guard !message.isEmpty else { return }

View File

@@ -7,7 +7,7 @@ struct OnboardingIntroStep: View {
VStack(spacing: 0) {
Spacer()
Image(systemName: UIDevice.current.userInterfaceIdiom == .pad ? "ipad" : "iphone.gen3")
Image(systemName: "iphone.gen3")
.font(.system(size: 60, weight: .semibold))
.foregroundStyle(.tint)
.padding(.bottom, 18)
@@ -17,7 +17,7 @@ struct OnboardingIntroStep: View {
.multilineTextAlignment(.center)
.padding(.bottom, 10)
Text("Turn this device into a secure OpenClaw node for chat, voice, camera, and device tools.")
Text("Turn this iPhone into a secure OpenClaw node for chat, voice, camera, and device tools.")
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
@@ -114,7 +114,7 @@ struct OnboardingWelcomeStep: View {
.foregroundStyle(.secondary)
Text("/pair qr")
.font(.system(.footnote, design: .monospaced).weight(.semibold))
Text("Then scan the QR code here to connect this device.")
Text("Then scan the QR code here to connect this iPhone.")
.font(.footnote)
.foregroundStyle(.secondary)
}

View File

@@ -669,8 +669,8 @@ extension OpenClawApp {
switch route {
case .agent, .dashboard:
await self.appModel.handleDeepLink(url: url)
case let .gateway(link):
self.appModel.stageGatewaySetupLink(link)
case .gateway:
break
}
}

View File

@@ -32,7 +32,6 @@ struct RootTabs: View {
@State private var didAutoOpenSettings: Bool = false
@State private var didApplyInitialAppearance: Bool = false
@State private var didApplyInitialChatSession: Bool = false
@State private var handledGatewaySetupRequestID: Int = 0
private enum AppTab: Hashable {
case control
@@ -238,7 +237,6 @@ struct RootTabs: View {
.onAppear { self.updateCanvasState() }
.onAppear { self.evaluateOnboardingPresentation(force: false) }
.onAppear { self.maybeAutoOpenSettings() }
.onAppear { self.maybeOpenSettingsForGatewaySetup() }
.onAppear { self.maybeShowQuickSetup() }
.onAppear { self.applyInitialAppearanceIfNeeded() }
.onAppear { self.applyInitialChatSessionIfNeeded() }
@@ -298,9 +296,6 @@ struct RootTabs: View {
.onChange(of: self.appModel.openChatRequestID) { _, _ in
self.selectedTab = .chat
}
.onChange(of: self.appModel.gatewaySetupRequestID) { _, _ in
self.maybeOpenSettingsForGatewaySetup()
}
}
private func rootPresentation(_ content: some View) -> some View {
@@ -565,16 +560,6 @@ struct RootTabs: View {
self.selectedTab = .settings
}
private func maybeOpenSettingsForGatewaySetup() {
let requestID = self.appModel.gatewaySetupRequestID
guard requestID != 0, requestID != self.handledGatewaySetupRequestID else { return }
self.handledGatewaySetupRequestID = requestID
self.showOnboarding = false
self.presentedSheet = nil
self.didAutoOpenSettings = true
self.selectedTab = .settings
}
private func applyInitialChatSessionIfNeeded() {
guard !self.didApplyInitialChatSession else { return }
self.didApplyInitialChatSession = true

View File

@@ -147,8 +147,8 @@ struct TalkPermissionPromptView: View {
case .upgradeRequested:
"Approve this request on your gateway. Talk will start automatically when approval lands."
default:
"This device needs gateway approval before Talk can use realtime voice. Audio will go directly from " +
"this device to the voice provider."
"This iPhone needs gateway approval before Talk can use realtime voice. Audio will go directly from " +
"this phone to the voice provider."
}
}

View File

@@ -1,12 +1,12 @@
OpenClaw is a personal AI assistant you run on your own devices.
Pair this iOS app with your OpenClaw Gateway to use your iPhone or iPad as a secure node for chat, voice, approvals, sharing, and device-aware automation.
Pair this iPhone app with your OpenClaw Gateway to use your phone as a secure node for chat, voice, approvals, sharing, and device-aware automation.
What you can do:
- Pair with your private OpenClaw Gateway by QR code or setup code
- Chat with your assistant from iPhone or iPad
- Chat with your assistant from iPhone
- Use realtime Talk mode and push-to-talk
- Review Gateway action approvals from your iPhone or iPad
- Review Gateway action approvals from your phone
- Share text, links, and media directly from iOS into OpenClaw
- Enable device capabilities such as camera, screen, location, photos, contacts, calendar, and reminders when you choose
- Receive push wakes and node status updates for connected workflows
@@ -16,4 +16,4 @@ OpenClaw is local-first: you control your gateway, keys, configuration, and perm
Getting started:
1) Set up your OpenClaw Gateway
2) Open the iOS app and pair with your gateway
3) Start using chat, Talk mode, approvals, and automations from your iPhone or iPad
3) Start using chat, Talk mode, approvals, and automations from your phone

View File

@@ -1 +1 @@
Pair your iPhone or iPad with your OpenClaw Gateway for chat, realtime voice, approvals, device capabilities, and private automation.
Pair your iPhone with your OpenClaw Gateway for chat, realtime voice, approvals, device capabilities, and private automation.

View File

@@ -97,7 +97,7 @@ targets:
DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)"
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_APP_BUNDLE_ID)"
PROVISIONING_PROFILE_SPECIFIER: "$(OPENCLAW_APP_PROFILE)"
TARGETED_DEVICE_FAMILY: "1,2"
TARGETED_DEVICE_FAMILY: "1"
SWIFT_VERSION: "6.0"
SWIFT_STRICT_CONCURRENCY: complete
SUPPORTS_LIVE_ACTIVITIES: YES

View File

@@ -1,3 +1,3 @@
{
"version": "2026.6.1"
"version": "2026.5.31"
}

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.6.1</string>
<string>2026.5.31</string>
<key>CFBundleVersion</key>
<string>2026053100</string>
<key>CFBundleIconFile</key>

View File

@@ -213,7 +213,7 @@ public enum GatewayConnectionProblemMapper {
owner: .both,
title: authError.titleOverride ?? "Gateway token required",
message: authError.userMessageOverride
?? "This gateway requires an auth token, but this device did not send one.",
?? "This gateway requires an auth token, but this iPhone did not send one.",
actionLabel: authError.actionLabel ?? "Open Settings",
actionCommand: authError.actionCommand,
docsURL: self.docsURL(
@@ -229,7 +229,7 @@ public enum GatewayConnectionProblemMapper {
owner: .both,
title: authError.titleOverride ?? "Gateway token is out of date",
message: authError.userMessageOverride
?? "The token on this device does not match the gateway token.",
?? "The token on this iPhone does not match the gateway token.",
actionLabel: authError
.actionLabel ?? (authError.canRetryWithDeviceToken ? "Retry once" : "Update gateway token"),
actionCommand: authError.actionCommand,
@@ -262,7 +262,7 @@ public enum GatewayConnectionProblemMapper {
owner: .both,
title: authError.titleOverride ?? "Gateway password required",
message: authError.userMessageOverride
?? "This gateway requires a password, but this device did not send one.",
?? "This gateway requires a password, but this iPhone did not send one.",
actionLabel: authError.actionLabel ?? "Open Settings",
actionCommand: authError.actionCommand,
docsURL: self.docsURL(
@@ -278,7 +278,7 @@ public enum GatewayConnectionProblemMapper {
owner: .both,
title: authError.titleOverride ?? "Gateway password is out of date",
message: authError.userMessageOverride
?? "The saved password on this device does not match the gateway password.",
?? "The saved password on this iPhone does not match the gateway password.",
actionLabel: authError.actionLabel ?? "Update password",
actionCommand: authError.actionCommand,
docsURL: self.docsURL(
@@ -322,7 +322,7 @@ public enum GatewayConnectionProblemMapper {
return self.problem(
kind: .deviceTokenMismatch,
owner: .both,
title: authError.titleOverride ?? "This device's saved device token is no longer valid",
title: authError.titleOverride ?? "This iPhone's saved device token is no longer valid",
message: authError.userMessageOverride
?? "The gateway rejected the stored device token for this role.",
actionLabel: authError.actionLabel ?? "Repair pairing",
@@ -355,7 +355,7 @@ public enum GatewayConnectionProblemMapper {
title: authError.titleOverride ?? "Secure device identity is required",
message: authError.userMessageOverride
??
"This connection must include a signed device identity before the gateway can bind permissions to this device.",
"This connection must include a signed device identity before the gateway can bind permissions to this iPhone.",
actionLabel: authError.actionLabel ?? "Retry from the app",
actionCommand: authError.actionCommand,
docsURL: self.docsURL(authError.docsURLString, fallback: "https://docs.openclaw.ai/platforms/ios"),
@@ -369,7 +369,7 @@ public enum GatewayConnectionProblemMapper {
owner: .iphone,
title: authError.titleOverride ?? "Secure handshake expired",
message: authError.userMessageOverride ?? "The device signature is too old to use.",
actionLabel: authError.actionLabel ?? "Check device time",
actionLabel: authError.actionLabel ?? "Check iPhone time",
actionCommand: authError.actionCommand,
docsURL: self.docsURL(
authError.docsURLString,
@@ -415,8 +415,8 @@ public enum GatewayConnectionProblemMapper {
owner: .iphone,
title: authError.titleOverride ?? "This device identity could not be verified",
message: authError.userMessageOverride
?? "The gateway could not verify the identity this device presented.",
actionLabel: authError.actionLabel ?? "Re-pair this device",
?? "The gateway could not verify the identity this iPhone presented.",
actionLabel: authError.actionLabel ?? "Re-pair this iPhone",
actionCommand: authError.actionCommand,
docsURL: self.docsURL(authError.docsURLString, fallback: "https://docs.openclaw.ai/gateway/pairing"),
requestId: authError.requestId,
@@ -429,8 +429,8 @@ public enum GatewayConnectionProblemMapper {
owner: .iphone,
title: authError.titleOverride ?? "This device identity could not be verified",
message: authError.userMessageOverride
?? "The gateway could not verify the public key this device presented.",
actionLabel: authError.actionLabel ?? "Re-pair this device",
?? "The gateway could not verify the public key this iPhone presented.",
actionLabel: authError.actionLabel ?? "Re-pair this iPhone",
actionCommand: authError.actionCommand,
docsURL: self.docsURL(authError.docsURLString, fallback: "https://docs.openclaw.ai/gateway/pairing"),
requestId: authError.requestId,
@@ -444,7 +444,7 @@ public enum GatewayConnectionProblemMapper {
title: authError.titleOverride ?? "This device identity could not be verified",
message: authError.userMessageOverride
?? "The gateway rejected the device identity because the device ID did not match.",
actionLabel: authError.actionLabel ?? "Re-pair this device",
actionLabel: authError.actionLabel ?? "Re-pair this iPhone",
actionCommand: authError.actionCommand,
docsURL: self.docsURL(authError.docsURLString, fallback: "https://docs.openclaw.ai/gateway/pairing"),
requestId: authError.requestId,
@@ -745,7 +745,7 @@ public enum GatewayConnectionProblemMapper {
title: authError.titleOverride ?? "Additional approval required",
message: authError.userMessageOverride
??
"This device is already paired, but it is requesting a new role that was not previously approved.",
"This iPhone is already paired, but it is requesting a new role that was not previously approved.",
actionLabel: authError.actionLabel ?? "Approve on gateway",
actionCommand: authError.actionCommand ?? pairingCommand,
docsURL: self.docsURL(authError.docsURLString, fallback: "https://docs.openclaw.ai/gateway/pairing"),
@@ -759,7 +759,7 @@ public enum GatewayConnectionProblemMapper {
owner: .gateway,
title: authError.titleOverride ?? "Additional permissions required",
message: authError.userMessageOverride
?? "This device is already paired, but it is requesting new permissions that require approval.",
?? "This iPhone is already paired, but it is requesting new permissions that require approval.",
actionLabel: authError.actionLabel ?? "Approve on gateway",
actionCommand: authError.actionCommand ?? pairingCommand,
docsURL: self.docsURL(authError.docsURLString, fallback: "https://docs.openclaw.ai/gateway/pairing"),
@@ -786,7 +786,7 @@ public enum GatewayConnectionProblemMapper {
return self.problem(
kind: .pairingRequired,
owner: .gateway,
title: authError.titleOverride ?? "This device is not approved yet",
title: authError.titleOverride ?? "This iPhone is not approved yet",
message: authError.userMessageOverride
?? "The gateway received the connection request, but this device must be approved first.",
actionLabel: authError.actionLabel ?? "Approve on gateway",

View File

@@ -5528,7 +5528,6 @@ public struct SkillsProposalRecordResult: Codable, Sendable {
public let createdat: String
public let updatedat: String
public let createdby: AnyCodable
public let origin: [String: AnyCodable]?
public let proposedversion: String
public let draftfile: String
public let drafthash: String
@@ -5553,7 +5552,6 @@ public struct SkillsProposalRecordResult: Codable, Sendable {
createdat: String,
updatedat: String,
createdby: AnyCodable,
origin: [String: AnyCodable]?,
proposedversion: String,
draftfile: String,
drafthash: String,
@@ -5577,7 +5575,6 @@ public struct SkillsProposalRecordResult: Codable, Sendable {
self.createdat = createdat
self.updatedat = updatedat
self.createdby = createdby
self.origin = origin
self.proposedversion = proposedversion
self.draftfile = draftfile
self.drafthash = drafthash
@@ -5603,7 +5600,6 @@ public struct SkillsProposalRecordResult: Codable, Sendable {
case createdat = "createdAt"
case updatedat = "updatedAt"
case createdby = "createdBy"
case origin
case proposedversion = "proposedVersion"
case draftfile = "draftFile"
case drafthash = "draftHash"

View File

@@ -165,8 +165,6 @@ const config = {
"vite.config.ts!",
"vitest*.ts!",
],
// Workboard lazy-loads Three.js at runtime; Knip's dependency pass misses it.
ignoreDependencies: ["three"],
project: ["src/**/*.{ts,tsx}!"],
},
"packages/sdk": {

View File

@@ -1,4 +1,4 @@
cc0fb4e3f1a7e8f233626adb80d686608ddac8c177fe6a55b33970c2baf4ace4 config-baseline.json
042ca98e6200a365accda00e5a6f3e72bdae5853f39ff0cdc3b2cb9c0d6f8f3e config-baseline.core.json
8f33d8e21daa30d74977f3e586bc0113eb90bd165629fb8ea7d54484508f35a6 config-baseline.json
18f148db6267582e46fcef54420c62b4e219b2e17ce4bcf964102f80381ca6be config-baseline.core.json
cbf81829dcc8cfd0a16435912da709f8c1d508707385b6493f94cafe211ec67c config-baseline.channel.json
4012b1f8de6f9527c47320a6c7120f30dc30ac1b5524ed63dadef890aad44b20 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
bdcf661ec680f79819096950295bdb04805aac9639477058d8855f294f6d8034 plugin-sdk-api-baseline.json
6b8c92cc5a9277f90973370102fa31efb23ffd93008c3ed961d38e4a8a3073b0 plugin-sdk-api-baseline.jsonl
19bdf1196ec771a00777a16fd1e9c3662b8fd788a81034e705c41a74ee79c7ec plugin-sdk-api-baseline.json
43feff80c90adad0f821d1f1e184a9bff1e93d81e6d53a26a26fd9e2972be759 plugin-sdk-api-baseline.jsonl

View File

@@ -65,7 +65,7 @@ Use this checklist when you already know your old BlueBubbles config and want th
imsg rpc --help
```
Replace `42` with a real chat id from `imsg chats`. Sending requires Automation permission for Messages.app. If OpenClaw will run through SSH, run these commands through the same SSH wrapper or user context that OpenClaw will use. If reads/probes work but sends fail with AppleEvents `-1743`, check whether Automation landed on `/usr/libexec/sshd-keygen-wrapper`; see [SSH wrapper sends fail with AppleEvents -1743](/channels/imessage#ssh-wrapper-sends-fail-with-appleevents-1743).
Replace `42` with a real chat id from `imsg chats`. Sending requires Automation permission for Messages.app. If OpenClaw will run through SSH, run these commands through the same SSH wrapper or user context that OpenClaw will use.
3. Enable the private API bridge when you need advanced actions:
@@ -248,7 +248,7 @@ iMessage catchup is now available as an opt-in feature on the bundled plugin. On
There is no supported BlueBubbles runtime to switch back to. If iMessage verification fails, set `channels.imessage.enabled: false`, restart the Gateway, fix the `imsg` blocker, and retry the cutover.
The reply cache lives in SQLite plugin state. `openclaw doctor --fix` imports and archives the old `imessage/reply-cache.jsonl` sidecar when present.
The reply cache lives at `~/.openclaw/state/imessage/reply-cache.jsonl` (mode `0600`, parent dir `0700`). It is safe to delete if you want a clean slate.
## Related

View File

@@ -151,29 +151,6 @@ imsg send <handle> "test"
</Tip>
<Accordion title="SSH wrapper sends fail with AppleEvents -1743">
A remote-SSH setup can read chats, pass `channels status --probe`, and process inbound messages while outbound sends still fail with an AppleEvents authorization error:
```text
Not authorized to send Apple events to Messages. (-1743)
```
Check the signed-in Mac user's TCC database or System Settings > Privacy & Security > Automation. If the Automation entry is recorded for `/usr/libexec/sshd-keygen-wrapper` instead of the `imsg` or local shell process, macOS may not expose a usable Messages toggle for that SSH server-side client:
```text
kTCCServiceAppleEvents | /usr/libexec/sshd-keygen-wrapper | auth_value=0 | com.apple.MobileSMS
```
In that state, repeating `tccutil reset AppleEvents` or rerunning `imsg send` through the same SSH wrapper may keep failing because the process context that needs Messages Automation is the SSH wrapper, not an app the UI can grant.
Use one of the supported `imsg` process contexts instead:
- Run the Gateway, or at least the `imsg` bridge, in the logged-in Messages user's local session.
- Start the Gateway with a LaunchAgent for that user after granting Full Disk Access and Automation from the same session.
- If you keep the two-user SSH topology, verify that a real outbound `imsg send` succeeds through the exact wrapper before enabling the channel. If it cannot be granted Automation, reconfigure to a single-user `imsg` setup instead of relying on the SSH wrapper for sends.
</Accordion>
## Enabling the imsg private API
`imsg` ships in two operational modes:
@@ -556,7 +533,7 @@ When `imsg launch` is running and `openclaw channels status --probe` reports `pr
</Accordion>
<Accordion title="Message IDs">
Inbound iMessage context includes both short `MessageSid` values and full message GUIDs when available. Short IDs are scoped to the recent SQLite-backed reply cache and are checked against the current chat before use. If a short ID has expired or belongs to another chat, retry with the full `MessageSidFull`.
Inbound iMessage context includes both short `MessageSid` values and full message GUIDs when available. Short IDs are scoped to the recent in-memory reply cache and are checked against the current chat before use. If a short ID has expired or belongs to another chat, retry with the full `MessageSidFull`.
</Accordion>
@@ -737,7 +714,7 @@ Each replayed row is fed through the live dispatch path (`evaluateIMessageInboun
### Cursor and retry semantics
Catchup keeps a per-account cursor in SQLite plugin state:
Catchup keeps a per-account cursor at `<openclawStateDir>/imessage/catchup/<account>__<hash>.json` (the OpenClaw state dir defaults to `~/.openclaw`, overridable with `OPENCLAW_STATE_DIR`):
```json
{
@@ -752,7 +729,6 @@ Catchup keeps a per-account cursor in SQLite plugin state:
- After the startup catchup query succeeds, later live-handled rows also advance the same cursor so a gateway restart does not replay messages that were already handled live. Live cursor writes do not jump past catchup failures that are still below `maxFailureRetries`.
- After `maxFailureRetries` consecutive throws against the same `guid`, catchup logs a `warn` and force-advances the cursor past the wedged message so subsequent startups can make progress.
- Already-given-up guids are skipped on sight (no dispatch attempt) on later runs and counted under `skippedGivenUp` in the run summary.
- `openclaw doctor --fix` imports legacy `<openclawStateDir>/imessage/catchup/*.json` cursor files into SQLite plugin state and archives the old files.
### Operator-visible signals

View File

@@ -329,19 +329,6 @@ openclaw plugins install -l ./my-plugin
Standalone plugin files must be listed in `plugins.load.paths` rather than placed directly in `~/.openclaw/extensions` or `<workspace>/.openclaw/extensions`. Those auto-discovered roots load plugin package or bundle directories, while top-level script files are treated as local helpers and skipped.
<Note>
Workspace-origin plugins discovered from a workspace extensions root are not
imported or executed until they are explicitly enabled. For local development,
run `openclaw plugins enable <plugin-id>` or set
`plugins.entries.<plugin-id>.enabled: true`; if your config uses
`plugins.allow`, include the same plugin id there too. This fail-closed rule
also applies when channel setup explicitly targets a workspace-origin plugin for
setup-only loading, so local channel plugin setup code will not run while that
workspace plugin remains disabled or excluded from the allowlist. Linked installs
and explicit `plugins.load.paths` entries follow the normal policy for their
resolved plugin origin. See
[Configure plugin policy](/tools/plugin#configure-plugin-policy)
and [Configuration reference](/gateway/configuration-reference#plugins).
`--force` is not supported with `--link` because linked installs reuse the source path instead of copying over a managed install target.
Use `--pin` on npm installs to save the resolved exact spec (`name@version`) in the managed plugin index while keeping the default behavior unpinned.
@@ -349,7 +336,7 @@ Use `--pin` on npm installs to save the resolved exact spec (`name@version`) in
### Plugin index
Plugin install metadata is machine-managed state, not user config. Installs and updates write it to the shared SQLite state database under the active OpenClaw state directory. The `installed_plugin_index` row stores durable `installRecords` metadata, including records for broken or missing plugin manifests, plus a manifest-derived cold registry cache used by `openclaw plugins update`, uninstall, diagnostics, and the cold plugin registry.
Plugin install metadata is machine-managed state, not user config. Installs and updates write it to `plugins/installs.json` under the active OpenClaw state directory. Its top-level `installRecords` map is the durable source of install metadata, including records for broken or missing plugin manifests. The `plugins` array is the manifest-derived cold registry cache. The file includes a do-not-edit warning and is used by `openclaw plugins update`, uninstall, diagnostics, and the cold plugin registry.
When OpenClaw sees shipped legacy `plugins.installs` records in config, runtime reads treat them as compatibility input without rewriting `openclaw.json`. Explicit plugin writes and `openclaw doctor --fix` move those records into the plugin index and remove the config key when config writes are allowed; if either write fails, the config records are kept so the install metadata is not lost.

View File

@@ -22,7 +22,6 @@ Initialize the baseline config and agent workspace. With any onboarding flag pre
| `--workspace <dir>` | Agent workspace directory (default `~/.openclaw/workspace`; stored as `agents.defaults.workspace`). |
| `--wizard` | Run interactive onboarding. |
| `--non-interactive` | Run onboarding without prompts. |
| `--accept-risk` | Acknowledge full-system agent access risk; required with `--non-interactive`. |
| `--mode <mode>` | Onboarding mode: `local` or `remote`. |
| `--import-from <provider>` | Migration provider to run during onboarding. |
| `--import-source <path>` | Source agent home for `--import-from`. |
@@ -34,7 +33,7 @@ Initialize the baseline config and agent workspace. With any onboarding flag pre
`openclaw setup` runs the wizard when any of these flags are explicitly present, even without `--wizard`:
`--wizard`, `--non-interactive`, `--accept-risk`, `--mode`, `--import-from`, `--import-source`, `--import-secrets`, `--remote-url`, `--remote-token`.
`--wizard`, `--non-interactive`, `--mode`, `--import-from`, `--import-source`, `--import-secrets`, `--remote-url`, `--remote-token`.
## Examples
@@ -43,7 +42,7 @@ openclaw setup
openclaw setup --workspace ~/.openclaw/workspace
openclaw setup --wizard
openclaw setup --wizard --import-from hermes --import-source ~/.hermes
openclaw setup --non-interactive --accept-risk --mode remote --remote-url wss://gateway-host:18789 --remote-token <token>
openclaw setup --non-interactive --mode remote --remote-url wss://gateway-host:18789 --remote-token <token>
```
## Notes

View File

@@ -99,10 +99,7 @@ openclaw workboard dispatch --url http://127.0.0.1:18789 --token "$OPENCLAW_GATE
`dispatch` first calls the running Gateway RPC method
`workboard.cards.dispatch`. That path uses the same subagent runtime as the
dashboard dispatch action, so ready cards become task-tracked worker runs with
linked session keys. Cards with an assigned agent use agent-scoped subagent
session keys; unassigned cards keep an unscoped subagent key so the Gateway's
configured default agent is preserved.
dashboard dispatch action, so ready cards can become real worker sessions.
The dispatch loop:
@@ -113,8 +110,8 @@ The dispatch loop:
5. Claims each selected card for the dispatcher or assigned agent.
6. Starts a subagent worker run with bounded card context and the card claim
token.
7. Stores the worker run id, session key, task linkage when the Gateway task
ledger reports it, execution status, and worker log on the card.
7. Stores the worker run id, session key, execution status, and worker log on
the card.
Selection is intentionally conservative. One dispatch starts at most three
workers by default, skips archived or already-claimed cards, and starts only one
@@ -149,10 +146,6 @@ JSON output includes the dispatch result. Gateway-backed dispatch can include
`started` and `startFailures`; data-only fallback includes
`gatewayUnavailable: true`. Claim tokens are redacted from card JSON output.
In the dashboard, the same dispatch result is shown as a short summary so an
operator can see how many cards started, promoted, blocked, reclaimed, or
failed without opening card details.
## Slash Command Parity
Command-capable channels can use the matching slash command:

View File

@@ -99,7 +99,7 @@ These are the standard files OpenClaw expects inside the workspace:
</AccordionGroup>
<Note>
If any bootstrap file is missing, OpenClaw injects a "missing file" marker into the session and continues. Large bootstrap files are truncated when injected; adjust limits with `agents.defaults.bootstrapMaxChars` (default: 20000) and `agents.defaults.bootstrapTotalMaxChars` (default: 60000). `openclaw setup` can recreate missing defaults without overwriting existing files.
If any bootstrap file is missing, OpenClaw injects a "missing file" marker into the session and continues. Large bootstrap files are truncated when injected; adjust limits with `agents.defaults.bootstrapMaxChars` (default: 12000) and `agents.defaults.bootstrapTotalMaxChars` (default: 60000). `openclaw setup` can recreate missing defaults without overwriting existing files.
</Note>
## What is NOT in the workspace

View File

@@ -41,8 +41,6 @@ If a file is missing, OpenClaw injects a single "missing file" marker line (and
`BOOTSTRAP.md` is only created for a **brand new workspace** (no other bootstrap files present). While it is pending, OpenClaw keeps it in Project Context and adds system-prompt bootstrap guidance for the initial ritual instead of copying it into the user message. If you delete it after completing the ritual, it should not be recreated on later restarts.
After a workspace has been observed, OpenClaw also keeps a state-dir attestation marker for the workspace path. If a recently attested workspace disappears or is wiped, startup refuses to silently re-seed `BOOTSTRAP.md`; restore the workspace or use a full onboard reset so the workspace and marker are cleared together.
To disable bootstrap file creation entirely (for pre-seeded workspaces), set:
```json5

View File

@@ -91,7 +91,7 @@ For the bundled non-ACP Codex harness, OpenClaw applies the same lifecycle by pr
OpenClaw calls two optional subagent lifecycle hooks:
<ParamField path="prepareSubagentSpawn" type="method">
Prepare shared context state before a child run starts. The hook receives parent/child session keys, `contextMode` (`isolated` or `fork`), available transcript ids/files, and optional TTL. If it returns a rollback handle, OpenClaw calls it when spawn fails after preparation succeeds. Native subagent spawns that request `lightContext` and resolve to `contextMode="isolated"` intentionally skip this hook so the child starts from the lightweight bootstrap context without context-engine-managed pre-spawn state.
Prepare shared context state before a child run starts. The hook receives parent/child session keys, `contextMode` (`isolated` or `fork`), available transcript ids/files, and optional TTL. If it returns a rollback handle, OpenClaw calls it when spawn fails after preparation succeeds.
</ParamField>
<ParamField path="onSubagentEnded" type="method">
Clean up when a subagent session completes or is swept.

View File

@@ -122,7 +122,7 @@ By default, OpenClaw injects a fixed set of workspace files (if present):
- `HEARTBEAT.md`
- `BOOTSTRAP.md` (first-run only)
Large files are truncated per-file using `agents.defaults.bootstrapMaxChars` (default `20000` chars). OpenClaw also enforces a total bootstrap injection cap across files with `agents.defaults.bootstrapTotalMaxChars` (default `60000` chars). `/context` shows **raw vs injected** sizes and whether truncation happened.
Large files are truncated per-file using `agents.defaults.bootstrapMaxChars` (default `12000` chars). OpenClaw also enforces a total bootstrap injection cap across files with `agents.defaults.bootstrapTotalMaxChars` (default `60000` chars). `/context` shows **raw vs injected** sizes and whether truncation happened.
When truncation occurs, the runtime can inject an in-prompt warning block under Project Context. Configure this with `agents.defaults.bootstrapPromptTruncationWarning` (`off`, `once`, `always`; default `always`).

View File

@@ -107,13 +107,6 @@ Deep ranking uses six weighted base signals plus phase reinforcement:
Light and REM phase hits add a small recency-decayed boost from `memory/.dreams/phase-signals.json`.
Shadow-trial results can be layered on top of that base score as a review
signal before any durable write. A helpful trial gives the candidate a small
bounded boost, a neutral trial keeps it deferred, and a harmful trial marks it
as rejected for that scoring pass. This signal is still report-only: it can
change candidate ordering or review metadata, but it does not write to
`MEMORY.md` or promote the candidate by itself.
## QA shadow trial report coverage
QA Lab includes a report-only scenario for exploring how a future dreaming

View File

@@ -303,7 +303,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
| 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-M3` |
| 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` |
@@ -331,7 +331,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
Gemini-backed refs follow the same proxy-Gemini sanitation path; `kilocode/kilo/auto` and other proxy-reasoning-unsupported refs skip proxy reasoning injection.
</Accordion>
<Accordion title="MiniMax">
API-key onboarding writes explicit M3 and M2.7 chat model definitions; image understanding stays on the plugin-owned `MiniMax-VL-01` media provider.
API-key onboarding writes explicit text-only M2.7 chat model definitions; image understanding stays on the plugin-owned `MiniMax-VL-01` media provider.
</Accordion>
<Accordion title="NVIDIA">
Model ids use a `nvidia/<vendor>/<model>` namespace (for example `nvidia/nvidia/nemotron-...` alongside `nvidia/moonshotai/kimi-k2.5`); pickers preserve the literal `<provider>/<model-id>` composition while the canonical key sent to the API stays single-prefixed.
@@ -537,7 +537,7 @@ On MiniMax's Anthropic-compatible streaming path, OpenClaw disables thinking by
Plugin-owned capability split:
- Text/chat defaults stay on `minimax/MiniMax-M3`
- Text/chat defaults stay on `minimax/MiniMax-M2.7`
- Image generation is `minimax/image-01` or `minimax-portal/image-01`
- Image understanding is plugin-owned `MiniMax-VL-01` on both MiniMax auth paths
- Web search stays on provider id `minimax`

View File

@@ -120,19 +120,6 @@ sparse token/cache counters from the latest transcript usage entry, and
the caller's current session; visible client labels such as `openclaw-tui` are
not session keys.
When route metadata is available, `session_status` also includes a visible
`Route context` JSON block and matching structured `details` fields. These
fields disambiguate the session key from the route that is currently handling
the live run:
- `origin` is where the session was created, or the provider inferred from a
deliverable session-key prefix when older state lacks stored origin metadata.
- `active` is the current live-run route. It is only reported for the live or
current session being handled now.
- `deliveryContext` is the persisted delivery route stored on the session,
which OpenClaw can reuse for later delivery even when the active surface
differs.
`sessions_yield` intentionally ends the current turn so the next message can be
the follow-up event you are waiting for. Use it after spawning sub-agents when
you want completion results to arrive as the next message instead of building

View File

@@ -208,7 +208,7 @@ because of the bootstrap file limits below.
</Note>
Large files are truncated with a marker. The max per-file size is controlled by
`agents.defaults.bootstrapMaxChars` (default: 20000). Total injected bootstrap
`agents.defaults.bootstrapMaxChars` (default: 12000). Total injected bootstrap
content across files is capped by `agents.defaults.bootstrapTotalMaxChars`
(default: 60000). Missing files inject a short missing-file marker. When truncation
occurs, OpenClaw can inject a concise system-prompt warning notice; control this with

View File

@@ -103,11 +103,11 @@ Per-agent override: `agents.list[].contextInjection`. Omitted values inherit
### `agents.defaults.bootstrapMaxChars`
Max characters per workspace bootstrap file before truncation. Default: `20000`.
Max characters per workspace bootstrap file before truncation. Default: `12000`.
```json5
{
agents: { defaults: { bootstrapMaxChars: 20000 } },
agents: { defaults: { bootstrapMaxChars: 12000 } },
}
```
@@ -138,7 +138,7 @@ injection behavior from the shared defaults. Omitted fields inherit from
agents: {
defaults: {
contextInjection: "continuation-skip",
bootstrapMaxChars: 20000,
bootstrapMaxChars: 12000,
bootstrapTotalMaxChars: 60000,
},
list: [
@@ -662,7 +662,9 @@ Periodic heartbeat runs.
- `mode`: `default` or `safeguard` (chunked summarization for long histories). See [Compaction](/concepts/compaction).
- `provider`: id of a registered compaction provider plugin. When set, the provider's `summarize()` is called instead of built-in LLM summarization. Falls back to built-in on failure. Setting a provider forces `mode: "safeguard"`. See [Compaction](/concepts/compaction).
- `timeoutSeconds`: maximum seconds allowed for a single compaction operation before OpenClaw aborts it. Default: `900`.
- `keepRecentTokens`: agent cut-point budget for keeping the most recent transcript tail verbatim. Manual `/compact` honors this when explicitly set; otherwise manual compaction is a hard checkpoint.
- `reserveTokens`: token headroom kept available for the model response and future tool output after compaction. When the active model context window is known, OpenClaw caps impossible reserve values so smaller local models still keep usable prompt budget.
- `reserveTokensFloor`: minimum reserve enforced by the embedded runtime. Set `0` to disable the floor. The floor is also capped against the active model context window when that window is known.
- `keepRecentTokens`: agent cut-point budget for keeping the most recent transcript tail verbatim. Manual `/compact` honors this when explicitly set; otherwise manual compaction is a hard checkpoint. When the active model context window is known, OpenClaw caps impossible values to the prompt budget left after reserve tokens and summary overhead.
- `identifierPolicy`: `strict` (default), `off`, or `custom`. `strict` prepends built-in opaque identifier retention guidance during compaction summarization.
- `identifierInstructions`: optional custom identifier-preservation text used when `identifierPolicy=custom`.
- `qualityGuard`: retry-on-malformed-output checks for safeguard summaries. Enabled by default in safeguard mode; set `enabled: false` to skip the audit.

View File

@@ -597,8 +597,6 @@ BlueBubbles support was removed. `channels.bluebubbles` is not a supported runti
If the Gateway is not running on the signed-in Messages Mac, keep `channels.imessage.enabled=true` and set `channels.imessage.cliPath` to an SSH wrapper that runs `imsg "$@"` on that Mac. The default local `imsg` path is macOS-only.
Before relying on an SSH wrapper for production sends, verify an outbound `imsg send` through that exact wrapper. Some macOS TCC states assign Messages Automation to `/usr/libexec/sshd-keygen-wrapper`, which can make reads and probes work while sends fail with AppleEvents `-1743`; see [SSH wrapper sends fail with AppleEvents -1743](/channels/imessage#ssh-wrapper-sends-fail-with-appleevents-1743).
```json5
{
channels: {

View File

@@ -76,37 +76,6 @@ Server globs use the provider-safe MCP server prefix, not necessarily the raw `m
Without that sandbox-layer entry, the MCP server can still load successfully while its tools are filtered before the provider request. Use `openclaw doctor` to catch this shape for OpenClaw-managed servers in `mcp.servers`. MCP servers loaded from bundled plugin manifests or Claude `.mcp.json` use the same sandbox gate, but this diagnostic does not enumerate those sources yet; use the same allowlist entries if their tools disappear in sandboxed turns.
### `tools.codeMode`
`tools.codeMode` enables the generic OpenClaw code-mode surface. When enabled
for a run with tools, the model sees only `exec` and `wait`; normal OpenClaw
tools move behind the in-sandbox `tools.*` catalog bridge, and MCP tools are
available through the generated `MCP` namespace.
```json5
{
tools: {
codeMode: {
enabled: true,
},
},
}
```
The shorthand is also accepted:
```json5
{
tools: { codeMode: true },
}
```
MCP declarations are exposed through the read-only virtual API file surface in
code mode. Guest code can call `API.list("mcp")` and
`API.read("mcp/<server>.d.ts")` to inspect TypeScript-style signatures before
calling `MCP.<server>.<tool>()`. See [Code mode](/reference/code-mode) for the
runtime contract, limits, and debugging steps.
### `tools.allow` / `tools.deny`
Global tool allow/deny policy (deny wins). Case-insensitive, supports `*` wildcards. Applied even when Docker sandbox is off.
@@ -645,14 +614,14 @@ Interactive custom-provider onboarding infers image input for common vision mode
<Accordion title="Local models (LM Studio)">
See [Local Models](/gateway/local-models). TL;DR: run a large local model via LM Studio Responses API on serious hardware; keep hosted models merged for fallback.
</Accordion>
<Accordion title="MiniMax M3 (direct)">
<Accordion title="MiniMax M2.7 (direct)">
```json5
{
agents: {
defaults: {
model: { primary: "minimax/MiniMax-M3" },
model: { primary: "minimax/MiniMax-M2.7" },
models: {
"minimax/MiniMax-M3": { alias: "Minimax" },
"minimax/MiniMax-M2.7": { alias: "Minimax" },
},
},
},
@@ -665,12 +634,12 @@ Interactive custom-provider onboarding infers image input for common vision mode
api: "anthropic-messages",
models: [
{
id: "MiniMax-M3",
name: "MiniMax M3",
id: "MiniMax-M2.7",
name: "MiniMax M2.7",
reasoning: true,
input: ["text", "image"],
cost: { input: 0.6, output: 2.4, cacheRead: 0.12, cacheWrite: 0 },
contextWindow: 1000000,
input: ["text"],
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
contextWindow: 204800,
maxTokens: 131072,
},
],
@@ -680,7 +649,7 @@ Interactive custom-provider onboarding infers image input for common vision mode
}
```
Set `MINIMAX_API_KEY`. Shortcuts: `openclaw onboard --auth-choice minimax-global-api` or `openclaw onboard --auth-choice minimax-cn-api`. The model catalog defaults to M3 and also includes the M2.7 variants. On the Anthropic-compatible streaming path, OpenClaw disables MiniMax thinking by default unless you explicitly set `thinking` yourself. `/fast on` or `params.fastMode: true` rewrites `MiniMax-M2.7` to `MiniMax-M2.7-highspeed`.
Set `MINIMAX_API_KEY`. Shortcuts: `openclaw onboard --auth-choice minimax-global-api` or `openclaw onboard --auth-choice minimax-cn-api`. The model catalog defaults to M2.7 only. On the Anthropic-compatible streaming path, OpenClaw disables MiniMax thinking by default unless you explicitly set `thinking` yourself. `/fast on` or `params.fastMode: true` rewrites `MiniMax-M2.7` to `MiniMax-M2.7-highspeed`.
</Accordion>
<Accordion title="Moonshot AI (Kimi)">

View File

@@ -215,7 +215,7 @@ troubleshooting, see the main [FAQ](/help/faq).
</Accordion>
<Accordion title='Why do I see "Unknown model: minimax/MiniMax-M3"?'>
<Accordion title='Why do I see "Unknown model: minimax/MiniMax-M2.7"?'>
This means the **provider isn't configured** (no MiniMax provider config or auth
profile was found), so the model can't be resolved.
@@ -227,9 +227,8 @@ troubleshooting, see the main [FAQ](/help/faq).
(`MINIMAX_API_KEY` for `minimax`, `MINIMAX_OAUTH_TOKEN` or stored MiniMax
OAuth for `minimax-portal`).
3. Use the exact model id (case-sensitive) for your auth path:
`minimax/MiniMax-M3`, `minimax/MiniMax-M2.7`, or
`minimax/MiniMax-M2.7-highspeed` for API-key setup, or
`minimax-portal/MiniMax-M3`, `minimax-portal/MiniMax-M2.7`, or
`minimax/MiniMax-M2.7` or `minimax/MiniMax-M2.7-highspeed` for API-key
setup, or `minimax-portal/MiniMax-M2.7` /
`minimax-portal/MiniMax-M2.7-highspeed` for OAuth setup.
4. Run:
@@ -254,9 +253,9 @@ troubleshooting, see the main [FAQ](/help/faq).
env: { MINIMAX_API_KEY: "sk-...", OPENAI_API_KEY: "sk-..." },
agents: {
defaults: {
model: { primary: "minimax/MiniMax-M3" },
model: { primary: "minimax/MiniMax-M2.7" },
models: {
"minimax/MiniMax-M3": { alias: "minimax" },
"minimax/MiniMax-M2.7": { alias: "minimax" },
"openai/gpt-5.5": { alias: "gpt" },
},
},

View File

@@ -631,48 +631,6 @@ lives on the [First-run FAQ](/help/faq-first-run).
</Accordion>
<Accordion title="Can I make SOUL.md bigger?">
Yes. `SOUL.md` is one of the workspace bootstrap files injected into the
agent context. The default per-file injection limit is `20000` characters,
and the total bootstrap budget across files is `60000` characters.
Change the shared defaults in your OpenClaw config:
```json5
{
agents: {
defaults: {
bootstrapMaxChars: 50000,
bootstrapTotalMaxChars: 300000,
},
},
}
```
Or override one agent:
```json5
{
agents: {
list: [
{
id: "main",
bootstrapMaxChars: 50000,
bootstrapTotalMaxChars: 300000,
},
],
},
}
```
Use `/context` to check raw vs injected sizes and whether truncation happened.
Keep `SOUL.md` focused on voice, stance, and personality; put operating rules
in `AGENTS.md` and durable facts in memory.
See [Context](/concepts/context) and [Agent config](/gateway/config-agents).
</Accordion>
<Accordion title="Recommended backup strategy">
Put your **agent workspace** in a **private** git repo and back it up somewhere
private (for example GitHub private). This captures memory + AGENTS/SOUL/USER

View File

@@ -73,11 +73,10 @@ Live tests are split into two layers so we can isolate failures:
- `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
- 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 M3, Grok 4.3)
- `OPENCLAW_LIVE_MODELS=small` to run the constrained small-model allowlist (Qwen 8B/9B local-compatible routes, Ollama Gemma, OpenRouter Qwen/GLM, and Z.AI GLM)
- `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,anthropic/claude-opus-4-6,..."` (comma allowlist)
- Local Ollama small-model runs default to `http://127.0.0.1:11434`; set `OPENCLAW_LIVE_OLLAMA_BASE_URL` only for LAN, custom, or Ollama Cloud endpoints.
- 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.
- 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.
@@ -109,7 +108,7 @@ Live tests are split into two layers so we can isolate failures:
- How to enable:
- `pnpm test:live` (or `OPENCLAW_LIVE_TEST=1` if invoking Vitest directly)
- How to select models:
- Default: modern allowlist (Opus/Sonnet 4.6+, GPT-5.2 + Codex, Gemini 3, DeepSeek V4, GLM 4.7, MiniMax M3, Grok 4.3)
- Default: 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_GATEWAY_MODELS=all` is an alias for the modern allowlist
- Or set `OPENCLAW_LIVE_GATEWAY_MODELS="provider/model"` (or comma list) to narrow
- Modern/all gateway sweeps default to a curated high-signal cap; set `OPENCLAW_LIVE_GATEWAY_MAX_MODELS=0` for an exhaustive modern sweep or a positive number for a smaller cap.
@@ -351,7 +350,7 @@ Narrow, explicit allowlists are fastest and least flaky:
- `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- Tool calling across several providers:
- `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3-flash-preview,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M3" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3-flash-preview,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- Google focus (Gemini API key + Antigravity):
- Gemini (API key): `OPENCLAW_LIVE_GATEWAY_MODELS="google/gemini-3-flash-preview" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
@@ -385,10 +384,10 @@ This is the "common models" run we expect to keep working:
- Google (Antigravity): `google-antigravity/claude-opus-4-6-thinking` and `google-antigravity/gemini-3-flash`
- DeepSeek: `deepseek/deepseek-v4-flash` and `deepseek/deepseek-v4-pro`
- Z.AI (GLM): `zai/glm-5.1`
- MiniMax: `minimax/MiniMax-M3`
- MiniMax: `minimax/MiniMax-M2.7`
Run gateway smoke with tools + image:
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3.1-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M3" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3.1-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
### Baseline: tool calling (Read + optional Exec)
@@ -399,7 +398,7 @@ Pick at least one per provider family:
- Google: `google/gemini-3-flash-preview` (or `google/gemini-3.1-pro-preview`)
- DeepSeek: `deepseek/deepseek-v4-flash`
- Z.AI (GLM): `zai/glm-5.1`
- MiniMax: `minimax/MiniMax-M3`
- MiniMax: `minimax/MiniMax-M2.7`
Optional additional coverage (nice to have):

View File

@@ -245,7 +245,7 @@ Notes:
The iOS app is a mobile node surface, not a Codex Computer Use backend. Codex
Computer Use and `cua-driver mcp` control a local macOS desktop through MCP
tools; the iOS app exposes iPhone and iPad capabilities through OpenClaw node commands
tools; the iOS app exposes iPhone capabilities through OpenClaw node commands
such as `canvas.*`, `camera.*`, `screen.*`, `location.*`, and `talk.*`.
Agents can still operate the iOS app through OpenClaw by invoking node

View File

@@ -1021,10 +1021,10 @@ plugin index entry with `source: "path"` and a workspace-relative
`plugins.load.paths`; the install record avoids duplicating local workstation
paths into long-lived config. This keeps local development installs visible to
source-plane diagnostics without adding a second raw filesystem-path disclosure
surface. The persisted `installed_plugin_index` SQLite row is the install
surface. The persisted `plugins/installs.json` plugin index is the install
source of truth and can be refreshed without loading plugin runtime modules.
Its `installRecords` map is durable even when a plugin manifest is missing or
invalid; its `plugins` payload is a rebuildable manifest view.
invalid; its `plugins` array is a rebuildable manifest view.
## Context engine plugins

View File

@@ -368,7 +368,7 @@ If discovery fails or times out, OpenClaw uses a bundled fallback catalog for:
- GPT-5.4 mini
- GPT-5.2
The current bundled harness is `@openai/codex` `0.135.0`. A `model/list` probe
The current bundled harness is `@openai/codex` `0.134.0`. A `model/list` probe
against that bundled app-server returned:
| Model id | Default | Hidden | Input modalities | Reasoning efforts |

View File

@@ -190,10 +190,11 @@ plugins, channels, and core code only see the standard
When `harness.compact` runs, the Copilot SDK harness:
1. Resumes the tracked SDK session without continuing pending work.
2. Calls the SDK's session-scoped history compaction RPC.
3. Returns the SDK compaction outcome without writing compatibility marker
files under the workspace.
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.

View File

@@ -399,10 +399,8 @@ media caption.
Message hook contexts expose stable correlation fields when available:
`ctx.sessionKey`, `ctx.runId`, `ctx.messageId`, `ctx.senderId`, `ctx.trace`,
`ctx.traceId`, `ctx.spanId`, `ctx.parentSpanId`, and `ctx.callDepth`. Inbound
and `before_dispatch` contexts also expose reply metadata when the channel has
visibility-filtered quoted message data: `replyToId`, `replyToBody`, and
`replyToSender`. Prefer these first-class fields before reading legacy metadata.
`ctx.traceId`, `ctx.spanId`, `ctx.parentSpanId`, and `ctx.callDepth`. Prefer
these first-class fields before reading legacy metadata.
Prefer typed `threadId` and `replyToId` fields before using channel-specific
metadata.

View File

@@ -107,7 +107,7 @@ commands.
| [oc-path](/plugins/reference/oc-path) | Adds the openclaw path CLI for oc:// workspace file addressing. | `@openclaw/oc-path`<br />included in OpenClaw | plugin |
| [ollama](/plugins/reference/ollama) | Adds Ollama, Ollama Cloud model provider support to OpenClaw. | `@openclaw/ollama-provider`<br />included in OpenClaw | providers: ollama, ollama-cloud; contracts: memoryEmbeddingProviders, webSearchProviders |
| [open-prose](/plugins/reference/open-prose) | OpenProse VM skill pack with a /prose slash command. | `@openclaw/open-prose`<br />included in OpenClaw | skills |
| [openai](/plugins/reference/openai) | Adds OpenAI model provider support to OpenClaw. | `@openclaw/openai-provider`<br />included in OpenClaw | providers: openai; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders |
| [openai](/plugins/reference/openai) | Adds OpenAI model provider support to OpenClaw, including ChatGPT/Codex OAuth. | `@openclaw/openai-provider`<br />included in OpenClaw | providers: openai; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders |
| [opencode](/plugins/reference/opencode) | Adds OpenCode model provider support to OpenClaw. | `@openclaw/opencode-provider`<br />included in OpenClaw | providers: opencode; contracts: mediaUnderstandingProviders |
| [opencode-go](/plugins/reference/opencode-go) | Adds OpenCode Go model provider support to OpenClaw. | `@openclaw/opencode-go-provider`<br />included in OpenClaw | providers: opencode-go; contracts: mediaUnderstandingProviders |
| [openrouter](/plugins/reference/openrouter) | Adds OpenRouter model provider support to OpenClaw. | `@openclaw/openrouter-provider`<br />included in OpenClaw | providers: openrouter; contracts: imageGenerationProviders, mediaUnderstandingProviders, musicGenerationProviders, speechProviders, videoGenerationProviders |

View File

@@ -95,7 +95,7 @@ pnpm plugins:inventory:gen
| [oc-path](/plugins/reference/oc-path) | Adds the openclaw path CLI for oc:// workspace file addressing. | `@openclaw/oc-path`<br />included in OpenClaw | plugin |
| [ollama](/plugins/reference/ollama) | Adds Ollama, Ollama Cloud model provider support to OpenClaw. | `@openclaw/ollama-provider`<br />included in OpenClaw | providers: ollama, ollama-cloud; contracts: memoryEmbeddingProviders, webSearchProviders |
| [open-prose](/plugins/reference/open-prose) | OpenProse VM skill pack with a /prose slash command. | `@openclaw/open-prose`<br />included in OpenClaw | skills |
| [openai](/plugins/reference/openai) | Adds OpenAI model provider support to OpenClaw. | `@openclaw/openai-provider`<br />included in OpenClaw | providers: openai; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders |
| [openai](/plugins/reference/openai) | Adds OpenAI model provider support to OpenClaw, including ChatGPT/Codex OAuth. | `@openclaw/openai-provider`<br />included in OpenClaw | providers: openai; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders |
| [opencode](/plugins/reference/opencode) | Adds OpenCode model provider support to OpenClaw. | `@openclaw/opencode-provider`<br />included in OpenClaw | providers: opencode; contracts: mediaUnderstandingProviders |
| [opencode-go](/plugins/reference/opencode-go) | Adds OpenCode Go model provider support to OpenClaw. | `@openclaw/opencode-go-provider`<br />included in OpenClaw | providers: opencode-go; contracts: mediaUnderstandingProviders |
| [openrouter](/plugins/reference/openrouter) | Adds OpenRouter model provider support to OpenClaw. | `@openclaw/openrouter-provider`<br />included in OpenClaw | providers: openrouter; contracts: imageGenerationProviders, mediaUnderstandingProviders, musicGenerationProviders, speechProviders, videoGenerationProviders |

View File

@@ -17,15 +17,3 @@ Adds syntax highlighting for languages outside the default diffs viewer set.
## Surface
plugin
<!-- openclaw-plugin-reference:manual-start -->
## Added languages
The base `diffs` plugin already highlights the common languages documented in [Diffs](/tools/diffs). Install this language pack when you want syntax highlighting for a broader set of Shiki-supported languages. If the pack is not installed, those files still render as readable plain text.
Examples include Astro, Vue, Svelte, MDX, GraphQL, Terraform/HCL, Nix, Clojure, Elixir, Haskell, OCaml, Scala, Zig, Solidity, Verilog/VHDL, Fortran, MATLAB, LaTeX, Mermaid, Sass/Less/SCSS, Nginx, Apache, CSV, dotenv, INI, and diff files.
See [Shiki languages](https://shiki.style/languages) for Shiki's upstream language and alias catalog.
<!-- openclaw-plugin-reference:manual-end -->

View File

@@ -1,5 +1,5 @@
---
summary: "Adds OpenAI model provider support to OpenClaw."
summary: "Adds OpenAI model provider support to OpenClaw, including ChatGPT/Codex OAuth."
read_when:
- You are installing, configuring, or auditing the openai plugin
title: "OpenAI plugin"
@@ -7,7 +7,7 @@ title: "OpenAI plugin"
# OpenAI plugin
Adds OpenAI model provider support to OpenClaw.
Adds OpenAI model provider support to OpenClaw, including ChatGPT/Codex OAuth.
## Distribution

View File

@@ -652,7 +652,6 @@ releases.
| `plugin-sdk/zod` | Deprecated Zod compatibility re-export | Import `zod` from `zod` directly |
| `plugin-sdk/memory-core` | Bundled memory-core helpers | Memory manager/config/file/CLI helper surface |
| `plugin-sdk/memory-core-engine-runtime` | Memory engine runtime facade | Memory index/search runtime facade |
| `plugin-sdk/memory-core-host-embedding-registry` | Memory embedding registry | Lightweight memory embedding provider registry helpers |
| `plugin-sdk/memory-core-host-engine-foundation` | Memory host foundation engine | Memory host foundation engine exports |
| `plugin-sdk/memory-core-host-engine-embeddings` | Memory host embedding engine | Memory embedding contracts, registry access, local provider, and generic batch/remote helpers; concrete remote providers live in their owning plugins |
| `plugin-sdk/memory-core-host-engine-qmd` | Memory host QMD engine | Memory host QMD engine exports |

View File

@@ -76,7 +76,6 @@ by package contract guardrails.
| `plugin-sdk/channel-config-helpers` | `createHybridChannelConfigAdapter`, `resolveChannelDmAccess`, `resolveChannelDmAllowFrom`, `resolveChannelDmPolicy`, `normalizeChannelDmPolicy`, `normalizeLegacyDmAliases` |
| `plugin-sdk/channel-config-schema` | Shared channel config schema primitives plus Zod and direct JSON/TypeBox builders |
| `plugin-sdk/bundled-channel-config-schema` | Bundled OpenClaw channel config schemas for maintained bundled plugins only |
| `plugin-sdk/chat-channel-ids` | `BUNDLED_CHAT_CHANNEL_IDS`, `BUNDLED_CHAT_CHANNEL_ENVELOPE_PREFIXES`, `ChatChannelId`. Canonical bundled/official chat channel ids plus formatter labels/aliases for plugins that need to recognize envelope-prefixed text without hardcoding their own table. |
| `plugin-sdk/channel-config-schema-legacy` | Deprecated compatibility alias for bundled-channel config schemas |
| `plugin-sdk/telegram-command-config` | Telegram custom-command normalization/validation helpers with bundled-contract fallback |
| `plugin-sdk/command-gating` | Narrow command authorization gate helpers |
@@ -356,7 +355,6 @@ usage endpoint failed or returned no usable usage data.
| --- | --- |
| `plugin-sdk/memory-core` | Bundled memory-core helper surface for manager/config/file/CLI helpers |
| `plugin-sdk/memory-core-engine-runtime` | Memory index/search runtime facade |
| `plugin-sdk/memory-core-host-embedding-registry` | Lightweight memory embedding provider registry helpers |
| `plugin-sdk/memory-core-host-engine-foundation` | Memory host foundation engine exports |
| `plugin-sdk/memory-core-host-engine-embeddings` | Memory host embedding contracts, registry access, local provider, and generic batch/remote helpers. `registerMemoryEmbeddingProvider` on this surface is deprecated; use the generic embedding provider API for new providers. |
| `plugin-sdk/memory-core-host-engine-qmd` | Memory host QMD engine exports |

View File

@@ -9,8 +9,7 @@ title: "Workboard plugin"
The Workboard plugin adds an optional Kanban-style board to the
[Control UI](/web/control-ui). Use it to collect agent-sized work cards, assign
them to agents, and track the linked background task, run, and dashboard
session from one card.
them to agents, and jump from a card into the linked dashboard session.
Workboard is intentionally small. It tracks local operating work for an
OpenClaw Gateway; it is not a replacement for GitHub Issues, Linear, Jira, or
@@ -48,8 +47,8 @@ Each card stores:
- priority: `low`, `normal`, `high`, or `urgent`
- labels
- optional agent id
- optional linked task, run, session, or source URL
- optional execution metadata for a Codex or Claude run started from the card
- optional linked session, run, task, or source URL
- optional execution metadata for a Codex or Claude session started from the card
- compact metadata for attempts, comments, links, proof, artifacts, automation,
attachments, worker logs, worker protocol state, claims, diagnostics,
notifications, templates, archive state, and stale-session detection
@@ -66,35 +65,26 @@ proof snippets, related links, comments, archive markers, and stale-session
markers are intentionally local metadata; they do not replace session
transcripts or GitHub issue history.
## Card executions and tasks
## Card executions
Unlinked cards can start work from the card. Autonomous starts use the
Gateway's task-tracked agent run path, then Workboard links the resulting task,
run id, and session key back onto the card. Start uses the Gateway's configured
Unlinked cards can start work from the card. Start uses the Gateway's configured
default agent and model. Codex and Claude actions are optional explicit model
choices:
- Run Codex or Run Claude starts a task-backed agent run, sends the card
prompt, and marks the card `running`.
- Run Codex or Run Claude creates a dashboard session, sends the card prompt,
and marks the card `running`.
- Open Codex or Open Claude creates a linked dashboard session without sending
the card prompt or moving the card, so you can work manually while it stays
attached to the board.
Execution metadata stores the selected engine, mode, model ref, session key,
run id, task id when available, and lifecycle status on the card. Codex
executions use `openai/gpt-5.5`; Claude executions use
`anthropic/claude-sonnet-4-6`.
run id, and lifecycle status on the card. Codex executions use
`openai/gpt-5.5`; Claude executions use `anthropic/claude-sonnet-4-6`.
Each linked execution also records an attempt summary on the same card record.
The attempt summary keeps the engine, mode, model, run id, timestamps, status,
and rolling failure count so repeated failures remain visible on the board.
The dashboard refreshes task status from the Gateway task ledger and matches
tasks back to cards by task id, run id, or linked session key. If a task is
queued or running, the card lifecycle shows active task state. If the task
finishes, fails, times out, or is cancelled, the card lifecycle moves toward
review or blocked status using the same lifecycle sync as linked sessions.
## Agent coordination
Workboard also exposes optional agent tools for board-aware workflows:
@@ -170,15 +160,13 @@ blocked cards that need attention, repeated failures, done cards without proof,
and running cards that only have a loose session link.
Dispatch is intentionally Gateway-local. It does not spawn arbitrary operating
system processes; normal OpenClaw subagent sessions still own execution. The
dispatch action promotes dependency-ready cards, records dispatch metadata on
system processes; normal OpenClaw subagent sessions still own execution. A
dispatch nudge promotes dependency-ready cards, records dispatch metadata on
ready cards, blocks expired claims or timed-out runs, marks board-configured
triage cards as orchestration candidates, then claims a small batch of ready
cards and starts worker runs through the Gateway subagent runtime. Assigned
cards use `agent:<id>:subagent:workboard-*` worker session keys; unassigned
cards use unscoped `subagent:workboard-*` keys so the Gateway still resolves the
configured default agent. Workers get bounded card context plus the claim token
they need to heartbeat, complete, or block the card through the Workboard tools.
cards and starts worker runs through the Gateway subagent runtime. Workers get
bounded card context plus the claim token they need to heartbeat, complete, or
block the card through the Workboard tools.
### Dispatch worker selection

View File

@@ -6,7 +6,7 @@ read_when:
title: "MiniMax"
---
OpenClaw's MiniMax provider defaults to **MiniMax M3**.
OpenClaw's MiniMax provider defaults to **MiniMax M2.7**.
MiniMax also provides:
@@ -26,8 +26,7 @@ Provider split:
| Model | Type | Description |
| ------------------------ | ---------------- | ---------------------------------------- |
| `MiniMax-M3` | Chat (reasoning) | Default hosted reasoning model |
| `MiniMax-M2.7` | Chat (reasoning) | Previous hosted reasoning model |
| `MiniMax-M2.7` | Chat (reasoning) | Default hosted reasoning model |
| `MiniMax-M2.7-highspeed` | Chat (reasoning) | Faster M2.7 reasoning tier |
| `MiniMax-VL-01` | Vision | Image understanding model |
| `image-01` | Image generation | Text-to-image and image-to-image editing |
@@ -80,7 +79,7 @@ Choose your preferred auth method and follow the setup steps.
</Tabs>
<Note>
OAuth setups use the `minimax-portal` provider id. Model refs follow the form `minimax-portal/MiniMax-M3`.
OAuth setups use the `minimax-portal` provider id. Model refs follow the form `minimax-portal/MiniMax-M2.7`.
</Note>
<Tip>
@@ -132,7 +131,7 @@ Choose your preferred auth method and follow the setup steps.
```json5
{
env: { MINIMAX_API_KEY: "sk-..." },
agents: { defaults: { model: { primary: "minimax/MiniMax-M3" } } },
agents: { defaults: { model: { primary: "minimax/MiniMax-M2.7" } } },
models: {
mode: "merge",
providers: {
@@ -141,15 +140,6 @@ Choose your preferred auth method and follow the setup steps.
apiKey: "${MINIMAX_API_KEY}",
api: "anthropic-messages",
models: [
{
id: "MiniMax-M3",
name: "MiniMax M3",
reasoning: true,
input: ["text", "image"],
cost: { input: 0.6, output: 2.4, cacheRead: 0.12, cacheWrite: 0 },
contextWindow: 1000000,
maxTokens: 131072,
},
{
id: "MiniMax-M2.7",
name: "MiniMax M2.7",
@@ -180,7 +170,7 @@ Choose your preferred auth method and follow the setup steps.
</Warning>
<Note>
API-key setups use the `minimax` provider id. Model refs follow the form `minimax/MiniMax-M3`.
API-key setups use the `minimax` provider id. Model refs follow the form `minimax/MiniMax-M2.7`.
</Note>
</Tab>
@@ -253,10 +243,9 @@ through the CN endpoint; the default global endpoint is
`https://api.minimax.io`.
When onboarding or API-key setup writes explicit `models.providers.minimax`
entries, OpenClaw materializes `MiniMax-M3`, `MiniMax-M2.7`, and
`MiniMax-M2.7-highspeed` as chat models. M3 advertises text and image input;
image understanding remains exposed separately through the plugin-owned
`MiniMax-VL-01` media provider.
entries, OpenClaw materializes `MiniMax-M2.7` and
`MiniMax-M2.7-highspeed` as text-only chat models. Image understanding is
exposed separately through the plugin-owned `MiniMax-VL-01` media provider.
<Note>
See [Image Generation](/tools/image-generation) for shared tool parameters, provider selection, and failover behavior.
@@ -364,7 +353,7 @@ catalog:
| `minimax-portal` | `MiniMax-VL-01` |
That is why automatic media routing can use MiniMax image understanding even
when the bundled text-provider catalog also includes M3 image-capable chat refs.
when the bundled text-provider catalog still shows text-only M2.7 chat refs.
### Web search
@@ -448,12 +437,12 @@ See [MiniMax Search](/tools/minimax-search) for full web search configuration an
- Model refs follow the auth path:
- API-key setup: `minimax/<model>`
- OAuth setup: `minimax-portal/<model>`
- Default chat model: `MiniMax-M3`
- Alternate chat models: `MiniMax-M2.7`, `MiniMax-M2.7-highspeed`
- Onboarding and direct API-key setup write model definitions for M3 and both M2.7 variants
- Default chat model: `MiniMax-M2.7`
- Alternate chat model: `MiniMax-M2.7-highspeed`
- Onboarding and direct API-key setup write text-only model definitions for both M2.7 variants
- Image understanding uses the plugin-owned `MiniMax-VL-01` media provider
- Update pricing values in `models.json` if you need exact cost tracking
- Use `openclaw models list` to confirm the current provider id, then switch with `openclaw models set minimax/MiniMax-M3` or `openclaw models set minimax-portal/MiniMax-M3`
- Use `openclaw models list` to confirm the current provider id, then switch with `openclaw models set minimax/MiniMax-M2.7` or `openclaw models set minimax-portal/MiniMax-M2.7`
<Tip>
Referral link for MiniMax Coding Plan (10% off): [MiniMax Coding Plan](https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link)
@@ -466,7 +455,7 @@ See [Model providers](/concepts/model-providers) for provider rules.
## Troubleshooting
<AccordionGroup>
<Accordion title='"Unknown model: minimax/MiniMax-M3"'>
<Accordion title='"Unknown model: minimax/MiniMax-M2.7"'>
This usually means the **MiniMax provider is not configured** (no matching provider entry and no MiniMax auth profile/env key found). A fix for this detection is in **2026.1.12**. Fix by:
- Upgrading to **2026.1.12** (or run from source `main`), then restarting the gateway.
@@ -476,8 +465,8 @@ See [Model providers](/concepts/model-providers) for provider rules.
Make sure the model id is **case-sensitive**:
- API-key path: `minimax/MiniMax-M3`, `minimax/MiniMax-M2.7`, or `minimax/MiniMax-M2.7-highspeed`
- OAuth path: `minimax-portal/MiniMax-M3`, `minimax-portal/MiniMax-M2.7`, or `minimax-portal/MiniMax-M2.7-highspeed`
- API-key path: `minimax/MiniMax-M2.7` or `minimax/MiniMax-M2.7-highspeed`
- OAuth path: `minimax-portal/MiniMax-M2.7` or `minimax-portal/MiniMax-M2.7-highspeed`
Then recheck with:

View File

@@ -457,10 +457,6 @@ The branch already has a real shared SQLite base:
- GitHub Copilot token exchange cache uses the shared SQLite plugin-state table
under `github-copilot/token-cache/default`. It is provider-owned cache state,
so it intentionally does not add a host schema table.
- GitHub Copilot compaction no longer writes `openclaw-compaction-*.json`
workspace sidecars. The harness calls the SDK history compaction RPC for the
tracked SDK session, and OpenClaw keeps durable session/transcript state in
SQLite instead of compatibility marker files.
- The shared Swift runtime (`OpenClawKit`) uses the same
`state/openclaw.sqlite` rows for device identity and device auth. macOS app
helpers import the shared SQLite helpers instead of owning a second JSON or

View File

@@ -100,11 +100,6 @@ The shorthand is also accepted:
Code mode remains off when `tools.codeMode` is omitted, `false`, or an object
without `enabled: true`.
When you use sandboxed agents with configured MCP servers, also make sure the
sandbox tool policy allows the bundled MCP plugin, for example with
`tools.sandbox.tools.alsoAllow: ["bundle-mcp"]`. See
[Configuration - tools and custom providers](/gateway/config-tools#mcp-and-plugin-tools-inside-sandbox-tool-policy).
Use explicit limits when you want tighter bounds:
```json5
@@ -498,20 +493,6 @@ declare namespace MCP.github {
}
```
The declaration files are virtual, not files written under the workspace or
state directory. For each code-mode `exec` call, OpenClaw builds the run-scoped
tool catalog, keeps the visible MCP entries, renders `mcp/index.d.ts` plus one
`mcp/<server>.d.ts` declaration per visible server, and injects that small
read-only table into the QuickJS worker. Guest code sees only the `API` object:
`API.list(prefix?)` returns file metadata and `API.read(path)` returns the
selected declaration content. Unknown paths and `.` / `..` segments are rejected.
This keeps large MCP schemas out of the model prompt. The agent learns that the
virtual API exists from the `exec` tool description, reads only the needed
declaration file, and then calls `MCP.<server>.<tool>()` with one object argument.
`MCP.<server>.$api()` remains available as an inline fallback when the agent
needs a single-tool schema response inside the program.
The guest runtime must not expose host objects directly. Inputs and outputs cross
the bridge as JSON-compatible values with explicit size caps.

View File

@@ -340,11 +340,19 @@ OpenClaw also enforces a safety floor for embedded runs:
- If `compaction.reserveTokens < reserveTokensFloor`, OpenClaw bumps it.
- Default floor is `20000` tokens.
- Set `agents.defaults.compaction.reserveTokensFloor: 0` to disable the floor.
- If it's already higher, OpenClaw leaves it alone.
- When the active model context window is known, OpenClaw first caps that floor
so `reserveTokens` cannot consume the whole context window. This keeps
smaller local models with 16K-32K windows from entering an immediate
compaction loop.
- Explicit or existing `reserveTokens` values can also be capped to the same
context-aware maximum. Without a known context window, OpenClaw keeps the old
uncapped floor behavior.
- Manual `/compact` honors an explicit `agents.defaults.compaction.keepRecentTokens`
and keeps OpenClaw runtime's recent-tail cut point. Without an explicit keep budget,
manual compaction remains a hard checkpoint and rebuilt context starts from
the new summary.
- When the active model context window is known, OpenClaw caps `keepRecentTokens`
to the prompt budget left after `reserveTokens` and summary overhead.
- Set `agents.defaults.compaction.midTurnPrecheck.enabled: true` to run the
optional tool-loop precheck after new tool results and before the next model
call. This is a trigger only; summary generation still uses the configured
@@ -362,8 +370,12 @@ OpenClaw also enforces a safety floor for embedded runs:
Why: leave enough headroom for multi-turn "housekeeping" (like memory writes) before compaction becomes unavoidable.
Implementation: `ensureAgentCompactionReserveTokens()` in `src/agents/agent-settings.ts`
(called from `src/agents/embedded-agent-runner.ts`).
Implementation: `applyAgentCompactionSettingsFromConfig()` in
`src/agents/agent-settings.ts` applies config, reserve floors, context-window
caps, and keep-recent caps for embedded runs. The older
`ensureAgentCompactionReserveTokens()` helper is not context-aware and should
not be used for small-context model setup unless the caller has already chosen
a safe floor.
---

View File

@@ -20,7 +20,7 @@ OpenClaw assembles its own system prompt on every run. It includes:
prompt surface. It is bounded by `skills.limits.maxSkillsPromptChars`, with
optional per-agent override at `agents.list[].skillsLimits.maxSkillsPromptChars`.
- Self-update instructions
- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus `MEMORY.md` when present). Native Codex turns do not paste raw `MEMORY.md` from the configured agent workspace when memory tools are available for that workspace; they include a small memory pointer in turn-scoped collaboration developer instructions and use memory tools on demand. If tools are disabled, memory search is unavailable, or the active workspace differs from the agent memory workspace, `MEMORY.md` uses the normal bounded turn-context path. Lowercase root `memory.md` is not injected; it is legacy repair input for `openclaw doctor --fix` when paired with `MEMORY.md`. Large injected files are truncated by `agents.defaults.bootstrapMaxChars` (default: 20000), and total bootstrap injection is capped by `agents.defaults.bootstrapTotalMaxChars` (default: 60000). `memory/*.md` daily files are not part of the normal bootstrap prompt; they remain on-demand via memory tools on ordinary turns, but reset/startup model runs can prepend a one-shot startup-context block with recent daily memory for that first turn. Bare chat `/new` and `/reset` commands are acknowledged without invoking the model. The startup prelude is controlled by `agents.defaults.startupContext`. Post-compaction AGENTS.md excerpts are separate and require explicit `agents.defaults.compaction.postCompactionSections` opt-in.
- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus `MEMORY.md` when present). Native Codex turns do not paste raw `MEMORY.md` from the configured agent workspace when memory tools are available for that workspace; they include a small memory pointer in turn-scoped collaboration developer instructions and use memory tools on demand. If tools are disabled, memory search is unavailable, or the active workspace differs from the agent memory workspace, `MEMORY.md` uses the normal bounded turn-context path. Lowercase root `memory.md` is not injected; it is legacy repair input for `openclaw doctor --fix` when paired with `MEMORY.md`. Large injected files are truncated by `agents.defaults.bootstrapMaxChars` (default: 12000), and total bootstrap injection is capped by `agents.defaults.bootstrapTotalMaxChars` (default: 60000). `memory/*.md` daily files are not part of the normal bootstrap prompt; they remain on-demand via memory tools on ordinary turns, but reset/startup model runs can prepend a one-shot startup-context block with recent daily memory for that first turn. Bare chat `/new` and `/reset` commands are acknowledged without invoking the model. The startup prelude is controlled by `agents.defaults.startupContext`. Post-compaction AGENTS.md excerpts are separate and require explicit `agents.defaults.compaction.postCompactionSections` opt-in.
- Time (UTC + user timezone)
- Reply tags + heartbeat behavior
- Runtime metadata (host/OS/model/thinking)

View File

@@ -47,7 +47,7 @@ For a high-level overview, see [Onboarding (CLI)](/start/wizard).
- More detail: [Vercel AI Gateway](/providers/vercel-ai-gateway)
- **Cloudflare AI Gateway**: prompts for Account ID, Gateway ID, and `CLOUDFLARE_AI_GATEWAY_API_KEY`.
- More detail: [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway)
- **MiniMax**: config is auto-written; hosted default is `MiniMax-M3`.
- **MiniMax**: config is auto-written; hosted default is `MiniMax-M2.7`.
API-key setup uses `minimax/...`, and OAuth setup uses
`minimax-portal/...`.
- More detail: [MiniMax](/providers/minimax)

View File

@@ -182,7 +182,7 @@ What you set:
More detail: [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway).
</Accordion>
<Accordion title="MiniMax">
Config is auto-written. Hosted default is `MiniMax-M3`; API-key setup uses
Config is auto-written. Hosted default is `MiniMax-M2.7`; API-key setup uses
`minimax/...`, and OAuth setup uses `minimax-portal/...`.
More detail: [MiniMax](/providers/minimax).
</Accordion>

View File

@@ -216,9 +216,7 @@ Install the Diff Viewer Language Pack plugin to highlight other languages:
openclaw plugins install clawhub:@openclaw/diffs-language-pack
```
With the language pack available, OpenClaw can highlight many more languages. If the pack is not installed, files outside the default list still render as readable plain text. Examples include Astro, Vue, Svelte, MDX, GraphQL, Terraform/HCL, Nix, Clojure, Elixir, Haskell, OCaml, Scala, Zig, Solidity, Verilog/VHDL, Fortran, MATLAB, LaTeX, Mermaid, Sass/Less/SCSS, Nginx, Apache, CSV, dotenv, INI, and diff files.
See [Diffs Language Pack plugin](/plugins/reference/diffs-language-pack) for details and [Shiki languages](https://shiki.style/languages) for Shiki's upstream language and alias catalog.
With the language pack available, OpenClaw automatically uses it for languages outside the default list. Without it, those files stay readable as plain text.
## Output details contract

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/acpx",
"version": "2026.6.1",
"version": "2026.5.31",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/acpx",
"version": "2026.6.1",
"version": "2026.5.31",
"dependencies": {
"@agentclientprotocol/claude-agent-acp": "0.39.0",
"@zed-industries/codex-acp": "0.15.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/acpx",
"version": "2026.6.1",
"version": "2026.5.31",
"description": "OpenClaw ACP runtime backend with plugin-owned session and transport management.",
"repository": {
"type": "git",
@@ -26,10 +26,10 @@
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.5.31"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.5.31",
"staticAssets": [
{
"source": "./src/runtime-internals/mcp-proxy.mjs",

View File

@@ -312,120 +312,6 @@ describe("prepareAcpxCodexAuthConfig", () => {
expect(path.resolve(String(launched.codexHome))).toBe(expectedCodexHome);
});
it("writes API-key auth into the isolated Codex ACP home when env auth is present", async () => {
const root = await makeTempDir();
const stateDir = path.join(root, "state");
const generated = generatedCodexPaths(stateDir);
const installedBinPath = path.join(root, "codex-acp-bin.js");
await fs.writeFile(
installedBinPath,
"console.log(JSON.stringify({ codexHome: process.env.CODEX_HOME }));\n",
"utf8",
);
const pluginConfig = resolveAcpxPluginConfig({
rawConfig: {},
workspaceDir: root,
});
await prepareAcpxCodexAuthConfig({
pluginConfig,
stateDir,
resolveInstalledCodexAcpBinPath: async () => installedBinPath,
});
await execFileAsync(process.execPath, [generated.wrapperPath], {
cwd: root,
env: { ...process.env, CODEX_API_KEY: "", OPENAI_API_KEY: "sk-test-api-key" },
});
const authPath = path.join(stateDir, "acpx", "codex-home", "auth.json");
const auth = JSON.parse(await fs.readFile(authPath, "utf8")) as {
auth_mode?: unknown;
OPENAI_API_KEY?: unknown;
};
expect(auth).toMatchObject({
OPENAI_API_KEY: "sk-test-api-key",
});
expect(auth).not.toHaveProperty("auth_mode");
if (process.platform !== "win32") {
const mode = (await fs.stat(authPath)).mode & 0o777;
expect(mode).toBe(0o600);
}
});
it("preserves existing isolated Codex auth when env auth is present", async () => {
const root = await makeTempDir();
const stateDir = path.join(root, "state");
const generated = generatedCodexPaths(stateDir);
const installedBinPath = path.join(root, "codex-acp-bin.js");
await fs.writeFile(installedBinPath, "console.log('ok');\n", "utf8");
const pluginConfig = resolveAcpxPluginConfig({
rawConfig: {},
workspaceDir: root,
});
await prepareAcpxCodexAuthConfig({
pluginConfig,
stateDir,
resolveInstalledCodexAcpBinPath: async () => installedBinPath,
});
const authPath = path.join(stateDir, "acpx", "codex-home", "auth.json");
const existingAuth = {
auth_mode: "chatgpt",
tokens: { access_token: "existing-token" },
last_refresh: null,
};
await fs.writeFile(authPath, `${JSON.stringify(existingAuth)}\n`, { mode: 0o600 });
await execFileAsync(process.execPath, [generated.wrapperPath], {
cwd: root,
env: { ...process.env, OPENAI_API_KEY: "sk-test-api-key" },
});
expect(JSON.parse(await fs.readFile(authPath, "utf8"))).toEqual(existingAuth);
});
it("updates existing isolated Codex API-key auth when env auth changes", async () => {
const root = await makeTempDir();
const stateDir = path.join(root, "state");
const generated = generatedCodexPaths(stateDir);
const installedBinPath = path.join(root, "codex-acp-bin.js");
await fs.writeFile(installedBinPath, "console.log('ok');\n", "utf8");
const pluginConfig = resolveAcpxPluginConfig({
rawConfig: {},
workspaceDir: root,
});
await prepareAcpxCodexAuthConfig({
pluginConfig,
stateDir,
resolveInstalledCodexAcpBinPath: async () => installedBinPath,
});
const authPath = path.join(stateDir, "acpx", "codex-home", "auth.json");
await fs.writeFile(
authPath,
`${JSON.stringify({
OPENAI_API_KEY: "sk-old-api-key",
tokens: null,
last_refresh: null,
})}\n`,
{ mode: 0o600 },
);
await execFileAsync(process.execPath, [generated.wrapperPath], {
cwd: root,
env: { ...process.env, CODEX_API_KEY: "sk-new-api-key", OPENAI_API_KEY: "sk-other-key" },
});
expect(JSON.parse(await fs.readFile(authPath, "utf8"))).toMatchObject({
OPENAI_API_KEY: "sk-new-api-key",
tokens: null,
last_refresh: null,
});
});
it("launches the locally installed Claude ACP bin without going through npm", async () => {
const root = await makeTempDir();
const stateDir = path.join(root, "state");

View File

@@ -4,11 +4,11 @@ import { createRequire } from "node:module";
import os from "node:os";
import path from "node:path";
import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
import { quoteCommandPart, splitCommandParts } from "./command-line.js";
import {
extractTrustedCodexProjectPaths,
renderIsolatedCodexConfig,
} from "./codex-trust-config.js";
import { quoteCommandPart, splitCommandParts } from "./command-line.js";
import { resolveAcpxPluginRoot } from "./config.js";
import type { ResolvedAcpxPluginConfig } from "./config.js";
import {
@@ -528,35 +528,6 @@ function buildCodexAcpWrapperScript(installedBinPath?: string): string {
installedBinPath,
stderrLogFileNamePrefix: "codex-acp-wrapper.stderr",
envSetup: `const codexHome = fileURLToPath(new URL("./codex-home/", import.meta.url));
const codexAuthPath = fileURLToPath(new URL("./codex-home/auth.json", import.meta.url));
const codexApiKey = (process.env.CODEX_API_KEY || process.env.OPENAI_API_KEY || "").trim();
let shouldWriteCodexApiKeyAuth = false;
if (codexApiKey) {
if (!existsSync(codexAuthPath)) {
shouldWriteCodexApiKeyAuth = true;
} else {
try {
const existingCodexAuth = JSON.parse(readFileSync(codexAuthPath, "utf8"));
shouldWriteCodexApiKeyAuth =
!existingCodexAuth ||
typeof existingCodexAuth !== "object" ||
typeof existingCodexAuth.OPENAI_API_KEY === "string";
} catch {
shouldWriteCodexApiKeyAuth = true;
}
}
}
if (shouldWriteCodexApiKeyAuth) {
writeFileSync(
codexAuthPath,
JSON.stringify({
OPENAI_API_KEY: codexApiKey,
tokens: null,
last_refresh: null,
}) + "\\n",
{ mode: 0o600 },
);
}
const env = {
...process.env,
CODEX_HOME: codexHome,

View File

@@ -262,12 +262,7 @@ async function terminatePids(
deps: AcpxProcessCleanupDeps | undefined,
): Promise<number[]> {
const killProcess = deps?.killProcess ?? ((pid, signal) => process.kill(pid, signal));
const sleep =
deps?.sleep ??
((ms) =>
new Promise<void>((resolve) => {
setTimeout(resolve, ms);
}));
const sleep = deps?.sleep ?? ((ms) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
const terminated: number[] = [];
for (const pid of pids) {
@@ -307,7 +302,7 @@ export async function cleanupOpenClawOwnedAcpxProcessTree(params: {
return { inspectedPids: [], terminatedPids: [], skippedReason: "missing-root" };
}
let processes: AcpxProcessInfo[];
let processes: AcpxProcessInfo[] = [];
try {
processes = await (params.deps?.listProcesses ?? listPlatformProcesses)();
} catch {

View File

@@ -68,7 +68,7 @@ class LegacyRunTurnEventQueue {
return item;
}
if (this.error) {
throw toLintErrorObject(this.error, "Non-Error thrown");
throw this.error;
}
if (this.closed) {
return null;
@@ -178,17 +178,3 @@ export function lazyStartRuntimeTurn(
},
};
}
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
if (value instanceof Error) {
return value;
}
if (typeof value === "string") {
return new Error(value);
}
const error = new Error(fallbackMessage, { cause: value });
if ((typeof value === "object" && value !== null) || typeof value === "function") {
Object.assign(error, value);
}
return error;
}

View File

@@ -286,7 +286,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
})
.then(
() => ({ status: "resolved" as const }),
(error: unknown) => ({ status: "rejected" as const, error }),
(error) => ({ status: "rejected" as const, error }),
);
expect(outcome.status).toBe("rejected");
@@ -298,12 +298,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
code: "ACP_SESSION_INIT_FAILED",
message: expect.stringContaining("deployment missing"),
});
const error = outcome.error;
expect(error).toBeInstanceOf(AcpRuntimeError);
if (!(error instanceof AcpRuntimeError)) {
throw new Error("expected AcpRuntimeError");
}
expect(error.message).not.toContain("sk-testsecret1234567890");
expect(outcome.error.message).not.toContain("sk-testsecret1234567890");
});
it("adds Codex wrapper stderr tail to generic first-turn failures", async () => {

View File

@@ -1196,7 +1196,7 @@ export class AcpxRuntime implements AcpRuntime {
const record = await this.sessionStore.load(
input.handle.acpxRecordId ?? input.handle.sessionKey,
);
let closeSucceeded;
let closeSucceeded = false;
try {
await this.resolveDelegateForLoadedRecord(input.handle, record).close({
handle: input.handle,

View File

@@ -218,21 +218,13 @@ describe("active-memory plugin", () => {
};
const waitForAbort = async (abortSignal?: AbortSignal): Promise<never> => {
if (abortSignal?.aborted) {
throw toLintErrorObject(
(abortSignal.reason as unknown) ?? new Error("Operation aborted"),
"Non-Error thrown",
);
throw (abortSignal.reason as unknown) ?? new Error("Operation aborted");
}
return await new Promise<never>((_resolve, reject) => {
abortSignal?.addEventListener(
"abort",
() => {
reject(
toLintErrorObject(
(abortSignal.reason as unknown) ?? new Error("Operation aborted"),
"Non-Error rejection",
),
);
reject((abortSignal.reason as unknown) ?? new Error("Operation aborted"));
},
{ once: true },
);
@@ -2966,9 +2958,7 @@ describe("active-memory plugin", () => {
};
plugin.register(api as unknown as OpenClawPluginApi);
runEmbeddedAgent.mockImplementationOnce(async (params: { timeoutMs?: number }) => {
await new Promise((resolve) => {
setTimeout(resolve, (params.timeoutMs ?? 0) + 5);
});
await new Promise((resolve) => setTimeout(resolve, (params.timeoutMs ?? 0) + 5));
return {
payloads: [{ text: "late timeout payload that should never become memory context" }],
meta: { aborted: true },
@@ -3011,9 +3001,7 @@ describe("active-memory plugin", () => {
};
plugin.register(api as unknown as OpenClawPluginApi);
runEmbeddedAgent.mockImplementationOnce(async () => {
await new Promise((resolve) => {
setTimeout(resolve, CONFIGURED_TIMEOUT_MS + 5);
});
await new Promise((resolve) => setTimeout(resolve, CONFIGURED_TIMEOUT_MS + 5));
return { payloads: [{ text: "remember the ramen place" }] };
});
@@ -3143,9 +3131,7 @@ describe("active-memory plugin", () => {
},
},
]);
await new Promise((resolve) => {
setTimeout(resolve, 35);
});
await new Promise((resolve) => setTimeout(resolve, 35));
return { payloads: [{ text: "User usually orders ramen." }] };
});
@@ -3218,7 +3204,7 @@ describe("active-memory plugin", () => {
testing.setSetupGraceTimeoutMsForTests(0);
api.pluginConfig = {
agents: ["main"],
timeoutMs: 1_000,
timeoutMs: 100,
};
plugin.register(api as unknown as OpenClawPluginApi);
hoisted.sessionStore["agent:main:memory-get-miss"] = {
@@ -3235,9 +3221,7 @@ describe("active-memory plugin", () => {
},
},
]);
await new Promise((resolve) => {
setTimeout(resolve, 35);
});
await new Promise((resolve) => setTimeout(resolve, 35));
return { payloads: [{ text: "User usually orders ramen after late flights." }] };
});
@@ -4358,17 +4342,3 @@ describe("active-memory plugin", () => {
expect(config.circuitBreakerCooldownMs).toBe(5000);
});
});
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
if (value instanceof Error) {
return value;
}
if (typeof value === "string") {
return new Error(value);
}
const error = new Error(fallbackMessage, { cause: value });
if ((typeof value === "object" && value !== null) || typeof value === "function") {
Object.assign(error, value);
}
return error;
}

View File

@@ -1011,6 +1011,7 @@ function buildPromptStyleLines(style: ActiveMemoryPromptStyle): string[] {
"If relevant memory is mostly a stable user preference or recurring habit, lean toward returning it.",
"If the strongest match is only a one-off historical fact and not a recurring preference or habit, prefer NONE unless the latest user message clearly asks for that fact.",
];
case "balanced":
default:
return [
"Treat the latest user message as the primary query.",
@@ -1981,7 +1982,7 @@ async function waitForSubagentPartialTimeoutData(
(await Promise.race([
subagentPromise.then(
() => undefined,
(error: unknown) => readPartialTimeoutData(error),
(error) => readPartialTimeoutData(error),
),
timeoutPromise,
])) ?? {}

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/admin-http-rpc",
"version": "2026.6.1",
"version": "2026.5.31",
"private": true,
"description": "OpenClaw admin HTTP RPC endpoint",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/alibaba-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"private": true,
"description": "OpenClaw Alibaba Model Studio video provider plugin",
"type": "module",

View File

@@ -0,0 +1,7 @@
import { describePluginRegistrationContract } from "openclaw/plugin-sdk/plugin-test-contracts";
describePluginRegistrationContract({
pluginId: "alibaba",
videoGenerationProviderIds: ["alibaba"],
requireGenerateVideo: true,
});

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/amazon-bedrock-mantle-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/amazon-bedrock-mantle-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"dependencies": {
"@anthropic-ai/sdk": "0.100.1",
"@aws/bedrock-token-generator": "1.1.0"

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/amazon-bedrock-mantle-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"description": "OpenClaw Amazon Bedrock Mantle provider plugin for OpenAI-compatible model routing.",
"repository": {
"type": "git",
@@ -24,10 +24,10 @@
"minHostVersion": ">=2026.5.12-beta.1"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.5.31"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.5.31",
"bundledDist": false
},
"release": {

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/amazon-bedrock-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/amazon-bedrock-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"dependencies": {
"@aws-sdk/client-bedrock": "3.1056.0",
"@aws-sdk/client-bedrock-runtime": "3.1056.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/amazon-bedrock-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"description": "OpenClaw Amazon Bedrock provider plugin with model discovery, embeddings, and guardrail support.",
"repository": {
"type": "git",
@@ -28,10 +28,10 @@
"minHostVersion": ">=2026.5.12-beta.1"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.5.31"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.5.31",
"bundledDist": false
},
"release": {

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"dependencies": {
"@anthropic-ai/vertex-sdk": "0.16.1"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"description": "OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI.",
"repository": {
"type": "git",
@@ -23,10 +23,10 @@
"minHostVersion": ">=2026.5.12-beta.1"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.5.31"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.5.31",
"bundledDist": false
},
"release": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/anthropic-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"private": true,
"description": "OpenClaw Anthropic provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/arcee-provider",
"version": "2026.6.1",
"version": "2026.5.31",
"private": true,
"description": "OpenClaw Arcee provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/azure-speech",
"version": "2026.6.1",
"version": "2026.5.31",
"private": true,
"description": "OpenClaw Azure Speech plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/bonjour",
"version": "2026.6.1",
"version": "2026.5.31",
"description": "OpenClaw Bonjour/mDNS gateway discovery",
"type": "module",
"dependencies": {

View File

@@ -571,7 +571,7 @@ export async function startGatewayBonjourAdvertiser(
.then(() => {
logger.info(`bonjour: advertised ${serviceSummary(label, svc)}`);
})
.catch((err: unknown) => {
.catch((err) => {
handleAdvertiseFailure(label, svc, err, "failed");
});
} catch (err) {
@@ -747,7 +747,7 @@ export async function startGatewayBonjourAdvertiser(
)})`,
);
try {
void svc.advertise().catch((err: unknown) => {
void svc.advertise().catch((err) => {
logger.warn(
`bonjour: watchdog re-advertise failed (${serviceSummary(label, svc)}): ${formatBonjourError(err)}`,
);

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/brave-plugin",
"version": "2026.6.1",
"version": "2026.5.31",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/brave-plugin",
"version": "2026.6.1"
"version": "2026.5.31"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/brave-plugin",
"version": "2026.6.1",
"version": "2026.5.31",
"description": "OpenClaw Brave Search provider plugin for web search.",
"repository": {
"type": "git",
@@ -21,10 +21,10 @@
"allowInvalidConfigRecovery": true
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.5.31"
},
"build": {
"openclawVersion": "2026.6.1"
"openclawVersion": "2026.5.31"
},
"release": {
"publishToClawHub": true,

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