import readline from "node:readline"; import { rawMinuteWeights, summarizeSegmentWeights, type SegmentCandidate } from "../src/lib/rectification-agentic/core/segment-summary.ts"; import { segmentInformationGain } from "../src/lib/rectification-agentic/core/segment-probe-order.ts"; import type { ConflictProbe, AnswerClass } from "../src/lib/rectification-agentic/core/types.ts"; import { buildInferenceState, applyAnswerToState } from "../src/lib/rectification-agentic/core/build-state.ts"; import { runV9CandidateScore } from "../src/lib/rectification-agentic/v9/engine-client.ts"; import { buildCaseInferenceState } from "../src/lib/rectification-agentic/v9/inference-adapter.ts"; import { rectificationFollowupCatalog, decideFromDossier, holdoutStatusFromInference, type DecisionDossier } from "../src/lib/rectification-agentic/v9/decision-from-dossier.ts"; import { buildMethodFollowupPlan } from "../src/lib/rectification-agentic/v9/method-followup.ts"; import { evidenceLedgerFingerprint } from "../src/lib/rectification-agentic/v9/tool-service.ts"; import { refinementFromDecisionReceipt } from "../src/lib/rectification-agentic/v9/refinement-packet.ts"; import { segmentReplayOracleAnswer } from "./rectification-segment-oracle.ts"; for await (const line of readline.createInterface({ input: process.stdin })) { try { const input = JSON.parse(line); const candidates: SegmentCandidate[] = (input.rows ?? []).map((row: Record) => ({ time: row.time, score: row.score, cluster_times: row.cluster_times, eliminated: input.eliminated?.includes(row.time) ?? false, })); const weights = input.weights ?? rawMinuteWeights(candidates); if (input.operation === "method_replay") { const previousFetch = globalThis.fetch; try { // Transport a real score API response unchanged through its production parser. globalThis.fetch = (async () => new Response(JSON.stringify(input.response), { status: 200 })) as typeof fetch; const score = await runV9CandidateScore({ baselineBirthSnapshot: input.snapshot, candidateRange: input.range, events: input.request.events }); const probes = refinementFromDecisionReceipt(score.decisionReceipt).discriminating_event_probes; let state = buildCaseInferenceState({ range: input.range, candidates: score.candidates, evidence: input.evidence, probes, segmentMinutes: input.minutes, segmentTargets: input.targets, segmentScanComplete: true, segmentOrderEnabled: input.enabled }); const asked: string[] = []; const rounds: unknown[] = []; let stop = "budget"; for (let round = 0; round < input.ask_count; round++) { const latest = { ...score, resultId: score.engineResultId, evidenceLedgerFingerprint: evidenceLedgerFingerprint(input.evidence), decisionReceipt: { ...score.decisionReceipt, inference_state: state } }; const dossier: DecisionDossier = { latestResult: latest, evidence: input.evidence, conversationSummary: { activeFocus: null, declinedSkippedTopics: [] }, case: { acceptedTime: null, status: "candidate_ready", stage: "minute", candidateRange: input.range, reportedBirthTime: input.snapshot.reported_birth_time, birthTimeSource: input.snapshot.birth_time_source } }; const decision = decideFromDossier(dossier, { birthDate: input.snapshot.birth_date }); const catalog = rectificationFollowupCatalog(latest, input.evidence); const plan = buildMethodFollowupPlan({ ...catalog, evidence: input.evidence, sessionOutcome: decision.sessionOutcome, birthDate: input.snapshot.birth_date, candidatesSeparated: decision.separation.status === "separated" || decision.separation.status === "sole_candidate", holdoutValidation: holdoutStatusFromInference(state, catalog.oosBlindPrompts), caseStage: "minute", candidateRange: input.range, reportedTime: input.snapshot.reported_birth_time }); const followup = plan.next_followup; const probe = state.probes.find(probe => probe.semantic_key === followup?.semantic_key); rounds.push({ outcome: decision.sessionOutcome, key: followup?.semantic_key ?? null, intent: followup?.intent ?? null, choice: Boolean(followup?.choice_frame), dropped: plan.dropped_probes }); if (!probe || followup?.intent !== "distinguish_candidates" || !followup.choice_frame) { stop = decision.sessionOutcome; break; } // Truth is used only by the offline answer oracle, never by question selection. const answer = segmentReplayOracleAnswer(probe, input.true_time, state.transitions); asked.push(probe.semantic_key); state = applyAnswerToState(state, probe.id, answer); } process.stdout.write(JSON.stringify({ asked, rounds, stop, state }) + "\n"); } finally { globalThis.fetch = previousFetch; } } else if (input.operation === "replay_raw") { const probes: ConflictProbe[] = input.probes.map((probe: ConflictProbe & { user_meaning?: string }) => ({ ...probe, id: probe.semantic_key, question: probe.user_meaning ?? probe.question, })); const times = input.rows.map((row: SegmentCandidate) => row.time).sort(); const state = buildInferenceState({ range_start: times[0], range_end: times.at(-1), candidates: input.rows.map((row: SegmentCandidate) => ({ id: row.time, time: row.time, raw_score: row.score, relative_support: row.score, cluster_times: row.cluster_times })), events: [], probes, answered_probes: probes.flatMap((probe, index) => { const answer = input.answers[index] as AnswerClass | null; return answer ? [{ probe_id: probe.id, semantic_key: probe.semantic_key, candidate_split_hash: probe.candidate_split_hash, answer_class: answer, classified_from: "choice" as const }] : []; }), }); process.stdout.write(JSON.stringify({ scores: Object.fromEntries(state.candidates.map((candidate) => [candidate.time, candidate.raw_posterior_score])), eliminated: state.candidates.filter((candidate) => candidate.raw_eliminated).map((candidate) => candidate.time) }) + "\n"); } else if (input.operation === "weights") { process.stdout.write(JSON.stringify(weights) + "\n"); } else if (input.operation === "summary" || input.operation === "summary_weights") { process.stdout.write(JSON.stringify(summarizeSegmentWeights(weights, input.segments, input.offsets, input.truth_offset)) + "\n"); } else { const gains = input.probes.map((probe: ConflictProbe) => segmentInformationGain(probe, weights, input.segments, input.offsets)); process.stdout.write(JSON.stringify({ gains }) + "\n"); } } catch (error) { process.stdout.write(JSON.stringify({ error: error instanceof Error ? error.message : "bridge_error" }) + "\n"); } }