fix(agentrun): 收敛 Target 派单输出

This commit is contained in:
Codex
2026-07-13 09:56:58 +02:00
parent 982d45b1bf
commit 49b45ed868
4 changed files with 511 additions and 12 deletions
+32 -11
View File
@@ -39,6 +39,7 @@ import { status } from "./control-plane";
import { arrayRecords, displayValue, isRecord, nextPagedResourceCommand, parseTaskManifest, pickCompact, relativeAge, renderFailureLines, renderResourceNextLines, renderTable, resourceName, shortId, stringOrDash, truncateMultiline, truncateOneLine } from "./options";
import { activeAgentRunRestTarget, agentRunRestRequest, runAgentRunRestCommand } from "./rest-bridge";
import { renderSessionSendResult } from "./session-send-render";
import { compactTargetTaskMutationPayload } from "./target-task-output";
import { record, stringOrNull } from "./utils";
export function resolveAgentRunCancelPolicyTarget(config: UniDeskConfig | null, options: AgentRunResourceOptions): { configPath: string; spec: AgentRunLaneSpec; source: "selected-lane" | "default-lane" } | null {
@@ -479,7 +480,10 @@ export function renderResultSummary(command: string, raw: Record<string, unknown
export function renderMutationSummary(command: string, raw: Record<string, unknown>, options: AgentRunResourceOptions, headline: string, overrideNextLines?: string[]): RenderedCliResult {
if (options.raw) return renderMachine(command, raw, "json", raw.ok !== false);
if (options.output === "json" || options.output === "yaml") return renderMachine(command, raw, options.output, raw.ok !== false);
const targetTaskPayload = compactTargetTaskMutationPayload(raw);
if (options.output === "json" || options.output === "yaml") {
return renderMachine(command, targetTaskPayload ?? raw, options.output, raw.ok !== false);
}
const data = record(innerData(raw));
const id = stringOrNull(data.id) ?? stringOrNull(data.taskId) ?? stringOrNull(data.sessionId);
const lines = [
@@ -496,12 +500,13 @@ export function renderMutationSummary(command: string, raw: Record<string, unkno
if (decision !== null) lines.push(`Decision: ${decision}`);
if (internalCommandType !== null) lines.push(`InternalCommandType: ${internalCommandType}`);
lines.push(...renderCancelLifecycleMutationLines(record(data.cancelLifecycle ?? raw.cancelLifecycle)));
lines.push(...renderTargetTaskMutationLines(record(data.targetTask ?? raw.targetTask)));
lines.push(...renderAipodBindingMutationLines(record(data.aipodBinding ?? raw.aipodBinding)));
lines.push(...renderAutoDispatchMutationLines(record(data.autoDispatch ?? raw.autoDispatch)));
lines.push(...renderTargetTaskMutationLines(record(targetTaskPayload?.targetTask ?? data.targetTask ?? raw.targetTask)));
lines.push(...renderAipodBindingMutationLines(record(targetTaskPayload?.aipodBinding ?? data.aipodBinding ?? raw.aipodBinding)));
lines.push(...renderAutoDispatchMutationLines(record(targetTaskPayload?.autoDispatch ?? data.autoDispatch ?? raw.autoDispatch)));
lines.push(...renderDispatchPlanMutationLines(record(data.dispatchPlan ?? raw.dispatchPlan)));
const next = record(raw.next ?? data.next);
const nextLines = (overrideNextLines ?? Object.values(next).map(String)).filter((line) => line.length > 0).slice(0, 5);
const next = record(targetTaskPayload?.next ?? raw.next ?? data.next);
const nextLimit = targetTaskPayload === null ? 5 : 8;
const nextLines = (overrideNextLines ?? Object.values(next).map(String)).filter((line) => line.length > 0).slice(0, nextLimit);
if (nextLines.length > 0) lines.push("", "Next:", ...nextLines.map((line) => ` ${line}`));
return renderedCliResult(raw.ok !== false, command, lines.join("\n"));
}
@@ -525,8 +530,11 @@ export function renderAipodBindingMutationLines(binding: Record<string, unknown>
const workspaceRef = record(binding.workspaceRef);
const resourceBundleRef = record(binding.resourceBundleRef);
const session = record(binding.session);
const providerCredentials = arrayRecords(binding.providerCredentials);
const toolCredentials = arrayRecords(binding.toolCredentials);
const credentialRefs = record(binding.credentialRefs);
const providerSummary = record(credentialRefs.provider);
const toolSummary = record(credentialRefs.tool);
const providerCredentials = arrayRecords(binding.providerCredentials ?? providerSummary.items);
const toolCredentials = arrayRecords(binding.toolCredentials ?? toolSummary.items);
const lines = [
"",
"AipodBinding:",
@@ -535,6 +543,16 @@ export function renderAipodBindingMutationLines(binding: Record<string, unknown>
` ResourceBundle: ${displayValue(resourceBundleRef.repoUrl)} ref=${displayValue(resourceBundleRef.ref)} inherited=${displayValue(resourceBundleRef.inheritedFromAipod)}`,
` Session: ${displayValue(session.sessionId)} source=${displayValue(session.identitySource)}`,
];
if (Object.keys(credentialRefs).length > 0) {
const providerPresence = `${displayValue(providerSummary.presentCount)}/${displayValue(providerSummary.count)}`;
const toolPresence = `${displayValue(toolSummary.presentCount)}/${displayValue(toolSummary.count)}`;
lines.push([
` SecretRefs: total=${displayValue(credentialRefs.totalCount)}`,
`allPresent=${displayValue(credentialRefs.allPresent)}`,
`provider=${providerPresence}`,
`tool=${toolPresence}`,
].join(" "));
}
for (const credential of providerCredentials.slice(0, 4)) {
const secretRef = record(credential.secretRef);
lines.push(` ProviderSecretRef: ${displayValue(secretRef.namespace)}/${displayValue(secretRef.name)} keys=${displayValue(secretRef.keys)} present=${displayValue(credential.bindingPresent)}`);
@@ -553,9 +571,12 @@ export function renderAutoDispatchMutationLines(autoDispatch: Record<string, unk
"",
"AutoDispatch:",
` Decision: ${displayValue(autoDispatch.decision)} mutation=${displayValue(autoDispatch.mutation)} duplicatePrevented=${displayValue(autoDispatch.duplicateDispatchPrevented)}`,
` Task: ${displayValue(identity.taskId)} state=${displayValue(identity.taskState)} attempt=${displayValue(identity.attemptId)}`,
` Run: ${displayValue(identity.runId)} command=${displayValue(identity.commandId)} runner=${displayValue(identity.runnerJobId)}`,
` Session: ${displayValue(identity.sessionId)}`,
` Task: ${displayValue(identity.taskId)} state=${displayValue(identity.taskState)}`,
` Attempt: ${displayValue(identity.attemptId)} state=${displayValue(identity.attemptState)}`,
` Run: ${displayValue(identity.runId)} state=${displayValue(identity.runState)}`,
` Command: ${displayValue(identity.commandId)} state=${displayValue(identity.commandState)}`,
` Runner: ${displayValue(identity.runnerJobId)} state=${displayValue(identity.runnerState)}`,
` Session: ${displayValue(identity.sessionId)} state=${displayValue(identity.sessionState)}`,
];
}
+271
View File
@@ -0,0 +1,271 @@
type TargetDispatchDecision = "dispatched" | "idempotent-replay" | "concurrent-replay";
export function compactTargetTaskMutationPayload(raw: Record<string, unknown>): Record<string, unknown> | null {
const data = asRecord(innerData(raw));
const targetTask = firstRecord(data.targetTask, raw.targetTask);
const aipodBinding = firstRecord(data.aipodBinding, raw.aipodBinding);
const autoDispatch = firstRecord(data.autoDispatch, raw.autoDispatch);
const decision = boundedText(autoDispatch.decision, 80);
if (Object.keys(targetTask).length === 0 || !isTargetDispatchDecision(decision)) return null;
const identity = compactTargetTaskIdentity(data, autoDispatch);
const targetSummary = compactTargetTaskSummary(targetTask);
const aipodSummary = compactTargetAipodBinding(aipodBinding);
const autoDispatchSummary = {
enabled: autoDispatch.enabled === true,
decision,
mutation: booleanOrNull(autoDispatch.mutation),
duplicateDispatchPrevented: booleanOrNull(autoDispatch.duplicateDispatchPrevented),
identity,
valuesPrinted: false,
};
const next = targetTaskIdentityNext(identity, targetSummary, aipodSummary);
return {
ok: raw.ok !== false,
action: boundedText(raw.action, 120) ?? "queue-submit-auto-dispatch",
data: {
...identity,
state: identity.taskState,
mutation: booleanOrNull(data.mutation) ?? autoDispatchSummary.mutation,
autoDispatch: autoDispatchSummary,
valuesPrinted: false,
},
targetTask: targetSummary,
aipodBinding: aipodSummary,
autoDispatch: autoDispatchSummary,
next,
valuesPrinted: false,
};
}
function compactTargetTaskIdentity(
data: Record<string, unknown>,
autoDispatch: Record<string, unknown>,
): Record<string, unknown> {
const declared = asRecord(autoDispatch.identity);
const task = asRecord(data.task);
const attempt = firstRecord(data.latestAttempt, task.latestAttempt);
const run = asRecord(data.run);
const command = asRecord(data.command);
const runner = firstRecord(data.runner, data.runnerJob);
const session = firstRecord(data.session, run.sessionRef, task.sessionRef);
return {
taskId: firstText(200, declared.taskId, data.taskId, task.id),
taskState: firstText(80, declared.taskState, data.taskState, task.state, task.status),
attemptId: firstText(200, declared.attemptId, data.attemptId, attempt.attemptId, attempt.id),
attemptState: firstText(80, data.attemptState, attempt.state, attempt.status),
runId: firstText(200, declared.runId, data.runId, run.id),
runState: firstText(80, data.runState, run.state, run.status),
commandId: firstText(200, declared.commandId, data.commandId, command.id),
commandState: firstText(80, data.commandState, command.state, command.status),
runnerJobId: firstText(200, declared.runnerJobId, data.runnerJobId, attempt.runnerJobId, runner.id),
runnerState: firstText(80, data.runnerState, attempt.runnerState, runner.state, runner.status),
sessionId: firstText(240, declared.sessionId, data.sessionId, attempt.sessionId, session.sessionId, session.id),
sessionState: firstText(80, data.sessionState, session.state, session.status),
valuesPrinted: false,
};
}
function compactTargetTaskSummary(targetTask: Record<string, unknown>): Record<string, unknown> {
const workspace = asRecord(targetTask.workspace);
return {
mdtodoId: boundedText(targetTask.mdtodoId, 80),
target: boundedText(targetTask.target, 120),
targetWorkspace: boundedText(targetTask.targetWorkspace, 360),
targetRoute: boundedText(targetTask.targetRoute, 480),
projectId: boundedText(targetTask.projectId, 240),
repository: boundedText(targetTask.repository, 240),
ref: boundedText(targetTask.ref, 240),
verifiedCommit: boundedText(targetTask.verifiedCommit, 160),
refRelation: boundedText(targetTask.refRelation, 120),
workspace: {
branch: boundedText(workspace.branch, 240),
headCommit: boundedText(workspace.headCommit, 160),
clean: booleanOrNull(workspace.clean),
valuesPrinted: false,
},
valuesPrinted: false,
};
}
function compactTargetAipodBinding(binding: Record<string, unknown>): Record<string, unknown> {
const workspaceRef = asRecord(binding.workspaceRef);
const resourceBundleRef = asRecord(binding.resourceBundleRef);
const session = asRecord(binding.session);
const provider = compactCredentialPresence(binding.providerCredentials, "provider", 4);
const tool = compactCredentialPresence(binding.toolCredentials, "tool", 6);
return {
aipod: boundedText(binding.aipod, 160),
node: boundedText(binding.node, 120),
lane: boundedText(binding.lane, 120),
namespace: boundedText(binding.namespace, 160),
projectId: boundedText(binding.projectId, 240),
providerId: boundedText(binding.providerId, 160),
backendProfile: boundedText(binding.backendProfile, 160),
workspaceRef: {
kind: boundedText(workspaceRef.kind, 80),
path: boundedText(workspaceRef.path, 360),
valid: booleanOrNull(workspaceRef.valid),
valuesPrinted: false,
},
resourceBundleRef: {
kind: boundedText(resourceBundleRef.kind, 80),
repoUrl: boundedRepositoryUrl(resourceBundleRef.repoUrl),
ref: boundedText(resourceBundleRef.ref, 240),
inheritedFromAipod: booleanOrNull(resourceBundleRef.inheritedFromAipod),
credentialRefPresent: booleanOrNull(resourceBundleRef.credentialRefPresent),
valuesPrinted: false,
},
session: {
sessionId: boundedText(session.sessionId, 240),
identitySource: boundedText(session.identitySource, 120),
valuesPrinted: false,
},
credentialRefs: {
totalCount: provider.count + tool.count,
allPresent: provider.missingCount + tool.missingCount === 0,
provider,
tool,
valuesPrinted: false,
},
valuesPrinted: false,
};
}
function compactCredentialPresence(value: unknown, kind: "provider" | "tool", limit: number): {
count: number;
presentCount: number;
missingCount: number;
omittedCount: number;
items: Record<string, unknown>[];
valuesPrinted: false;
} {
const source = asRecords(value);
const presentCount = source.filter((item) => item.bindingPresent === true).length;
const items = source.slice(0, limit).map((item) => {
const secretRef = asRecord(item.secretRef);
return {
...(kind === "provider"
? { profile: boundedText(item.profile, 160) }
: {
tool: boundedText(item.tool, 160),
purpose: boundedText(item.purpose, 200),
}),
secretRef: {
namespace: boundedText(secretRef.namespace, 160),
name: boundedText(secretRef.name, 200),
keys: boundedStrings(secretRef.keys, 6, 80),
valuesPrinted: false,
},
bindingPresent: item.bindingPresent === true,
valuesPrinted: false,
};
});
return {
count: source.length,
presentCount,
missingCount: source.length - presentCount,
omittedCount: source.length - items.length,
items,
valuesPrinted: false,
};
}
function targetTaskIdentityNext(
identity: Record<string, unknown>,
targetTask: Record<string, unknown>,
aipodBinding: Record<string, unknown>,
): Record<string, unknown> {
const taskId = asString(identity.taskId);
const runId = asString(identity.runId);
const commandId = asString(identity.commandId);
const runnerJobId = asString(identity.runnerJobId);
const sessionId = asString(identity.sessionId);
const target = asString(targetTask.target) ?? asString(aipodBinding.node);
const lane = asString(aipodBinding.lane);
const suffix = [
target === null ? null : `--target ${target}`,
lane === null ? null : `--lane ${lane}`,
].filter((value): value is string => value !== null).join(" ");
const scoped = (command: string): string => suffix.length === 0 ? command : `${command} ${suffix}`;
return {
...(taskId === null ? {} : {
task: scoped(`bun scripts/cli.ts agentrun describe task/${taskId}`),
attempts: scoped(`bun scripts/cli.ts agentrun get attempts --task ${taskId}`),
}),
...(runId === null ? {} : {
events: scoped(`bun scripts/cli.ts agentrun events run/${runId} --after-seq 0 --limit 20`),
}),
...(runId === null || commandId === null ? {} : {
result: scoped(`bun scripts/cli.ts agentrun result run/${runId} --command ${commandId}`),
}),
...(runnerJobId === null || runId === null ? {} : {
runner: scoped(`bun scripts/cli.ts agentrun describe runnerjob/${runnerJobId} --run ${runId}`),
}),
...(sessionId === null ? {} : {
logs: scoped(`bun scripts/cli.ts agentrun logs session/${sessionId} --tail 120`),
}),
};
}
function innerData(value: Record<string, unknown>): unknown {
return value.data ?? value;
}
function firstRecord(...values: unknown[]): Record<string, unknown> {
for (const value of values) {
const item = asRecord(value);
if (Object.keys(item).length > 0) return item;
}
return {};
}
function asRecord(value: unknown): Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
? value as Record<string, unknown>
: {};
}
function asRecords(value: unknown): Record<string, unknown>[] {
return Array.isArray(value) ? value.map(asRecord).filter((item) => Object.keys(item).length > 0) : [];
}
function asString(value: unknown): string | null {
return typeof value === "string" && value.length > 0 ? value : null;
}
function firstText(maxChars: number, ...values: unknown[]): string | null {
for (const value of values) {
const text = boundedText(value, maxChars);
if (text !== null) return text;
}
return null;
}
function boundedText(value: unknown, maxChars: number): string | null {
const text = asString(value);
if (text === null) return null;
const oneLine = text.replace(/\s+/gu, " ").trim();
return oneLine.length <= maxChars ? oneLine : `${oneLine.slice(0, Math.max(0, maxChars - 1))}`;
}
function boundedStrings(value: unknown, limit: number, maxChars: number): string[] {
return Array.isArray(value)
? value.slice(0, limit).map((item) => boundedText(item, maxChars)).filter((item): item is string => item !== null)
: [];
}
function boundedRepositoryUrl(value: unknown): string | null {
const text = asString(value);
if (text === null) return null;
return boundedText(text.replace(/^(https?:\/\/)[^@/]+@/u, "$1[redacted]@"), 320);
}
function booleanOrNull(value: unknown): boolean | null {
return typeof value === "boolean" ? value : null;
}
function isTargetDispatchDecision(value: string | null): value is TargetDispatchDecision {
return value === "dispatched" || value === "idempotent-replay" || value === "concurrent-replay";
}