refactor: split control-plane cli modules
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. resource-actions module for scripts/src/agentrun.ts.
|
||||
|
||||
// Moved mechanically from scripts/src/agentrun.ts:303-686 for #903.
|
||||
|
||||
// SPEC: PJ2026-01020108 cancel lifecycle + PJ2026-01020205 AipodSpec binding + PJ2026-01020302 session policy + PJ2026-01020305 cancel control + PJ2026-01060305/06 YAML execution policy and bounded output draft-2026-06-25-p0.
|
||||
// Exposes AgentRun lane-scoped policy, AipodSpec SecretRef binding, cancel lifecycle, and bounded default output in the UniDesk CLI.
|
||||
import { chmodSync, copyFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { rootPath, type UniDeskConfig } from "../config";
|
||||
import type { RenderedCliResult } from "../output";
|
||||
import { applyLocalCaddyManagedSite } from "../pk01-caddy";
|
||||
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";
|
||||
import { runRemoteSshCommandCapture } from "../remote";
|
||||
import { startJob } from "../jobs";
|
||||
import {
|
||||
AGENTRUN_CONFIG_PATH,
|
||||
agentRunLaneSummary,
|
||||
agentRunPipelineRunName,
|
||||
agentRunProviderCredentialRefs,
|
||||
resolveAgentRunLaneTarget,
|
||||
type AgentRunCancelLifecycleSpec,
|
||||
type AgentRunLaneSpec,
|
||||
} from "../agentrun-lanes";
|
||||
import {
|
||||
agentRunImageArtifact,
|
||||
placeholderAgentRunImage,
|
||||
renderedFilesDigest,
|
||||
renderedObjectsDigest,
|
||||
renderAgentRunControlPlaneManifests,
|
||||
renderAgentRunGitopsFiles,
|
||||
type AgentRunArtifactService,
|
||||
} from "../agentrun-manifests";
|
||||
import { sha256Fingerprint } from "../platform-infra-ops-library";
|
||||
|
||||
import type { AgentRunResourceOptions, AgentRunResourceRef, AgentRunResourceVerb } from "./utils";
|
||||
import { agentRunDryRunPlan } from "./config";
|
||||
import { isHelpArg, renderAgentRunHelp } from "./entry";
|
||||
import { agentRunExplain, arrayRecords, shortId } from "./options";
|
||||
import { agentRunCancelAuthorityDisclosure, agentRunCancelCascadeScope, agentRunCancelFencingDisclosure, agentRunCancelRunnerAbortDisclosure, compactTaskDescriptionPayload, innerData, normalizeAipodSpecItems, normalizeAttemptResources, normalizeEventItems, normalizeLogItems, normalizeRunnerJobItems, normalizeSessionItems, normalizeSingleCommand, normalizeTaskItems, parseResourceKind, parseResourceRef, renderDescribe, renderEventLike, renderGenericDescription, renderMachine, renderMutationSummary, renderResourceResult, renderResultSummary, renderTaskDescription, renderedCliResult, requiredContext, rerunWithoutDryRun, resolveAgentRunCancelPolicyTarget, resourceApply, resourceCreate, resourceDispatch, resourceSessionPromptCommand, taskListState, unwrapTaskDetail } from "./render";
|
||||
import { AgentRunRestError, renderAgentRunRestError, resolveAgentRunRestTarget, runAgentRunRestCommand, withAgentRunRestTarget } from "./rest-bridge";
|
||||
import { nonNegativeIntegerOrNull, record } from "./utils";
|
||||
|
||||
export function agentRunGetKindHelp(kindRaw: string): string {
|
||||
const kind = parseResourceKind(kindRaw);
|
||||
if (kind === "task") return "Usage: bun scripts/cli.ts agentrun get tasks [--queue commander] [--state running,pending,completed,failed] [--unread] [--limit 20] [-o wide|name|json|yaml]";
|
||||
if (kind === "session") return "Usage: bun scripts/cli.ts agentrun get sessions [--limit 20] [-o wide|name|json|yaml]";
|
||||
if (kind === "run") return "Usage: bun scripts/cli.ts agentrun get runs --task <taskId> [--limit 20] [-o wide|name|json|yaml]";
|
||||
if (kind === "command") return "Usage: bun scripts/cli.ts agentrun get commands --run <runId> [--command <commandId>] [-o wide|name|json|yaml]";
|
||||
if (kind === "runnerjob") return "Usage: bun scripts/cli.ts agentrun get runnerjobs --run <runId> --command <commandId> [-o wide|name|json|yaml]";
|
||||
if (kind === "aipodspec") return "Usage: bun scripts/cli.ts agentrun get aipodspecs [-o wide|name|json|yaml]";
|
||||
return "Unknown resource. Supported: tasks, sessions, runs, commands, runnerjobs, aipodspecs.";
|
||||
}
|
||||
|
||||
export async function runAgentRunResourceCommand(config: UniDeskConfig | null, verb: AgentRunResourceVerb, action: string | undefined, actionArgs: string[], canonicalArgs: string[]): Promise<RenderedCliResult> {
|
||||
if (isHelpArg(action) || actionArgs.some(isHelpArg)) return renderAgentRunHelp(canonicalArgs);
|
||||
const resourceArgs = action === undefined ? actionArgs : [action, ...actionArgs];
|
||||
const options = parseResourceOptions(resourceArgs);
|
||||
const bridgeActionArgs = stripAgentRunResourceWrapperArgs(actionArgs);
|
||||
const command = `agentrun ${canonicalArgs.join(" ")}`.trim();
|
||||
try {
|
||||
return await withAgentRunRestTarget(resolveAgentRunRestTarget(config, options), async () => {
|
||||
if (verb === "explain") return renderedCliResult(true, command, agentRunExplain(action ?? "task", resourceArgs, options));
|
||||
if (verb === "get") return await resourceGet(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "describe") return await resourceDescribe(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "events") return await resourceEvents(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "logs") return await resourceLogs(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "result") return await resourceResult(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "ack") return await resourceAck(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "cancel") return await resourceCancel(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "dispatch") return await resourceDispatch(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "create") return await resourceCreate(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "apply") return await resourceApply(config, command, bridgeActionArgs, options);
|
||||
if (verb === "send") return await resourceSessionPromptCommand(config, command, action, bridgeActionArgs, options);
|
||||
return renderedCliResult(false, command, `Unsupported AgentRun resource command. Try: bun scripts/cli.ts agentrun --help`);
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof AgentRunRestError) return renderAgentRunRestError(command, error, options);
|
||||
return renderedCliResult(false, command, `Error: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
return renderedCliResult(false, command, `Unsupported AgentRun resource command. Try: bun scripts/cli.ts agentrun --help`);
|
||||
}
|
||||
|
||||
export function stripAgentRunResourceWrapperArgs(args: string[]): string[] {
|
||||
const result: string[] = [];
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index] ?? "";
|
||||
if (arg === "-o" || arg === "--output") {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--node" || arg === "--lane") {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith("--output=") || arg.startsWith("-o=")) continue;
|
||||
if (arg.startsWith("--node=") || arg.startsWith("--lane=")) continue;
|
||||
if (arg === "--full" || arg === "--raw" || arg === "--debug" || arg === "--full-text") continue;
|
||||
result.push(arg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function parseResourceOptions(args: string[]): AgentRunResourceOptions {
|
||||
const options: AgentRunResourceOptions = {
|
||||
output: "human",
|
||||
full: false,
|
||||
raw: false,
|
||||
debug: false,
|
||||
limit: 20,
|
||||
queue: null,
|
||||
state: null,
|
||||
unread: false,
|
||||
readerId: "cli",
|
||||
taskId: null,
|
||||
runId: null,
|
||||
commandId: null,
|
||||
sessionId: null,
|
||||
afterSeq: null,
|
||||
tail: null,
|
||||
fullText: false,
|
||||
reason: null,
|
||||
dryRun: false,
|
||||
file: null,
|
||||
aipod: null,
|
||||
idempotencyKey: null,
|
||||
promptStdin: false,
|
||||
node: null,
|
||||
lane: null,
|
||||
passthroughArgs: [],
|
||||
};
|
||||
const valueFlags = new Set(["-o", "--output", "--limit", "--queue", "--state", "--reader-id", "--task", "--task-id", "--run", "--run-id", "--command", "--command-id", "--session", "--session-id", "--after-seq", "--tail", "--reason", "-f", "--file", "--filename", "--aipod", "--idempotency-key", "--node", "--lane"]);
|
||||
const booleanFlags = new Set(["--full", "--raw", "--debug", "--unread", "--dry-run", "--full-text", "--prompt-stdin", "--stdin"]);
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index] ?? "";
|
||||
if (!arg.startsWith("-")) continue;
|
||||
if (arg.startsWith("--") && arg.includes("=")) {
|
||||
const [flag, ...rest] = arg.split("=");
|
||||
applyResourceOption(options, flag, rest.join("="));
|
||||
continue;
|
||||
}
|
||||
if (booleanFlags.has(arg)) {
|
||||
applyResourceOption(options, arg, null);
|
||||
continue;
|
||||
}
|
||||
if (valueFlags.has(arg)) {
|
||||
const value = args[index + 1];
|
||||
if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`);
|
||||
applyResourceOption(options, arg, value);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
export function applyResourceOption(options: AgentRunResourceOptions, flag: string, value: string | null): void {
|
||||
if (flag === "-o" || flag === "--output") {
|
||||
if (value !== "human" && value !== "wide" && value !== "name" && value !== "json" && value !== "yaml") throw new Error(`${flag} must be one of human,wide,name,json,yaml`);
|
||||
options.output = value;
|
||||
return;
|
||||
}
|
||||
if (flag === "--full") options.full = true;
|
||||
else if (flag === "--raw") options.raw = true;
|
||||
else if (flag === "--debug") options.debug = true;
|
||||
else if (flag === "--unread") options.unread = true;
|
||||
else if (flag === "--dry-run") options.dryRun = true;
|
||||
else if (flag === "--full-text") options.fullText = true;
|
||||
else if (flag === "--prompt-stdin" || flag === "--stdin") options.promptStdin = true;
|
||||
else if (flag === "--limit") options.limit = parseNonNegativeInt(value, "--limit", 20, 500);
|
||||
else if (flag === "--queue") options.queue = requiredValue(value, flag);
|
||||
else if (flag === "--state") options.state = requiredValue(value, flag);
|
||||
else if (flag === "--reader-id") options.readerId = requiredValue(value, flag);
|
||||
else if (flag === "--task" || flag === "--task-id") options.taskId = requiredValue(value, flag);
|
||||
else if (flag === "--run" || flag === "--run-id") options.runId = requiredValue(value, flag);
|
||||
else if (flag === "--command" || flag === "--command-id") options.commandId = requiredValue(value, flag);
|
||||
else if (flag === "--session" || flag === "--session-id") options.sessionId = requiredValue(value, flag);
|
||||
else if (flag === "--after-seq") options.afterSeq = parseNonNegativeInt(value, "--after-seq", 0, Number.MAX_SAFE_INTEGER);
|
||||
else if (flag === "--tail") options.tail = parseNonNegativeInt(value, "--tail", 100, 1000);
|
||||
else if (flag === "--reason") options.reason = requiredValue(value, flag);
|
||||
else if (flag === "-f" || flag === "--file" || flag === "--filename") options.file = requiredValue(value, flag);
|
||||
else if (flag === "--aipod") options.aipod = requiredValue(value, flag);
|
||||
else if (flag === "--idempotency-key") options.idempotencyKey = requiredValue(value, flag);
|
||||
else if (flag === "--node") options.node = requiredValue(value, flag);
|
||||
else if (flag === "--lane") options.lane = requiredValue(value, flag);
|
||||
}
|
||||
|
||||
export function parseNonNegativeInt(raw: string | null, flag: string, defaultValue: number, maxValue: number): number {
|
||||
if (raw === null || raw.length === 0) return defaultValue;
|
||||
const value = Number(raw);
|
||||
if (!Number.isInteger(value) || value < 0) throw new Error(`${flag} must be a non-negative integer`);
|
||||
return Math.min(value, maxValue);
|
||||
}
|
||||
|
||||
export function requiredValue(value: string | null, flag: string): string {
|
||||
if (value === null || value.length === 0) throw new Error(`${flag} requires a value`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export async function resourceGet(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const kind = parseResourceKind(action);
|
||||
if (kind === null) throw new Error("get requires a resource: tasks|sessions|runs|commands|runnerjobs|aipodspecs");
|
||||
let result: Record<string, unknown>;
|
||||
if (kind === "task") {
|
||||
const taskListArgs = options.unread
|
||||
? ["commander", "--reader-id", options.readerId, "--limit", String(options.limit)]
|
||||
: [
|
||||
"list",
|
||||
"--state",
|
||||
taskListState(options),
|
||||
"--limit",
|
||||
String(options.queue === null ? options.limit : Math.max(options.limit, 100)),
|
||||
...(options.queue === null ? [] : ["--queue", options.queue]),
|
||||
];
|
||||
result = await runAgentRunRestCommand(config, "queue", taskListArgs);
|
||||
return renderResourceResult(command, result, options, "Task", normalizeTaskItems(innerData(result), options).slice(0, options.limit));
|
||||
}
|
||||
if (kind === "session") {
|
||||
result = await runAgentRunRestCommand(config, "sessions", ["ps", "--limit", String(options.limit)]);
|
||||
return renderResourceResult(command, result, options, "Session", normalizeSessionItems(innerData(result)).slice(0, options.limit));
|
||||
}
|
||||
if (kind === "aipodspec") {
|
||||
result = await runAgentRunRestCommand(config, "aipod-specs", ["list"]);
|
||||
return renderResourceResult(command, result, options, "AipodSpec", normalizeAipodSpecItems(innerData(result)).slice(0, options.limit));
|
||||
}
|
||||
if (kind === "run" && options.taskId !== null) {
|
||||
result = await runAgentRunRestCommand(config, "queue", ["show", options.taskId]);
|
||||
const task = record(innerData(result));
|
||||
return renderResourceResult(command, result, options, "Run", normalizeAttemptResources(task, "run"));
|
||||
}
|
||||
if (kind === "command" && options.runId !== null && options.commandId !== null) {
|
||||
result = await runAgentRunRestCommand(config, "commands", ["show", options.commandId, "--run-id", options.runId]);
|
||||
return renderResourceResult(command, result, options, "Command", normalizeSingleCommand(innerData(result)));
|
||||
}
|
||||
if (kind === "runnerjob" && options.runId !== null && options.commandId !== null) {
|
||||
result = await runAgentRunRestCommand(config, "runner", ["jobs", "--run-id", options.runId, "--command-id", options.commandId]);
|
||||
return renderResourceResult(command, result, options, "RunnerJob", normalizeRunnerJobItems(innerData(result)));
|
||||
}
|
||||
throw new Error(`get ${kind}s requires more context; use --task, --run/--command, or describe <kind>/<id>`);
|
||||
}
|
||||
|
||||
export async function resourceDescribe(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const ref = parseResourceRef(action, args);
|
||||
if (ref.kind === "task") {
|
||||
const result = await runAgentRunRestCommand(config, "queue", ["show", ref.name, ...(options.full ? ["--full"] : [])]);
|
||||
const data = record(innerData(result));
|
||||
const task = unwrapTaskDetail(data);
|
||||
if (options.raw) return renderMachine(command, result, "json", result.ok !== false);
|
||||
if (options.output === "json" || options.output === "yaml") {
|
||||
const payload = options.full ? { kind: ref.kind, name: ref.name, resource: task } : compactTaskDescriptionPayload(ref, task);
|
||||
return renderMachine(command, payload, options.output, result.ok !== false);
|
||||
}
|
||||
return renderedCliResult(result.ok !== false, command, renderTaskDescription(task, options));
|
||||
}
|
||||
if (ref.kind === "run") {
|
||||
const result = await runAgentRunRestCommand(config, "runs", ["show", ref.name, ...(options.full ? ["--full"] : [])]);
|
||||
return renderDescribe(command, result, options, ref, renderGenericDescription(ref, innerData(result)));
|
||||
}
|
||||
if (ref.kind === "command") {
|
||||
const runId = options.runId ?? requiredContext("command describe", "--run <runId>");
|
||||
const result = await runAgentRunRestCommand(config, "commands", ["show", ref.name, "--run-id", runId, ...(options.full ? ["--full"] : [])]);
|
||||
return renderDescribe(command, result, options, ref, renderGenericDescription(ref, innerData(result)));
|
||||
}
|
||||
if (ref.kind === "runnerjob") {
|
||||
const runId = options.runId ?? requiredContext("runnerjob describe", "--run <runId>");
|
||||
const result = await runAgentRunRestCommand(config, "runner", ["job-status", ref.name, "--run-id", runId]);
|
||||
return renderDescribe(command, result, options, ref, renderGenericDescription(ref, innerData(result)));
|
||||
}
|
||||
if (ref.kind === "session") {
|
||||
const result = await runAgentRunRestCommand(config, "sessions", ["show", ref.name, ...(options.full ? ["--full"] : [])]);
|
||||
return renderDescribe(command, result, options, ref, renderGenericDescription(ref, innerData(result)));
|
||||
}
|
||||
if (ref.kind === "aipodspec") {
|
||||
const result = await runAgentRunRestCommand(config, "aipod-specs", ["show", ref.name, ...(options.full ? ["--full"] : [])]);
|
||||
return renderDescribe(command, result, options, ref, renderGenericDescription(ref, innerData(result)));
|
||||
}
|
||||
throw new Error(`unsupported describe kind: ${ref.kind}`);
|
||||
}
|
||||
|
||||
export async function resourceEvents(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const ref = parseResourceRef(action, args, "run");
|
||||
const runId = ref.kind === "run" ? ref.name : options.runId ?? requiredContext("events", "--run <runId>");
|
||||
const eventArgs = ["events", runId, "--after-seq", String(options.afterSeq ?? 0), "--limit", String(options.limit), "--tail-summary"];
|
||||
const result = await runAgentRunRestCommand(config, "runs", eventArgs);
|
||||
return renderEventLike(command, result, options, "Event", normalizeEventItems(innerData(result)), runId);
|
||||
}
|
||||
|
||||
export async function resourceLogs(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const ref = parseResourceRef(action, args, "session");
|
||||
if (ref.kind !== "session") throw new Error("logs currently requires session/<sessionId>");
|
||||
const effectiveLimit = options.tail ?? options.limit;
|
||||
const logArgs = ["output", ref.name, "--limit", String(effectiveLimit)];
|
||||
if (options.afterSeq !== null) logArgs.push("--after-seq", String(options.afterSeq));
|
||||
if (options.fullText) logArgs.push("--full-text");
|
||||
let result: Record<string, unknown>;
|
||||
if (options.afterSeq === null && options.tail !== null) {
|
||||
result = await resourceLogsTailResult(config, ref.name, effectiveLimit, options.fullText);
|
||||
} else {
|
||||
result = await runAgentRunRestCommand(config, "sessions", logArgs);
|
||||
}
|
||||
return renderEventLike(command, result, { ...options, limit: effectiveLimit }, "Log", normalizeLogItems(innerData(result)), ref.name);
|
||||
}
|
||||
|
||||
export async function resourceLogsTailResult(config: UniDeskConfig | null, sessionId: string, limit: number, fullText: boolean): Promise<Record<string, unknown>> {
|
||||
const session = record(innerData(await runAgentRunRestCommand(config, "sessions", ["show", sessionId])));
|
||||
const lastSeq = nonNegativeIntegerOrNull(session.lastEventSeq) ?? 0;
|
||||
let window = Math.max(limit * 4, 100);
|
||||
let result: Record<string, unknown> | null = null;
|
||||
let data: Record<string, unknown> = {};
|
||||
for (let attempt = 0; attempt < 6; attempt += 1) {
|
||||
const afterSeq = Math.max(0, lastSeq - window);
|
||||
const queryLimit = Math.max(limit, Math.min(1000, window));
|
||||
const args = ["output", sessionId, "--limit", String(queryLimit), "--after-seq", String(afterSeq)];
|
||||
if (fullText) args.push("--full-text");
|
||||
result = await runAgentRunRestCommand(config, "sessions", args);
|
||||
data = record(innerData(result));
|
||||
const items = arrayRecords(data.items);
|
||||
if (items.length >= limit || afterSeq === 0) break;
|
||||
window *= 2;
|
||||
}
|
||||
if (result === null) return await runAgentRunRestCommand(config, "sessions", ["output", sessionId, "--limit", String(limit), "--after-seq", "0"]);
|
||||
const items = arrayRecords(data.items);
|
||||
const tailedItems = items.slice(-limit);
|
||||
const lastReturnedSeq = tailedItems.length > 0 ? nonNegativeIntegerOrNull(tailedItems[tailedItems.length - 1]?.seq) : null;
|
||||
return {
|
||||
...result,
|
||||
data: {
|
||||
...data,
|
||||
items: tailedItems,
|
||||
count: tailedItems.length,
|
||||
cursor: lastReturnedSeq === null ? data.cursor : String(lastReturnedSeq),
|
||||
clientTail: {
|
||||
requested: limit,
|
||||
sourceLastEventSeq: lastSeq,
|
||||
scanned: items.length,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function resourceResult(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const ref = parseResourceRef(action, args);
|
||||
if (ref.kind === "run") {
|
||||
const commandId = options.commandId ?? requiredContext("run result", "--command <commandId>");
|
||||
const result = await runAgentRunRestCommand(config, "runs", ["result", ref.name, "--command-id", commandId]);
|
||||
return renderResultSummary(command, result, options, ref);
|
||||
}
|
||||
if (ref.kind === "command") {
|
||||
const runId = options.runId ?? requiredContext("command result", "--run <runId>");
|
||||
const result = await runAgentRunRestCommand(config, "commands", ["result", ref.name, "--run-id", runId]);
|
||||
return renderResultSummary(command, result, options, ref);
|
||||
}
|
||||
throw new Error("result supports run/<runId> or command/<commandId>");
|
||||
}
|
||||
|
||||
export async function resourceAck(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const ref = parseResourceRef(action, args, "task");
|
||||
const result = ref.kind === "task"
|
||||
? await runAgentRunRestCommand(config, "queue", ["read", ref.name, "--reader-id", options.readerId])
|
||||
: ref.kind === "session"
|
||||
? await runAgentRunRestCommand(config, "sessions", ["read", ref.name, "--reader-id", options.readerId])
|
||||
: null;
|
||||
if (result === null) throw new Error("ack supports task/<taskId> or session/<sessionId>");
|
||||
return renderMutationSummary(command, result, options, `Acknowledged ${ref.kind}/${shortId(ref.name)}`);
|
||||
}
|
||||
|
||||
export async function resourceCancel(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const ref = parseResourceRef(action, args, "task");
|
||||
const cancelArgs = ["cancel", ref.name];
|
||||
if (options.reason !== null) cancelArgs.push("--reason", options.reason);
|
||||
if (ref.kind === "command") cancelArgs.push("--run-id", options.runId ?? requiredContext("command cancel", "--run <runId>"));
|
||||
if (options.dryRun) {
|
||||
const result = agentRunResourceCancelDryRunPlan(config, ref, options, rerunWithoutDryRun(command));
|
||||
return renderMutationSummary(command, result, options, `Planned cancel ${ref.kind}/${shortId(ref.name)}`, [rerunWithoutDryRun(command)]);
|
||||
}
|
||||
const result = ref.kind === "task"
|
||||
? await runAgentRunRestCommand(config, "queue", cancelArgs)
|
||||
: ref.kind === "session"
|
||||
? await runAgentRunRestCommand(config, "sessions", cancelArgs)
|
||||
: ref.kind === "run"
|
||||
? await runAgentRunRestCommand(config, "runs", cancelArgs)
|
||||
: ref.kind === "command"
|
||||
? await runAgentRunRestCommand(config, "commands", cancelArgs)
|
||||
: null;
|
||||
if (result === null) throw new Error("cancel supports task/<taskId>, session/<sessionId>, run/<runId>, or command/<commandId>");
|
||||
return renderMutationSummary(command, result, options, `${options.dryRun ? "Planned cancel" : "Cancel requested"} ${ref.kind}/${shortId(ref.name)}`, options.dryRun ? [rerunWithoutDryRun(command)] : undefined);
|
||||
}
|
||||
|
||||
export function agentRunResourceCancelDryRunPlan(config: UniDeskConfig | null, ref: AgentRunResourceRef, options: AgentRunResourceOptions, confirmCommand: string): Record<string, unknown> {
|
||||
const body: Record<string, unknown> = {};
|
||||
if (options.reason !== null) body.reason = options.reason;
|
||||
const cancelLifecycle = agentRunCancelLifecycleDryRunDisclosure(config, ref, options);
|
||||
if (ref.kind === "task") return agentRunDryRunPlan("task-cancel", `/api/v1/queue/tasks/${encodeURIComponent(ref.name)}/cancel`, body, confirmCommand, "POST", { cancelLifecycle });
|
||||
if (ref.kind === "session") return agentRunDryRunPlan("session-cancel", `/api/v1/sessions/${encodeURIComponent(ref.name)}/control`, { action: "cancel", ...body }, confirmCommand, "POST", { cancelLifecycle });
|
||||
if (ref.kind === "run") return agentRunDryRunPlan("run-cancel", `/api/v1/runs/${encodeURIComponent(ref.name)}/cancel`, body, confirmCommand, "POST", { cancelLifecycle });
|
||||
if (ref.kind === "command") {
|
||||
const runId = options.runId ?? requiredContext("command cancel", "--run <runId>");
|
||||
return agentRunDryRunPlan("command-cancel", `/api/v1/commands/${encodeURIComponent(ref.name)}/cancel`, body, confirmCommand, "POST", {
|
||||
commandRef: { runId, commandId: ref.name, valuesPrinted: false },
|
||||
cancelLifecycle,
|
||||
});
|
||||
}
|
||||
throw new Error("cancel supports task/<taskId>, session/<sessionId>, run/<runId>, or command/<commandId>");
|
||||
}
|
||||
|
||||
export function agentRunCancelLifecycleDryRunDisclosure(config: UniDeskConfig | null, ref: AgentRunResourceRef, options: AgentRunResourceOptions): Record<string, unknown> {
|
||||
const target = resolveAgentRunCancelPolicyTarget(config, options);
|
||||
const policy = target?.spec.deployment.runner.cancelLifecycle ?? null;
|
||||
return {
|
||||
specRefs: ["PJ2026-01020108", "PJ2026-01020305", "PJ2026-01060305"],
|
||||
authority: agentRunCancelAuthorityDisclosure(target),
|
||||
targetRef: {
|
||||
kind: ref.kind,
|
||||
name: ref.name,
|
||||
runId: ref.kind === "command" ? options.runId : options.runId ?? null,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
cascadeScope: agentRunCancelCascadeScope(ref.kind),
|
||||
terminalAuthority: "AgentRun Core canceled terminal/result event",
|
||||
expectedStages: policy?.eventStages ?? [],
|
||||
runnerAbort: policy === null ? null : agentRunCancelRunnerAbortDisclosure(policy),
|
||||
fencing: agentRunCancelFencingDisclosure(policy),
|
||||
verification: {
|
||||
describe: `bun scripts/cli.ts agentrun describe ${ref.kind}/${ref.name}`,
|
||||
events: ref.kind === "run" || options.runId !== null ? `bun scripts/cli.ts agentrun events run/${ref.kind === "run" ? ref.name : options.runId} --after-seq 0` : null,
|
||||
logs: ref.kind === "session" ? `bun scripts/cli.ts agentrun logs session/${ref.name} --tail 100` : null,
|
||||
result: ref.kind === "command" ? `bun scripts/cli.ts agentrun result command/${ref.name} --run ${options.runId ?? "<runId>"}` : null,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user