mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-21 16:41:56 +00:00
docs: rename onboarding user-facing wizard copy
Co-authored-by: Tak <contact-redacted@example.com>
This commit is contained in:
@@ -105,7 +105,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/usage tracking: stop forcing `supportsUsageInStreaming: false` on non-native OpenAI-completions providers so compatible backends report token usage and cost again instead of showing all zeros. (#46500) Fixes #46142. Thanks @ademczuk.
|
||||
- Plugins/subagents: preserve gateway-owned plugin subagent access across runtime, tool, and embedded-runner load paths so gateway plugin tools and context engines can still spawn and manage subagents after the loader cache split. (#46648) Thanks @jalehman.
|
||||
- Control UI/overview: keep the language dropdown aligned with the persisted locale during dashboard startup so refreshing the page does not fall back to English before locale hydration completes. (#48019) Thanks @git-jxj.
|
||||
- Agents/compaction: rerun transcript repair after `session.compact()` so orphaned `tool_result` blocks cannot survive compaction and break later Anthropic requests. (#16095) thanks @claw-sylphx.
|
||||
|
||||
## 2026.3.13
|
||||
|
||||
@@ -184,7 +183,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Auth/login lockout recovery: clear stale `auth_permanent` and `billing` disabled state for all profiles matching the target provider when `openclaw models auth login` is invoked, so users locked out by expired or revoked OAuth tokens can recover by re-authenticating instead of waiting for the cooldown timer to expire. (#43057)
|
||||
- Auto-reply/context-engine compaction: persist the exact embedded-run metadata compaction count for main and followup runner session accounting, so metadata-only auto-compactions no longer undercount multi-compaction runs. (#42629) thanks @uf-hy.
|
||||
- Auth/Codex CLI reuse: sync reused Codex CLI credentials into the supported `openai-codex:default` OAuth profile instead of reviving the deprecated `openai-codex:codex-cli` slot, so doctor cleanup no longer loops. (#45353) thanks @Gugu-sugar.
|
||||
- WhatsApp/group replies: recognize implicit reply-to-bot mentions when WhatsApp sends the quoted sender in `@lid` format, including device-suffixed self identities. (#23029) Thanks @sparkyrider.
|
||||
|
||||
## 2026.3.12
|
||||
|
||||
@@ -276,7 +274,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/Anthropic replay: drop replayed assistant thinking blocks for native Anthropic and Bedrock Claude providers so persisted follow-up turns no longer fail on stored thinking blocks. (#44843) Thanks @jmcte.
|
||||
- Docs/Brave pricing: escape literal dollar signs in Brave Search cost text so the docs render the free credit and per-request pricing correctly. (#44989) Thanks @keelanfh.
|
||||
- Feishu/file uploads: preserve literal UTF-8 filenames in `im.file.create` so Chinese and other non-ASCII filenames no longer appear percent-encoded in chat. (#34262) Thanks @fabiaodemianyang and @KangShuaiFu.
|
||||
- Agents/compaction safeguard: trim large kept `toolResult` payloads consistently for budgeting, pruning, and identifier seeding, then restore preserved payloads after prune so oversized safeguard summaries stay stable. (#44133) thanks @SayrWolfridge.
|
||||
|
||||
## 2026.3.11
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -23,10 +23,10 @@ It answers you on the channels you already use (WhatsApp, Telegram, Slack, Disco
|
||||
|
||||
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
|
||||
|
||||
[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [Vision](VISION.md) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/help/faq) · [Wizard](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-openclaw) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd)
|
||||
[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [Vision](VISION.md) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/help/faq) · [Onboarding](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-openclaw) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd)
|
||||
|
||||
Preferred setup: run the onboarding wizard (`openclaw onboard`) in your terminal.
|
||||
The wizard guides you step by step through setting up the gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
|
||||
Preferred setup: run `openclaw onboard` in your terminal.
|
||||
OpenClaw Onboard guides you step by step through setting up the gateway, workspace, channels, and skills. It is the recommended CLI setup path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
|
||||
Works with npm, pnpm, or bun.
|
||||
New install? Start here: [Getting started](https://docs.openclaw.ai/start/getting-started)
|
||||
|
||||
@@ -58,7 +58,7 @@ npm install -g openclaw@latest
|
||||
openclaw onboard --install-daemon
|
||||
```
|
||||
|
||||
The wizard installs the Gateway daemon (launchd/systemd user service) so it stays running.
|
||||
OpenClaw Onboard installs the Gateway daemon (launchd/systemd user service) so it stays running.
|
||||
|
||||
## Quick start (TL;DR)
|
||||
|
||||
@@ -132,7 +132,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
|
||||
- **[Live Canvas](https://docs.openclaw.ai/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
|
||||
- **[First-class tools](https://docs.openclaw.ai/tools)** — browser, canvas, nodes, cron, sessions, and Discord/Slack actions.
|
||||
- **[Companion apps](https://docs.openclaw.ai/platforms/macos)** — macOS menu bar app + iOS/Android [nodes](https://docs.openclaw.ai/nodes).
|
||||
- **[Onboarding](https://docs.openclaw.ai/start/wizard) + [skills](https://docs.openclaw.ai/tools/skills)** — wizard-driven setup with bundled/managed/workspace skills.
|
||||
- **[Onboarding](https://docs.openclaw.ai/start/wizard) + [skills](https://docs.openclaw.ai/tools/skills)** — onboarding-driven setup with bundled/managed/workspace skills.
|
||||
|
||||
## Star History
|
||||
|
||||
@@ -143,7 +143,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
|
||||
### Core platform
|
||||
|
||||
- [Gateway WS control plane](https://docs.openclaw.ai/gateway) with sessions, presence, config, cron, webhooks, [Control UI](https://docs.openclaw.ai/web), and [Canvas host](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
|
||||
- [CLI surface](https://docs.openclaw.ai/tools/agent-send): gateway, agent, send, [wizard](https://docs.openclaw.ai/start/wizard), and [doctor](https://docs.openclaw.ai/gateway/doctor).
|
||||
- [CLI surface](https://docs.openclaw.ai/tools/agent-send): gateway, agent, send, [onboarding](https://docs.openclaw.ai/start/wizard), and [doctor](https://docs.openclaw.ai/gateway/doctor).
|
||||
- [Pi agent runtime](https://docs.openclaw.ai/concepts/agent) in RPC mode with tool streaming and block streaming.
|
||||
- [Session model](https://docs.openclaw.ai/concepts/session): `main` for direct chats, group isolation, activation modes, queue modes, reply-back. Group rules: [Groups](https://docs.openclaw.ai/channels/groups).
|
||||
- [Media pipeline](https://docs.openclaw.ai/nodes/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.openclaw.ai/nodes/audio).
|
||||
@@ -422,7 +422,7 @@ Use these when you’re past the onboarding flow and want the deeper reference.
|
||||
- [Run the Gateway by the book with the operational runbook.](https://docs.openclaw.ai/gateway)
|
||||
- [Learn how the Control UI/Web surfaces work and how to expose them safely.](https://docs.openclaw.ai/web)
|
||||
- [Understand remote access over SSH tunnels or tailnets.](https://docs.openclaw.ai/gateway/remote)
|
||||
- [Follow the onboarding wizard flow for a guided setup.](https://docs.openclaw.ai/start/wizard)
|
||||
- [Follow OpenClaw Onboard for a guided setup.](https://docs.openclaw.ai/start/wizard)
|
||||
- [Wire external triggers via the webhook surface.](https://docs.openclaw.ai/automation/webhook)
|
||||
- [Set up Gmail Pub/Sub triggers.](https://docs.openclaw.ai/automation/gmail-pubsub)
|
||||
- [Learn the macOS menu bar companion details.](https://docs.openclaw.ai/platforms/mac/menu-bar)
|
||||
|
||||
@@ -126,7 +126,7 @@ launchctl load ~/Library/LaunchAgents/com.user.poke-messages.plist
|
||||
|
||||
## Onboarding
|
||||
|
||||
BlueBubbles is available in the interactive setup wizard:
|
||||
BlueBubbles is available in interactive onboarding:
|
||||
|
||||
```
|
||||
openclaw onboard
|
||||
|
||||
@@ -30,9 +30,9 @@ openclaw plugins install @openclaw/feishu
|
||||
|
||||
There are two ways to add the Feishu channel:
|
||||
|
||||
### Method 1: setup wizard (recommended)
|
||||
### Method 1: onboarding (recommended)
|
||||
|
||||
If you just installed OpenClaw, run the setup wizard:
|
||||
If you just installed OpenClaw, run onboarding:
|
||||
|
||||
```bash
|
||||
openclaw onboard
|
||||
|
||||
@@ -16,7 +16,7 @@ Nostr is a decentralized protocol for social networking. This channel enables Op
|
||||
|
||||
### Onboarding (recommended)
|
||||
|
||||
- The setup wizard (`openclaw onboard`) and `openclaw channels add` list optional channel plugins.
|
||||
- Onboarding (`openclaw onboard`) and `openclaw channels add` list optional channel plugins.
|
||||
- Selecting Nostr prompts you to install the plugin on demand.
|
||||
|
||||
Install defaults:
|
||||
|
||||
@@ -115,7 +115,7 @@ Token resolution order is account-aware. In practice, config values win over env
|
||||
|
||||
`channels.telegram.allowFrom` accepts numeric Telegram user IDs. `telegram:` / `tg:` prefixes are accepted and normalized.
|
||||
`dmPolicy: "allowlist"` with empty `allowFrom` blocks all DMs and is rejected by config validation.
|
||||
The setup wizard accepts `@username` input and resolves it to numeric IDs.
|
||||
Onboarding accepts `@username` input and resolves it to numeric IDs.
|
||||
If you upgraded and your config contains `@username` allowlist entries, run `openclaw doctor --fix` to resolve them (best-effort; requires a Telegram bot token).
|
||||
If you previously relied on pairing-store allowlist files, `openclaw doctor --fix` can recover entries into `channels.telegram.allowFrom` in allowlist flows (for example when `dmPolicy: "allowlist"` has no explicit IDs yet).
|
||||
|
||||
|
||||
@@ -318,22 +318,22 @@ Initialize config + workspace.
|
||||
Options:
|
||||
|
||||
- `--workspace <dir>`: agent workspace path (default `~/.openclaw/workspace`).
|
||||
- `--wizard`: run the setup wizard.
|
||||
- `--non-interactive`: run wizard without prompts.
|
||||
- `--mode <local|remote>`: wizard mode.
|
||||
- `--wizard`: run onboarding.
|
||||
- `--non-interactive`: run onboarding without prompts.
|
||||
- `--mode <local|remote>`: onboard mode.
|
||||
- `--remote-url <url>`: remote Gateway URL.
|
||||
- `--remote-token <token>`: remote Gateway token.
|
||||
|
||||
Wizard auto-runs when any wizard flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`).
|
||||
Onboarding auto-runs when any onboarding flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`).
|
||||
|
||||
### `onboard`
|
||||
|
||||
Interactive wizard to set up gateway, workspace, and skills.
|
||||
Interactive onboarding for gateway, workspace, and skills.
|
||||
|
||||
Options:
|
||||
|
||||
- `--workspace <dir>`
|
||||
- `--reset` (reset config + credentials + sessions before wizard)
|
||||
- `--reset` (reset config + credentials + sessions before onboarding)
|
||||
- `--reset-scope <config|config+creds+sessions|full>` (default `config+creds+sessions`; use `full` to also remove workspace)
|
||||
- `--non-interactive`
|
||||
- `--mode <local|remote>`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw onboard` (interactive setup wizard)"
|
||||
summary: "CLI reference for `openclaw onboard` (interactive onboarding)"
|
||||
read_when:
|
||||
- You want guided setup for gateway, workspace, auth, channels, and skills
|
||||
title: "onboard"
|
||||
@@ -7,11 +7,11 @@ title: "onboard"
|
||||
|
||||
# `openclaw onboard`
|
||||
|
||||
Interactive setup wizard (local or remote Gateway setup).
|
||||
Interactive onboarding for local or remote Gateway setup.
|
||||
|
||||
## Related guides
|
||||
|
||||
- CLI onboarding hub: [Setup Wizard (CLI)](/start/wizard)
|
||||
- CLI onboarding hub: [Onboarding (CLI)](/start/wizard)
|
||||
- Onboarding overview: [Onboarding Overview](/start/onboarding-overview)
|
||||
- CLI onboarding reference: [CLI Setup Reference](/start/wizard-cli-reference)
|
||||
- CLI automation: [CLI Automation](/start/wizard-cli-automation)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw setup` (initialize config + workspace)"
|
||||
read_when:
|
||||
- You’re doing first-run setup without the full setup wizard
|
||||
- You’re doing first-run setup without full CLI onboarding
|
||||
- You want to set the default workspace path
|
||||
title: "setup"
|
||||
---
|
||||
@@ -13,7 +13,7 @@ Initialize `~/.openclaw/openclaw.json` and the agent workspace.
|
||||
Related:
|
||||
|
||||
- Getting started: [Getting started](/start/getting-started)
|
||||
- Wizard: [Onboarding](/start/onboarding)
|
||||
- CLI onboarding: [Onboarding (CLI)](/start/wizard)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -22,7 +22,7 @@ openclaw setup
|
||||
openclaw setup --workspace ~/.openclaw/workspace
|
||||
```
|
||||
|
||||
To run the wizard via setup:
|
||||
To run onboarding via setup:
|
||||
|
||||
```bash
|
||||
openclaw setup --wizard
|
||||
|
||||
@@ -34,9 +34,9 @@ Related:
|
||||
- Use fallbacks for cost/latency-sensitive tasks and lower-stakes chat.
|
||||
- For tool-enabled agents or untrusted inputs, avoid older/weaker model tiers.
|
||||
|
||||
## Setup wizard (recommended)
|
||||
## Onboarding (recommended)
|
||||
|
||||
If you don’t want to hand-edit config, run the setup wizard:
|
||||
If you don’t want to hand-edit config, run onboarding:
|
||||
|
||||
```bash
|
||||
openclaw onboard
|
||||
|
||||
@@ -49,7 +49,7 @@ openclaw models status
|
||||
openclaw doctor
|
||||
```
|
||||
|
||||
If you’d rather not manage env vars yourself, the setup wizard can store
|
||||
If you’d rather not manage env vars yourself, onboarding can store
|
||||
API keys for daemon use: `openclaw onboard`.
|
||||
|
||||
See [Help](/help) for details on env inheritance (`env.shellEnv`,
|
||||
|
||||
@@ -2950,7 +2950,7 @@ Notes:
|
||||
|
||||
## Wizard
|
||||
|
||||
Metadata written by CLI wizards (`onboard`, `configure`, `doctor`):
|
||||
Metadata written by CLI guided setup flows (`onboard`, `configure`, `doctor`):
|
||||
|
||||
```json5
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ See the [full reference](/gateway/configuration-reference) for every available f
|
||||
<Tabs>
|
||||
<Tab title="Interactive wizard">
|
||||
```bash
|
||||
openclaw onboard # full setup wizard
|
||||
openclaw onboard # full onboarding flow
|
||||
openclaw configure # config wizard
|
||||
```
|
||||
</Tab>
|
||||
|
||||
@@ -738,7 +738,7 @@ In minimal mode, the Gateway still broadcasts enough for device discovery (`role
|
||||
Gateway auth is **required by default**. If no token/password is configured,
|
||||
the Gateway refuses WebSocket connections (fail‑closed).
|
||||
|
||||
The setup wizard generates a token by default (even for loopback) so
|
||||
Onboarding generates a token by default (even for loopback) so
|
||||
local clients must authenticate.
|
||||
|
||||
Set a token so **all** WS clients must authenticate:
|
||||
|
||||
@@ -36,7 +36,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [How do I install OpenClaw on a VPS?](#how-do-i-install-openclaw-on-a-vps)
|
||||
- [Where are the cloud/VPS install guides?](#where-are-the-cloudvps-install-guides)
|
||||
- [Can I ask OpenClaw to update itself?](#can-i-ask-openclaw-to-update-itself)
|
||||
- [What does the setup wizard actually do?](#what-does-the-setup-wizard-actually-do)
|
||||
- [What does onboarding actually do?](#what-does-onboarding-actually-do)
|
||||
- [Do I need a Claude or OpenAI subscription to run this?](#do-i-need-a-claude-or-openai-subscription-to-run-this)
|
||||
- [Can I use Claude Max subscription without an API key](#can-i-use-claude-max-subscription-without-an-api-key)
|
||||
- [How does Anthropic "setup-token" auth work?](#how-does-anthropic-setuptoken-auth-work)
|
||||
@@ -317,7 +317,7 @@ Install docs: [Install](/install), [Installer flags](/install/installer), [Updat
|
||||
|
||||
### What's the recommended way to install and set up OpenClaw
|
||||
|
||||
The repo recommends running from source and using the setup wizard:
|
||||
The repo recommends running from source and using onboarding:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://openclaw.ai/install.sh | bash
|
||||
@@ -627,7 +627,7 @@ More detail: [Install](/install) and [Installer flags](/install/installer).
|
||||
|
||||
### How do I install OpenClaw on Linux
|
||||
|
||||
Short answer: follow the Linux guide, then run the setup wizard.
|
||||
Short answer: follow the Linux guide, then run onboarding.
|
||||
|
||||
- Linux quick path + service install: [Linux](/platforms/linux).
|
||||
- Full walkthrough: [Getting Started](/start/getting-started).
|
||||
@@ -685,7 +685,7 @@ openclaw gateway restart
|
||||
|
||||
Docs: [Update](/cli/update), [Updating](/install/updating).
|
||||
|
||||
### What does the setup wizard actually do
|
||||
### What does onboarding actually do
|
||||
|
||||
`openclaw onboard` is the recommended setup path. In **local mode** it walks you through:
|
||||
|
||||
@@ -723,7 +723,7 @@ If you want the clearest and safest supported path for production, use an Anthro
|
||||
|
||||
### How does Anthropic setuptoken auth work
|
||||
|
||||
`claude setup-token` generates a **token string** via the Claude Code CLI (it is not available in the web console). You can run it on **any machine**. Choose **Anthropic token (paste setup-token)** in the wizard or paste it with `openclaw models auth paste-token --provider anthropic`. The token is stored as an auth profile for the **anthropic** provider and used like an API key (no auto-refresh). More detail: [OAuth](/concepts/oauth).
|
||||
`claude setup-token` generates a **token string** via the Claude Code CLI (it is not available in the web console). You can run it on **any machine**. Choose **Anthropic token (paste setup-token)** in onboarding or paste it with `openclaw models auth paste-token --provider anthropic`. The token is stored as an auth profile for the **anthropic** provider and used like an API key (no auto-refresh). More detail: [OAuth](/concepts/oauth).
|
||||
|
||||
### Where do I find an Anthropic setuptoken
|
||||
|
||||
@@ -733,7 +733,7 @@ It is **not** in the Anthropic Console. The setup-token is generated by the **Cl
|
||||
claude setup-token
|
||||
```
|
||||
|
||||
Copy the token it prints, then choose **Anthropic token (paste setup-token)** in the wizard. If you want to run it on the gateway host, use `openclaw models auth setup-token --provider anthropic`. If you ran `claude setup-token` elsewhere, paste it on the gateway host with `openclaw models auth paste-token --provider anthropic`. See [Anthropic](/providers/anthropic).
|
||||
Copy the token it prints, then choose **Anthropic token (paste setup-token)** in onboarding. If you want to run it on the gateway host, use `openclaw models auth setup-token --provider anthropic`. If you ran `claude setup-token` elsewhere, paste it on the gateway host with `openclaw models auth paste-token --provider anthropic`. See [Anthropic](/providers/anthropic).
|
||||
|
||||
### Do you support Claude subscription auth (Claude Pro or Max)
|
||||
|
||||
@@ -767,15 +767,15 @@ Yes - via pi-ai's **Amazon Bedrock (Converse)** provider with **manual config**.
|
||||
|
||||
### How does Codex auth work
|
||||
|
||||
OpenClaw supports **OpenAI Code (Codex)** via OAuth (ChatGPT sign-in). The wizard can run the OAuth flow and will set the default model to `openai-codex/gpt-5.4` when appropriate. See [Model providers](/concepts/model-providers) and [Wizard](/start/wizard).
|
||||
OpenClaw supports **OpenAI Code (Codex)** via OAuth (ChatGPT sign-in). Onboarding can run the OAuth flow and will set the default model to `openai-codex/gpt-5.4` when appropriate. See [Model providers](/concepts/model-providers) and [Onboarding (CLI)](/start/wizard).
|
||||
|
||||
### Do you support OpenAI subscription auth Codex OAuth
|
||||
|
||||
Yes. OpenClaw fully supports **OpenAI Code (Codex) subscription OAuth**.
|
||||
OpenAI explicitly allows subscription OAuth usage in external tools/workflows
|
||||
like OpenClaw. The setup wizard can run the OAuth flow for you.
|
||||
like OpenClaw. Onboarding can run the OAuth flow for you.
|
||||
|
||||
See [OAuth](/concepts/oauth), [Model providers](/concepts/model-providers), and [Wizard](/start/wizard).
|
||||
See [OAuth](/concepts/oauth), [Model providers](/concepts/model-providers), and [Onboarding (CLI)](/start/wizard).
|
||||
|
||||
### How do I set up Gemini CLI OAuth
|
||||
|
||||
@@ -844,7 +844,7 @@ without WhatsApp/Telegram.
|
||||
|
||||
`channels.telegram.allowFrom` is **the human sender's Telegram user ID** (numeric). It is not the bot username.
|
||||
|
||||
The setup wizard accepts `@username` input and resolves it to a numeric ID, but OpenClaw authorization uses numeric IDs only.
|
||||
Onboarding accepts `@username` input and resolves it to a numeric ID, but OpenClaw authorization uses numeric IDs only.
|
||||
|
||||
Safer (no third-party bot):
|
||||
|
||||
@@ -1909,7 +1909,7 @@ openclaw onboard --install-daemon
|
||||
|
||||
Notes:
|
||||
|
||||
- The setup wizard also offers **Reset** if it sees an existing config. See [Wizard](/start/wizard).
|
||||
- Onboarding also offers **Reset** if it sees an existing config. See [Onboarding (CLI)](/start/wizard).
|
||||
- If you used profiles (`--profile` / `OPENCLAW_PROFILE`), reset each state dir (defaults are `~/.openclaw-<profile>`).
|
||||
- Dev reset: `openclaw gateway --dev --reset` (dev-only; wipes dev config + credentials + sessions + workspace).
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ title: "OpenClaw"
|
||||
<Card title="Get Started" href="/start/getting-started" icon="rocket">
|
||||
Install OpenClaw and bring up the Gateway in minutes.
|
||||
</Card>
|
||||
<Card title="Run the Wizard" href="/start/wizard" icon="sparkles">
|
||||
<Card title="Run Onboarding" href="/start/wizard" icon="sparkles">
|
||||
Guided setup with `openclaw onboard` and pairing flows.
|
||||
</Card>
|
||||
<Card title="Open the Control UI" href="/web/control-ui" icon="layout-dashboard">
|
||||
|
||||
@@ -51,7 +51,7 @@ From repo root:
|
||||
This script:
|
||||
|
||||
- builds the gateway image locally (or pulls a remote image if `OPENCLAW_IMAGE` is set)
|
||||
- runs the setup wizard
|
||||
- runs onboarding
|
||||
- prints optional provider setup hints
|
||||
- starts the gateway via Docker Compose
|
||||
- generates a gateway token and writes it to `.env`
|
||||
|
||||
@@ -33,7 +33,7 @@ For VPS/cloud hosts, avoid third-party "1-click" marketplace images when possibl
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Installer script" icon="rocket" defaultOpen>
|
||||
Downloads the CLI, installs it globally via npm, and launches the setup wizard.
|
||||
Downloads the CLI, installs it globally via npm, and launches onboarding.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="macOS / Linux / WSL2">
|
||||
|
||||
@@ -21,7 +21,7 @@ and you configure everything via the `/setup` web wizard.
|
||||
## What you get
|
||||
|
||||
- Hosted OpenClaw Gateway + Control UI
|
||||
- Web setup wizard at `/setup` (no terminal commands)
|
||||
- Web setup at `/setup` (no terminal commands)
|
||||
- Persistent storage via Northflank Volume (`/data`) so config/credentials/workspace survive redeploys
|
||||
|
||||
## Setup flow
|
||||
@@ -32,7 +32,7 @@ and you configure everything via the `/setup` web wizard.
|
||||
4. Click **Run setup**.
|
||||
5. Open the Control UI at `https://<your-northflank-domain>/openclaw`
|
||||
|
||||
If Telegram DMs are set to pairing, the setup wizard can approve the pairing code.
|
||||
If Telegram DMs are set to pairing, web setup can approve the pairing code.
|
||||
|
||||
## Getting chat tokens
|
||||
|
||||
|
||||
@@ -29,13 +29,13 @@ Railway will either:
|
||||
|
||||
Then open:
|
||||
|
||||
- `https://<your-railway-domain>/setup` — setup wizard (password protected)
|
||||
- `https://<your-railway-domain>/setup` — web setup (password protected)
|
||||
- `https://<your-railway-domain>/openclaw` — Control UI
|
||||
|
||||
## What you get
|
||||
|
||||
- Hosted OpenClaw Gateway + Control UI
|
||||
- Web setup wizard at `/setup` (no terminal commands)
|
||||
- Web setup at `/setup` (no terminal commands)
|
||||
- Persistent storage via Railway Volume (`/data`) so config/credentials/workspace survive redeploys
|
||||
- Backup export at `/setup/export` to migrate off Railway later
|
||||
|
||||
@@ -70,7 +70,7 @@ Set these variables on the service:
|
||||
3. (Optional) Add Telegram/Discord/Slack tokens.
|
||||
4. Click **Run setup**.
|
||||
|
||||
If Telegram DMs are set to pairing, the setup wizard can approve the pairing code.
|
||||
If Telegram DMs are set to pairing, web setup can approve the pairing code.
|
||||
|
||||
## Getting chat tokens
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ The Blueprint defaults to `starter`. To use free tier, change `plan: free` in yo
|
||||
|
||||
## After deployment
|
||||
|
||||
### Complete the setup wizard
|
||||
### Complete web setup
|
||||
|
||||
1. Navigate to `https://<your-service>.onrender.com/setup`
|
||||
2. Enter your `SETUP_PASSWORD`
|
||||
|
||||
@@ -22,7 +22,7 @@ curl -fsSL https://openclaw.ai/install.sh | bash
|
||||
|
||||
Notes:
|
||||
|
||||
- Add `--no-onboard` if you don’t want the setup wizard to run again.
|
||||
- Add `--no-onboard` if you don’t want onboarding to run again.
|
||||
- For **source installs**, use:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -321,7 +321,7 @@ Since the Pi is just the Gateway (models run in the cloud), use API-based models
|
||||
|
||||
## Auto-Start on Boot
|
||||
|
||||
The setup wizard sets this up, but to verify:
|
||||
Onboarding sets this up, but to verify:
|
||||
|
||||
```bash
|
||||
# Check service is enabled
|
||||
|
||||
@@ -16,15 +16,15 @@ Ollama is a local LLM runtime that makes it easy to run open-source models on yo
|
||||
|
||||
## Quick start
|
||||
|
||||
### Onboarding wizard (recommended)
|
||||
### Onboarding (recommended)
|
||||
|
||||
The fastest way to set up Ollama is through the setup wizard:
|
||||
The fastest way to set up Ollama is through onboarding:
|
||||
|
||||
```bash
|
||||
openclaw onboard
|
||||
```
|
||||
|
||||
Select **Ollama** from the provider list. The wizard will:
|
||||
Select **Ollama** from the provider list. Onboarding will:
|
||||
|
||||
1. Ask for the Ollama base URL where your instance can be reached (default `http://127.0.0.1:11434`).
|
||||
2. Let you choose **Cloud + Local** (cloud models and local models) or **Local** (local models only).
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
---
|
||||
summary: "Full reference for the CLI setup wizard: every step, flag, and config field"
|
||||
summary: "Full reference for CLI onboarding: every step, flag, and config field"
|
||||
read_when:
|
||||
- Looking up a specific wizard step or flag
|
||||
- Looking up a specific onboarding step or flag
|
||||
- Automating onboarding with non-interactive mode
|
||||
- Debugging wizard behavior
|
||||
title: "Setup Wizard Reference"
|
||||
sidebarTitle: "Wizard Reference"
|
||||
- Debugging onboarding behavior
|
||||
title: "Onboarding Reference"
|
||||
sidebarTitle: "Onboarding Reference"
|
||||
---
|
||||
|
||||
# Setup Wizard Reference
|
||||
# Onboarding Reference
|
||||
|
||||
This is the full reference for the `openclaw onboard` CLI wizard.
|
||||
For a high-level overview, see [Setup Wizard](/start/wizard).
|
||||
This is the full reference for `openclaw onboard`.
|
||||
For a high-level overview, see [Onboarding (CLI)](/start/wizard).
|
||||
|
||||
## Flow details (local mode)
|
||||
|
||||
<Steps>
|
||||
<Step title="Existing config detection">
|
||||
- If `~/.openclaw/openclaw.json` exists, choose **Keep / Modify / Reset**.
|
||||
- Re-running the wizard does **not** wipe anything unless you explicitly choose **Reset**
|
||||
- Re-running onboarding does **not** wipe anything unless you explicitly choose **Reset**
|
||||
(or pass `--reset`).
|
||||
- CLI `--reset` defaults to `config+creds+sessions`; use `--reset-scope full`
|
||||
to also remove workspace.
|
||||
@@ -31,9 +31,9 @@ For a high-level overview, see [Setup Wizard](/start/wizard).
|
||||
</Step>
|
||||
<Step title="Model/Auth">
|
||||
- **Anthropic API key**: uses `ANTHROPIC_API_KEY` if present or prompts for a key, then saves it for daemon use.
|
||||
- **Anthropic OAuth (Claude Code CLI)**: on macOS the wizard checks Keychain item "Claude Code-credentials" (choose "Always Allow" so launchd starts don't block); on Linux/Windows it reuses `~/.claude/.credentials.json` if present.
|
||||
- **Anthropic OAuth (Claude Code CLI)**: on macOS onboarding checks Keychain item "Claude Code-credentials" (choose "Always Allow" so launchd starts don't block); on Linux/Windows it reuses `~/.claude/.credentials.json` if present.
|
||||
- **Anthropic token (paste setup-token)**: run `claude setup-token` on any machine, then paste the token (you can name it; blank = default).
|
||||
- **OpenAI Code (Codex) subscription (Codex CLI)**: if `~/.codex/auth.json` exists, the wizard can reuse it.
|
||||
- **OpenAI Code (Codex) subscription (Codex CLI)**: if `~/.codex/auth.json` exists, onboarding can reuse it.
|
||||
- **OpenAI Code (Codex) subscription (OAuth)**: browser flow; paste the `code#state`.
|
||||
- Sets `agents.defaults.model` to `openai-codex/gpt-5.2` when model is unset or `openai/*`.
|
||||
- **OpenAI API key**: uses `OPENAI_API_KEY` if present or prompts for a key, then stores it in auth profiles.
|
||||
@@ -55,7 +55,7 @@ For a high-level overview, see [Setup Wizard](/start/wizard).
|
||||
- More detail: [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot)
|
||||
- **Skip**: no auth configured yet.
|
||||
- Pick a default model from detected options (or enter provider/model manually). For best quality and lower prompt-injection risk, choose the strongest latest-generation model available in your provider stack.
|
||||
- Wizard runs a model check and warns if the configured model is unknown or missing auth.
|
||||
- Onboarding runs a model check and warns if the configured model is unknown or missing auth.
|
||||
- API key storage mode defaults to plaintext auth-profile values. Use `--secret-input-mode ref` to store env-backed refs instead (for example `keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" }`).
|
||||
- OAuth credentials live in `~/.openclaw/credentials/oauth.json`; auth profiles live in `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` (API keys + OAuth).
|
||||
- More detail: [/concepts/oauth](/concepts/oauth)
|
||||
@@ -106,7 +106,7 @@ For a high-level overview, see [Setup Wizard](/start/wizard).
|
||||
- macOS: LaunchAgent
|
||||
- Requires a logged-in user session; for headless, use a custom LaunchDaemon (not shipped).
|
||||
- Linux (and Windows via WSL2): systemd user unit
|
||||
- Wizard attempts to enable lingering via `loginctl enable-linger <user>` so the Gateway stays up after logout.
|
||||
- Onboarding attempts to enable lingering via `loginctl enable-linger <user>` so the Gateway stays up after logout.
|
||||
- May prompt for sudo (writes `/var/lib/systemd/linger`); it tries without sudo first.
|
||||
- **Runtime selection:** Node (recommended; required for WhatsApp/Telegram). Bun is **not recommended**.
|
||||
- If token auth requires a token and `gateway.auth.token` is SecretRef-managed, daemon install validates it but does not persist resolved plaintext token values into supervisor service environment metadata.
|
||||
@@ -128,8 +128,8 @@ For a high-level overview, see [Setup Wizard](/start/wizard).
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
If no GUI is detected, the wizard prints SSH port-forward instructions for the Control UI instead of opening a browser.
|
||||
If the Control UI assets are missing, the wizard attempts to build them; fallback is `pnpm ui:build` (auto-installs UI deps).
|
||||
If no GUI is detected, onboarding prints SSH port-forward instructions for the Control UI instead of opening a browser.
|
||||
If the Control UI assets are missing, onboarding attempts to build them; fallback is `pnpm ui:build` (auto-installs UI deps).
|
||||
</Note>
|
||||
|
||||
## Non-interactive mode
|
||||
@@ -183,12 +183,12 @@ openclaw agents add work \
|
||||
|
||||
## Gateway wizard RPC
|
||||
|
||||
The Gateway exposes the wizard flow over RPC (`wizard.start`, `wizard.next`, `wizard.cancel`, `wizard.status`).
|
||||
The Gateway exposes the onboarding flow over RPC (`wizard.start`, `wizard.next`, `wizard.cancel`, `wizard.status`).
|
||||
Clients (macOS app, Control UI) can render steps without re‑implementing onboarding logic.
|
||||
|
||||
## Signal setup (signal-cli)
|
||||
|
||||
The wizard can install `signal-cli` from GitHub releases:
|
||||
Onboarding can install `signal-cli` from GitHub releases:
|
||||
|
||||
- Downloads the appropriate release asset.
|
||||
- Stores it under `~/.openclaw/tools/signal-cli/<version>/`.
|
||||
@@ -223,12 +223,12 @@ Typical fields in `~/.openclaw/openclaw.json`:
|
||||
WhatsApp credentials go under `~/.openclaw/credentials/whatsapp/<accountId>/`.
|
||||
Sessions are stored under `~/.openclaw/agents/<agentId>/sessions/`.
|
||||
|
||||
Some channels are delivered as plugins. When you pick one during setup, the wizard
|
||||
Some channels are delivered as plugins. When you pick one during setup, onboarding
|
||||
will prompt to install it (npm or a local path) before it can be configured.
|
||||
|
||||
## Related docs
|
||||
|
||||
- Wizard overview: [Setup Wizard](/start/wizard)
|
||||
- Onboarding overview: [Onboarding (CLI)](/start/wizard)
|
||||
- macOS app onboarding: [Onboarding](/start/onboarding)
|
||||
- Config reference: [Gateway configuration](/gateway/configuration)
|
||||
- Providers: [WhatsApp](/channels/whatsapp), [Telegram](/channels/telegram), [Discord](/channels/discord), [Google Chat](/channels/googlechat), [Signal](/channels/signal), [BlueBubbles](/channels/bluebubbles) (iMessage), [iMessage](/channels/imessage) (legacy)
|
||||
|
||||
@@ -52,13 +52,13 @@ Check your Node version with `node --version` if you are unsure.
|
||||
</Note>
|
||||
|
||||
</Step>
|
||||
<Step title="Run the setup wizard">
|
||||
<Step title="Run onboarding">
|
||||
```bash
|
||||
openclaw onboard --install-daemon
|
||||
```
|
||||
|
||||
The wizard configures auth, gateway settings, and optional channels.
|
||||
See [Setup Wizard](/start/wizard) for details.
|
||||
Onboarding configures auth, gateway settings, and optional channels.
|
||||
See [Onboarding (CLI)](/start/wizard) for details.
|
||||
|
||||
</Step>
|
||||
<Step title="Check the Gateway">
|
||||
@@ -114,8 +114,8 @@ Full environment variable reference: [Environment vars](/help/environment).
|
||||
## Go deeper
|
||||
|
||||
<Columns>
|
||||
<Card title="Setup Wizard (details)" href="/start/wizard">
|
||||
Full CLI wizard reference and advanced options.
|
||||
<Card title="Onboarding (CLI)" href="/start/wizard">
|
||||
Full CLI onboarding reference and advanced options.
|
||||
</Card>
|
||||
<Card title="macOS app onboarding" href="/start/onboarding">
|
||||
First run flow for the macOS app.
|
||||
|
||||
@@ -19,7 +19,7 @@ Use these hubs to discover every page, including deep dives and reference docs t
|
||||
- [Getting Started](/start/getting-started)
|
||||
- [Quick start](/start/quickstart)
|
||||
- [Onboarding](/start/onboarding)
|
||||
- [Wizard](/start/wizard)
|
||||
- [Onboarding (CLI)](/start/wizard)
|
||||
- [Setup](/start/setup)
|
||||
- [Dashboard (local Gateway)](http://127.0.0.1:18789/)
|
||||
- [Help](/help)
|
||||
|
||||
@@ -14,21 +14,21 @@ and how you prefer to configure providers.
|
||||
|
||||
## Choose your onboarding path
|
||||
|
||||
- **CLI wizard** for macOS, Linux, and Windows (via WSL2).
|
||||
- **CLI onboarding** for macOS, Linux, and Windows (via WSL2).
|
||||
- **macOS app** for a guided first run on Apple silicon or Intel Macs.
|
||||
|
||||
## CLI setup wizard
|
||||
## CLI onboarding
|
||||
|
||||
Run the wizard in a terminal:
|
||||
Run onboarding in a terminal:
|
||||
|
||||
```bash
|
||||
openclaw onboard
|
||||
```
|
||||
|
||||
Use the CLI wizard when you want full control of the Gateway, workspace,
|
||||
Use CLI onboarding when you want full control of the Gateway, workspace,
|
||||
channels, and skills. Docs:
|
||||
|
||||
- [Setup Wizard (CLI)](/start/wizard)
|
||||
- [Onboarding (CLI)](/start/wizard)
|
||||
- [`openclaw onboard` command](/cli/onboard)
|
||||
|
||||
## macOS app onboarding
|
||||
@@ -41,7 +41,7 @@ Use the OpenClaw app when you want a fully guided setup on macOS. Docs:
|
||||
|
||||
If you need an endpoint that is not listed, including hosted providers that
|
||||
expose standard OpenAI or Anthropic APIs, choose **Custom Provider** in the
|
||||
CLI wizard. You will be asked to:
|
||||
CLI onboarding. You will be asked to:
|
||||
|
||||
- Pick OpenAI-compatible, Anthropic-compatible, or **Unknown** (auto-detect).
|
||||
- Enter a base URL and API key (if required by the provider).
|
||||
|
||||
@@ -16,7 +16,7 @@ Quick start is now part of [Getting Started](/start/getting-started).
|
||||
<Card title="Getting Started" href="/start/getting-started">
|
||||
Install OpenClaw and run your first chat in minutes.
|
||||
</Card>
|
||||
<Card title="Onboarding Wizard" href="/start/wizard">
|
||||
Full CLI wizard reference and advanced options.
|
||||
<Card title="Onboarding (CLI)" href="/start/wizard">
|
||||
Full CLI onboarding reference and advanced options.
|
||||
</Card>
|
||||
</Columns>
|
||||
|
||||
@@ -10,7 +10,7 @@ title: "Setup"
|
||||
|
||||
<Note>
|
||||
If you are setting up for the first time, start with [Getting Started](/start/getting-started).
|
||||
For wizard details, see [Onboarding Wizard](/start/wizard).
|
||||
For onboarding details, see [Onboarding (CLI)](/start/wizard).
|
||||
</Note>
|
||||
|
||||
Last updated: 2026-01-01
|
||||
|
||||
@@ -33,7 +33,7 @@ openclaw onboard --non-interactive \
|
||||
Add `--json` for a machine-readable summary.
|
||||
|
||||
Use `--secret-input-mode ref` to store env-backed refs in auth profiles instead of plaintext values.
|
||||
Interactive selection between env refs and configured provider refs (`file` or `exec`) is available in the setup wizard flow.
|
||||
Interactive selection between env refs and configured provider refs (`file` or `exec`) is available in the onboarding flow.
|
||||
|
||||
In non-interactive `ref` mode, provider env vars must be set in the process environment.
|
||||
Passing inline key flags without the matching env var now fails fast.
|
||||
@@ -210,6 +210,6 @@ Notes:
|
||||
|
||||
## Related docs
|
||||
|
||||
- Onboarding hub: [Setup Wizard (CLI)](/start/wizard)
|
||||
- Onboarding hub: [Onboarding (CLI)](/start/wizard)
|
||||
- Full reference: [CLI Setup Reference](/start/wizard-cli-reference)
|
||||
- Command reference: [`openclaw onboard`](/cli/onboard)
|
||||
|
||||
@@ -10,7 +10,7 @@ sidebarTitle: "CLI reference"
|
||||
# CLI Setup Reference
|
||||
|
||||
This page is the full reference for `openclaw onboard`.
|
||||
For the short guide, see [Setup Wizard (CLI)](/start/wizard).
|
||||
For the short guide, see [Onboarding (CLI)](/start/wizard).
|
||||
|
||||
## What the wizard does
|
||||
|
||||
@@ -294,6 +294,6 @@ Signal setup behavior:
|
||||
|
||||
## Related docs
|
||||
|
||||
- Onboarding hub: [Setup Wizard (CLI)](/start/wizard)
|
||||
- Onboarding hub: [Onboarding (CLI)](/start/wizard)
|
||||
- Automation and scripts: [CLI Automation](/start/wizard-cli-automation)
|
||||
- Command reference: [`openclaw onboard`](/cli/onboard)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
summary: "CLI setup wizard: guided setup for gateway, workspace, channels, and skills"
|
||||
summary: "CLI onboarding: guided setup for gateway, workspace, channels, and skills"
|
||||
read_when:
|
||||
- Running or configuring the setup wizard
|
||||
- Running or configuring CLI onboarding
|
||||
- Setting up a new machine
|
||||
title: "Setup Wizard (CLI)"
|
||||
title: "Onboarding (CLI)"
|
||||
sidebarTitle: "Onboarding: CLI"
|
||||
---
|
||||
|
||||
# Setup Wizard (CLI)
|
||||
# Onboarding (CLI)
|
||||
|
||||
The setup wizard is the **recommended** way to set up OpenClaw on macOS,
|
||||
CLI onboarding is the **recommended** way to set up OpenClaw on macOS,
|
||||
Linux, or Windows (via WSL2; strongly recommended).
|
||||
It configures a local Gateway or a remote Gateway connection, plus channels, skills,
|
||||
and workspace defaults in one guided flow.
|
||||
@@ -35,7 +35,7 @@ openclaw agents add <name>
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
The setup wizard includes a web search step where you can pick a provider
|
||||
CLI onboarding includes a web search step where you can pick a provider
|
||||
(Perplexity, Brave, Gemini, Grok, or Kimi) and paste your API key so the agent
|
||||
can use `web_search`. You can also configure this later with
|
||||
`openclaw configure --section web`. Docs: [Web tools](/tools/web).
|
||||
@@ -43,7 +43,7 @@ can use `web_search`. You can also configure this later with
|
||||
|
||||
## QuickStart vs Advanced
|
||||
|
||||
The wizard starts with **QuickStart** (defaults) vs **Advanced** (full control).
|
||||
Onboarding starts with **QuickStart** (defaults) vs **Advanced** (full control).
|
||||
|
||||
<Tabs>
|
||||
<Tab title="QuickStart (defaults)">
|
||||
@@ -61,7 +61,7 @@ The wizard starts with **QuickStart** (defaults) vs **Advanced** (full control).
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## What the wizard configures
|
||||
## What onboarding configures
|
||||
|
||||
**Local mode (default)** walks you through these steps:
|
||||
|
||||
@@ -84,9 +84,9 @@ The wizard starts with **QuickStart** (defaults) vs **Advanced** (full control).
|
||||
7. **Skills** — Installs recommended skills and optional dependencies.
|
||||
|
||||
<Note>
|
||||
Re-running the wizard does **not** wipe anything unless you explicitly choose **Reset** (or pass `--reset`).
|
||||
Re-running onboarding does **not** wipe anything unless you explicitly choose **Reset** (or pass `--reset`).
|
||||
CLI `--reset` defaults to config, credentials, and sessions; use `--reset-scope full` to include workspace.
|
||||
If the config is invalid or contains legacy keys, the wizard asks you to run `openclaw doctor` first.
|
||||
If the config is invalid or contains legacy keys, onboarding asks you to run `openclaw doctor` first.
|
||||
</Note>
|
||||
|
||||
**Remote mode** only configures the local client to connect to a Gateway elsewhere.
|
||||
@@ -95,7 +95,7 @@ It does **not** install or change anything on the remote host.
|
||||
## Add another agent
|
||||
|
||||
Use `openclaw agents add <name>` to create a separate agent with its own workspace,
|
||||
sessions, and auth profiles. Running without `--workspace` launches the wizard.
|
||||
sessions, and auth profiles. Running without `--workspace` launches onboarding.
|
||||
|
||||
What it sets:
|
||||
|
||||
@@ -106,7 +106,7 @@ What it sets:
|
||||
Notes:
|
||||
|
||||
- Default workspaces follow `~/.openclaw/workspace-<agentId>`.
|
||||
- Add `bindings` to route inbound messages (the wizard can do this).
|
||||
- Add `bindings` to route inbound messages (onboarding can do this).
|
||||
- Non-interactive flags: `--model`, `--agent-dir`, `--bind`, `--non-interactive`.
|
||||
|
||||
## Full reference
|
||||
@@ -115,7 +115,7 @@ For detailed step-by-step breakdowns and config outputs, see
|
||||
[CLI Setup Reference](/start/wizard-cli-reference).
|
||||
For non-interactive examples, see [CLI Automation](/start/wizard-cli-automation).
|
||||
For the deeper technical reference, including RPC details, see
|
||||
[Wizard Reference](/reference/wizard).
|
||||
[Onboarding Reference](/reference/wizard).
|
||||
|
||||
## Related docs
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Auth is supplied during the WebSocket handshake via:
|
||||
- `connect.params.auth.token`
|
||||
- `connect.params.auth.password`
|
||||
The dashboard settings panel keeps a token for the current browser tab session and selected gateway URL; passwords are not persisted.
|
||||
The setup wizard generates a gateway token by default, so paste it here on first connect.
|
||||
Onboarding generates a gateway token by default, so paste it here on first connect.
|
||||
|
||||
## Device pairing (first connection)
|
||||
|
||||
|
||||
@@ -324,22 +324,22 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
选项:
|
||||
|
||||
- `--workspace <dir>`:智能体工作区路径(默认 `~/.openclaw/workspace`)。
|
||||
- `--wizard`:运行设置向导。
|
||||
- `--non-interactive`:无提示运行向导。
|
||||
- `--mode <local|remote>`:向导模式。
|
||||
- `--wizard`:运行新手引导。
|
||||
- `--non-interactive`:无提示运行新手引导。
|
||||
- `--mode <local|remote>`:新手引导模式。
|
||||
- `--remote-url <url>`:远程 Gateway 网关 URL。
|
||||
- `--remote-token <token>`:远程 Gateway 网关 token。
|
||||
|
||||
只要存在任意向导标志(`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`),就会自动运行向导。
|
||||
只要存在任意新手引导标志(`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`),就会自动运行新手引导。
|
||||
|
||||
### `onboard`
|
||||
|
||||
用于设置 gateway、工作区和 Skills 的交互式向导。
|
||||
用于设置 gateway、工作区和 Skills 的交互式新手引导。
|
||||
|
||||
选项:
|
||||
|
||||
- `--workspace <dir>`
|
||||
- `--reset`(在运行向导前重置配置 + 凭据 + 会话)
|
||||
- `--reset`(在运行新手引导前重置配置 + 凭据 + 会话)
|
||||
- `--reset-scope <config|config+creds+sessions|full>`(默认 `config+creds+sessions`;使用 `full` 还会删除工作区)
|
||||
- `--non-interactive`
|
||||
- `--mode <local|remote>`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
read_when:
|
||||
- 你想通过引导式设置来配置 Gateway 网关、工作区、身份验证、渠道和 Skills
|
||||
summary: "`openclaw onboard` 的 CLI 参考(交互式设置向导)"
|
||||
summary: "`openclaw onboard` 的 CLI 参考(交互式新手引导)"
|
||||
title: onboard
|
||||
x-i18n:
|
||||
generated_at: "2026-03-16T06:21:32Z"
|
||||
@@ -14,11 +14,11 @@ x-i18n:
|
||||
|
||||
# `openclaw onboard`
|
||||
|
||||
交互式设置向导(本地或远程 Gateway 网关设置)。
|
||||
交互式新手引导(本地或远程 Gateway 网关设置)。
|
||||
|
||||
## 相关指南
|
||||
|
||||
- CLI 新手引导中心:[设置向导(CLI)](/start/wizard)
|
||||
- CLI 新手引导中心:[CLI 新手引导](/start/wizard)
|
||||
- 新手引导概览:[新手引导概览](/start/onboarding-overview)
|
||||
- CLI 新手引导参考:[CLI 设置参考](/start/wizard-cli-reference)
|
||||
- CLI 自动化:[CLI 自动化](/start/wizard-cli-automation)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
read_when:
|
||||
- 你正在进行首次运行设置,但不使用完整的设置向导
|
||||
- 你正在进行首次运行设置,但不使用完整的 CLI 新手引导
|
||||
- 你想设置默认工作区路径
|
||||
summary: "`openclaw setup` 的 CLI 参考(初始化配置 + 工作区)"
|
||||
title: setup
|
||||
@@ -20,7 +20,7 @@ x-i18n:
|
||||
相关内容:
|
||||
|
||||
- 入门指南:[入门指南](/start/getting-started)
|
||||
- 向导:[新手引导](/start/onboarding)
|
||||
- CLI 新手引导:[CLI 新手引导](/start/wizard)
|
||||
|
||||
## 示例
|
||||
|
||||
@@ -29,7 +29,7 @@ openclaw setup
|
||||
openclaw setup --workspace ~/.openclaw/workspace
|
||||
```
|
||||
|
||||
通过 setup 运行向导:
|
||||
通过 setup 运行新手引导:
|
||||
|
||||
```bash
|
||||
openclaw setup --wizard
|
||||
|
||||
@@ -39,7 +39,7 @@ x-i18n:
|
||||
- [如何在 VPS 上安装 OpenClaw?](#how-do-i-install-openclaw-on-a-vps)
|
||||
- [云/VPS 安装指南在哪里?](#where-are-the-cloudvps-install-guides)
|
||||
- [可以让 OpenClaw 自行更新吗?](#can-i-ask-openclaw-to-update-itself)
|
||||
- [新手引导向导具体做了什么?](#what-does-the-onboarding-wizard-actually-do)
|
||||
- [新手引导具体做了什么?](#新手引导具体做了什么)
|
||||
- [运行 OpenClaw 需要 Claude 或 OpenAI 订阅吗?](#do-i-need-a-claude-or-openai-subscription-to-run-this)
|
||||
- [能否使用 Claude Max 订阅而不需要 API 密钥?](#can-i-use-claude-max-subscription-without-an-api-key)
|
||||
- [Anthropic "setup-token" 认证如何工作?](#how-does-anthropic-setuptoken-auth-work)
|
||||
@@ -310,14 +310,14 @@ openclaw doctor
|
||||
|
||||
### 安装和设置 OpenClaw 的推荐方式是什么
|
||||
|
||||
仓库推荐从源码运行并使用新手引导向导:
|
||||
仓库推荐从源码运行并使用新手引导:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://openclaw.ai/install.sh | bash
|
||||
openclaw onboard --install-daemon
|
||||
```
|
||||
|
||||
向导还可以自动构建 UI 资源。新手引导后,通常在端口 **18789** 上运行 Gateway 网关。
|
||||
新手引导还可以自动构建 UI 资源。新手引导后,通常在端口 **18789** 上运行 Gateway 网关。
|
||||
|
||||
从源码安装(贡献者/开发者):
|
||||
|
||||
@@ -334,7 +334,7 @@ openclaw onboard
|
||||
|
||||
### 新手引导后如何打开仪表板
|
||||
|
||||
向导现在会在新手引导完成后立即使用带令牌的仪表板 URL 打开浏览器,并在摘要中打印完整链接(带令牌)。保持该标签页打开;如果没有自动启动,请在同一台机器上复制/粘贴打印的 URL。令牌保持在本地主机上——不会从浏览器获取任何内容。
|
||||
新手引导现在会在完成后立即使用带令牌的仪表板 URL 打开浏览器,并在摘要中打印完整链接(带令牌)。保持该标签页打开;如果没有自动启动,请在同一台机器上复制/粘贴打印的 URL。令牌保持在本地主机上,不会从浏览器获取任何内容。
|
||||
|
||||
### 如何在本地和远程环境中验证仪表板令牌
|
||||
|
||||
@@ -562,7 +562,7 @@ curl -fsSL https://openclaw.ai/install.sh | bash -s -- --install-method git
|
||||
|
||||
### 如何在 Linux 上安装 OpenClaw
|
||||
|
||||
简短回答:按照 Linux 指南操作,然后运行新手引导向导。
|
||||
简短回答:按照 Linux 指南操作,然后运行新手引导。
|
||||
|
||||
- Linux 快速路径 + 服务安装:[Linux](/platforms/linux)。
|
||||
- 完整指南:[入门](/start/getting-started)。
|
||||
@@ -614,7 +614,7 @@ openclaw gateway restart
|
||||
|
||||
文档:[更新](/cli/update)、[更新指南](/install/updating)。
|
||||
|
||||
### 新手引导向导具体做了什么
|
||||
### 新手引导具体做了什么
|
||||
|
||||
`openclaw onboard` 是推荐的设置路径。在**本地模式**下,它引导你完成:
|
||||
|
||||
@@ -642,7 +642,7 @@ Claude Pro/Max 订阅**不包含 API 密钥**,因此这是订阅账户的正
|
||||
|
||||
### Anthropic setup-token 认证如何工作
|
||||
|
||||
`claude setup-token` 通过 Claude Code CLI 生成一个**令牌字符串**(在 Web 控制台中不可用)。你可以在**任何机器**上运行它。在向导中选择 **Anthropic token (paste setup-token)** 或使用 `openclaw models auth paste-token --provider anthropic` 粘贴。令牌作为 **anthropic** 提供商的认证配置文件存储,像 API 密钥一样使用(无自动刷新)。更多详情:[OAuth](/concepts/oauth)。
|
||||
`claude setup-token` 通过 Claude Code CLI 生成一个**令牌字符串**(在 Web 控制台中不可用)。你可以在**任何机器**上运行它。在新手引导中选择 **Anthropic token (paste setup-token)** 或使用 `openclaw models auth paste-token --provider anthropic` 粘贴。令牌作为 **anthropic** 提供商的认证配置文件存储,像 API 密钥一样使用(无自动刷新)。更多详情:[OAuth](/concepts/oauth)。
|
||||
|
||||
### 在哪里获取 Anthropic setup-token
|
||||
|
||||
@@ -652,7 +652,7 @@ Claude Pro/Max 订阅**不包含 API 密钥**,因此这是订阅账户的正
|
||||
claude setup-token
|
||||
```
|
||||
|
||||
复制它打印的令牌,然后在向导中选择 **Anthropic token (paste setup-token)**。如果你想在 Gateway 网关主机上运行,使用 `openclaw models auth setup-token --provider anthropic`。如果你在其他地方运行了 `claude setup-token`,在 Gateway 网关主机上使用 `openclaw models auth paste-token --provider anthropic` 粘贴。参阅 [Anthropic](/providers/anthropic)。
|
||||
复制它打印的令牌,然后在新手引导中选择 **Anthropic token (paste setup-token)**。如果你想在 Gateway 网关主机上运行,使用 `openclaw models auth setup-token --provider anthropic`。如果你在其他地方运行了 `claude setup-token`,在 Gateway 网关主机上使用 `openclaw models auth paste-token --provider anthropic` 粘贴。参阅 [Anthropic](/providers/anthropic)。
|
||||
|
||||
### 是否支持 Claude 订阅认证(Claude Pro/Max)
|
||||
|
||||
@@ -673,13 +673,13 @@ claude setup-token
|
||||
|
||||
### Codex 认证如何工作
|
||||
|
||||
OpenClaw 通过 OAuth(ChatGPT 登录)支持 **OpenAI Code (Codex)**。向导可以运行 OAuth 流程,并在适当时将默认模型设置为 `openai-codex/gpt-5.2`。参阅[模型提供商](/concepts/model-providers)和[向导](/start/wizard)。
|
||||
OpenClaw 通过 OAuth(ChatGPT 登录)支持 **OpenAI Code (Codex)**。新手引导可以运行 OAuth 流程,并在适当时将默认模型设置为 `openai-codex/gpt-5.2`。参阅[模型提供商](/concepts/model-providers)和[CLI 新手引导](/start/wizard)。
|
||||
|
||||
### 是否支持 OpenAI 订阅认证(Codex OAuth)
|
||||
|
||||
是的。OpenClaw 完全支持 **OpenAI Code (Codex) 订阅 OAuth**。新手引导向导可以为你运行 OAuth 流程。
|
||||
是的。OpenClaw 完全支持 **OpenAI Code (Codex) 订阅 OAuth**。新手引导可以为你运行 OAuth 流程。
|
||||
|
||||
参阅 [OAuth](/concepts/oauth)、[模型提供商](/concepts/model-providers)和[向导](/start/wizard)。
|
||||
参阅 [OAuth](/concepts/oauth)、[模型提供商](/concepts/model-providers)和[CLI 新手引导](/start/wizard)。
|
||||
|
||||
### 如何设置 Gemini CLI OAuth
|
||||
|
||||
@@ -1632,7 +1632,7 @@ openclaw onboard --install-daemon
|
||||
|
||||
注意:
|
||||
|
||||
- 新手引导向导在看到现有配置时也提供**重置**选项。参阅[向导](/start/wizard)。
|
||||
- 新手引导在看到现有配置时也提供**重置**选项。参阅[CLI 新手引导](/start/wizard)。
|
||||
- 如果你使用了配置文件(`--profile` / `OPENCLAW_PROFILE`),重置每个状态目录(默认为 `~/.openclaw-<profile>`)。
|
||||
- 开发重置:`openclaw gateway --dev --reset`(仅限开发;清除开发配置 + 凭据 + 会话 + 工作区)。
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ x-i18n:
|
||||
<Card title="入门指南" href="/start/getting-started" icon="rocket">
|
||||
安装 OpenClaw 并在几分钟内启动 Gateway 网关。
|
||||
</Card>
|
||||
<Card title="运行向导" href="/start/wizard" icon="sparkles">
|
||||
<Card title="运行新手引导" href="/start/wizard" icon="sparkles">
|
||||
通过 `openclaw onboard` 和配对流程进行引导式设置。
|
||||
</Card>
|
||||
<Card title="打开控制界面" href="/web/control-ui" icon="layout-dashboard">
|
||||
|
||||
@@ -60,13 +60,13 @@ x-i18n:
|
||||
</Note>
|
||||
|
||||
</Step>
|
||||
<Step title="运行设置向导">
|
||||
<Step title="运行新手引导">
|
||||
```bash
|
||||
openclaw onboard --install-daemon
|
||||
```
|
||||
|
||||
向导会配置认证、Gateway 网关设置和可选渠道。
|
||||
详情请参见 [Setup Wizard](/start/wizard)。
|
||||
新手引导会配置认证、Gateway 网关设置和可选渠道。
|
||||
详情请参见 [CLI 新手引导](/start/wizard)。
|
||||
|
||||
</Step>
|
||||
<Step title="检查 Gateway 网关">
|
||||
@@ -122,8 +122,8 @@ x-i18n:
|
||||
## 深入了解
|
||||
|
||||
<Columns>
|
||||
<Card title="设置向导(详情)" href="/start/wizard">
|
||||
完整的 CLI 向导参考和高级选项。
|
||||
<Card title="CLI 新手引导" href="/start/wizard">
|
||||
完整的 CLI 新手引导参考和高级选项。
|
||||
</Card>
|
||||
<Card title="macOS 应用新手引导" href="/start/onboarding">
|
||||
macOS 应用的首次运行流程。
|
||||
|
||||
@@ -26,7 +26,7 @@ x-i18n:
|
||||
- [入门指南](/start/getting-started)
|
||||
- [快速开始](/start/quickstart)
|
||||
- [新手引导](/start/onboarding)
|
||||
- [向导](/start/wizard)
|
||||
- [CLI 新手引导](/start/wizard)
|
||||
- [安装配置](/start/setup)
|
||||
- [仪表盘(本地 Gateway 网关)](http://127.0.0.1:18789/)
|
||||
- [帮助](/help)
|
||||
|
||||
@@ -21,21 +21,21 @@ OpenClaw 支持多种新手引导路径,具体取决于 Gateway 网关运行
|
||||
|
||||
## 选择你的新手引导路径
|
||||
|
||||
- 适用于 macOS、Linux 和 Windows(通过 WSL2)的 **CLI 向导**。
|
||||
- 适用于 macOS、Linux 和 Windows(通过 WSL2)的 **CLI 新手引导**。
|
||||
- 适用于 Apple silicon 或 Intel Mac 的 **macOS 应用**,提供引导式首次运行体验。
|
||||
|
||||
## CLI 设置向导
|
||||
## CLI 新手引导
|
||||
|
||||
在终端中运行向导:
|
||||
在终端中运行新手引导:
|
||||
|
||||
```bash
|
||||
openclaw onboard
|
||||
```
|
||||
|
||||
当你希望完全控制 Gateway 网关、工作区、
|
||||
渠道和 Skills 时,请使用 CLI 向导。文档:
|
||||
渠道和 Skills 时,请使用 CLI 新手引导。文档:
|
||||
|
||||
- [设置向导(CLI)](/start/wizard)
|
||||
- [CLI 新手引导](/start/wizard)
|
||||
- [`openclaw onboard` 命令](/cli/onboard)
|
||||
|
||||
## macOS 应用新手引导
|
||||
@@ -48,7 +48,7 @@ openclaw onboard
|
||||
|
||||
如果你需要一个未列出的端点,包括那些
|
||||
公开标准 OpenAI 或 Anthropic API 的托管提供商,请在
|
||||
CLI 向导中选择 **Custom Provider**。系统会要求你:
|
||||
在 CLI 新手引导中选择 **Custom Provider**。系统会要求你:
|
||||
|
||||
- 选择兼容 OpenAI、兼容 Anthropic,或 **Unknown**(自动检测)。
|
||||
- 输入基础 URL 和 API 密钥(如果提供商需要)。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
read_when:
|
||||
- 运行或配置设置向导
|
||||
- 运行或配置 CLI 新手引导
|
||||
- 设置一台新机器
|
||||
sidebarTitle: "Onboarding: CLI"
|
||||
summary: CLI 设置向导:用于 Gateway 网关、工作区、渠道和 Skills 的引导式设置
|
||||
title: 设置向导(CLI)
|
||||
summary: CLI 新手引导:用于 Gateway 网关、工作区、渠道和 Skills 的引导式设置
|
||||
title: CLI 新手引导
|
||||
x-i18n:
|
||||
generated_at: "2026-03-16T06:28:38Z"
|
||||
model: gpt-5.4
|
||||
@@ -14,9 +14,9 @@ x-i18n:
|
||||
workflow: 15
|
||||
---
|
||||
|
||||
# 设置向导(CLI)
|
||||
# CLI 新手引导
|
||||
|
||||
设置向导是在 macOS、
|
||||
CLI 新手引导是在 macOS、
|
||||
Linux 或 Windows(通过 WSL2;强烈推荐)上设置 OpenClaw 的**推荐**方式。
|
||||
它可在一次引导式流程中配置本地 Gateway 网关或远程 Gateway 网关连接,以及渠道、Skills
|
||||
和工作区默认值。
|
||||
@@ -42,7 +42,7 @@ openclaw agents add <name>
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
设置向导包含一个 web search 步骤,你可以选择一个提供商
|
||||
CLI 新手引导包含一个 web search 步骤,你可以选择一个提供商
|
||||
(Perplexity、Brave、Gemini、Grok 或 Kimi),并粘贴你的 API 密钥,以便智能体
|
||||
可以使用 `web_search`。你也可以稍后通过
|
||||
`openclaw configure --section web` 进行配置。文档:[Web 工具](/tools/web)。
|
||||
@@ -50,7 +50,7 @@ openclaw agents add <name>
|
||||
|
||||
## 快速开始与高级模式
|
||||
|
||||
向导开始时会让你选择**快速开始**(默认值)或**高级模式**(完全控制)。
|
||||
新手引导开始时会让你选择**快速开始**(默认值)或**高级模式**(完全控制)。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="快速开始(默认值)">
|
||||
@@ -68,7 +68,7 @@ openclaw agents add <name>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## 向导会配置什么
|
||||
## 新手引导会配置什么
|
||||
|
||||
**本地模式(默认)**会引导你完成以下步骤:
|
||||
|
||||
@@ -91,9 +91,9 @@ openclaw agents add <name>
|
||||
7. **Skills** —— 安装推荐的 Skills 和可选依赖项。
|
||||
|
||||
<Note>
|
||||
重新运行向导**不会**清除任何内容,除非你显式选择 **Reset**(或传入 `--reset`)。
|
||||
重新运行新手引导**不会**清除任何内容,除非你显式选择 **Reset**(或传入 `--reset`)。
|
||||
CLI `--reset` 默认会重置配置、凭证和会话;如需包含工作区,请使用 `--reset-scope full`。
|
||||
如果配置无效或包含旧版键,向导会先要求你运行 `openclaw doctor`。
|
||||
如果配置无效或包含旧版键,新手引导会先要求你运行 `openclaw doctor`。
|
||||
</Note>
|
||||
|
||||
**远程模式**只会配置本地客户端以连接到其他地方的 Gateway 网关。
|
||||
@@ -102,7 +102,7 @@ CLI `--reset` 默认会重置配置、凭证和会话;如需包含工作区,
|
||||
## 添加另一个智能体
|
||||
|
||||
使用 `openclaw agents add <name>` 创建一个单独的智能体,它拥有自己的工作区、
|
||||
会话和认证配置文件。不带 `--workspace` 运行会启动向导。
|
||||
会话和认证配置文件。不带 `--workspace` 运行会启动新手引导。
|
||||
|
||||
它会设置:
|
||||
|
||||
@@ -113,7 +113,7 @@ CLI `--reset` 默认会重置配置、凭证和会话;如需包含工作区,
|
||||
说明:
|
||||
|
||||
- 默认工作区遵循 `~/.openclaw/workspace-<agentId>`。
|
||||
- 添加 `bindings` 以路由入站消息(向导可以完成这项操作)。
|
||||
- 添加 `bindings` 以路由入站消息(新手引导可以完成这项操作)。
|
||||
- 非交互式标志:`--model`、`--agent-dir`、`--bind`、`--non-interactive`。
|
||||
|
||||
## 完整参考
|
||||
@@ -122,7 +122,7 @@ CLI `--reset` 默认会重置配置、凭证和会话;如需包含工作区,
|
||||
[CLI 设置参考](/start/wizard-cli-reference)。
|
||||
有关非交互式示例,请参见 [CLI 自动化](/start/wizard-cli-automation)。
|
||||
有关更深入的技术参考(包括 RPC 细节),请参见
|
||||
[向导参考](/reference/wizard)。
|
||||
[新手引导参考](/reference/wizard)。
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -127,17 +127,14 @@ export function applyGroupGating(params: ApplyGroupGatingParams) {
|
||||
conversationId: params.conversationId,
|
||||
});
|
||||
const requireMention = activation !== "always";
|
||||
const selfJid = params.msg.selfJid?.replace(/:\d+/, "");
|
||||
const selfLid = params.msg.selfLid?.replace(/:\d+/, "");
|
||||
// replyToSenderJid may carry either a standard JID or an @lid identifier.
|
||||
const replySenderJid = params.msg.replyToSenderJid?.replace(/:\d+/, "");
|
||||
const selfJid = params.msg.selfJid?.replace(/:\\d+/, "");
|
||||
const replySenderJid = params.msg.replyToSenderJid?.replace(/:\\d+/, "");
|
||||
const selfE164 = params.msg.selfE164 ? normalizeE164(params.msg.selfE164) : null;
|
||||
const replySenderE164 = params.msg.replyToSenderE164
|
||||
? normalizeE164(params.msg.replyToSenderE164)
|
||||
: null;
|
||||
const implicitMention = Boolean(
|
||||
(selfJid && replySenderJid && selfJid === replySenderJid) ||
|
||||
(selfLid && replySenderJid && selfLid === replySenderJid) ||
|
||||
(selfE164 && replySenderE164 && selfE164 === replySenderE164),
|
||||
);
|
||||
const mentionGate = resolveMentionGating({
|
||||
|
||||
@@ -112,7 +112,7 @@ describe("applyGroupGating", () => {
|
||||
accountId: "default",
|
||||
body: "following up",
|
||||
timestamp: Date.now(),
|
||||
selfJid: "15551234567:1@s.whatsapp.net",
|
||||
selfJid: "15551234567@s.whatsapp.net",
|
||||
selfE164: "+15551234567",
|
||||
replyToId: "m0",
|
||||
replyToBody: "bot said hi",
|
||||
@@ -125,29 +125,6 @@ describe("applyGroupGating", () => {
|
||||
expect(result.shouldProcess).toBe(true);
|
||||
});
|
||||
|
||||
it("treats LID-format reply-to-bot as implicit mention", () => {
|
||||
const cfg = makeConfig({});
|
||||
const { result } = runGroupGating({
|
||||
cfg,
|
||||
msg: createGroupMessage({
|
||||
id: "m1-lid",
|
||||
to: "+15550000",
|
||||
accountId: "default",
|
||||
body: "following up",
|
||||
timestamp: Date.now(),
|
||||
selfJid: "15551234567@s.whatsapp.net",
|
||||
selfLid: "1234567890123:1@lid",
|
||||
selfE164: "+15551234567",
|
||||
replyToId: "m0",
|
||||
replyToBody: "bot said hi",
|
||||
replyToSender: "1234567890123@lid",
|
||||
replyToSenderJid: "1234567890123@lid",
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result.shouldProcess).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ id: "g-new", command: "/new" },
|
||||
{ id: "g-status", command: "/status" },
|
||||
|
||||
@@ -66,7 +66,6 @@ export async function monitorWebInbox(options: {
|
||||
}
|
||||
|
||||
const selfJid = sock.user?.id;
|
||||
const selfLid = sock.user?.lid;
|
||||
const selfE164 = selfJid ? jidToE164(selfJid) : null;
|
||||
const debouncer = createInboundDebouncer<WebInboundMessage>({
|
||||
debounceMs: options.debounceMs ?? 0,
|
||||
@@ -373,7 +372,6 @@ export async function monitorWebInbox(options: {
|
||||
groupParticipants: inbound.groupParticipants,
|
||||
mentionedJids: mentionedJids ?? undefined,
|
||||
selfJid,
|
||||
selfLid,
|
||||
selfE164,
|
||||
fromMe: Boolean(msg.key?.fromMe),
|
||||
location: enriched.location ?? undefined,
|
||||
|
||||
@@ -30,7 +30,6 @@ export type WebInboundMessage = {
|
||||
groupParticipants?: string[];
|
||||
mentionedJids?: string[];
|
||||
selfJid?: string | null;
|
||||
selfLid?: string | null;
|
||||
selfE164?: string | null;
|
||||
fromMe?: boolean;
|
||||
location?: NormalizedLocation;
|
||||
|
||||
@@ -118,12 +118,7 @@ describe("web monitor inbox", () => {
|
||||
await tick();
|
||||
|
||||
expect(onMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
body: "ping",
|
||||
from: "+999",
|
||||
to: "+123",
|
||||
selfLid: "123:1@lid",
|
||||
}),
|
||||
expect.objectContaining({ body: "ping", from: "+999", to: "+123" }),
|
||||
);
|
||||
expect(sock.readMessages).toHaveBeenCalledWith([
|
||||
{
|
||||
@@ -186,12 +181,7 @@ describe("web monitor inbox", () => {
|
||||
|
||||
expect(getPNForLID).toHaveBeenCalledWith("999@lid");
|
||||
expect(onMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
body: "ping",
|
||||
from: "+999",
|
||||
to: "+123",
|
||||
selfLid: "123:1@lid",
|
||||
}),
|
||||
expect.objectContaining({ body: "ping", from: "+999", to: "+123" }),
|
||||
);
|
||||
|
||||
await listener.close();
|
||||
|
||||
@@ -44,7 +44,7 @@ export type MockSock = {
|
||||
getPNForLID: AnyMockFn;
|
||||
};
|
||||
};
|
||||
user: { id: string; lid?: string };
|
||||
user: { id: string };
|
||||
};
|
||||
|
||||
function createResolvedMock() {
|
||||
@@ -66,7 +66,7 @@ function createMockSock(): MockSock {
|
||||
getPNForLID: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
},
|
||||
user: { id: "123@s.whatsapp.net", lid: "123:1@lid" },
|
||||
user: { id: "123@s.whatsapp.net" },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -64,10 +64,7 @@ import { ensureRuntimePluginsLoaded } from "../runtime-plugins.js";
|
||||
import { resolveSandboxContext } from "../sandbox.js";
|
||||
import { repairSessionFileIfNeeded } from "../session-file-repair.js";
|
||||
import { guardSessionManager } from "../session-tool-result-guard-wrapper.js";
|
||||
import {
|
||||
repairToolUseResultPairing,
|
||||
sanitizeToolUseResultPairing,
|
||||
} from "../session-transcript-repair.js";
|
||||
import { sanitizeToolUseResultPairing } from "../session-transcript-repair.js";
|
||||
import {
|
||||
acquireSessionWriteLock,
|
||||
resolveSessionLockMaxHoldFromTimeout,
|
||||
@@ -957,22 +954,6 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
},
|
||||
},
|
||||
);
|
||||
// Re-run tool_use/tool_result pairing repair after compaction.
|
||||
// Compaction can remove assistant messages containing tool_use blocks
|
||||
// while leaving orphaned tool_result blocks behind, which causes
|
||||
// Anthropic API 400 errors: "unexpected tool_use_id found in tool_result blocks".
|
||||
// See: https://github.com/openclaw/openclaw/issues/15691
|
||||
if (transcriptPolicy.repairToolUseResultPairing) {
|
||||
const postCompactRepair = repairToolUseResultPairing(session.messages);
|
||||
if (postCompactRepair.droppedOrphanCount > 0 || postCompactRepair.moved) {
|
||||
session.agent.replaceMessages(postCompactRepair.messages);
|
||||
log.info(
|
||||
`[compaction] post-compact repair: dropped ${postCompactRepair.droppedOrphanCount} orphaned tool_result(s), ` +
|
||||
`${postCompactRepair.droppedDuplicateCount} duplicate(s) ` +
|
||||
`(sessionKey=${params.sessionKey ?? params.sessionId})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
await runPostCompactionSideEffects({
|
||||
config: params.config,
|
||||
sessionKey: params.sessionKey,
|
||||
|
||||
@@ -28,8 +28,6 @@ const mockSummarizeInStages = vi.mocked(compactionModule.summarizeInStages);
|
||||
const {
|
||||
collectToolFailures,
|
||||
formatToolFailuresSection,
|
||||
trimToolResultsForSummarization,
|
||||
restoreOriginalToolResultsForKeptMessages,
|
||||
splitPreservedRecentTurns,
|
||||
formatPreservedTurnsSection,
|
||||
buildCompactionStructureInstructions,
|
||||
@@ -47,26 +45,6 @@ const {
|
||||
SAFETY_MARGIN,
|
||||
} = __testing;
|
||||
|
||||
function readTextBlocks(message: AgentMessage): string {
|
||||
const content = (message as { content?: unknown }).content;
|
||||
if (typeof content === "string") {
|
||||
return content;
|
||||
}
|
||||
if (!Array.isArray(content)) {
|
||||
return "";
|
||||
}
|
||||
return content
|
||||
.map((block) => {
|
||||
if (!block || typeof block !== "object") {
|
||||
return "";
|
||||
}
|
||||
const text = (block as { text?: unknown }).text;
|
||||
return typeof text === "string" ? text : "";
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function stubSessionManager(): ExtensionContext["sessionManager"] {
|
||||
const stub: ExtensionContext["sessionManager"] = {
|
||||
getCwd: () => "/stub",
|
||||
@@ -256,116 +234,6 @@ describe("compaction-safeguard tool failures", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("compaction-safeguard toolResult trimming", () => {
|
||||
it("truncates oversized tool results and compacts older entries to stay within budget", () => {
|
||||
const messages: AgentMessage[] = Array.from({ length: 9 }, (_unused, index) => ({
|
||||
role: "toolResult",
|
||||
toolCallId: `call-${index}`,
|
||||
toolName: "read",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `head-${index}\n${"x".repeat(25_000)}\ntail-${index}`,
|
||||
},
|
||||
],
|
||||
timestamp: index + 1,
|
||||
})) as AgentMessage[];
|
||||
|
||||
const trimmed = trimToolResultsForSummarization(messages);
|
||||
|
||||
expect(trimmed.stats.truncatedCount).toBe(9);
|
||||
expect(trimmed.stats.compactedCount).toBe(1);
|
||||
expect(readTextBlocks(trimmed.messages[0])).toBe("");
|
||||
expect(trimmed.stats.afterChars).toBeLessThan(trimmed.stats.beforeChars);
|
||||
expect(readTextBlocks(trimmed.messages[8])).toContain("head-8");
|
||||
expect(readTextBlocks(trimmed.messages[8])).toContain(
|
||||
"[...tool result truncated for compaction budget...]",
|
||||
);
|
||||
expect(readTextBlocks(trimmed.messages[8])).toContain("tail-8");
|
||||
});
|
||||
|
||||
it("restores kept tool results after prune for both toolCallId and toolUseId", () => {
|
||||
const originalMessages: AgentMessage[] = [
|
||||
{ role: "user", content: "keep these tool results", timestamp: 1 },
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "call-1",
|
||||
toolName: "read",
|
||||
content: [{ type: "text", text: "original call payload" }],
|
||||
timestamp: 2,
|
||||
} as AgentMessage,
|
||||
{
|
||||
role: "toolResult",
|
||||
toolUseId: "use-1",
|
||||
toolName: "exec",
|
||||
content: [{ type: "text", text: "original use payload" }],
|
||||
timestamp: 3,
|
||||
} as unknown as AgentMessage,
|
||||
];
|
||||
const prunedMessages: AgentMessage[] = [
|
||||
originalMessages[0],
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "call-1",
|
||||
toolName: "read",
|
||||
content: [{ type: "text", text: "trimmed call payload" }],
|
||||
timestamp: 2,
|
||||
} as AgentMessage,
|
||||
{
|
||||
role: "toolResult",
|
||||
toolUseId: "use-1",
|
||||
toolName: "exec",
|
||||
content: [{ type: "text", text: "trimmed use payload" }],
|
||||
timestamp: 3,
|
||||
} as unknown as AgentMessage,
|
||||
];
|
||||
|
||||
const restored = restoreOriginalToolResultsForKeptMessages({
|
||||
prunedMessages,
|
||||
originalMessages,
|
||||
});
|
||||
|
||||
expect(readTextBlocks(restored[1])).toBe("original call payload");
|
||||
expect(readTextBlocks(restored[2])).toBe("original use payload");
|
||||
});
|
||||
|
||||
it("extracts identifiers from the trimmed kept payloads after prune restore", () => {
|
||||
const hiddenIdentifier = "DEADBEEF12345678";
|
||||
const restored = restoreOriginalToolResultsForKeptMessages({
|
||||
prunedMessages: [
|
||||
{ role: "user", content: "recent ask", timestamp: 1 },
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "call-1",
|
||||
toolName: "read",
|
||||
content: [{ type: "text", text: "placeholder" }],
|
||||
timestamp: 2,
|
||||
} as AgentMessage,
|
||||
],
|
||||
originalMessages: [
|
||||
{ role: "user", content: "recent ask", timestamp: 1 },
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "call-1",
|
||||
toolName: "read",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `visible head ${"a".repeat(16_000)}${hiddenIdentifier}${"b".repeat(16_000)} visible tail`,
|
||||
},
|
||||
],
|
||||
timestamp: 2,
|
||||
} as AgentMessage,
|
||||
],
|
||||
});
|
||||
|
||||
const trimmed = trimToolResultsForSummarization(restored).messages;
|
||||
const identifierSeedText = trimmed.map((message) => readTextBlocks(message)).join("\n");
|
||||
|
||||
expect(extractOpaqueIdentifiers(identifierSeedText)).not.toContain(hiddenIdentifier);
|
||||
});
|
||||
});
|
||||
|
||||
describe("computeAdaptiveChunkRatio", () => {
|
||||
const CONTEXT_WINDOW = 200_000;
|
||||
|
||||
|
||||
@@ -407,179 +407,6 @@ function formatPreservedTurnsSection(messages: AgentMessage[]): string {
|
||||
return `\n\n## Recent turns preserved verbatim\n${lines.join("\n")}`;
|
||||
}
|
||||
|
||||
type ToolResultSummaryTrimStats = {
|
||||
truncatedCount: number;
|
||||
compactedCount: number;
|
||||
beforeChars: number;
|
||||
afterChars: number;
|
||||
};
|
||||
|
||||
const COMPACTION_SUMMARY_TOOL_RESULT_MAX_CHARS = 2_500;
|
||||
const COMPACTION_SUMMARY_TOOL_RESULT_TOTAL_CHARS_BUDGET = 20_000;
|
||||
const COMPACTION_SUMMARY_TOOL_RESULT_TRUNCATION_NOTICE =
|
||||
"[...tool result truncated for compaction budget...]";
|
||||
const COMPACTION_SUMMARY_TOOL_RESULT_COMPACTED_NOTICE =
|
||||
"[tool result compacted due to global compaction budget]";
|
||||
const COMPACTION_SUMMARY_TOOL_RESULT_NON_TEXT_NOTICE = "[non-text tool result content omitted]";
|
||||
|
||||
function getToolResultTextFromContent(content: unknown): string {
|
||||
if (!Array.isArray(content)) {
|
||||
return "";
|
||||
}
|
||||
const parts: string[] = [];
|
||||
for (const block of content) {
|
||||
if (!block || typeof block !== "object") {
|
||||
continue;
|
||||
}
|
||||
const text = (block as { text?: unknown }).text;
|
||||
if (typeof text === "string" && text.length > 0) {
|
||||
parts.push(text);
|
||||
}
|
||||
}
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
function hasNonTextToolResultContent(content: unknown): boolean {
|
||||
if (!Array.isArray(content)) {
|
||||
return false;
|
||||
}
|
||||
return content.some((block) => {
|
||||
if (!block || typeof block !== "object") {
|
||||
return false;
|
||||
}
|
||||
const t = (block as { type?: unknown }).type;
|
||||
return t !== "text";
|
||||
});
|
||||
}
|
||||
|
||||
function replaceToolResultContentForSummary(msg: AgentMessage, text: string): AgentMessage {
|
||||
return {
|
||||
...(msg as unknown as Record<string, unknown>),
|
||||
content: [{ type: "text", text }],
|
||||
} as AgentMessage;
|
||||
}
|
||||
|
||||
function trimToolResultsForSummarization(messages: AgentMessage[]): {
|
||||
messages: AgentMessage[];
|
||||
stats: ToolResultSummaryTrimStats;
|
||||
} {
|
||||
const next = [...messages];
|
||||
let truncatedCount = 0;
|
||||
let compactedCount = 0;
|
||||
let beforeChars = 0;
|
||||
|
||||
for (let i = 0; i < next.length; i += 1) {
|
||||
const msg = next[i];
|
||||
if ((msg as { role?: unknown }).role !== "toolResult") {
|
||||
continue;
|
||||
}
|
||||
const content = (msg as { content?: unknown }).content;
|
||||
const text = getToolResultTextFromContent(content);
|
||||
const hasNonText = hasNonTextToolResultContent(content);
|
||||
beforeChars += text.length;
|
||||
|
||||
let normalized = text;
|
||||
if (normalized.length === 0 && hasNonText) {
|
||||
normalized = COMPACTION_SUMMARY_TOOL_RESULT_NON_TEXT_NOTICE;
|
||||
}
|
||||
|
||||
if (normalized.length > COMPACTION_SUMMARY_TOOL_RESULT_MAX_CHARS) {
|
||||
const separator = `\n\n${COMPACTION_SUMMARY_TOOL_RESULT_TRUNCATION_NOTICE}\n\n`;
|
||||
const available = Math.max(0, COMPACTION_SUMMARY_TOOL_RESULT_MAX_CHARS - separator.length);
|
||||
const tailBudget = Math.floor(available * 0.35);
|
||||
const headBudget = Math.max(0, available - tailBudget);
|
||||
const head = normalized.slice(0, headBudget);
|
||||
const tail = tailBudget > 0 ? normalized.slice(-tailBudget) : "";
|
||||
normalized = `${head}${separator}${tail}`;
|
||||
truncatedCount += 1;
|
||||
}
|
||||
|
||||
if (hasNonText || normalized !== text) {
|
||||
next[i] = replaceToolResultContentForSummary(msg, normalized);
|
||||
}
|
||||
}
|
||||
|
||||
let runningChars = 0;
|
||||
for (let i = next.length - 1; i >= 0; i -= 1) {
|
||||
const msg = next[i];
|
||||
if ((msg as { role?: unknown }).role !== "toolResult") {
|
||||
continue;
|
||||
}
|
||||
const text = getToolResultTextFromContent((msg as { content?: unknown }).content);
|
||||
if (runningChars + text.length <= COMPACTION_SUMMARY_TOOL_RESULT_TOTAL_CHARS_BUDGET) {
|
||||
runningChars += text.length;
|
||||
continue;
|
||||
}
|
||||
const placeholderLen = COMPACTION_SUMMARY_TOOL_RESULT_COMPACTED_NOTICE.length;
|
||||
const remainingBudget = Math.max(
|
||||
0,
|
||||
COMPACTION_SUMMARY_TOOL_RESULT_TOTAL_CHARS_BUDGET - runningChars,
|
||||
);
|
||||
const replacementText =
|
||||
remainingBudget >= placeholderLen ? COMPACTION_SUMMARY_TOOL_RESULT_COMPACTED_NOTICE : "";
|
||||
next[i] = replaceToolResultContentForSummary(msg, replacementText);
|
||||
runningChars += replacementText.length;
|
||||
compactedCount += 1;
|
||||
}
|
||||
|
||||
let afterChars = 0;
|
||||
for (const msg of next) {
|
||||
if ((msg as { role?: unknown }).role !== "toolResult") {
|
||||
continue;
|
||||
}
|
||||
afterChars += getToolResultTextFromContent((msg as { content?: unknown }).content).length;
|
||||
}
|
||||
|
||||
return {
|
||||
messages: next,
|
||||
stats: { truncatedCount, compactedCount, beforeChars, afterChars },
|
||||
};
|
||||
}
|
||||
|
||||
function getToolResultStableId(message: AgentMessage): string | null {
|
||||
if ((message as { role?: unknown }).role !== "toolResult") {
|
||||
return null;
|
||||
}
|
||||
const toolCallId = (message as { toolCallId?: unknown }).toolCallId;
|
||||
if (typeof toolCallId === "string" && toolCallId.length > 0) {
|
||||
return `call:${toolCallId}`;
|
||||
}
|
||||
const toolUseId = (message as { toolUseId?: unknown }).toolUseId;
|
||||
if (typeof toolUseId === "string" && toolUseId.length > 0) {
|
||||
return `use:${toolUseId}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function restoreOriginalToolResultsForKeptMessages(params: {
|
||||
prunedMessages: AgentMessage[];
|
||||
originalMessages: AgentMessage[];
|
||||
}): AgentMessage[] {
|
||||
const originalByStableId = new Map<string, AgentMessage[]>();
|
||||
for (const message of params.originalMessages) {
|
||||
const stableId = getToolResultStableId(message);
|
||||
if (!stableId) {
|
||||
continue;
|
||||
}
|
||||
const bucket = originalByStableId.get(stableId) ?? [];
|
||||
bucket.push(message);
|
||||
originalByStableId.set(stableId, bucket);
|
||||
}
|
||||
|
||||
return params.prunedMessages.map((message) => {
|
||||
const stableId = getToolResultStableId(message);
|
||||
if (!stableId) {
|
||||
return message;
|
||||
}
|
||||
const bucket = originalByStableId.get(stableId);
|
||||
if (!bucket || bucket.length === 0) {
|
||||
return message;
|
||||
}
|
||||
const restored = bucket.shift();
|
||||
return restored ?? message;
|
||||
});
|
||||
}
|
||||
|
||||
function wrapUntrustedInstructionBlock(label: string, text: string): string {
|
||||
return wrapUntrustedPromptDataBlock({
|
||||
label,
|
||||
@@ -928,18 +755,6 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
|
||||
const modelContextWindow = resolveContextWindowTokens(model);
|
||||
const contextWindowTokens = runtime?.contextWindowTokens ?? modelContextWindow;
|
||||
const turnPrefixMessages = preparation.turnPrefixMessages ?? [];
|
||||
const prefixTrimmedForBudget = trimToolResultsForSummarization(turnPrefixMessages);
|
||||
if (
|
||||
prefixTrimmedForBudget.stats.truncatedCount > 0 ||
|
||||
prefixTrimmedForBudget.stats.compactedCount > 0
|
||||
) {
|
||||
log.warn(
|
||||
`Compaction safeguard: pre-trimmed prefix toolResult payloads for budgeting ` +
|
||||
`(truncated=${prefixTrimmedForBudget.stats.truncatedCount}, compacted=${prefixTrimmedForBudget.stats.compactedCount}, ` +
|
||||
`chars=${prefixTrimmedForBudget.stats.beforeChars}->${prefixTrimmedForBudget.stats.afterChars})`,
|
||||
);
|
||||
}
|
||||
const prefixMessagesForSummary = prefixTrimmedForBudget.messages;
|
||||
let messagesToSummarize = preparation.messagesToSummarize;
|
||||
const recentTurnsPreserve = resolveRecentTurnsPreserve(runtime?.recentTurnsPreserve);
|
||||
const qualityGuardEnabled = runtime?.qualityGuardEnabled ?? false;
|
||||
@@ -959,44 +774,28 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
|
||||
let droppedSummary: string | undefined;
|
||||
|
||||
if (tokensBefore !== undefined) {
|
||||
const budgetTrimmedForSummary = trimToolResultsForSummarization(messagesToSummarize);
|
||||
if (
|
||||
budgetTrimmedForSummary.stats.truncatedCount > 0 ||
|
||||
budgetTrimmedForSummary.stats.compactedCount > 0
|
||||
) {
|
||||
log.warn(
|
||||
`Compaction safeguard: pre-trimmed toolResult payloads for budgeting ` +
|
||||
`(truncated=${budgetTrimmedForSummary.stats.truncatedCount}, compacted=${budgetTrimmedForSummary.stats.compactedCount}, ` +
|
||||
`chars=${budgetTrimmedForSummary.stats.beforeChars}->${budgetTrimmedForSummary.stats.afterChars})`,
|
||||
);
|
||||
}
|
||||
const summarizableTokens =
|
||||
estimateMessagesTokens(budgetTrimmedForSummary.messages) +
|
||||
estimateMessagesTokens(prefixMessagesForSummary);
|
||||
estimateMessagesTokens(messagesToSummarize) + estimateMessagesTokens(turnPrefixMessages);
|
||||
const newContentTokens = Math.max(0, Math.floor(tokensBefore - summarizableTokens));
|
||||
// Apply SAFETY_MARGIN so token underestimates don't trigger unnecessary pruning
|
||||
const maxHistoryTokens = Math.floor(contextWindowTokens * maxHistoryShare * SAFETY_MARGIN);
|
||||
|
||||
if (newContentTokens > maxHistoryTokens) {
|
||||
const originalMessagesBeforePrune = messagesToSummarize;
|
||||
const pruned = pruneHistoryForContextShare({
|
||||
messages: budgetTrimmedForSummary.messages,
|
||||
messages: messagesToSummarize,
|
||||
maxContextTokens: contextWindowTokens,
|
||||
maxHistoryShare,
|
||||
parts: 2,
|
||||
});
|
||||
if (pruned.droppedChunks > 0) {
|
||||
const historyRatio = (summarizableTokens / contextWindowTokens) * 100;
|
||||
const newContentRatio = (newContentTokens / contextWindowTokens) * 100;
|
||||
log.warn(
|
||||
`Compaction safeguard: summarizable history uses ${historyRatio.toFixed(
|
||||
`Compaction safeguard: new content uses ${newContentRatio.toFixed(
|
||||
1,
|
||||
)}% of context; dropped ${pruned.droppedChunks} older chunk(s) ` +
|
||||
`(${pruned.droppedMessages} messages) to fit history budget.`,
|
||||
);
|
||||
messagesToSummarize = restoreOriginalToolResultsForKeptMessages({
|
||||
prunedMessages: pruned.messages,
|
||||
originalMessages: originalMessagesBeforePrune,
|
||||
});
|
||||
messagesToSummarize = pruned.messages;
|
||||
|
||||
// Summarize dropped messages so context isn't lost
|
||||
if (pruned.droppedMessagesList.length > 0) {
|
||||
@@ -1010,19 +809,8 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
|
||||
Math.floor(contextWindowTokens * droppedChunkRatio) -
|
||||
SUMMARIZATION_OVERHEAD_TOKENS,
|
||||
);
|
||||
const droppedTrimmed = trimToolResultsForSummarization(pruned.droppedMessagesList);
|
||||
if (
|
||||
droppedTrimmed.stats.truncatedCount > 0 ||
|
||||
droppedTrimmed.stats.compactedCount > 0
|
||||
) {
|
||||
log.warn(
|
||||
`Compaction safeguard: trimmed dropped toolResult payloads before summarize ` +
|
||||
`(truncated=${droppedTrimmed.stats.truncatedCount}, compacted=${droppedTrimmed.stats.compactedCount}, ` +
|
||||
`chars=${droppedTrimmed.stats.beforeChars}->${droppedTrimmed.stats.afterChars})`,
|
||||
);
|
||||
}
|
||||
droppedSummary = await summarizeInStages({
|
||||
messages: droppedTrimmed.messages,
|
||||
messages: pruned.droppedMessagesList,
|
||||
model,
|
||||
apiKey,
|
||||
signal,
|
||||
@@ -1054,21 +842,8 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
|
||||
});
|
||||
messagesToSummarize = summaryTargetMessages;
|
||||
const preservedTurnsSection = formatPreservedTurnsSection(preservedRecentMessages);
|
||||
const latestUserAsk = extractLatestUserAsk([
|
||||
...messagesToSummarize,
|
||||
...prefixMessagesForSummary,
|
||||
]);
|
||||
const summaryTrimmed = trimToolResultsForSummarization(messagesToSummarize);
|
||||
if (summaryTrimmed.stats.truncatedCount > 0 || summaryTrimmed.stats.compactedCount > 0) {
|
||||
log.warn(
|
||||
`Compaction safeguard: trimmed toolResult payloads before summarize ` +
|
||||
`(truncated=${summaryTrimmed.stats.truncatedCount}, compacted=${summaryTrimmed.stats.compactedCount}, ` +
|
||||
`chars=${summaryTrimmed.stats.beforeChars}->${summaryTrimmed.stats.afterChars})`,
|
||||
);
|
||||
}
|
||||
|
||||
const identifierSourceMessages = [...summaryTrimmed.messages, ...prefixMessagesForSummary];
|
||||
const identifierSeedText = identifierSourceMessages
|
||||
const latestUserAsk = extractLatestUserAsk([...messagesToSummarize, ...turnPrefixMessages]);
|
||||
const identifierSeedText = [...messagesToSummarize, ...turnPrefixMessages]
|
||||
.slice(-10)
|
||||
.map((message) => extractMessageText(message))
|
||||
.filter(Boolean)
|
||||
@@ -1078,7 +853,7 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
|
||||
// Use adaptive chunk ratio based on message sizes, reserving headroom for
|
||||
// the summarization prompt, system prompt, previous summary, and reasoning budget
|
||||
// that generateSummary adds on top of the serialized conversation chunk.
|
||||
const allMessages = [...summaryTrimmed.messages, ...prefixMessagesForSummary];
|
||||
const allMessages = [...messagesToSummarize, ...turnPrefixMessages];
|
||||
const adaptiveRatio = computeAdaptiveChunkRatio(allMessages, contextWindowTokens);
|
||||
const maxChunkTokens = Math.max(
|
||||
1,
|
||||
@@ -1100,9 +875,9 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
|
||||
let summaryWithPreservedTurns = "";
|
||||
try {
|
||||
const historySummary =
|
||||
summaryTrimmed.messages.length > 0
|
||||
messagesToSummarize.length > 0
|
||||
? await summarizeInStages({
|
||||
messages: summaryTrimmed.messages,
|
||||
messages: messagesToSummarize,
|
||||
model,
|
||||
apiKey,
|
||||
signal,
|
||||
@@ -1116,9 +891,9 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
|
||||
: buildStructuredFallbackSummary(effectivePreviousSummary, summarizationInstructions);
|
||||
|
||||
summaryWithoutPreservedTurns = historySummary;
|
||||
if (preparation.isSplitTurn && prefixMessagesForSummary.length > 0) {
|
||||
if (preparation.isSplitTurn && turnPrefixMessages.length > 0) {
|
||||
const prefixSummary = await summarizeInStages({
|
||||
messages: prefixMessagesForSummary,
|
||||
messages: turnPrefixMessages,
|
||||
model,
|
||||
apiKey,
|
||||
signal,
|
||||
@@ -1218,8 +993,6 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
|
||||
export const __testing = {
|
||||
collectToolFailures,
|
||||
formatToolFailuresSection,
|
||||
trimToolResultsForSummarization,
|
||||
restoreOriginalToolResultsForKeptMessages,
|
||||
splitPreservedRecentTurns,
|
||||
formatPreservedTurnsSection,
|
||||
buildCompactionStructureInstructions,
|
||||
|
||||
@@ -488,143 +488,3 @@ describe("stripToolResultDetails", () => {
|
||||
expect(out).toBe(input);
|
||||
});
|
||||
});
|
||||
|
||||
describe("post-compaction orphaned tool_result removal (#15691)", () => {
|
||||
it("drops orphaned tool_result blocks left after compaction removes tool_use messages", () => {
|
||||
const input = castAgentMessages([
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Here is a summary of our earlier conversation..." }],
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "toolu_compacted_1",
|
||||
toolName: "Read",
|
||||
content: [{ type: "text", text: "file contents" }],
|
||||
isError: false,
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "toolu_compacted_2",
|
||||
toolName: "exec",
|
||||
content: [{ type: "text", text: "command output" }],
|
||||
isError: false,
|
||||
},
|
||||
{ role: "user", content: "now do something else" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "I'll read that file" },
|
||||
{ type: "toolCall", id: "toolu_active_1", name: "Read", arguments: { path: "foo.ts" } },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "toolu_active_1",
|
||||
toolName: "Read",
|
||||
content: [{ type: "text", text: "actual content" }],
|
||||
isError: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const result = repairToolUseResultPairing(input);
|
||||
|
||||
expect(result.droppedOrphanCount).toBe(2);
|
||||
const toolResults = result.messages.filter((message) => message.role === "toolResult");
|
||||
expect(toolResults).toHaveLength(1);
|
||||
expect((toolResults[0] as { toolCallId?: string }).toolCallId).toBe("toolu_active_1");
|
||||
expect(result.messages.map((message) => message.role)).toEqual([
|
||||
"assistant",
|
||||
"user",
|
||||
"assistant",
|
||||
"toolResult",
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles synthetic tool_result from interrupted request after compaction", () => {
|
||||
const input = castAgentMessages([
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Compaction summary of previous conversation." }],
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "toolu_interrupted",
|
||||
toolName: "unknown",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "[openclaw] missing tool result in session history; inserted synthetic error result for transcript repair.",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
},
|
||||
{ role: "user", content: "continue please" },
|
||||
]);
|
||||
|
||||
const result = repairToolUseResultPairing(input);
|
||||
|
||||
expect(result.droppedOrphanCount).toBe(1);
|
||||
expect(result.messages.some((message) => message.role === "toolResult")).toBe(false);
|
||||
expect(result.messages.map((message) => message.role)).toEqual(["assistant", "user"]);
|
||||
});
|
||||
|
||||
it("preserves valid tool_use/tool_result pairs while removing orphans", () => {
|
||||
const input = castAgentMessages([
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "toolCall", id: "toolu_valid", name: "Read", arguments: { path: "a.ts" } },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "toolu_valid",
|
||||
toolName: "Read",
|
||||
content: [{ type: "text", text: "content of a.ts" }],
|
||||
isError: false,
|
||||
},
|
||||
{ role: "user", content: "thanks, what about b.ts?" },
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "toolu_gone",
|
||||
toolName: "Read",
|
||||
content: [{ type: "text", text: "content of old file" }],
|
||||
isError: false,
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Let me check b.ts" }],
|
||||
},
|
||||
]);
|
||||
|
||||
const result = repairToolUseResultPairing(input);
|
||||
|
||||
expect(result.droppedOrphanCount).toBe(1);
|
||||
const toolResults = result.messages.filter((message) => message.role === "toolResult");
|
||||
expect(toolResults).toHaveLength(1);
|
||||
expect((toolResults[0] as { toolCallId?: string }).toolCallId).toBe("toolu_valid");
|
||||
});
|
||||
|
||||
it("returns original array when no orphans exist", () => {
|
||||
const input = castAgentMessages([
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "toolCall", id: "toolu_1", name: "Read", arguments: { path: "x.ts" } }],
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "toolu_1",
|
||||
toolName: "Read",
|
||||
content: [{ type: "text", text: "ok" }],
|
||||
isError: false,
|
||||
},
|
||||
{ role: "user", content: "good" },
|
||||
]);
|
||||
|
||||
const result = repairToolUseResultPairing(input);
|
||||
|
||||
expect(result.droppedOrphanCount).toBe(0);
|
||||
expect(result.messages).toStrictEqual(input);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -396,7 +396,7 @@ export function registerConfigCli(program: Command) {
|
||||
const cmd = program
|
||||
.command("config")
|
||||
.description(
|
||||
"Non-interactive config helpers (get/set/unset/file/validate). Run without subcommand for the setup wizard.",
|
||||
"Non-interactive config helpers (get/set/unset/file/validate). Run without subcommand for guided setup.",
|
||||
)
|
||||
.addHelpText(
|
||||
"after",
|
||||
@@ -405,7 +405,7 @@ export function registerConfigCli(program: Command) {
|
||||
)
|
||||
.option(
|
||||
"--section <section>",
|
||||
"Configure wizard sections (repeatable). Use with no subcommand.",
|
||||
"Configuration sections for guided setup (repeatable). Use with no subcommand.",
|
||||
(value: string, previous: string[]) => [...previous, value],
|
||||
[] as string[],
|
||||
)
|
||||
|
||||
@@ -56,7 +56,7 @@ const coreEntries: CoreCliEntry[] = [
|
||||
commands: [
|
||||
{
|
||||
name: "onboard",
|
||||
description: "Interactive setup wizard for gateway, workspace, and skills",
|
||||
description: "Interactive onboarding for gateway, workspace, and skills",
|
||||
hasSubcommands: false,
|
||||
},
|
||||
],
|
||||
@@ -70,7 +70,7 @@ const coreEntries: CoreCliEntry[] = [
|
||||
{
|
||||
name: "configure",
|
||||
description:
|
||||
"Interactive setup wizard for credentials, channels, gateway, and agent defaults",
|
||||
"Interactive configuration for credentials, channels, gateway, and agent defaults",
|
||||
hasSubcommands: false,
|
||||
},
|
||||
],
|
||||
@@ -84,7 +84,7 @@ const coreEntries: CoreCliEntry[] = [
|
||||
{
|
||||
name: "config",
|
||||
description:
|
||||
"Non-interactive config helpers (get/set/unset/file/validate). Default: starts setup wizard.",
|
||||
"Non-interactive config helpers (get/set/unset/file/validate). Default: starts guided setup.",
|
||||
hasSubcommands: true,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -12,18 +12,18 @@ export const CORE_CLI_COMMAND_DESCRIPTORS = [
|
||||
},
|
||||
{
|
||||
name: "onboard",
|
||||
description: "Interactive setup wizard for gateway, workspace, and skills",
|
||||
description: "Interactive onboarding for gateway, workspace, and skills",
|
||||
hasSubcommands: false,
|
||||
},
|
||||
{
|
||||
name: "configure",
|
||||
description: "Interactive setup wizard for credentials, channels, gateway, and agent defaults",
|
||||
description: "Interactive configuration for credentials, channels, gateway, and agent defaults",
|
||||
hasSubcommands: false,
|
||||
},
|
||||
{
|
||||
name: "config",
|
||||
description:
|
||||
"Non-interactive config helpers (get/set/unset/file/validate). Default: starts setup wizard.",
|
||||
"Non-interactive config helpers (get/set/unset/file/validate). Default: starts guided setup.",
|
||||
hasSubcommands: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ import { runCommandWithRuntime } from "../cli-utils.js";
|
||||
export function registerConfigureCommand(program: Command) {
|
||||
program
|
||||
.command("configure")
|
||||
.description("Interactive setup wizard for credentials, channels, gateway, and agent defaults")
|
||||
.description("Interactive configuration for credentials, channels, gateway, and agent defaults")
|
||||
.addHelpText(
|
||||
"after",
|
||||
() =>
|
||||
|
||||
@@ -63,7 +63,7 @@ function pickOnboardProviderAuthOptionValues(
|
||||
export function registerOnboardCommand(program: Command) {
|
||||
const command = program
|
||||
.command("onboard")
|
||||
.description("Interactive wizard to set up the gateway, workspace, and skills")
|
||||
.description("Interactive onboarding for the gateway, workspace, and skills")
|
||||
.addHelpText(
|
||||
"after",
|
||||
() =>
|
||||
@@ -72,7 +72,7 @@ export function registerOnboardCommand(program: Command) {
|
||||
.option("--workspace <dir>", "Agent workspace directory (default: ~/.openclaw/workspace)")
|
||||
.option(
|
||||
"--reset",
|
||||
"Reset config + credentials + sessions before running wizard (workspace only with --reset-scope full)",
|
||||
"Reset config + credentials + sessions before running onboard (workspace only with --reset-scope full)",
|
||||
)
|
||||
.option("--reset-scope <scope>", "Reset scope: config|config+creds+sessions|full")
|
||||
.option("--non-interactive", "Run without prompts", false)
|
||||
@@ -81,8 +81,8 @@ export function registerOnboardCommand(program: Command) {
|
||||
"Acknowledge that agents are powerful and full system access is risky (required for --non-interactive)",
|
||||
false,
|
||||
)
|
||||
.option("--flow <flow>", "Wizard flow: quickstart|advanced|manual")
|
||||
.option("--mode <mode>", "Wizard mode: local|remote")
|
||||
.option("--flow <flow>", "Onboard flow: quickstart|advanced|manual")
|
||||
.option("--mode <mode>", "Onboard mode: local|remote")
|
||||
.option("--auth-choice <choice>", `Auth: ${AUTH_CHOICE_HELP}`)
|
||||
.option(
|
||||
"--token-provider <id>",
|
||||
|
||||
@@ -20,9 +20,9 @@ export function registerSetupCommand(program: Command) {
|
||||
"--workspace <dir>",
|
||||
"Agent workspace directory (default: ~/.openclaw/workspace; stored as agents.defaults.workspace)",
|
||||
)
|
||||
.option("--wizard", "Run the interactive onboarding wizard", false)
|
||||
.option("--non-interactive", "Run the wizard without prompts", false)
|
||||
.option("--mode <mode>", "Wizard mode: local|remote")
|
||||
.option("--wizard", "Run interactive onboarding", false)
|
||||
.option("--non-interactive", "Run onboarding without prompts", false)
|
||||
.option("--mode <mode>", "Onboard mode: local|remote")
|
||||
.option("--remote-url <url>", "Remote Gateway WebSocket URL")
|
||||
.option("--remote-token <token>", "Remote Gateway token (optional)")
|
||||
.action(async (opts, command) => {
|
||||
|
||||
@@ -238,7 +238,7 @@ export async function applyAuthChoicePluginProvider(
|
||||
const provider = resolveProviderMatch(providers, options.providerId);
|
||||
if (!provider) {
|
||||
await params.prompter.note(
|
||||
`${options.label} auth plugin is not available. Enable it and re-run the wizard.`,
|
||||
`${options.label} auth plugin is not available. Enable it and re-run onboarding.`,
|
||||
options.label,
|
||||
);
|
||||
return { config: nextConfig };
|
||||
|
||||
@@ -3,11 +3,14 @@ import { readLatestAssistantReply } from "../../agents/tools/agent-step.js";
|
||||
import { SILENT_REPLY_TOKEN } from "../../auto-reply/tokens.js";
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
|
||||
const FAST_TEST_MODE = process.env.OPENCLAW_TEST_FAST === "1";
|
||||
|
||||
const CRON_SUBAGENT_WAIT_MIN_MS = FAST_TEST_MODE ? 10 : 30_000;
|
||||
const CRON_SUBAGENT_FINAL_REPLY_GRACE_MS = FAST_TEST_MODE ? 50 : 5_000;
|
||||
const CRON_SUBAGENT_GRACE_POLL_MS = FAST_TEST_MODE ? 8 : 200;
|
||||
function resolveCronSubagentTimings() {
|
||||
const fastTestMode = process.env.OPENCLAW_TEST_FAST === "1";
|
||||
return {
|
||||
waitMinMs: fastTestMode ? 10 : 30_000,
|
||||
finalReplyGraceMs: fastTestMode ? 50 : 5_000,
|
||||
gracePollMs: fastTestMode ? 8 : 200,
|
||||
};
|
||||
}
|
||||
|
||||
const SUBAGENT_FOLLOWUP_HINTS = [
|
||||
"subagent spawned",
|
||||
@@ -121,8 +124,9 @@ export async function waitForDescendantSubagentSummary(params: {
|
||||
timeoutMs: number;
|
||||
observedActiveDescendants?: boolean;
|
||||
}): Promise<string | undefined> {
|
||||
const timings = resolveCronSubagentTimings();
|
||||
const initialReply = params.initialReply?.trim();
|
||||
const deadline = Date.now() + Math.max(CRON_SUBAGENT_WAIT_MIN_MS, Math.floor(params.timeoutMs));
|
||||
const deadline = Date.now() + Math.max(timings.waitMinMs, Math.floor(params.timeoutMs));
|
||||
|
||||
// Snapshot the currently active descendant run IDs.
|
||||
const getActiveRuns = () =>
|
||||
@@ -166,8 +170,8 @@ export async function waitForDescendantSubagentSummary(params: {
|
||||
// --- Grace period: wait for the cron agent's synthesis ---
|
||||
// After the subagent announces fire and the cron agent processes them, it
|
||||
// produces a new assistant message. Poll briefly (bounded by
|
||||
// CRON_SUBAGENT_FINAL_REPLY_GRACE_MS) to capture that synthesis.
|
||||
const gracePeriodDeadline = Math.min(Date.now() + CRON_SUBAGENT_FINAL_REPLY_GRACE_MS, deadline);
|
||||
// finalReplyGraceMs) to capture that synthesis.
|
||||
const gracePeriodDeadline = Math.min(Date.now() + timings.finalReplyGraceMs, deadline);
|
||||
|
||||
const resolveUsableLatestReply = async () => {
|
||||
const latest = (await readLatestAssistantReply({ sessionKey: params.sessionKey }))?.trim();
|
||||
@@ -186,7 +190,7 @@ export async function waitForDescendantSubagentSummary(params: {
|
||||
if (latest) {
|
||||
return latest;
|
||||
}
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, CRON_SUBAGENT_GRACE_POLL_MS));
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, timings.gracePollMs));
|
||||
}
|
||||
|
||||
// Final read after grace period expires.
|
||||
|
||||
@@ -50,6 +50,13 @@ describe("resolveProviderAuths key normalization", () => {
|
||||
|
||||
process.env.HOME = base;
|
||||
process.env.USERPROFILE = base;
|
||||
if (process.platform === "win32") {
|
||||
const match = base.match(/^([A-Za-z]:)(.*)$/);
|
||||
if (match) {
|
||||
process.env.HOMEDRIVE = match[1];
|
||||
process.env.HOMEPATH = match[2] || "\\";
|
||||
}
|
||||
}
|
||||
delete process.env.OPENCLAW_HOME;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(base, ".openclaw");
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
|
||||
@@ -73,10 +73,13 @@ describe("loadEnabledBundleMcpConfig", () => {
|
||||
cfg: config,
|
||||
});
|
||||
const resolvedServerPath = await fs.realpath(serverPath);
|
||||
const loadedServerPath = loaded.config.mcpServers.bundleProbe?.args?.[0];
|
||||
|
||||
expect(loaded.diagnostics).toEqual([]);
|
||||
expect(loaded.config.mcpServers.bundleProbe?.command).toBe("node");
|
||||
expect(loaded.config.mcpServers.bundleProbe?.args).toEqual([resolvedServerPath]);
|
||||
expect(loaded.config.mcpServers.bundleProbe?.args).toHaveLength(1);
|
||||
expect(loadedServerPath).toBeDefined();
|
||||
expect(await fs.realpath(loadedServerPath as string)).toBe(resolvedServerPath);
|
||||
} finally {
|
||||
env.restore();
|
||||
}
|
||||
|
||||
@@ -45,21 +45,22 @@ describe("marketplace plugins", () => {
|
||||
|
||||
const { listMarketplacePlugins } = await import("./marketplace.js");
|
||||
const result = await listMarketplacePlugins({ marketplace: rootDir });
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
sourceLabel: expect.stringContaining(".claude-plugin/marketplace.json"),
|
||||
manifest: {
|
||||
name: "Example Marketplace",
|
||||
version: "1.0.0",
|
||||
plugins: [
|
||||
{
|
||||
name: "frontend-design",
|
||||
version: "0.1.0",
|
||||
description: "Design system bundle",
|
||||
source: { kind: "path", path: "./plugins/frontend-design" },
|
||||
},
|
||||
],
|
||||
},
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
throw new Error("expected marketplace listing to succeed");
|
||||
}
|
||||
expect(result.sourceLabel.replaceAll("\\", "/")).toContain(".claude-plugin/marketplace.json");
|
||||
expect(result.manifest).toEqual({
|
||||
name: "Example Marketplace",
|
||||
version: "1.0.0",
|
||||
plugins: [
|
||||
{
|
||||
name: "frontend-design",
|
||||
version: "0.1.0",
|
||||
description: "Design system bundle",
|
||||
source: { kind: "path", path: "./plugins/frontend-design" },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user