fix: 校正实时探针可选工具事件合同
This commit is contained in:
@@ -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 || "")
|
||||
|
||||
Reference in New Issue
Block a user