|
| 1 | +import {useEffect, useRef, useState} from "react"; |
| 2 | +import {readLoopXTeamWork, type DelegationDependency, type DelegationReadback} from "../../data/chat"; |
| 3 | + |
| 4 | +type VerifiedLink = {link: DelegationDependency; source: DelegationReadback}; |
| 5 | +type Episode = {original: VerifiedLink; response: VerifiedLink; downstream: DelegationReadback | null}; |
| 6 | + |
| 7 | +/** Resolve only an explicitly requested correction path. Reads are on demand and never start work. */ |
| 8 | +export function GoalTeamEpisode({sessionId, result, zh, onInspect}: { |
| 9 | + sessionId: string; result: DelegationReadback; zh: boolean; onInspect: (operationId: string) => void; |
| 10 | +}) { |
| 11 | + const original = result.dependencies?.find(link => link.relation === "revises"); |
| 12 | + const response = result.dependencies?.find(link => link.relation === "responds_to" && link.operation_id !== original?.operation_id); |
| 13 | + const adoption = result.adoptions?.find(row => row.state === "current") ?? result.adoptions?.[0]; |
| 14 | + const [episode, setEpisode] = useState<Episode | null>(null); |
| 15 | + const [busy, setBusy] = useState(false); |
| 16 | + const [error, setError] = useState(""); |
| 17 | + const generation = useRef(0); |
| 18 | + useEffect(() => { |
| 19 | + generation.current++; setEpisode(null); setError(""); setBusy(false); |
| 20 | + return () => {generation.current++;}; |
| 21 | + }, [result, sessionId]); |
| 22 | + if (!original || !response) return null; |
| 23 | + async function trace() { |
| 24 | + const current = ++generation.current; |
| 25 | + setEpisode(null); setError(""); setBusy(true); |
| 26 | + try { |
| 27 | + const [first, challenge, revised, downstream] = await Promise.all([ |
| 28 | + readLoopXTeamWork(sessionId, original!.operation_id), |
| 29 | + readLoopXTeamWork(sessionId, response!.operation_id), |
| 30 | + readLoopXTeamWork(sessionId, result.operation_id), |
| 31 | + adoption?.state === "current" ? readLoopXTeamWork(sessionId, adoption.consumer_operation_id) : Promise.resolve(null), |
| 32 | + ]); |
| 33 | + const matches = (link: DelegationDependency, source: DelegationReadback) => |
| 34 | + link.state === "current" && source.status === "accepted" && !source.recovery_required && !source.error |
| 35 | + && source.artifacts?.some(artifact => artifact.ref === link.ref && artifact.sha256 === link.sha256); |
| 36 | + const responseBindsOriginal = challenge.dependencies?.some(link => |
| 37 | + link.relation === "responds_to" && link.operation_id === original!.operation_id && matches(link, first)); |
| 38 | + const revisionMatchesObservation = revised.status === "accepted" && revised.agent_id === result.agent_id |
| 39 | + && !revised.recovery_required && !revised.error |
| 40 | + && revised.artifacts?.length === result.artifacts?.length |
| 41 | + && result.artifacts?.every(expected => revised.artifacts?.some( |
| 42 | + artifact => artifact.ref === expected.ref && artifact.sha256 === expected.sha256)) |
| 43 | + && revised.dependencies?.some(link => link.relation === "revises" && link.operation_id === original!.operation_id && matches(link, first)) |
| 44 | + && revised.dependencies?.some(link => link.relation === "responds_to" && link.operation_id === response!.operation_id && matches(link, challenge)); |
| 45 | + const refreshedAdoption = adoption?.state === "current" ? revised.adoptions?.find(row => |
| 46 | + row.consumer_operation_id === adoption.consumer_operation_id && row.requester_agent_id === adoption.requester_agent_id |
| 47 | + && row.consumer_agent_id === adoption.consumer_agent_id && row.state === "current") : null; |
| 48 | + const downstreamBindsRevision = downstream?.dependencies?.some(link => |
| 49 | + link.relation === "uses" && link.operation_id === revised.operation_id && matches(link, revised)); |
| 50 | + const downstreamMatchesReceipt = downstream?.status === "accepted" && !downstream.recovery_required && !downstream.error |
| 51 | + && refreshedAdoption?.consumer_artifacts.every(expected => downstream.artifacts?.some( |
| 52 | + artifact => artifact.ref === expected.ref && artifact.sha256 === expected.sha256)); |
| 53 | + const sourceMatchesReceipt = refreshedAdoption?.source_artifacts.every(expected => revised.artifacts?.some( |
| 54 | + artifact => artifact.ref === expected.ref && artifact.sha256 === expected.sha256)); |
| 55 | + if (result.status !== "accepted" || result.error || result.recovery_required |
| 56 | + || !matches(original!, first) || !matches(response!, challenge) |
| 57 | + || !responseBindsOriginal || !revisionMatchesObservation |
| 58 | + || (adoption?.state === "current" && (!sourceMatchesReceipt || !downstreamBindsRevision || !downstreamMatchesReceipt))) { |
| 59 | + throw new Error("linked evidence unavailable"); |
| 60 | + } |
| 61 | + if (current === generation.current) setEpisode({original: {link: original!, source: first}, |
| 62 | + response: {link: response!, source: challenge}, downstream}); |
| 63 | + } catch { |
| 64 | + if (current === generation.current) setError(zh ? "关联执行或版本已变化;请重新读取证据。" : "A linked execution or version changed; recheck the evidence."); |
| 65 | + } finally {if (current === generation.current) setBusy(false);} |
| 66 | + } |
| 67 | + return <section className="goal-team-episode" aria-label={zh ? "纠偏证据路径" : "Correction evidence path"} aria-busy={busy}> |
| 68 | + <div className="goal-team-episode-heading"><h4>{zh ? "追踪一次纠偏" : "Trace a correction"}</h4> |
| 69 | + <button type="button" disabled={busy} onClick={() => void trace()}>{zh ? "核验关联执行" : "Verify linked work"}</button></div> |
| 70 | + <p>{zh ? "按需核验原始版本、复核回应、修订及后续采用。关系和验收不能替代对异议内容的判断。" |
| 71 | + : "Verify the original version, review response, revision and downstream use on demand. Relationships and acceptance do not judge the objection's content."}</p> |
| 72 | + {busy ? <p role="status">{zh ? "正在核验关联执行与指定版本…" : "Checking linked executions and exact versions…"}</p> : null} |
| 73 | + {error ? <p role="alert">{error}</p> : null} |
| 74 | + {episode ? <ol> |
| 75 | + <li><span>01 · {zh ? "原始版本" : "Original version"}</span><strong>{episode.original.source.agent_id}</strong> |
| 76 | + <button type="button" onClick={() => onInspect(episode.original.link.operation_id)}>{zh ? "阅读原始产物" : "Read original"}</button></li> |
| 77 | + <li><span>02 · {zh ? "复核回应" : "Review response"}</span><strong>{episode.response.source.agent_id}</strong> |
| 78 | + <button type="button" onClick={() => onInspect(episode.response.link.operation_id)}>{zh ? "阅读回应与证据" : "Read response and evidence"}</button></li> |
| 79 | + <li><span>03 · {zh ? "修订产物 · 当前验收有效" : "Revised output · currently accepted"}</span><strong>{result.agent_id}</strong> |
| 80 | + <span>{zh ? "与原始版本的正文对照见下方" : "Compare with the original below"}</span></li> |
| 81 | + <li><span>04 · {episode.downstream ? (zh ? "后续结果 · 当前验收与采用记录有效" : "Downstream result · acceptance and adoption current") |
| 82 | + : adoption ? (zh ? "采用证据无法核验" : "Adoption evidence unavailable") : (zh ? "尚无请求方采用" : "No requester adoption")}</span> |
| 83 | + {episode.downstream && adoption ? <><strong>{episode.downstream.agent_id} · {adoption.requester_agent_id}</strong> |
| 84 | + <button type="button" onClick={() => onInspect(adoption.consumer_operation_id)}>{zh ? "阅读后续结果" : "Read downstream result"}</button></> : null}</li> |
| 85 | + </ol> : null} |
| 86 | + </section>; |
| 87 | +} |
0 commit comments