fix: 增强长 turn liveness 可见性
This commit is contained in:
+105
-1
@@ -114,6 +114,92 @@ async function refreshQueueTaskRecordForRead(store: AgentRunStore, task: QueueTa
|
||||
}
|
||||
}
|
||||
|
||||
async function queueCommanderForRead(store: AgentRunStore, queue: string | undefined, readerId: string | null): Promise<JsonValue> {
|
||||
const snapshot = await store.queueCommander(queue, readerId) as unknown as JsonRecord;
|
||||
const items = Array.isArray(snapshot.items) ? snapshot.items : [];
|
||||
const enrichedItems = await Promise.all(items.map(async (item) => {
|
||||
const task = asJsonRecord(item);
|
||||
if (!task) return item as JsonValue;
|
||||
const supervisor = await queueTaskSupervisor(store, task);
|
||||
return supervisor ? { ...task, supervisor, valuesPrinted: false } : task;
|
||||
}));
|
||||
return { ...snapshot, items: enrichedItems, valuesPrinted: false } as JsonValue;
|
||||
}
|
||||
|
||||
async function queueTaskSupervisor(store: AgentRunStore, task: JsonRecord): Promise<JsonRecord | null> {
|
||||
const attempt = asJsonRecord(task.latestAttempt);
|
||||
const runId = stringJsonValue(attempt?.runId);
|
||||
if (!runId) return null;
|
||||
try {
|
||||
const result = await buildRunResult(store, runId, stringJsonValue(attempt?.commandId) ?? undefined);
|
||||
const liveness = asJsonRecord(result.liveness);
|
||||
const lastActivity = asJsonRecord(liveness?.lastActivity ?? liveness?.lastCommandActivity);
|
||||
const timeoutBudget = asJsonRecord(liveness?.timeoutBudget);
|
||||
return {
|
||||
runId: stringJsonValue(result.runId),
|
||||
commandId: stringJsonValue(result.commandId),
|
||||
status: stringJsonValue(result.status),
|
||||
terminalStatus: stringJsonValue(result.terminalStatus),
|
||||
failureKind: stringJsonValue(result.failureKind),
|
||||
phase: stringJsonValue(liveness?.phase),
|
||||
active: liveness?.active === true,
|
||||
lastSeq: numberJsonValue(liveness?.lastSeq ?? result.lastSeq),
|
||||
lastActivity: lastActivity ? compactActivity(lastActivity) : null,
|
||||
timeoutBudget: timeoutBudget ? compactTimeoutBudget(timeoutBudget) : null,
|
||||
recoveryActions: compactRecoveryActions(liveness?.recoveryActions),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
} catch (error) {
|
||||
return { phase: "unavailable", failureKind: "infra-failed", message: error instanceof Error ? error.message : String(error), valuesPrinted: false };
|
||||
}
|
||||
}
|
||||
|
||||
function compactActivity(activity: JsonRecord): JsonRecord {
|
||||
return {
|
||||
sourceSeq: numberJsonValue(activity.sourceSeq ?? activity.seq),
|
||||
eventId: stringJsonValue(activity.eventId),
|
||||
activityKind: stringJsonValue(activity.activityKind),
|
||||
type: stringJsonValue(activity.type),
|
||||
status: stringJsonValue(activity.status),
|
||||
toolName: stringJsonValue(activity.toolName),
|
||||
itemId: stringJsonValue(activity.itemId),
|
||||
ageMs: numberJsonValue(activity.ageMs),
|
||||
summary: boundedJsonString(activity.summary, 180),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function compactTimeoutBudget(budget: JsonRecord): JsonRecord {
|
||||
return {
|
||||
state: stringJsonValue(budget.state),
|
||||
timeoutMs: numberJsonValue(budget.timeoutMs),
|
||||
elapsedMs: numberJsonValue(budget.elapsedMs),
|
||||
remainingMs: numberJsonValue(budget.remainingMs),
|
||||
startedAt: stringJsonValue(budget.startedAt),
|
||||
source: stringJsonValue(budget.source),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function compactRecoveryActions(value: JsonValue | undefined): JsonValue[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value.slice(0, 5).map((item) => {
|
||||
const action = asJsonRecord(item);
|
||||
if (!action) return { action: "unknown", valuesPrinted: false };
|
||||
return {
|
||||
action: stringJsonValue(action.action),
|
||||
reason: stringJsonValue(action.reason),
|
||||
runId: stringJsonValue(action.runId),
|
||||
commandId: stringJsonValue(action.commandId),
|
||||
sessionId: stringJsonValue(action.sessionId),
|
||||
afterSeq: numberJsonValue(action.afterSeq),
|
||||
command: boundedJsonString(action.command, 220),
|
||||
hint: boundedJsonString(action.hint, 220),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function route({ method, url, body, store, sourceCommit, runnerJobDefaults, sessionPvcDefaults, providerProfileDefaults }: { method: string; url: URL; body: unknown; store: AgentRunStore; sourceCommit: string; runnerJobDefaults?: NonNullable<ManagerServerOptions["runnerJobDefaults"]>; sessionPvcDefaults?: NonNullable<ManagerServerOptions["sessionPvcOptions"]>; providerProfileDefaults?: NonNullable<ManagerServerOptions["providerProfileOptions"]> }): Promise<JsonValue> {
|
||||
const path = url.pathname;
|
||||
if (method === "GET" && (path === "/health" || path === "/health/live" || path === "/health/readiness")) {
|
||||
@@ -334,7 +420,7 @@ async function route({ method, url, body, store, sourceCommit, runnerJobDefaults
|
||||
if (method === "GET" && path === "/api/v1/queue/commander") {
|
||||
const queue = url.searchParams.get("queue") ?? undefined;
|
||||
await refreshRunningQueueTasksForRead(store, queue);
|
||||
return await store.queueCommander(queue, url.searchParams.get("readerId")) as unknown as JsonValue;
|
||||
return await queueCommanderForRead(store, queue, url.searchParams.get("readerId"));
|
||||
}
|
||||
if (method === "POST" && path === "/api/v1/runs") return await store.createRun(validateCreateRun(body)) as unknown as JsonValue;
|
||||
const runMatch = path.match(/^\/api\/v1\/runs\/([^/]+)$/u);
|
||||
@@ -460,6 +546,24 @@ function numberField(record: JsonRecord, key: string, fallback: number): number
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
||||
}
|
||||
|
||||
function asJsonRecord(value: unknown): JsonRecord | null {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as JsonRecord : null;
|
||||
}
|
||||
|
||||
function stringJsonValue(value: JsonValue | undefined): string | null {
|
||||
return typeof value === "string" && value.length > 0 ? value : null;
|
||||
}
|
||||
|
||||
function numberJsonValue(value: JsonValue | undefined): number | null {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function boundedJsonString(value: JsonValue | undefined, limit: number): string | null {
|
||||
if (typeof value !== "string" || value.length === 0) return null;
|
||||
const normalized = value.replace(/\s+/gu, " ").trim();
|
||||
return normalized.length > limit ? `${normalized.slice(0, Math.max(0, limit - 3))}...` : normalized;
|
||||
}
|
||||
|
||||
function stringField(record: JsonRecord, key: string): string {
|
||||
const value = record[key];
|
||||
if (typeof value !== "string" || value.trim().length === 0) throw new AgentRunError("schema-invalid", `${key} is required`, { httpStatus: 400 });
|
||||
|
||||
Reference in New Issue
Block a user