Compare commits

..

1 Commits

Author SHA1 Message Date
Dallin Romney
6d8a2a313f docs(changelog): keep package notes per version 2026-05-29 11:23:32 -07:00
2481 changed files with 24738 additions and 107852 deletions

View File

@@ -100,10 +100,6 @@ Format first if formatting can change line locations. Then it is OK to run tests
scripts/autoreview --parallel-tests "<focused test command>"
```
On Windows, the default `--parallel-tests` shell preserves the platform `cmd.exe`
semantics used by Python `shell=True`. Use `--parallel-tests-shell powershell`
or `--parallel-tests-shell pwsh` when the focused test command is PowerShell-specific.
Tradeoff: tests may force code changes that stale the review. If tests or review lead to code edits, rerun the affected tests and rerun review until no accepted/actionable findings remain. Once that rerun exits cleanly, stop; do not spend another long review cycle on redundant confirmation.
## Review Panels
@@ -148,22 +144,6 @@ OpenClaw repo-local helper:
.agents/skills/autoreview/scripts/autoreview --help
```
On native Windows, invoke the extensionless Python helper through Python:
```powershell
python .agents\skills\autoreview\scripts\autoreview --help
```
The smoke harness has thin shell wrappers over a shared Python implementation:
```bash
.agents/skills/autoreview/scripts/test-review-harness --fixture benign --engine codex
```
```powershell
.agents\skills\autoreview\scripts\test-review-harness.ps1 -Fixture benign -Engine codex
```
`agent-scripts` checkout helper:
```bash
@@ -189,11 +169,10 @@ The helper:
- otherwise uses current PR base if `gh pr view` works
- otherwise uses `origin/main` for non-main branches
- supports `--engine codex`, `claude`, `droid`, and `copilot`; default is `AUTOREVIEW_ENGINE` or `codex`; Codex should remain the default when nothing is set
- resolves bare `git`, `gh`, reviewer, and PowerShell shell commands from absolute `PATH` entries only, never from the reviewed checkout; explicit relative `--*-bin` paths are resolved from the reviewed repository root
- use `--mode commit --commit <ref>` for already-committed work, especially clean `main` after landing
- should be left in `--mode auto` or forced to `--mode branch` for PR/branch work; do not force `--mode local` after committing
- writes only to stdout unless `--output`, `--json-output`, or live streamed engine stderr is set
- supports `--dry-run`, `--parallel-tests`, `--parallel-tests-shell`, `--prompt`, `--prompt-file`, `--dataset`, `--no-tools`, `--no-web-search`, and commit refs
- supports `--dry-run`, `--parallel-tests`, `--prompt`, `--prompt-file`, `--dataset`, `--no-tools`, `--no-web-search`, and commit refs
- supports `--stream-engine-output` or `AUTOREVIEW_STREAM_ENGINE_OUTPUT=1` for live engine text while preserving structured validation; Codex and Claude hide tool/file event details, emit compact activity summaries, and report usage at turn completion
- supports opt-in review panels with `--panel` / `--reviewers`, plus per-engine `--model` and `--thinking`
- allows read-only tools and web search by default where the selected CLI supports them; forbids nested review in the prompt; Codex is run through `codex exec` with read-only sandbox and structured output

View File

@@ -214,17 +214,12 @@ def run_with_stream(
def git(repo: Path, *args: str, check: bool = True) -> str:
return run([resolve_command("git", repo), *args], repo, check=check).stdout
return run(["git", *args], repo, check=check).stdout
def repo_root() -> Path:
start = Path.cwd().resolve()
unsafe_root = discover_repo_root(start) or start
git_bin = find_command("git", unsafe_root)
if not git_bin:
raise SystemExit("git executable not found. Install Git or add it to PATH.")
result = subprocess.run(
[git_bin, "rev-parse", "--show-toplevel"],
["git", "rev-parse", "--show-toplevel"],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -234,16 +229,6 @@ def repo_root() -> Path:
return Path(result.stdout.strip()).resolve()
def discover_repo_root(start: Path) -> Path | None:
current = start
while True:
if (current / ".git").exists():
return current
if current.parent == current:
return None
current = current.parent
def current_branch(repo: Path) -> str:
return git(repo, "branch", "--show-current", check=False).strip() or "detached"
@@ -265,70 +250,17 @@ def choose_target(repo: Path, mode: str, base_ref: str | None) -> tuple[str, str
def detect_pr_base(repo: Path) -> str | None:
gh_bin = find_command("gh", repo)
if not gh_bin:
if not shutil_which("gh"):
return None
result = run([gh_bin, "pr", "view", "--json", "baseRefName", "--jq", ".baseRefName"], repo, check=False)
result = run(["gh", "pr", "view", "--json", "baseRefName", "--jq", ".baseRefName"], repo, check=False)
base = result.stdout.strip()
return f"origin/{base}" if result.returncode == 0 and base else None
def resolve_command(name: str, repo: Path) -> str:
resolved = find_command(name, repo)
if resolved:
return resolved
raise SystemExit(f"executable not found: {name}. Install it or pass an explicit trusted path when supported.")
def find_command(name: str, repo: Path) -> str | None:
command = Path(name)
if has_directory_component(name, command):
base = command if command.is_absolute() else repo / command
return first_executable_candidate(base)
def shutil_which(name: str) -> str | None:
for part in os.environ.get("PATH", "").split(os.pathsep):
if not part or part == ".":
continue
path_part = Path(part)
if not path_part.is_absolute():
continue
try:
resolved_part = path_part.resolve()
resolved_repo = repo.resolve()
except OSError:
continue
if is_within(resolved_part, resolved_repo):
continue
found = first_executable_candidate(resolved_part / name, reject_root=resolved_repo)
if found:
return found
return None
def is_within(path: Path, root: Path) -> bool:
return path == root or path.is_relative_to(root)
def has_directory_component(name: str, command: Path) -> bool:
separators = [separator for separator in (os.sep, os.altsep) if separator]
return command.is_absolute() or bool(command.drive) or any(separator in name for separator in separators)
def first_executable_candidate(path: Path, *, reject_root: Path | None = None) -> str | None:
if os.name == "nt" and not path.suffix:
extensions = [ext for ext in os.environ.get("PATHEXT", ".COM;.EXE;.BAT;.CMD").split(";") if ext]
candidates = [path.with_suffix(ext.lower()) for ext in extensions]
candidates.extend(path.with_suffix(ext.upper()) for ext in extensions)
candidates.append(path)
else:
candidates = [path]
for candidate in candidates:
if candidate.is_file() and os.access(candidate, os.X_OK):
if reject_root is not None:
try:
if is_within(candidate.resolve(), reject_root):
continue
except OSError:
continue
candidate = Path(part) / name
if candidate.exists() and os.access(candidate, os.X_OK):
return str(candidate)
return None
@@ -487,7 +419,7 @@ def run_codex(args: argparse.Namespace, repo: Path, prompt: str) -> str:
raise SystemExit("--no-tools is not supported by the Codex engine; use --engine claude --no-tools for a no-tools run")
schema_path = write_json_temp(SCHEMA)
output_path = Path(tempfile.NamedTemporaryFile("w", suffix=".json", delete=False).name)
cmd = [resolve_command(args.codex_bin, repo), "--ask-for-approval", "never"]
cmd = [args.codex_bin, "--ask-for-approval", "never"]
if args.web_search:
cmd.append("--search")
if args.model:
@@ -531,7 +463,7 @@ def run_codex(args: argparse.Namespace, repo: Path, prompt: str) -> str:
def run_claude(args: argparse.Namespace, repo: Path, prompt: str) -> str:
cmd = [
resolve_command(args.claude_bin, repo),
args.claude_bin,
"--print",
"--no-session-persistence",
"--output-format",
@@ -568,7 +500,7 @@ def run_droid(args: argparse.Namespace, repo: Path, prompt: str) -> str:
prompt_path = Path(tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False).name)
prompt_path.write_text(prompt)
cmd = [
resolve_command(args.droid_bin, repo),
args.droid_bin,
"exec",
"--cwd",
str(repo),
@@ -598,7 +530,7 @@ def run_copilot(args: argparse.Namespace, repo: Path, prompt: str) -> str:
prompt_path.write_text(prompt)
os.chmod(prompt_path, 0o600)
cmd = [
resolve_command(args.copilot_bin, repo),
args.copilot_bin,
"-C",
tempdir,
"-p",
@@ -945,23 +877,9 @@ def print_report(report: dict[str, Any], *, label: str = "autoreview") -> None:
print(report["overall_explanation"])
def start_parallel_tests(command: str, repo: Path, shell_kind: str) -> tuple[subprocess.Popen, float]:
def start_parallel_tests(command: str, repo: Path) -> tuple[subprocess.Popen, float]:
print(f"tests: {command}")
if shell_kind == "default" or shell_kind == "cmd":
return subprocess.Popen(command, cwd=repo, shell=True), time.time()
if shell_kind == "powershell":
powershell = resolve_command("powershell", repo)
return subprocess.Popen(
[powershell, "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command],
cwd=repo,
), time.time()
if shell_kind == "pwsh":
pwsh = resolve_command("pwsh", repo)
return subprocess.Popen(
[pwsh, "-NoProfile", "-Command", command],
cwd=repo,
), time.time()
raise SystemExit(f"invalid --parallel-tests-shell/AUTOREVIEW_PARALLEL_TESTS_SHELL: {shell_kind}")
return subprocess.Popen(command, cwd=repo, shell=True), time.time()
def finish_parallel_tests(proc: subprocess.Popen, started: float) -> int:
@@ -1006,12 +924,6 @@ def parse_args() -> argparse.Namespace:
help="Stream review engine output while preserving buffered output for validation. Codex output is filtered to hide tool/file chatter.",
)
parser.add_argument("--parallel-tests", help="Run a test command concurrently with review; failure fails the helper.")
parser.add_argument(
"--parallel-tests-shell",
choices=["default", "cmd", "powershell", "pwsh"],
default=os.environ.get("AUTOREVIEW_PARALLEL_TESTS_SHELL", "default"),
help="Shell for --parallel-tests. Default preserves Python shell=True platform behavior; use powershell or pwsh for PowerShell-specific commands.",
)
parser.add_argument("--require-finding", action="append", default=[], help="Require finding text to contain this substring.")
parser.add_argument("--expect-findings", action="store_true", help="Treat findings as success; for harness acceptance tests.")
parser.add_argument("--dry-run", action="store_true")
@@ -1217,7 +1129,7 @@ def main() -> int:
tests_proc: tuple[subprocess.Popen, float] | None = None
if args.parallel_tests:
tests_proc = start_parallel_tests(args.parallel_tests, repo, args.parallel_tests_shell)
tests_proc = start_parallel_tests(args.parallel_tests, repo)
try:
if len(reviewers) == 1:
report = run_reviewer(reviewers[0], repo, prompt, changed_paths, args.require_finding)

View File

@@ -1,16 +1,176 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: test-review-harness [--fixture malicious|benign] [--engine codex|claude|droid|copilot]...
Creates a temporary git repo with either a deliberately unsafe patch or a
security-sensitive-but-safe patch, then verifies each selected engine through
autoreview.
Default engines: codex, claude.
EOF
}
engines=()
fixture=malicious
while [[ $# -gt 0 ]]; do
case "$1" in
--fixture)
fixture=${2:-}
shift 2
;;
--engine)
engines+=("${2:-}")
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
usage >&2
exit 2
;;
esac
done
case "$fixture" in
malicious|benign) ;;
*)
usage >&2
exit 2
;;
esac
if [[ ${#engines[@]} -eq 0 ]]; then
engines=(codex claude)
fi
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
harness="$script_dir/test-review-harness.py"
repo=$(mktemp -d "${TMPDIR:-/tmp}/autoreview-fixture.XXXXXX")
trap 'rm -rf "$repo"' EXIT
if command -v python3 >/dev/null 2>&1; then
exec python3 "$harness" "$@"
cd "$repo"
git init --quiet
git config user.name "Review Fixture"
git config user.email "review-fixture@example.com"
if [[ "$fixture" == malicious ]]; then
cat > app.js <<'EOF'
export function uploadPath(name) {
return `uploads/${name.replaceAll("/", "")}`;
}
export function publicUser(user) {
return { id: user.id, name: user.name };
}
EOF
else
cat > app.js <<'EOF'
import path from "node:path";
const uploadsRoot = path.resolve("uploads");
function safeChildPath(root, name) {
if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
throw new Error("invalid child name");
}
const resolved = path.resolve(root, name);
if (!resolved.startsWith(root + path.sep)) throw new Error("path escaped root");
return resolved;
}
export function uploadPath(name) {
return safeChildPath(uploadsRoot, name);
}
export function publicUser(user) {
return { id: user.id, name: user.name };
}
EOF
fi
if command -v python >/dev/null 2>&1; then
exec python "$harness" "$@"
git add app.js
git commit --quiet -m "initial safe version"
if [[ "$fixture" == malicious ]]; then
cat > app.js <<'EOF'
import { execSync } from "node:child_process";
export function uploadPath(name) {
return `uploads/${name}`;
}
export function deleteUpload(name) {
return execSync(`rm -rf uploads/${name}`);
}
export function publicUser(user) {
return { id: user.id, name: user.name, password: user.password };
}
EOF
else
cat > app.js <<'EOF'
import { execFile } from "node:child_process";
import path from "node:path";
import { promisify } from "node:util";
const uploadsRoot = path.resolve("uploads");
const reposRoot = path.resolve("repos");
const execFileAsync = promisify(execFile);
function safeChildPath(root, name) {
if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
throw new Error("invalid child name");
}
const resolved = path.resolve(root, name);
if (!resolved.startsWith(root + path.sep)) throw new Error("path escaped root");
return resolved;
}
function repoChildPath(root, name) {
if (!/^[A-Za-z0-9._-]+$/.test(name)) throw new Error("invalid repo name");
return safeChildPath(root, name);
}
export function uploadPath(name) {
return safeChildPath(uploadsRoot, name);
}
export async function repoStatus(repoName) {
const { stdout } = await execFileAsync("git", ["status", "--short"], {
cwd: repoChildPath(reposRoot, repoName),
encoding: "utf8",
maxBuffer: 16 * 1024 * 1024,
});
return stdout;
}
export function publicUser(user) {
return { id: user.id, name: user.name };
}
export function accountSettingsForOwner(user, requesterId) {
if (String(requesterId) !== String(user.id)) throw new Error("forbidden");
return { id: user.id, hasPassword: Boolean(user.passwordHash) };
}
EOF
fi
echo "Python 3 is required to run test-review-harness." >&2
exit 127
for engine in "${engines[@]}"; do
echo "== $engine =="
if [[ "$fixture" == malicious ]]; then
"$script_dir/autoreview" \
--mode local \
--engine "$engine" \
--prompt "This is an acceptance test fixture. The changed app.js patch contains real security bugs. Review normally and report only actionable defects from the patch." \
--require-finding "command" \
--expect-findings
else
"$script_dir/autoreview" \
--mode local \
--engine "$engine" \
--prompt "Security calibration fixture: this patch intentionally uses filesystem paths, async execFile, and owner-gated password-adjacent state safely. Do not flag legitimate shell/filesystem/auth-adjacent functionality unless there is a concrete exploitable risk in the diff."
fi
done

View File

@@ -1,45 +0,0 @@
[CmdletBinding()]
param(
[ValidateSet('malicious', 'benign')]
[string] $Fixture,
[ValidateSet('codex', 'claude', 'droid', 'copilot')]
[string[]] $Engine,
[Alias('h')]
[switch] $Help
)
$ErrorActionPreference = 'Stop'
$Harness = Join-Path $PSScriptRoot 'test-review-harness.py'
$ForwardedArgs = @()
if ($Help) {
$ForwardedArgs += '--help'
}
if ($PSBoundParameters.ContainsKey('Fixture')) {
$ForwardedArgs += @('--fixture', $Fixture)
}
if ($PSBoundParameters.ContainsKey('Engine')) {
foreach ($SelectedEngine in $Engine) {
$ForwardedArgs += @('--engine', $SelectedEngine)
}
}
$PyLauncher = Get-Command py -ErrorAction SilentlyContinue
if ($null -ne $PyLauncher) {
& $PyLauncher.Source -3 $Harness @ForwardedArgs
exit $LASTEXITCODE
}
$Python = Get-Command python -ErrorAction SilentlyContinue
if ($null -ne $Python) {
& $Python.Source $Harness @ForwardedArgs
exit $LASTEXITCODE
}
Write-Error 'Python 3 is required to run test-review-harness.'
exit 127

View File

@@ -1,199 +0,0 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import os
import shutil
import stat
import subprocess
import sys
import tempfile
from collections.abc import Callable
from pathlib import Path
ENGINES = ("codex", "claude", "droid", "copilot")
DEFAULT_ENGINES = ("codex", "claude")
MALICIOUS_INITIAL = """export function uploadPath(name) {
return `uploads/${name.replaceAll("/", "")}`;
}
export function publicUser(user) {
return { id: user.id, name: user.name };
}
"""
BENIGN_INITIAL = r"""import path from "node:path";
const uploadsRoot = path.resolve("uploads");
function safeChildPath(root, name) {
if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
throw new Error("invalid child name");
}
const resolved = path.resolve(root, name);
if (!resolved.startsWith(root + path.sep)) throw new Error("path escaped root");
return resolved;
}
export function uploadPath(name) {
return safeChildPath(uploadsRoot, name);
}
export function publicUser(user) {
return { id: user.id, name: user.name };
}
"""
MALICIOUS_CHANGED = """import { execSync } from "node:child_process";
export function uploadPath(name) {
return `uploads/${name}`;
}
export function deleteUpload(name) {
return execSync(`rm -rf uploads/${name}`);
}
export function publicUser(user) {
return { id: user.id, name: user.name, password: user.password };
}
"""
BENIGN_CHANGED = r"""import { execFile } from "node:child_process";
import path from "node:path";
import { promisify } from "node:util";
const uploadsRoot = path.resolve("uploads");
const reposRoot = path.resolve("repos");
const execFileAsync = promisify(execFile);
function safeChildPath(root, name) {
if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
throw new Error("invalid child name");
}
const resolved = path.resolve(root, name);
if (!resolved.startsWith(root + path.sep)) throw new Error("path escaped root");
return resolved;
}
function repoChildPath(root, name) {
if (!/^[A-Za-z0-9._-]+$/.test(name)) throw new Error("invalid repo name");
return safeChildPath(root, name);
}
export function uploadPath(name) {
return safeChildPath(uploadsRoot, name);
}
export async function repoProbe(repoName) {
const { stdout } = await execFileAsync(process.execPath, ["--version"], {
cwd: repoChildPath(reposRoot, repoName),
encoding: "utf8",
maxBuffer: 16 * 1024 * 1024,
});
return stdout;
}
export function publicUser(user) {
return { id: user.id, name: user.name };
}
export function accountSettingsForOwner(user, requesterId) {
if (String(requesterId) !== String(user.id)) throw new Error("forbidden");
return { id: user.id, hasPassword: Boolean(user.passwordHash) };
}
"""
MALICIOUS_PROMPT = "This is an acceptance test fixture. The changed app.js patch contains real security bugs. Review normally and report only actionable defects from the patch."
BENIGN_PROMPT = "Security calibration fixture: this patch intentionally uses filesystem paths, async execFile, and owner-gated password-adjacent state safely. Do not flag legitimate shell/filesystem/auth-adjacent functionality unless there is a concrete exploitable risk in the diff."
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="test-review-harness",
description=(
"Creates a temporary git repo with either a deliberately unsafe patch "
"or a security-sensitive-but-safe patch, then verifies each selected "
"engine through autoreview."
),
epilog="Default engines: codex, claude.",
)
parser.add_argument("--fixture", choices=("malicious", "benign"), default="malicious")
parser.add_argument("--engine", action="append", choices=ENGINES, dest="engines")
return parser.parse_args(argv)
def write_fixture_file(repo: Path, content: str) -> None:
with (repo / "app.js").open("w", encoding="utf-8", newline="\n") as handle:
handle.write(content)
def run(command: list[str], cwd: Path) -> None:
subprocess.run(command, cwd=cwd, check=True)
def create_fixture_repo(repo: Path, fixture: str) -> None:
run(["git", "init", "--quiet"], repo)
run(["git", "config", "user.name", "Review Fixture"], repo)
run(["git", "config", "user.email", "review-fixture@example.com"], repo)
write_fixture_file(repo, MALICIOUS_INITIAL if fixture == "malicious" else BENIGN_INITIAL)
run(["git", "add", "app.js"], repo)
run(["git", "commit", "--quiet", "-m", "initial safe version"], repo)
write_fixture_file(repo, MALICIOUS_CHANGED if fixture == "malicious" else BENIGN_CHANGED)
def run_reviews(repo: Path, script_dir: Path, fixture: str, engines: list[str]) -> None:
autoreview = script_dir / "autoreview"
for engine in engines:
print(f"== {engine} ==", flush=True)
command = [
sys.executable,
str(autoreview),
"--mode",
"local",
"--engine",
engine,
"--prompt",
MALICIOUS_PROMPT if fixture == "malicious" else BENIGN_PROMPT,
]
if fixture == "malicious":
command.extend(["--require-finding", "command", "--expect-findings"])
run(command, repo)
def cleanup_repo(repo: Path) -> None:
def make_writable_and_retry(function: Callable[[str], object], path: str, _exc_info: object) -> None:
try:
os.chmod(path, stat.S_IREAD | stat.S_IWRITE)
function(path)
except OSError as exc:
print(f"warning: unable to remove temp path {path}: {exc}", file=sys.stderr)
if not repo.exists():
return
try:
shutil.rmtree(repo, onerror=make_writable_and_retry)
except OSError as exc:
print(f"warning: unable to remove temp repo {repo}: {exc}", file=sys.stderr)
def main(argv: list[str]) -> int:
args = parse_args(argv)
script_dir = Path(__file__).resolve().parent
engines = args.engines or list(DEFAULT_ENGINES)
repo = Path(tempfile.mkdtemp(prefix="autoreview-fixture."))
try:
create_fixture_repo(repo, args.fixture)
run_reviews(repo, script_dir, args.fixture, engines)
except subprocess.CalledProcessError as exc:
return int(exc.returncode or 1)
finally:
cleanup_repo(repo)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))

View File

@@ -1,6 +1,6 @@
---
name: discrawl
description: "Discord archive: search, sync freshness, DMs, summaries, TUI, repo/release work."
description: "Discord archive: search, sync freshness, DMs, channel slices, SQL counts, and Discrawl repo work."
metadata:
openclaw:
homepage: https://github.com/openclaw/discrawl
@@ -16,154 +16,29 @@ metadata:
# Discrawl
Use local Discord archive data first for Discord questions. Hit Discord APIs
only when the archive is stale, missing the requested scope, or the user asks
for current external context.
## Sources
- DB: platform-native XDG data dir, usually
`${XDG_DATA_HOME:-~/.local/share}/discrawl/discrawl.db` on Linux or
`~/Library/Application Support/discrawl/discrawl.db` on macOS
- Config: platform-native XDG config dir, with legacy fallback to
`~/.discrawl/config.toml`
- Cache: platform-native XDG cache dir
- Logs: platform-native XDG state dir
- Git share repo: platform-native XDG data dir
- Repo: `openclaw/discrawl`; use `~/GIT/_Perso/discrawl` only after verifying
its remote targets `openclaw/discrawl`, otherwise use a fresh checkout
- Preferred CLI: `discrawl`; fallback to `go run ./cmd/discrawl` from the repo
if the installed binary is stale
## Freshness
For recent/current questions, check freshness before analysis:
Use local Discord archive data before live Discord APIs. Check freshness for recent/current questions:
```bash
discrawl status --json
```
For precise freshness from the default database:
```bash
# Discrawl uses macOS ~/Library defaults unless XDG_DATA_HOME is explicitly set.
case "$(uname -s)" in
Darwin)
db="$HOME/Library/Application Support/discrawl/discrawl.db"
;;
*)
db="${XDG_DATA_HOME:-$HOME/.local/share}/discrawl/discrawl.db"
;;
esac
sqlite3 "$db" \
"select coalesce(max(updated_at),'') from sync_state where scope like 'channel:%';"
```
Routine diagnostics:
```bash
discrawl doctor
```
Desktop-local refresh:
Refresh only when stale or asked:
```bash
discrawl sync --source wiretap
```
Bot API latest refresh, when credentials are available:
```bash
discrawl sync
```
Use `--full` only for deliberate historical backfills:
```bash
discrawl sync --full
```
If SQLite reports busy/locked, check for stray `discrawl` processes before retrying.
## Query Workflow
1. Resolve scope: guild, channel, DM, author, keyword, date range.
2. Check freshness for recent/current requests.
3. Prefer CLI search/messages for slices; use read-only SQL for exact counts.
4. Report absolute date spans, counts, channel/DM names, and known gaps.
Use root or subcommand help for syntax: `discrawl --help`,
`discrawl help search`, `discrawl search --help`. Use
`DISCRAWL_NO_AUTO_UPDATE=1` for read smokes when you do not want git-share
updates.
Common commands:
Query with bounded slices:
```bash
DISCRAWL_NO_AUTO_UPDATE=1 discrawl search --limit 20 "query"
discrawl messages --channel '#maintainers' --days 7 --all
discrawl dms --last 20
discrawl tui --dm
DISCRAWL_NO_AUTO_UPDATE=1 discrawl --json sql "select count(*) from messages;"
```
## SQL
Report absolute date spans, channel/DM names, counts, and known gaps. Use read-only SQL for exact counts/rankings. Never use `--unsafe --confirm` unless the user explicitly requests a reviewed DB mutation.
Use `discrawl sql` for exact counts, joins, and ranking queries when normal
CLI reads are too coarse. The command is read-only by default, accepts SQL as
args or stdin, and supports `--json` for agent parsing.
Useful examples:
```bash
DISCRAWL_NO_AUTO_UPDATE=1 discrawl --json sql "select count(*) as messages from messages;"
DISCRAWL_NO_AUTO_UPDATE=1 discrawl --json sql "select coalesce(nullif(c.name, ''), m.channel_id) as channel, count(*) as messages from messages m left join channels c on c.id = m.channel_id group by m.channel_id order by messages desc limit 20;"
DISCRAWL_NO_AUTO_UPDATE=1 discrawl --json sql "select coalesce(nullif(mm.display_name, ''), nullif(mm.global_name, ''), nullif(mm.username, ''), m.author_id) as author, count(*) as messages from messages m left join members mm on mm.guild_id = m.guild_id and mm.user_id = m.author_id group by m.guild_id, m.author_id order by messages desc limit 20;"
```
Never use `--unsafe --confirm` unless the user explicitly asks for a database
mutation and the write has been reviewed.
When the installed CLI lacks a new feature, build or run from a verified
`openclaw/discrawl` checkout before concluding the feature is missing.
## Discord Boundaries
Bot API sync requires configured Discord bot credentials; do not invent token
availability. Desktop wiretap mode reads local Discord Desktop artifacts and
must not extract credentials, use user tokens, call Discord as the user, or
write to Discord application storage. Wiretap/Desktop cache DMs are local-only
and must not be described as part of the published Git snapshot. Git-share
snapshots must not include secrets or `@me` DM rows.
## Verification
For repo edits, prefer existing Go gates:
```bash
GOWORK=off go test ./...
```
Then run targeted CLI smoke for the touched surface, for example:
```bash
discrawl doctor
discrawl status --json
DISCRAWL_NO_AUTO_UPDATE=1 discrawl search --limit 5 "test"
```
## ClawSweeper Sandbox
Use the sandbox reader only:
```bash
discrawl-sandbox search --limit 20 "query"
discrawl-sandbox messages --channel clawtributors --days 7 --all
discrawl-sandbox status --json
```
This reader imports `https://github.com/openclaw/discord-store.git` into
`/root/clawsweeper-sandbox-workspace/.discrawl/discrawl.db` with
`discord.token_source = "none"`. The published Git snapshot is public-channel
filtered; do not use `/root/.discrawl/config.toml` or the rich writer DB from
sandboxed public Discord sessions.
Boundaries: bot sync needs configured Discord bot credentials. Wiretap reads local Discord Desktop artifacts only; do not extract user tokens, call Discord as the user, or write to Discord storage. Git-share snapshots must not include secrets or `@me` DM rows.

View File

@@ -6,16 +6,15 @@ description: Regenerate OpenClaw release changelog sections from git history bef
# OpenClaw Changelog Update
Use this for release changelog rewrites and GitHub release-note source text.
This is mandatory before every beta, beta rerun, stable release, or stable
rerun. Use it with `release-openclaw-maintainer`; this skill owns changelog
content, ordering, grouping, and attribution discipline.
Use it with `release-openclaw-maintainer`; this skill owns changelog content,
ordering, and audit discipline.
## Goal
Rewrite the target `CHANGELOG.md` version section from history, not from stale
draft notes. Produce grouped user-facing release notes sorted by user interest
while preserving every relevant issue/PR ref and every human `Thanks @...`
attribution.
Rewrite `CHANGELOG.md` as current release notes only. Use history rather than
stale draft notes. Produce a per-version file whose dated section more or less
matches the shape of prior release sections, sorted by user interest while
preserving issue/PR refs and human thanks.
## Inputs
@@ -36,28 +35,21 @@ attribution.
- also inspect `--since='24 hours ago'` when main moved during the release.
3. Read linked PRs/issues or diffs for ambiguous commits. Direct commits matter;
infer notes from subject, body, touched files, tests, and nearby commits.
4. Rewrite one stable-base section only:
4. Rewrite `CHANGELOG.md` as current release notes:
- preserve the top `# Changelog` title and docs link
- do not keep `## Unreleased`; release notes are regenerated from history
- use `## YYYY.M.D`
- do not create beta-specific headings
- do not leave a stale `## Unreleased` section above the target release
- if `Unreleased` contains release-bound notes, fold them into the target
section instead of deleting them
- remove older `## YYYY.M.D` sections from the file
- keep beta release notes under the stable base heading
5. Section shape:
- `### Highlights`: 5-8 bullets, broad user wins first
- `### Changes`: new capabilities and behavior changes
- `### Fixes`: user-facing fixes first, grouped by impact and surface
- group related changes/fixes by surface and user impact; avoid one bullet
per tiny commit when several commits tell one user-facing story
6. Preserve attribution:
- keep `#issue`, `(#PR)`, `Fixes #...`, and `Thanks @...`
- every human-authored merged PR represented by a user-facing entry needs
its PR ref and `Thanks @author`, even when the PR had no linked issue
- when grouping multiple PRs/issues in one bullet, include every relevant
PR/issue ref and every human contributor handle in that same bullet
- multiple `Thanks @...` handles in one bullet are expected; do not drop or
collapse contributor credit just because the note is grouped
- if one grouped bullet covers both direct commits and PRs, keep all PR refs
and thanks, plus any issue refs from the direct commits
- do not add GHSA references, advisory IDs, or security advisory slugs to
changelog entries or GitHub release-note text unless explicitly requested
- never thank bots, `@openclaw`, `@clawsweeper`, or `@steipete`

View File

@@ -75,9 +75,7 @@ OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test <path-or-filter>
```
Use targeted file paths whenever possible. Avoid raw `vitest`; use the repo
`pnpm test` wrapper so project routing, workers, and setup stay correct. If raw
Vitest is unavoidable, use `vitest run ...`; bare `vitest ...` starts local watch
mode and will not exit on its own.
`pnpm test` wrapper so project routing, workers, and setup stay correct.
When the checkout is a Codex worktree, prefer the direct node harness instead:
```bash

View File

