feat: 补齐 HWLAB 手动调度能力

This commit is contained in:
Codex
2026-06-01 11:40:08 +08:00
parent eb5e0f57a6
commit 62846f6369
25 changed files with 1159 additions and 70 deletions
+137
View File
@@ -0,0 +1,137 @@
import type { AgentRunStore } from "./store.js";
import type { CommandRecord, JsonRecord, JsonValue, RunEvent, RunRecord, RunnerJobRecord, TerminalStatus } from "../common/types.js";
export async function buildRunResult(store: AgentRunStore, runId: string, commandId?: string): Promise<JsonRecord> {
const run = await store.getRun(runId);
const command = await selectCommand(store, runId, commandId);
const events = await store.listEvents(runId, 0, 500);
const jobs = await store.listRunnerJobs(runId, command?.id);
const latestJob = jobs.at(-1) ?? null;
const terminal = terminalFromEvents(events) ?? run.terminalStatus;
const failureKind = run.failureKind ?? failureKindFromEvents(events);
const reply = assistantReply(events);
const blocker = terminal === "blocked" || terminal === "failed" ? { failureKind, message: run.failureMessage ?? messageFromEvents(events) } : null;
return {
runId: run.id,
commandId: command?.id ?? commandId ?? null,
attemptId: latestJob?.attemptId ?? attemptFromEvents(events),
runnerId: latestJob?.runnerId ?? null,
jobName: latestJob?.jobName ?? null,
namespace: latestJob?.namespace ?? null,
status: command?.state ?? run.status,
runStatus: run.status,
commandState: command?.state ?? null,
terminalStatus: terminal,
reply,
failureKind,
failureMessage: run.failureMessage ?? messageFromEvents(events),
blocker,
lastSeq: events.at(-1)?.seq ?? 0,
eventCount: events.length,
artifactSummary: artifactSummary(events),
sessionRef: sessionSummary(run),
resourceBundleRef: resourceBundleSummary(run, events),
runnerJobCount: jobs.length,
};
}
async function selectCommand(store: AgentRunStore, runId: string, commandId?: string): Promise<CommandRecord | null> {
if (commandId) {
const command = await store.getCommand(commandId);
return command.runId === runId ? command : null;
}
const commands = await store.listCommands(runId, 0, 100);
return commands.at(-1) ?? null;
}
function terminalFromEvents(events: RunEvent[]): TerminalStatus | null {
for (const event of [...events].reverse()) {
if (event.type !== "terminal_status") continue;
const value = event.payload.terminalStatus;
if (value === "completed" || value === "failed" || value === "blocked" || value === "cancelled") return value;
}
return null;
}
function failureKindFromEvents(events: RunEvent[]): string | null {
for (const event of [...events].reverse()) {
const value = event.payload.failureKind;
if (typeof value === "string") return value;
}
return null;
}
function messageFromEvents(events: RunEvent[]): string | null {
for (const event of [...events].reverse()) {
const value = event.payload.message;
if (typeof value === "string" && value.length > 0) return value;
}
return null;
}
function assistantReply(events: RunEvent[]): string {
return events
.filter((event) => event.type === "assistant_message")
.map((event) => textPayload(event.payload))
.filter((text) => text.length > 0)
.join("");
}
function textPayload(payload: JsonRecord): string {
for (const key of ["text", "content", "delta"]) {
const value = payload[key];
if (typeof value === "string") return value;
}
return "";
}
function artifactSummary(events: RunEvent[]): JsonRecord {
let commandOutputEvents = 0;
let diffEvents = 0;
let toolCallEvents = 0;
let outputChars = 0;
let truncatedEvents = 0;
for (const event of events) {
if (event.type === "command_output") {
commandOutputEvents += 1;
const text = textPayload(event.payload);
outputChars += text.length;
if (event.payload.truncated === true) truncatedEvents += 1;
}
if (event.type === "diff") diffEvents += 1;
if (event.type === "tool_call") toolCallEvents += 1;
}
return { commandOutputEvents, diffEvents, toolCallEvents, outputChars, truncatedEvents };
}
function attemptFromEvents(events: RunEvent[]): string | null {
for (const event of [...events].reverse()) {
const value = event.payload.attemptId;
if (typeof value === "string") return value;
}
return null;
}
function sessionSummary(run: RunRecord): JsonRecord | null {
if (!run.sessionRef) return null;
return {
sessionId: run.sessionRef.sessionId,
conversationId: run.sessionRef.conversationId ?? null,
threadId: run.sessionRef.threadId ?? null,
expiresAt: run.sessionRef.expiresAt ?? null,
metadataKeys: Object.keys(run.sessionRef.metadata ?? {}).sort(),
valuesPrinted: false,
};
}
function resourceBundleSummary(run: RunRecord, events: RunEvent[]): JsonRecord | null {
if (!run.resourceBundleRef) return null;
const materialized = events.find((event) => event.type === "backend_status" && event.payload.phase === "resource-bundle-materialized")?.payload ?? null;
return {
kind: run.resourceBundleRef.kind,
repoUrl: run.resourceBundleRef.repoUrl,
commitId: run.resourceBundleRef.commitId,
subdir: run.resourceBundleRef.subdir ?? null,
materialized: materialized as JsonValue,
};
}