@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
923 lines (910 loc) • 28.7 kB
JavaScript
import { k as theme, p as defaultRuntime } from "./entry.js";
import "./auth-profiles-CYBuGiBb.js";
import "./utils-DX85MiPR.js";
import "./exec-B8JKbXKW.js";
import "./agent-scope-C9VjJXEK.js";
import "./github-copilot-token-SLWintYd.js";
import { i as loadConfig, j as VERSION } from "./config-CKLedg5Y.js";
import "./manifest-registry-C69Z-I4v.js";
import { t as ensureOpenClawCliOnPath } from "./path-env-h3xp5PqO.js";
import "./tailscale-9MusRvOi.js";
import { i as resolveGatewayAuth } from "./auth-DksjO6WG.js";
import { t as GatewayClient } from "./client-CxbkcEZ7.js";
import { t as buildGatewayConnectionDetails } from "./call-90HgQQ8o.js";
import { h as GATEWAY_CLIENT_NAMES, m as GATEWAY_CLIENT_MODES } from "./message-channel-BlgPSDAh.js";
import { t as isMainModule } from "./is-main-qJ675wPV.js";
import { t as formatDocsLink } from "./links-D0uzJbi6.js";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { randomUUID } from "node:crypto";
import { AgentSideConnection, ClientSideConnection, PROTOCOL_VERSION, ndJsonStream } from "@agentclientprotocol/sdk";
import * as readline$1 from "node:readline";
import { Readable, Writable } from "node:stream";
//#region src/acp/client.ts
function toArgs(value) {
if (!value) return [];
return Array.isArray(value) ? value : [value];
}
function buildServerArgs(opts) {
const args = ["acp", ...toArgs(opts.serverArgs)];
if (opts.serverVerbose && !args.includes("--verbose") && !args.includes("-v")) args.push("--verbose");
return args;
}
function printSessionUpdate(notification) {
const update = notification.update;
if (!("sessionUpdate" in update)) return;
switch (update.sessionUpdate) {
case "agent_message_chunk":
if (update.content?.type === "text") process.stdout.write(update.content.text);
return;
case "tool_call":
console.log(`\n[tool] ${update.title} (${update.status})`);
return;
case "tool_call_update":
if (update.status) console.log(`[tool update] ${update.toolCallId}: ${update.status}`);
return;
case "available_commands_update": {
const names = update.availableCommands?.map((cmd) => `/${cmd.name}`).join(" ");
if (names) console.log(`\n[commands] ${names}`);
return;
}
default: return;
}
}
async function createAcpClient(opts = {}) {
const cwd = opts.cwd ?? process.cwd();
const log = Boolean(opts.verbose) ? (msg) => console.error(`[acp-client] ${msg}`) : () => {};
ensureOpenClawCliOnPath({ cwd });
const serverCommand = opts.serverCommand ?? "openclaw";
const serverArgs = buildServerArgs(opts);
log(`spawning: ${serverCommand} ${serverArgs.join(" ")}`);
const agent = spawn(serverCommand, serverArgs, {
stdio: [
"pipe",
"pipe",
"inherit"
],
cwd
});
if (!agent.stdin || !agent.stdout) throw new Error("Failed to create ACP stdio pipes");
const client = new ClientSideConnection(() => ({
sessionUpdate: async (params) => {
printSessionUpdate(params);
},
requestPermission: async (params) => {
console.log("\n[permission requested]", params.toolCall?.title ?? "tool");
const options = params.options ?? [];
const allowOnce = options.find((option) => option.kind === "allow_once");
const fallback = options[0];
return { outcome: {
outcome: "selected",
optionId: allowOnce?.optionId ?? fallback?.optionId ?? "allow"
} };
}
}), ndJsonStream(Writable.toWeb(agent.stdin), Readable.toWeb(agent.stdout)));
log("initializing");
await client.initialize({
protocolVersion: PROTOCOL_VERSION,
clientCapabilities: {
fs: {
readTextFile: true,
writeTextFile: true
},
terminal: true
},
clientInfo: {
name: "openclaw-acp-client",
version: "1.0.0"
}
});
log("creating session");
return {
client,
agent,
sessionId: (await client.newSession({
cwd,
mcpServers: []
})).sessionId
};
}
async function runAcpClientInteractive(opts = {}) {
const { client, agent, sessionId } = await createAcpClient(opts);
const rl = readline$1.createInterface({
input: process.stdin,
output: process.stdout
});
console.log("OpenClaw ACP client");
console.log(`Session: ${sessionId}`);
console.log("Type a prompt, or \"exit\" to quit.\n");
const prompt = () => {
rl.question("> ", async (input) => {
const text = input.trim();
if (!text) {
prompt();
return;
}
if (text === "exit" || text === "quit") {
agent.kill();
rl.close();
process.exit(0);
}
try {
const response = await client.prompt({
sessionId,
prompt: [{
type: "text",
text
}]
});
console.log(`\n[${response.stopReason}]\n`);
} catch (err) {
console.error(`\n[error] ${String(err)}\n`);
}
prompt();
});
};
prompt();
agent.on("exit", (code) => {
console.log(`\nAgent exited with code ${code ?? 0}`);
rl.close();
process.exit(code ?? 0);
});
}
//#endregion
//#region src/acp/commands.ts
function getAvailableCommands() {
return [
{
name: "help",
description: "Show help and common commands."
},
{
name: "commands",
description: "List available commands."
},
{
name: "status",
description: "Show current status."
},
{
name: "context",
description: "Explain context usage (list|detail|json).",
input: { hint: "list | detail | json" }
},
{
name: "whoami",
description: "Show sender id (alias: /id)."
},
{
name: "id",
description: "Alias for /whoami."
},
{
name: "subagents",
description: "List or manage sub-agents."
},
{
name: "config",
description: "Read or write config (owner-only)."
},
{
name: "debug",
description: "Set runtime-only overrides (owner-only)."
},
{
name: "usage",
description: "Toggle usage footer (off|tokens|full)."
},
{
name: "stop",
description: "Stop the current run."
},
{
name: "restart",
description: "Restart the gateway (if enabled)."
},
{
name: "dock-telegram",
description: "Route replies to Telegram."
},
{
name: "dock-discord",
description: "Route replies to Discord."
},
{
name: "dock-slack",
description: "Route replies to Slack."
},
{
name: "activation",
description: "Set group activation (mention|always)."
},
{
name: "send",
description: "Set send mode (on|off|inherit)."
},
{
name: "reset",
description: "Reset the session (/new)."
},
{
name: "new",
description: "Reset the session (/reset)."
},
{
name: "think",
description: "Set thinking level (off|minimal|low|medium|high|xhigh)."
},
{
name: "verbose",
description: "Set verbose mode (on|full|off)."
},
{
name: "reasoning",
description: "Toggle reasoning output (on|off|stream)."
},
{
name: "elevated",
description: "Toggle elevated mode (on|off)."
},
{
name: "model",
description: "Select a model (list|status|<name>)."
},
{
name: "queue",
description: "Adjust queue mode and options."
},
{
name: "bash",
description: "Run a host command (if enabled)."
},
{
name: "compact",
description: "Compact the session history."
}
];
}
//#endregion
//#region src/acp/event-mapper.ts
function extractTextFromPrompt(prompt) {
const parts = [];
for (const block of prompt) {
if (block.type === "text") {
parts.push(block.text);
continue;
}
if (block.type === "resource") {
const resource = block.resource;
if (resource?.text) parts.push(resource.text);
continue;
}
if (block.type === "resource_link") {
const title = block.title ? ` (${block.title})` : "";
const uri = block.uri ?? "";
const line = uri ? `[Resource link${title}] ${uri}` : `[Resource link${title}]`;
parts.push(line);
}
}
return parts.join("\n");
}
function extractAttachmentsFromPrompt(prompt) {
const attachments = [];
for (const block of prompt) {
if (block.type !== "image") continue;
const image = block;
if (!image.data || !image.mimeType) continue;
attachments.push({
type: "image",
mimeType: image.mimeType,
content: image.data
});
}
return attachments;
}
function formatToolTitle(name, args) {
const base = name ?? "tool";
if (!args || Object.keys(args).length === 0) return base;
return `${base}: ${Object.entries(args).map(([key, value]) => {
const raw = typeof value === "string" ? value : JSON.stringify(value);
return `${key}: ${raw.length > 100 ? `${raw.slice(0, 100)}...` : raw}`;
}).join(", ")}`;
}
function inferToolKind(name) {
if (!name) return "other";
const normalized = name.toLowerCase();
if (normalized.includes("read")) return "read";
if (normalized.includes("write") || normalized.includes("edit")) return "edit";
if (normalized.includes("delete") || normalized.includes("remove")) return "delete";
if (normalized.includes("move") || normalized.includes("rename")) return "move";
if (normalized.includes("search") || normalized.includes("find")) return "search";
if (normalized.includes("exec") || normalized.includes("run") || normalized.includes("bash")) return "execute";
if (normalized.includes("fetch") || normalized.includes("http")) return "fetch";
return "other";
}
//#endregion
//#region src/acp/meta.ts
function readString(meta, keys) {
if (!meta) return;
for (const key of keys) {
const value = meta[key];
if (typeof value === "string" && value.trim()) return value.trim();
}
}
function readBool(meta, keys) {
if (!meta) return;
for (const key of keys) {
const value = meta[key];
if (typeof value === "boolean") return value;
}
}
function readNumber(meta, keys) {
if (!meta) return;
for (const key of keys) {
const value = meta[key];
if (typeof value === "number" && Number.isFinite(value)) return value;
}
}
//#endregion
//#region src/acp/session-mapper.ts
function parseSessionMeta(meta) {
if (!meta || typeof meta !== "object") return {};
const record = meta;
return {
sessionKey: readString(record, [
"sessionKey",
"session",
"key"
]),
sessionLabel: readString(record, ["sessionLabel", "label"]),
resetSession: readBool(record, ["resetSession", "reset"]),
requireExisting: readBool(record, ["requireExistingSession", "requireExisting"]),
prefixCwd: readBool(record, ["prefixCwd"])
};
}
async function resolveSessionKey(params) {
const requestedLabel = params.meta.sessionLabel ?? params.opts.defaultSessionLabel;
const requestedKey = params.meta.sessionKey ?? params.opts.defaultSessionKey;
const requireExisting = params.meta.requireExisting ?? params.opts.requireExistingSession ?? false;
if (params.meta.sessionLabel) {
const resolved = await params.gateway.request("sessions.resolve", { label: params.meta.sessionLabel });
if (!resolved?.key) throw new Error(`Unable to resolve session label: ${params.meta.sessionLabel}`);
return resolved.key;
}
if (params.meta.sessionKey) {
if (!requireExisting) return params.meta.sessionKey;
const resolved = await params.gateway.request("sessions.resolve", { key: params.meta.sessionKey });
if (!resolved?.key) throw new Error(`Session key not found: ${params.meta.sessionKey}`);
return resolved.key;
}
if (requestedLabel) {
const resolved = await params.gateway.request("sessions.resolve", { label: requestedLabel });
if (!resolved?.key) throw new Error(`Unable to resolve session label: ${requestedLabel}`);
return resolved.key;
}
if (requestedKey) {
if (!requireExisting) return requestedKey;
const resolved = await params.gateway.request("sessions.resolve", { key: requestedKey });
if (!resolved?.key) throw new Error(`Session key not found: ${requestedKey}`);
return resolved.key;
}
return params.fallbackKey;
}
async function resetSessionIfNeeded(params) {
if (!(params.meta.resetSession ?? params.opts.resetSession ?? false)) return;
await params.gateway.request("sessions.reset", { key: params.sessionKey });
}
//#endregion
//#region src/acp/session.ts
function createInMemorySessionStore() {
const sessions = /* @__PURE__ */ new Map();
const runIdToSessionId = /* @__PURE__ */ new Map();
const createSession = (params) => {
const sessionId = params.sessionId ?? randomUUID();
const session = {
sessionId,
sessionKey: params.sessionKey,
cwd: params.cwd,
createdAt: Date.now(),
abortController: null,
activeRunId: null
};
sessions.set(sessionId, session);
return session;
};
const getSession = (sessionId) => sessions.get(sessionId);
const getSessionByRunId = (runId) => {
const sessionId = runIdToSessionId.get(runId);
return sessionId ? sessions.get(sessionId) : void 0;
};
const setActiveRun = (sessionId, runId, abortController) => {
const session = sessions.get(sessionId);
if (!session) return;
session.activeRunId = runId;
session.abortController = abortController;
runIdToSessionId.set(runId, sessionId);
};
const clearActiveRun = (sessionId) => {
const session = sessions.get(sessionId);
if (!session) return;
if (session.activeRunId) runIdToSessionId.delete(session.activeRunId);
session.activeRunId = null;
session.abortController = null;
};
const cancelActiveRun = (sessionId) => {
const session = sessions.get(sessionId);
if (!session?.abortController) return false;
session.abortController.abort();
if (session.activeRunId) runIdToSessionId.delete(session.activeRunId);
session.abortController = null;
session.activeRunId = null;
return true;
};
const clearAllSessionsForTest = () => {
for (const session of sessions.values()) session.abortController?.abort();
sessions.clear();
runIdToSessionId.clear();
};
return {
createSession,
getSession,
getSessionByRunId,
setActiveRun,
clearActiveRun,
cancelActiveRun,
clearAllSessionsForTest
};
}
const defaultAcpSessionStore = createInMemorySessionStore();
//#endregion
//#region src/acp/types.ts
const ACP_AGENT_INFO = {
name: "openclaw-acp",
title: "OpenClaw ACP Gateway",
version: VERSION
};
//#endregion
//#region src/acp/translator.ts
var AcpGatewayAgent = class {
constructor(connection, gateway, opts = {}) {
this.pendingPrompts = /* @__PURE__ */ new Map();
this.connection = connection;
this.gateway = gateway;
this.opts = opts;
this.log = opts.verbose ? (msg) => process.stderr.write(`[acp] ${msg}\n`) : () => {};
this.sessionStore = opts.sessionStore ?? defaultAcpSessionStore;
}
start() {
this.log("ready");
}
handleGatewayReconnect() {
this.log("gateway reconnected");
}
handleGatewayDisconnect(reason) {
this.log(`gateway disconnected: ${reason}`);
for (const pending of this.pendingPrompts.values()) {
pending.reject(/* @__PURE__ */ new Error(`Gateway disconnected: ${reason}`));
this.sessionStore.clearActiveRun(pending.sessionId);
}
this.pendingPrompts.clear();
}
async handleGatewayEvent(evt) {
if (evt.event === "chat") {
await this.handleChatEvent(evt);
return;
}
if (evt.event === "agent") await this.handleAgentEvent(evt);
}
async initialize(_params) {
return {
protocolVersion: PROTOCOL_VERSION,
agentCapabilities: {
loadSession: true,
promptCapabilities: {
image: true,
audio: false,
embeddedContext: true
},
mcpCapabilities: {
http: false,
sse: false
},
sessionCapabilities: { list: {} }
},
agentInfo: ACP_AGENT_INFO,
authMethods: []
};
}
async newSession(params) {
if (params.mcpServers.length > 0) this.log(`ignoring ${params.mcpServers.length} MCP servers`);
const sessionId = randomUUID();
const meta = parseSessionMeta(params._meta);
const sessionKey = await resolveSessionKey({
meta,
fallbackKey: `acp:${sessionId}`,
gateway: this.gateway,
opts: this.opts
});
await resetSessionIfNeeded({
meta,
sessionKey,
gateway: this.gateway,
opts: this.opts
});
const session = this.sessionStore.createSession({
sessionId,
sessionKey,
cwd: params.cwd
});
this.log(`newSession: ${session.sessionId} -> ${session.sessionKey}`);
await this.sendAvailableCommands(session.sessionId);
return { sessionId: session.sessionId };
}
async loadSession(params) {
if (params.mcpServers.length > 0) this.log(`ignoring ${params.mcpServers.length} MCP servers`);
const meta = parseSessionMeta(params._meta);
const sessionKey = await resolveSessionKey({
meta,
fallbackKey: params.sessionId,
gateway: this.gateway,
opts: this.opts
});
await resetSessionIfNeeded({
meta,
sessionKey,
gateway: this.gateway,
opts: this.opts
});
const session = this.sessionStore.createSession({
sessionId: params.sessionId,
sessionKey,
cwd: params.cwd
});
this.log(`loadSession: ${session.sessionId} -> ${session.sessionKey}`);
await this.sendAvailableCommands(session.sessionId);
return {};
}
async unstable_listSessions(params) {
const limit = readNumber(params._meta, ["limit"]) ?? 100;
const result = await this.gateway.request("sessions.list", { limit });
const cwd = params.cwd ?? process.cwd();
return {
sessions: result.sessions.map((session) => ({
sessionId: session.key,
cwd,
title: session.displayName ?? session.label ?? session.key,
updatedAt: session.updatedAt ? new Date(session.updatedAt).toISOString() : void 0,
_meta: {
sessionKey: session.key,
kind: session.kind,
channel: session.channel
}
})),
nextCursor: null
};
}
async authenticate(_params) {
return {};
}
async setSessionMode(params) {
const session = this.sessionStore.getSession(params.sessionId);
if (!session) throw new Error(`Session ${params.sessionId} not found`);
if (!params.modeId) return {};
try {
await this.gateway.request("sessions.patch", {
key: session.sessionKey,
thinkingLevel: params.modeId
});
this.log(`setSessionMode: ${session.sessionId} -> ${params.modeId}`);
} catch (err) {
this.log(`setSessionMode error: ${String(err)}`);
}
return {};
}
async prompt(params) {
const session = this.sessionStore.getSession(params.sessionId);
if (!session) throw new Error(`Session ${params.sessionId} not found`);
if (session.abortController) this.sessionStore.cancelActiveRun(params.sessionId);
const abortController = new AbortController();
const runId = randomUUID();
this.sessionStore.setActiveRun(params.sessionId, runId, abortController);
const meta = parseSessionMeta(params._meta);
const userText = extractTextFromPrompt(params.prompt);
const attachments = extractAttachmentsFromPrompt(params.prompt);
const message = meta.prefixCwd ?? this.opts.prefixCwd ?? true ? `[Working directory: ${session.cwd}]\n\n${userText}` : userText;
return new Promise((resolve, reject) => {
this.pendingPrompts.set(params.sessionId, {
sessionId: params.sessionId,
sessionKey: session.sessionKey,
idempotencyKey: runId,
resolve,
reject
});
this.gateway.request("chat.send", {
sessionKey: session.sessionKey,
message,
attachments: attachments.length > 0 ? attachments : void 0,
idempotencyKey: runId,
thinking: readString(params._meta, ["thinking", "thinkingLevel"]),
deliver: readBool(params._meta, ["deliver"]),
timeoutMs: readNumber(params._meta, ["timeoutMs"])
}, { expectFinal: true }).catch((err) => {
this.pendingPrompts.delete(params.sessionId);
this.sessionStore.clearActiveRun(params.sessionId);
reject(err instanceof Error ? err : new Error(String(err)));
});
});
}
async cancel(params) {
const session = this.sessionStore.getSession(params.sessionId);
if (!session) return;
this.sessionStore.cancelActiveRun(params.sessionId);
try {
await this.gateway.request("chat.abort", { sessionKey: session.sessionKey });
} catch (err) {
this.log(`cancel error: ${String(err)}`);
}
const pending = this.pendingPrompts.get(params.sessionId);
if (pending) {
this.pendingPrompts.delete(params.sessionId);
pending.resolve({ stopReason: "cancelled" });
}
}
async handleAgentEvent(evt) {
const payload = evt.payload;
if (!payload) return;
const stream = payload.stream;
const data = payload.data;
const sessionKey = payload.sessionKey;
if (!stream || !data || !sessionKey) return;
if (stream !== "tool") return;
const phase = data.phase;
const name = data.name;
const toolCallId = data.toolCallId;
if (!toolCallId) return;
const pending = this.findPendingBySessionKey(sessionKey);
if (!pending) return;
if (phase === "start") {
if (!pending.toolCalls) pending.toolCalls = /* @__PURE__ */ new Set();
if (pending.toolCalls.has(toolCallId)) return;
pending.toolCalls.add(toolCallId);
const args = data.args;
await this.connection.sessionUpdate({
sessionId: pending.sessionId,
update: {
sessionUpdate: "tool_call",
toolCallId,
title: formatToolTitle(name, args),
status: "in_progress",
rawInput: args,
kind: inferToolKind(name)
}
});
return;
}
if (phase === "result") {
const isError = Boolean(data.isError);
await this.connection.sessionUpdate({
sessionId: pending.sessionId,
update: {
sessionUpdate: "tool_call_update",
toolCallId,
status: isError ? "failed" : "completed",
rawOutput: data.result
}
});
}
}
async handleChatEvent(evt) {
const payload = evt.payload;
if (!payload) return;
const sessionKey = payload.sessionKey;
const state = payload.state;
const runId = payload.runId;
const messageData = payload.message;
if (!sessionKey || !state) return;
const pending = this.findPendingBySessionKey(sessionKey);
if (!pending) return;
if (runId && pending.idempotencyKey !== runId) return;
if (state === "delta" && messageData) {
await this.handleDeltaEvent(pending.sessionId, messageData);
return;
}
if (state === "final") {
this.finishPrompt(pending.sessionId, pending, "end_turn");
return;
}
if (state === "aborted") {
this.finishPrompt(pending.sessionId, pending, "cancelled");
return;
}
if (state === "error") this.finishPrompt(pending.sessionId, pending, "refusal");
}
async handleDeltaEvent(sessionId, messageData) {
const fullText = messageData.content?.find((c) => c.type === "text")?.text ?? "";
const pending = this.pendingPrompts.get(sessionId);
if (!pending) return;
const sentSoFar = pending.sentTextLength ?? 0;
if (fullText.length <= sentSoFar) return;
const newText = fullText.slice(sentSoFar);
pending.sentTextLength = fullText.length;
pending.sentText = fullText;
await this.connection.sessionUpdate({
sessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: {
type: "text",
text: newText
}
}
});
}
finishPrompt(sessionId, pending, stopReason) {
this.pendingPrompts.delete(sessionId);
this.sessionStore.clearActiveRun(sessionId);
pending.resolve({ stopReason });
}
findPendingBySessionKey(sessionKey) {
for (const pending of this.pendingPrompts.values()) if (pending.sessionKey === sessionKey) return pending;
}
async sendAvailableCommands(sessionId) {
await this.connection.sessionUpdate({
sessionId,
update: {
sessionUpdate: "available_commands_update",
availableCommands: getAvailableCommands()
}
});
}
};
//#endregion
//#region src/acp/server.ts
function serveAcpGateway(opts = {}) {
const cfg = loadConfig();
const connection = buildGatewayConnectionDetails({
config: cfg,
url: opts.gatewayUrl
});
const isRemoteMode = cfg.gateway?.mode === "remote";
const remote = isRemoteMode ? cfg.gateway?.remote : void 0;
const auth = resolveGatewayAuth({
authConfig: cfg.gateway?.auth,
env: process.env
});
const token = opts.gatewayToken ?? (isRemoteMode ? remote?.token?.trim() : void 0) ?? process.env.OPENCLAW_GATEWAY_TOKEN ?? auth.token;
const password = opts.gatewayPassword ?? (isRemoteMode ? remote?.password?.trim() : void 0) ?? process.env.OPENCLAW_GATEWAY_PASSWORD ?? auth.password;
let agent = null;
const gateway = new GatewayClient({
url: connection.url,
token: token || void 0,
password: password || void 0,
clientName: GATEWAY_CLIENT_NAMES.CLI,
clientDisplayName: "ACP",
clientVersion: "acp",
mode: GATEWAY_CLIENT_MODES.CLI,
onEvent: (evt) => {
agent?.handleGatewayEvent(evt);
},
onHelloOk: () => {
agent?.handleGatewayReconnect();
},
onClose: (code, reason) => {
agent?.handleGatewayDisconnect(`${code}: ${reason}`);
}
});
new AgentSideConnection((conn) => {
agent = new AcpGatewayAgent(conn, gateway, opts);
agent.start();
return agent;
}, ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin)));
gateway.start();
}
function parseArgs(args) {
const opts = {};
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (arg === "--url" || arg === "--gateway-url") {
opts.gatewayUrl = args[i + 1];
i += 1;
continue;
}
if (arg === "--token" || arg === "--gateway-token") {
opts.gatewayToken = args[i + 1];
i += 1;
continue;
}
if (arg === "--password" || arg === "--gateway-password") {
opts.gatewayPassword = args[i + 1];
i += 1;
continue;
}
if (arg === "--session") {
opts.defaultSessionKey = args[i + 1];
i += 1;
continue;
}
if (arg === "--session-label") {
opts.defaultSessionLabel = args[i + 1];
i += 1;
continue;
}
if (arg === "--require-existing") {
opts.requireExistingSession = true;
continue;
}
if (arg === "--reset-session") {
opts.resetSession = true;
continue;
}
if (arg === "--no-prefix-cwd") {
opts.prefixCwd = false;
continue;
}
if (arg === "--verbose" || arg === "-v") {
opts.verbose = true;
continue;
}
if (arg === "--help" || arg === "-h") {
printHelp();
process.exit(0);
}
}
return opts;
}
function printHelp() {
console.log(`Usage: openclaw acp [options]
Gateway-backed ACP server for IDE integration.
Options:
--url <url> Gateway WebSocket URL
--token <token> Gateway auth token
--password <password> Gateway auth password
--session <key> Default session key (e.g. "agent:main:main")
--session-label <label> Default session label to resolve
--require-existing Fail if the session key/label does not exist
--reset-session Reset the session key before first use
--no-prefix-cwd Do not prefix prompts with the working directory
--verbose, -v Verbose logging to stderr
--help, -h Show this help message
`);
}
if (isMainModule({ currentFile: fileURLToPath(import.meta.url) })) serveAcpGateway(parseArgs(process.argv.slice(2)));
//#endregion
//#region src/cli/acp-cli.ts
function registerAcpCli(program) {
const acp = program.command("acp").description("Run an ACP bridge backed by the Gateway");
acp.option("--url <url>", "Gateway WebSocket URL (defaults to gateway.remote.url when configured)").option("--token <token>", "Gateway token (if required)").option("--password <password>", "Gateway password (if required)").option("--session <key>", "Default session key (e.g. agent:main:main)").option("--session-label <label>", "Default session label to resolve").option("--require-existing", "Fail if the session key/label does not exist", false).option("--reset-session", "Reset the session key before first use", false).option("--no-prefix-cwd", "Do not prefix prompts with the working directory", false).option("--verbose, -v", "Verbose logging to stderr", false).addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/acp", "docs.openclaw.ai/cli/acp")}\n`).action((opts) => {
try {
serveAcpGateway({
gatewayUrl: opts.url,
gatewayToken: opts.token,
gatewayPassword: opts.password,
defaultSessionKey: opts.session,
defaultSessionLabel: opts.sessionLabel,
requireExistingSession: Boolean(opts.requireExisting),
resetSession: Boolean(opts.resetSession),
prefixCwd: !opts.noPrefixCwd,
verbose: Boolean(opts.verbose)
});
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
acp.command("client").description("Run an interactive ACP client against the local ACP bridge").option("--cwd <dir>", "Working directory for the ACP session").option("--server <command>", "ACP server command (default: openclaw)").option("--server-args <args...>", "Extra arguments for the ACP server").option("--server-verbose", "Enable verbose logging on the ACP server", false).option("--verbose, -v", "Verbose client logging", false).action(async (opts) => {
try {
await runAcpClientInteractive({
cwd: opts.cwd,
serverCommand: opts.server,
serverArgs: opts.serverArgs,
serverVerbose: Boolean(opts.serverVerbose),
verbose: Boolean(opts.verbose)
});
} catch (err) {
defaultRuntime.error(String(err));
defaultRuntime.exit(1);
}
});
}
//#endregion
export { registerAcpCli };