{"record":{"id":"665975d139df9b8f","repo":"BigPizzaV3/CodexPlusPlus","slug":"canvas-user-665975","errorCode":null,"errorMessage":"历史轮次接口返回格式无效","messagePattern":"历史轮次接口返回格式无效","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/conversation-canvas/public/canvas.user.js","lineNumber":406,"sourceCode":"}\nconst loadNativeRuntime=createNativeLoader();\n\n\nfunction isExportSizeError(message){\n  return /超过.*(?:大小|限制)|too (?:large|big)|size.{0,30}limit|maximum.{0,20}size/i.test(String(message));\n}\n\n// Wire contracts verified in the installed Desktop's history loader:\n// thread/turns/list(itemsView=notLoaded), then thread/items/list per turn.\nasync function readNativeHistory(send,threadId,cache=new Map(),onProgress=()=>{},signal){\n  const turns=[],turnIds=new Set(),turnCursors=new Set();\n  let cursor=null;\n  do{\n    signal?.throwIfAborted();\n    if(turnCursors.has(cursor))throw Error('历史轮次分页游标重复');\n    turnCursors.add(cursor);\n    const page=await send('thread/turns/list',{threadId,cursor,limit:20,itemsView:'notLoaded',sortDirection:'asc'});\n    if(!Array.isArray(page?.data))throw Error('历史轮次接口返回格式无效');\n    for(const turn of page.data){\n      if(typeof turn.id!=='string')throw Error('历史轮次缺少 ID');\n      if(!turnIds.has(turn.id)){turnIds.add(turn.id);turns.push(turn);}\n    }\n    cursor=page.nextCursor??null;\n    onProgress(`正在读取轮次目录 · ${turns.length} 轮…`);\n  }while(cursor!==null);\n  const messages=[],seen=new Set();\n  for(const [index,turn] of turns.entries()){\n    signal?.throwIfAborted();onProgress(`正在读取历史 ${index+1}/${turns.length}…`);\n    const key=`${threadId}:${turn.id}`,cached=await cache.get(key);\n    let parsed;\n    if(cached&&turn.status==='completed')parsed=cached;\n    else{\n      const partial=turn.status==='completed'?await cache.get(`${key}:partial`):null;\n      parsed=partial?.messages?.slice()||[];let itemCursor=partial?.cursor??null,limit=32;const cursors=new Set();\n      do{\n        signal?.throwIfAborted();","sourceCodeStart":388,"sourceCodeEnd":424,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/tools/conversation-canvas/public/canvas.user.js#L388-L424","documentation":"readNativeHistory requires each thread/turns/list response to carry a data array of turns. This throw fires when the response is null/undefined or page.data is not an array, indicating the native API response does not match the expected thread/turns/list(itemsView=notLoaded) shape.","triggerScenarios":"send('thread/turns/list',{...}) resolves with null, an error object, or an object whose data field is missing or not an array — e.g. backend returns {error:...}, or a protocol version changed the response envelope.","commonSituations":"Codex app updated with a changed RPC schema while the plugin adapter still expects the old shape; transient RPC failure surfaced as a null response; wrong threadId causing an error-shaped payload.","solutions":["Update the plugin so its adapter mapping matches the installed Codex API schema","Log the raw page response to identify the new envelope shape and adapt parsing","Verify the threadId is valid and the thread still exists","Retry the read — a transient RPC failure can return a null/empty response"],"exampleFix":"// before\nif(!Array.isArray(page?.data))throw Error('历史轮次接口返回格式无效');\n// after\nconst data=page?.data ?? page?.turns;\nif(!Array.isArray(data))throw Object.assign(Error('历史轮次接口返回格式无效'),{cause:page});","handlingStrategy":"type-guard","validationCode":"const page=await send('thread/turns/list',{threadId,cursor,limit:20,itemsView:'notLoaded',sortDirection:'asc'});\nif(!page||typeof page!=='object'||!Array.isArray(page.data)) throw new Error('malformed turns page');","typeGuard":"function isTurnsPage(p){ return !!p && Array.isArray(p.data) && p.data.every(t=>typeof t?.id==='string'); }","tryCatchPattern":"try { history=await readNativeHistory(send,threadId) } catch(e){ if(e.message==='历史轮次接口返回格式无效'){ logRawResponse(e.cause); retryOrAbort(); } else throw e; }","preventionTips":["Validate the response envelope before iterating page.data","Keep the plugin adapter in sync with the installed Codex RPC schema","Log the raw response object as error cause to catch protocol changes early"],"tags":["api","response-shape","rpc"],"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"}