{"record":{"id":"dc1bb36dc2ea71dd","repo":"BigPizzaV3/CodexPlusPlus","slug":"result-message-codex-bridge-data","errorCode":null,"errorMessage":"result?.message||'Codex++ 会话读取失败'","messagePattern":"result\\?\\.message\\|\\|'Codex\\+\\+ 会话读取失败'","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/conversation-canvas/bridge-data.mjs","lineNumber":4,"sourceCode":"import {parseMessage,buildGraph} from './model.mjs';\n\nexport function graphFromExport(result, threadId, annotations={nodes:[]}) {\n  if(result?.status!=='ok')throw new Error(result?.message||'Codex++ 会话读取失败');\n  if(result.kind!=='codex-rollout'||result.session_id!==threadId||typeof result.content!=='string')throw new Error('会话接口返回了不匹配的数据');\n  const messages=[],seen=new Set();\n  let declaredId=null;\n  // Ignore an unfinished JSONL tail; the next refresh will read the completed line.\n  const lines=result.content.slice(0,result.content.lastIndexOf('\\n')+1).split('\\n');\n  for(let i=0;i<lines.length;i++){\n    if(!lines[i].trim())continue;\n    let r;try{r=JSON.parse(lines[i]);}catch{continue;}\n    if(r.type==='session_meta')declaredId=r.payload?.id;\n    const m=parseMessage(r,`message-${threadId}-line-${i}`);\n    if(m&&!seen.has(m.id)){seen.add(m.id);messages.push(m);}\n  }\n  if(declaredId&&declaredId!==threadId)throw new Error('记录中的任务 ID 与当前任务不一致');\n  return {threadId,title:annotations.title||'当前对话',messages,...buildGraph(messages,annotations)};\n}\n","sourceCodeStart":1,"sourceCodeEnd":20,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/tools/conversation-canvas/bridge-data.mjs#L1-L20","documentation":"bridge-data.mjs exports the shared graphFromExport used by tests/bridge consumers; its first guard throws Error(result?.message || 'Codex++ 会话读取失败') when result.status is not 'ok'. Same contract as the userscript copy: the caller must pass a successful bridge result.","triggerScenarios":"Calling graphFromExport with a bridge result of {status:'error', message}, or with null/undefined (e.g. a failed readCanvasSource call) — raised through sync.","commonSituations":"Unit/integration test feeding a simulated error result; a consumer reusing graphFromExport on a raw response without checking status; bridge failing mid-read.","solutions":["Only call graphFromExport after confirming result?.status==='ok'","Prefer the kind==='canvas-messages' branch for native-shaped results","In tests, pass a stub result with status:'ok', kind:'codex-rollout', content string","Wrap calls in try/catch and surface result.message"],"exampleFix":"// before\nconst g = graphFromExport(result, id);\n// after\nif (result?.status === 'ok') { const g = graphFromExport(result, id); } else { handleReadFailure(result?.message); }","handlingStrategy":"validation","validationCode":"if (result?.status !== 'ok') { handleReadFailure(result?.message); return; }\nif (result.kind === 'canvas-messages') { return fromNative(result); }","typeGuard":"const okRollout = (r) => r?.status==='ok' && r.kind==='codex-rollout' && typeof r.content==='string';","tryCatchPattern":"try { const g = graphFromExport(result, id, ann); } catch (e) { console.error('graphFromExport failed:', e.message); }","preventionTips":["Only call graphFromExport on status:'ok' results","In tests, stub results with the full expected shape (status/kind/session_id/content)","Handle the native canvas-messages shape separately"],"tags":["bridge","validation","runtime"],"backgroundTag":"unexpected-response-shape","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}