@gguf/claw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,042 lines (1,031 loc) • 41.6 kB
JavaScript
import "./paths-B4BZAPZh.js";
import { B as theme, P as setVerbose, S as shortenHomePath, y as resolveUserPath } from "./utils-CP9YLh6M.js";
import "./thinking-EAliFiVK.js";
import { Ht as createDefaultDeps, Mt as resolveSessionKeyForRequest, jt as agentCommand } from "./reply-BylFHBd4.js";
import { l as normalizeAgentId, n as DEFAULT_AGENT_ID, t as DEFAULT_ACCOUNT_ID } from "./session-key-CZ6OwgSB.js";
import { r as DEFAULT_CHAT_CHANNEL } from "./registry-B-j4DRfe.js";
import { f as defaultRuntime } from "./subsystem-BCQGGxdd.js";
import "./exec-DYqRzFbo.js";
import { _ as DEFAULT_IDENTITY_FILENAME, c as resolveAgentWorkspaceDir, i as resolveAgentDir, l as resolveDefaultAgentId, n as listAgentIds, t as listAgentEntries } from "./agent-scope-BnZW9Gh2.js";
import { Mt as resolveAuthStorePath, kt as ensureAuthProfileStore } from "./model-selection-CqaTAlhy.js";
import "./github-copilot-token-D2zp6kMZ.js";
import { t as formatCliCommand } from "./command-format-DEKzLnLg.js";
import "./boolean-BsqeuxE6.js";
import "./env-VriqyjXT.js";
import { h as GATEWAY_CLIENT_NAMES, l as normalizeMessageChannel, m as GATEWAY_CLIENT_MODES } from "./message-channel-Bena1Tzd.js";
import "./send-CsyONLnQ.js";
import { i as loadConfig, l as writeConfigFile } from "./config-PQiujvsf.js";
import "./manifest-registry-4k4vkhPS.js";
import "./runner-U04aiHHC.js";
import "./image-DxEpBZim.js";
import "./models-config-B459BnCS.js";
import "./pi-model-discovery-4uUnLc3n.js";
import "./pi-embedded-helpers-BdscQhyS.js";
import "./sandbox-UAzvS0V6.js";
import "./image-ops-CI1VknD1.js";
import "./common-a25M2Kvi.js";
import "./chrome-Dd5zBIFu.js";
import "./tailscale-C3JsBEiY.js";
import "./auth-DZSWPd8D.js";
import "./server-context-DAWsUNUs.js";
import "./frontmatter-6HZ0_y2V.js";
import "./skills-CLmzWt48.js";
import "./routes-DIQa_0pt.js";
import "./redact-B7Mjvk0c.js";
import "./errors-kfGqPQ4b.js";
import "./fs-safe-_OpKPuZA.js";
import "./paths-D1WZUYry.js";
import "./ssrf-6f5m2MMA.js";
import "./store-DSGYY-H9.js";
import "./ports-DwDxX6cS.js";
import "./trash-DLzIf__E.js";
import "./sessions-Z1BZU3xh.js";
import "./dock-7F-MiPtF.js";
import "./normalize-Ci8UyR3Z.js";
import "./accounts-Bj1dJ-fd.js";
import "./accounts-DOl1Wkxo.js";
import "./accounts-t6R5_PzC.js";
import "./bindings-Dja1z-h8.js";
import "./logging-xYH6GmRT.js";
import "./send-uu2Jc-Js.js";
import { n as listChannelPlugins, r as normalizeChannelId, t as getChannelPlugin } from "./plugins-CKbXkuXd.js";
import { s as resolveSessionTranscriptsDirForAgent } from "./paths-C2NfoGZE.js";
import "./tool-display-Bx4M6uNT.js";
import "./fetch-guard-C1sYsWgl.js";
import "./api-key-rotation-CpGefdGL.js";
import "./local-roots-DiDvXIIo.js";
import "./sqlite-JcMMx8Z5.js";
import "./model-catalog-CIixTnGt.js";
import "./tokens-D4lZk7-h.js";
import "./with-timeout-DijiQjw6.js";
import "./deliver-DgvS3uCz.js";
import "./diagnostic-UUwku4RV.js";
import "./diagnostic-session-state-ByqoIyGn.js";
import "./send-DbwyyQAv.js";
import "./model-VbWjwqaW.js";
import "./reply-prefix-DAK7-zK4.js";
import "./memory-cli-DxmtLhh_.js";
import "./manager-C0teWocQ.js";
import "./retry-rUEdE6zT.js";
import "./chunk-CMylpCPi.js";
import "./markdown-tables-B9VGUdDc.js";
import "./ir-24qCLTKH.js";
import "./render-CXDO_kgw.js";
import "./commands-registry-bb_U-3FM.js";
import "./client-BdSkEtCd.js";
import { a as randomIdempotencyKey, n as callGateway } from "./call-BCz_8mqq.js";
import "./pairing-token-B-_eiWlR.js";
import "./channel-activity-DynIQUB_.js";
import "./fetch-Bu5zBCce.js";
import "./tables-C70h_80U.js";
import "./send-CAfFUUkm.js";
import "./pairing-store-T5DpOfoL.js";
import "./proxy-BiOWrea2.js";
import { t as formatDocsLink } from "./links-BVCMOGeE.js";
import { n as runCommandWithRuntime } from "./cli-utils-CX1oQ81G.js";
import { t as formatHelpExamples } from "./help-format-dbk2xW0E.js";
import { n as withProgress } from "./progress-CaVMHLaE.js";
import "./resolve-route-2TU_aaLs.js";
import "./replies-NhrZJ3yf.js";
import "./skill-commands-N8Sho46-.js";
import "./workspace-dirs-CE3CxabZ.js";
import "./pi-tools.policy-DTy1DnPK.js";
import "./outbound-attachment-CntoLTI_.js";
import "./delivery-queue-CxDDEWQg.js";
import "./session-cost-usage-BlUDaylg.js";
import "./send-4LoRaltQ.js";
import { a as ensureWorkspaceAndSessions, l as moveToTrash } from "./onboard-helpers-DbjfBSMX.js";
import "./prompt-style-VqCNjBi8.js";
import "./pairing-labels-DaESq9ML.js";
import "./exec-approvals-q0C8VDMT.js";
import "./nodes-screen-5fMfTT2n.js";
import "./control-service-BoO2TGOx.js";
import "./stagger-B6VQyn1F.js";
import "./channel-selection-DuDSoVSC.js";
import { t as collectOption } from "./helpers-B5MBaaJr.js";
import "./catalog-CgtgPHMj.js";
import { t as hasExplicitOptions } from "./command-options-ByE5SNIE.js";
import "./note-C-OTWf0F.js";
import { t as WizardCancelledError } from "./prompts-xO_GDgJd.js";
import { t as createClackPrompter } from "./clack-prompter-C8qX15zH.js";
import { t as resolveChannelDefaultAccountId } from "./helpers-tGq4L3A8.js";
import "./plugin-auto-enable-DJOFx9vI.js";
import "./install-safe-path-DSS8Pu5f.js";
import "./npm-registry-spec-pAPjxG47.js";
import "./skill-scanner-D56xvwQF.js";
import "./installs-BNOz2SvJ.js";
import { r as setupChannels } from "./onboard-channels--VW5mf3G.js";
import { t as requireValidConfigSnapshot } from "./config-validation-C3sQczw2.js";
import { a as pruneAgentConfig, c as parseIdentityMarkdown, i as loadAgentIdentity, n as buildAgentSummaries, o as identityHasValues, r as findAgentEntryIndex, t as applyAgentConfig } from "./agents.config--k-cL9FH.js";
import "./shared-B-zggieW.js";
import "./auth-token-CliHE70g.js";
import { n as logConfigUpdated } from "./logging-Cl6-FWdR.js";
import "./provider-auth-helpers-Bnx6HWum.js";
import { n as promptAuthChoiceGrouped } from "./auth-choice-prompt-CpiJWkMe.js";
import { i as applyAuthChoice, r as warnIfModelConfigLooksOff } from "./auth-choice-DfFGpXtC.js";
import "./openai-model-default-8jds2v9m.js";
import "./vllm-setup-G6R-Hqd8.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/commands/agent-via-gateway.ts
const NO_GATEWAY_TIMEOUT_MS = 2147e6;
function parseTimeoutSeconds(opts) {
const raw = opts.timeout !== void 0 ? Number.parseInt(String(opts.timeout), 10) : opts.cfg.agents?.defaults?.timeoutSeconds ?? 600;
if (Number.isNaN(raw) || raw < 0) throw new Error("--timeout must be a non-negative integer (seconds; 0 means no timeout)");
return raw;
}
function formatPayloadForLog(payload) {
const lines = [];
if (payload.text) lines.push(payload.text.trimEnd());
const mediaUrl = typeof payload.mediaUrl === "string" && payload.mediaUrl.trim() ? payload.mediaUrl.trim() : void 0;
const media = payload.mediaUrls ?? (mediaUrl ? [mediaUrl] : []);
for (const url of media) lines.push(`MEDIA:${url}`);
return lines.join("\n").trimEnd();
}
async function agentViaGatewayCommand(opts, runtime) {
const body = (opts.message ?? "").trim();
if (!body) throw new Error("Message (--message) is required");
if (!opts.to && !opts.sessionId && !opts.agent) throw new Error("Pass --to <E.164>, --session-id, or --agent to choose a session");
const cfg = loadConfig();
const agentIdRaw = opts.agent?.trim();
const agentId = agentIdRaw ? normalizeAgentId(agentIdRaw) : void 0;
if (agentId) {
if (!listAgentIds(cfg).includes(agentId)) throw new Error(`Unknown agent id "${agentIdRaw}". Use "${formatCliCommand("openclaw agents list")}" to see configured agents.`);
}
const timeoutSeconds = parseTimeoutSeconds({
cfg,
timeout: opts.timeout
});
const gatewayTimeoutMs = timeoutSeconds === 0 ? NO_GATEWAY_TIMEOUT_MS : Math.max(1e4, (timeoutSeconds + 30) * 1e3);
const sessionKey = resolveSessionKeyForRequest({
cfg,
agentId,
to: opts.to,
sessionId: opts.sessionId
}).sessionKey;
const channel = normalizeMessageChannel(opts.channel) ?? DEFAULT_CHAT_CHANNEL;
const idempotencyKey = opts.runId?.trim() || randomIdempotencyKey();
const response = await withProgress({
label: "Waiting for agent reply…",
indeterminate: true,
enabled: opts.json !== true
}, async () => await callGateway({
method: "agent",
params: {
message: body,
agentId,
to: opts.to,
replyTo: opts.replyTo,
sessionId: opts.sessionId,
sessionKey,
thinking: opts.thinking,
deliver: Boolean(opts.deliver),
channel,
replyChannel: opts.replyChannel,
replyAccountId: opts.replyAccount,
timeout: timeoutSeconds,
lane: opts.lane,
extraSystemPrompt: opts.extraSystemPrompt,
idempotencyKey
},
expectFinal: true,
timeoutMs: gatewayTimeoutMs,
clientName: GATEWAY_CLIENT_NAMES.CLI,
mode: GATEWAY_CLIENT_MODES.CLI
}));
if (opts.json) {
runtime.log(JSON.stringify(response, null, 2));
return response;
}
const payloads = (response?.result)?.payloads ?? [];
if (payloads.length === 0) {
runtime.log(response?.summary ? String(response.summary) : "No reply from agent.");
return response;
}
for (const payload of payloads) {
const out = formatPayloadForLog(payload);
if (out) runtime.log(out);
}
return response;
}
async function agentCliCommand(opts, runtime, deps) {
const localOpts = {
...opts,
agentId: opts.agent,
replyAccountId: opts.replyAccount
};
if (opts.local === true) return await agentCommand(localOpts, runtime, deps);
try {
return await agentViaGatewayCommand(opts, runtime);
} catch (err) {
runtime.error?.(`Gateway agent failed; falling back to embedded: ${String(err)}`);
return await agentCommand(localOpts, runtime, deps);
}
}
//#endregion
//#region src/commands/agents.bindings.ts
function bindingMatchKey(match) {
const accountId = match.accountId?.trim() || DEFAULT_ACCOUNT_ID;
return [
match.channel,
accountId,
match.peer?.kind ?? "",
match.peer?.id ?? "",
match.guildId ?? "",
match.teamId ?? ""
].join("|");
}
function describeBinding(binding) {
const match = binding.match;
const parts = [match.channel];
if (match.accountId) parts.push(`accountId=${match.accountId}`);
if (match.peer) parts.push(`peer=${match.peer.kind}:${match.peer.id}`);
if (match.guildId) parts.push(`guild=${match.guildId}`);
if (match.teamId) parts.push(`team=${match.teamId}`);
return parts.join(" ");
}
function applyAgentBindings(cfg, bindings) {
const existing = cfg.bindings ?? [];
const existingMatchMap = /* @__PURE__ */ new Map();
for (const binding of existing) {
const key = bindingMatchKey(binding.match);
if (!existingMatchMap.has(key)) existingMatchMap.set(key, normalizeAgentId(binding.agentId));
}
const added = [];
const skipped = [];
const conflicts = [];
for (const binding of bindings) {
const agentId = normalizeAgentId(binding.agentId);
const key = bindingMatchKey(binding.match);
const existingAgentId = existingMatchMap.get(key);
if (existingAgentId) {
if (existingAgentId === agentId) skipped.push(binding);
else conflicts.push({
binding,
existingAgentId
});
continue;
}
existingMatchMap.set(key, agentId);
added.push({
...binding,
agentId
});
}
if (added.length === 0) return {
config: cfg,
added,
skipped,
conflicts
};
return {
config: {
...cfg,
bindings: [...existing, ...added]
},
added,
skipped,
conflicts
};
}
function resolveDefaultAccountId$1(cfg, provider) {
const plugin = getChannelPlugin(provider);
if (!plugin) return DEFAULT_ACCOUNT_ID;
return resolveChannelDefaultAccountId({
plugin,
cfg
});
}
function buildChannelBindings(params) {
const bindings = [];
const agentId = normalizeAgentId(params.agentId);
for (const channel of params.selection) {
const match = { channel };
const accountId = params.accountIds?.[channel]?.trim();
if (accountId) match.accountId = accountId;
else if (getChannelPlugin(channel)?.meta.forceAccountBinding) match.accountId = resolveDefaultAccountId$1(params.config, channel);
bindings.push({
agentId,
match
});
}
return bindings;
}
function parseBindingSpecs(params) {
const bindings = [];
const errors = [];
const specs = params.specs ?? [];
const agentId = normalizeAgentId(params.agentId);
for (const raw of specs) {
const trimmed = raw?.trim();
if (!trimmed) continue;
const [channelRaw, accountRaw] = trimmed.split(":", 2);
const channel = normalizeChannelId(channelRaw);
if (!channel) {
errors.push(`Unknown channel "${channelRaw}".`);
continue;
}
let accountId = accountRaw?.trim();
if (accountRaw !== void 0 && !accountId) {
errors.push(`Invalid binding "${trimmed}" (empty account id).`);
continue;
}
if (!accountId) {
if (getChannelPlugin(channel)?.meta.forceAccountBinding) accountId = resolveDefaultAccountId$1(params.config, channel);
}
const match = { channel };
if (accountId) match.accountId = accountId;
bindings.push({
agentId,
match
});
}
return {
bindings,
errors
};
}
//#endregion
//#region src/commands/agents.command-shared.ts
function createQuietRuntime(runtime) {
return {
...runtime,
log: () => {}
};
}
async function requireValidConfig(runtime) {
return await requireValidConfigSnapshot(runtime);
}
//#endregion
//#region src/commands/agents.commands.add.ts
async function fileExists(pathname) {
try {
await fs.stat(pathname);
return true;
} catch {
return false;
}
}
async function agentsAddCommand(opts, runtime = defaultRuntime, params) {
const cfg = await requireValidConfig(runtime);
if (!cfg) return;
const workspaceFlag = opts.workspace?.trim();
const nameInput = opts.name?.trim();
const hasFlags = params?.hasFlags === true;
const nonInteractive = Boolean(opts.nonInteractive || hasFlags);
if (nonInteractive && !workspaceFlag) {
runtime.error("Non-interactive mode requires --workspace. Re-run without flags to use the wizard.");
runtime.exit(1);
return;
}
if (nonInteractive) {
if (!nameInput) {
runtime.error("Agent name is required in non-interactive mode.");
runtime.exit(1);
return;
}
if (!workspaceFlag) {
runtime.error("Non-interactive mode requires --workspace. Re-run without flags to use the wizard.");
runtime.exit(1);
return;
}
const agentId = normalizeAgentId(nameInput);
if (agentId === DEFAULT_AGENT_ID) {
runtime.error(`"${DEFAULT_AGENT_ID}" is reserved. Choose another name.`);
runtime.exit(1);
return;
}
if (agentId !== nameInput) runtime.log(`Normalized agent id to "${agentId}".`);
if (findAgentEntryIndex(listAgentEntries(cfg), agentId) >= 0) {
runtime.error(`Agent "${agentId}" already exists.`);
runtime.exit(1);
return;
}
const workspaceDir = resolveUserPath(workspaceFlag);
const agentDir = opts.agentDir?.trim() ? resolveUserPath(opts.agentDir.trim()) : resolveAgentDir(cfg, agentId);
const model = opts.model?.trim();
const nextConfig = applyAgentConfig(cfg, {
agentId,
name: nameInput,
workspace: workspaceDir,
agentDir,
...model ? { model } : {}
});
const bindingParse = parseBindingSpecs({
agentId,
specs: opts.bind,
config: nextConfig
});
if (bindingParse.errors.length > 0) {
runtime.error(bindingParse.errors.join("\n"));
runtime.exit(1);
return;
}
const bindingResult = bindingParse.bindings.length > 0 ? applyAgentBindings(nextConfig, bindingParse.bindings) : {
config: nextConfig,
added: [],
skipped: [],
conflicts: []
};
await writeConfigFile(bindingResult.config);
if (!opts.json) logConfigUpdated(runtime);
await ensureWorkspaceAndSessions(workspaceDir, opts.json ? createQuietRuntime(runtime) : runtime, {
skipBootstrap: Boolean(bindingResult.config.agents?.defaults?.skipBootstrap),
agentId
});
const payload = {
agentId,
name: nameInput,
workspace: workspaceDir,
agentDir,
model,
bindings: {
added: bindingResult.added.map(describeBinding),
skipped: bindingResult.skipped.map(describeBinding),
conflicts: bindingResult.conflicts.map((conflict) => `${describeBinding(conflict.binding)} (agent=${conflict.existingAgentId})`)
}
};
if (opts.json) runtime.log(JSON.stringify(payload, null, 2));
else {
runtime.log(`Agent: ${agentId}`);
runtime.log(`Workspace: ${shortenHomePath(workspaceDir)}`);
runtime.log(`Agent dir: ${shortenHomePath(agentDir)}`);
if (model) runtime.log(`Model: ${model}`);
if (bindingResult.conflicts.length > 0) runtime.error(["Skipped bindings already claimed by another agent:", ...bindingResult.conflicts.map((conflict) => `- ${describeBinding(conflict.binding)} (agent=${conflict.existingAgentId})`)].join("\n"));
}
return;
}
const prompter = createClackPrompter();
try {
await prompter.intro("Add OpenClaw agent");
const name = nameInput ?? await prompter.text({
message: "Agent name",
validate: (value) => {
if (!value?.trim()) return "Required";
if (normalizeAgentId(value) === DEFAULT_AGENT_ID) return `"${DEFAULT_AGENT_ID}" is reserved. Choose another name.`;
}
});
const agentName = String(name ?? "").trim();
const agentId = normalizeAgentId(agentName);
if (agentName !== agentId) await prompter.note(`Normalized id to "${agentId}".`, "Agent id");
if (listAgentEntries(cfg).find((agent) => normalizeAgentId(agent.id) === agentId)) {
if (!await prompter.confirm({
message: `Agent "${agentId}" already exists. Update it?`,
initialValue: false
})) {
await prompter.outro("No changes made.");
return;
}
}
const workspaceDefault = resolveAgentWorkspaceDir(cfg, agentId);
const workspaceInput = await prompter.text({
message: "Workspace directory",
initialValue: workspaceDefault,
validate: (value) => value?.trim() ? void 0 : "Required"
});
const workspaceDir = resolveUserPath(String(workspaceInput ?? "").trim() || workspaceDefault);
const agentDir = resolveAgentDir(cfg, agentId);
let nextConfig = applyAgentConfig(cfg, {
agentId,
name: agentName,
workspace: workspaceDir,
agentDir
});
const defaultAgentId = resolveDefaultAgentId(cfg);
if (defaultAgentId !== agentId) {
const sourceAuthPath = resolveAuthStorePath(resolveAgentDir(cfg, defaultAgentId));
const destAuthPath = resolveAuthStorePath(agentDir);
if (!(path.resolve(sourceAuthPath).toLowerCase() === path.resolve(destAuthPath).toLowerCase()) && await fileExists(sourceAuthPath) && !await fileExists(destAuthPath)) {
if (await prompter.confirm({
message: `Copy auth profiles from "${defaultAgentId}"?`,
initialValue: false
})) {
await fs.mkdir(path.dirname(destAuthPath), { recursive: true });
await fs.copyFile(sourceAuthPath, destAuthPath);
await prompter.note(`Copied auth profiles from "${defaultAgentId}".`, "Auth profiles");
}
}
}
if (await prompter.confirm({
message: "Configure model/auth for this agent now?",
initialValue: false
})) {
const authResult = await applyAuthChoice({
authChoice: await promptAuthChoiceGrouped({
prompter,
store: ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false }),
includeSkip: true
}),
config: nextConfig,
prompter,
runtime,
agentDir,
setDefaultModel: false,
agentId
});
nextConfig = authResult.config;
if (authResult.agentModelOverride) nextConfig = applyAgentConfig(nextConfig, {
agentId,
model: authResult.agentModelOverride
});
}
await warnIfModelConfigLooksOff(nextConfig, prompter, {
agentId,
agentDir
});
let selection = [];
const channelAccountIds = {};
nextConfig = await setupChannels(nextConfig, runtime, prompter, {
allowSignalInstall: true,
onSelection: (value) => {
selection = value;
},
promptAccountIds: true,
onAccountId: (channel, accountId) => {
channelAccountIds[channel] = accountId;
}
});
if (selection.length > 0) if (await prompter.confirm({
message: "Route selected channels to this agent now? (bindings)",
initialValue: false
})) {
const desiredBindings = buildChannelBindings({
agentId,
selection,
config: nextConfig,
accountIds: channelAccountIds
});
const result = applyAgentBindings(nextConfig, desiredBindings);
nextConfig = result.config;
if (result.conflicts.length > 0) await prompter.note(["Skipped bindings already claimed by another agent:", ...result.conflicts.map((conflict) => `- ${describeBinding(conflict.binding)} (agent=${conflict.existingAgentId})`)].join("\n"), "Routing bindings");
} else await prompter.note(["Routing unchanged. Add bindings when you're ready.", "Docs: https://docs.openclaw.ai/concepts/multi-agent"].join("\n"), "Routing");
await writeConfigFile(nextConfig);
logConfigUpdated(runtime);
await ensureWorkspaceAndSessions(workspaceDir, runtime, {
skipBootstrap: Boolean(nextConfig.agents?.defaults?.skipBootstrap),
agentId
});
const payload = {
agentId,
name: agentName,
workspace: workspaceDir,
agentDir
};
if (opts.json) runtime.log(JSON.stringify(payload, null, 2));
await prompter.outro(`Agent "${agentId}" ready.`);
} catch (err) {
if (err instanceof WizardCancelledError) {
runtime.exit(1);
return;
}
throw err;
}
}
//#endregion
//#region src/commands/agents.commands.delete.ts
async function agentsDeleteCommand(opts, runtime = defaultRuntime) {
const cfg = await requireValidConfig(runtime);
if (!cfg) return;
const input = opts.id?.trim();
if (!input) {
runtime.error("Agent id is required.");
runtime.exit(1);
return;
}
const agentId = normalizeAgentId(input);
if (agentId !== input) runtime.log(`Normalized agent id to "${agentId}".`);
if (agentId === DEFAULT_AGENT_ID) {
runtime.error(`"${DEFAULT_AGENT_ID}" cannot be deleted.`);
runtime.exit(1);
return;
}
if (findAgentEntryIndex(listAgentEntries(cfg), agentId) < 0) {
runtime.error(`Agent "${agentId}" not found.`);
runtime.exit(1);
return;
}
if (!opts.force) {
if (!process.stdin.isTTY) {
runtime.error("Non-interactive session. Re-run with --force.");
runtime.exit(1);
return;
}
if (!await createClackPrompter().confirm({
message: `Delete agent "${agentId}" and prune workspace/state?`,
initialValue: false
})) {
runtime.log("Cancelled.");
return;
}
}
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
const agentDir = resolveAgentDir(cfg, agentId);
const sessionsDir = resolveSessionTranscriptsDirForAgent(agentId);
const result = pruneAgentConfig(cfg, agentId);
await writeConfigFile(result.config);
if (!opts.json) logConfigUpdated(runtime);
const quietRuntime = opts.json ? createQuietRuntime(runtime) : runtime;
await moveToTrash(workspaceDir, quietRuntime);
await moveToTrash(agentDir, quietRuntime);
await moveToTrash(sessionsDir, quietRuntime);
if (opts.json) runtime.log(JSON.stringify({
agentId,
workspace: workspaceDir,
agentDir,
sessionsDir,
removedBindings: result.removedBindings,
removedAllow: result.removedAllow
}, null, 2));
else runtime.log(`Deleted agent: ${agentId}`);
}
//#endregion
//#region src/commands/agents.commands.identity.ts
const normalizeWorkspacePath = (input) => path.resolve(resolveUserPath(input));
const coerceTrimmed = (value) => {
const trimmed = value?.trim();
return trimmed ? trimmed : void 0;
};
async function loadIdentityFromFile(filePath) {
try {
const parsed = parseIdentityMarkdown(await fs.readFile(filePath, "utf-8"));
if (!identityHasValues(parsed)) return null;
return parsed;
} catch {
return null;
}
}
function resolveAgentIdByWorkspace(cfg, workspaceDir) {
const list = listAgentEntries(cfg);
const ids = list.length > 0 ? list.map((entry) => normalizeAgentId(entry.id)) : [resolveDefaultAgentId(cfg)];
const normalizedTarget = normalizeWorkspacePath(workspaceDir);
return ids.filter((id) => normalizeWorkspacePath(resolveAgentWorkspaceDir(cfg, id)) === normalizedTarget);
}
async function agentsSetIdentityCommand(opts, runtime = defaultRuntime) {
const cfg = await requireValidConfig(runtime);
if (!cfg) return;
const agentRaw = coerceTrimmed(opts.agent);
const nameRaw = coerceTrimmed(opts.name);
const emojiRaw = coerceTrimmed(opts.emoji);
const themeRaw = coerceTrimmed(opts.theme);
const avatarRaw = coerceTrimmed(opts.avatar);
const hasExplicitIdentity = Boolean(nameRaw || emojiRaw || themeRaw || avatarRaw);
const identityFileRaw = coerceTrimmed(opts.identityFile);
const workspaceRaw = coerceTrimmed(opts.workspace);
const wantsIdentityFile = Boolean(opts.fromIdentity || identityFileRaw || !hasExplicitIdentity);
let identityFilePath;
let workspaceDir;
if (identityFileRaw) {
identityFilePath = normalizeWorkspacePath(identityFileRaw);
workspaceDir = path.dirname(identityFilePath);
} else if (workspaceRaw) workspaceDir = normalizeWorkspacePath(workspaceRaw);
else if (wantsIdentityFile || !agentRaw) workspaceDir = path.resolve(process.cwd());
let agentId = agentRaw ? normalizeAgentId(agentRaw) : void 0;
if (!agentId) {
if (!workspaceDir) {
runtime.error("Select an agent with --agent or provide a workspace via --workspace.");
runtime.exit(1);
return;
}
const matches = resolveAgentIdByWorkspace(cfg, workspaceDir);
if (matches.length === 0) {
runtime.error(`No agent workspace matches ${shortenHomePath(workspaceDir)}. Pass --agent to target a specific agent.`);
runtime.exit(1);
return;
}
if (matches.length > 1) {
runtime.error(`Multiple agents match ${shortenHomePath(workspaceDir)}: ${matches.join(", ")}. Pass --agent to choose one.`);
runtime.exit(1);
return;
}
agentId = matches[0];
}
let identityFromFile = null;
if (wantsIdentityFile) {
if (identityFilePath) identityFromFile = await loadIdentityFromFile(identityFilePath);
else if (workspaceDir) identityFromFile = loadAgentIdentity(workspaceDir);
if (!identityFromFile) {
const targetPath = identityFilePath ?? (workspaceDir ? path.join(workspaceDir, DEFAULT_IDENTITY_FILENAME) : "IDENTITY.md");
runtime.error(`No identity data found in ${shortenHomePath(targetPath)}.`);
runtime.exit(1);
return;
}
}
const fileTheme = identityFromFile?.theme ?? identityFromFile?.creature ?? identityFromFile?.vibe ?? void 0;
const incomingIdentity = {
...nameRaw || identityFromFile?.name ? { name: nameRaw ?? identityFromFile?.name } : {},
...emojiRaw || identityFromFile?.emoji ? { emoji: emojiRaw ?? identityFromFile?.emoji } : {},
...themeRaw || fileTheme ? { theme: themeRaw ?? fileTheme } : {},
...avatarRaw || identityFromFile?.avatar ? { avatar: avatarRaw ?? identityFromFile?.avatar } : {}
};
if (!incomingIdentity.name && !incomingIdentity.emoji && !incomingIdentity.theme && !incomingIdentity.avatar) {
runtime.error("No identity fields provided. Use --name/--emoji/--theme/--avatar or --from-identity.");
runtime.exit(1);
return;
}
const list = listAgentEntries(cfg);
const index = findAgentEntryIndex(list, agentId);
const base = index >= 0 ? list[index] : { id: agentId };
const nextIdentity = {
...base.identity,
...incomingIdentity
};
const nextEntry = {
...base,
identity: nextIdentity
};
const nextList = [...list];
if (index >= 0) nextList[index] = nextEntry;
else {
const defaultId = normalizeAgentId(resolveDefaultAgentId(cfg));
if (nextList.length === 0 && agentId !== defaultId) nextList.push({ id: defaultId });
nextList.push(nextEntry);
}
await writeConfigFile({
...cfg,
agents: {
...cfg.agents,
list: nextList
}
});
if (opts.json) {
runtime.log(JSON.stringify({
agentId,
identity: nextIdentity,
workspace: workspaceDir ?? null,
identityFile: identityFilePath ?? null
}, null, 2));
return;
}
logConfigUpdated(runtime);
runtime.log(`Agent: ${agentId}`);
if (nextIdentity.name) runtime.log(`Name: ${nextIdentity.name}`);
if (nextIdentity.theme) runtime.log(`Theme: ${nextIdentity.theme}`);
if (nextIdentity.emoji) runtime.log(`Emoji: ${nextIdentity.emoji}`);
if (nextIdentity.avatar) runtime.log(`Avatar: ${nextIdentity.avatar}`);
if (workspaceDir) runtime.log(`Workspace: ${shortenHomePath(workspaceDir)}`);
}
//#endregion
//#region src/commands/agents.providers.ts
function providerAccountKey(provider, accountId) {
return `${provider}:${accountId ?? DEFAULT_ACCOUNT_ID}`;
}
function formatChannelAccountLabel(params) {
return `${getChannelPlugin(params.provider)?.meta.label ?? params.provider} ${params.name?.trim() ? `${params.accountId} (${params.name.trim()})` : params.accountId}`;
}
function formatProviderState(entry) {
const parts = [entry.state];
if (entry.enabled === false && entry.state !== "disabled") parts.push("disabled");
return parts.join(", ");
}
async function buildProviderStatusIndex(cfg) {
const map = /* @__PURE__ */ new Map();
for (const plugin of listChannelPlugins()) {
const accountIds = plugin.config.listAccountIds(cfg);
for (const accountId of accountIds) {
const account = plugin.config.resolveAccount(cfg, accountId);
const snapshot = plugin.config.describeAccount?.(account, cfg);
const enabled = plugin.config.isEnabled ? plugin.config.isEnabled(account, cfg) : typeof snapshot?.enabled === "boolean" ? snapshot.enabled : account.enabled;
const configured = plugin.config.isConfigured ? await plugin.config.isConfigured(account, cfg) : snapshot?.configured;
const resolvedEnabled = typeof enabled === "boolean" ? enabled : true;
const resolvedConfigured = typeof configured === "boolean" ? configured : true;
const state = plugin.status?.resolveAccountState?.({
account,
cfg,
configured: resolvedConfigured,
enabled: resolvedEnabled
}) ?? (typeof snapshot?.linked === "boolean" ? snapshot.linked ? "linked" : "not linked" : resolvedConfigured ? "configured" : "not configured");
const name = snapshot?.name ?? account.name;
map.set(providerAccountKey(plugin.id, accountId), {
provider: plugin.id,
accountId,
name,
state,
enabled,
configured
});
}
}
return map;
}
function resolveDefaultAccountId(cfg, provider) {
const plugin = getChannelPlugin(provider);
if (!plugin) return DEFAULT_ACCOUNT_ID;
return resolveChannelDefaultAccountId({
plugin,
cfg
});
}
function shouldShowProviderEntry(entry, cfg) {
const plugin = getChannelPlugin(entry.provider);
if (!plugin) return Boolean(entry.configured);
if (plugin.meta.showConfigured === false) {
const providerConfig = cfg[plugin.id];
return Boolean(entry.configured) || Boolean(providerConfig);
}
return Boolean(entry.configured);
}
function formatProviderEntry(entry) {
return `${formatChannelAccountLabel({
provider: entry.provider,
accountId: entry.accountId,
name: entry.name
})}: ${formatProviderState(entry)}`;
}
function summarizeBindings(cfg, bindings) {
if (bindings.length === 0) return [];
const seen = /* @__PURE__ */ new Map();
for (const binding of bindings) {
const channel = normalizeChannelId(binding.match.channel);
if (!channel) continue;
const accountId = binding.match.accountId ?? resolveDefaultAccountId(cfg, channel);
const key = providerAccountKey(channel, accountId);
if (!seen.has(key)) {
const label = formatChannelAccountLabel({
provider: channel,
accountId
});
seen.set(key, label);
}
}
return [...seen.values()];
}
function listProvidersForAgent(params) {
const allProviderEntries = [...params.providerStatus.values()];
const providerLines = [];
if (params.bindings.length > 0) {
const seen = /* @__PURE__ */ new Set();
for (const binding of params.bindings) {
const channel = normalizeChannelId(binding.match.channel);
if (!channel) continue;
const accountId = binding.match.accountId ?? resolveDefaultAccountId(params.cfg, channel);
const key = providerAccountKey(channel, accountId);
if (seen.has(key)) continue;
seen.add(key);
const status = params.providerStatus.get(key);
if (status) providerLines.push(formatProviderEntry(status));
else providerLines.push(`${formatChannelAccountLabel({
provider: channel,
accountId
})}: unknown`);
}
return providerLines;
}
if (params.summaryIsDefault) {
for (const entry of allProviderEntries) if (shouldShowProviderEntry(entry, params.cfg)) providerLines.push(formatProviderEntry(entry));
}
return providerLines;
}
//#endregion
//#region src/commands/agents.commands.list.ts
function formatSummary(summary) {
const defaultTag = summary.isDefault ? " (default)" : "";
const header = summary.name && summary.name !== summary.id ? `${summary.id}${defaultTag} (${summary.name})` : `${summary.id}${defaultTag}`;
const identityParts = [];
if (summary.identityEmoji) identityParts.push(summary.identityEmoji);
if (summary.identityName) identityParts.push(summary.identityName);
const identityLine = identityParts.length > 0 ? identityParts.join(" ") : null;
const identitySource = summary.identitySource === "identity" ? "IDENTITY.md" : summary.identitySource === "config" ? "config" : null;
const lines = [`- ${header}`];
if (identityLine) lines.push(` Identity: ${identityLine}${identitySource ? ` (${identitySource})` : ""}`);
lines.push(` Workspace: ${shortenHomePath(summary.workspace)}`);
lines.push(` Agent dir: ${shortenHomePath(summary.agentDir)}`);
if (summary.model) lines.push(` Model: ${summary.model}`);
lines.push(` Routing rules: ${summary.bindings}`);
if (summary.routes?.length) lines.push(` Routing: ${summary.routes.join(", ")}`);
if (summary.providers?.length) {
lines.push(" Providers:");
for (const provider of summary.providers) lines.push(` - ${provider}`);
}
if (summary.bindingDetails?.length) {
lines.push(" Routing rules:");
for (const binding of summary.bindingDetails) lines.push(` - ${binding}`);
}
return lines.join("\n");
}
async function agentsListCommand(opts, runtime = defaultRuntime) {
const cfg = await requireValidConfig(runtime);
if (!cfg) return;
const summaries = buildAgentSummaries(cfg);
const bindingMap = /* @__PURE__ */ new Map();
for (const binding of cfg.bindings ?? []) {
const agentId = normalizeAgentId(binding.agentId);
const list = bindingMap.get(agentId) ?? [];
list.push(binding);
bindingMap.set(agentId, list);
}
if (opts.bindings) for (const summary of summaries) {
const bindings = bindingMap.get(summary.id) ?? [];
if (bindings.length > 0) summary.bindingDetails = bindings.map((binding) => describeBinding(binding));
}
const providerStatus = await buildProviderStatusIndex(cfg);
for (const summary of summaries) {
const bindings = bindingMap.get(summary.id) ?? [];
const routes = summarizeBindings(cfg, bindings);
if (routes.length > 0) summary.routes = routes;
else if (summary.isDefault) summary.routes = ["default (no explicit rules)"];
const providerLines = listProvidersForAgent({
summaryIsDefault: summary.isDefault,
cfg,
bindings,
providerStatus
});
if (providerLines.length > 0) summary.providers = providerLines;
}
if (opts.json) {
runtime.log(JSON.stringify(summaries, null, 2));
return;
}
const lines = ["Agents:", ...summaries.map(formatSummary)];
lines.push("Routing rules map channel/account/peer to an agent. Use --bindings for full rules.");
lines.push(`Channel status reflects local config/creds. For live health: ${formatCliCommand("openclaw channels status --probe")}.`);
runtime.log(lines.join("\n"));
}
//#endregion
//#region src/cli/program/register.agent.ts
function registerAgentCommands(program, args) {
program.command("agent").description("Run an agent turn via the Gateway (use --local for embedded)").requiredOption("-m, --message <text>", "Message body for the agent").option("-t, --to <number>", "Recipient number in E.164 used to derive the session key").option("--session-id <id>", "Use an explicit session id").option("--agent <id>", "Agent id (overrides routing bindings)").option("--thinking <level>", "Thinking level: off | minimal | low | medium | high").option("--verbose <on|off>", "Persist agent verbose level for the session").option("--channel <channel>", `Delivery channel: ${args.agentChannelOptions} (default: ${DEFAULT_CHAT_CHANNEL})`).option("--reply-to <target>", "Delivery target override (separate from session routing)").option("--reply-channel <channel>", "Delivery channel override (separate from routing)").option("--reply-account <id>", "Delivery account id override").option("--local", "Run the embedded agent locally (requires model provider API keys in your shell)", false).option("--deliver", "Send the agent's reply back to the selected channel", false).option("--json", "Output result as JSON", false).option("--timeout <seconds>", "Override agent command timeout (seconds, default 600 or config value)").addHelpText("after", () => `
${theme.heading("Examples:")}
${formatHelpExamples([
["openclaw agent --to +15555550123 --message \"status update\"", "Start a new session."],
["openclaw agent --agent ops --message \"Summarize logs\"", "Use a specific agent."],
["openclaw agent --session-id 1234 --message \"Summarize inbox\" --thinking medium", "Target a session with explicit thinking level."],
["openclaw agent --to +15555550123 --message \"Trace logs\" --verbose on --json", "Enable verbose logging and JSON output."],
["openclaw agent --to +15555550123 --message \"Summon reply\" --deliver", "Deliver reply."],
["openclaw agent --agent ops --message \"Generate report\" --deliver --reply-channel slack --reply-to \"#reports\"", "Send reply to a different channel/target."]
])}
${theme.muted("Docs:")} ${formatDocsLink("/cli/agent", "docs.openclaw.ai/cli/agent")}`).action(async (opts) => {
setVerbose((typeof opts.verbose === "string" ? opts.verbose.toLowerCase() : "") === "on");
const deps = createDefaultDeps();
await runCommandWithRuntime(defaultRuntime, async () => {
await agentCliCommand(opts, defaultRuntime, deps);
});
});
const agents = program.command("agents").description("Manage isolated agents (workspaces + auth + routing)").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/agents", "docs.openclaw.ai/cli/agents")}\n`);
agents.command("list").description("List configured agents").option("--json", "Output JSON instead of text", false).option("--bindings", "Include routing bindings", false).action(async (opts) => {
await runCommandWithRuntime(defaultRuntime, async () => {
await agentsListCommand({
json: Boolean(opts.json),
bindings: Boolean(opts.bindings)
}, defaultRuntime);
});
});
agents.command("add [name]").description("Add a new isolated agent").option("--workspace <dir>", "Workspace directory for the new agent").option("--model <id>", "Model id for this agent").option("--agent-dir <dir>", "Agent state directory for this agent").option("--bind <channel[:accountId]>", "Route channel binding (repeatable)", collectOption, []).option("--non-interactive", "Disable prompts; requires --workspace", false).option("--json", "Output JSON summary", false).action(async (name, opts, command) => {
await runCommandWithRuntime(defaultRuntime, async () => {
const hasFlags = hasExplicitOptions(command, [
"workspace",
"model",
"agentDir",
"bind",
"nonInteractive"
]);
await agentsAddCommand({
name: typeof name === "string" ? name : void 0,
workspace: opts.workspace,
model: opts.model,
agentDir: opts.agentDir,
bind: Array.isArray(opts.bind) ? opts.bind : void 0,
nonInteractive: Boolean(opts.nonInteractive),
json: Boolean(opts.json)
}, defaultRuntime, { hasFlags });
});
});
agents.command("set-identity").description("Update an agent identity (name/theme/emoji/avatar)").option("--agent <id>", "Agent id to update").option("--workspace <dir>", "Workspace directory used to locate the agent + IDENTITY.md").option("--identity-file <path>", "Explicit IDENTITY.md path to read").option("--from-identity", "Read values from IDENTITY.md", false).option("--name <name>", "Identity name").option("--theme <theme>", "Identity theme").option("--emoji <emoji>", "Identity emoji").option("--avatar <value>", "Identity avatar (workspace path, http(s) URL, or data URI)").option("--json", "Output JSON summary", false).addHelpText("after", () => `
${theme.heading("Examples:")}
${formatHelpExamples([
["openclaw agents set-identity --agent main --name \"OpenClaw\" --emoji \"🦞\"", "Set name + emoji."],
["openclaw agents set-identity --agent main --avatar avatars/openclaw.png", "Set avatar path."],
["openclaw agents set-identity --workspace ~/.openclaw/workspace --from-identity", "Load from IDENTITY.md."],
["openclaw agents set-identity --identity-file ~/.openclaw/workspace/IDENTITY.md --agent main", "Use a specific IDENTITY.md."]
])}
`).action(async (opts) => {
await runCommandWithRuntime(defaultRuntime, async () => {
await agentsSetIdentityCommand({
agent: opts.agent,
workspace: opts.workspace,
identityFile: opts.identityFile,
fromIdentity: Boolean(opts.fromIdentity),
name: opts.name,
theme: opts.theme,
emoji: opts.emoji,
avatar: opts.avatar,
json: Boolean(opts.json)
}, defaultRuntime);
});
});
agents.command("delete <id>").description("Delete an agent and prune workspace/state").option("--force", "Skip confirmation", false).option("--json", "Output JSON summary", false).action(async (id, opts) => {
await runCommandWithRuntime(defaultRuntime, async () => {
await agentsDeleteCommand({
id: String(id),
force: Boolean(opts.force),
json: Boolean(opts.json)
}, defaultRuntime);
});
});
agents.action(async () => {
await runCommandWithRuntime(defaultRuntime, async () => {
await agentsListCommand({}, defaultRuntime);
});
});
}
//#endregion
export { registerAgentCommands };