Merge pull request #2667 from pikasTech/fix/agentrun-resource-help-lane
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / platform-infra-gitea-nc01- Success
Pipelines as Code CI / unidesk-host- Success

修复 AgentRun L1 资源查询入口
This commit is contained in:
Lyon
2026-07-20 17:40:47 +08:00
committed by GitHub
6 changed files with 68 additions and 20 deletions
+1
View File
@@ -52,6 +52,7 @@ bun scripts/cli.ts agentrun native-development manager start
bun scripts/cli.ts agentrun native-development manager status
bun scripts/cli.ts agentrun native-development manager logs
bun scripts/cli.ts agentrun native-development manager restart
bun scripts/cli.ts agentrun result run/<runId> --command <commandId> --native-development
```
- 配置唯一来源是 `config/agentrun.yaml#nativeDevelopment.manager`
+21
View File
@@ -53,6 +53,27 @@ export function readAgentRunClientConfig(env: NodeJS.ProcessEnv = process.env):
}
}
export function readAgentRunNativeDevelopmentClientConfig(env: NodeJS.ProcessEnv = process.env): AgentRunClientConfig {
const config = readAgentRunClientConfig(env);
const input = record(Bun.YAML.parse(readFileSync(config.sourcePath, "utf8")) as unknown);
const nativeDevelopment = record(input.nativeDevelopment);
const manager = record(nativeDevelopment.manager);
const probeHost = stringFieldFromRecord(manager, "probeHost", "nativeDevelopment.manager");
const port = numberFieldFromRecord(manager, "port", "nativeDevelopment.manager", { min: 1, max: 65_535 });
const apiKeySourceRef = stringFieldFromRecord(manager, "apiKeySourceRef", "nativeDevelopment.manager");
const apiKeySourceKey = stringFieldFromRecord(manager, "apiKeySourceKey", "nativeDevelopment.manager");
if (apiKeySourceRef.includes("..")) throw new Error("nativeDevelopment.manager.apiKeySourceRef must not contain ..");
const apiKeyFile = apiKeySourceRef.startsWith("/")
? apiKeySourceRef
: join("/root/.unidesk/.state/secrets", ...apiKeySourceRef.split("/"));
return {
...config,
manager: { ...config.manager, baseUrl: `http://${probeHost}:${port}/` },
auth: { ...config.auth, env: apiKeySourceKey, file: apiKeyFile },
publicExposure: null,
};
}
export function parseAgentRunClientConfigYaml(raw: string, sourcePath = "config/agentrun.yaml"): AgentRunClientConfig {
const input = record(Bun.YAML.parse(raw) as unknown);
validateAgentRunConfigEnvelope(input, sourcePath);
+6 -6
View File
@@ -42,7 +42,7 @@ import { agentRunExplain, isRecord, parseGitMirrorStatusOptions, parseStatusOpti
import { renderAgentRunControlPlaneActionSummary, renderAgentRunControlPlanePlanSummary, renderAgentRunControlPlaneStatusSummary } from "./public-exposure";
import { providerProfileHelp, runProviderProfileCommand } from "./provider-profile";
import { renderedCliResult } from "./render";
import { agentRunGetKindHelp, runAgentRunResourceCommand } from "./resource-actions";
import { AGENTRUN_RESOURCE_TRANSPORT_SCOPE, agentRunGetKindHelp, runAgentRunResourceCommand } from "./resource-actions";
import { runAgentRunRestCompatCommand, runGitMirrorJob, startAsyncAgentRunJob } from "./rest-bridge";
import { renderAgentRunSecretSyncResult } from "./secret-sync-output";
import { exposeAgentRun, restartYamlLane, secretSync, triggerCurrent } from "./trigger";
@@ -269,7 +269,7 @@ export function agentRunHelpText(args: string[]): string {
if (verb === "get" && kind !== undefined) return agentRunGetKindHelp(kind);
if (verb === "get") {
return [
"Usage: bun scripts/cli.ts agentrun get <resource> [options]",
`Usage: bun scripts/cli.ts agentrun get <resource> [options] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`,
"",
"Resources: tasks|attempts|sessions|runs|commands|runnerjobs|aipodspecs",
"Output: table by default; use -o wide|name|json|yaml.",
@@ -296,18 +296,18 @@ export function agentRunHelpText(args: string[]): string {
}
if (verb === "events") {
return [
"Usage: bun scripts/cli.ts agentrun events run/<runId> [--after-seq N] [--expect N [--timeout 120s]] [--limit 100] [-o json|yaml] [--full|--raw]",
" bun scripts/cli.ts agentrun events run/<runId> --detail-seq <seq> [-o json|yaml]",
`Usage: bun scripts/cli.ts agentrun events run/<runId> [--after-seq N] [--expect N [--timeout 120s]] [--limit 100] [-o json|yaml] [--full|--raw] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`,
` bun scripts/cli.ts agentrun events run/<runId> --detail-seq <seq> [-o json|yaml] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`,
"",
"--expect waits in one manager-held request until N new events, target completion, or timeout; --timeout defaults to 120s. --limit remains an independent output budget.",
"--detail-seq returns one bounded Secret-safe EventDetail projection. Use the separate --after-seq <seq-1> --limit 1 --full|--raw path only for explicit complete disclosure.",
].join("\n");
}
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]";
return `Usage: bun scripts/cli.ts agentrun logs session/<sessionId> [--tail 100|--after-seq N] [--limit 100] [--full-text] [-o json|yaml] [--raw] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`;
}
if (verb === "result") {
return "Usage: bun scripts/cli.ts agentrun result run/<runId> --command <commandId> | agentrun result command/<commandId> --run <runId>";
return `Usage: bun scripts/cli.ts agentrun result run/<runId> --command <commandId> ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}\n bun scripts/cli.ts agentrun result command/<commandId> --run <runId> ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`;
}
if (verb === "ack") {
return "Usage: bun scripts/cli.ts agentrun ack task/<taskId>|session/<sessionId> [--reader-id cli]";
+27 -13
View File
@@ -34,27 +34,29 @@ import {
import { sha256Fingerprint } from "../platform-infra-ops-library";
import type { AgentRunResourceOptions, AgentRunResourceRef, AgentRunResourceVerb } from "./utils";
import { agentRunDryRunPlan } from "./config";
import { agentRunDryRunPlan, readAgentRunNativeDevelopmentClientConfig } from "./config";
import { renderEventDetail } from "./event-detail";
import { isHelpArg, renderAgentRunHelp } from "./entry";
import { agentRunExplain, arrayRecords, shortId } from "./options";
import { agentRunCancelAuthorityDisclosure, agentRunCancelCascadeScope, agentRunCancelFencingDisclosure, agentRunCancelRunnerAbortDisclosure, compactAipodSpecDescriptionPayload, compactTaskDescriptionPayload, innerData, normalizeAipodSpecItems, normalizeAttemptResources, normalizeEventItems, normalizeLogItems, normalizeQueueAttemptItems, normalizeRunnerJobItems, normalizeSessionItems, normalizeSingleCommand, normalizeTaskItems, parseResourceKind, parseResourceRef, renderAipodSpecDescription, renderDescribe, renderEventLike, renderGenericDescription, renderMachine, renderMutationSummary, renderQueueAttemptList, renderQueueRetrySummary, renderResourceResult, renderResultSummary, renderTaskDescription, renderedCliResult, requiredContext, rerunWithoutDryRun, resolveAgentRunCancelPolicyTarget, resourceApply, resourceCreate, resourceDispatch, resourceSessionPromptCommand, taskInputDescriptionPayload, taskListState, unwrapTaskDetail } from "./render";
import { AgentRunRestError, renderAgentRunRestError, resolveAgentRunRestTarget, runAgentRunRestCommand, withAgentRunRestTarget } from "./rest-bridge";
import { AgentRunRestError, renderAgentRunRestError, resolveAgentRunRestTarget, runAgentRunRestCommand, withAgentRunNativeClientConfig, withAgentRunRestTarget } from "./rest-bridge";
import { renderSessionSendError } from "./session-send-render";
import { prepareAgentRunTargetTaskPreflight } from "./target-execution";
import { runnerJobObservationScript, type RunnerJobObservationIdentity } from "./runner-observation";
import { isChineseTaskTitle, validateChineseTaskTitle, withAgentRunTargetTaskPreflight } from "./target-task";
import { capture, captureJsonPayload, compactCapture, nonNegativeIntegerOrNull, record, stringOrNull } from "./utils";
export const AGENTRUN_RESOURCE_TRANSPORT_SCOPE = "[--native-development | [--target <NODE>|--node <NODE>] [--lane <lane>]]";
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 === "attempt") return "Usage: bun scripts/cli.ts agentrun get attempts --task <taskId> [--cursor <retry-index>] [--limit 20] [--full] [-o json|yaml] [--raw]";
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]";
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] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`;
if (kind === "attempt") return `Usage: bun scripts/cli.ts agentrun get attempts --task <taskId> [--cursor <retry-index>] [--limit 20] [--full] [-o json|yaml] [--raw] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`;
if (kind === "session") return `Usage: bun scripts/cli.ts agentrun get sessions [--limit 20] [-o wide|name|json|yaml] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`;
if (kind === "run") return `Usage: bun scripts/cli.ts agentrun get runs --task <taskId> [--limit 20] [-o wide|name|json|yaml] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`;
if (kind === "command") return `Usage: bun scripts/cli.ts agentrun get commands --run <runId> [--command <commandId>] [-o wide|name|json|yaml] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`;
if (kind === "runnerjob") return `Usage: bun scripts/cli.ts agentrun get runnerjobs --run <runId> --command <commandId> [-o wide|name|json|yaml] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`;
if (kind === "aipodspec") return `Usage: bun scripts/cli.ts agentrun get aipodspecs [-o wide|name|json|yaml] ${AGENTRUN_RESOURCE_TRANSPORT_SCOPE}`;
return "Unknown resource. Supported: tasks, attempts, sessions, runs, commands, runnerjobs, aipodspecs.";
}
@@ -76,7 +78,9 @@ export async function runAgentRunResourceCommand(config: UniDeskConfig | null, v
const bridgeActionArgs = stripAgentRunResourceWrapperArgs(actionArgs);
try {
const targetTaskPreflight = await prepareAgentRunTargetTaskPreflight(config, canonicalArgs, options);
return await withAgentRunTargetTaskPreflight(targetTaskPreflight, async () => await withAgentRunRestTarget(resolveAgentRunRestTarget(config, options), async () => {
const nativeClientConfig = options.nativeDevelopment ? readAgentRunNativeDevelopmentClientConfig() : null;
const restTarget = resolveAgentRunRestTarget(options.nativeDevelopment ? null : config, options);
return await withAgentRunTargetTaskPreflight(targetTaskPreflight, async () => await withAgentRunNativeClientConfig(nativeClientConfig, async () => await withAgentRunRestTarget(restTarget, 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);
@@ -91,7 +95,7 @@ export async function runAgentRunResourceCommand(config: UniDeskConfig | null, v
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) {
if (verb === "send") return renderSessionSendError(command, error.toPayload(command), options);
@@ -157,7 +161,7 @@ export function stripAgentRunResourceWrapperArgs(args: string[]): string[] {
}
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" || arg === "--input") continue;
if (arg === "--full" || arg === "--raw" || arg === "--debug" || arg === "--full-text" || arg === "--input" || arg === "--native-development") continue;
result.push(arg);
}
return result;
@@ -193,6 +197,7 @@ export function parseResourceOptions(args: string[]): AgentRunResourceOptions {
title: null,
idempotencyKey: null,
promptStdin: false,
nativeDevelopment: false,
node: null,
lane: null,
target: null,
@@ -203,7 +208,7 @@ export function parseResourceOptions(args: string[]): AgentRunResourceOptions {
passthroughArgs: [],
};
const valueFlags = new Set(["-o", "--output", "--limit", "--cursor", "--queue", "--state", "--reader-id", "--task", "--task-id", "--run", "--run-id", "--command", "--command-id", "--session", "--session-id", "--after-seq", "--expect", "--timeout", "--detail-seq", "--tail", "--reason", "-f", "--file", "--filename", "--aipod", "--title", "--idempotency-key", "--node", "--lane", "--target", "--target-workspace", "--repo", "--ref", "--mdtodo-id"]);
const booleanFlags = new Set(["--full", "--raw", "--debug", "--input", "--unread", "--dry-run", "--full-text", "--prompt-stdin", "--stdin"]);
const booleanFlags = new Set(["--full", "--raw", "--debug", "--input", "--unread", "--dry-run", "--full-text", "--prompt-stdin", "--stdin", "--native-development"]);
for (let index = 0; index < args.length; index += 1) {
const arg = args[index] ?? "";
if (!arg.startsWith("-")) continue;
@@ -284,6 +289,14 @@ export function parseResourceOptions(args: string[]): AgentRunResourceOptions {
}
function validateResourceOptionsForVerb(verb: AgentRunResourceVerb, options: AgentRunResourceOptions): void {
if (options.nativeDevelopment) {
if (options.node !== null || options.lane !== null || options.target !== null) {
throw new AgentRunRestError("validation-failed", "--native-development cannot be combined with --target, --node, or --lane");
}
if (!new Set<AgentRunResourceVerb>(["get", "describe", "events", "logs", "result"]).has(verb)) {
throw new AgentRunRestError("validation-failed", "--native-development is read-only and supports get, describe, events, logs, and result");
}
}
if (options.eventDetailSeq !== null && verb !== "events") {
throw new AgentRunRestError(
"validation-failed",
@@ -328,6 +341,7 @@ export function applyResourceOption(options: AgentRunResourceOptions, flag: stri
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 === "--native-development") options.nativeDevelopment = true;
else if (flag === "--limit") options.limit = parseNonNegativeInt(value, "--limit", 20, 500);
else if (flag === "--cursor") options.cursor = parseNonNegativeInt(value, "--cursor", 0, Number.MAX_SAFE_INTEGER);
else if (flag === "--queue") options.queue = requiredValue(value, flag);
+12 -1
View File
@@ -1220,7 +1220,7 @@ export async function sessionRunnerJobBody(args: string[], defaults: Record<stri
}
export async function agentRunRestRequest(command: string, method: AgentRunHttpMethod, pathValue: string, body?: unknown, requestTimeoutMs?: number): Promise<Record<string, unknown>> {
const clientConfig = readAgentRunClientConfig();
const clientConfig = activeAgentRunNativeClientConfig ?? readAgentRunClientConfig();
const effectiveTimeoutMs = Math.max(clientConfig.manager.timeoutMs, requestTimeoutMs ?? 0);
if (activeAgentRunRestTarget !== null) return await agentRunLaneRestRequest(command, method, pathValue, body, clientConfig, activeAgentRunRestTarget, effectiveTimeoutMs);
const auth = resolveAgentRunAuth(clientConfig);
@@ -1409,6 +1409,16 @@ export async function withAgentRunRestTarget<T>(target: AgentRunRestTarget | nul
}
}
export async function withAgentRunNativeClientConfig<T>(config: AgentRunClientConfig | null, action: () => Promise<T>): Promise<T> {
const previous = activeAgentRunNativeClientConfig;
activeAgentRunNativeClientConfig = config;
try {
return await action();
} finally {
activeAgentRunNativeClientConfig = previous;
}
}
export function renderAgentRunRestError(command: string, error: AgentRunRestError, options: AgentRunResourceOptions): RenderedCliResult {
const rawPayload = error.toPayload(command);
if (options.raw) return renderMachine(command, rawPayload, "json", false);
@@ -1500,3 +1510,4 @@ export class AgentRunRestError extends Error {
}
export let activeAgentRunRestTarget: AgentRunRestTarget | null = null;
export let activeAgentRunNativeClientConfig: AgentRunClientConfig | null = null;
+1
View File
@@ -88,6 +88,7 @@ export interface AgentRunResourceOptions {
title: string | null;
idempotencyKey: string | null;
promptStdin: boolean;
nativeDevelopment: boolean;
node: string | null;
lane: string | null;
target: string | null;