File size: 8,717 Bytes
eb3f11e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | // Single agent-turn command registration; delegates execution to the Gateway-backed agent command.
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
import type { Command } from "commander";
import { formatDocsLink } from "../../../packages/terminal-core/src/links.js";
import { theme } from "../../../packages/terminal-core/src/theme.js";
import { THINKING_LEVELS_HELP } from "../../auto-reply/thinking.shared.js";
import { inheritOptionFromParent } from "../command-options.js";
import { measureCliCommandStartup } from "../command-startup-timing.js";
import { formatHelpExamples } from "../help-format.js";
function collectFallback(value: string, previous: string[]): string[] {
return [...previous, value];
}
/** Register `openclaw agent` for one Gateway-backed agent turn. */
export function registerAgentTurnCommand(
program: Command,
args: { agentChannelOptions: string },
): void {
const agent = program
.command("agent")
.description("Run an agent turn via the Gateway (use --local for embedded)")
.option("-m, --message <text>", "Message body for the agent")
.option("--message-file <path>", "Read the agent message body from a UTF-8 file (max 4 MiB)")
.option("-t, --to <number>", "Recipient number in E.164 used to derive the session key")
.option("--session-key <key>", "Explicit session key (agent:<id>:<key>, or scoped to --agent)")
.option("--session-id <id>", "Use an explicit session id")
.option("--agent <id>", "Agent id (overrides routing bindings)")
.option("--model <id>", "Model override for this run (provider/model or model id)")
.option(
"--thinking <level>",
`Thinking level: ${THINKING_LEVELS_HELP.replaceAll("|", " | ")} where supported`,
)
.option("--verbose <on|off>", "Persist agent verbose level for the session")
.option(
"--channel <channel>",
`Delivery channel: ${args.agentChannelOptions} (omit to use the main session 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 using configured provider credentials or local CLI logins",
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 --agent ops --message-file ./task.md", "Read a multiline message file."],
[
'openclaw agent --session-key agent:ops:incident-42 --message "Summarize status"',
"Target an exact session key.",
],
[
'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): Promise<void> => {
const verboseLevel =
typeof opts.verbose === "string" ? normalizeLowercaseStringOrEmpty(opts.verbose) : "";
const [
{ defaultRuntime },
{ runCommandWithRuntime },
{ setVerbose },
{ agentCliCommand },
{ requestExitAfterOneShotOutput },
] = await measureCliCommandStartup("agent-action-imports", () =>
Promise.all([
import("../../runtime.js"),
import("../cli-utils.js"),
import("../../global-state.js"),
import("../../commands/agent-via-gateway.js"),
import("../one-shot-exit.js"),
]),
);
await runCommandWithRuntime(defaultRuntime, async () => {
setVerbose(verboseLevel === "on");
await agentCliCommand(opts, defaultRuntime);
requestExitAfterOneShotOutput(defaultRuntime);
});
});
agent
.command("exec [message]")
.description("Run one isolated headless embedded agent turn")
.option("--message-file <path>", "Read the UTF-8 prompt from a file; use - for stdin")
.option("--cwd <dir>", "Set both the agent workspace and tool working directory")
.option("--state-dir <dir>", "Use an existing state directory without deleting it")
.option(
"--config <path>",
"Run against this config file instead of the ambient config (pins a reproducible run)",
)
.option("--isolated", "Ignore the ambient config and run against exec defaults only", false)
.option("--model <provider/model>", "Use an explicit primary model for this run")
.option("--code-mode <mode>", "Tool mode: direct | auto | code")
.option("--local-model-lean", "Use the reduced local-model tool surface")
.option(
"--thinking <level>",
`Thinking level: ${THINKING_LEVELS_HELP.replaceAll("|", " | ")} where supported`,
)
.option(
"--fallback <provider/model>",
"Add an ordered fallback model (repeatable; requires --model)",
collectFallback,
[],
)
.option("--auth-env-only", "Use provider credentials from environment variables only", false)
.option("--no-auth-env-only", "Allow stored and external CLI credential discovery")
.option("--timeout <seconds>", "Agent deadline in seconds", "600")
.option("--json", "Emit the stable agent-exec JSON envelope", false)
.addHelpText(
"after",
() =>
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
['openclaw agent exec "Fix the failing test"', "Run in the current directory."],
[
"openclaw agent exec --message-file task.md --cwd ./repo",
"Read a prompt file and set the workspace.",
],
[
'openclaw agent exec "Summarize this repo" --model openai/gpt-6-astra --fallback anthropic/claude-sonnet-4-6 --json',
"Use an explicit fallback chain and JSON output.",
],
[
'openclaw agent exec "Inspect this repo" --model ollama/qwen3.5:9b --code-mode code --local-model-lean --json',
"Force Code Mode with the lean local-model tool surface.",
],
])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/agent#agent-exec", "docs.openclaw.ai/cli/agent#agent-exec")}`,
)
.action(async (message: string | undefined, opts, command): Promise<void> => {
const parentOpts = command.parent?.opts() as
| {
messageFile?: string;
model?: string;
thinking?: string;
timeout?: string;
json?: boolean;
}
| undefined;
const execOpts = {
...opts,
messageFile: opts.messageFile ?? parentOpts?.messageFile,
model: opts.model ?? parentOpts?.model,
thinking: opts.thinking ?? parentOpts?.thinking,
// Exec --timeout defaults to "600"; inherit a parent flag only when the
// leaf source is default. An explicit nested --timeout must win.
timeout: inheritOptionFromParent<string>(command, "timeout") ?? opts.timeout,
json: opts.json === true || parentOpts?.json === true,
};
const [
{ defaultRuntime },
{ runCommandWithRuntime },
{ agentExecCommand },
{ requestExitAfterOneShotOutput },
] = await Promise.all([
import("../../runtime.js"),
import("../cli-utils.js"),
import("../../commands/agent-exec.js"),
import("../one-shot-exit.js"),
]);
await runCommandWithRuntime(defaultRuntime, async () => {
const result = await agentExecCommand(message, execOpts, defaultRuntime);
if (result.exitCode !== 0) {
defaultRuntime.exit(result.exitCode, { resetStream: process.stderr });
return;
}
requestExitAfterOneShotOutput(defaultRuntime, result.exitCode);
});
});
}
|