feat: 为 gitbundle 装配 required skills (#138)
* Add gitbundle required skills validation * fix: 限定 required skill blocked result 覆盖 --------- Co-authored-by: AgentRun Codex <agentrun@example.invalid> Co-authored-by: Codex <codex@pikas.tech>
This commit is contained in:
+63
-8
@@ -1,5 +1,5 @@
|
||||
import type { AgentRunStore } from "./store.js";
|
||||
import type { CommandRecord, JsonRecord, JsonValue, RunEvent, RunRecord, RunnerJobRecord, TerminalStatus } from "../common/types.js";
|
||||
import type { CommandRecord, FailureKind, JsonRecord, JsonValue, RunEvent, RunRecord, RunnerJobRecord, TerminalStatus } from "../common/types.js";
|
||||
import { boundedTextSummary, outputBytesFromPayload, outputTruncatedFromPayload } from "../common/output.js";
|
||||
|
||||
const maxToolCallSummaryItems = 40;
|
||||
@@ -35,12 +35,14 @@ export async function buildRunResult(store: AgentRunStore, runId: string, comman
|
||||
const latestJob = jobs.at(-1) ?? null;
|
||||
const commandTerminal = command ? terminalFromCommand(command) : null;
|
||||
const terminalEventStatus = terminalFromEvents(scopedEvents);
|
||||
const terminal = commandTerminal ?? terminalEventStatus ?? run.terminalStatus;
|
||||
const terminalSource = commandTerminal ? "command-record" : terminalEventStatus ? "terminal_status-event" : run.terminalStatus ? "run-record" : "none";
|
||||
const failureKind = resultFailureKind(run, command, scopedEvents, terminal);
|
||||
const preliminaryTerminal = commandTerminal ?? terminalEventStatus ?? run.terminalStatus;
|
||||
const failureKind = resultFailureKind(run, command, scopedEvents, preliminaryTerminal);
|
||||
const terminal = resultTerminal(commandTerminal, terminalEventStatus, run.terminalStatus, failureKind);
|
||||
const terminalSource = resultTerminalSource(commandTerminal, terminalEventStatus, run.terminalStatus, failureKind);
|
||||
const failureMessage = resultFailureMessage(run, command, scopedEvents, terminal);
|
||||
const failureDetails = resultFailureDetails(scopedEvents, terminal);
|
||||
const reply = assistantReply(scopedEvents);
|
||||
const blocker = terminal === "blocked" || terminal === "failed" ? { failureKind, message: failureMessage } : null;
|
||||
const blocker = terminal === "blocked" || terminal === "failed" ? { failureKind, message: failureMessage, details: failureDetails } : null;
|
||||
const liveness = livenessSnapshot(run, command, events, scopedEvents, terminal);
|
||||
const steerDelivery = command?.type === "steer" ? steerDeliverySummary(events, command.id) : null;
|
||||
return {
|
||||
@@ -72,6 +74,7 @@ export async function buildRunResult(store: AgentRunStore, runId: string, comman
|
||||
finalAssistantOutputTruncated: reply.outputTruncated,
|
||||
failureKind,
|
||||
failureMessage,
|
||||
failureDetails,
|
||||
blocker,
|
||||
liveness,
|
||||
...(steerDelivery ? { steerDelivery } : {}),
|
||||
@@ -292,25 +295,64 @@ function terminalFromCommand(command: CommandRecord): TerminalStatus | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function resultTerminal(commandTerminal: TerminalStatus | null, terminalEventStatus: TerminalStatus | null, runTerminalStatus: TerminalStatus | null, failureKind: FailureKind | null): TerminalStatus | null {
|
||||
if (commandTerminal === "failed" && terminalEventStatus === "blocked" && failureKind === "required-skill-unavailable") return "blocked";
|
||||
return commandTerminal ?? terminalEventStatus ?? runTerminalStatus;
|
||||
}
|
||||
|
||||
function resultTerminalSource(commandTerminal: TerminalStatus | null, terminalEventStatus: TerminalStatus | null, runTerminalStatus: TerminalStatus | null, failureKind: FailureKind | null): string {
|
||||
if (commandTerminal === "failed" && terminalEventStatus === "blocked" && failureKind === "required-skill-unavailable") return "terminal_status-event";
|
||||
if (commandTerminal) return "command-record";
|
||||
if (terminalEventStatus) return "terminal_status-event";
|
||||
if (runTerminalStatus) return "run-record";
|
||||
return "none";
|
||||
}
|
||||
|
||||
function eventsForCommand(events: RunEvent[], commandId: string): RunEvent[] {
|
||||
const scoped = events.filter((event) => event.payload.commandId === commandId || typeof event.payload.commandId !== "string");
|
||||
return scoped.length > 0 ? scoped : events;
|
||||
}
|
||||
|
||||
function failureKindFromEvents(events: RunEvent[]): string | null {
|
||||
function failureKindFromEvents(events: RunEvent[]): FailureKind | null {
|
||||
for (const event of [...events].reverse()) {
|
||||
const value = event.payload.failureKind;
|
||||
if (typeof value === "string") return value;
|
||||
if (isFailureKind(value)) return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resultFailureKind(run: RunRecord, command: CommandRecord | null, events: RunEvent[], terminal: TerminalStatus | null): string | null {
|
||||
function resultFailureKind(run: RunRecord, command: CommandRecord | null, events: RunEvent[], terminal: TerminalStatus | null): FailureKind | null {
|
||||
if (terminal === "completed") return null;
|
||||
if (command) return failureKindFromEvents(events);
|
||||
return run.failureKind ?? failureKindFromEvents(events);
|
||||
}
|
||||
|
||||
function isFailureKind(value: unknown): value is FailureKind {
|
||||
return typeof value === "string" && [
|
||||
"cancelled",
|
||||
"tenant-policy-denied",
|
||||
"secret-unavailable",
|
||||
"prompt-unavailable",
|
||||
"prompt-too-large",
|
||||
"required-skill-unavailable",
|
||||
"skill-unavailable",
|
||||
"runner-lease-conflict",
|
||||
"backend-failed",
|
||||
"backend-timeout",
|
||||
"backend-response-invalid",
|
||||
"thread-resume-failed",
|
||||
"provider-auth-failed",
|
||||
"provider-invalid-tool-call",
|
||||
"provider-compact-unsupported",
|
||||
"provider-rate-limited",
|
||||
"provider-unavailable",
|
||||
"provider-stream-disconnected",
|
||||
"provider-refused-retry-recovered",
|
||||
"infra-failed",
|
||||
"schema-invalid",
|
||||
].includes(value);
|
||||
}
|
||||
|
||||
function messageFromEvents(events: RunEvent[]): string | null {
|
||||
for (const event of [...events].reverse()) {
|
||||
const value = event.payload.message;
|
||||
@@ -325,6 +367,18 @@ function resultFailureMessage(run: RunRecord, command: CommandRecord | null, eve
|
||||
return run.failureMessage ?? messageFromEvents(events);
|
||||
}
|
||||
|
||||
function detailsFromEvents(events: RunEvent[]): JsonRecord | null {
|
||||
for (const event of [...events].reverse()) {
|
||||
const value = event.payload.details;
|
||||
if (typeof value === "object" && value !== null && !Array.isArray(value)) return value as JsonRecord;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resultFailureDetails(events: RunEvent[], terminal: TerminalStatus | null): JsonRecord | null {
|
||||
return terminal === "completed" ? null : detailsFromEvents(events);
|
||||
}
|
||||
|
||||
function assistantReply(events: RunEvent[]): AssistantReplySummary {
|
||||
const assistantEvents = events.filter((event) => event.type === "assistant_message");
|
||||
const latestAuthoritative = [...assistantEvents].reverse().find((event) => (event.payload.replyAuthority === true || event.payload.final === true) && textPayload(event.payload).length > 0);
|
||||
@@ -474,6 +528,7 @@ function resourceBundleSummary(run: RunRecord, events: RunEvent[]): JsonRecord |
|
||||
valuesPrinted: false,
|
||||
},
|
||||
promptRefs: run.resourceBundleRef.promptRefs ? { count: run.resourceBundleRef.promptRefs.length, names: run.resourceBundleRef.promptRefs.map((item) => item.name), required: run.resourceBundleRef.promptRefs.filter((item) => item.required === true).map((item) => item.name), valuesPrinted: false } : { count: 0, names: [], required: [], valuesPrinted: false },
|
||||
requiredSkills: run.resourceBundleRef.requiredSkills ? { count: run.resourceBundleRef.requiredSkills.length, names: run.resourceBundleRef.requiredSkills.map((item) => item.name), valuesPrinted: false } : { count: 0, names: [], valuesPrinted: false },
|
||||
materialized: materialized as JsonValue,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -761,6 +761,7 @@ export function summarizeResourceBundleRef(resourceBundleRef: RunRecord["resourc
|
||||
valuesPrinted: false,
|
||||
},
|
||||
promptRefs: resourceBundleRef.promptRefs ? { count: resourceBundleRef.promptRefs.length, names: resourceBundleRef.promptRefs.map((item) => item.name), required: resourceBundleRef.promptRefs.filter((item) => item.required === true).map((item) => item.name), valuesPrinted: false } : { count: 0, names: [], required: [], valuesPrinted: false },
|
||||
requiredSkills: resourceBundleRef.requiredSkills ? { count: resourceBundleRef.requiredSkills.length, names: resourceBundleRef.requiredSkills.map((item) => item.name), valuesPrinted: false } : { count: 0, names: [], valuesPrinted: false },
|
||||
submodules: resourceBundleRef.submodules ?? false,
|
||||
lfs: resourceBundleRef.lfs ?? false,
|
||||
credentialRef: resourceBundleRef.credentialRef ? { name: resourceBundleRef.credentialRef.name, namespace: resourceBundleRef.credentialRef.namespace ?? null, keys: resourceBundleRef.credentialRef.keys ?? [], valuesPrinted: false } : null,
|
||||
|
||||
Reference in New Issue
Block a user