501 lines
20 KiB
TypeScript
501 lines
20 KiB
TypeScript
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. trigger module for scripts/src/agentrun.ts.
|
|
|
|
// Moved mechanically from scripts/src/agentrun.ts:2747-3186 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 { AgentRunPublicExposure } from "./config";
|
|
import type { ConfirmOptions, LaneConfirmOptions, SecretSyncOptions, TriggerOptions } from "./options";
|
|
import { triggerCurrentYamlLaneConfirmed } from "./cleanup-scripts";
|
|
import { readAgentRunClientConfig } from "./config";
|
|
import { displayValue } from "./options";
|
|
import { pathValue } from "./render";
|
|
import { startAsyncAgentRunJob } from "./rest-bridge";
|
|
import { collectLaneSecretSources, readSecretSourceValue, restartYamlLaneScript, secretSyncScript } from "./secrets";
|
|
import { capture, captureJsonPayload, compactCapture, isGitSha, stringOrNull } from "./utils";
|
|
import { yamlLaneK3sSourceStatusScript, yamlLaneSourceBootstrapProbeScript } from "./yaml-lane";
|
|
|
|
export function renderNextObjectLines(next: Record<string, unknown>): string[] {
|
|
return Object.values(next)
|
|
.filter((value): value is string => typeof value === "string" && value.length > 0)
|
|
.slice(0, 5)
|
|
.map((line) => ` ${line}`);
|
|
}
|
|
|
|
export function yesNo(value: unknown): string {
|
|
if (value === true) return "yes";
|
|
if (value === false) return "no";
|
|
if (value === null || value === undefined) return "-";
|
|
return displayValue(value);
|
|
}
|
|
|
|
export function shortSha(value: unknown): string {
|
|
const text = displayValue(value);
|
|
if (/^[0-9a-f]{40}$/u.test(text)) return text.slice(0, 12);
|
|
return text;
|
|
}
|
|
|
|
export function compactLaneSecretsStatus(secrets: Record<string, unknown>): Record<string, unknown> {
|
|
const items = Array.isArray(secrets.items) ? secrets.items.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null && !Array.isArray(item)) : [];
|
|
const missing = items
|
|
.filter((item) => item.present !== true || item.keyPresent !== true)
|
|
.map((item) => ({
|
|
namespace: item.namespace ?? null,
|
|
name: item.name ?? null,
|
|
key: item.key ?? null,
|
|
present: item.present ?? false,
|
|
keyPresent: item.keyPresent ?? false,
|
|
}));
|
|
return {
|
|
ready: secrets.ready ?? false,
|
|
count: secrets.count ?? items.length,
|
|
missingCount: missing.length,
|
|
missing,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
export function compactAgentRunLaneStatusTarget(spec: AgentRunLaneSpec): Record<string, unknown> {
|
|
return {
|
|
node: {
|
|
id: spec.nodeId,
|
|
route: spec.nodeRoute,
|
|
kubeRoute: spec.nodeKubeRoute,
|
|
},
|
|
lane: spec.lane,
|
|
version: spec.version,
|
|
source: {
|
|
repository: spec.source.repository,
|
|
branch: spec.source.branch,
|
|
},
|
|
runtime: {
|
|
namespace: spec.runtime.namespace,
|
|
managerDeployment: spec.runtime.managerDeployment,
|
|
managerService: spec.runtime.managerService,
|
|
},
|
|
ci: {
|
|
namespace: spec.ci.namespace,
|
|
pipeline: spec.ci.pipeline,
|
|
pipelineRunPrefix: spec.ci.pipelineRunPrefix,
|
|
},
|
|
gitops: {
|
|
branch: spec.gitops.branch,
|
|
argoApplication: spec.gitops.argoApplication,
|
|
},
|
|
gitMirror: {
|
|
namespace: spec.gitMirror.namespace,
|
|
readService: spec.gitMirror.readService,
|
|
writeService: spec.gitMirror.writeService,
|
|
repositoryCount: spec.gitMirror.repositories.length,
|
|
},
|
|
database: {
|
|
mode: spec.database.mode,
|
|
provider: spec.database.provider,
|
|
configRef: spec.database.configRef,
|
|
localPostgresExpectedAbsent: spec.database.localPostgresExpectedAbsent,
|
|
valuesPrinted: false,
|
|
},
|
|
secretCount: spec.secrets.length,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
export async function restartYamlLane(config: UniDeskConfig, options: LaneConfirmOptions): Promise<Record<string, unknown>> {
|
|
const { configPath, spec } = resolveAgentRunLaneTarget(options);
|
|
const plan = {
|
|
node: spec.nodeId,
|
|
kubeRoute: spec.nodeKubeRoute,
|
|
lane: spec.lane,
|
|
namespace: spec.runtime.namespace,
|
|
deployment: spec.runtime.managerDeployment,
|
|
annotation: "kubectl.kubernetes.io/restartedAt",
|
|
reason: "reload-lane-secrets-or-runtime-env",
|
|
valuesPrinted: false,
|
|
};
|
|
if (options.dryRun || !options.confirm) {
|
|
return {
|
|
ok: true,
|
|
command: "agentrun control-plane restart",
|
|
mode: "dry-run",
|
|
mutation: false,
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
plan,
|
|
next: {
|
|
confirm: `bun scripts/cli.ts agentrun control-plane restart --node ${spec.nodeId} --lane ${spec.lane} --confirm`,
|
|
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const result = await capture(config, spec.nodeKubeRoute, ["sh", "--", restartYamlLaneScript(spec)]);
|
|
const payload = captureJsonPayload(result);
|
|
return {
|
|
ok: result.exitCode === 0 && payload.ok !== false,
|
|
command: "agentrun control-plane restart",
|
|
mode: "confirmed-rollout-restart",
|
|
mutation: true,
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
plan,
|
|
result: payload,
|
|
capture: compactCapture(result, { full: result.exitCode !== 0, stdoutTailChars: 4000, stderrTailChars: 4000 }),
|
|
next: {
|
|
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
export async function secretSync(config: UniDeskConfig, options: SecretSyncOptions): Promise<Record<string, unknown>> {
|
|
const { configPath, spec } = resolveAgentRunLaneTarget(options);
|
|
const sources = collectLaneSecretSources(spec);
|
|
if (sources.length === 0) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane secret-sync",
|
|
mode: "yaml-declared-node-lane",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
degradedReason: "lane-secret-sources-not-declared",
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const values = sources.map((source) => ({ spec: source, value: readSecretSourceValue(spec, source) }));
|
|
const plan = values.map(({ spec: item, value }) => ({
|
|
id: item.id,
|
|
namespace: item.targetRef.namespace,
|
|
secret: item.targetRef.name,
|
|
key: item.targetRef.key,
|
|
sourceRef: item.sourceRef,
|
|
sourceMode: item.sourceMode,
|
|
sourceKey: item.sourceKey,
|
|
sourcePath: value.redactedPath,
|
|
fingerprint: value.fingerprint,
|
|
valueBytes: value.valueBytes,
|
|
valuesPrinted: false,
|
|
}));
|
|
if (options.dryRun || !options.confirm) {
|
|
return {
|
|
ok: true,
|
|
command: "agentrun control-plane secret-sync",
|
|
mode: "dry-run",
|
|
mutation: false,
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
plan: { secretCount: plan.length, items: plan, valuesPrinted: false },
|
|
next: {
|
|
confirm: `bun scripts/cli.ts agentrun control-plane secret-sync --node ${spec.nodeId} --lane ${spec.lane} --confirm`,
|
|
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const result = await capture(config, spec.nodeKubeRoute, ["sh", "--", secretSyncScript(spec, values.map(({ spec: item, value }) => ({ targetRef: item.targetRef, value: value.value })))]);
|
|
const payload = captureJsonPayload(result);
|
|
return {
|
|
ok: result.exitCode === 0 && payload.ok !== false,
|
|
command: "agentrun control-plane secret-sync",
|
|
mode: "confirmed-sync",
|
|
mutation: true,
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
plan: { secretCount: plan.length, items: plan, valuesPrinted: false },
|
|
result: payload,
|
|
capture: compactCapture(result, { full: result.exitCode !== 0, stdoutTailChars: 3000, stderrTailChars: 3000 }),
|
|
next: {
|
|
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
export async function exposeAgentRun(_config: UniDeskConfig, options: ConfirmOptions): Promise<Record<string, unknown>> {
|
|
const clientConfig = readAgentRunClientConfig();
|
|
const exposure = clientConfig.publicExposure;
|
|
if (!exposure?.enabled) {
|
|
return {
|
|
ok: true,
|
|
command: "agentrun control-plane expose",
|
|
action: "disabled-by-yaml",
|
|
publicExposure: exposureSummary(exposure),
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
if (options.dryRun) {
|
|
return {
|
|
ok: true,
|
|
command: "agentrun control-plane expose",
|
|
dryRun: true,
|
|
mutation: false,
|
|
publicExposure: exposureSummary(exposure),
|
|
plan: {
|
|
masterFrps: {
|
|
action: "ensure-allow-port",
|
|
configPath: exposure.masterFrps.configPath,
|
|
containerName: exposure.masterFrps.containerName,
|
|
remotePort: exposure.remotePort,
|
|
},
|
|
masterCaddy: {
|
|
action: "ensure-https-vhost",
|
|
domain: exposure.masterCaddy.domain,
|
|
configPath: exposure.masterCaddy.configPath,
|
|
serviceName: exposure.masterCaddy.serviceName,
|
|
upstreamBaseUrl: exposure.masterCaddy.upstreamBaseUrl,
|
|
responseHeaderTimeoutSeconds: exposure.masterCaddy.responseHeaderTimeoutSeconds,
|
|
},
|
|
},
|
|
next: { confirm: "bun scripts/cli.ts agentrun control-plane expose --confirm" },
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const masterFrps = applyAgentRunMasterFrpsAllowPort(exposure);
|
|
const masterCaddy = applyAgentRunMasterCaddySite(exposure);
|
|
const ok = masterFrps.ok === true && masterCaddy.ok === true;
|
|
return {
|
|
ok,
|
|
command: "agentrun control-plane expose",
|
|
mutation: true,
|
|
publicExposure: exposureSummary(exposure),
|
|
masterFrps,
|
|
masterCaddy,
|
|
validation: {
|
|
publicProbe: `curl -fsS ${exposure.publicBaseUrl.replace(/\/+$/u, "")}/health/readiness`,
|
|
directClient: "bun scripts/cli.ts agentrun get tasks --queue commander -o json",
|
|
valuesPrinted: false,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
export function exposureSummary(exposure: AgentRunPublicExposure | null | undefined): Record<string, unknown> {
|
|
if (!exposure) return { enabled: false, valuesPrinted: false };
|
|
return {
|
|
enabled: exposure.enabled,
|
|
proxyName: exposure.proxyName,
|
|
remotePort: exposure.remotePort,
|
|
publicBaseUrl: exposure.publicBaseUrl,
|
|
masterBaseUrl: exposure.masterBaseUrl,
|
|
frps: {
|
|
configPath: exposure.masterFrps.configPath,
|
|
containerName: exposure.masterFrps.containerName,
|
|
},
|
|
caddy: {
|
|
enabled: exposure.masterCaddy.enabled,
|
|
domain: exposure.masterCaddy.domain,
|
|
configPath: exposure.masterCaddy.configPath,
|
|
serviceName: exposure.masterCaddy.serviceName,
|
|
upstreamBaseUrl: exposure.masterCaddy.upstreamBaseUrl,
|
|
responseHeaderTimeoutSeconds: exposure.masterCaddy.responseHeaderTimeoutSeconds,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
export function applyAgentRunMasterFrpsAllowPort(exposure: AgentRunPublicExposure): Record<string, unknown> {
|
|
const pathValue = exposure.masterFrps.configPath;
|
|
const port = exposure.remotePort;
|
|
const container = exposure.masterFrps.containerName;
|
|
if (!existsSync(pathValue)) return { ok: false, error: "master-frps-config-missing", path: pathValue, valuesPrinted: false };
|
|
const before = readFileSync(pathValue, "utf8");
|
|
const alreadyAllowed = frpsAllowPortExists(before, port);
|
|
let backupPath: string | null = null;
|
|
let action = "kept-existing";
|
|
if (!alreadyAllowed) {
|
|
const stamp = new Date().toISOString().replace(/[-:]/gu, "").replace(/\..*$/u, "Z");
|
|
backupPath = `${pathValue}.bak-agentrun-${stamp}`;
|
|
copyFileSync(pathValue, backupPath);
|
|
writeFileSync(pathValue, `${before.replace(/\s*$/u, "")}\n\n[[allowPorts]]\nstart = ${port}\nend = ${port}\n`, "utf8");
|
|
chmodSync(pathValue, statSync(backupPath).mode & 0o777);
|
|
action = "added-allow-port";
|
|
}
|
|
const restart = alreadyAllowed ? null : spawnSync("docker", ["restart", container], { encoding: "utf8" });
|
|
const inspect = spawnSync("docker", ["inspect", "-f", "{{.State.Running}}", container], { encoding: "utf8" });
|
|
const ok = alreadyAllowed || (restart?.status === 0 && inspect.status === 0 && String(inspect.stdout).trim() === "true");
|
|
return {
|
|
ok,
|
|
action,
|
|
path: pathValue,
|
|
backupPath,
|
|
remotePort: port,
|
|
containerName: container,
|
|
restart: restart === null ? null : { exitCode: restart.status, stdoutTail: String(restart.stdout).slice(-1000), stderrTail: String(restart.stderr).slice(-1000) },
|
|
inspect: { exitCode: inspect.status, running: String(inspect.stdout).trim() === "true", stderrTail: String(inspect.stderr).slice(-1000) },
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
export function applyAgentRunMasterCaddySite(exposure: AgentRunPublicExposure): Record<string, unknown> {
|
|
const caddy = exposure.masterCaddy;
|
|
if (!caddy.enabled) return { ok: true, action: "disabled-by-yaml", valuesPrinted: false };
|
|
const pathValue = caddy.configPath;
|
|
if (!existsSync(pathValue)) return { ok: false, error: "master-caddy-config-missing", path: pathValue, valuesPrinted: false };
|
|
const desiredBlock = renderAgentRunCaddySiteBlock(caddy.domain, caddy.upstreamBaseUrl, caddy.responseHeaderTimeoutSeconds);
|
|
const applied = applyLocalCaddyManagedSite({
|
|
serviceId: "agentrun",
|
|
markerName: "agentrun",
|
|
configPath: pathValue,
|
|
serviceName: caddy.serviceName,
|
|
siteBlock: desiredBlock,
|
|
legacySiteDomain: caddy.domain,
|
|
});
|
|
return {
|
|
...applied,
|
|
domain: caddy.domain,
|
|
upstreamBaseUrl: caddy.upstreamBaseUrl,
|
|
responseHeaderTimeoutSeconds: caddy.responseHeaderTimeoutSeconds,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
export function renderAgentRunCaddySiteBlock(domain: string, upstreamBaseUrl: string, responseHeaderTimeoutSeconds: number): string {
|
|
const upstream = new URL(upstreamBaseUrl);
|
|
const upstreamHost = `${upstream.hostname}${upstream.port ? `:${upstream.port}` : ""}`;
|
|
return `${domain} {
|
|
encode zstd gzip
|
|
reverse_proxy ${upstreamHost} {
|
|
header_up Host {host}
|
|
header_up X-Real-IP {remote_host}
|
|
transport http {
|
|
dial_timeout 5s
|
|
response_header_timeout ${responseHeaderTimeoutSeconds}s
|
|
}
|
|
}
|
|
}`;
|
|
}
|
|
|
|
export function frpsAllowPortExists(toml: string, port: number): boolean {
|
|
const sections = toml.split(/(?=\[\[allowPorts\]\])/u);
|
|
return sections.some((section) => {
|
|
if (!section.includes("[[allowPorts]]")) return false;
|
|
const start = section.match(/^\s*start\s*=\s*(\d+)\s*$/mu);
|
|
const end = section.match(/^\s*end\s*=\s*(\d+)\s*$/mu);
|
|
return Number(start?.[1]) === port && Number(end?.[1]) === port;
|
|
});
|
|
}
|
|
|
|
export async function triggerCurrent(config: UniDeskConfig, options: TriggerOptions): Promise<Record<string, unknown>> {
|
|
return await triggerCurrentYamlLane(config, options, resolveAgentRunLaneTarget(options));
|
|
}
|
|
|
|
export async function triggerCurrentYamlLane(config: UniDeskConfig, options: TriggerOptions, target: { configPath: string; spec: AgentRunLaneSpec }): Promise<Record<string, unknown>> {
|
|
const spec = target.spec;
|
|
const probe = spec.source.statusMode === "k3s-git-mirror"
|
|
? await capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneK3sSourceStatusScript(spec)])
|
|
: await capture(config, spec.nodeRoute, ["sh", "--", yamlLaneSourceBootstrapProbeScript(spec)]);
|
|
const source = captureJsonPayload(probe);
|
|
const sourceCommit = stringOrNull(source.sourceCommit);
|
|
const remoteBranchExists = source.remoteBranchExists === true;
|
|
const pipelineRun = sourceCommit !== null && isGitSha(sourceCommit) ? agentRunPipelineRunName(spec, sourceCommit) : null;
|
|
const placeholderImage = sourceCommit === null ? null : placeholderAgentRunImage(spec, sourceCommit);
|
|
const renderedFiles = placeholderImage === null ? [] : renderAgentRunGitopsFiles(spec, { sourceCommit, image: placeholderImage });
|
|
const sourcePlan = spec.source.statusMode === "k3s-git-mirror"
|
|
? {
|
|
statusMode: spec.source.statusMode,
|
|
sourceAuthority: spec.source.sourceAuthority,
|
|
sourceSnapshot: spec.source.sourceSnapshot,
|
|
repository: spec.source.repository,
|
|
branch: spec.source.branch,
|
|
remoteBranchExists,
|
|
remoteBranchCommit: stringOrNull(source.remoteBranchCommit),
|
|
sourceCommit,
|
|
sourceRef: stringOrNull(source.sourceRef),
|
|
sourceStageRef: stringOrNull(source.sourceStageRef),
|
|
snapshotPresent: source.snapshotPresent === true,
|
|
degradedReason: stringOrNull(source.degradedReason),
|
|
next: source.snapshotPresent === true ? null : `bun scripts/cli.ts agentrun git-mirror sync --node ${spec.nodeId} --lane ${spec.lane} --confirm --wait`,
|
|
valuesPrinted: false,
|
|
}
|
|
: {
|
|
statusMode: spec.source.statusMode,
|
|
workspace: spec.source.workspace,
|
|
remote: spec.source.remote,
|
|
branch: spec.source.branch,
|
|
bootstrapFromBranch: spec.source.bootstrapFromBranch,
|
|
bootstrapTimeoutSeconds: spec.source.bootstrapTimeoutSeconds,
|
|
bootstrapPollSeconds: spec.source.bootstrapPollSeconds,
|
|
remoteBranchExists,
|
|
sourceCommit,
|
|
valuesPrinted: false,
|
|
};
|
|
const plan = {
|
|
node: spec.nodeId,
|
|
lane: spec.lane,
|
|
version: spec.version,
|
|
source: sourcePlan,
|
|
deploymentFormat: spec.deployment.format,
|
|
deploymentTruth: "config/agentrun.yaml",
|
|
removedServiceDeployJson: true,
|
|
pipelineRun,
|
|
imageBuild: {
|
|
repository: `${spec.ci.registryPrefix}/${spec.deployment.manager.imageBuild.repository}`,
|
|
containerfile: spec.deployment.manager.imageBuild.containerfile,
|
|
buildArgNames: Object.keys(spec.deployment.manager.imageBuild.buildArgs).sort(),
|
|
timeoutSeconds: spec.deployment.manager.imageBuild.timeoutSeconds,
|
|
pollSeconds: spec.deployment.manager.imageBuild.pollSeconds,
|
|
proxyConfigured: spec.deployment.manager.imageBuild.httpProxy !== null || spec.deployment.manager.imageBuild.httpsProxy !== null,
|
|
},
|
|
gitops: {
|
|
branch: spec.gitops.branch,
|
|
path: spec.gitops.path,
|
|
renderedFileCount: renderedFiles.length,
|
|
renderedFilesDigest: renderedFiles.length === 0 ? null : renderedFilesDigest(renderedFiles),
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
if (options.dryRun || !options.confirm) {
|
|
return {
|
|
ok: true,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: "dry-run",
|
|
mutation: false,
|
|
configPath: target.configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
plan,
|
|
sourceProbe: compactCapture(probe, { full: probe.exitCode !== 0, stdoutTailChars: 3000, stderrTailChars: 3000 }),
|
|
next: {
|
|
confirm: `bun scripts/cli.ts agentrun control-plane trigger-current --node ${spec.nodeId} --lane ${spec.lane} --confirm`,
|
|
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
if (options.wait) {
|
|
return await triggerCurrentYamlLaneConfirmed(config, spec, target.configPath, true);
|
|
}
|
|
return startAsyncAgentRunJob(
|
|
`agentrun_${spec.lane}_trigger_current`,
|
|
["bun", "scripts/cli.ts", "agentrun", "control-plane", "trigger-current", "--node", spec.nodeId, "--lane", spec.lane, "--confirm", "--wait"],
|
|
`Run AgentRun ${spec.version} YAML-only trigger-current on ${spec.nodeId}`,
|
|
);
|
|
}
|