multica/apps/cli/src/commands/run.ts
Jiayuan Zhang f60551195a chore(agent): remove old sessions_spawn/sessions_list tools and update references
Delete sessions-spawn.ts, sessions-list.ts and their tests. Update CLI
to remove waitForSubagents polling workaround (delegate is synchronous).
Update UI, desktop IPC, SWE-bench, and system prompt tests to use the
new delegate tool name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:09:21 +08:00

245 lines
6.7 KiB
TypeScript

/**
* Run command - Execute a single prompt non-interactively
*
* Usage:
* multica run [options] <prompt>
* echo "prompt" | multica run
*/
import { join } from "node:path";
import { Agent, Hub } from "@multica/core";
import type { AgentOptions } from "@multica/core";
import type { ToolsConfig } from "@multica/core";
import { DATA_DIR } from "@multica/utils";
import { cyan, yellow, dim } from "../colors.js";
type RunOptions = {
profile?: string | undefined;
provider?: string | undefined;
model?: string | undefined;
apiKey?: string | undefined;
baseUrl?: string | undefined;
system?: string | undefined;
thinking?: string | undefined;
reasoning?: string | undefined;
cwd?: string | undefined;
session?: string | undefined;
debug?: boolean;
runLog?: boolean;
toolsAllow?: string[];
toolsDeny?: string[];
contextWindow?: number;
help?: boolean;
};
function printHelp() {
console.log(`
${cyan("Usage:")} multica run [options] <prompt>
echo "prompt" | multica run
${cyan("Options:")}
${yellow("--profile")} ID Load agent profile
${yellow("--provider")} NAME LLM provider (openai, anthropic, kimi, etc.)
${yellow("--model")} NAME Model name
${yellow("--api-key")} KEY API key (overrides environment)
${yellow("--base-url")} URL Custom base URL for provider
${yellow("--system")} TEXT System prompt (ignored if --profile set)
${yellow("--thinking")} LEVEL Thinking level
${yellow("--reasoning")} MODE Reasoning display mode (off, on, stream)
${yellow("--cwd")} DIR Working directory
${yellow("--session")} ID Session ID for persistence
${yellow("--debug")} Enable debug logging
${yellow("--run-log")} Enable structured run logging (run-log.jsonl)
${yellow("--context-window")} N Override context window token count
${yellow("--help")}, -h Show this help
${cyan("Tools Configuration:")}
${yellow("--tools-allow")} T Allow specific tools (comma-separated)
${yellow("--tools-deny")} T Deny specific tools (comma-separated)
${cyan("Examples:")}
${dim("# Run with default settings")}
multica run "What is 2+2?"
${dim("# Use a specific profile")}
multica run --profile coder "List files in this directory"
${dim("# Pipe input")}
echo "Explain this code" | multica run
${dim("# Resume a session")}
multica run --session abc123 "Continue from where we left off"
`);
}
function parseArgs(argv: string[]): { opts: RunOptions; prompt: string } {
const args = [...argv];
const opts: RunOptions = {};
const promptParts: string[] = [];
while (args.length > 0) {
const arg = args.shift();
if (!arg) break;
if (arg === "--help" || arg === "-h") {
opts.help = true;
break;
}
if (arg === "--profile") {
opts.profile = args.shift();
continue;
}
if (arg === "--provider") {
opts.provider = args.shift();
continue;
}
if (arg === "--model") {
opts.model = args.shift();
continue;
}
if (arg === "--api-key") {
opts.apiKey = args.shift();
continue;
}
if (arg === "--base-url") {
opts.baseUrl = args.shift();
continue;
}
if (arg === "--system") {
opts.system = args.shift();
continue;
}
if (arg === "--thinking") {
opts.thinking = args.shift();
continue;
}
if (arg === "--reasoning") {
opts.reasoning = args.shift();
continue;
}
if (arg === "--cwd") {
opts.cwd = args.shift();
continue;
}
if (arg === "--session") {
opts.session = args.shift();
continue;
}
if (arg === "--debug") {
opts.debug = true;
continue;
}
if (arg === "--run-log") {
opts.runLog = true;
continue;
}
if (arg === "--tools-allow") {
const value = args.shift();
opts.toolsAllow = value?.split(",").map((s) => s.trim()) ?? [];
continue;
}
if (arg === "--tools-deny") {
const value = args.shift();
opts.toolsDeny = value?.split(",").map((s) => s.trim()) ?? [];
continue;
}
if (arg === "--context-window") {
const value = args.shift();
opts.contextWindow = value ? parseInt(value, 10) : undefined;
continue;
}
if (arg === "--") {
promptParts.push(...args);
break;
}
promptParts.push(arg);
}
return { opts, prompt: promptParts.join(" ") };
}
async function readStdin(): Promise<string> {
if (process.stdin.isTTY) return "";
return new Promise((resolve, reject) => {
let data = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => (data += chunk));
process.stdin.on("end", () => resolve(data.trim()));
process.stdin.on("error", reject);
});
}
export async function runCommand(args: string[]): Promise<void> {
const { opts, prompt } = parseArgs(args);
if (opts.help) {
printHelp();
return;
}
const stdinPrompt = await readStdin();
const finalPrompt = prompt || stdinPrompt;
if (!finalPrompt) {
printHelp();
process.exit(1);
}
// Build tools config if any tools options are set
let toolsConfig: ToolsConfig | undefined;
if (opts.toolsAllow || opts.toolsDeny) {
toolsConfig = {};
if (opts.toolsAllow) {
toolsConfig.allow = opts.toolsAllow;
}
if (opts.toolsDeny) {
toolsConfig.deny = opts.toolsDeny;
}
}
const enableRunLog = opts.runLog || !!process.env.MULTICA_RUN_LOG;
// Initialize Hub to enable full agent capabilities (sub-agents, channels, cron).
// Matches Desktop environment where Hub is always active.
// Gateway connection failures are non-blocking (auto-reconnect with backoff).
const gatewayUrl = process.env.GATEWAY_URL || "http://localhost:3000";
const hub = new Hub(gatewayUrl);
try {
const agent = new Agent({
profileId: opts.profile,
provider: opts.provider,
model: opts.model,
apiKey: opts.apiKey,
baseUrl: opts.baseUrl,
systemPrompt: opts.system,
thinkingLevel: opts.thinking as any,
reasoningMode: opts.reasoning as AgentOptions["reasoningMode"],
cwd: opts.cwd,
sessionId: opts.session,
debug: opts.debug,
enableRunLog,
tools: toolsConfig,
contextWindowTokens: opts.contextWindow,
});
const sessionDir = join(DATA_DIR, "sessions", agent.sessionId);
// If it's a newly created session, notify user of sessionId
if (!opts.session) {
console.error(`[session: ${agent.sessionId}]`);
}
if (enableRunLog) {
console.error(`[session-dir: ${sessionDir}]`);
}
const result = await agent.run(finalPrompt);
if (result.error) {
console.error(`Error: ${result.error}`);
process.exitCode = 1;
}
} finally {
hub.shutdown();
}
}