fix: 校正实时探针可选工具事件合同

This commit is contained in:
Codex
2026-07-11 20:56:01 +02:00
parent 127cd8c220
commit 98e4cde51f
6 changed files with 196 additions and 15 deletions
@@ -747,6 +747,7 @@ function realtimeFanoutEffectiveProfile(profile, durationMs) {
reconnectQuietMs: Number(profile.reconnectQuietMs),
forbiddenRequestPaths: Array.isArray(profile.forbiddenRequestPaths) ? profile.forbiddenRequestPaths.map(String) : [],
requiredEventTypes: profile.requiredEventTypes && typeof profile.requiredEventTypes === "object" ? profile.requiredEventTypes : {},
conditionalEventTypePairs: profile.conditionalEventTypePairs && typeof profile.conditionalEventTypePairs === "object" ? sanitize(profile.conditionalEventTypePairs) : {},
expectedProductSse: sanitize(expectedProductSse),
expectedKafka: sanitize(expectedKafka),
};
@@ -1043,15 +1044,44 @@ async function realtimeWaitTurnIds(debug, key, traceId, timeoutMs) {
}
async function realtimeWaitDebugTurnComplete(debug, key, traceId, profile) {
return realtimePoll(async () => {
const snapshot = await realtimeDebugSnapshot(debug, key);
if (!profile.debugStreams.every((stream) => (snapshot.records[stream] || []).some((item) => item.traceId === traceId))) return null;
const agentrunTypes = new Set((snapshot.records.agentrun || []).filter((item) => item.traceId === traceId).map((item) => item.eventType));
const hwlabTypes = new Set((snapshot.records.hwlab || []).filter((item) => item.traceId === traceId).map((item) => item.eventType));
if (!(profile.requiredEventTypes.agentrun || []).every((type) => agentrunTypes.has(type))) return null;
if (!(profile.requiredEventTypes.hwlab || []).every((type) => hwlabTypes.has(type))) return null;
return snapshot;
}, profile.terminalTimeoutMs, "realtime debug terminal event families");
let latest = null;
try {
return await realtimePoll(async () => {
const snapshot = await realtimeDebugSnapshot(debug, key);
latest = realtimeDebugTurnCompletion(snapshot, traceId, profile);
return latest.complete ? snapshot : null;
}, profile.terminalTimeoutMs, "realtime debug terminal event families");
} catch (error) {
const wrapped = error instanceof Error ? error : new Error(String(error));
const missing = latest?.missingEventTypes || { agentrun: [], hwlab: [] };
wrapped.message = "realtime debug terminal event families missing agentrun=[" + missing.agentrun.join(",") + "] hwlab=[" + missing.hwlab.join(",") + "] after " + profile.terminalTimeoutMs + "ms";
wrapped.details = { ...(wrapped.details || {}), traceId, completion: latest, valuesRedacted: true };
throw wrapped;
}
}
function realtimeDebugTurnCompletion(snapshot, traceId, profile) {
const observedEventTypes = {};
const recordCounts = {};
for (const stream of profile.debugStreams || []) {
const rows = (snapshot.records?.[stream] || []).filter((item) => item.traceId === traceId);
observedEventTypes[stream] = [...new Set(rows.map((item) => item.eventType).filter(Boolean))];
recordCounts[stream] = rows.length;
}
const missingEventTypes = {
agentrun: (profile.requiredEventTypes?.agentrun || []).filter((type) => !observedEventTypes.agentrun?.includes(type)),
hwlab: (profile.requiredEventTypes?.hwlab || []).filter((type) => !observedEventTypes.hwlab?.includes(type)),
};
const missingStreams = (profile.debugStreams || []).filter((stream) => !recordCounts[stream]);
return {
complete: missingStreams.length === 0 && missingEventTypes.agentrun.length === 0 && missingEventTypes.hwlab.length === 0,
traceId,
observedEventTypes,
missingEventTypes,
missingStreams,
recordCounts,
valuesRedacted: true,
};
}
async function realtimeWaitTurnStable(debug, key, traceId, subscribers, profile) {
@@ -1241,6 +1271,7 @@ function realtimeValidateTurnEvidence(input) {
const hwlabTypes = new Set(input.debug.records.hwlab.filter((item) => item.traceId === input.traceId).map((item) => item.eventType));
for (const type of profile.requiredEventTypes.agentrun || []) if (!agentrunTypes.has(type)) throw new Error("turn " + input.turn + " AgentRun lacks " + type);
for (const type of profile.requiredEventTypes.hwlab || []) if (!hwlabTypes.has(type)) throw new Error("turn " + input.turn + " HWLAB lacks " + type);
realtimeValidateConditionalEventTypePairs(input);
const identityRows = Object.values(input.debug.records).flat().filter((item) => item.traceId === input.traceId);
if (!input.runId || new Set(identityRows.map((item) => item.runId)).size !== 1) throw new Error("turn " + input.turn + " runId missing or mismatched across streams");
const commandIds = new Set(identityRows.map((item) => item.commandId).filter(Boolean));
@@ -1267,6 +1298,30 @@ function realtimeValidateTurnEvidence(input) {
}
}
function realtimeValidateConditionalEventTypePairs(input) {
const pairs = input.profile.conditionalEventTypePairs || {};
const agentrunRows = (input.debug.records.agentrun || []).filter((item) => item.traceId === input.traceId);
const hwlabRows = (input.debug.records.hwlab || []).filter((item) => item.traceId === input.traceId);
for (const [id, pair] of Object.entries(pairs)) {
const sourceRows = agentrunRows.filter((item) => item.eventType === pair.agentrun);
const projectedRows = hwlabRows.filter((item) => item.eventType === pair.hwlab);
if (sourceRows.length === 0 && projectedRows.length === 0) continue;
if (sourceRows.length !== projectedRows.length) {
throw new Error("turn " + input.turn + " conditional event pair " + id + " count differs AgentRun=" + sourceRows.length + " HWLAB=" + projectedRows.length);
}
for (const source of sourceRows) {
if (!source.eventId || !projectedRows.some((projected) => projected.sourceEventId === source.eventId)) {
throw new Error("turn " + input.turn + " conditional event pair " + id + " lacks HWLAB sourceEventId lineage");
}
}
for (const projected of projectedRows) {
if (!projected.sourceEventId || !sourceRows.some((source) => source.eventId === projected.sourceEventId)) {
throw new Error("turn " + input.turn + " conditional event pair " + id + " has an orphan HWLAB event");
}
}
}
}
function realtimeExpectedStreamSessionId(stream, hwlabSessionId) {
if (stream !== "stdio") return hwlabSessionId;
const base = String(hwlabSessionId || "")