fix: add bounded AgentRun task input drill-down
This commit is contained in:
@@ -57,6 +57,7 @@ export function agentRunHelp(): unknown {
|
||||
"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 describe task/<taskId> --input -o json",
|
||||
"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>",
|
||||
@@ -272,11 +273,12 @@ export function agentRunHelpText(args: string[]): string {
|
||||
}
|
||||
if (verb === "describe") {
|
||||
return [
|
||||
"Usage: bun scripts/cli.ts agentrun describe <kind/name> [--node <node> --lane <lane>] [--full] [-o json|yaml] [--raw]",
|
||||
"Usage: bun scripts/cli.ts agentrun describe <kind/name> [--node <node> --lane <lane>] [--input] [--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 task/qt_... --input -o json",
|
||||
" bun scripts/cli.ts agentrun describe run/<runId> --node NC01 --lane nc01-v02",
|
||||
" bun scripts/cli.ts agentrun describe command/cmd_... --run <runId>",
|
||||
" bun scripts/cli.ts agentrun describe session/<sessionId> --full",
|
||||
@@ -391,6 +393,7 @@ export function agentRunHelpText(args: string[]): string {
|
||||
"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 task/<taskId> --input -o json",
|
||||
" bun scripts/cli.ts agentrun describe run/<runId> --node NC01 --lane nc01-v02",
|
||||
" bun scripts/cli.ts agentrun events run/<runId> --after-seq 0 --limit 100",
|
||||
" bun scripts/cli.ts agentrun logs session/<sessionId> --tail 100",
|
||||
|
||||
@@ -1335,12 +1335,13 @@ export function renderTaskDescription(task: Record<string, unknown>, options: Ag
|
||||
"Next:",
|
||||
];
|
||||
const next = [
|
||||
`bun scripts/cli.ts agentrun describe task/${taskId} --input -o json`,
|
||||
runId === null ? null : `bun scripts/cli.ts agentrun events run/${runId} --after-seq 0 --limit 100`,
|
||||
sessionId === null ? null : `bun scripts/cli.ts agentrun logs session/${sessionId} --tail 100`,
|
||||
runId === null || commandId === null ? null : `bun scripts/cli.ts agentrun result run/${runId} --command ${commandId}`,
|
||||
`bun scripts/cli.ts agentrun ack task/${taskId}`,
|
||||
options.full ? null : `bun scripts/cli.ts agentrun describe task/${taskId} --full`,
|
||||
].filter((item): item is string => item !== null).slice(0, 5);
|
||||
].filter((item): item is string => item !== null).slice(0, 6);
|
||||
lines.push(...next.map((item) => ` ${item}`));
|
||||
return lines.join("\n");
|
||||
}
|
||||
@@ -1379,12 +1380,147 @@ export function compactTaskDescriptionPayload(ref: AgentRunResourceRef, task: Re
|
||||
logs: sessionId === null ? null : `bun scripts/cli.ts agentrun logs session/${sessionId} --tail 100`,
|
||||
result: runId === null || commandId === null ? null : `bun scripts/cli.ts agentrun result run/${runId} --command ${commandId}`,
|
||||
ack: `bun scripts/cli.ts agentrun ack task/${taskId}`,
|
||||
input: `bun scripts/cli.ts agentrun describe task/${taskId} --input -o json`,
|
||||
full: `bun scripts/cli.ts agentrun describe task/${taskId} --full -o json`,
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function taskInputDescriptionPayload(ref: AgentRunResourceRef, task: Record<string, unknown>): Record<string, unknown> {
|
||||
const metadata = taskInputMetadata(task.metadata);
|
||||
const aipodImageRef = taskInputImageRef(metadata.aipodImageRef);
|
||||
if (aipodImageRef !== null) metadata.aipodImageRef = aipodImageRef;
|
||||
const spec = pickCompact(task, [
|
||||
"tenantId",
|
||||
"projectId",
|
||||
"queue",
|
||||
"lane",
|
||||
"title",
|
||||
"priority",
|
||||
"backendProfile",
|
||||
"providerId",
|
||||
"payload",
|
||||
"references",
|
||||
"idempotencyKey",
|
||||
]);
|
||||
spec.workspaceRef = taskInputWorkspaceRef(task.workspaceRef);
|
||||
spec.sessionRef = taskInputSessionRef(task.sessionRef);
|
||||
spec.executionPolicy = taskInputExecutionPolicy(task.executionPolicy);
|
||||
spec.resourceBundleRef = taskInputResourceBundleRef(task.resourceBundleRef);
|
||||
spec.metadata = metadata;
|
||||
return {
|
||||
kind: "TaskInput",
|
||||
name: ref.name,
|
||||
spec,
|
||||
aipodSpec: {
|
||||
name: metadata.aipod ?? null,
|
||||
specHash: metadata.aipodSpecHash ?? null,
|
||||
imageRef: aipodImageRef,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
provenance: {
|
||||
sourceTaskId: stringOrNull(task.id) ?? ref.name,
|
||||
source: metadata.source ?? null,
|
||||
payloadHash: task.payloadHash ?? null,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
redaction: {
|
||||
credentialValues: "omitted",
|
||||
secretRefsOnly: true,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function taskInputExecutionPolicy(value: unknown): Record<string, unknown> | null {
|
||||
if (!isRecord(value)) return null;
|
||||
const policy = pickCompact(value, ["sandbox", "approval", "timeoutMs", "network"]);
|
||||
const secretScope = record(value.secretScope);
|
||||
policy.secretScope = {
|
||||
allowCredentialEcho: false,
|
||||
providerCredentials: arrayRecords(secretScope.providerCredentials).map((credential) => ({
|
||||
...pickCompact(credential, ["profile"]),
|
||||
secretRef: taskInputSecretRef(credential.secretRef),
|
||||
valuesPrinted: false,
|
||||
})),
|
||||
toolCredentials: arrayRecords(secretScope.toolCredentials).map((credential) => ({
|
||||
...pickCompact(credential, ["tool", "purpose"]),
|
||||
secretRef: taskInputSecretRef(credential.secretRef),
|
||||
projection: pickCompact(record(credential.projection), ["kind", "envName", "secretKey", "mountPath"]),
|
||||
valuesPrinted: false,
|
||||
})),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
return policy;
|
||||
}
|
||||
|
||||
function taskInputSecretRef(value: unknown): Record<string, unknown> | null {
|
||||
if (!isRecord(value)) return null;
|
||||
return {
|
||||
...pickCompact(value, ["namespace", "name", "keys", "mountPath"]),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function taskInputResourceBundleRef(value: unknown): Record<string, unknown> | null {
|
||||
if (!isRecord(value)) return null;
|
||||
const result = pickCompact(value, ["kind", "commitId", "ref", "submodules", "lfs"]);
|
||||
result.repoUrl = taskInputRepoUrl(value.repoUrl);
|
||||
result.bundles = arrayRecords(value.bundles).map((bundle) => ({
|
||||
...pickCompact(bundle, ["name", "commitId", "ref", "subpath", "targetPath"]),
|
||||
...(bundle.repoUrl === undefined ? {} : { repoUrl: taskInputRepoUrl(bundle.repoUrl) }),
|
||||
}));
|
||||
if (Array.isArray(value.promptRefs)) {
|
||||
result.promptRefs = arrayRecords(value.promptRefs).map((item) => pickCompact(item, ["name", "path", "inject", "required"]));
|
||||
}
|
||||
if (Array.isArray(value.requiredSkills)) {
|
||||
result.requiredSkills = arrayRecords(value.requiredSkills).map((item) => pickCompact(item, ["name"]));
|
||||
}
|
||||
if (value.credentialRef !== undefined) result.credentialRef = taskInputSecretRef(value.credentialRef);
|
||||
result.valuesPrinted = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
function taskInputWorkspaceRef(value: unknown): Record<string, unknown> | null {
|
||||
if (!isRecord(value)) return null;
|
||||
const result = pickCompact(value, ["kind", "path", "branch"]);
|
||||
if (value.repo !== undefined) result.repo = taskInputRepoUrl(value.repo);
|
||||
return result;
|
||||
}
|
||||
|
||||
function taskInputSessionRef(value: unknown): Record<string, unknown> | null {
|
||||
if (!isRecord(value)) return null;
|
||||
return pickCompact(value, ["sessionId", "conversationId", "threadId", "expiresAt"]);
|
||||
}
|
||||
|
||||
function taskInputMetadata(value: unknown): Record<string, unknown> {
|
||||
if (!isRecord(value)) return {};
|
||||
return pickCompact(value, ["aipod", "source", "aipodSpecHash", "aipodImageRef"]);
|
||||
}
|
||||
|
||||
function taskInputImageRef(value: unknown): Record<string, unknown> | null {
|
||||
if (!isRecord(value)) return null;
|
||||
const result = pickCompact(value, ["kind", "commitId", "dockerfilePath", "sourceIdentity"]);
|
||||
if (value.repoUrl !== undefined) result.repoUrl = taskInputRepoUrl(value.repoUrl);
|
||||
result.valuesPrinted = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
function taskInputRepoUrl(value: unknown): unknown {
|
||||
if (typeof value !== "string") return value ?? null;
|
||||
if (!value.includes("://")) return value;
|
||||
try {
|
||||
const parsed = new URL(value);
|
||||
parsed.username = "";
|
||||
parsed.password = "";
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return "<invalid-or-redacted-url>";
|
||||
}
|
||||
}
|
||||
|
||||
export function unwrapTaskDetail(data: Record<string, unknown>): Record<string, unknown> {
|
||||
const task = record(data.task);
|
||||
if (Object.keys(task).length > 0) return task;
|
||||
|
||||
@@ -37,7 +37,7 @@ import type { AgentRunResourceOptions, AgentRunResourceRef, AgentRunResourceVerb
|
||||
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, normalizeQueueAttemptItems, normalizeRunnerJobItems, normalizeSessionItems, normalizeSingleCommand, normalizeTaskItems, parseResourceKind, parseResourceRef, renderDescribe, renderEventLike, renderGenericDescription, renderMachine, renderMutationSummary, renderQueueAttemptList, renderQueueRetrySummary, renderResourceResult, renderResultSummary, renderTaskDescription, renderedCliResult, requiredContext, rerunWithoutDryRun, resolveAgentRunCancelPolicyTarget, resourceApply, resourceCreate, resourceDispatch, resourceSessionPromptCommand, taskListState, unwrapTaskDetail } from "./render";
|
||||
import { agentRunCancelAuthorityDisclosure, agentRunCancelCascadeScope, agentRunCancelFencingDisclosure, agentRunCancelRunnerAbortDisclosure, compactTaskDescriptionPayload, innerData, normalizeAipodSpecItems, normalizeAttemptResources, normalizeEventItems, normalizeLogItems, normalizeQueueAttemptItems, normalizeRunnerJobItems, normalizeSessionItems, normalizeSingleCommand, normalizeTaskItems, parseResourceKind, parseResourceRef, 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 { runnerJobObservationScript, type RunnerJobObservationIdentity } from "./runner-observation";
|
||||
import { capture, captureJsonPayload, compactCapture, nonNegativeIntegerOrNull, record, stringOrNull } from "./utils";
|
||||
@@ -121,7 +121,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") continue;
|
||||
if (arg === "--full" || arg === "--raw" || arg === "--debug" || arg === "--full-text" || arg === "--input") continue;
|
||||
result.push(arg);
|
||||
}
|
||||
return result;
|
||||
@@ -133,6 +133,7 @@ export function parseResourceOptions(args: string[]): AgentRunResourceOptions {
|
||||
full: false,
|
||||
raw: false,
|
||||
debug: false,
|
||||
taskInput: false,
|
||||
limit: 20,
|
||||
cursor: null,
|
||||
queue: null,
|
||||
@@ -157,7 +158,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", "--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"]);
|
||||
const booleanFlags = new Set(["--full", "--raw", "--debug", "--input", "--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;
|
||||
@@ -190,6 +191,7 @@ export function applyResourceOption(options: AgentRunResourceOptions, flag: stri
|
||||
if (flag === "--full") options.full = true;
|
||||
else if (flag === "--raw") options.raw = true;
|
||||
else if (flag === "--debug") options.debug = true;
|
||||
else if (flag === "--input") options.taskInput = true;
|
||||
else if (flag === "--unread") options.unread = true;
|
||||
else if (flag === "--dry-run") options.dryRun = true;
|
||||
else if (flag === "--full-text") options.fullText = true;
|
||||
@@ -303,15 +305,21 @@ export async function resourceRetry(config: UniDeskConfig | null, command: strin
|
||||
|
||||
export async function resourceDescribe(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const ref = parseResourceRef(action, args);
|
||||
if (options.taskInput && ref.kind !== "task") throw new AgentRunRestError("validation-failed", "describe --input supports task/<taskId> only");
|
||||
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);
|
||||
const payload = options.full
|
||||
? { kind: ref.kind, name: ref.name, resource: task }
|
||||
: options.taskInput
|
||||
? taskInputDescriptionPayload(ref, task)
|
||||
: compactTaskDescriptionPayload(ref, task);
|
||||
return renderMachine(command, payload, options.output, result.ok !== false);
|
||||
}
|
||||
if (options.taskInput && !options.full) return renderMachine(command, taskInputDescriptionPayload(ref, task), "json", result.ok !== false);
|
||||
return renderedCliResult(result.ok !== false, command, renderTaskDescription(task, options));
|
||||
}
|
||||
if (ref.kind === "run") {
|
||||
|
||||
@@ -64,6 +64,7 @@ export interface AgentRunResourceOptions {
|
||||
full: boolean;
|
||||
raw: boolean;
|
||||
debug: boolean;
|
||||
taskInput: boolean;
|
||||
limit: number;
|
||||
cursor: number | null;
|
||||
queue: string | null;
|
||||
|
||||
Reference in New Issue
Block a user