330 lines
18 KiB
TypeScript
330 lines
18 KiB
TypeScript
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. entry module for scripts/src/agentrun.ts.
|
|
|
|
// Moved mechanically from scripts/src/agentrun.ts:1-302 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 { AgentRunResourceVerb, AgentRunRestCompatGroup } from "./utils";
|
|
import { controlPlaneApply, controlPlanePlan, parseCleanupReleasedPvOptions, parseCleanupRunnersOptions, parseCleanupRunsOptions, parseConfirmOptions, parseGitMirrorOptions, parseLaneConfirmOptions, parseRefreshOptions, parseSecretSyncOptions, status } from "./control-plane";
|
|
import { gitMirrorStatus } from "./git-mirror";
|
|
import { agentRunExplain, isRecord, parseGitMirrorStatusOptions, parseStatusOptions, parseTriggerOptions } from "./options";
|
|
import { renderAgentRunControlPlaneActionSummary, renderAgentRunControlPlanePlanSummary, renderAgentRunControlPlaneStatusSummary } from "./public-exposure";
|
|
import { renderedCliResult } from "./render";
|
|
import { agentRunGetKindHelp, runAgentRunResourceCommand } from "./resource-actions";
|
|
import { runAgentRunRestCompatCommand, runGitMirrorJob, startAsyncAgentRunJob } from "./rest-bridge";
|
|
import { exposeAgentRun, restartYamlLane, secretSync, triggerCurrent } from "./trigger";
|
|
import { unsupported } from "./utils";
|
|
import { cleanupReleasedPvs, cleanupRunners, cleanupRuns, refresh } from "./yaml-lane";
|
|
|
|
export function agentRunHelp(): unknown {
|
|
return {
|
|
command: "agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|send|explain",
|
|
output: "human by default; use -o json|yaml or --raw for machine/debug output",
|
|
usage: [
|
|
"bun scripts/cli.ts agentrun get tasks --queue commander --limit 20",
|
|
"bun scripts/cli.ts agentrun describe run/<runId> --node D601 --lane v02",
|
|
"bun scripts/cli.ts agentrun get tasks -o wide",
|
|
"bun scripts/cli.ts agentrun get tasks -o json",
|
|
"bun scripts/cli.ts agentrun describe task/<taskId>",
|
|
"bun scripts/cli.ts agentrun events run/<runId> --after-seq 0 --limit 100",
|
|
"bun scripts/cli.ts agentrun logs session/<sessionId> --tail 100",
|
|
"bun scripts/cli.ts agentrun result run/<runId> --command <commandId>",
|
|
"bun scripts/cli.ts agentrun ack task/<taskId> --reader-id cli",
|
|
"bun scripts/cli.ts agentrun cancel task/<taskId> --reason <text> --dry-run",
|
|
"bun scripts/cli.ts agentrun dispatch task/<taskId>",
|
|
"bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin --idempotency-key <key>",
|
|
"bun scripts/cli.ts agentrun apply -f - --dry-run",
|
|
"bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin",
|
|
"bun scripts/cli.ts agentrun explain task",
|
|
"bun scripts/cli.ts agentrun explain session-policy",
|
|
"bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02",
|
|
"bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 --dry-run",
|
|
"bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02",
|
|
"bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 --dry-run",
|
|
"bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 --confirm",
|
|
"bun scripts/cli.ts agentrun control-plane restart --node D601 --lane v02 --dry-run",
|
|
"bun scripts/cli.ts agentrun control-plane restart --node D601 --lane v02 --confirm",
|
|
"bun scripts/cli.ts agentrun control-plane trigger-current --node D601 --lane v02 --dry-run",
|
|
"bun scripts/cli.ts agentrun control-plane trigger-current --node D601 --lane v02 --confirm",
|
|
"bun scripts/cli.ts agentrun control-plane status",
|
|
"bun scripts/cli.ts agentrun control-plane status --full",
|
|
"bun scripts/cli.ts agentrun control-plane status --pipeline-run <yaml-lane-pipelinerun>",
|
|
"bun scripts/cli.ts agentrun control-plane status --source-commit <full-sha>",
|
|
"bun scripts/cli.ts agentrun control-plane expose --dry-run",
|
|
"bun scripts/cli.ts agentrun control-plane expose --confirm",
|
|
"bun scripts/cli.ts agentrun control-plane trigger-current --dry-run",
|
|
"bun scripts/cli.ts agentrun control-plane trigger-current --confirm",
|
|
"bun scripts/cli.ts agentrun control-plane refresh --dry-run",
|
|
"bun scripts/cli.ts agentrun control-plane refresh --confirm",
|
|
"bun scripts/cli.ts agentrun control-plane cleanup-runners --node D601 --lane v02 --dry-run",
|
|
"bun scripts/cli.ts agentrun control-plane cleanup-runners --node D601 --lane v02 --confirm",
|
|
"bun scripts/cli.ts agentrun control-plane cleanup-runs --min-age-minutes 30 --limit 200 --dry-run",
|
|
"bun scripts/cli.ts agentrun control-plane cleanup-runs --min-age-minutes 30 --limit 200 --confirm",
|
|
"bun scripts/cli.ts agentrun control-plane cleanup-released-pvs --limit 200 --dry-run",
|
|
"bun scripts/cli.ts agentrun control-plane cleanup-released-pvs --limit 200 --confirm",
|
|
"bun scripts/cli.ts agentrun git-mirror status",
|
|
"bun scripts/cli.ts agentrun git-mirror status --full",
|
|
"bun scripts/cli.ts agentrun git-mirror sync --confirm",
|
|
"bun scripts/cli.ts agentrun git-mirror flush --confirm",
|
|
],
|
|
resources: ["task/qt", "run", "command/cmd", "runnerjob/rjob", "session/ses", "aipodspec/aps"],
|
|
description: "Operate AgentRun through Kubernetes-style resource verbs. Human output is compact by default; -o json|yaml returns the UniDesk render-only client schema, --raw exposes the REST envelope, and --node/--lane targets a YAML-declared runtime lane.",
|
|
legacyCompatibility: "queue/runs/commands/runner/sessions/aipod-specs remain as compatibility groups backed by direct HTTP; new commander work should use get/describe/events/logs/result/ack/cancel/dispatch/create/apply/send. sessions turn/steer are removed; use send only.",
|
|
};
|
|
}
|
|
|
|
export async function runAgentRunCommand(config: UniDeskConfig | null, args: string[]): Promise<Record<string, unknown> | RenderedCliResult> {
|
|
const route = normalizeAgentRunCommandArgs(args);
|
|
const { group, action, actionArgs } = route;
|
|
if (group === undefined || isHelpArg(group)) return renderAgentRunHelp(route.canonicalArgs);
|
|
if (isHelpArg(action) || actionArgs.some(isHelpArg)) return renderAgentRunHelp(route.canonicalArgs);
|
|
if (isResourceVerb(group)) return await runAgentRunResourceCommand(config, group, action, actionArgs, route.canonicalArgs);
|
|
if (isAgentRunRestCompatGroup(group)) {
|
|
return await runAgentRunRestCompatCommand(config, group, route.forwardArgs, route.canonicalArgs);
|
|
}
|
|
if (config === null) throw new Error("agentrun control-plane and git-mirror commands require UniDesk config");
|
|
if (group === "control-plane") {
|
|
if (action === "plan") {
|
|
const options = parseStatusOptions(actionArgs);
|
|
const result = await controlPlanePlan(config, options);
|
|
return options.full || options.raw ? result : renderAgentRunControlPlanePlanSummary(result);
|
|
}
|
|
if (action === "apply") return await controlPlaneApply(config, parseLaneConfirmOptions(actionArgs));
|
|
if (action === "status") {
|
|
const options = parseStatusOptions(actionArgs);
|
|
const result = await status(config, options);
|
|
return options.full || options.raw ? result : renderAgentRunControlPlaneStatusSummary(result);
|
|
}
|
|
if (action === "secret-sync") return await secretSync(config, parseSecretSyncOptions(actionArgs));
|
|
if (action === "restart") return await restartYamlLane(config, parseLaneConfirmOptions(actionArgs));
|
|
if (action === "expose") return await exposeAgentRun(config, parseConfirmOptions(actionArgs));
|
|
if (action === "trigger-current") {
|
|
const options = parseTriggerOptions(actionArgs);
|
|
const result = await triggerCurrent(config, options);
|
|
return options.full || options.raw || !isRecord(result.target) ? result : renderAgentRunControlPlaneActionSummary(result, "AGENTRUN TRIGGER CURRENT");
|
|
}
|
|
if (action === "refresh") {
|
|
const options = parseRefreshOptions(actionArgs);
|
|
const result = await refresh(config, options);
|
|
return options.full || options.raw ? result : renderAgentRunControlPlaneActionSummary(result, "AGENTRUN CONTROL-PLANE REFRESH");
|
|
}
|
|
if (action === "cleanup-runners") {
|
|
const options = parseCleanupRunnersOptions(actionArgs);
|
|
const result = await cleanupRunners(config, options);
|
|
return options.full || options.raw ? result : renderAgentRunControlPlaneActionSummary(result, "AGENTRUN RUNNER CLEANUP");
|
|
}
|
|
if (action === "cleanup-runs") return await cleanupRuns(config, parseCleanupRunsOptions(actionArgs));
|
|
if (action === "cleanup-released-pvs") return await cleanupReleasedPvs(config, parseCleanupReleasedPvOptions(actionArgs));
|
|
}
|
|
if (group === "git-mirror") {
|
|
if (action === "status") return await gitMirrorStatus(config, parseGitMirrorStatusOptions(actionArgs));
|
|
if (action === "sync" || action === "flush") {
|
|
const options = parseGitMirrorOptions(actionArgs);
|
|
const { spec } = resolveAgentRunLaneTarget(options);
|
|
if (options.confirm && !options.wait) {
|
|
return startAsyncAgentRunJob(
|
|
`agentrun_${spec.lane}_git_mirror_${action}`,
|
|
["bun", "scripts/cli.ts", "agentrun", "git-mirror", action, "--node", spec.nodeId, "--lane", spec.lane, "--confirm", "--wait", "--timeout-seconds", String(options.timeoutSeconds)],
|
|
`Run AgentRun ${spec.version} git mirror ${action} on ${spec.nodeId}`,
|
|
);
|
|
}
|
|
return await runGitMirrorJob(config, action, options);
|
|
}
|
|
}
|
|
return unsupported(route.canonicalArgs);
|
|
}
|
|
|
|
export function isAgentRunRestCompatGroup(group: string | undefined): group is AgentRunRestCompatGroup {
|
|
return group === "queue" || group === "sessions" || group === "aipod-specs" || group === "aipods" || group === "runs" || group === "commands" || group === "runner";
|
|
}
|
|
|
|
export interface AgentRunCommandRoute {
|
|
canonicalArgs: string[];
|
|
group: string | undefined;
|
|
action: string | undefined;
|
|
actionArgs: string[];
|
|
forwardArgs: string[];
|
|
}
|
|
|
|
export function normalizeAgentRunCommandArgs(args: string[]): AgentRunCommandRoute {
|
|
const canonicalArgs = args[0] === "v01" ? args.slice(1) : args;
|
|
const [group, action, ...actionArgs] = canonicalArgs;
|
|
return {
|
|
canonicalArgs,
|
|
group,
|
|
action,
|
|
actionArgs,
|
|
forwardArgs: canonicalArgs.slice(1),
|
|
};
|
|
}
|
|
|
|
export function isHelpArg(value: string | undefined): boolean {
|
|
return value === "help" || value === "--help" || value === "-h";
|
|
}
|
|
|
|
export function isResourceVerb(value: string | undefined): value is AgentRunResourceVerb {
|
|
return value === "get"
|
|
|| value === "describe"
|
|
|| value === "events"
|
|
|| value === "logs"
|
|
|| value === "result"
|
|
|| value === "ack"
|
|
|| value === "cancel"
|
|
|| value === "dispatch"
|
|
|| value === "create"
|
|
|| value === "apply"
|
|
|| value === "send"
|
|
|| value === "explain";
|
|
}
|
|
|
|
export function renderAgentRunHelp(args: string[]): RenderedCliResult {
|
|
const text = agentRunHelpText(args);
|
|
return renderedCliResult(true, "agentrun help", text);
|
|
}
|
|
|
|
export function agentRunHelpText(args: string[]): string {
|
|
const [verb, kind] = args.filter((arg) => !isHelpArg(arg));
|
|
if (verb === "get" && kind !== undefined) return agentRunGetKindHelp(kind);
|
|
if (verb === "get") {
|
|
return [
|
|
"Usage: bun scripts/cli.ts agentrun get <resource> [options]",
|
|
"",
|
|
"Resources: tasks|sessions|runs|commands|runnerjobs|aipodspecs",
|
|
"Output: table by default; use -o wide|name|json|yaml.",
|
|
"",
|
|
"Examples:",
|
|
" bun scripts/cli.ts agentrun get tasks --queue commander --limit 20",
|
|
" bun scripts/cli.ts agentrun get sessions --limit 20",
|
|
" bun scripts/cli.ts agentrun get tasks -o json",
|
|
].join("\n");
|
|
}
|
|
if (verb === "describe") {
|
|
return [
|
|
"Usage: bun scripts/cli.ts agentrun describe <kind/name> [--node <node> --lane <lane>] [--full] [-o json|yaml] [--raw]",
|
|
"",
|
|
"Kinds: task|run|command|runnerjob|session|aipodspec",
|
|
"Examples:",
|
|
" bun scripts/cli.ts agentrun describe task/qt_...",
|
|
" bun scripts/cli.ts agentrun describe run/<runId> --node D601 --lane v02",
|
|
" bun scripts/cli.ts agentrun describe command/cmd_... --run <runId>",
|
|
" bun scripts/cli.ts agentrun describe session/<sessionId> --full",
|
|
].join("\n");
|
|
}
|
|
if (verb === "events") {
|
|
return "Usage: bun scripts/cli.ts agentrun events run/<runId> [--after-seq N] [--limit 100] [-o json|yaml] [--raw]";
|
|
}
|
|
if (verb === "logs") {
|
|
return "Usage: bun scripts/cli.ts agentrun logs session/<sessionId> [--tail 100|--after-seq N] [--limit 100] [--full-text] [-o json|yaml] [--raw]";
|
|
}
|
|
if (verb === "result") {
|
|
return "Usage: bun scripts/cli.ts agentrun result run/<runId> --command <commandId> | agentrun result command/<commandId> --run <runId>";
|
|
}
|
|
if (verb === "ack") {
|
|
return "Usage: bun scripts/cli.ts agentrun ack task/<taskId>|session/<sessionId> [--reader-id cli]";
|
|
}
|
|
if (verb === "cancel") {
|
|
return "Usage: bun scripts/cli.ts agentrun cancel task/<taskId>|session/<sessionId>|run/<runId>|command/<commandId> --reason <text> [--run <runId>] [--dry-run]";
|
|
}
|
|
if (verb === "dispatch") {
|
|
return "Usage: bun scripts/cli.ts agentrun dispatch task/<taskId>";
|
|
}
|
|
if (verb === "create") {
|
|
return "Usage: bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin [--idempotency-key <key>] [--dry-run]";
|
|
}
|
|
if (verb === "apply") {
|
|
return "Usage: bun scripts/cli.ts agentrun apply -f task.yaml|json|- [--dry-run]\nTask manifests use kind: Task and spec: <AgentRun task payload>.";
|
|
}
|
|
if (verb === "send") {
|
|
return "Usage: bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin\nThe server decides whether this becomes an internal steer or a new turn from session state.";
|
|
}
|
|
if (verb === "explain") return agentRunExplain(kind ?? "task");
|
|
if (verb === "control-plane") {
|
|
return [
|
|
"Usage: bun scripts/cli.ts agentrun control-plane <action> [options]",
|
|
"",
|
|
"Actions: plan, apply, status, secret-sync, expose, trigger-current, refresh, cleanup-runners, cleanup-runs, cleanup-released-pvs",
|
|
"Examples:",
|
|
" bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02",
|
|
" bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 --dry-run",
|
|
" bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02",
|
|
" bun scripts/cli.ts agentrun control-plane secret-sync --node D601 --lane v02 --dry-run",
|
|
" bun scripts/cli.ts agentrun control-plane trigger-current --node D601 --lane v02 --dry-run",
|
|
" bun scripts/cli.ts agentrun control-plane status",
|
|
" bun scripts/cli.ts agentrun control-plane status --pipeline-run <yaml-lane-pipelinerun>",
|
|
" bun scripts/cli.ts agentrun control-plane expose --dry-run",
|
|
" bun scripts/cli.ts agentrun control-plane trigger-current --dry-run",
|
|
" bun scripts/cli.ts agentrun control-plane cleanup-runners --node D601 --lane v02 --dry-run",
|
|
" bun scripts/cli.ts agentrun control-plane cleanup-runs --min-age-minutes 30 --limit 200 --dry-run",
|
|
].join("\n");
|
|
}
|
|
if (verb === "git-mirror") {
|
|
return [
|
|
"Usage: bun scripts/cli.ts agentrun git-mirror <status|sync|flush> [--full|--raw|--confirm]",
|
|
"",
|
|
"Confirmed sync/flush returns an async job unless --wait is set.",
|
|
].join("\n");
|
|
}
|
|
if (verb !== undefined && isAgentRunRestCompatGroup(verb)) {
|
|
return [
|
|
`Compatibility group: agentrun ${verb} ...`,
|
|
"",
|
|
"Use resource primitives for daily commander work:",
|
|
" bun scripts/cli.ts agentrun get tasks --queue commander --limit 20",
|
|
" bun scripts/cli.ts agentrun describe task/<taskId>",
|
|
" bun scripts/cli.ts agentrun describe run/<runId> --node D601 --lane v02",
|
|
" bun scripts/cli.ts agentrun events run/<runId> --after-seq 0 --limit 100",
|
|
" bun scripts/cli.ts agentrun logs session/<sessionId> --tail 100",
|
|
"",
|
|
"Use --raw on a resource command when you need the direct AgentRun REST envelope.",
|
|
].join("\n");
|
|
}
|
|
return [
|
|
"Usage: bun scripts/cli.ts agentrun <verb> <resource> [options]",
|
|
"",
|
|
"Verbs: get, describe, events, logs, result, ack, cancel, dispatch, create, apply, send, explain",
|
|
"Resources: task/qt, run, command/cmd, runnerjob/rjob, session/ses, aipodspec/aps",
|
|
"",
|
|
"Common:",
|
|
" bun scripts/cli.ts agentrun get tasks --queue commander --limit 20",
|
|
" bun scripts/cli.ts agentrun describe task/<taskId>",
|
|
" bun scripts/cli.ts agentrun describe run/<runId> --node D601 --lane v02",
|
|
" bun scripts/cli.ts agentrun events run/<runId> --after-seq 0 --limit 100",
|
|
" bun scripts/cli.ts agentrun logs session/<sessionId> --tail 100",
|
|
" bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin",
|
|
"",
|
|
"Machine/debug output:",
|
|
" -o json|yaml emits a stable UniDesk resource object without the global JSON envelope.",
|
|
" --raw emits the direct AgentRun REST envelope.",
|
|
" --node/--lane targets a YAML-declared AgentRun runtime lane; default remains the configured public manager.",
|
|
].join("\n");
|
|
}
|