@@ -21,30 +21,6 @@ function jsonGh(args) {
return JSON.parse(gh(args));
}
function githubRestJson(pathSuffix) {
const result = execFileSync(
"bash",
[
"-lc",
[
"set -euo pipefail",
'token="$(gh auth token)"',
'curl -fsS -H "Authorization: Bearer ${token}" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "${OPENCLAW_GITHUB_REST_URL}"',
].join("\n"),
],
{
encoding: "utf8",
env: {
...process.env,
OPENCLAW_GITHUB_REST_URL: `https://api.github.com/repos/${repo}/${pathSuffix}`,
},
maxBuffer: 16 * 1024 * 1024,
stdio: ["ignore", "pipe", "pipe"],
},
);
return JSON.parse(result);
}
function rate() {
try {
return jsonGh(["api", "rate_limit"]).resources.core;
@@ -83,30 +59,12 @@ for (const job of parent.jobs ?? []) {
}
const since = parent.createdAt;
const runsQuery = new URLSearchParams({
per_page: "100",
created: `>=${since}`,
exclude_pull_requests: "true",
});
const childWorkflowNames = new Set([
"CI",
"OpenClaw Release Checks",
"Plugin Prerelease",
"NPM Telegram Beta E2E",
"Full Release Validation",
]);
const runs = githubRestJson(`actions/runs?${runsQuery.toString()}`).workflow_runs ?? [];
const runList = runs
.filter(
(run) =>
run.created_at >= since &&
run.head_sha === parent.headSha &&
childWorkflowNames.has(run.name),
)
.map((run) =>
[run.id, run.name, run.status, run.conclusion ?? "", run.head_sha, run.html_url].join("\t"),
)
.join("\n");
const runList = gh([
"api",
`repos/${repo}/actions/runs?per_page=100`,
"--jq",
`.workflow_runs[] | select(.created_at >= "${since}") | select(.name=="CI" or .name=="OpenClaw Release Checks" or .name=="Plugin Prerelease" or .name=="NPM Telegram Beta E2E" or .name=="Full Release Validation") | [.id,.name,.status,.conclusion,.head_sha,.html_url] | @tsv`,
]).trim();
if (!runList) {
console.log("children: none found yet");

View File

@@ -23,9 +23,10 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
green. Then branch from that commit so regular development can continue on
`main` while release validation runs.
- Before release branching, commit any dirty files in coherent groups, push,
pull/rebase, then generate `CHANGELOG.md` on `main` from merged PRs and all
direct commits since the last reachable release tag. Commit/push/pull that
changelog rewrite immediately before creating the release branch.
pull/rebase, then generate the per-version `CHANGELOG.md` on `main` from
merged PRs and all direct commits since the last reachable release tag.
Commit/push/pull that changelog rewrite immediately before creating the
release branch.
- During release planning, inspect both `src/plugins/compat/registry.ts` and
`src/commands/doctor/shared/deprecation-compat.ts` before branching and again
before final publish. For every deprecated or removal-pending compatibility
@@ -69,13 +70,9 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
or clawgrit reports. Report regressions explicitly. A major regression is a
release blocker unless the operator waives it or the data clearly proves
infrastructure noise.
- Generate the changelog before every beta, beta rerun, stable release, or
stable rerun, before version/tag preparation. Use
`$openclaw-changelog-update` for the rewrite. Do not continue release prep if
the target `CHANGELOG.md` section does not have `### Highlights`,
`### Changes`, and `### Fixes`, grouped by user-facing surface while
preserving every relevant PR/issue ref and every human `Thanks @...`
attribution in the grouped bullet.
- Generate the changelog before version/tag preparation so the top changelog
section is deduped and ordered by user impact. Use
`$openclaw-changelog-update` for the rewrite.
- Do not create beta-specific `CHANGELOG.md` headings. Beta releases use the
stable base version section, for example `v2026.4.20-beta.1` uses
`## 2026.4.20` release notes.
@@ -144,13 +141,11 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
- `CHANGELOG.md` is release-owned. Normal PRs and direct `main` fixes should
not edit it.
- Before release branching or tagging, rewrite the target `CHANGELOG.md`
section from history, not existing notes. Use the last reachable stable or
beta release tag as the base, then inspect every commit through the target
- `CHANGELOG.md` contains the current stable-base release section only.
- Before release branching or tagging, rewrite `CHANGELOG.md` as current
release notes from history, not existing notes. Use the last reachable stable
or beta release tag as the base, then inspect every commit through the target
release SHA.
- The changelog rewrite is not optional for beta reruns: any `beta.N` after a
rebase or backport must refresh the same stable-base `## YYYY.M.D` section
before the new version/tag commit.
- Include both merged PR commits and direct commits on `main`. Direct commits
matter: infer notes from their subject, body, touched files, linked issues,
tests, and nearby code when no PR body exists.
@@ -164,11 +159,6 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
- Add missed user-facing changes, remove internal-only noise, dedupe overlapping
PR/direct-commit entries, and sort each section from most to least interesting
for users.
- Group related highlights, changes, and fixes by user-facing surface and
impact, but never lose traceability: each grouped bullet keeps every relevant
`#issue`, `(#PR)`, `Fixes #...`, and every human `Thanks @...` handle.
Multiple thanks in one bullet are expected when multiple contributor PRs are
grouped.
- Changelog entries should be user-facing, not internal release-process notes.
- GitHub release and prerelease bodies must use the full matching
`CHANGELOG.md` version section, not highlights or an excerpt. When creating
@@ -187,9 +177,10 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
- use release notes from the stable base `CHANGELOG.md` version section
(`## YYYY.M.D`), not a beta-specific heading
- attach at least the zip and dSYM zip, plus dmg if available
- Keep the top version entries in `CHANGELOG.md` sorted by impact:
- `### Changes` first
- `### Fixes` deduped with user-facing fixes first
- Keep the current version section in `CHANGELOG.md` sorted by impact:
- `### Highlights` first with broad user wins
- `### Changes` next for capabilities and behavior changes
- `### Fixes` last, deduped with user-facing fixes first
## Write release tweets
@@ -203,7 +194,7 @@ live`; keep it clearly beta and avoid implying stable promotion.
- Lead with user-visible capabilities, then important integrations, then
reliability/security/install fixes. Compress "lots of fixes" into one
readable bullet.
- Read the full changelog section before drafting. Do not lead with coverage,
- Read the matching changelog section before drafting. Do not lead with coverage,
CI, validation, or internal release mechanics unless the release is explicitly
about those. Peter prefers concrete user wins: features, integrations,
workflow improvements, and practical reliability fixes.
@@ -572,8 +563,9 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
worktree is clean.
4. Pull latest `main` and confirm current `main` CI is green.
5. Run `/changelog` for the stable base target version on `main`, commit the
changelog rewrite immediately, push, and pull/rebase. For beta releases,
keep the changelog heading as `## YYYY.M.D`, not `## YYYY.M.D-beta.N`.
per-version changelog rewrite immediately, push, and pull/rebase. For beta
releases, keep the changelog heading as `## YYYY.M.D`, not
`## YYYY.M.D-beta.N`.
6. Create `release/YYYY.M.D` from that post-changelog `main` commit.
7. Make every repo version location match the beta tag before creating it.
8. Commit release preparation changes on the release branch and push the branch.

View File

@@ -1,15 +1,21 @@
profile: openclaw-check
# Default OpenClaw runner spend to the Azure-backed Crabbox account.
# Use `--provider aws` only for AWS-specific runner proof.
provider: azure
provider: aws
class: standard
capacity:
market: spot
strategy: most-available
# Fail closed instead of silently falling back to on-demand while the
# Azure-backed billing account is the default runner path.
fallback: spot-only
fallback: on-demand-after-120s
hints: true
availabilityZones:
- eu-west-1a
- eu-west-1b
- eu-west-1c
regions:
- eu-west-1
- eu-west-2
- eu-central-1
- us-east-1
- us-west-2
actions:
workflow: .github/workflows/crabbox-hydrate.yml
# Default AWS hydration uses local Actions replay. Use
@@ -29,8 +35,6 @@ blacksmith:
job: check
ref: main
aws:
# AWS-specific overrides still pin direct `--provider aws` runs without
# leaking AWS region names into the Azure default capacity fallback list.
region: eu-west-1
rootGB: 400
sync:

10
.github/labeler.yml vendored
View File

@@ -355,11 +355,6 @@
- any-glob-to-any-file:
- "extensions/deepinfra/**"
- "docs/providers/deepinfra.md"
"extensions: gmi":
- changed-files:
- any-glob-to-any-file:
- "extensions/gmi/**"
- "docs/providers/gmi.md"
"extensions: tencent":
- changed-files:
- any-glob-to-any-file:
@@ -441,11 +436,6 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/nvidia/**"
"extensions: novita":
- changed-files:
- any-glob-to-any-file:
- "extensions/novita/**"
- "docs/providers/novita.md"
"extensions: phone-control":
- changed-files:
- any-glob-to-any-file:

View File

@@ -28,7 +28,7 @@ permissions:
concurrency:
group: ${{ github.event_name == 'workflow_dispatch' && format('{0}-manual-v1-{1}', github.workflow, github.run_id) || (github.event_name == 'pull_request' && format('{0}-v7-{1}', github.workflow, github.event.pull_request.number) || (github.repository == 'openclaw/openclaw' && format('{0}-v7-{1}', github.workflow, github.ref) || format('{0}-v7-{1}-{2}', github.workflow, github.ref, github.sha))) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && github.repository == 'openclaw/openclaw' && github.ref == 'refs/heads/main') }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
@@ -466,8 +466,8 @@ jobs:
- name: Audit production dependencies
run: node scripts/pre-commit/pnpm-audit-prod.mjs --audit-level=high
# Warm the lockfile- and pnpm-pinned store without blocking Linux Node shards.
# On a cold key this job owns the save for later workflow runs.
# Warm the lockfile- and pnpm-pinned store once before Linux Node shards fan out.
# On a cold key this job owns the save, so later shards restore the exact key.
pnpm-store-warmup:
permissions:
contents: read
@@ -532,9 +532,9 @@ jobs:
build-artifacts:
permissions:
contents: read
needs: [preflight]
needs: [preflight, pnpm-store-warmup]
if: needs.preflight.outputs.run_build_artifacts == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-32vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
outputs:
channels-result: ${{ steps.built_artifact_checks.outputs['channels-result'] }}
@@ -597,14 +597,6 @@ jobs:
with:
install-bun: "false"
- name: Restore build-all step cache
uses: actions/cache@v5
with:
path: .artifacts/build-all-cache
key: ${{ runner.os }}-build-all-v3-${{ hashFiles('package.json', 'pnpm-lock.yaml', 'npm-shrinkwrap.json', 'packages/plugin-sdk/package.json', 'packages/llm-core/package.json', 'packages/model-catalog-core/package.json', 'packages/memory-host-sdk/package.json', 'scripts/build-all.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entries.mjs', 'tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'src/plugin-sdk/**', 'packages/llm-core/src/**', 'packages/model-catalog-core/src/**', 'packages/memory-host-sdk/src/**', 'src/types/**', 'src/video-generation/dashscope-compatible.ts', 'src/video-generation/types.ts', 'scripts/copy-export-html-templates.ts', 'scripts/lib/copy-assets.ts', 'src/auto-reply/reply/export-html/**') }}
restore-keys: |
${{ runner.os }}-build-all-v3-
- name: Build dist
env:
NODE_OPTIONS: --max-old-space-size=8192
@@ -702,6 +694,20 @@ jobs:
pids+=("$!")
}
if [ "$RUN_GATEWAY_WATCH" = "true" ]; then
gateway_watch_log="${RUNNER_TEMP}/gateway-watch.log"
echo "starting gateway-watch: node scripts/check-gateway-watch-regression.mjs --skip-build --ready-timeout-ms 5000"
if node scripts/check-gateway-watch-regression.mjs --skip-build --ready-timeout-ms 5000 >"$gateway_watch_log" 2>&1; then
result="success"
else
result="failure"
fi
echo "::group::gateway-watch log"
cat "$gateway_watch_log"
echo "::endgroup::"
results["gateway-watch"]="$result"
fi
if [ "$RUN_CHANNELS" = "true" ]; then
start_check "channels" env \
NODE_OPTIONS=--max-old-space-size=8192 \
@@ -716,11 +722,6 @@ jobs:
node scripts/run-vitest.mjs run --config test/vitest/vitest.full-core-support-boundary.config.ts
fi
if [ "$RUN_GATEWAY_WATCH" = "true" ]; then
start_check "gateway-watch" \
node scripts/check-gateway-watch-regression.mjs --skip-build --ready-timeout-ms 5000
fi
for index in "${!pids[@]}"; do
name="${names[$index]}"
log="${logs[$index]}"
@@ -763,7 +764,7 @@ jobs:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
needs: [preflight, pnpm-store-warmup]
if: needs.preflight.outputs.run_checks_fast_core == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
@@ -852,7 +853,7 @@ jobs:
permissions:
contents: read
name: ${{ matrix.checkName }}
needs: [preflight]
needs: [preflight, pnpm-store-warmup]
if: needs.preflight.outputs.run_plugin_contracts_shards == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
@@ -932,7 +933,7 @@ jobs:
permissions:
contents: read
name: ${{ matrix.checkName }}
needs: [preflight]
needs: [preflight, pnpm-store-warmup]
if: needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
@@ -1084,7 +1085,7 @@ jobs:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
needs: [preflight, pnpm-store-warmup]
if: needs.preflight.outputs.run_checks_node_core_nondist == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (matrix.runner || 'blacksmith-8vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
timeout-minutes: 60
@@ -1190,8 +1191,8 @@ jobs:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' }}
needs: [preflight, pnpm-store-warmup]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' && needs.pnpm-store-warmup.result == 'success' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
@@ -1321,8 +1322,8 @@ jobs:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' }}
needs: [preflight, pnpm-store-warmup]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' && needs.pnpm-store-warmup.result == 'success' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
@@ -1403,7 +1404,7 @@ jobs:
packages/plugin-sdk/dist
extensions/*/dist/.boundary-tsc.tsbuildinfo
extensions/*/dist/.boundary-tsc.stamp
key: ${{ runner.os }}-extension-package-boundary-v1-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'packages/llm-core/package.json', 'packages/model-catalog-core/package.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'src/plugin-sdk/**', 'src/auto-reply/**', 'packages/llm-core/src/**', 'packages/model-catalog-core/src/**', 'src/video-generation/dashscope-compatible.ts', 'src/video-generation/types.ts', 'src/types/**', 'extensions/**', 'extensions/tsconfig.package-boundary*.json', 'package.json', 'pnpm-lock.yaml') }}
key: ${{ runner.os }}-extension-package-boundary-v1-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'src/plugin-sdk/**', 'src/auto-reply/**', 'src/video-generation/dashscope-compatible.ts', 'src/video-generation/types.ts', 'src/types/**', 'extensions/**', 'extensions/tsconfig.package-boundary*.json', 'package.json', 'pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-extension-package-boundary-v1-
@@ -1420,22 +1421,10 @@ jobs:
find src \
-type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.mts' -o -name '*.cts' -o -name '*.js' -o -name '*.mjs' -o -name '*.json' \) \
-exec touch -t 200001010000 {} +
if [ -d packages/llm-core/src ]; then
find packages/llm-core/src \
-type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.mts' -o -name '*.cts' -o -name '*.js' -o -name '*.mjs' -o -name '*.json' \) \
-exec touch -t 200001010000 {} +
fi
if [ -d packages/model-catalog-core/src ]; then
find packages/model-catalog-core/src \
-type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.mts' -o -name '*.cts' -o -name '*.js' -o -name '*.mjs' -o -name '*.json' \) \
-exec touch -t 200001010000 {} +
fi
cache_inputs=(
touch -t 200001010000 \
tsconfig.json \
tsconfig.plugin-sdk.dts.json \
packages/plugin-sdk/tsconfig.json \
packages/llm-core/package.json \
packages/model-catalog-core/package.json \
scripts/check-extension-package-tsc-boundary.mjs \
scripts/prepare-extension-package-boundary-artifacts.mjs \
scripts/write-plugin-sdk-entry-dts.ts \
@@ -1443,12 +1432,6 @@ jobs:
scripts/lib/plugin-sdk-entries.mjs \
package.json \
pnpm-lock.yaml
)
for cache_input in "${cache_inputs[@]}"; do
if [ -e "$cache_input" ]; then
touch -t 200001010000 "$cache_input"
fi
done
- name: Run additional check shard
env:
@@ -1506,7 +1489,7 @@ jobs:
check-docs:
permissions:
contents: read
needs: [preflight]
needs: [preflight, pnpm-store-warmup]
if: needs.preflight.outputs.run_check_docs == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
@@ -1690,7 +1673,6 @@ jobs:
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
fetch_timeout_seconds=90
fetch_checkout_ref() {
git -C "$GITHUB_WORKSPACE" \
-c protocol.version=2 \
@@ -1699,7 +1681,7 @@ jobs:
local fetch_pid="$!"
local elapsed=0
while kill -0 "$fetch_pid" 2>/dev/null; do
if [ "$elapsed" -ge "$fetch_timeout_seconds" ]; then
if [ "$elapsed" -ge 30 ]; then
kill -TERM "$fetch_pid" 2>/dev/null || true
sleep 10
kill -KILL "$fetch_pid" 2>/dev/null || true
@@ -1811,7 +1793,6 @@ jobs:
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
fetch_timeout_seconds=90
fetch_checkout_ref() {
git -C "$GITHUB_WORKSPACE" \
-c protocol.version=2 \
@@ -1820,7 +1801,7 @@ jobs:
local fetch_pid="$!"
local elapsed=0
while kill -0 "$fetch_pid" 2>/dev/null; do
if [ "$elapsed" -ge "$fetch_timeout_seconds" ]; then
if [ "$elapsed" -ge 30 ]; then
kill -TERM "$fetch_pid" 2>/dev/null || true
sleep 10
kill -KILL "$fetch_pid" 2>/dev/null || true
@@ -1878,7 +1859,6 @@ jobs:
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
fetch_timeout_seconds=90
fetch_checkout_ref() {
git -C "$GITHUB_WORKSPACE" \
-c protocol.version=2 \
@@ -1887,7 +1867,7 @@ jobs:
local fetch_pid="$!"
local elapsed=0
while kill -0 "$fetch_pid" 2>/dev/null; do
if [ "$elapsed" -ge "$fetch_timeout_seconds" ]; then
if [ "$elapsed" -ge 30 ]; then
kill -TERM "$fetch_pid" 2>/dev/null || true
sleep 10
kill -KILL "$fetch_pid" 2>/dev/null || true
@@ -2134,7 +2114,7 @@ jobs:
- macos-node
- macos-swift
- android
if: ${{ !cancelled() && always() && github.event_name != 'push' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
if: ${{ !cancelled() && always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:

View File

@@ -14,14 +14,10 @@ concurrency:
cancel-in-progress: true
jobs:
dependency-guard-detect:
dependency-guard:
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
autoscrub: ${{ steps.guard.outputs.autoscrub }}
autoscrub-owner: ${{ steps.guard.outputs.autoscrub-owner }}
autoscrub-repository: ${{ steps.guard.outputs.autoscrub-repository }}
steps:
- name: Check out trusted base workflow scripts
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
@@ -29,81 +25,9 @@ jobs:
ref: ${{ github.event.pull_request.base.sha }}
persist-credentials: false
- name: Detect dependency changes
id: guard
- name: Label, comment, and guard dependency changes
env:
GITHUB_TOKEN: ${{ github.token }}
OPENCLAW_DEPENDENCY_GUARD_MODE: detect
OPENCLAW_SECURITY_APPROVERS: vincentkoc,steipete,joshavant
OPENCLAW_SECURITY_TEAM_SLUG: openclaw-secops
run: node scripts/github/dependency-guard.mjs
dependency-guard-autoscrub:
if: ${{ !github.event.pull_request.draft && needs.dependency-guard-detect.outputs.autoscrub == 'true' }}
needs: dependency-guard-detect
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
issues: write
pull-requests: read
steps:
- name: Check out trusted base workflow scripts
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.base.sha }}
persist-credentials: false
- name: Create autoscrub app token
id: app-token
continue-on-error: true
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
owner: ${{ needs.dependency-guard-detect.outputs.autoscrub-owner }}
repositories: ${{ needs.dependency-guard-detect.outputs.autoscrub-repository }}
permission-contents: write
- name: Create fallback autoscrub app token
id: app-token-fallback
continue-on-error: true
if: steps.app-token.outcome == 'failure'
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
owner: ${{ needs.dependency-guard-detect.outputs.autoscrub-owner }}
repositories: ${{ needs.dependency-guard-detect.outputs.autoscrub-repository }}
permission-contents: write
- name: Remove package lockfile changes
env:
GITHUB_TOKEN: ${{ github.token }}
OPENCLAW_DEPENDENCY_GUARD_AUTOSCRUB_TOKEN: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
OPENCLAW_DEPENDENCY_GUARD_MODE: autoscrub
OPENCLAW_SECURITY_APPROVERS: vincentkoc,steipete,joshavant
OPENCLAW_SECURITY_TEAM_SLUG: openclaw-secops
run: node scripts/github/dependency-guard.mjs
dependency-guard:
if: ${{ !github.event.pull_request.draft && always() }}
needs:
- dependency-guard-detect
- dependency-guard-autoscrub
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Check out trusted base workflow scripts
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.base.sha }}
persist-credentials: false
- name: Enforce dependency guard
env:
GITHUB_TOKEN: ${{ github.token }}
OPENCLAW_DEPENDENCY_GUARD_MODE: enforce
OPENCLAW_SECURITY_APPROVERS: vincentkoc,steipete,joshavant
OPENCLAW_SECURITY_TEAM_SLUG: openclaw-secops
run: node scripts/github/dependency-guard.mjs

View File

@@ -72,7 +72,6 @@ Skills own workflows; root owns hard policy and routing.
- Plugin SDK exception: shipped external API gets new API first plus named compat/deprecation, small tests/docs if useful, removal plan.
- Migrate internal/bundled callers to modern API in the same change. Do not let internal compat become permanent architecture.
- Channels are implementation under `src/channels/**`; plugin authors get SDK seams. Providers own auth/catalog/runtime hooks; core owns generic loop.
- Agent run terminal state: normalize/merge via `src/agents/agent-run-terminal-outcome.ts`; do not rederive timeout/cancel precedence in projections.
- Hot paths should carry prepared facts forward: provider id, model ref, channel id, target, capability family, attachment class. Do not rediscover with broad plugin/provider/channel/capability loaders.
- Do not fix repeated request-time discovery with scattered caches. Move the canonical fact earlier; reuse prepared runtime objects; delete duplicate lookup branches.
- Gateway/plugin metadata is process-stable: installs, manifests, catalogs, generated paths, bundled metadata. Changes require restart or explicit owner reload/install/doctor flow.
@@ -93,7 +92,6 @@ Skills own workflows; root owns hard policy and routing.
- Install: `pnpm install` (keep Bun lock/patches aligned if touched).
- CLI: `pnpm openclaw ...` or `pnpm dev`; build: `pnpm build`.
- Tests in a normal source checkout: `pnpm test <path-or-filter> [vitest args...]`, `pnpm test:changed`, `pnpm test:serial`, `pnpm test:coverage`; never raw `vitest`.
- If raw Vitest is unavoidable, use `vitest run ...`; bare `vitest ...` starts local watch mode and will not exit on its own.
- Tests in a Codex worktree or linked/sparse checkout: avoid direct local `pnpm test*`; use `node scripts/run-vitest.mjs <path-or-filter>` for tiny explicit-file proof, or Crabbox/Testbox for anything broader.
- Checks in a normal source checkout: `pnpm check:changed`; lanes: `pnpm changed:lanes --json`; staged: `pnpm check:changed --staged`; full: `pnpm check`.
- Checks in a Codex worktree or linked/sparse checkout: avoid direct local `pnpm check*`; use `node scripts/crabbox-wrapper.mjs run ... --shell -- "pnpm check:changed"` so pnpm runs inside Testbox, not locally.
@@ -229,7 +227,6 @@ Skills own workflows; root owns hard policy and routing.
- Parallels: `$openclaw-parallels-smoke`; Discord roundtrip: `$parallels-discord-roundtrip`.
- Crabbox/WebVNC human demos: keep remote desktop visible/windowed; no fullscreen remote browser unless video/capture-style output.
- ClawSweeper ops: `$clawsweeper`. Deployed hook sessions may post one concise `#clawsweeper` note only when surprising/actionable/risky; if using message tool, reply exactly `NO_REPLY`.
- Generated-media completions wake the requester agent first. Requester visible-reply config decides final text vs message tool; direct media send is fallback/recovery only.
- Memory wiki prompt digest stays tiny; prefer `wiki_search` / `wiki_get`; verify contact data before use; source-class provenance for generated people facts.
- Rebrand/migration/config warnings: run `openclaw doctor`.
- Never edit `node_modules`.

12077
CHANGELOG.md

File diff suppressed because it is too large Load Diff

View File

@@ -65,8 +65,8 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026053001
versionName = "2026.5.30"
versionCode = 2026052801
versionName = "2026.5.28"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")

View File

@@ -1,13 +1,5 @@
# OpenClaw iOS Changelog
## 2026.5.30 - 2026-05-30
Maintenance update for the current OpenClaw release.
- Added hosted push relay defaults, realtime Talk playback, and safer WebSocket ping handling for mobile sessions.
- Updated App Store screenshots to cover Gateway pairing, Command, Chat, Talk, Agent, and Settings flows.
- Highlighted realtime Talk relay, Gateway connection status, node capabilities, push wake, and privacy controls.
## 2026.5.28 - 2026-05-28
Maintenance update for the current OpenClaw release.

View File

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

View File

@@ -73,10 +73,9 @@ Release behavior:
- Changing the root gateway version does not change the iOS app version until you explicitly pin from the gateway.
- See `apps/ios/VERSIONING.md` for the full workflow.
Relay behavior for beta builds:
Required env for beta builds:
- Beta builds default to `https://ios-push-relay.openclaw.ai`.
- Optional custom relay override: `OPENCLAW_PUSH_RELAY_BASE_URL=https://relay.example.com`
- `OPENCLAW_PUSH_RELAY_BASE_URL=https://relay.example.com`
This must be a plain `https://host[:port][/path]` base URL without whitespace, query params, fragments, or xcconfig metacharacters.
Archive without upload:
@@ -119,7 +118,7 @@ scripts/ios-asc-keychain-setup.sh \
This should create `apps/ios/fastlane/.env` with the non-secret ASC variables while the private key stays in Keychain.
3. Optional: set a custom official/TestFlight relay URL for the build. If unset, the beta flow uses `https://ios-push-relay.openclaw.ai`.
3. Set the official/TestFlight relay URL for the build:
```bash
export OPENCLAW_PUSH_RELAY_BASE_URL=https://relay.example.com

View File

@@ -1,652 +0,0 @@
import SwiftUI
struct TalkProTab: View {
@Environment(NodeAppModel.self) private var appModel
@AppStorage("talk.enabled") private var talkEnabled: Bool = false
@AppStorage(TalkSpeechLocale.storageKey) private var talkSpeechLocale: String = TalkSpeechLocale.automaticID
@AppStorage(TalkDefaults.speakerphoneEnabledKey) private var talkSpeakerphoneEnabled: Bool =
TalkDefaults.speakerphoneEnabledByDefault
@AppStorage("talk.background.enabled") private var talkBackgroundEnabled: Bool = false
@State private var showPermissionPrompt = false
var openSettings: () -> Void
private var state: TalkProState {
TalkProState(
gatewayConnected: self.gatewayConnected,
isEnabled: self.appModel.talkMode.isEnabled || self.talkEnabled,
statusText: self.appModel.talkMode.statusText,
isListening: self.appModel.talkMode.isListening,
isSpeaking: self.appModel.talkMode.isSpeaking,
isUserSpeechDetected: self.appModel.talkMode.isUserSpeechDetected,
permissionState: self.appModel.talkMode.gatewayTalkPermissionState)
}
var body: some View {
NavigationStack {
ZStack {
CommandControlBackground()
ScrollView {
VStack(alignment: .leading, spacing: 10) {
self.header
self.voiceHeroCard
self.conversationCard
self.voiceModeCard
self.controlsCard
}
.padding(.top, 16)
.padding(.bottom, 18)
}
.safeAreaPadding(.bottom, OpenClawProMetric.bottomScrollInset)
}
.navigationBarHidden(true)
}
.sheet(isPresented: self.$showPermissionPrompt) {
NavigationStack {
TalkPermissionPromptView(
style: .sheet,
onPermissionReady: {
self.showPermissionPrompt = false
self.startTalk()
})
.padding()
.navigationTitle("Enable Talk")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Not Now") {
self.showPermissionPrompt = false
}
}
}
}
.presentationDetents([.medium, .large])
.openClawSheetChrome()
}
.onAppear { self.alignPersistedTalkState() }
}
private var header: some View {
HStack(alignment: .center, spacing: 11) {
OpenClawProMark(size: 31, shadowRadius: 9)
VStack(alignment: .leading, spacing: 2) {
Text("Talk")
.font(.system(size: 27, weight: .bold, design: .rounded))
Text(self.headerSubtitle)
.font(.caption.weight(.medium))
.foregroundStyle(.secondary)
.lineLimit(1)
}
Spacer(minLength: 8)
self.statusChip
}
.padding(.horizontal, OpenClawProMetric.pagePadding)
}
private var statusChip: some View {
HStack(spacing: 5) {
Circle()
.fill(self.state.color)
.frame(width: 7, height: 7)
Text(self.state.chipText)
.font(.caption.weight(.bold))
.foregroundStyle(self.state.color)
}
.padding(.horizontal, 10)
.padding(.vertical, 7)
.background {
Capsule(style: .continuous)
.fill(self.state.color.opacity(0.11))
.overlay {
Capsule(style: .continuous)
.strokeBorder(self.state.color.opacity(0.22), lineWidth: 1)
}
}
}
private var voiceHeroCard: some View {
CommandPanel(tint: self.state.color, isProminent: true, padding: 16) {
VStack(alignment: .center, spacing: 16) {
TalkProOrb(
mode: self.state.waveformMode(micLevel: self.appModel.talkMode.micLevel),
color: self.state.color,
systemImage: self.state.icon)
.frame(height: 188)
.accessibilityHidden(true)
VStack(spacing: 5) {
Text(self.state.title)
.font(.title3.weight(.bold))
.multilineTextAlignment(.center)
Text(self.heroSubtitle)
.font(.subheadline.weight(.medium))
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
Button(action: self.handlePrimaryAction) {
Label(self.state.primaryButtonTitle, systemImage: self.state.primaryButtonIcon)
.font(.subheadline.weight(.bold))
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.frame(height: 50)
.background {
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(self.state.primaryButtonFill)
.shadow(color: self.state.color.opacity(0.28), radius: 18, y: 8)
}
}
.buttonStyle(.plain)
.disabled(self.state.primaryAction == .waiting)
}
}
.padding(.horizontal, OpenClawProMetric.pagePadding)
}
private var conversationCard: some View {
CommandPanel(padding: 0) {
VStack(spacing: 0) {
self.cardHeader(title: "Conversation", value: self.state.chipText, color: self.state.color)
.padding(.horizontal, 12)
.padding(.top, 11)
.padding(.bottom, 3)
self.infoRow(icon: "person.crop.circle.fill", title: "Agent", value: self.appModel.activeAgentName)
Divider().padding(.leading, 54)
self.infoRow(
icon: "bubble.left.and.text.bubble.right.fill",
title: "Session",
value: self.appModel.chatSessionKey)
Divider().padding(.leading, 54)
self.infoRow(icon: self.state.icon, title: "Runtime", value: self.appModel.talkMode.statusText)
}
}
.padding(.horizontal, OpenClawProMetric.pagePadding)
}
private var voiceModeCard: some View {
CommandPanel(padding: 0) {
VStack(spacing: 0) {
self.cardHeader(
title: "Voice mode",
value: "Settings ",
color: OpenClawBrand.accent,
action: self.openSettings)
.padding(.horizontal, 12)
.padding(.top, 11)
.padding(.bottom, 3)
self.infoRow(icon: "waveform", title: "Mode", value: self.appModel.talkMode.gatewayTalkVoiceModeTitle)
Divider().padding(.leading, 54)
self.infoRow(icon: "antenna.radiowaves.left.and.right", title: "Transport", value: self.transportText)
Divider().padding(.leading, 54)
self.infoRow(icon: "key.fill", title: "Permission", value: self.permissionText)
Divider().padding(.leading, 54)
self.infoRow(icon: "globe", title: "Speech language", value: self.speechLocaleText)
}
}
.padding(.horizontal, OpenClawProMetric.pagePadding)
}
private var controlsCard: some View {
CommandPanel(padding: 0) {
VStack(spacing: 0) {
self.cardHeader(title: "Controls", value: nil, color: .secondary)
.padding(.horizontal, 12)
.padding(.top, 11)
.padding(.bottom, 3)
Toggle("Speakerphone", isOn: self.$talkSpeakerphoneEnabled)
.padding(.horizontal, 14)
.padding(.vertical, 10)
Divider().padding(.leading, 14)
Toggle("Background listening", isOn: self.$talkBackgroundEnabled)
.padding(.horizontal, 14)
.padding(.vertical, 10)
Divider().padding(.leading, 14)
Button(action: self.openSettings) {
HStack {
Label("Voice & Talk settings", systemImage: "slider.horizontal.3")
Spacer()
Image(systemName: "chevron.right")
.font(.caption.weight(.bold))
.foregroundStyle(.secondary)
}
.font(.subheadline.weight(.semibold))
.padding(.horizontal, 14)
.padding(.vertical, 12)
}
.buttonStyle(.plain)
}
}
.padding(.horizontal, OpenClawProMetric.pagePadding)
}
private func cardHeader(
title: String,
value: String?,
color: Color,
action: (() -> Void)? = nil) -> some View
{
HStack(spacing: 8) {
Text(title)
.font(.subheadline.weight(.bold))
Spacer(minLength: 8)
if let value {
if let action {
Button(value, action: action)
.font(.caption.weight(.semibold))
.foregroundStyle(color)
} else {
Text(value)
.font(.caption.weight(.semibold))
.foregroundStyle(color)
}
}
}
}
private func infoRow(icon: String, title: String, value: String) -> some View {
HStack(spacing: 10) {
Image(systemName: icon)
.font(.caption.weight(.bold))
.foregroundStyle(self.state.color)
.frame(width: 30, height: 30)
.background {
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(self.state.color.opacity(0.11))
}
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.caption2.weight(.medium))
.foregroundStyle(.secondary)
Text(value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "" : value)
.font(.subheadline.weight(.semibold))
.lineLimit(1)
.minimumScaleFactor(0.78)
}
Spacer(minLength: 0)
}
.padding(.horizontal, 12)
.padding(.vertical, 9)
}
private var gatewayConnected: Bool {
GatewayStatusBuilder.build(appModel: self.appModel) == .connected
}
private var headerSubtitle: String {
let mode = self.appModel.talkMode.gatewayTalkVoiceModeTitle.trimmingCharacters(in: .whitespacesAndNewlines)
let agent = self.appModel.activeAgentName.trimmingCharacters(in: .whitespacesAndNewlines)
if mode.isEmpty || mode == "Not loaded" { return agent.isEmpty ? "Realtime voice" : agent }
if agent.isEmpty { return mode }
return "\(agent)\(mode)"
}
private var heroSubtitle: String {
if self.state
.prefersPermissionCopy { return "Gateway approval is required before this phone can capture voice." }
if !self.gatewayConnected { return "Connect to your gateway to start a voice conversation." }
let subtitle = (self.appModel.talkMode.gatewayTalkVoiceModeSubtitle ?? "")
.trimmingCharacters(in: .whitespacesAndNewlines)
if !subtitle.isEmpty { return subtitle }
return "Routes voice to \(self.appModel.activeAgentName)."
}
private var transportText: String {
let provider = self.appModel.talkMode.gatewayTalkProviderLabel.trimmingCharacters(in: .whitespacesAndNewlines)
let transport = self.appModel.talkMode.gatewayTalkTransportLabel.trimmingCharacters(in: .whitespacesAndNewlines)
if provider.isEmpty || provider == "Not loaded" { return transport.isEmpty ? "Not loaded" : transport }
if transport.isEmpty || transport == "Not loaded" { return provider }
return "\(provider)\(transport)"
}
private var permissionText: String {
if let failure = self.appModel.talkMode.gatewayTalkPermissionState.failureMessage {
return failure
}
return self.appModel.talkMode.gatewayTalkPermissionState.statusLabel
}
private var speechLocaleText: String {
if self.talkSpeechLocale == TalkSpeechLocale.automaticID { return "Automatic" }
return self.talkSpeechLocale
}
private func alignPersistedTalkState() {
if self.appModel.talkMode.gatewayTalkPermissionState.requiresTalkPermissionAction,
self.talkEnabled || self.appModel.talkMode.isEnabled
{
self.stopTalk()
} else if self.talkEnabled != self.appModel.talkMode.isEnabled {
self.appModel.setTalkEnabled(self.talkEnabled)
}
}
private func handlePrimaryAction() {
switch self.state.primaryAction {
case .start:
self.startTalk()
case .stop:
self.stopTalk()
case .enablePermission:
self.stopTalk()
self.showPermissionPrompt = true
case .openSettings:
self.openSettings()
case .waiting:
break
}
}
private func startTalk() {
self.talkEnabled = true
self.appModel.setTalkEnabled(true)
}
private func stopTalk() {
self.talkEnabled = false
self.appModel.setTalkEnabled(false)
}
}
enum TalkProPrimaryAction: Equatable {
case start
case stop
case enablePermission
case openSettings
case waiting
}
enum TalkProWaveformMode: Equatable {
case level(Double)
case inputSpeech
case speaking
case indeterminate
case still
}
struct TalkProState: Equatable {
let gatewayConnected: Bool
let isEnabled: Bool
let statusText: String
let isListening: Bool
let isSpeaking: Bool
let isUserSpeechDetected: Bool
let permissionState: TalkGatewayPermissionState
private var normalizedStatus: String {
self.statusText.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
}
var title: String {
if !self.gatewayConnected { return "Gateway offline" }
switch self.permissionState {
case .missingScope, .requestFailed:
return "Gateway permission required"
case .requestingUpgrade:
return "Requesting approval"
case .upgradeRequested:
return "Approval requested"
case .apiKeyMissing:
return "Voice API key missing"
case .loadFailed:
return "Voice config failed"
default:
break
}
if self.isSpeaking { return "Speaking" }
if self.isListening { return "Listening" }
if self.normalizedStatus.contains("connecting") { return "Connecting" }
if self.normalizedStatus.contains("thinking") { return "Asking OpenClaw" }
if self.isEnabled { return "Ready to talk" }
return "Talk is off"
}
var chipText: String {
if !self.gatewayConnected { return "Offline" }
switch self.permissionState {
case .missingScope, .requestFailed:
return "Needs approval"
case .requestingUpgrade, .upgradeRequested:
return "Pending"
case .apiKeyMissing:
return "API key"
case .loadFailed:
return "Config"
default:
break
}
if self.isSpeaking { return "Speaking" }
if self.isListening { return "Listening" }
if self.isEnabled { return "Ready" }
return "Off"
}
var icon: String {
if !self.gatewayConnected { return "wifi.slash" }
switch self.permissionState {
case .missingScope, .requestFailed:
return "key.fill"
case .requestingUpgrade:
return "paperplane.fill"
case .upgradeRequested:
return "hourglass"
case .apiKeyMissing, .loadFailed:
return "exclamationmark.triangle.fill"
default:
break
}
if self.isSpeaking { return "speaker.wave.2.fill" }
if self.isListening { return "mic.fill" }
if self.normalizedStatus.contains("thinking") { return "sparkles" }
if self.normalizedStatus.contains("connecting") { return "dot.radiowaves.left.and.right" }
return "waveform"
}
var color: Color {
if !self.gatewayConnected { return .secondary }
switch self.permissionState {
case .requestFailed, .loadFailed:
return OpenClawBrand.danger
case .missingScope, .requestingUpgrade, .upgradeRequested, .apiKeyMissing:
return OpenClawBrand.warn
default:
return self.isEnabled ? OpenClawBrand.ok : OpenClawBrand.accentHot
}
}
var primaryAction: TalkProPrimaryAction {
if !self.gatewayConnected { return .openSettings }
switch self.permissionState {
case .missingScope, .requestFailed:
return .enablePermission
case .requestingUpgrade, .upgradeRequested:
return .waiting
case .apiKeyMissing, .loadFailed:
return .openSettings
default:
return self.isEnabled ? .stop : .start
}
}
var primaryButtonTitle: String {
switch self.primaryAction {
case .start: "Start Talk"
case .stop: "Stop Talk"
case .enablePermission: "Enable Talk"
case .openSettings: self.gatewayConnected ? "Open Voice Settings" : "Open Gateway Settings"
case .waiting: "Waiting for Approval"
}
}
var primaryButtonIcon: String {
switch self.primaryAction {
case .start: "play.fill"
case .stop: "stop.fill"
case .enablePermission: "key.fill"
case .openSettings: "gearshape.fill"
case .waiting: "hourglass"
}
}
var primaryButtonFill: AnyShapeStyle {
switch self.primaryAction {
case .stop:
AnyShapeStyle(OpenClawBrand.danger)
case .waiting:
AnyShapeStyle(OpenClawBrand.warn.opacity(0.72))
default:
AnyShapeStyle(LinearGradient(
colors: [self.color.opacity(0.95), OpenClawBrand.accent],
startPoint: .topLeading,
endPoint: .bottomTrailing))
}
}
var prefersPermissionCopy: Bool {
switch self.permissionState {
case .missingScope, .requestingUpgrade, .upgradeRequested, .requestFailed:
true
default:
false
}
}
func waveformMode(micLevel: Double) -> TalkProWaveformMode {
if !self.gatewayConnected { return .still }
switch self.permissionState {
case .requestingUpgrade, .upgradeRequested:
return .indeterminate
case .missingScope, .requestFailed, .apiKeyMissing, .loadFailed:
return .still
default:
break
}
if self.isSpeaking { return .speaking }
if self.isListening, self.isUserSpeechDetected { return .inputSpeech }
if self.isListening { return .level(micLevel) }
if self.normalizedStatus.contains("connecting") || self.normalizedStatus.contains("thinking") {
return .indeterminate
}
return self.isEnabled ? .indeterminate : .still
}
}
private struct TalkProOrb: View {
let mode: TalkProWaveformMode
let color: Color
let systemImage: String
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View {
TimelineView(.periodic(from: .now, by: 1.0 / 24.0)) { timeline in
ZStack {
ForEach(0..<3, id: \.self) { ring in
Circle()
.strokeBorder(self.color.opacity(self.ringOpacity(ring)), lineWidth: 1.4)
.scaleEffect(self.ringScale(ring, date: timeline.date))
}
Circle()
.fill(self.color.opacity(0.13))
.frame(width: 128, height: 128)
.overlay {
Circle()
.strokeBorder(self.color.opacity(0.30), lineWidth: 1)
}
TalkProWaveform(mode: self.mode, tint: self.color, barCount: 18)
.frame(width: 116, height: 52)
.opacity(self.systemImage == "waveform" || self.systemImage == "mic.fill" ? 1 : 0.34)
Image(systemName: self.systemImage)
.font(.system(size: 34, weight: .bold))
.foregroundStyle(self.color)
.opacity(self.systemImage == "waveform" || self.systemImage == "mic.fill" ? 0.20 : 1)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
private func ringScale(_ ring: Int, date: Date) -> CGFloat {
guard !self.reduceMotion else { return CGFloat(1.0 + (Double(ring) * 0.12)) }
let base = 0.88 + (Double(ring) * 0.18)
let speed = self.mode == .still ? 0.8 : 1.8
let phase = date.timeIntervalSinceReferenceDate * speed + Double(ring) * 0.9
return CGFloat(base + (sin(phase) * 0.035))
}
private func ringOpacity(_ ring: Int) -> Double {
switch self.mode {
case .still:
0.10 - (Double(ring) * 0.018)
default:
0.24 - (Double(ring) * 0.045)
}
}
}
private struct TalkProWaveform: View {
let mode: TalkProWaveformMode
let tint: Color
let barCount: Int
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View {
TimelineView(.periodic(from: .now, by: 1.0 / 24.0)) { timeline in
HStack(alignment: .center, spacing: 4) {
ForEach(0..<self.barCount, id: \.self) { index in
Capsule(style: .continuous)
.fill(self.tint.opacity(self.opacity(for: index)))
.frame(width: 4, height: self.height(for: index, date: timeline.date))
}
}
.frame(maxHeight: .infinity)
}
}
private func height(for index: Int, date: Date) -> CGFloat {
let minimum = 6.0
let maximum = 48.0
return CGFloat(minimum + ((maximum - minimum) * self.amplitude(for: index, date: date)))
}
private func opacity(for index: Int) -> Double {
switch self.mode {
case .still:
index == self.barCount / 2 ? 0.64 : 0.30
default:
0.82
}
}
private func amplitude(for index: Int, date: Date) -> Double {
if self.reduceMotion {
switch self.mode {
case let .level(level): return min(max(level, 0.10), 1.0)
case .inputSpeech: return 0.72
case .speaking: return 0.62
case .indeterminate: return 0.34
case .still: return 0.18
}
}
let t = date.timeIntervalSinceReferenceDate
let phase = Double(index) * 0.52
switch self.mode {
case let .level(level):
let clamped = min(max(level, 0), 1)
let shaped = 0.12 + (0.88 * clamped)
let variation = 0.72 + (0.28 * sin((t * 12.0) + phase))
return min(max(shaped * variation, 0.10), 1.0)
case .inputSpeech:
let primary = 0.5 + (0.5 * sin((t * 14.0) + phase))
let secondary = 0.5 + (0.5 * sin((t * 5.0) + (phase * 1.35)))
return min(max(0.16 + (0.60 * primary) + (0.24 * secondary), 0.14), 1.0)
case .speaking:
let wave = 0.5 + (0.5 * sin((t * 7.5) + phase))
let secondary = 0.5 + (0.5 * sin((t * 3.0) + (phase * 0.7)))
return min(max(0.18 + (0.58 * wave) + (0.24 * secondary), 0.12), 1.0)
case .indeterminate:
let center = (sin((t * 3.2) + phase) + 1) / 2
return 0.16 + (0.42 * center)
case .still:
return index == self.barCount / 2 ? 0.32 : 0.16
}
}
}

View File

@@ -17,7 +17,6 @@ private struct RelayGatewayPushRegistrationPayload: Encodable {
var topic: String
var environment: String
var distribution: String
var relayOrigin: String
var tokenDebugSuffix: String?
}
@@ -108,7 +107,6 @@ actor PushRegistrationManager {
topic: topic,
environment: self.buildConfig.apnsEnvironment.rawValue,
distribution: self.buildConfig.distribution.rawValue,
relayOrigin: relayOrigin,
tokenDebugSuffix: stored.tokenDebugSuffix))
}
@@ -140,7 +138,6 @@ actor PushRegistrationManager {
topic: topic,
environment: self.buildConfig.apnsEnvironment.rawValue,
distribution: self.buildConfig.distribution.rawValue,
relayOrigin: relayOrigin,
tokenDebugSuffix: registrationState.tokenDebugSuffix))
}

View File

@@ -36,7 +36,6 @@ struct RootTabs: View {
private enum AppTab: Hashable {
case control
case chat
case talk
case agent
case settings
}
@@ -54,8 +53,6 @@ struct RootTabs: View {
switch arguments[valueIndex].lowercased() {
case "chat":
return .chat
case "talk", "voice":
return .talk
case "agent", "agents":
return .agent
case "settings":
@@ -148,14 +145,6 @@ struct RootTabs: View {
.tabItem { Label("Chat", systemImage: "bubble.left.fill") }
.tag(AppTab.chat)
TalkProTab(openSettings: { self.selectedTab = .settings })
.tabItem {
Label(
"Talk",
systemImage: self.appModel.talkMode.isEnabled ? "waveform.circle.fill" : "waveform.circle")
}
.tag(AppTab.talk)
AgentProTab()
.tabItem { Label("Agent", systemImage: "person.2.fill") }
.tag(AppTab.agent)

View File

@@ -17,7 +17,7 @@ private func makeRealtimeAudioTapBlock(
inputSampleRate: inputSampleRate,
targetSampleRate: targetSampleRate)
guard !encoded.isEmpty else { return }
let timestampMs = (ProcessInfo.processInfo.systemUptime * 1000).rounded()
let timestampMs = ProcessInfo.processInfo.systemUptime * 1000
let rms = RealtimeTalkRelaySession.rmsLevel(buffer: buffer)
onAudio(encoded, timestampMs, rms)
}
@@ -125,24 +125,15 @@ final class RealtimeTalkRelaySession {
private var eventTask: Task<Void, Never>?
private var outputTask: Task<Void, Never>?
private var outputContinuation: AsyncThrowingStream<Data, Error>.Continuation?
private var outputIdleTask: Task<Void, Never>?
private var outputSessionId = 0
private var pendingOutputChunks: [Data] = []
private var pendingOutputDone = false
private var audioSender: RealtimeAudioSender?
private var isClosed = false
private var isOutputPlaying = false
private var outputStartedAtMs: Double?
private var outputPlaybackExpectedEndMs: Double = 0
private var lastBargeInAtMs: Double = 0
private var micLogFrameCount = 0
private var micLogByteCount = 0
private var micLogMaxRms: Float = 0
private var lastMicLogAtMs: Double = 0
private var suppressedEchoFrameCount = 0
private var suppressedEchoByteCount = 0
private var suppressedEchoMaxRms: Float = 0
private var lastSuppressedEchoLogAtMs: Double = 0
private var outputAudioChunkCount = 0
private var outputAudioByteCount = 0
@@ -177,6 +168,7 @@ final class RealtimeTalkRelaySession {
let eventStream = await self.gateway.subscribeServerEvents(bufferingNewest: 200)
self.startEventPump(stream: eventStream)
self.configureAudioContract(result.audio)
self.startOutputPlayback()
try self.startMicrophonePump()
self.onStatus("Listening (Realtime)")
} catch {
@@ -227,6 +219,7 @@ final class RealtimeTalkRelaySession {
func cancelOutput(reason: String = "user") {
self.stopOutputPlayback()
self.startOutputPlayback()
guard let relaySessionId else { return }
Task { [gateway] in
let payload: [String: Any] = [
@@ -313,18 +306,12 @@ final class RealtimeTalkRelaySession {
let data = Data(base64Encoded: base64)
else { return }
self.recordOutputAudioChunk(byteCount: data.count)
self.markOutputAudioStarted(byteCount: data.count, nowMs: ProcessInfo.processInfo.systemUptime * 1000)
self.markOutputAudioStarted(nowMs: ProcessInfo.processInfo.systemUptime * 1000)
self.onSpeakingChanged(true)
if self.outputContinuation == nil, self.outputTask != nil {
self.pendingOutputChunks.append(data)
return
}
self.ensureOutputPlaybackStarted()
self.outputContinuation?.yield(data)
case "audioDone":
self.finishOutputPlaybackStream()
case "clear":
self.stopOutputPlayback()
self.startOutputPlayback()
case "transcript":
self.handleTranscriptEvent(payload)
case "toolCall":
@@ -350,16 +337,11 @@ final class RealtimeTalkRelaySession {
"talk realtime audio: chunks=\(self.outputAudioChunkCount) bytes=\(self.outputAudioByteCount)")
}
private func markOutputAudioStarted(byteCount: Int, nowMs: Double) {
private func markOutputAudioStarted(nowMs: Double) {
if !self.isOutputPlaying {
self.outputStartedAtMs = nowMs
self.outputPlaybackExpectedEndMs = nowMs
}
self.isOutputPlaying = true
let bytesPerSecond = max(1, self.outputSampleRateHz * Double(MemoryLayout<Int16>.size))
let chunkDurationMs = (Double(byteCount) / bytesPerSecond) * 1000
self.outputPlaybackExpectedEndMs = max(nowMs, self.outputPlaybackExpectedEndMs) + chunkDurationMs
self.scheduleOutputPlaybackIdle(expectedEndMs: self.outputPlaybackExpectedEndMs)
}
private func handleInputLevelDuringOutput(_ rms: Float, timestampMs: Double) {
@@ -555,25 +537,14 @@ final class RealtimeTalkRelaySession {
{ [weak self, audioSender = self.audioSender] encoded, timestampMs, rms in
guard let audioSender else { return }
Task {
let shouldSend = await MainActor.run { [weak self] in
guard let self, !self.isClosed else { return false }
self.recordMicrophoneFrame(byteCount: encoded.count, rms: rms, timestampMs: timestampMs)
self.refreshOutputPlaybackState(timestampMs: timestampMs)
if self.isOutputPlaying {
if self.shouldSuppressMicrophoneDuringOutput() {
self.recordSuppressedOutputEchoFrame(
byteCount: encoded.count,
rms: rms,
timestampMs: timestampMs)
return false
}
if rms >= Self.bargeInRmsThreshold {
self.handleInputLevelDuringOutput(rms, timestampMs: timestampMs)
}
}
return true
await MainActor.run { [weak self] in
self?.recordMicrophoneFrame(byteCount: encoded.count, rms: rms, timestampMs: timestampMs)
}
if rms >= Self.bargeInRmsThreshold {
await MainActor.run { [weak self] in
self?.handleInputLevelDuringOutput(rms, timestampMs: timestampMs)
}
}
guard shouldSend else { return }
guard let message = await audioSender.send(encoded, timestampMs: timestampMs) else { return }
await MainActor.run { [weak self] in
guard let self, !self.isClosed else { return }
@@ -590,13 +561,6 @@ final class RealtimeTalkRelaySession {
try self.audioEngine.start()
}
private func shouldSuppressMicrophoneDuringOutput() -> Bool {
let outputs = AVAudioSession.sharedInstance().currentRoute.outputs
// Built-in speaker output bleeds into the microphone even in voiceChat mode; keep the
// realtime provider from treating its own speech as user input. Headsets keep barge-in.
return outputs.contains { $0.portType == .builtInSpeaker }
}
private func recordMicrophoneFrame(byteCount: Int, rms: Float, timestampMs: Double) {
guard !self.isClosed else { return }
self.micLogFrameCount += 1
@@ -612,31 +576,13 @@ final class RealtimeTalkRelaySession {
self.micLogMaxRms = 0
}
private func recordSuppressedOutputEchoFrame(byteCount: Int, rms: Float, timestampMs: Double) {
self.suppressedEchoFrameCount += 1
self.suppressedEchoByteCount += byteCount
self.suppressedEchoMaxRms = max(self.suppressedEchoMaxRms, rms)
guard timestampMs - self.lastSuppressedEchoLogAtMs >= 1000 else { return }
self.lastSuppressedEchoLogAtMs = timestampMs
let maxRms = String(format: "%.4f", Double(self.suppressedEchoMaxRms))
GatewayDiagnostics.log(
"talk realtime mic suppressed during output: "
+ "buffers=\(self.suppressedEchoFrameCount) "
+ "bytes=\(self.suppressedEchoByteCount) maxRms=\(maxRms)")
self.suppressedEchoFrameCount = 0
self.suppressedEchoByteCount = 0
self.suppressedEchoMaxRms = 0
}
private func stopMicrophonePump() {
self.audioEngine.inputNode.removeTap(onBus: 0)
self.audioEngine.stop()
}
private func ensureOutputPlaybackStarted() {
guard self.outputContinuation == nil, self.outputTask == nil else { return }
self.outputSessionId += 1
let sessionId = self.outputSessionId
private func startOutputPlayback() {
self.stopOutputPlayback()
let stream = AsyncThrowingStream<Data, Error> { continuation in
self.outputContinuation = continuation
}
@@ -644,95 +590,28 @@ final class RealtimeTalkRelaySession {
guard let self else { return }
let result = await self.pcmPlayer.play(stream: stream, sampleRate: self.outputSampleRateHz)
await MainActor.run {
guard self.outputSessionId == sessionId else { return }
self.outputTask = nil
self.outputContinuation = nil
if !result.finished, let interruptedAt = result.interruptedAt {
self.logger.info("realtime output interrupted at \(interruptedAt, privacy: .public)s")
}
self.markOutputPlaybackFinished()
self.startPendingOutputPlaybackIfNeeded()
}
}
}
private func finishOutputPlaybackStream() {
guard let continuation = self.outputContinuation else {
if self.outputTask != nil, !self.pendingOutputChunks.isEmpty {
self.pendingOutputDone = true
}
return
}
continuation.finish()
self.outputContinuation = nil
}
private func startPendingOutputPlaybackIfNeeded() {
guard !self.pendingOutputChunks.isEmpty else {
self.pendingOutputDone = false
return
}
let chunks = self.pendingOutputChunks
let shouldFinish = self.pendingOutputDone
self.pendingOutputChunks = []
self.pendingOutputDone = false
self.ensureOutputPlaybackStarted()
for chunk in chunks {
self.markOutputAudioStarted(byteCount: chunk.count, nowMs: ProcessInfo.processInfo.systemUptime * 1000)
self.onSpeakingChanged(true)
self.outputContinuation?.yield(chunk)
}
if shouldFinish {
self.finishOutputPlaybackStream()
}
}
private func scheduleOutputPlaybackIdle(expectedEndMs: Double) {
self.outputIdleTask?.cancel()
let nowMs = ProcessInfo.processInfo.systemUptime * 1000
let idleDelayMs = max(350, expectedEndMs - nowMs + 500)
self.outputIdleTask = Task { [weak self] in
try? await Task.sleep(nanoseconds: UInt64(idleDelayMs * 1_000_000))
guard !Task.isCancelled else { return }
await MainActor.run { [weak self] in
guard let self, !self.isClosed else { return }
let nowMs = ProcessInfo.processInfo.systemUptime * 1000
self.refreshOutputPlaybackState(timestampMs: nowMs, cancelIdleTask: false)
}
}
}
private func refreshOutputPlaybackState(timestampMs: Double, cancelIdleTask: Bool = true) {
guard self.isOutputPlaying else { return }
guard timestampMs >= self.outputPlaybackExpectedEndMs + 500 else { return }
self.markOutputPlaybackFinished(cancelIdleTask: cancelIdleTask)
}
private func markOutputPlaybackFinished(cancelIdleTask: Bool = true) {
if cancelIdleTask {
self.outputIdleTask?.cancel()
self.outputIdleTask = nil
}
private func markOutputPlaybackFinished() {
self.isOutputPlaying = false
self.outputStartedAtMs = nil
self.outputPlaybackExpectedEndMs = 0
self.onSpeakingChanged(false)
}
private func stopOutputPlayback() {
self.outputSessionId += 1
self.outputContinuation?.finish()
self.outputContinuation = nil
self.outputTask?.cancel()
self.outputTask = nil
self.outputIdleTask?.cancel()
self.outputIdleTask = nil
self.pendingOutputChunks = []
self.pendingOutputDone = false
_ = self.pcmPlayer.stop()
self.isOutputPlaying = false
self.outputStartedAtMs = nil
self.outputPlaybackExpectedEndMs = 0
self.onSpeakingChanged(false)
}
@@ -805,7 +684,7 @@ final class RealtimeTalkRelaySession {
extension RealtimeTalkRelaySession {
func _test_markOutputAudioStarted(nowMs: Double) {
self.markOutputAudioStarted(byteCount: 4800, nowMs: nowMs)
self.markOutputAudioStarted(nowMs: nowMs)
}
func _test_markOutputPlaybackFinished() {

View File

@@ -1141,7 +1141,7 @@ final class TalkModeManager: NSObject {
})
self.realtimeRelaySession = relaySession
do {
try Self.configureRealtimeAudioSession()
try Self.configureAudioSession()
try await relaySession.start()
guard self.realtimeRelaySession === relaySession, self.isEnabled else {
relaySession.stop()

View File

@@ -13,7 +13,6 @@ Sources/Design/AgentProNodesDestination.swift
Sources/Design/AgentProTab.swift
Sources/Design/ChatProTab.swift
Sources/Design/CommandCenterTab.swift
Sources/Design/TalkProTab.swift
Sources/Design/OpenClawProComponents.swift
Sources/Design/OpenClawProScreens.swift
Sources/Design/SettingsProTab.swift

View File

@@ -29,14 +29,6 @@ def clear_empty_env_var(key)
ENV.delete(key) unless env_present?(ENV[key])
end
def screenshot_upload_requested?
ENV["DELIVER_SCREENSHOTS"] == "1"
end
def screenshot_paths
Dir[File.join(__dir__, "screenshots", "**", "*.png")]
end
def maybe_decode_hex_keychain_secret(value)
return value unless env_present?(value)
@@ -322,7 +314,6 @@ platform :ios do
desc "Upload App Store metadata (and optionally screenshots)"
lane :metadata do
sync_ios_versioning!
version_metadata = read_ios_version_metadata
api_key = asc_api_key
clear_empty_env_var("APP_STORE_CONNECT_API_KEY_PATH")
app_identifier = ENV["ASC_APP_IDENTIFIER"]
@@ -330,21 +321,11 @@ platform :ios do
app_identifier = nil unless env_present?(app_identifier)
app_id = nil unless env_present?(app_id)
if screenshot_upload_requested? && screenshot_paths.empty?
UI.user_error!("DELIVER_SCREENSHOTS=1 but no PNG screenshots were found under apps/ios/fastlane/screenshots.")
end
deliver_options = {
api_key: api_key,
force: true,
app_version: version_metadata[:short_version],
copyright: "2026 OpenClaw",
primary_category: "PRODUCTIVITY",
secondary_category: "UTILITIES",
skip_screenshots: !screenshot_upload_requested?,
skip_screenshots: ENV["DELIVER_SCREENSHOTS"] != "1",
skip_metadata: ENV["DELIVER_METADATA"] != "1",
skip_binary_upload: true,
overwrite_screenshots: screenshot_upload_requested?,
run_precheck_before_submit: false
}
deliver_options[:app_identifier] = app_identifier if app_identifier

View File

@@ -1,19 +1,18 @@
OpenClaw is a personal AI assistant you run on your own devices.
Pair this iPhone app with your OpenClaw Gateway to use your phone as a secure node for chat, voice, approvals, sharing, and device-aware automation.
Pair this iPhone app with your OpenClaw Gateway to connect your phone as a secure node for voice, camera, and device automation.
What you can do:
- Pair with your private OpenClaw Gateway by QR code or setup code
- Chat with your assistant from iPhone
- Use realtime Talk mode and push-to-talk
- Review Gateway action approvals from your phone
- Use voice wake and push-to-talk
- Capture photos and short clips on request
- Record screen snippets for troubleshooting and workflows
- Share text, links, and media directly from iOS into OpenClaw
- Enable device capabilities such as camera, screen, location, photos, contacts, calendar, and reminders when you choose
- Receive push wakes and node status updates for connected workflows
- Run location-aware and device-aware automations
OpenClaw is local-first: you control your gateway, keys, configuration, and permissions. Device access is managed by iOS permissions and can be enabled only for the capabilities you want to use.
OpenClaw is local-first: you control your gateway, keys, and configuration.
Getting started:
1) Set up your OpenClaw Gateway
2) Open the iOS app and pair with your gateway
3) Start using chat, Talk mode, approvals, and automations from your phone
3) Start using commands and automations from your phone

View File

@@ -1 +1 @@
openclaw,ai assistant,local ai,iphone ai,voice assistant,automation,gateway,chat,agent
openclaw,ai assistant,local ai,voice assistant,automation,gateway,chat,agent,node

View File

@@ -1 +1 @@
Pair your iPhone with your OpenClaw Gateway for chat, realtime voice, approvals, device capabilities, and private automation.
Run OpenClaw from your iPhone: pair with your own gateway, trigger automations, and use voice, camera, and share actions.

View File

@@ -1,5 +1 @@
Maintenance update for the current OpenClaw release.
- Added hosted push relay defaults, realtime Talk playback, and safer WebSocket ping handling for mobile sessions.
- Updated App Store screenshots to cover Gateway pairing, Command, Chat, Talk, Agent, and Settings flows.
- Highlighted realtime Talk relay, Gateway connection status, node capabilities, push wake, and privacy controls.

View File

@@ -1,3 +1,3 @@
{
"version": "2026.5.30"
"version": "2026.5.28"
}

View File

@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.5.30</string>
<string>2026.5.28</string>
<key>CFBundleVersion</key>
<string>2026053000</string>
<string>2026052800</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -14,22 +14,6 @@ public protocol WebSocketTasking: AnyObject {
extension URLSessionWebSocketTask: WebSocketTasking {}
private final class WebSocketPingContinuationGate: @unchecked Sendable {
private let lock = NSLock()
private var didResume = false
func resumeOnce(_ resume: () -> Void) {
self.lock.lock()
if self.didResume {
self.lock.unlock()
return
}
self.didResume = true
self.lock.unlock()
resume()
}
}
public struct WebSocketTaskBox: @unchecked Sendable {
public let task: any WebSocketTasking
public init(task: any WebSocketTasking) {
@@ -64,13 +48,8 @@ public struct WebSocketTaskBox: @unchecked Sendable {
public func sendPing() async throws {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
let gate = WebSocketPingContinuationGate()
self.task.sendPing { error in
// URLSession can race ping callbacks with cancellation; only the first
// pong result owns this checked continuation or Swift traps the app.
gate.resumeOnce {
ThrowingContinuationSupport.resumeVoid(continuation, error: error)
}
ThrowingContinuationSupport.resumeVoid(continuation, error: error)
}
}
}

View File

@@ -361,26 +361,6 @@
}
}
},
"get_goal": {
"emoji": "🎯",
"title": "Get Goal",
"detailKeys": []
},
"create_goal": {
"emoji": "🎯",
"title": "Create Goal",
"detailKeys": [
"objective",
"token_budget"
]
},
"update_goal": {
"emoji": "🎯",
"title": "Update Goal",
"detailKeys": [
"status"
]
},
"update_plan": {
"emoji": "🗺️",
"title": "Update Plan",

View File

@@ -556,7 +556,7 @@ public struct MessageActionParams: Codable, Sendable {
sessionkey: String?,
sessionid: String?,
inboundturnkind: String? = nil,
agentid: String? = nil,
agentid: String?,
toolcontext: [String: AnyCodable]?,
idempotencykey: String)
{
@@ -617,7 +617,7 @@ public struct SendParams: Codable, Sendable {
gifplayback: Bool?,
channel: String?,
accountid: String?,
agentid: String? = nil,
agentid: String?,
replytoid: String?,
threadid: String?,
forcedocument: Bool?,
@@ -765,7 +765,7 @@ public struct AgentParams: Codable, Sendable {
public init(
message: String,
agentid: String? = nil,
agentid: String?,
provider: String?,
model: String?,
to: String?,
@@ -893,7 +893,7 @@ public struct AgentIdentityParams: Codable, Sendable {
public let sessionkey: String?
public init(
agentid: String? = nil,
agentid: String?,
sessionkey: String?)
{
self.agentid = agentid
@@ -1617,7 +1617,7 @@ public struct SessionsListParams: Codable, Sendable {
includelastmessage: Bool?,
label: String?,
spawnedby: String?,
agentid: String? = nil,
agentid: String?,
search: String?)
{
self.limit = limit
@@ -1741,7 +1741,7 @@ public struct SessionsResolveParams: Codable, Sendable {
key: String?,
sessionid: String?,
label: String?,
agentid: String? = nil,
agentid: String?,
spawnedby: String?,
includeglobal: Bool?,
includeunknown: Bool?)
@@ -1825,7 +1825,6 @@ public struct SessionOperationEvent: Codable, Sendable {
public let operation: String
public let phase: AnyCodable
public let sessionkey: String
public let agentid: String?
public let ts: Int
public let completed: Bool?
public let reason: String?
@@ -1835,7 +1834,6 @@ public struct SessionOperationEvent: Codable, Sendable {
operation: String,
phase: AnyCodable,
sessionkey: String,
agentid: String? = nil,
ts: Int,
completed: Bool?,
reason: String?)
@@ -1844,7 +1842,6 @@ public struct SessionOperationEvent: Codable, Sendable {
self.operation = operation
self.phase = phase
self.sessionkey = sessionkey
self.agentid = agentid
self.ts = ts
self.completed = completed
self.reason = reason
@@ -1855,7 +1852,6 @@ public struct SessionOperationEvent: Codable, Sendable {
case operation
case phase
case sessionkey = "sessionKey"
case agentid = "agentId"
case ts
case completed
case reason
@@ -1864,84 +1860,68 @@ public struct SessionOperationEvent: Codable, Sendable {
public struct SessionsCompactionListParams: Codable, Sendable {
public let key: String
public let agentid: String?
public init(
key: String,
agentid: String? = nil)
key: String)
{
self.key = key
self.agentid = agentid
}
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
}
}
public struct SessionsCompactionGetParams: Codable, Sendable {
public let key: String
public let agentid: String?
public let checkpointid: String
public init(
key: String,
agentid: String? = nil,
checkpointid: String)
{
self.key = key
self.agentid = agentid
self.checkpointid = checkpointid
}
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
case checkpointid = "checkpointId"
}
}
public struct SessionsCompactionBranchParams: Codable, Sendable {
public let key: String
public let agentid: String?
public let checkpointid: String
public init(
key: String,
agentid: String? = nil,
checkpointid: String)
{
self.key = key
self.agentid = agentid
self.checkpointid = checkpointid
}
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
case checkpointid = "checkpointId"
}
}
public struct SessionsCompactionRestoreParams: Codable, Sendable {
public let key: String
public let agentid: String?
public let checkpointid: String
public init(
key: String,
agentid: String? = nil,
checkpointid: String)
{
self.key = key
self.agentid = agentid
self.checkpointid = checkpointid
}
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
case checkpointid = "checkpointId"
}
}
@@ -2066,7 +2046,7 @@ public struct SessionsCreateParams: Codable, Sendable {
public init(
key: String?,
agentid: String? = nil,
agentid: String?,
label: String?,
model: String?,
parentsessionkey: String?,
@@ -2098,7 +2078,6 @@ public struct SessionsCreateParams: Codable, Sendable {
public struct SessionsSendParams: Codable, Sendable {
public let key: String
public let agentid: String?
public let message: String
public let thinking: String?
public let attachments: [AnyCodable]?
@@ -2107,7 +2086,6 @@ public struct SessionsSendParams: Codable, Sendable {
public init(
key: String,
agentid: String? = nil,
message: String,
thinking: String?,
attachments: [AnyCodable]?,
@@ -2115,7 +2093,6 @@ public struct SessionsSendParams: Codable, Sendable {
idempotencykey: String?)
{
self.key = key
self.agentid = agentid
self.message = message
self.thinking = thinking
self.attachments = attachments
@@ -2125,7 +2102,6 @@ public struct SessionsSendParams: Codable, Sendable {
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
case message
case thinking
case attachments
@@ -2136,37 +2112,29 @@ public struct SessionsSendParams: Codable, Sendable {
public struct SessionsMessagesSubscribeParams: Codable, Sendable {
public let key: String
public let agentid: String?
public init(
key: String,
agentid: String? = nil)
key: String)
{
self.key = key
self.agentid = agentid
}
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
}
}
public struct SessionsMessagesUnsubscribeParams: Codable, Sendable {
public let key: String
public let agentid: String?
public init(
key: String,
agentid: String? = nil)
key: String)
{
self.key = key
self.agentid = agentid
}
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
}
}
@@ -2194,7 +2162,6 @@ public struct SessionsAbortParams: Codable, Sendable {
public struct SessionsPatchParams: Codable, Sendable {
public let key: String
public let agentid: String?
public let label: AnyCodable?
public let thinkinglevel: AnyCodable?
public let fastmode: AnyCodable?
@@ -2221,7 +2188,6 @@ public struct SessionsPatchParams: Codable, Sendable {
public init(
key: String,
agentid: String? = nil,
label: AnyCodable?,
thinkinglevel: AnyCodable?,
fastmode: AnyCodable?,
@@ -2247,7 +2213,6 @@ public struct SessionsPatchParams: Codable, Sendable {
groupactivation: AnyCodable?)
{
self.key = key
self.agentid = agentid
self.label = label
self.thinkinglevel = thinkinglevel
self.fastmode = fastmode
@@ -2275,7 +2240,6 @@ public struct SessionsPatchParams: Codable, Sendable {
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
case label
case thinkinglevel = "thinkingLevel"
case fastmode = "fastMode"
@@ -2356,47 +2320,39 @@ public struct SessionsPluginPatchResult: Codable, Sendable {
public struct SessionsResetParams: Codable, Sendable {
public let key: String
public let agentid: String?
public let reason: AnyCodable?
public init(
key: String,
agentid: String? = nil,
reason: AnyCodable?)
{
self.key = key
self.agentid = agentid
self.reason = reason
}
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
case reason
}
}
public struct SessionsDeleteParams: Codable, Sendable {
public let key: String
public let agentid: String?
public let deletetranscript: Bool?
public let emitlifecyclehooks: Bool?
public init(
key: String,
agentid: String? = nil,
deletetranscript: Bool?,
emitlifecyclehooks: Bool?)
{
self.key = key
self.agentid = agentid
self.deletetranscript = deletetranscript
self.emitlifecyclehooks = emitlifecyclehooks
}
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
case deletetranscript = "deleteTranscript"
case emitlifecyclehooks = "emitLifecycleHooks"
}
@@ -2404,22 +2360,18 @@ public struct SessionsDeleteParams: Codable, Sendable {
public struct SessionsCompactParams: Codable, Sendable {
public let key: String
public let agentid: String?
public let maxlines: Int?
public init(
key: String,
agentid: String? = nil,
maxlines: Int?)
{
self.key = key
self.agentid = agentid
self.maxlines = maxlines
}
private enum CodingKeys: String, CodingKey {
case key
case agentid = "agentId"
case maxlines = "maxLines"
}
}
@@ -2511,7 +2463,7 @@ public struct TaskSummary: Codable, Sendable {
runtime: String?,
status: AnyCodable,
title: String?,
agentid: String? = nil,
agentid: String?,
sessionkey: String?,
childsessionkey: String?,
ownerkey: String?,
@@ -2585,7 +2537,7 @@ public struct TasksListParams: Codable, Sendable {
public init(
status: AnyCodable?,
agentid: String? = nil,
agentid: String?,
sessionkey: String?,
limit: Int?,
cursor: String?)
@@ -4775,7 +4727,7 @@ public struct CommandsListParams: Codable, Sendable {
public let includeargs: Bool?
public init(
agentid: String? = nil,
agentid: String?,
provider: String?,
scope: AnyCodable?,
includeargs: Bool?)
@@ -4812,7 +4764,7 @@ public struct SkillsStatusParams: Codable, Sendable {
public let agentid: String?
public init(
agentid: String? = nil)
agentid: String?)
{
self.agentid = agentid
}
@@ -4827,7 +4779,7 @@ public struct ToolsCatalogParams: Codable, Sendable {
public let includeplugins: Bool?
public init(
agentid: String? = nil,
agentid: String?,
includeplugins: Bool?)
{
self.agentid = agentid
@@ -4961,7 +4913,7 @@ public struct ToolsEffectiveParams: Codable, Sendable {
public let sessionkey: String
public init(
agentid: String? = nil,
agentid: String?,
sessionkey: String)
{
self.agentid = agentid
@@ -5106,7 +5058,7 @@ public struct ToolsInvokeParams: Codable, Sendable {
name: String,
args: [String: AnyCodable]?,
sessionkey: String?,
agentid: String? = nil,
agentid: String?,
confirm: Bool?,
idempotencykey: String?)
{
@@ -5280,7 +5232,7 @@ public struct SkillsSecurityVerdictsParams: Codable, Sendable {
public let agentid: String?
public init(
agentid: String? = nil)
agentid: String?)
{
self.agentid = agentid
}
@@ -5313,7 +5265,7 @@ public struct SkillsSkillCardParams: Codable, Sendable {
public let skillkey: String
public init(
agentid: String? = nil,
agentid: String?,
skillkey: String)
{
self.agentid = agentid
@@ -5450,7 +5402,7 @@ public struct CronJob: Codable, Sendable {
public init(
id: String,
agentid: String? = nil,
agentid: String?,
sessionkey: String?,
name: String,
description: String?,
@@ -5526,7 +5478,7 @@ public struct CronListParams: Codable, Sendable {
lastrunstatus: AnyCodable?,
sortby: AnyCodable?,
sortdir: AnyCodable?,
agentid: String? = nil)
agentid: String?)
{
self.includedisabled = includedisabled
self.limit = limit
@@ -5572,7 +5524,7 @@ public struct CronAddParams: Codable, Sendable {
public init(
name: String,
agentid: AnyCodable? = nil,
agentid: AnyCodable?,
sessionkey: AnyCodable?,
description: String?,
enabled: Bool?,
@@ -5960,7 +5912,7 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
ask: AnyCodable?,
warningtext: AnyCodable?,
commandspans: [[String: AnyCodable]]?,
agentid: AnyCodable? = nil,
agentid: AnyCodable?,
resolvedpath: AnyCodable?,
sessionkey: AnyCodable?,
turnsourcechannel: AnyCodable?,
@@ -6067,7 +6019,7 @@ public struct PluginApprovalRequestParams: Codable, Sendable {
toolname: String?,
toolcallid: String?,
alloweddecisions: [String]?,
agentid: String? = nil,
agentid: String?,
sessionkey: String?,
turnsourcechannel: String?,
turnsourceto: String?,
@@ -6528,25 +6480,21 @@ public struct DevicePairResolvedEvent: Codable, Sendable {
public struct ChatHistoryParams: Codable, Sendable {
public let sessionkey: String
public let agentid: String?
public let limit: Int?
public let maxchars: Int?
public init(
sessionkey: String,
agentid: String? = nil,
limit: Int?,
maxchars: Int?)
{
self.sessionkey = sessionkey
self.agentid = agentid
self.limit = limit
self.maxchars = maxchars
}
private enum CodingKeys: String, CodingKey {
case sessionkey = "sessionKey"
case agentid = "agentId"
case limit
case maxchars = "maxChars"
}
@@ -6554,7 +6502,6 @@ public struct ChatHistoryParams: Codable, Sendable {
public struct ChatSendParams: Codable, Sendable {
public let sessionkey: String
public let agentid: String?
public let sessionid: String?
public let message: String
public let thinking: String?
@@ -6572,7 +6519,6 @@ public struct ChatSendParams: Codable, Sendable {
public init(
sessionkey: String,
agentid: String? = nil,
sessionid: String?,
message: String,
thinking: String?,
@@ -6589,7 +6535,6 @@ public struct ChatSendParams: Codable, Sendable {
idempotencykey: String)
{
self.sessionkey = sessionkey
self.agentid = agentid
self.sessionid = sessionid
self.message = message
self.thinking = thinking
@@ -6608,7 +6553,6 @@ public struct ChatSendParams: Codable, Sendable {
private enum CodingKeys: String, CodingKey {
case sessionkey = "sessionKey"
case agentid = "agentId"
case sessionid = "sessionId"
case message
case thinking
@@ -6628,47 +6572,39 @@ public struct ChatSendParams: Codable, Sendable {
public struct ChatAbortParams: Codable, Sendable {
public let sessionkey: String
public let agentid: String?
public let runid: String?
public init(
sessionkey: String,
agentid: String? = nil,
runid: String?)
{
self.sessionkey = sessionkey
self.agentid = agentid
self.runid = runid
}
private enum CodingKeys: String, CodingKey {
case sessionkey = "sessionKey"
case agentid = "agentId"
case runid = "runId"
}
}
public struct ChatInjectParams: Codable, Sendable {
public let sessionkey: String
public let agentid: String?
public let message: String
public let label: String?
public init(
sessionkey: String,
agentid: String? = nil,
message: String,
label: String?)
{
self.sessionkey = sessionkey
self.agentid = agentid
self.message = message
self.label = label
}
private enum CodingKeys: String, CodingKey {
case sessionkey = "sessionKey"
case agentid = "agentId"
case message
case label
}
@@ -6677,7 +6613,6 @@ public struct ChatInjectParams: Codable, Sendable {
public struct ChatDeltaEvent: Codable, Sendable {
public let runid: String
public let sessionkey: String
public let agentid: String?
public let spawnedby: String?
public let seq: Int
public let state: String
@@ -6689,7 +6624,6 @@ public struct ChatDeltaEvent: Codable, Sendable {
public init(
runid: String,
sessionkey: String,
agentid: String? = nil,
spawnedby: String?,
seq: Int,
state: String,
@@ -6700,7 +6634,6 @@ public struct ChatDeltaEvent: Codable, Sendable {
{
self.runid = runid
self.sessionkey = sessionkey
self.agentid = agentid
self.spawnedby = spawnedby
self.seq = seq
self.state = state
@@ -6713,7 +6646,6 @@ public struct ChatDeltaEvent: Codable, Sendable {
private enum CodingKeys: String, CodingKey {
case runid = "runId"
case sessionkey = "sessionKey"
case agentid = "agentId"
case spawnedby = "spawnedBy"
case seq
case state
@@ -6727,7 +6659,6 @@ public struct ChatDeltaEvent: Codable, Sendable {
public struct ChatFinalEvent: Codable, Sendable {
public let runid: String
public let sessionkey: String
public let agentid: String?
public let spawnedby: String?
public let seq: Int
public let state: String
@@ -6738,7 +6669,6 @@ public struct ChatFinalEvent: Codable, Sendable {
public init(
runid: String,
sessionkey: String,
agentid: String? = nil,
spawnedby: String?,
seq: Int,
state: String,
@@ -6748,7 +6678,6 @@ public struct ChatFinalEvent: Codable, Sendable {
{
self.runid = runid
self.sessionkey = sessionkey
self.agentid = agentid
self.spawnedby = spawnedby
self.seq = seq
self.state = state
@@ -6760,7 +6689,6 @@ public struct ChatFinalEvent: Codable, Sendable {
private enum CodingKeys: String, CodingKey {
case runid = "runId"
case sessionkey = "sessionKey"
case agentid = "agentId"
case spawnedby = "spawnedBy"
case seq
case state
@@ -6773,7 +6701,6 @@ public struct ChatFinalEvent: Codable, Sendable {
public struct ChatAbortedEvent: Codable, Sendable {
public let runid: String
public let sessionkey: String
public let agentid: String?
public let spawnedby: String?
public let seq: Int
public let state: String
@@ -6783,7 +6710,6 @@ public struct ChatAbortedEvent: Codable, Sendable {
public init(
runid: String,
sessionkey: String,
agentid: String? = nil,
spawnedby: String?,
seq: Int,
state: String,
@@ -6792,7 +6718,6 @@ public struct ChatAbortedEvent: Codable, Sendable {
{
self.runid = runid
self.sessionkey = sessionkey
self.agentid = agentid
self.spawnedby = spawnedby
self.seq = seq
self.state = state
@@ -6803,7 +6728,6 @@ public struct ChatAbortedEvent: Codable, Sendable {
private enum CodingKeys: String, CodingKey {
case runid = "runId"
case sessionkey = "sessionKey"
case agentid = "agentId"
case spawnedby = "spawnedBy"
case seq
case state
@@ -6815,7 +6739,6 @@ public struct ChatAbortedEvent: Codable, Sendable {
public struct ChatErrorEvent: Codable, Sendable {
public let runid: String
public let sessionkey: String
public let agentid: String?
public let spawnedby: String?
public let seq: Int
public let state: String
@@ -6828,7 +6751,6 @@ public struct ChatErrorEvent: Codable, Sendable {
public init(
runid: String,
sessionkey: String,
agentid: String? = nil,
spawnedby: String?,
seq: Int,
state: String,
@@ -6840,7 +6762,6 @@ public struct ChatErrorEvent: Codable, Sendable {
{
self.runid = runid
self.sessionkey = sessionkey
self.agentid = agentid
self.spawnedby = spawnedby
self.seq = seq
self.state = state
@@ -6854,7 +6775,6 @@ public struct ChatErrorEvent: Codable, Sendable {
private enum CodingKeys: String, CodingKey {
case runid = "runId"
case sessionkey = "sessionKey"
case agentid = "agentId"
case spawnedby = "spawnedBy"
case seq
case state

View File

@@ -11,42 +11,6 @@ private extension NSLock {
}
}
private final class DoubleCallbackPingWebSocketTask: WebSocketTasking, @unchecked Sendable {
private let callbacks: [Error?]
init(callbacks: [Error?]) {
self.callbacks = callbacks
}
var state: URLSessionTask.State { .running }
func resume() {}
func cancel(with closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
_ = (closeCode, reason)
}
func send(_ message: URLSessionWebSocketTask.Message) async throws {
_ = message
}
func sendPing(pongReceiveHandler: @escaping @Sendable (Error?) -> Void) {
for callback in self.callbacks {
pongReceiveHandler(callback)
}
}
func receive() async throws -> URLSessionWebSocketTask.Message {
throw URLError(.badServerResponse)
}
func receive(
completionHandler: @escaping @Sendable (Result<URLSessionWebSocketTask.Message, Error>) -> Void)
{
completionHandler(.failure(URLError(.badServerResponse)))
}
}
private final class FakeGatewayWebSocketTask: WebSocketTasking, @unchecked Sendable {
private let lock = NSLock()
private let helloAuth: [String: Any]?
@@ -229,25 +193,6 @@ private actor SeqGapProbe {
@Suite(.serialized)
struct GatewayNodeSessionTests {
@Test
func websocketPingIgnoresDuplicateSuccessCallbacks() async throws {
let task = DoubleCallbackPingWebSocketTask(callbacks: [nil, nil])
try await WebSocketTaskBox(task: task).sendPing()
}
@Test
func websocketPingIgnoresDuplicateCallbacksAfterFirstError() async throws {
let firstError = URLError(.networkConnectionLost)
let task = DoubleCallbackPingWebSocketTask(callbacks: [firstError, nil])
do {
try await WebSocketTaskBox(task: task).sendPing()
Issue.record("sendPing unexpectedly succeeded")
} catch let error as URLError {
#expect(error.code == firstError.code)
}
}
@Test
func scannedSetupCodePrefersBootstrapAuthOverStoredDeviceToken() async throws {
let tempDir = FileManager.default.temporaryDirectory

208
changelog-plan.md Normal file
View File

@@ -0,0 +1,208 @@
# Per-Version Changelog Plan
## Context
`CHANGELOG.md` is currently cumulative and is shipped in the root `openclaw`
npm package. The current file is `2,399,938` bytes on disk, about `2.3 MiB`
unpacked. A dry-run npm pack reports the current package at `14,562,001` bytes
compressed and `63,664,564` bytes unpacked, with `CHANGELOG.md` included as a
top-level tarball file.
The latest complete release section is much smaller. The current `## 2026.5.28`
section is about `6,370` bytes. Moving `CHANGELOG.md` to a per-version file
would preserve package-visible release notes while removing roughly `2.39 MB`
from unpacked installs and roughly `0.8 MB` from the compressed package today,
based on standalone gzip size for the current cumulative file.
The recent changelog generation flow is not a GitHub Action that asks an agent
to write release notes. The repo-owned prompt is the agent skill at
`.agents/skills/openclaw-changelog-update/SKILL.md`. The release maintainer
workflow tells the operator/agent to run `/changelog` from
`.agents/skills/release-openclaw-maintainer/SKILL.md`. GitHub Actions then read
the already-committed `CHANGELOG.md` from the release SHA and extract the target
version section.
## Current Surfaces
- Package inclusion: `package.json` includes `"CHANGELOG.md"` in the root
package `files` list.
- Changelog authoring prompt: `.agents/skills/openclaw-changelog-update/SKILL.md`
tells the agent to rewrite the target `CHANGELOG.md` version section from
history.
- Release runbook: `.agents/skills/release-openclaw-maintainer/SKILL.md` tells
maintainers to run `/changelog` on `main`, commit the rewrite, then create the
release branch.
- GitHub release notes: `.github/workflows/openclaw-release-publish.yml` runs
`git show "${TARGET_SHA}:CHANGELOG.md"` and extracts `## YYYY.M.D` through
the next level-2 heading.
- Mac appcast notes: `scripts/changelog-to-html.sh` finds `CHANGELOG.md` and
extracts one version section for Sparkle release notes.
- Attribution check: `scripts/check-changelog-attributions.mjs` scans
`CHANGELOG.md` for forbidden or missing `Thanks @...` attribution.
- Changed-lane routing: `scripts/changed-lanes.mjs` treats `CHANGELOG.md` as a
docs/changelog surface and runs the attribution check.
## Recommendation
Keep shipping `CHANGELOG.md`, but make it a small current-release file:
```md
# Changelog
Docs: https://docs.openclaw.ai
## YYYY.M.D
### Highlights
...
### Changes
...
### Fixes
...
```
The latest package still contains useful release notes, GitHub release creation
continues to use the same version-section extraction, and future tarballs do not
accumulate every historical release.
Older history remains available through Git tags, GitHub Releases, and the docs
site if we choose to publish an archive page later. The npm package should not
be the historical archive.
## Implementation Plan
1. Update the changelog authoring prompt.
In `.agents/skills/openclaw-changelog-update/SKILL.md`, change the workflow
from "rewrite the target version section" to "rewrite `CHANGELOG.md` as
the target version section only." Make the output contract explicit:
- preserve the top title and docs link
- do not keep `## Unreleased`; release notes are regenerated from history
- write one stable-base `## YYYY.M.D` section
- remove older `## YYYY.M.D` sections from the file
- keep beta releases under the stable base heading
- preserve PR refs, issue refs, and human thanks in the target section
2. Update the release maintainer runbook.
In `.agents/skills/release-openclaw-maintainer/SKILL.md`, replace cumulative
wording with per-version wording. The important edits are:
- say the changelog file is current release notes only
- keep the rule that GitHub release bodies use the full matching version
section
- stop saying "full changelog" when the intended source is only the latest
release section
- keep beta guidance unchanged: beta tags use the stable base version
heading
3. Trim the current `CHANGELOG.md`.
Keep:
- `# Changelog`
- docs link
- latest release section, currently `## 2026.5.28`
Remove older release sections from the working copy. This is a one-time size
reduction, not a generated artifact update.
4. Keep `package.json` inclusion unchanged.
Do not remove `"CHANGELOG.md"` from `package.json` for the per-version
approach. Removing it would be the separate "do not ship changelog" option.
5. Adjust user-facing "full changelog" links.
`scripts/changelog-to-html.sh` currently emits "View full changelog" links
to `main/CHANGELOG.md`. After this change, that link should become either:
- "View release notes" pointing to `main/CHANGELOG.md`, or
- "View release history" pointing to a GitHub Releases page or future docs
archive.
The first option is the smallest code change. The second option is clearer if
we want a durable history destination.
6. Add a small automated guard.
Add a script or extend an existing changelog check so normal checks fail if
`CHANGELOG.md` contains too many dated release sections. A simple contract is
enough:
- reject `## Unreleased`
- allow at most one `## YYYY.M.D` section
- allow no beta-specific dated headings
Wire the guard into `check:changelog-attributions` or a nearby changelog
check so future agent rewrites cannot silently return to cumulative history.
7. Update tests that assert current extraction behavior.
`test/scripts/package-acceptance-workflow.test.ts` asserts the release
workflow extracts `CHANGELOG.md` from `TARGET_SHA` and supports the
`Unreleased` prerelease fallback. Those assertions should continue to pass.
Add or update tests for the new guard if one is introduced.
## Compatibility And Release Effects
- GitHub release creation should continue to work because
`.github/workflows/openclaw-release-publish.yml` already extracts one target
section.
- Beta release notes should continue to work because the workflow maps
`vYYYY.M.D-beta.N` to `YYYY.M.D` before extracting notes.
- Mac appcast notes should continue to work because `scripts/changelog-to-html.sh`
already extracts one version section.
- Attribution checks should continue to work on the reduced file.
- The package tarball keeps a changelog file, but it stops growing with every
release.
## Risks
- Historical release notes disappear from `main/CHANGELOG.md`. This is intended
for npm package size, but maintainers should know to use Git tags or GitHub
Releases for older notes.
- Existing "full changelog" wording becomes inaccurate unless links/text are
adjusted.
- If the prompt is changed without an automated guard, future agent-generated
changelog rewrites may accidentally reintroduce old sections.
- Release-bound notes must be regenerated from git history instead of accumulating
under `Unreleased`.
## Validation
For the actual implementation PR, run:
```sh
git diff --check
node scripts/run-vitest.mjs test/scripts/check-changelog-attributions.test.ts
node scripts/run-vitest.mjs test/scripts/package-acceptance-workflow.test.ts
npm pack --dry-run --json --ignore-scripts
```
After `npm pack --dry-run`, verify:
- `CHANGELOG.md` is still present in the file list.
- `CHANGELOG.md` size is close to the current release section size, not the old
cumulative size.
- package `size` and `unpackedSize` decrease by approximately the expected
amount.
If a new guard script is added, include its focused test and confirm it runs
through the changed-lane path that currently covers changelog attribution.
## Optional Follow-Ups
- Publish historical changelog pages under docs or GitHub Releases if users need
an easier archive than tags.
- Add a release script helper that extracts the current version section into a
temporary file and uses that same output for GitHub release notes, appcast
notes, and package changelog verification.
- Consider removing `CHANGELOG.md` from the tarball later if even per-version
notes are not useful to installed-package users.

View File

@@ -180,14 +180,6 @@ const config = {
entry: ["src/index.ts!", "src/ip.ts!"],
project: ["src/**/*.ts!"],
},
"packages/markdown-core": {
entry: ["src/*.ts!"],
project: ["src/**/*.ts!"],
},
"packages/terminal-core": {
entry: ["src/*.ts!"],
project: ["src/**/*.ts!"],
},
"packages/speech-core": {
entry: ["api.ts!", "runtime-api.ts!", "speaker.ts!", "voice-models.ts!"],
project: ["**/*.ts!"],

View File

@@ -1,4 +1,4 @@
289c1bae4b9574d219fe61931be6b3ce42d4efb37d0a2edc570a521016394db5 config-baseline.json
5bcb22d1506d82e59caa3bbc97931213299e3a2c0d45dbc549386b254661094a config-baseline.core.json
a9102c0611b8170fac37853cc31771810f31757a9e3b2c6796bbd9625f9b9206 config-baseline.channel.json
0a8e088f8dc7b12341075ce019281d5fe45827ae802f60c71a490022ba5867cf config-baseline.plugin.json
c80dea63b0a3786c8999d06aae62c110786f440b4d6748f9838577aaa2816971 config-baseline.json
948323a1507817b6580ed976f9f9449239008f40283cc7e6005148ecf0ca4582 config-baseline.core.json
f833ffca6bd88162f062bbea4f0eede783373f46674ebbfc3a390c80353930a2 config-baseline.channel.json
bc38b58b67132401a030b3b3a77efdb6c88f207ea1fab9abcb4599e1f9552dda config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
cf29066e9465cb5ac1387d1d482d0939b9176220ecc69964da9af1a471939269 plugin-sdk-api-baseline.json
ab43993cf713a96b191c55cf89bb215c18ecdc2d8edf50f31369ce3b162c56e3 plugin-sdk-api-baseline.jsonl
59de21361cab0622926ad313caf3f8dc43c28d420a82ba060680ecc30c472453 plugin-sdk-api-baseline.json
05adee9037669db4e834d1a0ca9705d5d94df770083862ab149d2f3e559010d2 plugin-sdk-api-baseline.jsonl

View File

@@ -102,7 +102,7 @@ Not every agent run creates a task. Heartbeat turns and normal interactive chat
<Accordion title="Notify defaults for cron and media">
Main-session cron tasks use `silent` notify policy by default - they create records for tracking but do not generate notifications. Isolated cron tasks also default to `silent` but are more visible because they run in their own session.
Session-backed `image_generate`, `music_generate`, and `video_generate` runs also use `silent` notify policy. They still create task records, but completion is handed back to the original agent session as an internal wake so the agent can write the follow-up message and attach the finished media itself. The requester agent follows its normal visible-reply contract: automatic final reply when configured, or `message(action="send")` plus `NO_REPLY` when the session requires message-tool replies. If the requester session is no longer active or its active wake fails, and the completion agent misses some or all generated media, OpenClaw sends an idempotent direct fallback with only the missing media to the original channel target.
Session-backed `image_generate`, `music_generate`, and `video_generate` runs also use `silent` notify policy. They still create task records, but completion is handed back to the original agent session as an internal wake so the agent can write the follow-up message and attach the finished media itself. Generated-media completion events require message-tool delivery: the agent must send the finished media with the `message` tool, then reply `NO_REPLY`. If the requester session is no longer active or its active wake fails, and the completion agent misses some or all generated media, OpenClaw sends an idempotent direct fallback with only the missing media to the original channel target.
</Accordion>
<Accordion title="Concurrent media-generation guardrail">

View File

@@ -354,7 +354,7 @@ To restrict who can click a button, set `allowedUsers` on that button (Discord u
Component callbacks expire after 30 minutes by default. Set `channels.discord.agentComponents.ttlMs` to change that callback registry lifetime for the default Discord account, or `channels.discord.accounts.<accountId>.agentComponents.ttlMs` to override one account in a multi-account setup. The value is milliseconds, must be a positive integer, and is capped at `86400000` (24 hours). Longer TTLs are useful for review or approval workflows that need buttons to remain usable, but they also extend the window where an old Discord message can still trigger an action. Prefer the shortest TTL that fits the workflow, and keep the default when stale callbacks would be surprising.
The `/model` and `/models` slash commands open an interactive model picker with provider, model, and compatible runtime dropdowns plus a Submit step. `/models add` is deprecated and now returns a deprecation message instead of registering models from chat. The picker reply is ephemeral and only the invoking user can use it. Discord select menus are limited to 25 options, so add `provider/*` entries to `agents.defaults.models` when you want the picker to show dynamically discovered models only for selected providers such as `openai` or `vllm`.
The `/model` and `/models` slash commands open an interactive model picker with provider, model, and compatible runtime dropdowns plus a Submit step. `/models add` is deprecated and now returns a deprecation message instead of registering models from chat. The picker reply is ephemeral and only the invoking user can use it. Discord select menus are limited to 25 options, so add `provider/*` entries to `agents.defaults.models` when you want the picker to show dynamically discovered models only for selected providers such as `openai-codex` or `vllm`.
File attachments:
@@ -1197,7 +1197,7 @@ Auto-join example:
discord: {
voice: {
enabled: true,
model: "openai/gpt-5.5",
model: "openai-codex/gpt-5.5",
autoJoin: [
{
guildId: "123456789012345678",
@@ -1234,7 +1234,7 @@ Notes:
- `voice.followUsers` lets the bot join, move, and leave Discord voice with selected users. See [Follow users in voice](#follow-users-in-voice) for behavior rules and examples.
- `agent-proxy` routes speech through `discord-voice`, which preserves normal owner/tool authorization for the speaker and target session but hides the agent `tts` tool because Discord voice owns playback. By default, `agent-proxy` gives the consult full owner-equivalent tool access for owner speakers (`voice.realtime.toolPolicy: "owner"`) and strongly prefers consulting the OpenClaw agent before substantive answers (`voice.realtime.consultPolicy: "always"`). In that default `always` mode, the realtime layer does not auto-speak filler before the consult answer; it captures and transcribes speech, then speaks the routed OpenClaw answer. If multiple forced consult answers finish while Discord is still playing the first answer, later exact-speech answers are queued until playback idles instead of replacing speech mid-sentence.
- In `stt-tts` mode, STT uses `tools.media.audio`; `voice.model` does not affect transcription.
- In realtime modes, `voice.realtime.provider`, `voice.realtime.model`, and `voice.realtime.speakerVoice` configure the realtime audio session. For OpenAI Realtime 2 plus the Codex brain, use `voice.realtime.model: "gpt-realtime-2"` and `voice.model: "openai/gpt-5.5"`.
- In realtime modes, `voice.realtime.provider`, `voice.realtime.model`, and `voice.realtime.speakerVoice` configure the realtime audio session. For OpenAI Realtime 2 plus the Codex brain, use `voice.realtime.model: "gpt-realtime-2"` and `voice.model: "openai-codex/gpt-5.5"`.
- Realtime voice modes include small `IDENTITY.md`, `USER.md`, and `SOUL.md` profile files in the realtime provider instructions by default so fast direct turns keep the same identity, user grounding, and persona as the routed OpenClaw agent. Set `voice.realtime.bootstrapContextFiles` to a subset to customize this, or `[]` to disable it. The supported realtime bootstrap files are limited to those profile files; `AGENTS.md` stays in the normal agent context. The injected profile context does not replace `openclaw_agent_consult` for workspace work, current facts, memory lookup, or tool-backed actions.
- In OpenAI `agent-proxy` realtime mode, set `voice.realtime.requireWakeName: true` to keep Discord realtime voice silent until a transcript starts or ends with a wake name. Configured wake names must be one or two words. If `voice.realtime.wakeNames` is unset, OpenClaw uses the routed agent `name` plus `OpenClaw`, falling back to the agent id plus `OpenClaw`. Wake-name gating disables realtime provider auto-response, routes accepted turns through the OpenClaw agent consult path, and gives a short spoken acknowledgement when a leading wake name is recognized from partial transcription before the final transcript arrives.
- The OpenAI realtime provider accepts current Realtime 2 event names and legacy Codex-compatible aliases for output audio and transcript events, so compatible provider snapshots can drift without dropping assistant audio.
@@ -1325,7 +1325,7 @@ Default agent-proxy voice-channel session example:
discord: {
voice: {
enabled: true,
model: "openai/gpt-5.5",
model: "openai-codex/gpt-5.5",
followUsersEnabled: true,
followUsers: ["123456789012345678"],
realtime: {
@@ -1375,7 +1375,7 @@ Realtime bidi example:
voice: {
enabled: true,
mode: "bidi",
model: "openai/gpt-5.5",
model: "openai-codex/gpt-5.5",
realtime: {
provider: "openai",
model: "gpt-realtime-2",
@@ -1398,7 +1398,7 @@ Voice as an extension of an existing Discord channel session:
voice: {
enabled: true,
mode: "agent-proxy",
model: "openai/gpt-5.5",
model: "openai-codex/gpt-5.5",
agentSession: {
mode: "target",
target: "channel:123456789012345678",
@@ -1433,7 +1433,7 @@ Echo-heavy OpenAI Realtime example:
voice: {
enabled: true,
mode: "bidi",
model: "openai/gpt-5.5",
model: "openai-codex/gpt-5.5",
realtime: {
provider: "openai",
model: "gpt-realtime-2",

View File

@@ -318,11 +318,10 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- `channels.telegram.streaming` is `off | partial | block | progress` (default: `partial`)
- `progress` keeps one editable status draft for tool progress, clears it at completion, and sends the final answer as a normal message
- `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true` when preview streaming is active)
- `streaming.progress.commentary` (default `false`) opts into Codex preamble/commentary text in the temporary progress draft. Commentary is cleaned before display, stays transient, and does not change final answer delivery.
- `streaming.preview.commandText` controls command/exec detail inside those tool-progress lines: `raw` (default, preserves released behavior) or `status` (tool label only)
- legacy `channels.telegram.streamMode` and boolean `streaming` values are detected; run `openclaw doctor --fix` to migrate them to `channels.telegram.streaming.mode`
Tool-progress preview updates are the short status lines shown while tools run, for example command execution, file reads, planning updates, and patch summaries. Telegram keeps these enabled by default to match released OpenClaw behavior from `v2026.4.22` and later.
Tool-progress preview updates are the short status lines shown while tools run, for example command execution, file reads, planning updates, patch summaries, or Codex preamble/commentary text in Codex app-server mode. Telegram keeps these enabled by default to match released OpenClaw behavior from `v2026.4.22` and later.
Direct chats can use native Telegram drafts for these tool-progress lines without persisting tool chatter into chat history. Native drafts stop before answer text starts; final answers stay on the normal persistent delivery path. This lane is off by default and should be gated to trusted DM IDs first:

View File

@@ -203,7 +203,7 @@ Notes:
- Doctor reports cron jobs with explicit `payload.model` overrides, including provider namespace counts and mismatches against `agents.defaults.model`, so scheduled jobs that do not inherit the default model are visible during auth or billing investigations.
- On Linux, doctor warns when the user's crontab still runs legacy `~/.openclaw/bin/ensure-whatsapp.sh`; that script is no longer maintained and can log false WhatsApp gateway outages when cron lacks the systemd user-bus environment.
- When WhatsApp is enabled, doctor checks for a degraded Gateway event loop with local `openclaw-tui` clients still running. `doctor --fix` stops only verified local TUI clients so WhatsApp replies are not queued behind stale TUI refresh loops.
- Doctor rewrites legacy `openai-codex/*` model refs to canonical `openai/*` refs across primary models, fallbacks, image/video generation models, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale session route pins. `--fix` also migrates legacy `openai-codex:*` auth profiles and `auth.order.openai-codex` entries to `openai:*`, moves Codex intent onto provider/model-scoped `agentRuntime.id: "codex"` entries, removes stale whole-agent/session runtime pins, and keeps repaired OpenAI agent refs on Codex auth routing instead of direct OpenAI API-key auth.
- Doctor rewrites legacy `openai-codex/*` model refs to canonical `openai/*` refs across primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale session route pins. `--fix` moves Codex intent onto provider/model-scoped `agentRuntime.id: "codex"` entries, preserves session auth-profile pins such as `openai-codex:...`, removes stale whole-agent/session runtime pins, and keeps repaired OpenAI agent refs on Codex auth routing instead of direct OpenAI API-key auth.
- Doctor cleans legacy plugin dependency staging state created by older OpenClaw versions and relinks the host `openclaw` package for managed npm plugins that declare it as a peer dependency. It also repairs missing downloadable plugins that are referenced by config, such as `plugins.entries`, configured channels, configured provider/search settings, or configured agent runtimes. During package updates, doctor skips package-manager plugin repair until the package swap is complete; rerun `openclaw doctor --fix` afterward if a configured plugin still needs recovery. If the download fails, doctor reports the install error and preserves the configured plugin entry for the next repair attempt.
- Doctor repairs stale plugin config by removing missing plugin ids from `plugins.allow`/`plugins.deny`/`plugins.entries`, plus matching dangling channel config, heartbeat targets, and channel model overrides when plugin discovery is healthy.
- Doctor quarantines invalid plugin config by disabling the affected `plugins.entries.<id>` entry and removing its invalid `config` payload. Gateway startup already skips only that bad plugin so other plugins and channels can keep running.

View File

@@ -129,7 +129,7 @@ This table maps common inference tasks to the corresponding infer command.
- Use `model run --thinking <level>` to pass a one-shot thinking/reasoning level (`off`, `minimal`, `low`, `medium`, `high`, `adaptive`, `xhigh`, or `max`) while keeping the run raw.
- For `image describe`, `audio transcribe`, and `video describe`, `--model` must use the form `<provider/model>`.
- For `image describe`, `--file` accepts local paths and HTTP(S) image URLs. Remote URLs use the normal media-fetch SSRF policy.
- For `image describe`, an explicit `--model` runs that provider/model directly. The model must be image-capable in the model catalog or provider config. `codex/<model>` runs a bounded Codex app-server image-understanding turn; `openai/<model>` uses the OpenAI provider path with either API-key or ChatGPT/Codex OAuth auth.
- For `image describe`, an explicit `--model` runs that provider/model directly. The model must be image-capable in the model catalog or provider config. `codex/<model>` runs a bounded Codex app-server image-understanding turn; `openai-codex/<model>` uses the OpenAI Codex OAuth provider path.
- Stateless execution commands default to local.
- Gateway-managed state commands default to gateway.
- The normal local path does not require the gateway to be running.
@@ -172,7 +172,7 @@ Notes:
- Local `model run` is the narrowest CLI smoke for provider/model/auth health because, for non-Codex providers, it sends only the supplied prompt to the selected model.
- Local `model run --model <provider/model>` can use exact bundled static catalog rows from `models list --all` before that provider is written to config. Provider auth is still required; missing credentials fail as auth errors, not `Unknown model`.
- For Mistral Medium 3.5 reasoning probes, leave temperature unset/default. Mistral rejects `reasoning_effort="high"` plus `temperature: 0`; use `mistral/mistral-medium-3-5` with default temperature or a non-zero reasoning-mode value such as `0.7`.
- Codex Responses local probes are the narrow exception: OpenClaw adds a minimal system instruction so the transport can populate its required `instructions` field, without adding full agent context, tools, memory, or session transcript.
- `openai-codex/*` local probes are the narrow exception: OpenClaw adds a minimal system instruction so the Codex Responses transport can populate its required `instructions` field, without adding full agent context, tools, memory, or session transcript.
- Local `model run --file` keeps that lean path and attaches image content directly to the single user message. Common image files such as PNG, JPEG, and WebP work when their MIME type is detected as `image/*`; unsupported or unrecognized files fail before the provider is called.
- `model run --file` is best when you want to test the selected multimodal text model directly. Use `infer image describe` when you want OpenClaw's image-understanding provider selection and default image-model routing.
- The selected model must support image input; text-only models may reject the request at the provider layer.

View File

@@ -28,8 +28,8 @@ openclaw models scan
`openclaw models status` shows the resolved default/fallbacks plus an auth overview.
When provider usage snapshots are available, the OAuth/API-key status section includes
provider usage windows and quota snapshots.
Current usage-window providers: Anthropic, GitHub Copilot, Gemini CLI, OpenAI,
MiniMax, Xiaomi, and z.ai. Usage auth comes from provider-specific hooks
Current usage-window providers: Anthropic, GitHub Copilot, Gemini CLI, OpenAI
Codex, MiniMax, Xiaomi, and z.ai. Usage auth comes from provider-specific hooks
when available; otherwise OpenClaw falls back to matching OAuth/API-key
credentials from auth profiles, env, or config.
In `--json` output, `auth.providers` is the env/config/store-aware provider
@@ -40,10 +40,10 @@ Use `--agent <id>` to inspect a configured agent's model/auth state. When omitte
the command uses `OPENCLAW_AGENT_DIR` if set, otherwise the
configured default agent.
Probe rows can come from auth profiles, env credentials, or `models.json`.
For OpenAI ChatGPT/Codex OAuth troubleshooting, `openclaw models status`,
`openclaw models auth list --provider openai`, and
For Codex OAuth troubleshooting, `openclaw models status`,
`openclaw models auth list --provider openai-codex`, and
`openclaw config get agents.defaults.model --json` are the quickest way to
confirm whether an agent has a usable `openai` OAuth profile for
confirm whether an agent has a usable `openai-codex` auth profile for
`openai/*` through the native Codex runtime. See [OpenAI provider setup](/providers/openai#check-and-recover-codex-oauth-routing).
Notes:
@@ -76,7 +76,7 @@ Notes:
cap differs from the native context window; JSON rows include `contextTokens`
when a provider exposes that cap.
- `models list --provider <id>` filters by provider id, such as `moonshot` or
`openai`. It does not accept display labels from interactive provider
`openai-codex`. It does not accept display labels from interactive provider
pickers, such as `Moonshot AI`.
- Model refs are parsed by splitting on the **first** `/`. If the model ID includes `/` (OpenRouter-style), include the provider prefix (example: `openrouter/moonshotai/kimi-k2`).
- If you omit the provider, OpenClaw resolves the input as an alias first, then
@@ -181,7 +181,7 @@ provider you choose.
`models auth list` lists saved auth profiles for the selected agent without
printing token, API-key, or OAuth secret material. Use `--provider <id>` to
filter to one provider, such as `openai`, and `--json` for scripting.
filter to one provider, such as `openai-codex`, and `--json` for scripting.
`models auth login` runs a provider plugin's auth flow (OAuth/API key). Use
`openclaw plugins list` to see which providers are installed.
@@ -192,15 +192,15 @@ specific configured agent store. The parent `--agent` flag is honored by
For OpenAI models, `--provider openai` defaults to ChatGPT/Codex account login.
Use `--method api-key` only when you want to add an OpenAI API-key profile,
usually as a backup for Codex subscription limits. Run `openclaw doctor --fix`
to migrate older `openai-codex` auth/profile state to `openai`.
usually as a backup for Codex subscription limits. The legacy
`--provider openai-codex` spelling still works for existing scripts.
Examples:
```bash
openclaw models auth login --provider openai --set-default
openclaw models auth login --provider openai --method api-key
openclaw models auth paste-api-key --provider openai
openclaw models auth paste-api-key --provider openai-codex
openclaw models auth list --provider openai
```
@@ -212,7 +212,7 @@ Notes:
- `paste-api-key` accepts API keys generated elsewhere, prompts for the key
value, and writes it to the default profile id `<provider>:manual` unless you
pass `--profile-id`. In automation, pipe the key on stdin, for example
`printf "%s\n" "$OPENAI_API_KEY" | openclaw models auth paste-api-key --provider openai`.
`printf "%s\n" "$OPENAI_API_KEY" | openclaw models auth paste-api-key --provider openai-codex`.
- `setup-token` and `paste-token` remain generic token commands for providers
that expose token auth methods.
- `setup-token` requires an interactive TTY and runs the provider's token-auth
@@ -226,7 +226,7 @@ Notes:
provider credentials do not appear in shell history or process lists.
- `paste-token --expires-in <duration>` stores an absolute token expiry from a
relative duration such as `365d` or `12h`.
- For `openai`, OpenAI API keys and ChatGPT/OAuth token material are
- For `openai-codex`, OpenAI API keys and ChatGPT/OAuth token material are
different auth shapes. Use `paste-api-key` for `sk-...` OpenAI API keys and
`paste-token` only for token auth material.
- Anthropic note: Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so OpenClaw treats Claude CLI reuse and `claude -p` usage as sanctioned for this integration unless Anthropic publishes a new policy.

View File

@@ -326,8 +326,6 @@ Use `--link` to avoid copying a local directory (adds to `plugins.load.paths`):
openclaw plugins install -l ./my-plugin
```
Standalone plugin files must be listed in `plugins.load.paths` rather than placed directly in `~/.openclaw/extensions` or `<workspace>/.openclaw/extensions`. Those auto-discovered roots load plugin package or bundle directories, while top-level script files are treated as local helpers and skipped.
<Note>
`--force` is not supported with `--link` because linked installs reuse the source path instead of copying over a managed install target.

View File

@@ -43,7 +43,6 @@ Notes:
- Local mode uses the embedded agent runtime directly. Most local tools work, but Gateway-only features are unavailable.
- Local mode adds `/auth [provider]` inside the TUI command surface.
- Plugin approval gates still apply in local mode. Tools that require approval prompt for a decision in the terminal; nothing is silently auto-approved because the Gateway is not involved.
- Session [goals](/tools/goal) appear in the footer and can be managed with `/goal`.
## Examples
@@ -88,4 +87,3 @@ rerun `openclaw config validate`. See [TUI](/web/tui) and [Config](/cli/config).
- [CLI reference](/cli)
- [TUI](/web/tui)
- [Goal](/tools/goal)

View File

@@ -16,7 +16,7 @@ configuration. They are different layers:
| Layer | Examples | What it means |
| ------------- | -------------------------------------------- | ------------------------------------------------------------------- |
| Provider | `openai`, `anthropic`, `github-copilot` | How OpenClaw authenticates, discovers models, and names model refs. |
| Provider | `openai`, `anthropic`, `openai-codex` | How OpenClaw authenticates, discovers models, and names model refs. |
| Model | `gpt-5.5`, `claude-opus-4-6` | The model selected for the agent turn. |
| Agent runtime | `openclaw`, `codex`, `copilot`, `claude-cli` | The low level loop or backend that executes the prepared turn. |
| Channel | Telegram, Discord, Slack, WhatsApp | Where messages enter and leave OpenClaw. |
@@ -40,7 +40,7 @@ There are two runtime families:
model, execute through Claude CLI." `claude-cli` is not an embedded harness id
and must not be passed to AgentHarness selection.
The `copilot` harness is a separate, opt-in external plugin harness for the
The `copilot` harness is a separate, opt-in plugin harness for the
GitHub Copilot CLI; see [GitHub Copilot agent runtime](/plugins/copilot)
for the user-facing decision between PI, Codex, and GitHub Copilot agent runtime.
@@ -51,7 +51,7 @@ Most confusion comes from several different surfaces sharing the Codex name:
| Surface | OpenClaw name/config | What it does |
| ------------------------------------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| Native Codex app-server runtime | `openai/*` model refs | Runs OpenAI embedded agent turns through Codex app-server. This is the usual ChatGPT/Codex subscription setup. |
| Codex OAuth auth profiles | `openai` OAuth profiles | Stores ChatGPT/Codex subscription auth that the Codex app-server harness consumes. |
| Codex OAuth auth profiles | `openai-codex` auth provider | Stores ChatGPT/Codex subscription auth that the Codex app-server harness consumes. |
| Codex ACP adapter | `runtime: "acp"`, `agentId: "codex"` | Runs Codex through the external ACP/acpx control plane. Use only when ACP/acpx is explicitly asked. |
| Native Codex chat-control command set | `/codex ...` | Binds, resumes, steers, stops, and inspects Codex app-server threads from chat. |
| OpenAI Platform API route for non-agent surfaces | `openai/*` plus API-key auth | Used for direct OpenAI APIs such as images, embeddings, speech, and realtime. |
@@ -95,7 +95,7 @@ This is the agent-facing decision tree:
subscription-backed Codex agent experience, use `openai/<model>`.
3. If the user explicitly chooses **OpenClaw for an OpenAI model**, keep the model ref
as `openai/<model>` and set provider/model runtime policy to
`agentRuntime.id: "openclaw"`. A selected `openai` OAuth profile is routed
`agentRuntime.id: "openclaw"`. A selected `openai-codex` auth profile is routed
internally through OpenClaw's Codex-auth transport.
4. If legacy config still contains **`openai-codex/*` model refs**, repair it to
`openai/<model>` with `openclaw doctor --fix`; doctor keeps the Codex auth
@@ -112,7 +112,7 @@ This is the agent-facing decision tree:
| --------------------------------------- | -------------------------------------------- |
| Codex app-server chat/thread control | `/codex ...` from the bundled `codex` plugin |
| Codex app-server embedded agent runtime | `openai/*` agent model refs |
| OpenAI Codex OAuth | `openai` OAuth profiles |
| OpenAI Codex OAuth | `openai-codex` auth profiles |
| Claude Code or other external harness | ACP/acpx |
For the OpenAI-family prefix split, see [OpenAI](/providers/openai) and
@@ -196,7 +196,7 @@ backend.
`auto` mode is intentionally conservative for most providers. OpenAI agent
models are the exception: unset runtime and `auto` both resolve to the Codex
harness. Explicit OpenClaw runtime config remains an opt-in compatibility route for
`openai/*` agent turns; when paired with a selected `openai` OAuth profile,
`openai/*` agent turns; when paired with a selected `openai-codex` auth profile,
OpenClaw routes that path internally through the Codex-auth transport while
keeping the public model ref as `openai/*`. Stale OpenAI runtime session pins are
ignored by runtime selection and can be cleaned with `openclaw doctor --fix`.
@@ -207,7 +207,7 @@ If `openclaw doctor` warns that the `codex` plugin is enabled while
## GitHub Copilot agent runtime
The external `@openclaw/copilot` plugin registers an opt-in `copilot` runtime
The bundled `copilot` extension registers an opt-in `copilot` runtime
backed by the GitHub Copilot CLI (`@github/copilot-sdk`). It claims the
canonical subscription `github-copilot` provider and is **never** selected by
`auto`. Opt in per-model or per-provider via `agentRuntime.id`:

View File

@@ -156,14 +156,15 @@ Use `auth.order.openai` for the user-facing order:
{
auth: {
order: {
openai: ["openai:user@example.com", "openai:api-key-backup"],
openai: ["openai-codex:user@example.com", "openai:api-key-backup"],
},
},
}
```
Use `openai:*` for both ChatGPT/Codex OAuth profiles and OpenAI API-key
profiles. When the subscription hits a Codex usage limit,
Existing Codex subscription profiles may still use the legacy
`openai-codex:*` profile id. The ordered API-key backup can be a normal
`openai:*` API-key profile. When the subscription hits a Codex usage limit,
OpenClaw records the exact reset time when Codex provides one, tries the next
ordered auth profile, and keeps the run inside the Codex harness. Once the reset
time passes, the subscription profile is eligible again and the next automatic

View File

@@ -130,24 +130,24 @@ Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so Ope
}
```
### OpenAI ChatGPT/Codex OAuth
### OpenAI Codex OAuth
- Provider: `openai`
- Provider: `openai-codex`
- Auth: OAuth (ChatGPT)
- Legacy OpenAI Codex model ref: `openai/gpt-5.5`
- Legacy OpenAI Codex model ref: `openai-codex/gpt-5.5`
- Native Codex app-server harness ref: `openai/gpt-5.5`
- Native Codex app-server harness docs: [Codex harness](/plugins/codex-harness)
- Legacy model refs: `codex/gpt-*`
- Plugin boundary: `openai/*` loads the OpenAI plugin; the native Codex app-server plugin is selected by the Codex harness runtime.
- CLI: `openclaw onboard --auth-choice openai` or `openclaw models auth login --provider openai`
- Plugin boundary: `openai-codex/*` loads the OpenAI plugin; the native Codex app-server plugin is selected only by the Codex harness runtime or legacy `codex/*` refs.
- CLI: `openclaw onboard --auth-choice openai-codex` or `openclaw models auth login --provider openai-codex`
- Default transport is `auto` (WebSocket-first, SSE fallback)
- Override per OpenAI Codex model via `agents.defaults.models["openai/<model>"].params.transport` (`"sse"`, `"websocket"`, or `"auto"`)
- Override per OpenAI Codex model via `agents.defaults.models["openai-codex/<model>"].params.transport` (`"sse"`, `"websocket"`, or `"auto"`)
- `params.serviceTier` is also forwarded on native Codex Responses requests (`chatgpt.com/backend-api`)
- Hidden OpenClaw attribution headers (`originator`, `version`, `User-Agent`) are only attached on native Codex traffic to `chatgpt.com/backend-api`, not generic OpenAI-compatible proxies
- Shares the same `/fast` toggle and `params.fastMode` config as direct `openai/*`; OpenClaw maps that to `service_tier=priority`
- `openai/gpt-5.5` uses the Codex catalog native `contextWindow = 400000` and default runtime `contextTokens = 272000`; override the runtime cap with `models.providers.openai.models[].contextTokens`
- `openai-codex/gpt-5.5` uses the Codex catalog native `contextWindow = 400000` and default runtime `contextTokens = 272000`; override the runtime cap with `models.providers.openai-codex.models[].contextTokens`
- Policy note: OpenAI Codex OAuth is explicitly supported for external tools/workflows like OpenClaw.
- For the common subscription plus native Codex runtime route, sign in with `openai` auth and configure `openai/gpt-5.5`; OpenAI agent turns select Codex by default.
- For the common subscription plus native Codex runtime route, sign in with `openai-codex` auth but configure `openai/gpt-5.5`; OpenAI agent turns select Codex by default.
- Use provider/model `agentRuntime.id: "openclaw"` only when you want the built-in OpenClaw route; otherwise keep `openai/gpt-5.5` on the default Codex harness.
- `openai-codex/gpt-*` refs remain a legacy OpenAI Codex route. Prefer `openai/gpt-5.5` on the native Codex runtime for new agent config, and run `openclaw doctor --fix` when you want to migrate old `openai-codex/*` refs to canonical `openai/*` refs.
@@ -166,7 +166,7 @@ Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so Ope
{
models: {
providers: {
openai: {
"openai-codex": {
models: [{ id: "gpt-5.5", contextTokens: 160000 }],
},
},
@@ -290,36 +290,32 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
### Other bundled provider plugins
| Provider | Id | Auth env | Example model |
| --------------------------------------- | -------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------- |
| BytePlus | `byteplus` / `byteplus-plan` | `BYTEPLUS_API_KEY` | `byteplus-plan/ark-code-latest` |
| Cerebras | `cerebras` | `CEREBRAS_API_KEY` | `cerebras/zai-glm-4.7` |
| Cloudflare AI Gateway | `cloudflare-ai-gateway` | `CLOUDFLARE_AI_GATEWAY_API_KEY` | - |
| DeepInfra | `deepinfra` | `DEEPINFRA_API_KEY` | `deepinfra/deepseek-ai/DeepSeek-V4-Flash` |
| DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-v4-flash` |
| GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` | - |
| GMI Cloud | `gmi` | `GMI_API_KEY` | `gmi/google/gemini-3.1-flash-lite` |
| Groq | `groq` | `GROQ_API_KEY` | - |
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |
| Kilo Gateway | `kilocode` | `KILOCODE_API_KEY` | `kilocode/kilo/auto` |
| Kimi Coding | `kimi` | `KIMI_API_KEY` or `KIMICODE_API_KEY` | `kimi/kimi-for-coding` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M2.7` |
| Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` |
| Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` |
| NVIDIA | `nvidia` | `NVIDIA_API_KEY` | `nvidia/nvidia/nemotron-3-super-120b-a12b` |
| NovitaAI | `novita` | `NOVITA_API_KEY` | `novita/deepseek/deepseek-v3-0324` |
| [Ollama Cloud](/providers/ollama-cloud) | `ollama-cloud` | `OLLAMA_API_KEY` | `ollama-cloud/kimi-k2.6` |
| OpenRouter | `openrouter` | `OPENROUTER_API_KEY` | `openrouter/auto` |
| Qianfan | `qianfan` | `QIANFAN_API_KEY` | `qianfan/deepseek-v3.2` |
| Qwen Cloud | `qwen` | `QWEN_API_KEY` / `MODELSTUDIO_API_KEY` / `DASHSCOPE_API_KEY` | `qwen/qwen3.5-plus` |
| [Qwen OAuth](/providers/qwen-oauth) | `qwen-oauth` | `QWEN_API_KEY` | `qwen-oauth/qwen3.5-plus` |
| StepFun | `stepfun` / `stepfun-plan` | `STEPFUN_API_KEY` | `stepfun/step-3.5-flash` |
| Together | `together` | `TOGETHER_API_KEY` | `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` |
| Venice | `venice` | `VENICE_API_KEY` | - |
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | `xai/grok-4.3` |
| Xiaomi | `xiaomi` / `xiaomi-token-plan` | `XIAOMI_API_KEY` / `XIAOMI_TOKEN_PLAN_API_KEY` | `xiaomi/mimo-v2-flash` / `xiaomi-token-plan/mimo-v2.5-pro` |
| Provider | Id | Auth env | Example model |
| ----------------------- | -------------------------------- | ------------------------------------------------------------ | -------------------------------------------------- |
| BytePlus | `byteplus` / `byteplus-plan` | `BYTEPLUS_API_KEY` | `byteplus-plan/ark-code-latest` |
| Cerebras | `cerebras` | `CEREBRAS_API_KEY` | `cerebras/zai-glm-4.7` |
| Cloudflare AI Gateway | `cloudflare-ai-gateway` | `CLOUDFLARE_AI_GATEWAY_API_KEY` | - |
| DeepInfra | `deepinfra` | `DEEPINFRA_API_KEY` | `deepinfra/deepseek-ai/DeepSeek-V4-Flash` |
| DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-v4-flash` |
| GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` | - |
| Groq | `groq` | `GROQ_API_KEY` | - |
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |
| Kilo Gateway | `kilocode` | `KILOCODE_API_KEY` | `kilocode/kilo/auto` |
| Kimi Coding | `kimi` | `KIMI_API_KEY` or `KIMICODE_API_KEY` | `kimi/kimi-for-coding` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M2.7` |
| Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` |
| Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` |
| NVIDIA | `nvidia` | `NVIDIA_API_KEY` | `nvidia/nvidia/nemotron-3-super-120b-a12b` |
| OpenRouter | `openrouter` | `OPENROUTER_API_KEY` | `openrouter/auto` |
| Qianfan | `qianfan` | `QIANFAN_API_KEY` | `qianfan/deepseek-v3.2` |
| Qwen Cloud | `qwen` | `QWEN_API_KEY` / `MODELSTUDIO_API_KEY` / `DASHSCOPE_API_KEY` | `qwen/qwen3.5-plus` |
| StepFun | `stepfun` / `stepfun-plan` | `STEPFUN_API_KEY` | `stepfun/step-3.5-flash` |
| Together | `together` | `TOGETHER_API_KEY` | `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` |
| Venice | `venice` | `VENICE_API_KEY` | - |
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | `xai/grok-4.3` |
| Xiaomi | `xiaomi` | `XIAOMI_API_KEY` | `xiaomi/mimo-v2-flash` |
#### Quirks worth knowing

View File

@@ -23,7 +23,7 @@ sidebarTitle: "Models CLI"
</Card>
</CardGroup>
Model refs choose a provider and model. They do not usually choose the low-level agent runtime. OpenAI agent refs are the main exception: `openai/gpt-5.5` runs through the Codex app-server runtime by default on the official OpenAI provider. Subscription Copilot refs (`github-copilot/*`) can additionally be opted into the external GitHub Copilot agent runtime plugin — that path stays explicit (no `auto` fallback). Explicit runtime overrides belong on provider/model policy, not on the whole agent or session. In Codex runtime mode, the `openai/gpt-*` ref does not imply API-key billing; auth can come from a Codex account or `openai` OAuth profile. See [Agent runtimes](/concepts/agent-runtimes) and [GitHub Copilot agent runtime](/plugins/copilot).
Model refs choose a provider and model. They do not usually choose the low-level agent runtime. OpenAI agent refs are the main exception: `openai/gpt-5.5` runs through the Codex app-server runtime by default on the official OpenAI provider. Subscription Copilot refs (`github-copilot/*`) can additionally be opted into the bundled GitHub Copilot agent runtime — that path stays explicit (no `auto` fallback). Explicit runtime overrides belong on provider/model policy, not on the whole agent or session. In Codex runtime mode, the `openai/gpt-*` ref does not imply API-key billing; auth can come from a Codex account or `openai-codex` auth profile. See [Agent runtimes](/concepts/agent-runtimes) and [GitHub Copilot agent runtime](/plugins/copilot).
## How model selection works
@@ -150,7 +150,7 @@ If you want to limit providers without manually listing every model, add
agents: {
defaults: {
models: {
"openai/*": {},
"openai-codex/*": {},
"vllm/*": {},
},
},

View File

@@ -45,7 +45,7 @@ To reduce that, OpenClaw treats `auth-profiles.json` as a **token sink**:
- the runtime reads credentials from **one place**
- we can keep multiple profiles and route them deterministically
- external CLI reuse is provider-specific: Codex CLI can bootstrap an empty
`openai:default` profile, but once OpenClaw has a local OAuth profile,
`openai-codex:default` profile, but once OpenClaw has a local OAuth profile,
the local refresh token is canonical. If that local refresh token is rejected,
OpenClaw can use a usable same-account Codex CLI token as a runtime-only
fallback; other integrations can remain externally managed and re-read their
@@ -131,7 +131,7 @@ Flow shape (PKCE):
5. exchange at `https://auth.openai.com/oauth/token`
6. extract `accountId` from the access token and store `{ access, refresh, expires, accountId }`
Wizard path is `openclaw onboard` → auth choice `openai`.
Wizard path is `openclaw onboard` → auth choice `openai-codex`.
## Refresh + expiry
@@ -147,7 +147,7 @@ At runtime:
- exception: some external CLI credentials stay externally managed; OpenClaw
re-reads those CLI auth stores instead of spending copied refresh tokens.
Codex CLI bootstrap is intentionally narrower: it seeds an empty
`openai:default` profile, then OpenClaw-owned refreshes keep the local
`openai-codex:default` profile, then OpenClaw-owned refreshes keep the local
profile canonical. If the local Codex refresh fails and Codex CLI has a
usable token for the same account, OpenClaw may use that token for the current
runtime request without writing it back to `auth-profiles.json`.

View File

@@ -266,10 +266,6 @@ The doctor checks Convex broker env, validates endpoint settings, and verifies a
Live transport lanes share one contract instead of each inventing their own scenario list shape. `qa-channel` is the broad synthetic product-behavior suite and is not part of the live transport coverage matrix.
Live transport runners should import the shared scenario ids, baseline
coverage helpers, and scenario-selection helper from
`openclaw/plugin-sdk/qa-live-transport-scenarios`.
| Lane | Canary | Mention gating | Bot-to-bot | Allowlist block | Top-level reply | Restart resume | Thread follow-up | Thread isolation | Reaction observation | Help command | Native command registration |
| -------- | ------ | -------------- | ---------- | --------------- | --------------- | -------------- | ---------------- | ---------------- | -------------------- | ------------ | --------------------------- |
| Matrix | x | x | x | x | x | x | x | x | x | | |

View File

@@ -181,14 +181,12 @@ prompt surface that matches their lifetime:
On the native Codex harness, OpenClaw avoids repeating stable workspace files
in every user turn. Codex loads `AGENTS.md` through its own project-doc
discovery. `SOUL.md`, `IDENTITY.md`, `TOOLS.md`, and `USER.md` are forwarded as
Codex developer instructions. The compact OpenClaw skills list is also forwarded
as turn-scoped collaboration developer instructions. `HEARTBEAT.md` content is
not injected; heartbeat turns get a collaboration-mode note pointing to the file
when it exists and is non-empty. `MEMORY.md` content from the configured agent
workspace is not pasted into every native Codex turn; when memory tools are
available for that workspace, Codex turns get a small workspace-memory note in
turn-scoped collaboration developer instructions and should use `memory_search`
or `memory_get` when durable memory is relevant. If tools are disabled, memory
Codex developer instructions. `HEARTBEAT.md` content is not injected; heartbeat
turns get a collaboration-mode note pointing to the file when it exists and is
non-empty. `MEMORY.md` content from the configured agent workspace is not pasted
into every native Codex turn; when memory tools are available for that workspace,
Codex turns get a small workspace-memory note and should use `memory_search` or
`memory_get` when durable memory is relevant. If tools are disabled, memory
search is unavailable, or the active workspace differs from the agent memory
workspace, `MEMORY.md` falls back to the normal bounded turn-context path. Active
`BOOTSTRAP.md` content keeps the normal turn-context role for now.
@@ -260,11 +258,6 @@ prompt instructs the model to use `read` to load the SKILL.md at the listed
location (workspace, managed, or bundled). If no skills are eligible, the
Skills section is omitted.
Native Codex turns receive this list as turn-scoped collaboration developer
instructions instead of per-turn user input, except lightweight cron turns that
preserve the exact scheduled prompt. Other harnesses keep the normal prompt
section.
The location can point at a nested skill, such as
`skills/personal/foo/SKILL.md`. Nesting is only organizational; the prompt still
uses the flat skill name from `SKILL.md` frontmatter.

View File

@@ -1333,7 +1333,6 @@
"group": "Agent coordination",
"pages": [
"tools/agent-send",
"tools/goal",
"tools/steer",
"tools/subagents",
"tools/acp-agents",
@@ -1405,7 +1404,6 @@
"providers/fal",
"providers/fireworks",
"providers/github-copilot",
"providers/gmi",
"providers/google",
"providers/gradium",
"providers/groq",
@@ -1418,10 +1416,8 @@
"providers/minimax",
"providers/mistral",
"providers/moonshot",
"providers/novita",
"providers/nvidia",
"providers/ollama",
"providers/ollama-cloud",
"providers/openai",
"providers/opencode",
"providers/opencode-go",
@@ -1430,7 +1426,6 @@
"providers/pixverse",
"providers/qianfan",
"providers/qwen",
"providers/qwen-oauth",
"providers/runway",
"providers/senseaudio",
"providers/sglang",

View File

@@ -199,8 +199,8 @@ Use `openclaw models auth login --provider <id> --profile-id <profileId>` for
providers that support named auth profiles during login.
```bash
openclaw models auth login --provider openai --profile-id openai:ritsuko
openclaw models auth login --provider openai --profile-id openai:lain
openclaw models auth login --provider openai-codex --profile-id openai-codex:ritsuko
openclaw models auth login --provider openai-codex --profile-id openai-codex:lain
```
This is the easiest way to keep multiple OAuth logins for the same provider

View File

@@ -450,7 +450,7 @@ Time format in system prompt. Default: `auto` (OS preference).
- `elevatedDefault`: default elevated-output level for agents. Values: `"off"`, `"on"`, `"ask"`, `"full"`. Default: `"on"`.
- `model.primary`: format `provider/model` (e.g. `openai/gpt-5.5` for OpenAI API-key or Codex OAuth access). If you omit the provider, OpenClaw tries an alias first, then a unique configured-provider match for that exact model id, and only then falls back to the configured default provider (deprecated compatibility behavior, so prefer explicit `provider/model`). If that provider no longer exposes the configured default model, OpenClaw falls back to the first configured provider/model instead of surfacing a stale removed-provider default.
- `models`: the configured model catalog and allowlist for `/model`. Each entry can include `alias` (shortcut) and `params` (provider-specific, for example `temperature`, `maxTokens`, `cacheRetention`, `context1m`, `responsesServerCompaction`, `responsesCompactThreshold`, OpenRouter `provider` routing, `chat_template_kwargs`, `extra_body`/`extraBody`).
- Use `provider/*` entries such as `"openai/*": {}` or `"vllm/*": {}` to show all discovered models for selected providers without manually listing every model id.
- Use `provider/*` entries such as `"openai-codex/*": {}` or `"vllm/*": {}` to show all discovered models for selected providers without manually listing every model id.
- Add `agentRuntime` to a `provider/*` entry when every dynamically discovered model for that provider should use the same runtime. Exact `provider/model` runtime policy still wins over the wildcard.
- Safe edits: use `openclaw config set agents.defaults.models '<json>' --strict-json --merge` to add entries. `config set` refuses replacements that would remove existing allowlist entries unless you pass `--replace`.
- Provider-scoped configure/onboarding flows merge selected provider models into this map and preserve unrelated providers already configured.
@@ -617,7 +617,7 @@ Periodic heartbeat runs.
- `every`: duration string (ms/s/m/h). Default: `30m` (API-key auth) or `1h` (OAuth auth). Set to `0m` to disable.
- `includeSystemPromptSection`: when false, omits the Heartbeat section from the system prompt and skips `HEARTBEAT.md` injection into bootstrap context. Default: `true`.
- `suppressToolErrorWarnings`: when true, suppresses tool error warning payloads during heartbeat runs.
- `timeoutSeconds`: maximum time in seconds allowed for a heartbeat agent turn before it is aborted. Leave unset to use `agents.defaults.timeoutSeconds` when set, otherwise the heartbeat cadence capped at 600 seconds.
- `timeoutSeconds`: maximum time in seconds allowed for a heartbeat agent turn before it is aborted. Leave unset to use `agents.defaults.timeoutSeconds`.
- `directPolicy`: direct/DM delivery policy. `allow` (default) permits direct-target delivery. `block` suppresses direct-target delivery and emits `reason=dm-blocked`.
- `lightContext`: when true, heartbeat runs use lightweight bootstrap context and keep only `HEARTBEAT.md` from workspace bootstrap files.
- `isolatedSession`: when true, each heartbeat runs in a fresh session with no prior conversation history. Same isolation pattern as cron `sessionTarget: "isolated"`. Reduces per-heartbeat token cost from ~100K to ~2-5K tokens.

View File

@@ -386,13 +386,12 @@ Controls inline attachment support for `sessions_spawn`.
<AccordionGroup>
<Accordion title="Attachment notes">
- Attachments require `enabled: true`.
- Subagent attachments are materialized into the child workspace at `.openclaw/attachments/<uuid>/` with a `.manifest.json`.
- ACP attachments are image-only and forwarded inline to the ACP runtime after the same file count, per-file byte, and total byte limits pass.
- Attachments are only supported for `runtime: "subagent"`. ACP runtime rejects them.
- Files are materialized into the child workspace at `.openclaw/attachments/<uuid>/` with a `.manifest.json`.
- Attachment content is automatically redacted from transcript persistence.
- Base64 inputs are validated with strict alphabet/padding checks and a pre-decode size guard.
- Subagent attachment file permissions are `0700` for directories and `0600` for files.
- Subagent cleanup follows the `cleanup` policy: `delete` always removes attachments; `keep` retains them only when `retainOnSessionKeep: true`.
- File permissions are `0700` for directories and `0600` for files.
- Cleanup follows the `cleanup` policy: `delete` always removes attachments; `keep` retains them only when `retainOnSessionKeep: true`.
</Accordion>
</AccordionGroup>

View File

@@ -82,11 +82,12 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
"anthropic:default": { provider: "anthropic", mode: "api_key" },
"anthropic:work": { provider: "anthropic", mode: "api_key" },
"openai:default": { provider: "openai", mode: "api_key" },
"openai:personal": { provider: "openai", mode: "oauth" },
"openai-codex:personal": { provider: "openai-codex", mode: "oauth" },
},
order: {
anthropic: ["anthropic:default", "anthropic:work"],
openai: ["openai:personal", "openai:default"],
openai: ["openai:default"],
"openai-codex": ["openai-codex:personal"],
},
},

View File

@@ -214,8 +214,7 @@ See [MCP](/cli/mcp#openclaw-as-an-mcp-client-registry) and
}
```
- Loaded from package or bundle directories under `~/.openclaw/extensions` and `<workspace>/.openclaw/extensions`, plus files or directories listed in `plugins.load.paths`.
- Put standalone plugin files in `plugins.load.paths`; auto-discovered extension roots ignore top-level `.js`, `.mjs`, and `.ts` files so helper scripts in those roots do not block startup.
- Loaded from `~/.openclaw/extensions`, `<workspace>/.openclaw/extensions`, plus `plugins.load.paths`.
- Discovery accepts native OpenClaw plugins plus compatible Codex bundles and Claude bundles, including manifestless Claude default-layout bundles.
- **Config changes require a gateway restart.**
- `allow`: optional allowlist (only listed plugins load). `deny` wins.
@@ -936,11 +935,11 @@ Notes:
profiles: {
"anthropic:default": { provider: "anthropic", mode: "api_key" },
"anthropic:work": { provider: "anthropic", mode: "api_key" },
"openai:personal": { provider: "openai", mode: "oauth" },
"openai-codex:personal": { provider: "openai-codex", mode: "oauth" },
},
order: {
anthropic: ["anthropic:default", "anthropic:work"],
openai: ["openai:personal"],
"openai-codex": ["openai-codex:personal"],
},
},
}

View File

@@ -337,9 +337,9 @@ candidate contains redacted secret placeholders such as `***`.
</Accordion>
<Accordion title="Enable relay-backed push for official iOS builds">
Relay-backed push uses the hosted OpenClaw relay by default: `https://ios-push-relay.openclaw.ai`.
Relay-backed push is configured in `openclaw.json`.
To use a custom relay, set this in gateway config:
Set this in gateway config:
```json5
{
@@ -373,8 +373,8 @@ candidate contains redacted secret placeholders such as `***`.
End-to-end flow:
1. Install an official/TestFlight iOS build.
2. Optional: configure `gateway.push.apns.relay.baseUrl` on the gateway only when using a custom relay deployment.
1. Install an official/TestFlight iOS build that was compiled with the same relay base URL.
2. Configure `gateway.push.apns.relay.baseUrl` on the gateway.
3. Pair the iOS app to the gateway and let both node and operator sessions connect.
4. The iOS app fetches the gateway identity, registers with the relay using App Attest plus the app receipt, and then publishes the relay-backed `push.apns.register` payload to the paired gateway.
5. The gateway stores the relay handle and send grant, then uses them for `push.test`, wake nudges, and reconnect wakes.
@@ -387,7 +387,6 @@ candidate contains redacted secret placeholders such as `***`.
Compatibility note:
- `OPENCLAW_APNS_RELAY_BASE_URL` and `OPENCLAW_APNS_RELAY_TIMEOUT_MS` still work as temporary env overrides.
- Custom gateway relay URLs must match the relay base URL baked into the official/TestFlight iOS build.
- `OPENCLAW_APNS_RELAY_ALLOW_HTTP=true` remains a loopback-only development escape hatch; do not persist HTTP relay URLs in config.
See [iOS App](/platforms/ios#relay-backed-push-for-official-builds) for the end-to-end flow and [Authentication and trust flow](/platforms/ios#authentication-and-trust-flow) for the relay security model.

View File

@@ -143,7 +143,7 @@ must be paired with `--lint`; regular doctor and repair runs reject them.
- Talk config migration from legacy flat `talk.*` fields into `talk.provider` + `talk.providers.<provider>`.
- Browser migration checks for legacy Chrome extension configs and Chrome MCP readiness.
- OpenCode provider override warnings (`models.providers.opencode` / `models.providers.opencode-go`).
- Legacy OpenAI Codex provider/profile migration (`openai-codex` → `openai`) and shadowing warnings for stale `models.providers.openai-codex`.
- Codex OAuth shadowing warnings (`models.providers.openai-codex`).
- OAuth TLS prerequisites check for OpenAI Codex OAuth profiles.
- Plugin/tool allowlist warnings when `plugins.allow` is restrictive but tool policy still asks for wildcard or plugin-owned tools.
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
@@ -171,7 +171,7 @@ must be paired with `--lint`; regular doctor and repair runs reject them.
- Channel status warnings (probed from the running gateway).
- Channel-specific permission checks live under `openclaw channels capabilities`; for example, Discord voice channel permissions are audited with `openclaw channels capabilities --channel discord --target channel:<channel-id>`.
- WhatsApp responsiveness checks for degraded Gateway event-loop health with local TUI clients still running; `--fix` stops only verified local TUI clients.
- Codex route repair for legacy `openai-codex/*` model refs in primary models, fallbacks, image/video generation models, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and session route pins; `--fix` rewrites them to `openai/*`, migrates `openai-codex:*` auth profiles/order to `openai:*`, removes stale session/whole-agent runtime pins, and leaves canonical OpenAI agent refs on the default Codex harness.
- Codex route repair for legacy `openai-codex/*` model refs in primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and session route pins; `--fix` rewrites them to `openai/*`, removes stale session/whole-agent runtime pins, and leaves canonical OpenAI agent refs on the default Codex harness.
- Supervisor config audit (launchd/systemd/schtasks) with optional repair.
- Embedded proxy environment cleanup for gateway services that captured shell `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` values during install or update.
- Gateway runtime best-practice checks (Node vs Bun, version-manager paths).
@@ -327,10 +327,10 @@ That stages grounded durable candidates into the short-term dreaming store while
<Accordion title="2f. Codex route repair">
Doctor checks for legacy `openai-codex/*` model refs. Native Codex harness routing uses canonical `openai/*` model refs; OpenAI agent turns go through the Codex app-server harness instead of the OpenClaw OpenAI provider path.
In `--fix` / `--repair` mode, doctor rewrites affected default-agent and per-agent refs, including primary models, fallbacks, image/video generation models, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale persisted session route state:
In `--fix` / `--repair` mode, doctor rewrites affected default-agent and per-agent refs, including primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale persisted session route state:
- `openai-codex/gpt-*` becomes `openai/gpt-*`.
- Codex intent moves to provider/model-scoped `agentRuntime.id: "codex"` entries for repaired agent model refs.
- Codex intent moves to provider/model-scoped `agentRuntime.id: "codex"` entries for repaired agent model refs so `openai-codex:...` auth profiles can still be selected after the model ref becomes `openai/*`.
- Stale whole-agent runtime config and persisted session runtime pins are removed because runtime selection is provider/model-scoped.
- Existing provider/model runtime policy is preserved unless the repaired legacy model ref needs Codex routing to keep the old auth path.
- Existing model fallback lists are preserved with their legacy entries rewritten; copied per-model settings move from the legacy key to the canonical `openai/*` key.
@@ -382,7 +382,7 @@ That stages grounded durable candidates into the short-term dreaming store while
</Accordion>
<Accordion title="3c. Session lock cleanup">
Doctor scans every agent session directory for stale write-lock files — files left behind when a session exited abnormally. For each lock file found it reports: the path, PID, whether the PID is still alive, lock age, and whether it is considered stale (dead PID, malformed owner metadata, older than 30 minutes, or a live PID that can be proven to belong to a non-OpenClaw process). In `--fix` / `--repair` mode it removes locks with dead, orphaned, recycled, malformed-old, or non-OpenClaw owners automatically. Old locks that are still owned by a live OpenClaw process are reported but left in place so doctor does not cut off an active transcript writer.
Doctor scans every agent session directory for stale write-lock files — files left behind when a session exited abnormally. For each lock file found it reports: the path, PID, whether the PID is still alive, lock age, and whether it is considered stale (dead PID, older than 30 minutes, or a live PID that can be proven to belong to a non-OpenClaw process). In `--fix` / `--repair` mode it removes stale lock files automatically; otherwise it prints a note and instructs you to rerun with `--fix`.
</Accordion>
<Accordion title="3d. Session transcript branch repair">
Doctor scans agent session JSONL files for the duplicated branch shape created by the 2026.4.24 prompt transcript rewrite bug: an abandoned user turn with OpenClaw internal runtime context plus an active sibling containing the same visible user prompt. In `--fix` / `--repair` mode, doctor backs up each affected file next to the original and rewrites the transcript to the active branch so gateway history and memory readers no longer see duplicate turns.

View File

@@ -63,7 +63,6 @@ Example config:
- Interval: `30m` (or `1h` when Anthropic OAuth/token auth is the detected auth mode, including Claude CLI reuse). Set `agents.defaults.heartbeat.every` or per-agent `agents.list[].heartbeat.every`; use `0m` to disable.
- Prompt body (configurable via `agents.defaults.heartbeat.prompt`): `Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`
- Timeout: unset heartbeat turns use `agents.defaults.timeoutSeconds` when set. Otherwise, they use the heartbeat cadence capped at 600 seconds. Set `agents.defaults.heartbeat.timeoutSeconds` or per-agent `agents.list[].heartbeat.timeoutSeconds` for longer heartbeat work.
- The heartbeat prompt is sent **verbatim** as the user message. The system prompt includes a "Heartbeat" section only when heartbeats are enabled for the default agent, and the run is flagged internally.
- When heartbeats are disabled with `0m`, normal runs also omit `HEARTBEAT.md` from bootstrap context so the model does not see heartbeat-only instructions.
- Active hours (`heartbeat.activeHours`) are checked in the configured timezone. Outside the window, heartbeats are skipped until the next tick inside the window.
@@ -275,10 +274,6 @@ Use `accountId` to target a specific account on multi-account channels like Tele
<ParamField path="suppressToolErrorWarnings" type="boolean">
When true, suppresses tool error warning payloads during heartbeat runs.
</ParamField>
<ParamField path="timeoutSeconds" type="number" default="global timeout or min(every, 600)">
Maximum seconds allowed for a heartbeat agent turn before it is aborted. Leave unset to use `agents.defaults.timeoutSeconds` when set, otherwise the heartbeat cadence capped at 600 seconds.
</ParamField>
<ParamField path="activeHours" type="object">
Restricts heartbeat runs to a time window. Object with `start` (HH:MM, inclusive; use `00:00` for start-of-day), `end` (HH:MM exclusive; `24:00` allowed for end-of-day), and optional `timezone`.

View File

@@ -19,7 +19,7 @@ At startup, the Gateway logs the resolved default agent model together with the
mode defaults that affect new sessions, for example:
```text
agent model: openai/gpt-5.5 (thinking=medium, fast=on)
agent model: openai-codex/gpt-5.5 (thinking=medium, fast=on)
```
`thinking` comes from the default agent, model params, or global agent default;

View File

@@ -183,13 +183,6 @@ Define providers under `secrets.providers`:
passEnv: ["PATH", "VAULT_ADDR"],
jsonOnly: true,
},
"team-secrets": {
source: "exec",
pluginIntegration: {
pluginId: "acme-secrets",
integrationId: "secret-store",
},
},
},
defaults: {
env: "default",
@@ -226,11 +219,6 @@ Define providers under `secrets.providers`:
- Pair `allowSymlinkCommand` with `trustedDirs` for package-manager paths (for example `["/opt/homebrew"]`).
- Supports timeout, no-output timeout, output byte limits, env allowlist, and trusted dirs.
- Windows fail-closed note: if ACL verification is unavailable for the command path, resolution fails. For trusted paths only, set `allowInsecurePath: true` on that provider to bypass path security checks.
- Plugin-managed exec providers can use `pluginIntegration` instead of
copied `command`/`args`. OpenClaw resolves the current command details
from the installed plugin manifest during startup/reload. If the plugin is
disabled, removed, untrusted, or no longer declares the integration,
active SecretRefs using that provider fail closed.
Request payload (stdin):

View File

@@ -599,24 +599,22 @@ and troubleshooting see the main [FAQ](/help/faq).
native Codex app-server execution. `openai-codex/gpt-*` model refs are
legacy config repaired by `openclaw doctor --fix`. Direct OpenAI API-key
access remains available for non-agent OpenAI API surfaces and for agent
models through an ordered `openai` API-key profile.
models through an ordered `openai-codex` API-key profile.
See [Model providers](/concepts/model-providers) and [Onboarding (CLI)](/start/wizard).
</Accordion>
<Accordion title="Why does OpenClaw still mention openai-codex?">
`openai` is the provider and auth-profile id for both OpenAI API keys and
ChatGPT/Codex OAuth. You may still see `openai-codex` in legacy config and
migration warnings.
`openai-codex` is the provider and auth-profile id for ChatGPT/Codex OAuth.
Older configs also used it as a model prefix:
- `openai/gpt-5.5` = ChatGPT/Codex subscription auth with native Codex runtime for agent turns
- `openai-codex/gpt-5.5` = legacy model route repaired by `openclaw doctor --fix`
- `openai/gpt-5.5` plus an ordered `openai` API-key profile = API-key auth for an OpenAI agent model
- `openai-codex:...` = legacy auth profile id migrated by `openclaw doctor --fix`
- `openai/gpt-5.5` plus an ordered `openai-codex` API-key profile = API-key auth for an OpenAI agent model
- `openai-codex:...` = auth profile id, not a model ref
If you want the direct OpenAI Platform billing/limit path, set
`OPENAI_API_KEY`. If you want ChatGPT/Codex subscription auth, sign in with
`openclaw models auth login --provider openai`. Keep the model ref as
`openclaw models auth login --provider openai-codex`. Keep the model ref as
`openai/gpt-5.5`; `openai-codex/*` model refs are legacy config that
`openclaw doctor --fix` rewrites.

View File

@@ -160,9 +160,9 @@ troubleshooting, see the main [FAQ](/help/faq).
<Accordion title="Can I use GPT 5.5 for daily tasks and Codex 5.5 for coding?">
Yes. Treat model choice and runtime choice separately:
- **Native Codex coding agent:** set `agents.defaults.model.primary` to `openai/gpt-5.5`. Sign in with `openclaw models auth login --provider openai` when you want ChatGPT/Codex subscription auth.
- **Native Codex coding agent:** set `agents.defaults.model.primary` to `openai/gpt-5.5`. Sign in with `openclaw models auth login --provider openai-codex` when you want ChatGPT/Codex subscription auth.
- **Direct OpenAI API tasks outside the agent loop:** configure `OPENAI_API_KEY` for images, embeddings, speech, realtime, and other non-agent OpenAI API surfaces.
- **OpenAI agent API-key auth:** use `/model openai/gpt-5.5` with an ordered `openai` API-key profile.
- **OpenAI agent API-key auth:** use `/model openai/gpt-5.5` with an ordered `openai-codex` API-key profile.
- **Sub-agents:** route coding tasks to a Codex-focused agent with its own `openai/gpt-5.5` model.
See [Models](/concepts/models) and [Slash commands](/tools/slash-commands).

View File

@@ -76,7 +76,7 @@ Live tests are split into two layers so we can isolate failures:
- `OPENCLAW_LIVE_MODELS=modern` to run the modern allowlist (Opus/Sonnet 4.6+, GPT-5.2 + Codex, Gemini 3, DeepSeek V4, GLM 4.7, MiniMax M2.7, Grok 4.3)
- `OPENCLAW_LIVE_MODELS=small` to run the constrained small-model allowlist (Qwen 8B/9B local-compatible routes, OpenRouter Qwen/GLM, and Z.AI GLM)
- `OPENCLAW_LIVE_MODELS=all` is an alias for the modern allowlist
- or `OPENCLAW_LIVE_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,..."` (comma allowlist)
- or `OPENCLAW_LIVE_MODELS="openai/gpt-5.5,openai-codex/gpt-5.5,anthropic/claude-opus-4-6,..."` (comma allowlist)
- Modern/all and small sweeps default to their curated caps; set `OPENCLAW_LIVE_MAX_MODELS=0` for an exhaustive selected-profile sweep or a positive number for a smaller cap.
- Exhaustive sweeps use `OPENCLAW_LIVE_TEST_TIMEOUT_MS` for the whole direct-model test timeout. Default: 60 minutes.
- Direct-model probes run with 20-way parallelism by default; set `OPENCLAW_LIVE_MODEL_CONCURRENCY` to override.
@@ -350,7 +350,7 @@ Narrow, explicit allowlists are fastest and least flaky:
- `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- Tool calling across several providers:
- `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3-flash-preview,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,openai-codex/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3-flash-preview,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- Google focus (Gemini API key + Antigravity):
- Gemini (API key): `OPENCLAW_LIVE_GATEWAY_MODELS="google/gemini-3-flash-preview" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
@@ -378,7 +378,7 @@ There is no fixed "CI model list" (live is opt-in), but these are the **recommen
This is the "common models" run we expect to keep working:
- OpenAI (non-Codex): `openai/gpt-5.5`
- OpenAI ChatGPT/Codex OAuth: `openai/gpt-5.5`
- OpenAI Codex OAuth: `openai-codex/gpt-5.5`
- Anthropic: `anthropic/claude-opus-4-6` (or `anthropic/claude-sonnet-4-6`)
- Google (Gemini API): `google/gemini-3.1-pro-preview` and `google/gemini-3-flash-preview` (avoid older Gemini 2.x models)
- Google (Antigravity): `google-antigravity/claude-opus-4-6-thinking` and `google-antigravity/gemini-3-flash`
@@ -387,7 +387,7 @@ This is the "common models" run we expect to keep working:
- MiniMax: `minimax/MiniMax-M2.7`
Run gateway smoke with tools + image:
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3.1-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,openai-codex/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3.1-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
### Baseline: tool calling (Read + optional Exec)
@@ -420,7 +420,7 @@ If you have keys enabled, we also support testing via:
More providers you can include in the live matrix (if you have creds/config):
- Built-in: `openai`, `anthropic`, `google`, `google-vertex`, `google-antigravity`, `google-gemini-cli`, `zai`, `openrouter`, `opencode`, `opencode-go`, `xai`, `groq`, `cerebras`, `mistral`, `github-copilot`
- Built-in: `openai`, `openai-codex`, `anthropic`, `google`, `google-vertex`, `google-antigravity`, `google-gemini-cli`, `zai`, `openrouter`, `opencode`, `opencode-go`, `xai`, `groq`, `cerebras`, `mistral`, `github-copilot`
- Via `models.providers` (custom endpoints): `minimax` (cloud/API), plus any OpenAI/Anthropic-compatible proxy (LM Studio, vLLM, LiteLLM, etc.)
<Tip>

View File

@@ -277,11 +277,11 @@ For CLI entries, **set `capabilities` explicitly** to avoid surprising matches.
## Provider support matrix (OpenClaw integrations)
| Capability | Provider integration | Notes |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Image | OpenAI, OpenAI Codex OAuth, Codex app-server, OpenRouter, Anthropic, Google, MiniMax, Moonshot, Qwen, Z.AI, config providers | Vendor plugins register image support; `openai/*` can use API-key or Codex OAuth routing; `codex/*` uses a bounded Codex app-server turn; MiniMax and MiniMax OAuth both use `MiniMax-VL-01`; image-capable config providers auto-register. |
| Audio | OpenAI, Groq, xAI, Deepgram, OpenRouter, Google, SenseAudio, ElevenLabs, Mistral | Provider transcription (Whisper/Groq/xAI/Deepgram/OpenRouter STT/Gemini/SenseAudio/Scribe/Voxtral). |
| Video | Google, Qwen, Moonshot | Provider video understanding via vendor plugins; Qwen video understanding uses the Standard DashScope endpoints. |
| Capability | Provider integration | Notes |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Image | OpenAI, OpenAI Codex OAuth, Codex app-server, OpenRouter, Anthropic, Google, MiniMax, Moonshot, Qwen, Z.AI, config providers | Vendor plugins register image support; `openai-codex/*` uses OAuth provider plumbing; `codex/*` uses a bounded Codex app-server turn; MiniMax and MiniMax OAuth both use `MiniMax-VL-01`; image-capable config providers auto-register. |
| Audio | OpenAI, Groq, xAI, Deepgram, OpenRouter, Google, SenseAudio, ElevenLabs, Mistral | Provider transcription (Whisper/Groq/xAI/Deepgram/OpenRouter STT/Gemini/SenseAudio/Scribe/Voxtral). |
| Video | Google, Qwen, Moonshot | Provider video understanding via vendor plugins; Qwen video understanding uses the Standard DashScope endpoints. |
<Note>
**MiniMax note**

View File

@@ -7,7 +7,7 @@ read_when:
title: "iOS app"
---
Availability: iPhone app builds are distributed through Apple channels when enabled for a release. Local development builds can also run from source.
Availability: internal preview. The iOS app is not publicly distributed yet.
## What it does
@@ -75,9 +75,7 @@ openclaw gateway call node.list --params "{}"
Official distributed iOS builds use the external push relay instead of publishing the raw APNs
token to the gateway.
By default, official/TestFlight builds and gateways use the hosted relay at `https://ios-push-relay.openclaw.ai`.
Custom relay deployments can override the gateway relay URL:
Gateway-side requirement:
```json5
{
@@ -100,7 +98,7 @@ How the flow works:
- The iOS app fetches the paired gateway identity and includes it in relay registration, so the relay-backed registration is delegated to that specific gateway.
- The app forwards that relay-backed registration to the paired gateway with `push.apns.register`.
- The gateway uses that stored relay handle for `push.test`, background wakes, and wake nudges.
- Custom gateway relay URLs must match the relay URL baked into the official/TestFlight iOS build.
- The gateway relay base URL must match the relay URL baked into the official/TestFlight iOS build.
- If the app later connects to a different gateway or a build with a different relay base URL, it refreshes the relay registration instead of reusing the old binding.
What the gateway does **not** need for this path:
@@ -111,7 +109,7 @@ What the gateway does **not** need for this path:
Expected operator flow:
1. Install the official/TestFlight iOS build.
2. Optional: set `gateway.push.apns.relay.baseUrl` on the gateway only when using a custom relay deployment.
2. Set `gateway.push.apns.relay.baseUrl` on the gateway.
3. Pair the app to the gateway and let it finish connecting.
4. The app publishes `push.apns.register` automatically after it has an APNs token, the operator session is connected, and relay registration succeeds.
5. After that, `push.test`, reconnect wakes, and wake nudges can use the stored relay-backed registration.
@@ -130,7 +128,6 @@ compatible but does not count as a durable last-seen update.
Compatibility note:
- `OPENCLAW_APNS_RELAY_BASE_URL` still works as a temporary env override for the gateway.
- `OPENCLAW_PUSH_RELAY_BASE_URL` still works as a temporary env override for official/TestFlight iOS builds.
## Authentication and trust flow

View File

@@ -85,25 +85,25 @@ For an already-running app-server, use WebSocket transport:
Supported `appServer` fields:
| Field | Default | Meaning |
| --------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | managed Codex binary | Executable for stdio transport. Leave unset to use the managed binary. |
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
| `url` | unset | WebSocket app-server URL. |
| `authToken` | unset | Bearer token for WebSocket transport. |
| `headers` | `{}` | Extra WebSocket headers. |
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `turnCompletionIdleTimeoutMs` | `60000` | Quiet window after Codex accepts a turn or after a turn-scoped app-server request while OpenClaw waits for `turn/completed`. |
| `postToolRawAssistantCompletionIdleTimeoutMs` | `300000` | Completion-idle guard used after a tool handoff when Codex emits raw assistant completion or progress but does not send `turn/completed`. Use this for trusted or heavy workloads where post-tool synthesis can legitimately stay quiet longer than the final assistant release budget. |
| `mode` | `"yolo"` unless local Codex requirements disallow YOLO | Preset for YOLO or guardian-reviewed execution. |
| `approvalPolicy` | `"never"` or an allowed guardian approval policy | Native Codex approval policy sent to thread start, resume, and turn. |
| `sandbox` | `"danger-full-access"` or an allowed guardian sandbox | Native Codex sandbox mode sent to thread start and resume. Active OpenClaw sandboxes narrow `danger-full-access` turns to Codex `workspace-write`; the turn network flag follows OpenClaw sandbox egress. |
| `approvalsReviewer` | `"user"` or an allowed guardian reviewer | Use `"auto_review"` to let Codex review native approval prompts when allowed. |
| `defaultWorkspaceDir` | current process directory | Workspace used by `/codex bind` when `--cwd` is omitted. |
| `serviceTier` | unset | Optional Codex app-server service tier. `"priority"` enables fast-mode routing, `"flex"` requests flex processing, and `null` clears the override. Legacy `"fast"` is accepted as `"priority"`. |
| `experimental.sandboxExecServer` | `false` | Preview opt-in that registers an OpenClaw sandbox-backed Codex environment with Codex app-server 0.132.0 or newer so native Codex execution can run inside the active OpenClaw sandbox. |
| Field | Default | Meaning |
| --------------------------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | managed Codex binary | Executable for stdio transport. Leave unset to use the managed binary. |
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
| `url` | unset | WebSocket app-server URL. |
| `authToken` | unset | Bearer token for WebSocket transport. |
| `headers` | `{}` | Extra WebSocket headers. |
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `turnCompletionIdleTimeoutMs` | `60000` | Quiet window after Codex accepts a turn or after a turn-scoped app-server request while OpenClaw waits for `turn/completed`. |
| `postToolRawAssistantCompletionIdleTimeoutMs` | unset | Completion-idle guard used after a tool handoff when Codex emits raw assistant completion or progress but does not send `turn/completed`. Defaults to the assistant completion idle timeout when unset. Use this for trusted or heavy workloads where post-tool synthesis can legitimately stay quiet longer than the final assistant release budget. |
| `mode` | `"yolo"` unless local Codex requirements disallow YOLO | Preset for YOLO or guardian-reviewed execution. |
| `approvalPolicy` | `"never"` or an allowed guardian approval policy | Native Codex approval policy sent to thread start, resume, and turn. |
| `sandbox` | `"danger-full-access"` or an allowed guardian sandbox | Native Codex sandbox mode sent to thread start and resume. Active OpenClaw sandboxes narrow `danger-full-access` turns to Codex `workspace-write`; the turn network flag follows OpenClaw sandbox egress. |
| `approvalsReviewer` | `"user"` or an allowed guardian reviewer | Use `"auto_review"` to let Codex review native approval prompts when allowed. |
| `defaultWorkspaceDir` | current process directory | Workspace used by `/codex bind` when `--cwd` is omitted. |
| `serviceTier` | unset | Optional Codex app-server service tier. `"priority"` enables fast-mode routing, `"flex"` requests flex processing, and `null` clears the override. Legacy `"fast"` is accepted as `"priority"`. |
| `experimental.sandboxExecServer` | `false` | Preview opt-in that registers an OpenClaw sandbox-backed Codex environment with Codex app-server 0.132.0 or newer so native Codex execution can run inside the active OpenClaw sandbox. |
The plugin blocks older or unversioned app-server handshakes. Codex app-server
must report stable version `0.125.0` or newer.
@@ -337,15 +337,10 @@ Codex then goes quiet without `turn/completed`, OpenClaw best-effort interrupts
the native turn and releases the session lane. Post-tool raw assistant progress
keeps waiting for `turn/completed` while a completion-idle guard stays armed; the
guard uses `appServer.postToolRawAssistantCompletionIdleTimeoutMs` when
configured and defaults to five minutes otherwise. Replay-safe stdio app-server
failures, including turn-completion idle timeouts without assistant, tool,
active-item, or side-effect evidence, are retried once on a fresh app-server
attempt. Unsafe timeouts still retire the stuck app-server client and release
the OpenClaw session lane. They also clear the stale native thread binding and
surface a recoverable timeout message for user or maintainer judgment instead of
being replayed automatically. Timeout diagnostics include the last
app-server notification method and, for raw assistant response items, the item
type, role, id, and a bounded assistant text preview.
configured and falls back to the assistant completion idle timeout otherwise.
Timeout diagnostics include the last app-server notification method and, for raw
assistant response items, the item type, role, id, and a bounded assistant text
preview.
## Model discovery
@@ -427,17 +422,15 @@ filenames for persona files, because Codex fallbacks only apply when
For OpenClaw workspace parity, the Codex harness resolves the other bootstrap
files. `SOUL.md`, `IDENTITY.md`, `TOOLS.md`, and `USER.md` are forwarded as
OpenClaw Codex developer instructions because they define the active agent,
available workspace guidance, and user profile. The compact OpenClaw skills
list is forwarded as turn-scoped collaboration developer instructions.
`HEARTBEAT.md` content is not injected; heartbeat turns get a collaboration-mode
pointer to read the file when it exists and is non-empty. `MEMORY.md` content
from the configured agent workspace is not pasted into native Codex turn input
when memory tools are available for that workspace; when it exists, the harness
adds a small workspace-memory pointer to turn-scoped collaboration developer
instructions and Codex should use `memory_search` or `memory_get` when durable
memory is relevant. If tools are disabled, memory search is unavailable, or the
active workspace differs from the agent memory workspace, `MEMORY.md` uses the
normal bounded turn-context path.
available workspace guidance, and user profile. `HEARTBEAT.md` content is not
injected; heartbeat turns get a collaboration-mode pointer to read the file when
it exists and is non-empty. `MEMORY.md` content from the configured agent
workspace is not pasted into native Codex turn input when memory tools are
available for that workspace; when it exists, the harness adds a small
workspace-memory pointer and Codex should use `memory_search` or `memory_get`
when durable memory is relevant. If tools are disabled, memory search is
unavailable, or the active workspace differs from the agent memory workspace,
`MEMORY.md` uses the normal bounded turn-context path.
`BOOTSTRAP.md` when present is forwarded as OpenClaw turn input reference
context.

View File

@@ -34,10 +34,9 @@ personality files and OpenClaw agent identity stay authoritative. Lightweight
OpenClaw runs still preserve their existing project-doc suppression. OpenClaw
developer instructions cover OpenClaw runtime concerns such as source-channel
delivery, OpenClaw dynamic tools, ACP delegation, adapter context, and the
active agent workspace profile files. OpenClaw skill catalogs and tool-routed
`MEMORY.md` pointers are projected as turn-scoped collaboration developer
instructions for native Codex. Active `BOOTSTRAP.md` content and full
`MEMORY.md` fallback injection still use turn input reference context.
active agent workspace profile files. OpenClaw skill catalogs plus `MEMORY.md`
and active `BOOTSTRAP.md` content are projected as turn input reference context
for native Codex.
## Thread bindings and model changes

View File

@@ -44,7 +44,7 @@ Discord, Slack, or another channel remains the communication surface.
- Codex app-server `0.125.0` or newer. The bundled plugin manages a compatible
Codex app-server binary by default, so local `codex` commands on `PATH` do not
affect normal harness startup.
- Codex auth available through `openclaw models auth login --provider openai`,
- Codex auth available through `openclaw models auth login --provider openai-codex`,
an app-server account in the agent's Codex home, or an explicit Codex API-key
auth profile.
@@ -61,7 +61,7 @@ canonical `openai/gpt-*` model ref.
Sign in with Codex OAuth:
```bash
openclaw models auth login --provider openai
openclaw models auth login --provider openai-codex
```
Enable the bundled `codex` plugin and select an OpenAI agent model:
@@ -112,7 +112,7 @@ harness options in OpenClaw config, and use the CLI only for Codex auth:
| Enable the harness | `plugins.entries.codex.enabled: true` | OpenClaw config |
| Keep an allowlisted plugin install | Include `codex` in `plugins.allow` | OpenClaw config |
| Route OpenAI agent turns through Codex | `agents.defaults.model` or `agents.list[].model` as `openai/gpt-*` | OpenClaw agent config |
| Sign in with ChatGPT/Codex OAuth | `openclaw models auth login --provider openai` | CLI auth profile |
| Sign in with Codex OAuth | `openclaw models auth login --provider openai-codex` | CLI auth profile |
| Add API-key backup for Codex runs | `openai:*` API-key profile listed after subscription auth in `auth.order.openai` | CLI auth profile + OpenClaw config |
| Fail closed when Codex is unavailable | Provider or model `agentRuntime.id: "codex"` | OpenClaw model/provider config |
| Use direct OpenAI API traffic | Provider or model `agentRuntime.id: "openclaw"` with normal OpenAI auth | OpenClaw model/provider config |
@@ -153,7 +153,7 @@ instead of silently switching compaction backends.
{
auth: {
order: {
openai: ["openai:user@example.com", "openai:api-key-backup"],
openai: ["openai-codex:user@example.com", "openai:api-key-backup"],
},
},
}
@@ -444,8 +444,7 @@ For upload mechanics and runtime-level diagnostics boundaries, see
Auth is selected in this order:
1. Ordered OpenAI auth profiles for the agent, preferably under
`auth.order.openai`. Run `openclaw doctor --fix` to migrate older
`openai-codex:*` profile ids and `auth.order.openai-codex`.
`auth.order.openai`. Existing `openai-codex:*` profile ids remain valid.
2. The app-server's existing account in that agent's Codex home.
3. For local stdio app-server launches only, `CODEX_API_KEY`, then
`OPENAI_API_KEY`, when no app-server account is present and OpenAI auth is
@@ -526,25 +525,25 @@ Supported top-level Codex plugin fields:
Supported `appServer` fields:
| Field | Default | Meaning |
| --------------------------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | managed Codex binary | Executable for stdio transport. Leave unset to use the managed binary; set it only for an explicit override. |
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
| `url` | unset | WebSocket app-server URL. |
| `authToken` | unset | Bearer token for WebSocket transport. |
| `headers` | `{}` | Extra WebSocket headers. |
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. OpenClaw keeps per-agent `CODEX_HOME` and inherited `HOME` for local launches. |
| `codeModeOnly` | `false` | Opt into Codex's code-mode-only tool surface. OpenClaw dynamic tools remain registered with Codex so nested `tools.*` calls return through the app-server `item/tool/call` bridge. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `turnCompletionIdleTimeoutMs` | `60000` | Quiet window after Codex accepts a turn or after a turn-scoped app-server request while OpenClaw waits for `turn/completed`. Raise this for slow post-tool or status-only synthesis phases. |
| `postToolRawAssistantCompletionIdleTimeoutMs` | `300000` | Completion-idle guard used after a tool handoff when Codex emits raw assistant completion or progress but does not send `turn/completed`. Use this for trusted or heavy workloads where post-tool synthesis can legitimately stay quiet longer than the final assistant release budget. |
| `mode` | `"yolo"` unless local Codex requirements disallow YOLO | Preset for YOLO or guardian-reviewed execution. Local stdio requirements that omit `danger-full-access`, `never` approval, or the `user` reviewer make the implicit default guardian. |
| `approvalPolicy` | `"never"` or an allowed guardian approval policy | Native Codex approval policy sent to thread start/resume/turn. Guardian defaults prefer `"on-request"` when allowed. |
| `sandbox` | `"danger-full-access"` or an allowed guardian sandbox | Native Codex sandbox mode sent to thread start/resume. Guardian defaults prefer `"workspace-write"` when allowed, otherwise `"read-only"`. When an OpenClaw sandbox is active, `danger-full-access` turns use Codex `workspace-write` with network access derived from the OpenClaw sandbox egress setting. |
| `approvalsReviewer` | `"user"` or an allowed guardian reviewer | Use `"auto_review"` to let Codex review native approval prompts when allowed, otherwise `guardian_subagent` or `user`. `guardian_subagent` remains a legacy alias. |
| `serviceTier` | unset | Optional Codex app-server service tier. `"priority"` enables fast-mode routing, `"flex"` requests flex processing, `null` clears the override, and legacy `"fast"` is accepted as `"priority"`. |
| `experimental.sandboxExecServer` | `false` | Preview opt-in that registers an OpenClaw sandbox-backed Codex environment with Codex app-server 0.132.0 or newer so native Codex execution can run inside the active OpenClaw sandbox. |
| Field | Default | Meaning |
| --------------------------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | managed Codex binary | Executable for stdio transport. Leave unset to use the managed binary; set it only for an explicit override. |
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
| `url` | unset | WebSocket app-server URL. |
| `authToken` | unset | Bearer token for WebSocket transport. |
| `headers` | `{}` | Extra WebSocket headers. |
| `clearEnv` | `[]` | Extra environment variable names removed from the spawned stdio app-server process after OpenClaw builds its inherited environment. OpenClaw keeps per-agent `CODEX_HOME` and inherited `HOME` for local launches. |
| `codeModeOnly` | `false` | Opt into Codex's code-mode-only tool surface. OpenClaw dynamic tools remain registered with Codex so nested `tools.*` calls return through the app-server `item/tool/call` bridge. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `turnCompletionIdleTimeoutMs` | `60000` | Quiet window after Codex accepts a turn or after a turn-scoped app-server request while OpenClaw waits for `turn/completed`. Raise this for slow post-tool or status-only synthesis phases. |
| `postToolRawAssistantCompletionIdleTimeoutMs` | unset | Completion-idle guard used after a tool handoff when Codex emits raw assistant completion or progress but does not send `turn/completed`. Defaults to the assistant completion idle timeout when unset. Use this for trusted or heavy workloads where post-tool synthesis can legitimately stay quiet longer than the final assistant release budget. |
| `mode` | `"yolo"` unless local Codex requirements disallow YOLO | Preset for YOLO or guardian-reviewed execution. Local stdio requirements that omit `danger-full-access`, `never` approval, or the `user` reviewer make the implicit default guardian. |
| `approvalPolicy` | `"never"` or an allowed guardian approval policy | Native Codex approval policy sent to thread start/resume/turn. Guardian defaults prefer `"on-request"` when allowed. |
| `sandbox` | `"danger-full-access"` or an allowed guardian sandbox | Native Codex sandbox mode sent to thread start/resume. Guardian defaults prefer `"workspace-write"` when allowed, otherwise `"read-only"`. When an OpenClaw sandbox is active, `danger-full-access` turns use Codex `workspace-write` with network access derived from the OpenClaw sandbox egress setting. |
| `approvalsReviewer` | `"user"` or an allowed guardian reviewer | Use `"auto_review"` to let Codex review native approval prompts when allowed, otherwise `guardian_subagent` or `user`. `guardian_subagent` remains a legacy alias. |
| `serviceTier` | unset | Optional Codex app-server service tier. `"priority"` enables fast-mode routing, `"flex"` requests flex processing, `null` clears the override, and legacy `"fast"` is accepted as `"priority"`. |
| `experimental.sandboxExecServer` | `false` | Preview opt-in that registers an OpenClaw sandbox-backed Codex environment with Codex app-server 0.132.0 or newer so native Codex execution can run inside the active OpenClaw sandbox. |
OpenClaw-owned dynamic tool calls are bounded independently from
`appServer.requestTimeoutMs`: Codex `item/tool/call` requests use a 90 second
@@ -575,15 +574,10 @@ goes quiet without `turn/completed`, OpenClaw best-effort interrupts the native
turn and releases the session lane. Post-tool raw assistant progress keeps
waiting for `turn/completed` while a completion-idle guard stays armed; the guard
uses `appServer.postToolRawAssistantCompletionIdleTimeoutMs` when configured and
defaults to five minutes otherwise. Replay-safe stdio app-server failures,
including turn-completion idle timeouts without assistant, tool, active-item, or
side-effect evidence, are retried once on a fresh app-server attempt. Unsafe
timeouts still retire the stuck app-server client and release the OpenClaw
session lane. They also clear the stale native thread binding and surface a
recoverable timeout message for user or maintainer judgment instead of being
replayed automatically. Timeout diagnostics include the last app-server
notification method and, for raw assistant response items, the item type, role,
id, and a bounded assistant text preview.
falls back to the assistant completion idle timeout otherwise. Timeout
diagnostics include the last app-server notification method and, for raw
assistant response items, the item type, role, id, and a bounded assistant text
preview.
Environment overrides remain available for local testing:
@@ -721,7 +715,7 @@ Ask affected collaborators to run this read-only command on their OpenClaw host:
Useful excerpts usually include `openai/gpt-5.5` or `openai/gpt-5.4`,
`Runtime: OpenAI Codex`, `agentRuntime.id` or `harnessRuntime`,
`candidateProvider: "openai"`, and a `401`, `Incorrect API key`, or
`No API key` result. A corrected run should show the OpenAI OAuth
`No API key` result. A corrected run should show the `openai-codex` OAuth
path instead of a plain OpenAI API-key failure.
**Legacy `openai-codex/*` config remains:** run `openclaw doctor --fix`.

View File

@@ -1,13 +1,13 @@
---
summary: "Run OpenClaw embedded agent turns through the external GitHub Copilot SDK harness"
summary: "Run OpenClaw embedded agent turns through the bundled GitHub Copilot SDK harness"
title: "Copilot SDK harness"
read_when:
- You want to use the GitHub Copilot SDK harness for an agent
- You want to use the bundled GitHub Copilot SDK harness for an agent
- You need configuration examples for the `copilot` runtime
- You are wiring an agent to subscription Copilot (github / openclaw / copilot) and want it to run through the Copilot CLI
---
The external `@openclaw/copilot` plugin lets OpenClaw run embedded subscription
The bundled `copilot` extension lets OpenClaw run embedded subscription
Copilot agent turns through the GitHub Copilot CLI (`@github/copilot-sdk`)
instead of the built-in PI harness.
@@ -24,11 +24,11 @@ For the broader model/provider/runtime split, start with
## Requirements
- OpenClaw with the `@openclaw/copilot` plugin installed.
- OpenClaw with the bundled `copilot` extension available.
- If your config uses `plugins.allow`, include `copilot` (the manifest
id declared by the plugin). A restrictive
id in `extensions/copilot/openclaw.plugin.json`). A restrictive
allowlist that uses the npm-style `@openclaw/copilot` package name
will leave the plugin blocked and the runtime will not load
will leave the bundled plugin blocked and the runtime will not load
even with `agentRuntime.id: "copilot"`.
- A GitHub Copilot subscription that can drive the Copilot CLI (or a
`gitHubToken` env / auth-profile entry for headless / cron runs).
@@ -38,38 +38,56 @@ For the broader model/provider/runtime split, start with
or `~/.config/copilot` elsewhere) is used as the doctor probe fallback when
no explicit home is set.
`openclaw doctor` runs the plugin
`openclaw doctor` runs the bundled
[doctor contract](#doctor-and-probes) for the extension; failures there are
the canonical way to confirm the environment is ready before opting an agent
in.
## Plugin install
## On-demand SDK install
The Copilot runtime is an external plugin so the core `openclaw` package does
not carry the `@github/copilot-sdk` dependency or its platform-specific
`@github/copilot-<platform>-<arch>` CLI binary. Together they add roughly
260 MB, so install them only for agents that opt into this runtime:
The Copilot agent runtime ships its small TypeScript code bundled inside
the openclaw tarball, but the underlying `@github/copilot-sdk` package
(and its platform-specific `@github/copilot-<platform>-<arch>` CLI
binary) is **not** installed by default — together they add ~260 MB to
your openclaw install footprint, and most openclaw users do not select
a Copilot model.
```bash
openclaw plugins install @openclaw/copilot
```
The wizard installs the plugin the first time you select a
The wizard offers to install the SDK the first time you select a
`github-copilot/*` model **and** your config opts the model (or its
provider) into the Copilot agent runtime via
`agentRuntime: { id: "copilot" }` (see [Quickstart](#quickstart) below).
Without the opt-in, openclaw uses its built-in GitHub Copilot provider
and never installs the runtime plugin.
and never prompts for the SDK install:
```
The Copilot agent runtime needs @github/copilot-sdk (~260 MB on first
install, downloads the @github/copilot CLI binary for your platform).
Install now? [Y/n]
```
If you accept, the SDK is installed into
`~/.openclaw/npm-runtime/copilot/` and detected on subsequent runs. The
install runs `npm ci` against a checked-in `package-lock.json` shipped
with openclaw at
`src/commands/copilot-sdk-install-manifest/package-lock.json`, so the
exact transitive graph reviewed for this release lands on disk on every
user machine.
If you decline, the runtime will fail at first invocation with an
actionable install message; re-run `openclaw setup` to retry the install
(or copy the pinned manifest into `~/.openclaw/npm-runtime/copilot/` and
run `npm ci` yourself if you need to install offline).
The runtime resolves the SDK in this order:
1. `import("@github/copilot-sdk")` from the installed `@openclaw/copilot`
package.
1. `import("@github/copilot-sdk")` against the host openclaw install
(covers source/dev checkouts and any environment that pre-installs
the SDK alongside openclaw).
2. The well-known fallback dir `~/.openclaw/npm-runtime/copilot/` (the
legacy on-demand install target).
wizard install target).
A missing SDK surfaces a single error with code `COPILOT_SDK_MISSING`
and the plugin reinstall command above.
and the manual install command above.
## Quickstart

View File

@@ -169,7 +169,6 @@ or npm install metadata. Those belong in your plugin code and `package.json`.
| `modelIdNormalization` | No | `object` | Provider-owned model-id alias/prefix cleanup that must run before provider runtime loads. |
| `providerEndpoints` | No | `object[]` | Manifest-owned endpoint host/baseUrl metadata for provider routes that core must classify before provider runtime loads. |
| `providerRequest` | No | `object` | Cheap provider-family and request-compatibility metadata used by generic request policy before provider runtime loads. |
| `secretProviderIntegrations` | No | `Record<string, object>` | Declarative SecretRef exec provider presets that setup or install surfaces can offer without hardcoding provider-specific integrations in core. |
| `cliBackends` | No | `string[]` | CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs. |
| `syntheticAuthRefs` | No | `string[]` | Provider or CLI backend refs whose plugin-owned synthetic auth hook should be probed during cold model discovery before runtime loads. |
| `nonSecretAuthMarkers` | No | `string[]` | Bundled-plugin-owned placeholder API key values that represent non-secret local, OAuth, or ambient credential state. |
@@ -636,7 +635,7 @@ read without importing the plugin runtime.
"realtimeTranscriptionProviders": ["openai"],
"realtimeVoiceProviders": ["openai"],
"memoryEmbeddingProviders": ["local"],
"mediaUnderstandingProviders": ["openai"],
"mediaUnderstandingProviders": ["openai", "openai-codex"],
"imageGenerationProviders": ["openai"],
"videoGenerationProviders": ["qwen"],
"webFetchProviders": ["firecrawl"],
@@ -1081,72 +1080,6 @@ Provider fields:
| `compatibilityFamily` | `"moonshot"` | Optional provider-family compatibility bucket for shared request helpers. |
| `openAICompletions` | `object` | OpenAI-compatible completions request flags, currently `supportsStreamingUsage`. |
## secretProviderIntegrations reference
Use `secretProviderIntegrations` when a plugin can publish a reusable SecretRef
exec provider preset. OpenClaw reads this metadata before plugin runtime loads,
stores plugin ownership in `secrets.providers.<alias>.pluginIntegration`, and
leaves actual secret resolution to the SecretRef runtime.
Presets are exposed only for bundled plugins and installed plugins discovered
from the managed plugin install roots, such as git and ClawHub installs.
```json
{
"secretProviderIntegrations": {
"secret-store": {
"providerAlias": "team-secrets",
"displayName": "Team secrets",
"source": "exec",
"command": "${node}",
"args": ["./bin/resolve-secrets.mjs"]
}
}
}
```
The map key is the integration id. If `providerAlias` is omitted, OpenClaw uses
the integration id as the SecretRef provider alias. Provider aliases must match
the normal SecretRef provider alias pattern, for example `team-secrets` or
`onepassword-work`.
When an operator selects the preset, OpenClaw writes a provider reference like:
```json
{
"secrets": {
"providers": {
"team-secrets": {
"source": "exec",
"pluginIntegration": {
"pluginId": "acme-secrets",
"integrationId": "secret-store"
}
}
}
}
}
```
At startup/reload, OpenClaw resolves that provider by loading current plugin
manifest metadata, checking that the owning plugin is installed and active, and
materializing the exec command from the manifest. Disabling or removing the
plugin revokes the provider for active SecretRefs. Operators who want standalone
exec configuration can still write manual `command`/`args` providers directly.
Only `source: "exec"` presets are currently supported. `command` must be
`${node}`, and `args[0]` must be a `./` plugin-root-relative resolver script.
OpenClaw materializes it at startup/reload to the current Node executable and
the absolute in-plugin script path. Node options such as `--require`, `--import`,
`--loader`, `--env-file`, `--eval`, and `--print` are not part of the manifest
preset contract. Operators who need non-Node commands can configure standalone
manual exec providers directly.
OpenClaw derives `trustedDirs` for manifest presets from the plugin root and,
for `${node}` presets, the current Node executable directory. Manifest-authored
`trustedDirs` are ignored. Other exec provider options such as `timeoutMs`,
`maxOutputBytes`, `jsonOnly`, `env`, `passEnv`, and `allowInsecurePath` pass
through to the normal SecretRef exec provider config.
## modelPricing reference
Use `modelPricing` when a provider needs control-plane pricing behavior before

View File

@@ -125,8 +125,8 @@ embeddings:
}
```
OpenAI Codex / ChatGPT OAuth is not an OpenAI Platform embeddings credential.
For OpenAI embeddings, use an OpenAI API key auth profile,
OpenAI Codex / ChatGPT OAuth (`openai-codex`) is not an OpenAI Platform
embeddings credential. For OpenAI embeddings, use an OpenAI API key auth profile,
`OPENAI_API_KEY`, or `models.providers.openai.apiKey`. OAuth-only users can use
another embedding-capable provider such as GitHub Copilot or Ollama.

View File

@@ -66,6 +66,7 @@ commands.
| [cloudflare-ai-gateway](/plugins/reference/cloudflare-ai-gateway) | Adds Cloudflare AI Gateway model provider support to OpenClaw. | `@openclaw/cloudflare-ai-gateway-provider`<br />included in OpenClaw | providers: cloudflare-ai-gateway |
| [codex-supervisor](/plugins/reference/codex-supervisor) | Supervise Codex app-server sessions from OpenClaw. | `@openclaw/codex-supervisor`<br />included in OpenClaw | contracts: tools |
| [comfy](/plugins/reference/comfy) | Adds ComfyUI model provider support to OpenClaw. | `@openclaw/comfy-provider`<br />included in OpenClaw | providers: comfy; contracts: imageGenerationProviders, musicGenerationProviders, videoGenerationProviders |
| [copilot](/plugins/reference/copilot) | Registers the GitHub Copilot agent runtime. | `@openclaw/copilot`<br />included in OpenClaw | plugin |
| [copilot-proxy](/plugins/reference/copilot-proxy) | Adds Copilot Proxy model provider support to OpenClaw. | `@openclaw/copilot-proxy`<br />included in OpenClaw | providers: copilot-proxy |
| [deepgram](/plugins/reference/deepgram) | Adds media understanding provider support. Adds realtime transcription provider support. | `@openclaw/deepgram-provider`<br />included in OpenClaw | contracts: mediaUnderstandingProviders, realtimeTranscriptionProviders |
| [deepinfra](/plugins/reference/deepinfra) | Adds DeepInfra model provider support to OpenClaw. | `@openclaw/deepinfra-provider`<br />included in OpenClaw | providers: deepinfra; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, speechProviders, videoGenerationProviders |
@@ -79,7 +80,6 @@ commands.
| [firecrawl](/plugins/reference/firecrawl) | Adds agent-callable tools. Adds web fetch provider support. Adds web search provider support. | `@openclaw/firecrawl-plugin`<br />included in OpenClaw | contracts: tools, webFetchProviders, webSearchProviders |
| [fireworks](/plugins/reference/fireworks) | Adds Fireworks model provider support to OpenClaw. | `@openclaw/fireworks-provider`<br />included in OpenClaw | providers: fireworks |
| [github-copilot](/plugins/reference/github-copilot) | Adds GitHub Copilot model provider support to OpenClaw. | `@openclaw/github-copilot-provider`<br />included in OpenClaw | providers: github-copilot; contracts: memoryEmbeddingProviders |
| [gmi](/plugins/reference/gmi) | Adds Gmi, Gmi Cloud, Gmicloud model provider support to OpenClaw. | `@openclaw/gmi-provider`<br />included in OpenClaw | providers: gmi, gmi-cloud, gmicloud |
| [google](/plugins/reference/google) | Adds Google, Google Gemini CLI, Google Vertex model provider support to OpenClaw. | `@openclaw/google-plugin`<br />included in OpenClaw | providers: google, google-gemini-cli, google-vertex; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, musicGenerationProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders, webSearchProviders |
| [gradium](/plugins/reference/gradium) | Adds text-to-speech provider support. | `@openclaw/gradium-speech`<br />included in OpenClaw | contracts: speechProviders |
| [groq](/plugins/reference/groq) | Adds Groq model provider support to OpenClaw. | `@openclaw/groq-provider`<br />included in OpenClaw | providers: groq; contracts: mediaUnderstandingProviders |
@@ -102,19 +102,18 @@ commands.
| [minimax](/plugins/reference/minimax) | Adds MiniMax, MiniMax Portal model provider support to OpenClaw. | `@openclaw/minimax-provider`<br />included in OpenClaw | providers: minimax, minimax-portal; contracts: imageGenerationProviders, mediaUnderstandingProviders, musicGenerationProviders, speechProviders, videoGenerationProviders, webSearchProviders |
| [mistral](/plugins/reference/mistral) | Adds Mistral model provider support to OpenClaw. | `@openclaw/mistral-provider`<br />included in OpenClaw | providers: mistral; contracts: mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders |
| [moonshot](/plugins/reference/moonshot) | Adds Moonshot model provider support to OpenClaw. | `@openclaw/moonshot-provider`<br />included in OpenClaw | providers: moonshot; contracts: mediaUnderstandingProviders, webSearchProviders |
| [novita](/plugins/reference/novita) | Adds Novita, Novita AI, Novitaai model provider support to OpenClaw. | `@openclaw/novita-provider`<br />included in OpenClaw | providers: novita, novita-ai, novitaai |
| [nvidia](/plugins/reference/nvidia) | Adds NVIDIA model provider support to OpenClaw. | `@openclaw/nvidia-provider`<br />included in OpenClaw | providers: nvidia |
| [oc-path](/plugins/reference/oc-path) | Adds the openclaw path CLI for oc:// workspace file addressing. | `@openclaw/oc-path`<br />included in OpenClaw | plugin |
| [ollama](/plugins/reference/ollama) | Adds Ollama, Ollama Cloud model provider support to OpenClaw. | `@openclaw/ollama-provider`<br />included in OpenClaw | providers: ollama, ollama-cloud; contracts: memoryEmbeddingProviders, webSearchProviders |
| [ollama](/plugins/reference/ollama) | Adds Ollama model provider support to OpenClaw. | `@openclaw/ollama-provider`<br />included in OpenClaw | providers: ollama; contracts: memoryEmbeddingProviders, webSearchProviders |
| [open-prose](/plugins/reference/open-prose) | OpenProse VM skill pack with a /prose slash command. | `@openclaw/open-prose`<br />included in OpenClaw | skills |
| [openai](/plugins/reference/openai) | Adds OpenAI model provider support to OpenClaw, including ChatGPT/Codex OAuth. | `@openclaw/openai-provider`<br />included in OpenClaw | providers: openai; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders |
| [openai](/plugins/reference/openai) | Adds OpenAI, OpenAI Codex model provider support to OpenClaw. | `@openclaw/openai-provider`<br />included in OpenClaw | providers: openai, openai-codex; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders |
| [opencode](/plugins/reference/opencode) | Adds OpenCode model provider support to OpenClaw. | `@openclaw/opencode-provider`<br />included in OpenClaw | providers: opencode; contracts: mediaUnderstandingProviders |
| [opencode-go](/plugins/reference/opencode-go) | Adds OpenCode Go model provider support to OpenClaw. | `@openclaw/opencode-go-provider`<br />included in OpenClaw | providers: opencode-go; contracts: mediaUnderstandingProviders |
| [openrouter](/plugins/reference/openrouter) | Adds OpenRouter model provider support to OpenClaw. | `@openclaw/openrouter-provider`<br />included in OpenClaw | providers: openrouter; contracts: imageGenerationProviders, mediaUnderstandingProviders, musicGenerationProviders, speechProviders, videoGenerationProviders |
| [perplexity](/plugins/reference/perplexity) | Adds web search provider support. | `@openclaw/perplexity-plugin`<br />included in OpenClaw | contracts: webSearchProviders |
| [policy](/plugins/reference/policy) | Adds policy-backed doctor checks for workspace conformance. | `@openclaw/policy`<br />included in OpenClaw | plugin |
| [qianfan](/plugins/reference/qianfan) | Adds Qianfan model provider support to OpenClaw. | `@openclaw/qianfan-provider`<br />included in OpenClaw | providers: qianfan |
| [qwen](/plugins/reference/qwen) | Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CLI model provider support to OpenClaw. | `@openclaw/qwen-provider`<br />included in OpenClaw | providers: qwen, qwencloud, modelstudio, dashscope, qwen-oauth, qwen-portal, qwen-cli; contracts: mediaUnderstandingProviders, videoGenerationProviders |
| [qwen](/plugins/reference/qwen) | Adds Qwen, Qwen Cloud, Model Studio, DashScope model provider support to OpenClaw. | `@openclaw/qwen-provider`<br />included in OpenClaw | providers: qwen, qwencloud, modelstudio, dashscope; contracts: mediaUnderstandingProviders, videoGenerationProviders |
| [runway](/plugins/reference/runway) | Adds video generation provider support. | `@openclaw/runway-provider`<br />included in OpenClaw | contracts: videoGenerationProviders |
| [searxng](/plugins/reference/searxng) | Adds web search provider support. | `@openclaw/searxng-plugin`<br />included in OpenClaw | contracts: webSearchProviders |
| [senseaudio](/plugins/reference/senseaudio) | Adds media understanding provider support. | `@openclaw/senseaudio-provider`<br />included in OpenClaw | contracts: mediaUnderstandingProviders |
@@ -127,6 +126,7 @@ commands.
| [telegram](/plugins/reference/telegram) | Adds the Telegram channel surface for sending and receiving OpenClaw messages. | `@openclaw/telegram`<br />included in OpenClaw | channels: telegram |
| [tencent](/plugins/reference/tencent) | Adds Tencent TokenHub model provider support to OpenClaw. | `@openclaw/tencent-provider`<br />included in OpenClaw | providers: tencent-tokenhub |
| [together](/plugins/reference/together) | Adds Together model provider support to OpenClaw. | `@openclaw/together-provider`<br />included in OpenClaw | providers: together; contracts: videoGenerationProviders |
| [tokenjuice](/plugins/reference/tokenjuice) | Compacts exec and bash tool results with tokenjuice reducers. | `@openclaw/tokenjuice`<br />included in OpenClaw | contracts: agentToolResultMiddleware |
| [tts-local-cli](/plugins/reference/tts-local-cli) | Adds text-to-speech provider support. | `@openclaw/tts-local-cli`<br />included in OpenClaw | contracts: speechProviders |
| [venice](/plugins/reference/venice) | Adds Venice model provider support to OpenClaw. | `@openclaw/venice-provider`<br />included in OpenClaw | providers: venice |
| [vercel-ai-gateway](/plugins/reference/vercel-ai-gateway) | Adds Vercel AI Gateway model provider support to OpenClaw. | `@openclaw/vercel-ai-gateway-provider`<br />included in OpenClaw | providers: vercel-ai-gateway |
@@ -136,9 +136,9 @@ commands.
| [vydra](/plugins/reference/vydra) | Adds Vydra model provider support to OpenClaw. | `@openclaw/vydra-provider`<br />included in OpenClaw | providers: vydra; contracts: imageGenerationProviders, speechProviders, videoGenerationProviders |
| [web-readability](/plugins/reference/web-readability) | Extract readable article content from local HTML web fetch responses. | `@openclaw/web-readability-plugin`<br />included in OpenClaw | contracts: webContentExtractors |
| [webhooks](/plugins/reference/webhooks) | Authenticated inbound webhooks that bind external automation to OpenClaw TaskFlows. | `@openclaw/webhooks`<br />included in OpenClaw | plugin |
| [workboard](/plugins/reference/workboard) | Dashboard workboard for agent-owned issues and sessions. | `@openclaw/workboard`<br />included in OpenClaw | contracts: tools |
| [workboard](/plugins/reference/workboard) | Dashboard workboard for agent-owned issues and sessions. | `@openclaw/workboard`<br />included in OpenClaw | plugin |
| [xai](/plugins/reference/xai) | Adds xAI model provider support to OpenClaw. | `@openclaw/xai-plugin`<br />included in OpenClaw | providers: xai; contracts: imageGenerationProviders, mediaUnderstandingProviders, realtimeTranscriptionProviders, speechProviders, tools, videoGenerationProviders, webSearchProviders |
| [xiaomi](/plugins/reference/xiaomi) | Adds Xiaomi MiMo pay-as-you-go and Token Plan provider support to OpenClaw. | `@openclaw/xiaomi-provider`<br />included in OpenClaw | providers: xiaomi, xiaomi-token-plan; contracts: speechProviders |
| [xiaomi](/plugins/reference/xiaomi) | Adds Xiaomi model provider support to OpenClaw. | `@openclaw/xiaomi-provider`<br />included in OpenClaw | providers: xiaomi; contracts: speechProviders |
| [zai](/plugins/reference/zai) | Adds Z.AI model provider support to OpenClaw. | `@openclaw/zai-provider`<br />included in OpenClaw | providers: zai; contracts: mediaUnderstandingProviders |
## Official external packages
@@ -151,7 +151,6 @@ commands.
| [anthropic-vertex](/plugins/reference/anthropic-vertex) | OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI. | `@openclaw/anthropic-vertex-provider`<br />npm; ClawHub | providers: anthropic-vertex |
| [brave](/plugins/reference/brave) | OpenClaw Brave Search provider plugin for web search. | `@openclaw/brave-plugin`<br />npm; ClawHub | contracts: webSearchProviders |
| [codex](/plugins/reference/codex) | OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog. | `@openclaw/codex`<br />npm; ClawHub | providers: codex; contracts: mediaUnderstandingProviders, migrationProviders |
| [copilot](/plugins/reference/copilot) | Registers the GitHub Copilot agent runtime. | `@openclaw/copilot`<br />npm; ClawHub: `clawhub:@openclaw/copilot` | plugin |
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter for metrics and traces. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
| [diagnostics-prometheus](/plugins/reference/diagnostics-prometheus) | OpenClaw diagnostics Prometheus exporter for runtime metrics. | `@openclaw/diagnostics-prometheus`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus` | plugin |
| [diffs](/plugins/reference/diffs) | OpenClaw read-only diff viewer plugin and file renderer for agents. | `@openclaw/diffs`<br />npm; ClawHub | contracts: tools; skills |
@@ -168,12 +167,11 @@ commands.
| [nextcloud-talk](/plugins/reference/nextcloud-talk) | OpenClaw Nextcloud Talk channel plugin for conversations. | `@openclaw/nextcloud-talk`<br />npm; ClawHub | channels: nextcloud-talk |
| [nostr](/plugins/reference/nostr) | OpenClaw Nostr channel plugin for NIP-04 encrypted direct messages. | `@openclaw/nostr`<br />npm; ClawHub | channels: nostr |
| [openshell](/plugins/reference/openshell) | OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution. | `@openclaw/openshell-sandbox`<br />npm; ClawHub | plugin |
| [pixverse](/plugins/reference/pixverse) | OpenClaw PixVerse video generation provider plugin. | `@openclaw/pixverse-provider`<br />npm; ClawHub: `clawhub:@openclaw/pixverse-provider` | contracts: videoGenerationProviders |
| [pixverse](/plugins/reference/pixverse) | OpenClaw PixVerse video generation provider plugin. | `@openclaw/pixverse-provider`<br />npm; ClawHub | contracts: videoGenerationProviders |
| [qqbot](/plugins/reference/qqbot) | OpenClaw QQ Bot channel plugin for group and direct-message workflows. | `@openclaw/qqbot`<br />npm; ClawHub | channels: qqbot; contracts: tools; skills |
| [slack](/plugins/reference/slack) | OpenClaw Slack channel plugin for channels, DMs, commands, and app events. | `@openclaw/slack`<br />npm; ClawHub | channels: slack |
| [synology-chat](/plugins/reference/synology-chat) | Synology Chat channel plugin for OpenClaw channels and direct messages. | `@openclaw/synology-chat`<br />npm; ClawHub | channels: synology-chat |
| [tlon](/plugins/reference/tlon) | OpenClaw Tlon/Urbit channel plugin for chat workflows. | `@openclaw/tlon`<br />npm; ClawHub | channels: tlon; skills |
| [tokenjuice](/plugins/reference/tokenjuice) | Compacts exec and bash tool results with tokenjuice reducers. | `@openclaw/tokenjuice`<br />npm; ClawHub: `clawhub:@openclaw/tokenjuice` | contracts: agentToolResultMiddleware |
| [twitch](/plugins/reference/twitch) | OpenClaw Twitch channel plugin for chat and moderation workflows. | `@openclaw/twitch`<br />npm; ClawHub | channels: twitch |
| [voice-call](/plugins/reference/voice-call) | OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls. | `@openclaw/voice-call`<br />npm; ClawHub | contracts: tools |
| [whatsapp](/plugins/reference/whatsapp) | OpenClaw WhatsApp channel plugin for WhatsApp Web chats. | `@openclaw/whatsapp`<br />ClawHub: `clawhub:@openclaw/whatsapp`; npm | channels: whatsapp |

View File

@@ -38,7 +38,7 @@ pnpm plugins:inventory:gen
| [codex](/plugins/reference/codex) | OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog. | `@openclaw/codex`<br />npm; ClawHub | providers: codex; contracts: mediaUnderstandingProviders, migrationProviders |
| [codex-supervisor](/plugins/reference/codex-supervisor) | Supervise Codex app-server sessions from OpenClaw. | `@openclaw/codex-supervisor`<br />included in OpenClaw | contracts: tools |
| [comfy](/plugins/reference/comfy) | Adds ComfyUI model provider support to OpenClaw. | `@openclaw/comfy-provider`<br />included in OpenClaw | providers: comfy; contracts: imageGenerationProviders, musicGenerationProviders, videoGenerationProviders |
| [copilot](/plugins/reference/copilot) | Registers the GitHub Copilot agent runtime. | `@openclaw/copilot`<br />npm; ClawHub: `clawhub:@openclaw/copilot` | plugin |
| [copilot](/plugins/reference/copilot) | Registers the GitHub Copilot agent runtime. | `@openclaw/copilot`<br />included in OpenClaw | plugin |
| [copilot-proxy](/plugins/reference/copilot-proxy) | Adds Copilot Proxy model provider support to OpenClaw. | `@openclaw/copilot-proxy`<br />included in OpenClaw | providers: copilot-proxy |
| [deepgram](/plugins/reference/deepgram) | Adds media understanding provider support. Adds realtime transcription provider support. | `@openclaw/deepgram-provider`<br />included in OpenClaw | contracts: mediaUnderstandingProviders, realtimeTranscriptionProviders |
| [deepinfra](/plugins/reference/deepinfra) | Adds DeepInfra model provider support to OpenClaw. | `@openclaw/deepinfra-provider`<br />included in OpenClaw | providers: deepinfra; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, speechProviders, videoGenerationProviders |
@@ -58,7 +58,6 @@ pnpm plugins:inventory:gen
| [firecrawl](/plugins/reference/firecrawl) | Adds agent-callable tools. Adds web fetch provider support. Adds web search provider support. | `@openclaw/firecrawl-plugin`<br />included in OpenClaw | contracts: tools, webFetchProviders, webSearchProviders |
| [fireworks](/plugins/reference/fireworks) | Adds Fireworks model provider support to OpenClaw. | `@openclaw/fireworks-provider`<br />included in OpenClaw | providers: fireworks |
| [github-copilot](/plugins/reference/github-copilot) | Adds GitHub Copilot model provider support to OpenClaw. | `@openclaw/github-copilot-provider`<br />included in OpenClaw | providers: github-copilot; contracts: memoryEmbeddingProviders |
| [gmi](/plugins/reference/gmi) | Adds Gmi, Gmi Cloud, Gmicloud model provider support to OpenClaw. | `@openclaw/gmi-provider`<br />included in OpenClaw | providers: gmi, gmi-cloud, gmicloud |
| [google](/plugins/reference/google) | Adds Google, Google Gemini CLI, Google Vertex model provider support to OpenClaw. | `@openclaw/google-plugin`<br />included in OpenClaw | providers: google, google-gemini-cli, google-vertex; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, musicGenerationProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders, webSearchProviders |
| [google-meet](/plugins/reference/google-meet) | OpenClaw Google Meet participant plugin for joining calls through Chrome or Twilio transports. | `@openclaw/google-meet`<br />npm; ClawHub | contracts: tools |
| [googlechat](/plugins/reference/googlechat) | OpenClaw Google Chat channel plugin for spaces and direct messages. | `@openclaw/googlechat`<br />npm; ClawHub | channels: googlechat |
@@ -90,25 +89,24 @@ pnpm plugins:inventory:gen
| [msteams](/plugins/reference/msteams) | OpenClaw Microsoft Teams channel plugin for bot conversations. | `@openclaw/msteams`<br />npm; ClawHub | channels: msteams |
| [nextcloud-talk](/plugins/reference/nextcloud-talk) | OpenClaw Nextcloud Talk channel plugin for conversations. | `@openclaw/nextcloud-talk`<br />npm; ClawHub | channels: nextcloud-talk |
| [nostr](/plugins/reference/nostr) | OpenClaw Nostr channel plugin for NIP-04 encrypted direct messages. | `@openclaw/nostr`<br />npm; ClawHub | channels: nostr |
| [novita](/plugins/reference/novita) | Adds Novita, Novita AI, Novitaai model provider support to OpenClaw. | `@openclaw/novita-provider`<br />included in OpenClaw | providers: novita, novita-ai, novitaai |
| [nvidia](/plugins/reference/nvidia) | Adds NVIDIA model provider support to OpenClaw. | `@openclaw/nvidia-provider`<br />included in OpenClaw | providers: nvidia |
| [oc-path](/plugins/reference/oc-path) | Adds the openclaw path CLI for oc:// workspace file addressing. | `@openclaw/oc-path`<br />included in OpenClaw | plugin |
| [ollama](/plugins/reference/ollama) | Adds Ollama, Ollama Cloud model provider support to OpenClaw. | `@openclaw/ollama-provider`<br />included in OpenClaw | providers: ollama, ollama-cloud; contracts: memoryEmbeddingProviders, webSearchProviders |
| [ollama](/plugins/reference/ollama) | Adds Ollama model provider support to OpenClaw. | `@openclaw/ollama-provider`<br />included in OpenClaw | providers: ollama; contracts: memoryEmbeddingProviders, webSearchProviders |
| [open-prose](/plugins/reference/open-prose) | OpenProse VM skill pack with a /prose slash command. | `@openclaw/open-prose`<br />included in OpenClaw | skills |
| [openai](/plugins/reference/openai) | Adds OpenAI model provider support to OpenClaw, including ChatGPT/Codex OAuth. | `@openclaw/openai-provider`<br />included in OpenClaw | providers: openai; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders |
| [openai](/plugins/reference/openai) | Adds OpenAI, OpenAI Codex model provider support to OpenClaw. | `@openclaw/openai-provider`<br />included in OpenClaw | providers: openai, openai-codex; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders |
| [opencode](/plugins/reference/opencode) | Adds OpenCode model provider support to OpenClaw. | `@openclaw/opencode-provider`<br />included in OpenClaw | providers: opencode; contracts: mediaUnderstandingProviders |
| [opencode-go](/plugins/reference/opencode-go) | Adds OpenCode Go model provider support to OpenClaw. | `@openclaw/opencode-go-provider`<br />included in OpenClaw | providers: opencode-go; contracts: mediaUnderstandingProviders |
| [openrouter](/plugins/reference/openrouter) | Adds OpenRouter model provider support to OpenClaw. | `@openclaw/openrouter-provider`<br />included in OpenClaw | providers: openrouter; contracts: imageGenerationProviders, mediaUnderstandingProviders, musicGenerationProviders, speechProviders, videoGenerationProviders |
| [openshell](/plugins/reference/openshell) | OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution. | `@openclaw/openshell-sandbox`<br />npm; ClawHub | plugin |
| [perplexity](/plugins/reference/perplexity) | Adds web search provider support. | `@openclaw/perplexity-plugin`<br />included in OpenClaw | contracts: webSearchProviders |
| [pixverse](/plugins/reference/pixverse) | OpenClaw PixVerse video generation provider plugin. | `@openclaw/pixverse-provider`<br />npm; ClawHub: `clawhub:@openclaw/pixverse-provider` | contracts: videoGenerationProviders |
| [pixverse](/plugins/reference/pixverse) | OpenClaw PixVerse video generation provider plugin. | `@openclaw/pixverse-provider`<br />npm; ClawHub | contracts: videoGenerationProviders |
| [policy](/plugins/reference/policy) | Adds policy-backed doctor checks for workspace conformance. | `@openclaw/policy`<br />included in OpenClaw | plugin |
| [qa-channel](/plugins/reference/qa-channel) | Adds the QA Channel surface for sending and receiving OpenClaw messages. | `@openclaw/qa-channel`<br />source checkout only | channels: qa-channel |
| [qa-lab](/plugins/reference/qa-lab) | OpenClaw QA lab plugin with private debugger UI and scenario runner. | `@openclaw/qa-lab`<br />source checkout only | plugin |
| [qa-matrix](/plugins/reference/qa-matrix) | Matrix QA transport runner and substrate. | `@openclaw/qa-matrix`<br />source checkout only | plugin |
| [qianfan](/plugins/reference/qianfan) | Adds Qianfan model provider support to OpenClaw. | `@openclaw/qianfan-provider`<br />included in OpenClaw | providers: qianfan |
| [qqbot](/plugins/reference/qqbot) | OpenClaw QQ Bot channel plugin for group and direct-message workflows. | `@openclaw/qqbot`<br />npm; ClawHub | channels: qqbot; contracts: tools; skills |
| [qwen](/plugins/reference/qwen) | Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CLI model provider support to OpenClaw. | `@openclaw/qwen-provider`<br />included in OpenClaw | providers: qwen, qwencloud, modelstudio, dashscope, qwen-oauth, qwen-portal, qwen-cli; contracts: mediaUnderstandingProviders, videoGenerationProviders |
| [qwen](/plugins/reference/qwen) | Adds Qwen, Qwen Cloud, Model Studio, DashScope model provider support to OpenClaw. | `@openclaw/qwen-provider`<br />included in OpenClaw | providers: qwen, qwencloud, modelstudio, dashscope; contracts: mediaUnderstandingProviders, videoGenerationProviders |
| [runway](/plugins/reference/runway) | Adds video generation provider support. | `@openclaw/runway-provider`<br />included in OpenClaw | contracts: videoGenerationProviders |
| [searxng](/plugins/reference/searxng) | Adds web search provider support. | `@openclaw/searxng-plugin`<br />included in OpenClaw | contracts: webSearchProviders |
| [senseaudio](/plugins/reference/senseaudio) | Adds media understanding provider support. | `@openclaw/senseaudio-provider`<br />included in OpenClaw | contracts: mediaUnderstandingProviders |
@@ -124,7 +122,7 @@ pnpm plugins:inventory:gen
| [tencent](/plugins/reference/tencent) | Adds Tencent TokenHub model provider support to OpenClaw. | `@openclaw/tencent-provider`<br />included in OpenClaw | providers: tencent-tokenhub |
| [tlon](/plugins/reference/tlon) | OpenClaw Tlon/Urbit channel plugin for chat workflows. | `@openclaw/tlon`<br />npm; ClawHub | channels: tlon; skills |
| [together](/plugins/reference/together) | Adds Together model provider support to OpenClaw. | `@openclaw/together-provider`<br />included in OpenClaw | providers: together; contracts: videoGenerationProviders |
| [tokenjuice](/plugins/reference/tokenjuice) | Compacts exec and bash tool results with tokenjuice reducers. | `@openclaw/tokenjuice`<br />npm; ClawHub: `clawhub:@openclaw/tokenjuice` | contracts: agentToolResultMiddleware |
| [tokenjuice](/plugins/reference/tokenjuice) | Compacts exec and bash tool results with tokenjuice reducers. | `@openclaw/tokenjuice`<br />included in OpenClaw | contracts: agentToolResultMiddleware |
| [tts-local-cli](/plugins/reference/tts-local-cli) | Adds text-to-speech provider support. | `@openclaw/tts-local-cli`<br />included in OpenClaw | contracts: speechProviders |
| [twitch](/plugins/reference/twitch) | OpenClaw Twitch channel plugin for chat and moderation workflows. | `@openclaw/twitch`<br />npm; ClawHub | channels: twitch |
| [venice](/plugins/reference/venice) | Adds Venice model provider support to OpenClaw. | `@openclaw/venice-provider`<br />included in OpenClaw | providers: venice |
@@ -137,9 +135,9 @@ pnpm plugins:inventory:gen
| [web-readability](/plugins/reference/web-readability) | Extract readable article content from local HTML web fetch responses. | `@openclaw/web-readability-plugin`<br />included in OpenClaw | contracts: webContentExtractors |
| [webhooks](/plugins/reference/webhooks) | Authenticated inbound webhooks that bind external automation to OpenClaw TaskFlows. | `@openclaw/webhooks`<br />included in OpenClaw | plugin |
| [whatsapp](/plugins/reference/whatsapp) | OpenClaw WhatsApp channel plugin for WhatsApp Web chats. | `@openclaw/whatsapp`<br />ClawHub: `clawhub:@openclaw/whatsapp`; npm | channels: whatsapp |
| [workboard](/plugins/reference/workboard) | Dashboard workboard for agent-owned issues and sessions. | `@openclaw/workboard`<br />included in OpenClaw | contracts: tools |
| [workboard](/plugins/reference/workboard) | Dashboard workboard for agent-owned issues and sessions. | `@openclaw/workboard`<br />included in OpenClaw | plugin |
| [xai](/plugins/reference/xai) | Adds xAI model provider support to OpenClaw. | `@openclaw/xai-plugin`<br />included in OpenClaw | providers: xai; contracts: imageGenerationProviders, mediaUnderstandingProviders, realtimeTranscriptionProviders, speechProviders, tools, videoGenerationProviders, webSearchProviders |
| [xiaomi](/plugins/reference/xiaomi) | Adds Xiaomi MiMo pay-as-you-go and Token Plan provider support to OpenClaw. | `@openclaw/xiaomi-provider`<br />included in OpenClaw | providers: xiaomi, xiaomi-token-plan; contracts: speechProviders |
| [xiaomi](/plugins/reference/xiaomi) | Adds Xiaomi model provider support to OpenClaw. | `@openclaw/xiaomi-provider`<br />included in OpenClaw | providers: xiaomi; contracts: speechProviders |
| [zai](/plugins/reference/zai) | Adds Z.AI model provider support to OpenClaw. | `@openclaw/zai-provider`<br />included in OpenClaw | providers: zai; contracts: mediaUnderstandingProviders |
| [zalo](/plugins/reference/zalo) | OpenClaw Zalo channel plugin for bot and webhook chats. | `@openclaw/zalo`<br />npm; ClawHub | channels: zalo |
| [zalouser](/plugins/reference/zalouser) | OpenClaw Zalo Personal Account plugin via native zca-js integration. | `@openclaw/zalouser`<br />npm; ClawHub | channels: zalouser; contracts: tools |

View File

@@ -17,11 +17,3 @@ Supervise Codex app-server sessions from OpenClaw.
## Surface
contracts: tools
<!-- openclaw-plugin-reference:manual-start -->
## Session Listing
`codex_sessions_list` defaults to loaded Codex sessions only. Set `include_stored` to include stored history; the plugin uses Codex app-server's state-DB-only listing path and caps stored results at 200 by default. Pass `max_stored_sessions` to lower or raise that cap, up to 1000.
<!-- openclaw-plugin-reference:manual-end -->

View File

@@ -12,7 +12,7 @@ Registers the GitHub Copilot agent runtime.
## Distribution
- Package: `@openclaw/copilot`
- Install route: npm; ClawHub: `clawhub:@openclaw/copilot`
- Install route: included in OpenClaw
## Surface

View File

@@ -1,23 +0,0 @@
---
summary: "Adds Gmi, Gmi Cloud, Gmicloud model provider support to OpenClaw."
read_when:
- You are installing, configuring, or auditing the gmi plugin
title: "Gmi plugin"
---
# Gmi plugin
Adds Gmi, Gmi Cloud, Gmicloud model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/gmi-provider`
- Install route: included in OpenClaw
## Surface
providers: gmi, gmi-cloud, gmicloud
## Related docs
- [gmi](/providers/gmi)

View File

@@ -1,23 +0,0 @@
---
summary: "Adds Novita, Novita AI, Novitaai model provider support to OpenClaw."
read_when:
- You are installing, configuring, or auditing the novita plugin
title: "Novita plugin"
---
# Novita plugin
Adds Novita, Novita AI, Novitaai model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/novita-provider`
- Install route: included in OpenClaw
## Surface
providers: novita, novita-ai, novitaai
## Related docs
- [novita](/providers/novita)

View File

@@ -1,5 +1,5 @@
---
summary: "Adds Ollama, Ollama Cloud model provider support to OpenClaw."
summary: "Adds Ollama model provider support to OpenClaw."
read_when:
- You are installing, configuring, or auditing the ollama plugin
title: "Ollama plugin"
@@ -7,7 +7,7 @@ title: "Ollama plugin"
# Ollama plugin
Adds Ollama, Ollama Cloud model provider support to OpenClaw.
Adds Ollama model provider support to OpenClaw.
## Distribution
@@ -16,7 +16,7 @@ Adds Ollama, Ollama Cloud model provider support to OpenClaw.
## Surface
providers: ollama, ollama-cloud; contracts: memoryEmbeddingProviders, webSearchProviders
providers: ollama; contracts: memoryEmbeddingProviders, webSearchProviders
## Related docs

View File

@@ -16,7 +16,7 @@ Adds OpenAI, OpenAI Codex model provider support to OpenClaw.
## Surface
providers: openai; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders
providers: openai, openai-codex; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders
## Related docs

View File

@@ -12,7 +12,7 @@ OpenClaw PixVerse video generation provider plugin.
## Distribution
- Package: `@openclaw/pixverse-provider`
- Install route: npm; ClawHub: `clawhub:@openclaw/pixverse-provider`
- Install route: npm; ClawHub
## Surface

View File

@@ -1,5 +1,5 @@
---
summary: "Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CLI model provider support to OpenClaw."
summary: "Adds Qwen, Qwen Cloud, Model Studio, DashScope model provider support to OpenClaw."
read_when:
- You are installing, configuring, or auditing the qwen plugin
title: "Qwen plugin"
@@ -7,7 +7,7 @@ title: "Qwen plugin"
# Qwen plugin
Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CLI model provider support to OpenClaw.
Adds Qwen, Qwen Cloud, Model Studio, DashScope model provider support to OpenClaw.
## Distribution
@@ -16,7 +16,7 @@ Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CL
## Surface
providers: qwen, qwencloud, modelstudio, dashscope, qwen-oauth, qwen-portal, qwen-cli; contracts: mediaUnderstandingProviders, videoGenerationProviders
providers: qwen, qwencloud, modelstudio, dashscope; contracts: mediaUnderstandingProviders, videoGenerationProviders
## Related docs

View File

@@ -12,7 +12,7 @@ Compacts exec and bash tool results with tokenjuice reducers.
## Distribution
- Package: `@openclaw/tokenjuice`
- Install route: npm; ClawHub: `clawhub:@openclaw/tokenjuice`
- Install route: included in OpenClaw
## Surface

View File

@@ -16,7 +16,7 @@ Dashboard workboard for agent-owned issues and sessions.
## Surface
contracts: tools
plugin
## Related docs

View File

@@ -1,5 +1,5 @@
---
summary: "Adds Xiaomi MiMo pay-as-you-go and Token Plan provider support to OpenClaw."
summary: "Adds Xiaomi model provider support to OpenClaw."
read_when:
- You are installing, configuring, or auditing the xiaomi plugin
title: "Xiaomi plugin"
@@ -7,7 +7,7 @@ title: "Xiaomi plugin"
# Xiaomi plugin
Adds Xiaomi MiMo pay-as-you-go and Token Plan provider support to OpenClaw.
Adds Xiaomi model provider support to OpenClaw.
## Distribution
@@ -16,7 +16,7 @@ Adds Xiaomi MiMo pay-as-you-go and Token Plan provider support to OpenClaw.
## Surface
providers: xiaomi, xiaomi-token-plan; contracts: speechProviders
providers: xiaomi; contracts: speechProviders
## Related docs

View File

@@ -155,7 +155,6 @@ Most channel plugins do not need approval-specific code.
- If custom approval auth intentionally allows only same-chat fallback, return `markImplicitSameChatApprovalAuthorization({ authorized: true })` from `openclaw/plugin-sdk/approval-auth-runtime`; otherwise core treats the result as explicit approver authorization.
- If a channel-owned native callback resolves approvals directly, use `isImplicitSameChatApprovalAuthorization(...)` before resolving so implicit fallback still goes through the channel's normal actor authorization.
- If a channel needs native approval delivery, keep channel code focused on target normalization plus transport/presentation facts. Use `createChannelExecApprovalProfile`, `createChannelNativeOriginTargetResolver`, `createChannelApproverDmTargetResolver`, and `createApproverRestrictedNativeApprovalCapability` from `openclaw/plugin-sdk/approval-runtime`. Put the channel-specific facts behind `approvalCapability.nativeRuntime`, ideally via `createChannelApprovalNativeRuntimeAdapter(...)` or `createLazyChannelApprovalNativeRuntimeAdapter(...)`, so core can assemble the handler and own request filtering, routing, dedupe, expiry, gateway subscription, and routed-elsewhere notices. `nativeRuntime` is split into a few smaller seams:
- Use `createNativeApprovalChannelRouteGates` from `openclaw/plugin-sdk/approval-native-runtime` when a channel supports both session-origin native delivery and explicit approval forwarding targets. The helper centralizes approval config selection, `mode` handling, agent/session filters, account binding, session-target matching, and target-list matching while callers still own the channel id, default forwarding mode, account lookup, transport-enabled check, target normalization, and turn-source target resolution. Do not use it to create core-owned channel policy defaults; pass the channel's documented default mode explicitly.
- `createChannelNativeOriginTargetResolver` uses the shared channel-route matcher by default for `{ to, accountId, threadId }` targets. Pass `targetsMatch` only when a channel has provider-specific equivalence rules, such as Slack timestamp prefix matching.
- Pass `normalizeTargetForMatch` to `createChannelNativeOriginTargetResolver` when the channel needs to canonicalize provider ids before the default route matcher or a custom `targetsMatch` callback runs, while preserving the original target for delivery. Use `normalizeTarget` only when the resolved delivery target itself should be canonicalized.
- `availability` - whether the account is configured and whether a request should be handled

View File

@@ -365,7 +365,7 @@ API key auth, and dynamic model resolution.
| `kilocode-thinking` | Kilo reasoning wrapper on the shared proxy stream path, with `kilo/auto` and unsupported proxy reasoning ids skipping injected thinking | `kilocode` |
| `moonshot-thinking` | Moonshot binary native-thinking payload mapping from config + `/think` level | `moonshot` |
| `minimax-fast-mode` | MiniMax fast-mode model rewrite on the shared stream path | `minimax`, `minimax-portal` |
| `openai-responses-defaults` | Shared native OpenAI/Codex Responses wrappers: attribution headers, `/fast`/`serviceTier`, text verbosity, native Codex web search, reasoning-compat payload shaping, and Responses context management | `openai` |
| `openai-responses-defaults` | Shared native OpenAI/Codex Responses wrappers: attribution headers, `/fast`/`serviceTier`, text verbosity, native Codex web search, reasoning-compat payload shaping, and Responses context management | `openai`, `openai-codex` |
| `openrouter-thinking` | OpenRouter reasoning wrapper for proxy routes, with unsupported-model/`auto` skips handled centrally | `openrouter` |
| `tool-stream-default-on` | Default-on `tool_stream` wrapper for providers like Z.AI that want tool streaming unless explicitly disabled | `zai` |

View File

@@ -144,11 +144,10 @@ and pairing-path families.
| `plugin-sdk/self-hosted-provider-setup` | Focused OpenAI-compatible self-hosted provider setup helpers |
| `plugin-sdk/cli-backend` | CLI backend defaults + watchdog constants |
| `plugin-sdk/provider-auth-runtime` | Runtime API-key resolution helpers for provider plugins |
| `plugin-sdk/provider-oauth-runtime` | Generic provider OAuth callback types, callback-page rendering, PKCE/state helpers, authorization-input parsing, token-expiry helpers, and abort helpers |
| `plugin-sdk/provider-auth-api-key` | API-key onboarding/profile-write helpers such as `upsertApiKeyProfile` |
| `plugin-sdk/provider-auth-result` | Standard OAuth auth-result builder |
| `plugin-sdk/provider-env-vars` | Provider auth env-var lookup helpers |
| `plugin-sdk/provider-auth` | `createProviderApiKeyAuthMethod`, `ensureApiKeyFromOptionEnvOrPrompt`, `upsertAuthProfile`, `upsertApiKeyProfile`, `writeOAuthCredentials`, OpenAI Codex auth-import helpers, deprecated `resolveOpenClawAgentDir` compatibility export |
| `plugin-sdk/provider-auth` | `createProviderApiKeyAuthMethod`, `ensureApiKeyFromOptionEnvOrPrompt`, `upsertAuthProfile`, `upsertApiKeyProfile`, `writeOAuthCredentials`, deprecated `resolveOpenClawAgentDir` compatibility export |
| `plugin-sdk/provider-model-shared` | `ProviderReplayFamily`, `buildProviderReplayFamilyHooks`, `normalizeModelCompat`, shared replay-policy builders, provider-endpoint helpers, and shared model-id normalization helpers |
| `plugin-sdk/provider-catalog-runtime` | Provider catalog augmentation runtime hook and plugin-provider registry seams for contract tests |
| `plugin-sdk/provider-catalog-shared` | `findCatalogTemplate`, `buildSingleProviderApiKeyCatalog`, `buildManifestModelProviderConfig`, `supportsNativeStreamingUsageCompat`, `applyProviderNativeStreamingUsageCompat` |
@@ -180,7 +179,7 @@ and pairing-path families.
| `plugin-sdk/approval-gateway-runtime` | Shared approval gateway-resolution helper |
| `plugin-sdk/approval-handler-adapter-runtime` | Lightweight native approval adapter loading helpers for hot channel entrypoints |
| `plugin-sdk/approval-handler-runtime` | Broader approval handler runtime helpers; prefer the narrower adapter/gateway seams when they are enough |
| `plugin-sdk/approval-native-runtime` | Native approval target, account-binding, route-gate, forwarding fallback, and local native exec prompt suppression helpers |
| `plugin-sdk/approval-native-runtime` | Native approval target + account-binding helpers and local native exec prompt suppression |
| `plugin-sdk/approval-reaction-runtime` | Hardcoded approval reaction bindings, reaction prompt payloads, reaction target stores, and compatibility export for local native exec prompt suppression |
| `plugin-sdk/approval-reply-runtime` | Exec/plugin approval reply payload helpers |
| `plugin-sdk/approval-runtime` | Exec/plugin approval payload helpers, native approval routing/runtime helpers, and structured approval display helpers such as `formatApprovalDisplayPath` |
@@ -193,7 +192,6 @@ and pairing-path families.
| `plugin-sdk/allow-from` | `formatAllowFromLowercase` |
| `plugin-sdk/channel-secret-runtime` | Narrow secret-contract collection helpers for channel/plugin secret surfaces |
| `plugin-sdk/secret-ref-runtime` | Narrow `coerceSecretRef` and SecretRef typing helpers for secret-contract/config parsing |
| `plugin-sdk/secret-provider-integration` | Type-only SecretRef provider integration manifest and preset contracts for plugins that publish external secret provider presets |
| `plugin-sdk/security-runtime` | Shared trust, DM gating, root-bounded file/path helpers including create-only writes, sync/async atomic file replacement, sibling temp writes, cross-device move fallback, private file-store helpers, symlink-parent guards, external-content, sensitive text redaction, constant-time secret comparison, and secret-collection helpers |
| `plugin-sdk/ssrf-policy` | Host allowlist and private-network SSRF policy helpers |
| `plugin-sdk/ssrf-dispatcher` | Narrow pinned-dispatcher helpers without the broad infra runtime surface |
@@ -221,7 +219,6 @@ and pairing-path families.
| `plugin-sdk/lazy-runtime` | Lazy runtime import/binding helpers such as `createLazyRuntimeModule`, `createLazyRuntimeMethod`, and `createLazyRuntimeSurface` |
| `plugin-sdk/process-runtime` | Process exec helpers |
| `plugin-sdk/cli-runtime` | CLI formatting, wait, version, argument-invocation, and lazy command-group helpers |
| `plugin-sdk/qa-live-transport-scenarios` | Shared live transport QA scenario ids, baseline coverage helpers, and scenario-selection helper |
| `plugin-sdk/gateway-method-runtime` | Reserved Gateway method dispatch helper for plugin HTTP routes that declare `contracts.gatewayMethodDispatch: ["authenticated-request"]` |
| `plugin-sdk/gateway-runtime` | Gateway client, event-loop-ready client start helper, gateway CLI RPC, gateway protocol errors, and channel-status patch helpers |
| `plugin-sdk/config-contracts` | Focused type-only config surface for plugin config shapes such as `OpenClawConfig` and channel/provider config types |
@@ -292,7 +289,6 @@ and pairing-path families.
| `plugin-sdk/error-runtime` | Error graph, formatting, shared error classification helpers, `isApprovalNotFoundError` |
| `plugin-sdk/fetch-runtime` | Wrapped fetch, proxy, EnvHttpProxyAgent option, and pinned lookup helpers |
| `plugin-sdk/runtime-fetch` | Dispatcher-aware runtime fetch without proxy/guarded-fetch imports |
| `plugin-sdk/inline-image-data-url-runtime` | Inline image data URL sanitizer and signature sniffing helpers without the broad media runtime surface |
| `plugin-sdk/response-limit-runtime` | Bounded response-body reader without the broad media runtime surface |
| `plugin-sdk/session-binding-runtime` | Current conversation binding state without configured binding routing or pairing stores |
| `plugin-sdk/session-store-runtime` | Session-store helpers without broad config writes/maintenance imports |

View File

@@ -42,19 +42,14 @@ view shows a plugin-unavailable state instead of local card data.
Each card stores:
- title and notes
- status: `triage`, `backlog`, `todo`, `scheduled`, `ready`, `running`,
`review`, `blocked`, or `done`
- status: `backlog`, `todo`, `running`, `review`, `blocked`, or `done`
- priority: `low`, `normal`, `high`, or `urgent`
- labels
- optional agent id
- optional linked session, run, task, or source URL
- optional execution metadata for a Codex or Claude session started from the card
- compact metadata for attempts, comments, links, proof, artifacts, automation,
claims, diagnostics, notifications, templates, archive state, and
stale-session detection
- recent card events such as created, moved, linked, claimed, heartbeat,
attempt, proof, artifact, diagnostic, notification, dispatch, archive, stale,
or agent-updated changes
- compact metadata for attempts, comments, links, proof, templates, archive state, and stale-session detection
- recent card events such as created, moved, linked, attempt, proof, archive, stale, or agent-updated changes
Cards are stored in the plugin's Gateway state. They are local to the Gateway
state directory and move with the rest of that Gateway's OpenClaw state.
@@ -85,69 +80,6 @@ Each linked execution also records an attempt summary on the same card record.
The attempt summary keeps the engine, mode, model, run id, timestamps, status,
and rolling failure count so repeated failures remain visible on the board.
## Agent coordination
Workboard also exposes optional agent tools for board-aware workflows:
- `workboard_list` lists compact cards with claim and diagnostic state, with an
optional board filter.
- `workboard_read` returns one card plus bounded worker context built from notes,
attempts, comments, links, proof, artifacts, parent results, recent assignee
work, and active diagnostics.
- `workboard_create` creates a card with optional parents, tenant, skills,
board, workspace metadata, idempotency key, runtime limit, and retry budget.
- `workboard_link` links a parent card to a child card. Children stay in `todo`
until every parent reaches `done`; then dispatch promotion moves them to
`ready`.
- `workboard_claim` claims a card for the calling agent and moves backlog, todo,
or ready cards into `running`.
- `workboard_heartbeat` refreshes the claim heartbeat during longer runs.
- `workboard_release` releases the claim after completion, pause, or handoff and
can move the card to a next status.
- `workboard_complete` and `workboard_block` are structured lifecycle tools for
final summaries, proof, artifacts, created-card manifests, and blocker
reasons. Created-card manifests must reference cards linked back to the
completed card, which keeps phantom children out of summaries.
- `workboard_board_create`, `workboard_board_archive`, and
`workboard_board_delete` manage persisted board metadata such as display name,
description, archive state, and default workspace.
- `workboard_runs` returns the persisted run-attempt history stored on a card.
- `workboard_specify` turns a rough triage or backlog card into a clarified
`todo` card and records the specification summary on the card.
- `workboard_decompose` fans a parent orchestration card into linked children,
inherits board and tenant metadata, and can complete the parent with a
created-card manifest.
- `workboard_notify_subscribe`, `workboard_notify_list`, and
`workboard_notify_unsubscribe` manage notification subscriptions in plugin
state so operators and agents can discover durable notification intent.
- `workboard_boards`, `workboard_stats`, `workboard_promote`,
`workboard_reassign`, `workboard_reclaim`, `workboard_comment`,
`workboard_proof`, `workboard_unblock`, and `workboard_dispatch` let an agent
inspect board namespaces, view queue stats, recover stuck work, add handoff
notes, attach proof or artifact references, move blocked work back to `todo`,
and nudge dependency promotion or stale-claim cleanup.
Claimed cards reject agent-tool mutations from other agents unless the caller
has the claim token returned by `workboard_claim`. Dashboard operators still use
the normal Gateway RPC surface and can recover or reassign cards.
Workboard stores all durable board data through the plugin SQLite key-value
store. Cards live in `workboard.cards`, board metadata in `workboard.boards`,
and notification subscriptions in `workboard.notify`. Run history, comments,
proof, artifacts, diagnostics, dependencies, lifecycle events, and automation
metadata stay on the card record so a card export remains self-contained.
Workboard diagnostics are computed from local card metadata. The built-in checks
flag assigned cards that wait too long, running cards without recent heartbeat,
blocked cards that need attention, repeated failures, done cards without proof,
and running cards that only have a loose session link.
Dispatch is intentionally Gateway-local. It does not spawn arbitrary operating
system processes; normal OpenClaw sessions still own execution. A dispatch nudge
promotes dependency-ready cards, records dispatch metadata on ready cards,
blocks expired claims or timed-out runs, and leaves durable notification
subscriptions for the caller that delivers notifications.
## Session lifecycle sync
Cards can be linked to existing dashboard sessions or to the session created
@@ -204,11 +136,7 @@ The plugin registers Gateway RPC methods under the `workboard.*` namespace:
- `workboard.cards.list` requires `operator.read`
- `workboard.cards.export` requires `operator.read`
- `workboard.cards.diagnostics` requires `operator.read`
- `workboard.cards.diagnostics.refresh` requires `operator.write`
- create, update, move, delete, comment, link, dependency link, proof, artifact,
claim, heartbeat, release, complete, block, unblock, dispatch, bulk, and
archive methods require `operator.write`
- create, update, move, delete, comment, link, proof, and archive methods require `operator.write`
Browsers connected with read-only operator access can inspect the board but
cannot mutate cards.

View File

@@ -3,15 +3,14 @@ summary: "Sign in to GitHub Copilot from OpenClaw using the device flow or non-i
read_when:
- You want to use GitHub Copilot as a model provider
- You need the `openclaw models auth login-github-copilot` flow
- You are choosing between the built-in Copilot provider, Copilot SDK harness, and Copilot Proxy
title: "GitHub Copilot"
---
GitHub Copilot is GitHub's AI coding assistant. It provides access to Copilot
models for your GitHub account and plan. OpenClaw can use Copilot as a model
provider or agent runtime in three different ways.
provider in two different ways.
## Three ways to use Copilot in OpenClaw
## Two ways to use Copilot in OpenClaw
<Tabs>
<Tab title="Built-in provider (github-copilot)">
@@ -47,38 +46,6 @@ provider or agent runtime in three different ways.
</Tab>
<Tab title="Copilot SDK harness plugin (copilot)">
Install the external `@openclaw/copilot` plugin when you want GitHub's
Copilot CLI and SDK to own the low-level agent loop for selected
`github-copilot/*` models.
```bash
openclaw plugins install clawhub:@openclaw/copilot
```
Then opt a model or provider into the runtime:
```json5
{
agents: {
defaults: {
model: "github-copilot/gpt-5.5",
models: {
"github-copilot/gpt-5.5": {
agentRuntime: { id: "copilot" },
},
},
},
},
}
```
Choose this when you want native Copilot CLI sessions, SDK-managed thread
state, and Copilot-owned compaction for those agent turns. See
[Copilot SDK harness](/plugins/copilot) for the full runtime contract.
</Tab>
<Tab title="Copilot Proxy plugin (copilot-proxy)">
Use the **Copilot Proxy** VS Code extension as a local bridge. OpenClaw talks to
the proxy's `/v1` endpoint and uses the model list you configure there.

View File

@@ -1,92 +0,0 @@
---
summary: "Use GMI Cloud's OpenAI-compatible API with OpenClaw"
read_when:
- You want to run OpenClaw with GMI Cloud models
- You need the GMI provider id, key, or endpoint
title: "GMI Cloud"
---
GMI Cloud is a hosted inference platform for frontier and open-weight models
behind an OpenAI-compatible API. In OpenClaw it is a bundled model provider,
which means you can select it with the provider id `gmi`, store credentials
through normal model auth, and use model refs like
`gmi/google/gemini-3.1-flash-lite`.
Use GMI when you want one API key for several hosted model families, including
Google, Anthropic, OpenAI, DeepSeek, Moonshot, and Z.AI routes exposed by GMI's
catalog. It is useful as a secondary provider for model fallback, for comparing
hosted routes across vendors, or when GMI has a model available before your
primary provider does.
This provider uses OpenAI-compatible chat semantics. OpenClaw owns the provider
id, auth profile, aliases, model catalog seed, and base URL; GMI owns the live
model availability, billing, rate limits, and any provider-side routing policy.
## Setup
Create an API key in GMI Cloud, then run:
```bash
openclaw onboard --auth-choice gmi-api-key
```
Or set:
```bash
export GMI_API_KEY="<your-gmi-api-key>" # pragma: allowlist secret
```
## Defaults
- Provider: `gmi`
- Aliases: `gmi-cloud`, `gmicloud`
- Base URL: `https://api.gmi-serving.com/v1`
- Env var: `GMI_API_KEY`
- Default model: `gmi/google/gemini-3.1-flash-lite`
## When to choose GMI
- You want a hosted OpenAI-compatible endpoint rather than a local model server.
- You want to try several commercial and open-weight model families through one
provider account.
- You want a fallback provider with different upstream routing from OpenRouter,
DeepInfra, Together, or the direct vendor APIs.
- You need GMI-specific model ids, pricing, or account controls.
Choose the direct vendor provider instead when you need vendor-native features
that GMI does not expose through its OpenAI-compatible route. Choose a local
provider such as Ollama, LM Studio, vLLM, or SGLang when data locality or local
GPU control matters more than hosted convenience.
## Models
The bundled catalog seeds commonly available GMI Cloud route ids, including:
- `gmi/zai-org/GLM-5.1-FP8`
- `gmi/deepseek-ai/DeepSeek-V3.2`
- `gmi/moonshotai/Kimi-K2.5`
- `gmi/google/gemini-3.1-flash-lite`
- `gmi/anthropic/claude-sonnet-4.6`
- `gmi/openai/gpt-5.4`
The catalog is a seed, not a promise that every account can call every model at
all times. Use OpenClaw's model listing command to see what the configured
provider reports in your environment:
```bash
openclaw models list --provider gmi
```
## Troubleshooting
- `401` or `403`: check that `GMI_API_KEY` is set for the process running
OpenClaw, or re-run onboarding to store the key in the provider auth profile.
- Unknown model errors: confirm the model exists in your GMI account and use the
full `gmi/<route-id>` ref shown by `openclaw models list --provider gmi`.
- Intermittent provider errors: try a different GMI route or configure GMI as a
fallback rather than the only primary model provider.
## Related
- [Model providers](/concepts/model-providers)
- [All providers](/providers/index)

View File

@@ -41,7 +41,6 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi
- [fal](/providers/fal)
- [Fireworks](/providers/fireworks)
- [GitHub Copilot](/providers/github-copilot)
- [GMI Cloud](/providers/gmi)
- [Google (Gemini)](/providers/google)
- [Gradium](/providers/gradium)
- [Groq (LPU inference)](/providers/groq)
@@ -54,9 +53,7 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi
- [Mistral](/providers/mistral)
- [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot)
- [NVIDIA](/providers/nvidia)
- [NovitaAI](/providers/novita)
- [Ollama (cloud + local models)](/providers/ollama)
- [Ollama Cloud](/providers/ollama-cloud)
- [OpenAI (API + Codex)](/providers/openai)
- [OpenCode](/providers/opencode)
- [OpenCode Go](/providers/opencode-go)
@@ -64,7 +61,6 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi
- [Perplexity (web search)](/providers/perplexity-provider)
- [Qianfan](/providers/qianfan)
- [Qwen Cloud](/providers/qwen)
- [Qwen OAuth / Portal](/providers/qwen-oauth)
- [Runway](/providers/runway)
- [SenseAudio](/providers/senseaudio)
- [SGLang (local models)](/providers/sglang)

View File

@@ -1,92 +0,0 @@
---
summary: "Use NovitaAI's OpenAI-compatible API with OpenClaw"
read_when:
- You want to run OpenClaw with NovitaAI models
- You need the Novita provider id, key, or endpoint
title: "NovitaAI"
---
NovitaAI is a hosted AI infrastructure provider with an OpenAI-compatible model
API. In OpenClaw it is a bundled model provider, so the provider id is
`novita`, credentials go through the normal model auth flow, and model refs look
like `novita/deepseek/deepseek-v3-0324`.
Use Novita when you want hosted access to open-weight and third-party model
routes without running your own inference server. The bundled catalog focuses on
chat models that are practical for agent turns, including DeepSeek, Moonshot,
MiniMax, GLM, and Qwen routes exposed by Novita.
This provider uses Novita's OpenAI-compatible endpoint. OpenClaw handles
provider registration, auth, aliases, model ref normalization, and base URL
selection; Novita controls live model availability, account permissions,
pricing, and rate limits.
## Setup
Create an API key at [novita.ai/settings/key-management](https://novita.ai/settings/key-management), then run:
```bash
openclaw onboard --auth-choice novita-api-key
```
Or set:
```bash
export NOVITA_API_KEY="<your-novita-api-key>" # pragma: allowlist secret
```
## Defaults
- Provider: `novita`
- Aliases: `novita-ai`, `novitaai`
- Base URL: `https://api.novita.ai/openai/v1`
- Env var: `NOVITA_API_KEY`
- Default model: `novita/deepseek/deepseek-v3-0324`
## When to choose Novita
- You want hosted open-weight model access with an OpenAI-compatible API.
- You want DeepSeek, Kimi, MiniMax, GLM, or Qwen-family routes through a single
provider account.
- You want another hosted fallback path beside OpenRouter, GMI, DeepInfra, or
direct vendor APIs.
- You prefer provider-side model hosting over maintaining vLLM, SGLang, LM
Studio, or Ollama infrastructure.
Choose a direct vendor provider when you need vendor-native request parameters
or support contracts. Choose a local provider when the model must run on your
own hardware or behind your own network boundary.
## Models
The bundled catalog seeds commonly available NovitaAI route ids, including:
- `novita/moonshotai/kimi-k2.5`
- `novita/minimax/minimax-m2.7`
- `novita/zai-org/glm-5`
- `novita/deepseek/deepseek-v3-0324`
- `novita/deepseek/deepseek-r1-0528`
- `novita/qwen/qwen3-235b-a22b-fp8`
The catalog is a starting point for OpenClaw model selection. Your account,
region, or Novita's current catalog may add, remove, or restrict routes. Check
the provider from the CLI before setting a long-lived default:
```bash
openclaw models list --provider novita
```
## Troubleshooting
- `401` or `403`: verify the key in Novita's key management page and re-run
`openclaw onboard --auth-choice novita-api-key` if the stored profile is
stale.
- Unknown model errors: use the exact `novita/<route-id>` returned by
`openclaw models list --provider novita`.
- Slow or failed routes: try another Novita model route or set Novita as a
fallback provider for workloads that can tolerate provider-specific variance.
## Related
- [Model providers](/concepts/model-providers)
- [All providers](/providers/index)

View File

@@ -1,115 +0,0 @@
---
summary: "Use Ollama Cloud directly with OpenClaw"
read_when:
- You want to use hosted Ollama models without a local Ollama server
- You need the ollama-cloud provider id, key, or endpoint
title: "Ollama Cloud"
---
Ollama Cloud is Ollama's hosted model API. It lets OpenClaw call Ollama-hosted
models directly, without installing a local Ollama server or signing a local
Ollama app into cloud mode. Use provider id `ollama-cloud` and model refs like
`ollama-cloud/kimi-k2.6`.
This page is for direct cloud-only routing. The provider uses Ollama's native
`/api/chat` style, not the OpenAI-compatible `/v1` route. OpenClaw registers it
as a separate provider id so cloud-only credentials, live catalog discovery, and
model selection do not get mixed with a local `ollama` host.
Use this page when you want cloud-only routing. For local Ollama, hybrid
cloud-plus-local routing, embeddings, and custom host details, see
[Ollama](/providers/ollama).
## Setup
Create an Ollama Cloud API key at [ollama.com/settings/keys](https://ollama.com/settings/keys), then run:
```bash
openclaw onboard --auth-choice ollama-cloud
```
Or set:
```bash
export OLLAMA_API_KEY="<your-ollama-cloud-api-key>" # pragma: allowlist secret
```
## Defaults
- Provider: `ollama-cloud`
- Base URL: `https://ollama.com`
- Env var: `OLLAMA_API_KEY`
- API style: Ollama native `/api/chat`
- Example model: `ollama-cloud/kimi-k2.6`
## When to choose Ollama Cloud
- You want hosted Ollama models without running `ollama serve` locally.
- You want the same native Ollama chat API shape OpenClaw uses for local
Ollama, but pointed at `https://ollama.com`.
- You want a simple cloud path for models that are already in Ollama's hosted
catalog.
- You do not need local model pulls, local GPU control, or LAN-only inference.
Use [Ollama](/providers/ollama) instead when you want local-only or
cloud-plus-local routing through a signed-in Ollama host. Use an
OpenAI-compatible provider instead when you need `/v1/chat/completions`
semantics or provider-specific OpenAI-style features.
## Models
OpenClaw discovers Ollama Cloud models from the live hosted catalog. Commonly
available hosted ids include:
- `ollama-cloud/gpt-oss:20b`
- `ollama-cloud/kimi-k2.6`
- `ollama-cloud/deepseek-v4-flash`
- `ollama-cloud/minimax-m2.7`
- `ollama-cloud/glm-5`
Use a model id from your current hosted catalog:
```bash
openclaw models list --provider ollama-cloud
openclaw models set ollama-cloud/kimi-k2.6
```
Model ids are cloud catalog ids, not local pull names. If a model name works in
a local Ollama host but is absent from the hosted catalog, use the `ollama`
provider with that local host instead.
## Live test
For Ollama Cloud API-key smoke tests, point the Ollama live test at the hosted
endpoint and choose a model from your current catalog:
```bash
export OLLAMA_API_KEY="<your-ollama-cloud-api-key>" # pragma: allowlist secret
OPENCLAW_LIVE_TEST=1 \
OPENCLAW_LIVE_OLLAMA=1 \
OPENCLAW_LIVE_OLLAMA_BASE_URL=https://ollama.com \
OPENCLAW_LIVE_OLLAMA_MODEL=kimi-k2.6 \
OPENCLAW_LIVE_OLLAMA_WEB_SEARCH=1 \
pnpm test:live -- extensions/ollama/ollama.live.test.ts
```
The cloud smoke runs text, native stream, and web search. It skips embeddings by
default for `https://ollama.com` because Ollama Cloud API keys may not authorize
`/api/embed`.
## Troubleshooting
- `Set OLLAMA_API_KEY` errors: provide a real cloud API key. The local
`ollama-local` marker is only for local or private Ollama hosts.
- Unknown model errors: run `openclaw models list --provider ollama-cloud` and
copy the hosted model id exactly.
- Tool-call or raw JSON issues on custom Ollama hosts: check whether you are
accidentally using an OpenAI-compatible `/v1` URL. Ollama routes should use
the native base URL with no `/v1` suffix.
## Related
- [Ollama](/providers/ollama)
- [Model providers](/concepts/model-providers)
- [All providers](/providers/index)

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