mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 23:46:48 +00:00
* refactor: extract agent core package Introduce packages/agent-core as the OpenClaw-owned home for reusable agent loop, harness, session, prompt, and runtime dependency contracts. * refactor: extract shared llm runtime Move provider model registries, stream wrappers, OAuth helpers, and LLM utilities into src/llm with plugin-sdk barrels instead of depending on the old embedded runtime layout. * refactor: remove pi runtime internals Rename remaining Pi-shaped agent surfaces to OpenClaw agent runtime names, delete obsolete Pi docs and package graph checks, and add the third-party notice for incorporated code. * refactor: tighten agent session runtime Make agent-core/runtime dependencies explicit, consolidate compaction and session transcript helpers, and move model/session helpers behind OpenClaw-owned contracts. * refactor: remove static model and pi auth paths Drop static model catalogs and Pi auth bridges, move model/provider facts to manifest-owned runtime contracts, and harden internal embedded-agent utilities. * refactor: remove legacy provider compat paths * docs: remove agent parity notes * fix: skip provider wildcard metadata parsing * refactor: share session extension sdk loading * refactor: inline acpx proxy error formatter * refactor: fold edit recovery into edit tool * fix: accept extension batch separator * test: align startup provider plugin expectations * fix: restore provider-scoped release discovery * test: align static asset packaging expectations * fix: run static provider catalogs during scoped discovery * fix: add provider entry catalogs for scoped live discovery * fix: load lightweight provider catalog entries * fix: refresh provider-scoped plugin metadata * fix: keep provider catalog entries on release live path * fix: keep static manifest models in release live checks * fix: harden release model discovery * fix: reduce OpenAI live cache probe reasoning * fix: disable OpenAI cache probe reasoning * ci: extend OpenAI gateway live timeout * fix: extend live gateway model budget * fix: stabilize release validation regressions * fix: honor provider aliases in model rows * fix: stabilize release validation lanes * fix: stabilize release memory qa * ci: stabilize release validation lanes * ci: prefer ipv4 for live docker node calls * fix: restore shared tool-call stream wrapper * ci: remove legacy pi test shard alias * fix: clean up embedded agent test drift * fix: stabilize runtime alias status * fix: clean up embedded agent ci drift * fix: restore release ci invariants * fix: clean up post-rebase runtime drift * fix: restore release ci checks * fix: restore release ci after rebase * fix: remove stale pi runtime path * test: align compaction runtime expectations * test: update plugin prerelease expectations * fix: handle claude live tool approvals * fix: stabilize release validation gates * fix: finish agent runtime import * test: finish post-rebase agent runtime mocks * fix: keep codex compaction native * fix: stabilize codex app-server hook tests * test: isolate codex diagnostic active run * test: remove codex diagnostic completion race # Conflicts: # extensions/codex/src/app-server/run-attempt.test.ts * ci: fix full release manifest performance run id * refactor: narrow llm plugin sdk boundary * chore: drop generated google boundary stamps * fix: repair rebase fallout * fix: clean up rebased runtime references * fix: decode codex jwt payloads as base64url * fix: preserve shipped pi runtime alias * fix: add scoped sdk virtual modules * fix: decode llm codex oauth jwt as base64url * fix: avoid stale vertex adc negative cache * fix: harden tool arg decoding and codeql path * fix: keep vertex adc negative checks live * refactor: consolidate codex jwt and edit helpers * fix: await codex oauth node runtime imports * fix: preserve sdk tool and notice contracts * fix: preserve shipped compat config boundaries * fix: align codex oauth callback host * fix: terminate agent-core loop streams on failure * fix: keep codex oauth callback alive during fallback * ci: include session tools in critical codeql scans * fix: keep Cloudflare Anthropic provider auth header * docs: redirect legacy pi runtime pages * fix: honor bundled web provider compat discovery * fix: protect session output spill files * fix: keep legacy agent dir env blocked * fix: contain auto-discovered skill symlinks * fix: harden agent core sdk proxy surfaces * fix: restore approval reaction sdk compat * fix: keep live docker runs bounded * fix: keep codex oauth redirect host aligned * fix: resolve post-rebase agent runtime drift * fix: redact anthropic oauth parse failures * fix: preserve responses strict tool shaping * fix: repair agent runtime rebase cleanup * docs: redirect retired parity pages * fix: bound auto-discovered resources to roots * fix: repair post-rebase agent test drift * fix: preserve bundled provider allowlist migration * fix: preserve manifest-owned provider aliases * fix: declare photon image dependency * fix: keep provider headers out of proxy body * fix: preserve shipped env aliases * fix: refresh control ui i18n generated state * fix: quote read fallback paths * fix: preview edits through configured backend * test: satisfy core test typecheck * fix: preserve ZAI usage auth fallback * test: repair codex diagnostic test * fix: repair agent runtime rebase drift * test: finish embedded runner import rename * fix: repair agent runtime rebase integrations * test: align compaction oauth fallback expectations * fix: allow sdk-auth session models * fix: update doctor tool schema import * fix: preserve bedrock plugin region * fix: stream harmony-like prose immediately * ci: include session runtime in codeql shards * fix: repair latest rebase integrations * fix: honor explicit codex websocket transport * fix: keep openai-compatible credentials provider-scoped * fix: refresh sdk api baseline after rebase * fix: route cli runtime aliases through openclaw harness * test: rename stale harness mock expectation * test: rename embedded agent overflow calls * test: clean embedded auth test wording * test: use openclaw stream types in deepinfra cache test * fix: refresh sdk api baseline on latest main * fix: honor bundled discovery compat allowlists * fix: refresh sdk api baseline after latest rebase * fix: remove stale rebase imports * test: rename stale model catalog mock * test: mock renamed doctor runtime modules * fix: map canonical kimi env auth * fix: use internal model registry in bench script * fix: migrate deepinfra provider catalog entry * fix: enforce builtin tool suppression * fix: route compaction auth and proxy payloads safely * refactor: prune unused llm registry leftovers * test: update codex hooks session import * test: fix model picker ci coverage * test: align model picker auth mock types
663 lines
17 KiB
JavaScript
663 lines
17 KiB
JavaScript
#!/usr/bin/env node
|
|
import { randomBytes } from "node:crypto";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const DEFAULT_LABEL = "state";
|
|
const DEFAULT_SCENARIO = "empty";
|
|
const SCENARIOS = new Set([
|
|
"empty",
|
|
"minimal",
|
|
"update-stable",
|
|
"upgrade-survivor",
|
|
"gateway-loopback",
|
|
"external-service",
|
|
]);
|
|
|
|
function usage() {
|
|
return `Usage:
|
|
node scripts/lib/openclaw-test-state.mjs -- create [--label <name>] [--scenario <name>] [--env-file <path>] [--json]
|
|
node scripts/lib/openclaw-test-state.mjs shell [--label <name>] [--scenario <name>]
|
|
node scripts/lib/openclaw-test-state.mjs shell-function
|
|
|
|
Scenarios: ${[...SCENARIOS].join(", ")}
|
|
`;
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const args = argv[0] === "--" ? argv.slice(1) : argv;
|
|
const [command, ...rest] = args;
|
|
if (!command || command === "--help" || command === "-h") {
|
|
return { command: "help", options: {} };
|
|
}
|
|
const options = {};
|
|
for (let index = 0; index < rest.length; index += 1) {
|
|
const arg = rest[index];
|
|
if (arg === "--json") {
|
|
options.json = true;
|
|
continue;
|
|
}
|
|
if (
|
|
arg === "--label" ||
|
|
arg === "--scenario" ||
|
|
arg === "--env-file" ||
|
|
arg === "--port" ||
|
|
arg === "--token"
|
|
) {
|
|
const value = rest[index + 1];
|
|
if (!value) {
|
|
throw new Error(`missing value for ${arg}`);
|
|
}
|
|
index += 1;
|
|
options[arg.slice(2)] = value;
|
|
continue;
|
|
}
|
|
throw new Error(`unknown argument: ${arg}`);
|
|
}
|
|
return { command, options };
|
|
}
|
|
|
|
function normalizeLabel(value) {
|
|
return (
|
|
String(value || DEFAULT_LABEL)
|
|
.replace(/[^A-Za-z0-9_.-]+/gu, "-")
|
|
.replace(/^-+|-+$/gu, "") || DEFAULT_LABEL
|
|
);
|
|
}
|
|
|
|
function requireScenario(value) {
|
|
const scenario = value || DEFAULT_SCENARIO;
|
|
if (!SCENARIOS.has(scenario)) {
|
|
throw new Error(`unknown scenario: ${scenario}`);
|
|
}
|
|
return scenario;
|
|
}
|
|
|
|
function scenarioConfig(scenario, options = {}) {
|
|
if (scenario === "minimal" || scenario === "external-service") {
|
|
return {};
|
|
}
|
|
if (scenario === "update-stable") {
|
|
return {
|
|
update: {
|
|
channel: "stable",
|
|
},
|
|
plugins: {},
|
|
};
|
|
}
|
|
if (scenario === "upgrade-survivor") {
|
|
return {
|
|
update: {
|
|
channel: "stable",
|
|
},
|
|
gateway: {
|
|
mode: "local",
|
|
port: Number(options.port || 18789),
|
|
bind: "loopback",
|
|
auth: {
|
|
mode: "token",
|
|
token: { source: "env", provider: "default", id: "GATEWAY_AUTH_TOKEN_REF" },
|
|
},
|
|
controlUi: {
|
|
enabled: false,
|
|
},
|
|
},
|
|
models: {
|
|
providers: {
|
|
openai: {
|
|
api: "openai-responses",
|
|
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
|
|
baseUrl: "https://api.openai.com/v1",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
agents: {
|
|
defaults: {
|
|
model: {
|
|
primary: "openai/gpt-5.5",
|
|
},
|
|
contextTokens: 64000,
|
|
skills: ["memory"],
|
|
},
|
|
list: [
|
|
{
|
|
id: "main",
|
|
default: true,
|
|
name: "Main",
|
|
workspace: "~/workspace",
|
|
model: {
|
|
primary: "openai/gpt-5.5",
|
|
},
|
|
thinkingDefault: "low",
|
|
skills: ["memory"],
|
|
contextTokens: 64000,
|
|
},
|
|
{
|
|
id: "ops",
|
|
name: "Ops",
|
|
workspace: "~/workspace/ops",
|
|
model: {
|
|
primary: "openai/gpt-5.5",
|
|
},
|
|
fastModeDefault: true,
|
|
},
|
|
],
|
|
},
|
|
skills: {
|
|
allowBundled: ["memory", "openclaw-testing"],
|
|
limits: {
|
|
maxSkillsInPrompt: 8,
|
|
maxSkillsPromptChars: 30000,
|
|
},
|
|
},
|
|
plugins: {
|
|
enabled: true,
|
|
allow: ["discord", "telegram", "whatsapp", "memory"],
|
|
entries: {
|
|
discord: { enabled: true },
|
|
telegram: { enabled: true },
|
|
whatsapp: { enabled: true },
|
|
},
|
|
},
|
|
channels: {
|
|
discord: {
|
|
enabled: true,
|
|
token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
|
|
dm: {
|
|
policy: "allowlist",
|
|
allowFrom: ["111111111111111111"],
|
|
},
|
|
groupPolicy: "allowlist",
|
|
guilds: {
|
|
"222222222222222222": {
|
|
slug: "survivor-guild",
|
|
channels: {
|
|
"333333333333333333": {
|
|
enabled: true,
|
|
requireMention: true,
|
|
tools: {
|
|
allow: ["message_send"],
|
|
deny: ["exec"],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
threadBindings: {
|
|
enabled: true,
|
|
idleHours: 72,
|
|
},
|
|
},
|
|
telegram: {
|
|
enabled: true,
|
|
botToken: { source: "env", provider: "default", id: "TELEGRAM_BOT_TOKEN" },
|
|
dmPolicy: "allowlist",
|
|
allowFrom: ["123456789"],
|
|
groups: {
|
|
"-1001234567890": {
|
|
enabled: true,
|
|
requireMention: true,
|
|
},
|
|
},
|
|
},
|
|
whatsapp: {
|
|
enabled: true,
|
|
dmPolicy: "allowlist",
|
|
allowFrom: ["+15555550123"],
|
|
groups: {
|
|
"120363000000000000@g.us": {
|
|
systemPrompt: "Use the existing WhatsApp group prompt.",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
if (scenario === "gateway-loopback") {
|
|
return {
|
|
gateway: {
|
|
port: Number(options.port || 18789),
|
|
auth: {
|
|
mode: "token",
|
|
token: options.token || "openclaw-test-token",
|
|
},
|
|
controlUi: {
|
|
enabled: false,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function scenarioEnv(scenario) {
|
|
if (scenario === "external-service") {
|
|
return {
|
|
OPENCLAW_SERVICE_REPAIR_POLICY: "external",
|
|
};
|
|
}
|
|
return {};
|
|
}
|
|
|
|
function shellQuote(value) {
|
|
return `'${String(value).replace(/'/gu, `'\\''`)}'`;
|
|
}
|
|
|
|
function renderExports(env) {
|
|
return Object.entries(env)
|
|
.map(([key, value]) => `export ${key}=${shellQuote(value)}`)
|
|
.join("\n");
|
|
}
|
|
|
|
function generateAuthProfileSecretKey() {
|
|
return randomBytes(32).toString("hex");
|
|
}
|
|
|
|
function renderAuthProfileSecretKeyExport() {
|
|
return [
|
|
'OPENCLAW_AUTH_PROFILE_SECRET_KEY_FILE="$OPENCLAW_TEST_STATE_HOME/.openclaw-test-auth-profile-secret-key"',
|
|
'if [ -s "$OPENCLAW_AUTH_PROFILE_SECRET_KEY_FILE" ]; then',
|
|
' OPENCLAW_AUTH_PROFILE_SECRET_KEY="$(cat "$OPENCLAW_AUTH_PROFILE_SECRET_KEY_FILE")"',
|
|
"else",
|
|
' OPENCLAW_AUTH_PROFILE_SECRET_KEY="$(od -An -N 32 -tx1 /dev/urandom | tr -d " \\n")"',
|
|
' ( umask 077; printf "%s\\n" "$OPENCLAW_AUTH_PROFILE_SECRET_KEY" > "$OPENCLAW_AUTH_PROFILE_SECRET_KEY_FILE" )',
|
|
"fi",
|
|
'if [ -z "$OPENCLAW_AUTH_PROFILE_SECRET_KEY" ]; then',
|
|
' echo "failed to generate OPENCLAW_AUTH_PROFILE_SECRET_KEY" >&2',
|
|
" return 1 2>/dev/null || exit 1",
|
|
"fi",
|
|
"export OPENCLAW_AUTH_PROFILE_SECRET_KEY",
|
|
];
|
|
}
|
|
|
|
function renderConfigWrite(configPathExpression, config) {
|
|
if (config === undefined) {
|
|
return "";
|
|
}
|
|
const json = JSON.stringify(config, null, 2);
|
|
return [
|
|
`cat > ${configPathExpression} <<'OPENCLAW_TEST_STATE_JSON'`,
|
|
json,
|
|
"OPENCLAW_TEST_STATE_JSON",
|
|
].join("\n");
|
|
}
|
|
|
|
function buildCreatePlan(options = {}) {
|
|
const label = normalizeLabel(options.label);
|
|
const scenario = requireScenario(options.scenario);
|
|
if (!options.root) {
|
|
throw new Error("buildCreatePlan requires root");
|
|
}
|
|
const root = options.root;
|
|
const home = path.join(root, "home");
|
|
const stateDir = path.join(home, ".openclaw");
|
|
const configPath = path.join(stateDir, "openclaw.json");
|
|
const workspaceDir = path.join(home, "workspace");
|
|
const config = scenarioConfig(scenario, options);
|
|
const env = {
|
|
HOME: home,
|
|
USERPROFILE: home,
|
|
OPENCLAW_HOME: home,
|
|
OPENCLAW_STATE_DIR: stateDir,
|
|
OPENCLAW_CONFIG_PATH: configPath,
|
|
OPENCLAW_AUTH_PROFILE_SECRET_KEY: generateAuthProfileSecretKey(),
|
|
...scenarioEnv(scenario),
|
|
};
|
|
return {
|
|
label,
|
|
scenario,
|
|
root,
|
|
home,
|
|
stateDir,
|
|
configPath,
|
|
workspaceDir,
|
|
env,
|
|
hasConfig: config !== undefined,
|
|
config,
|
|
};
|
|
}
|
|
|
|
export async function createState(options = {}) {
|
|
const label = normalizeLabel(options.label);
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), `openclaw-${label}-`));
|
|
const plan = buildCreatePlan({ ...options, root });
|
|
await fs.mkdir(plan.stateDir, { recursive: true });
|
|
await fs.mkdir(plan.workspaceDir, { recursive: true });
|
|
if (plan.config !== undefined) {
|
|
await fs.writeFile(plan.configPath, `${JSON.stringify(plan.config, null, 2)}\n`, "utf8");
|
|
}
|
|
return plan;
|
|
}
|
|
|
|
export function renderEnvFile(plan) {
|
|
return `${renderExports(plan.env)}\n`;
|
|
}
|
|
|
|
export function renderShellSnippet(options = {}) {
|
|
const label = normalizeLabel(options.label);
|
|
const scenario = requireScenario(options.scenario);
|
|
const config = scenarioConfig(scenario, options);
|
|
const env = scenarioEnv(scenario);
|
|
const homeTemplate = `openclaw-${label}-${scenario}-home.XXXXXX`;
|
|
const lines = [
|
|
'OPENCLAW_TEST_STATE_TMP_ROOT="${OPENCLAW_TEST_STATE_TMPDIR:-${TMPDIR:-/tmp}}"',
|
|
'OPENCLAW_TEST_STATE_TMP_ROOT="${OPENCLAW_TEST_STATE_TMP_ROOT%/}"',
|
|
'[ -n "$OPENCLAW_TEST_STATE_TMP_ROOT" ] || OPENCLAW_TEST_STATE_TMP_ROOT="/tmp"',
|
|
"export OPENCLAW_TEST_STATE_TMP_ROOT",
|
|
'mkdir -p "$OPENCLAW_TEST_STATE_TMP_ROOT"',
|
|
`OPENCLAW_TEST_STATE_HOME="$(mktemp -d "$OPENCLAW_TEST_STATE_TMP_ROOT/${homeTemplate}")"`,
|
|
'export HOME="$OPENCLAW_TEST_STATE_HOME"',
|
|
'export USERPROFILE="$OPENCLAW_TEST_STATE_HOME"',
|
|
'export OPENCLAW_HOME="$OPENCLAW_TEST_STATE_HOME"',
|
|
'export OPENCLAW_STATE_DIR="$OPENCLAW_TEST_STATE_HOME/.openclaw"',
|
|
'export OPENCLAW_CONFIG_PATH="$OPENCLAW_STATE_DIR/openclaw.json"',
|
|
...renderAuthProfileSecretKeyExport(),
|
|
'export OPENCLAW_TEST_WORKSPACE_DIR="$OPENCLAW_TEST_STATE_HOME/workspace"',
|
|
'mkdir -p "$OPENCLAW_STATE_DIR" "$OPENCLAW_TEST_WORKSPACE_DIR"',
|
|
];
|
|
for (const [key, value] of Object.entries(env)) {
|
|
lines.push(`export ${key}=${shellQuote(value)}`);
|
|
}
|
|
const configWrite = renderConfigWrite('"$OPENCLAW_CONFIG_PATH"', config);
|
|
if (configWrite) {
|
|
lines.push(configWrite);
|
|
}
|
|
return `${lines.join("\n")}\n`;
|
|
}
|
|
|
|
export function renderShellFunction() {
|
|
return `openclaw_test_state_create() {
|
|
local raw_label="\${1:-state}"
|
|
local label="$raw_label"
|
|
local scenario="\${2:-empty}"
|
|
case "$scenario" in
|
|
empty|minimal|update-stable|upgrade-survivor|gateway-loopback|external-service) ;;
|
|
*)
|
|
echo "unknown OpenClaw test-state scenario: $scenario" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
case "$raw_label" in
|
|
/*)
|
|
OPENCLAW_TEST_STATE_HOME="$raw_label"
|
|
mkdir -p "$OPENCLAW_TEST_STATE_HOME"
|
|
;;
|
|
*)
|
|
label="$(printf "%s" "$label" | tr -cs "A-Za-z0-9_.-" "-" | sed -e "s/^-*//" -e "s/-*$//")"
|
|
[ -n "$label" ] || label="state"
|
|
local tmp_root="\${OPENCLAW_TEST_STATE_TMPDIR:-\${TMPDIR:-/tmp}}"
|
|
tmp_root="\${tmp_root%/}"
|
|
[ -n "$tmp_root" ] || tmp_root="/tmp"
|
|
mkdir -p "$tmp_root"
|
|
OPENCLAW_TEST_STATE_HOME="$(mktemp -d "$tmp_root/openclaw-$label-$scenario-home.XXXXXX")"
|
|
;;
|
|
esac
|
|
export HOME="$OPENCLAW_TEST_STATE_HOME"
|
|
export USERPROFILE="$OPENCLAW_TEST_STATE_HOME"
|
|
export OPENCLAW_HOME="$OPENCLAW_TEST_STATE_HOME"
|
|
export OPENCLAW_STATE_DIR="$OPENCLAW_TEST_STATE_HOME/.openclaw"
|
|
export OPENCLAW_CONFIG_PATH="$OPENCLAW_STATE_DIR/openclaw.json"
|
|
${renderAuthProfileSecretKeyExport().join("\n ")}
|
|
export OPENCLAW_TEST_WORKSPACE_DIR="$OPENCLAW_TEST_STATE_HOME/workspace"
|
|
unset OPENCLAW_AGENT_DIR
|
|
unset OPENCLAW_SERVICE_REPAIR_POLICY
|
|
mkdir -p "$OPENCLAW_STATE_DIR" "$OPENCLAW_TEST_WORKSPACE_DIR"
|
|
case "$scenario" in
|
|
minimal)
|
|
cat > "$OPENCLAW_CONFIG_PATH" <<'OPENCLAW_TEST_STATE_JSON'
|
|
{}
|
|
OPENCLAW_TEST_STATE_JSON
|
|
;;
|
|
update-stable)
|
|
cat > "$OPENCLAW_CONFIG_PATH" <<'OPENCLAW_TEST_STATE_JSON'
|
|
{
|
|
"update": {
|
|
"channel": "stable"
|
|
},
|
|
"plugins": {}
|
|
}
|
|
OPENCLAW_TEST_STATE_JSON
|
|
;;
|
|
upgrade-survivor)
|
|
cat > "$OPENCLAW_CONFIG_PATH" <<'OPENCLAW_TEST_STATE_JSON'
|
|
{
|
|
"update": {
|
|
"channel": "stable"
|
|
},
|
|
"gateway": {
|
|
"mode": "local",
|
|
"port": 18789,
|
|
"bind": "loopback",
|
|
"auth": {
|
|
"mode": "token",
|
|
"token": {
|
|
"source": "env",
|
|
"provider": "default",
|
|
"id": "GATEWAY_AUTH_TOKEN_REF"
|
|
}
|
|
},
|
|
"controlUi": {
|
|
"enabled": false
|
|
}
|
|
},
|
|
"models": {
|
|
"providers": {
|
|
"openai": {
|
|
"api": "openai-responses",
|
|
"apiKey": {
|
|
"source": "env",
|
|
"provider": "default",
|
|
"id": "OPENAI_API_KEY"
|
|
},
|
|
"baseUrl": "https://api.openai.com/v1",
|
|
"models": []
|
|
}
|
|
}
|
|
},
|
|
"agents": {
|
|
"defaults": {
|
|
"model": {
|
|
"primary": "openai/gpt-5.5"
|
|
},
|
|
"contextTokens": 64000,
|
|
"skills": [
|
|
"memory"
|
|
]
|
|
},
|
|
"list": [
|
|
{
|
|
"id": "main",
|
|
"default": true,
|
|
"name": "Main",
|
|
"workspace": "~/workspace",
|
|
"model": {
|
|
"primary": "openai/gpt-5.5"
|
|
},
|
|
"thinkingDefault": "low",
|
|
"skills": [
|
|
"memory"
|
|
],
|
|
"contextTokens": 64000
|
|
},
|
|
{
|
|
"id": "ops",
|
|
"name": "Ops",
|
|
"workspace": "~/workspace/ops",
|
|
"model": {
|
|
"primary": "openai/gpt-5.5"
|
|
},
|
|
"fastModeDefault": true
|
|
}
|
|
]
|
|
},
|
|
"skills": {
|
|
"allowBundled": [
|
|
"memory",
|
|
"openclaw-testing"
|
|
],
|
|
"limits": {
|
|
"maxSkillsInPrompt": 8,
|
|
"maxSkillsPromptChars": 30000
|
|
}
|
|
},
|
|
"plugins": {
|
|
"enabled": true,
|
|
"allow": [
|
|
"discord",
|
|
"telegram",
|
|
"whatsapp",
|
|
"memory"
|
|
],
|
|
"entries": {
|
|
"discord": {
|
|
"enabled": true
|
|
},
|
|
"telegram": {
|
|
"enabled": true
|
|
},
|
|
"whatsapp": {
|
|
"enabled": true
|
|
}
|
|
}
|
|
},
|
|
"channels": {
|
|
"discord": {
|
|
"enabled": true,
|
|
"token": {
|
|
"source": "env",
|
|
"provider": "default",
|
|
"id": "DISCORD_BOT_TOKEN"
|
|
},
|
|
"dm": {
|
|
"policy": "allowlist",
|
|
"allowFrom": [
|
|
"111111111111111111"
|
|
]
|
|
},
|
|
"groupPolicy": "allowlist",
|
|
"guilds": {
|
|
"222222222222222222": {
|
|
"slug": "survivor-guild",
|
|
"channels": {
|
|
"333333333333333333": {
|
|
"enabled": true,
|
|
"requireMention": true,
|
|
"tools": {
|
|
"allow": [
|
|
"message_send"
|
|
],
|
|
"deny": [
|
|
"exec"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"threadBindings": {
|
|
"enabled": true,
|
|
"idleHours": 72
|
|
}
|
|
},
|
|
"telegram": {
|
|
"enabled": true,
|
|
"botToken": {
|
|
"source": "env",
|
|
"provider": "default",
|
|
"id": "TELEGRAM_BOT_TOKEN"
|
|
},
|
|
"dmPolicy": "allowlist",
|
|
"allowFrom": [
|
|
"123456789"
|
|
],
|
|
"groups": {
|
|
"-1001234567890": {
|
|
"enabled": true,
|
|
"requireMention": true
|
|
}
|
|
}
|
|
},
|
|
"whatsapp": {
|
|
"enabled": true,
|
|
"dmPolicy": "allowlist",
|
|
"allowFrom": [
|
|
"+15555550123"
|
|
],
|
|
"groups": {
|
|
"120363000000000000@g.us": {
|
|
"systemPrompt": "Use the existing WhatsApp group prompt."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
OPENCLAW_TEST_STATE_JSON
|
|
;;
|
|
gateway-loopback)
|
|
cat > "$OPENCLAW_CONFIG_PATH" <<'OPENCLAW_TEST_STATE_JSON'
|
|
{
|
|
"gateway": {
|
|
"port": 18789,
|
|
"auth": {
|
|
"mode": "token",
|
|
"token": "openclaw-test-token"
|
|
},
|
|
"controlUi": {
|
|
"enabled": false
|
|
}
|
|
}
|
|
}
|
|
OPENCLAW_TEST_STATE_JSON
|
|
;;
|
|
external-service)
|
|
export OPENCLAW_SERVICE_REPAIR_POLICY="external"
|
|
cat > "$OPENCLAW_CONFIG_PATH" <<'OPENCLAW_TEST_STATE_JSON'
|
|
{}
|
|
OPENCLAW_TEST_STATE_JSON
|
|
;;
|
|
esac
|
|
}
|
|
`;
|
|
}
|
|
|
|
async function main(argv = process.argv.slice(2)) {
|
|
const { command, options } = parseArgs(argv);
|
|
if (command === "help") {
|
|
process.stdout.write(usage());
|
|
return;
|
|
}
|
|
if (command === "shell") {
|
|
process.stdout.write(renderShellSnippet(options));
|
|
return;
|
|
}
|
|
if (command === "shell-function") {
|
|
process.stdout.write(renderShellFunction());
|
|
return;
|
|
}
|
|
if (command === "create") {
|
|
const plan = await createState(options);
|
|
if (options["env-file"]) {
|
|
await fs.writeFile(options["env-file"], renderEnvFile(plan), "utf8");
|
|
}
|
|
if (options.json) {
|
|
process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
|
|
}
|
|
return;
|
|
}
|
|
throw new Error(`unknown command: ${command}`);
|
|
}
|
|
|
|
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
|
|
|
|
if (isMain) {
|
|
main().catch((error) => {
|
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
process.stderr.write(usage());
|
|
process.exitCode = 1;
|
|
});
|
|
}
|