shennian 0.3.85 → 0.3.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/bin/shennian.js +1 -1
  2. package/dist/publish-build-manifest.json +28 -28
  3. package/dist/src/agents/adapter.js +19 -1
  4. package/dist/src/agents/claude-managed-permissions.js +248 -1
  5. package/dist/src/agents/claude.js +393 -8
  6. package/dist/src/agents/codex-managed-permissions.js +294 -1
  7. package/dist/src/agents/codex-managed-room-tools.js +249 -1
  8. package/dist/src/agents/codex-utils.js +200 -7
  9. package/dist/src/agents/codex.js +1352 -17
  10. package/dist/src/agents/config-status.js +226 -1
  11. package/dist/src/agents/cursor-managed-permissions.js +299 -2
  12. package/dist/src/agents/cursor.js +314 -1
  13. package/dist/src/agents/custom.js +418 -4
  14. package/dist/src/agents/external-channel-instructions.js +94 -11
  15. package/dist/src/agents/gemini.js +180 -1
  16. package/dist/src/agents/manager.d.ts +2 -0
  17. package/dist/src/agents/manager.js +250 -18
  18. package/dist/src/agents/model-registry/cache.js +37 -1
  19. package/dist/src/agents/model-registry/discovery.js +188 -2
  20. package/dist/src/agents/model-registry/parsers.js +521 -4
  21. package/dist/src/agents/model-registry/runner.js +30 -1
  22. package/dist/src/agents/model-registry/service.js +115 -1
  23. package/dist/src/agents/model-registry/types.js +8 -1
  24. package/dist/src/agents/model-registry.js +19 -1
  25. package/dist/src/agents/native-capabilities.js +237 -1
  26. package/dist/src/agents/openclaw.js +293 -2
  27. package/dist/src/agents/opencode-managed-permissions.js +187 -2
  28. package/dist/src/agents/opencode.js +298 -1
  29. package/dist/src/agents/platform-instructions.js +46 -17
  30. package/dist/src/agents/runtime-compatibility.js +44 -1
  31. package/dist/src/agents/workbuddy-managed-permissions.js +329 -2
  32. package/dist/src/agents/workbuddy.js +444 -3
  33. package/dist/src/commands/room.js +2 -2
  34. package/dist/src/fs/boundary.js +185 -1
  35. package/dist/src/fs/handler.js +130 -1
  36. package/dist/src/fs/security.js +32 -1
  37. package/dist/src/fs/text-decoder.js +110 -1
  38. package/dist/src/index.js +587 -3
  39. package/dist/src/manager/prompt.js +34 -29
  40. package/dist/src/manager/registry.js +317 -1
  41. package/dist/src/manager/retired-external-channel-compat.js +40 -1
  42. package/dist/src/manager/runtime.d.ts +4 -0
  43. package/dist/src/manager/runtime.js +1514 -23
  44. package/dist/src/native-fusion/codex-parser.js +660 -4
  45. package/dist/src/native-fusion/codex-user-input.js +102 -5
  46. package/dist/src/native-fusion/config.js +5 -1
  47. package/dist/src/native-fusion/file-watcher.js +191 -1
  48. package/dist/src/native-fusion/local-repository.js +569 -2
  49. package/dist/src/native-fusion/opencode-parser.js +130 -3
  50. package/dist/src/native-fusion/parsers.js +140 -3
  51. package/dist/src/native-fusion/service.js +739 -2
  52. package/dist/src/native-fusion/session-header.js +101 -2
  53. package/dist/src/native-fusion/state.js +104 -2
  54. package/dist/src/native-fusion/types.js +1 -1
  55. package/dist/src/personal-sync/machine-authority.js +34 -1
  56. package/dist/src/room/activation-audit.js +45 -2
  57. package/dist/src/room/activation-policy.js +30 -1
  58. package/dist/src/room/activation-service.d.ts +1 -0
  59. package/dist/src/room/activation-service.js +408 -8
  60. package/dist/src/room/invite-bootstrap.js +156 -1
  61. package/dist/src/room/managed-context.d.ts +1 -1
  62. package/dist/src/room/managed-context.js +1 -1
  63. package/dist/src/room/managed-prompt-composer.d.ts +1 -1
  64. package/dist/src/room/managed-prompt-composer.js +6 -5
  65. package/dist/src/room/managed-room-binding.js +167 -1
  66. package/dist/src/room/managed-room-ipc.d.ts +2 -2
  67. package/dist/src/room/managed-room-ipc.js +2 -2
  68. package/dist/src/room/mirror-sync-service.js +183 -1
  69. package/dist/src/room/room-action-client.d.ts +11 -0
  70. package/dist/src/room/room-action-client.js +1 -1
  71. package/dist/src/room/room-binding-client.d.ts +1 -0
  72. package/dist/src/room/room-binding-client.js +151 -1
  73. package/dist/src/security/managed-agent-policy.d.ts +3 -2
  74. package/dist/src/security/managed-agent-policy.js +1 -1
  75. package/dist/src/session/archive-zip.js +220 -1
  76. package/dist/src/session/deletion-sync-state.js +136 -2
  77. package/dist/src/session/handlers/agent-capabilities.js +14 -1
  78. package/dist/src/session/handlers/agent-config.js +150 -1
  79. package/dist/src/session/handlers/agents.js +55 -1
  80. package/dist/src/session/handlers/chat.d.ts +1 -1
  81. package/dist/src/session/handlers/chat.js +1030 -2
  82. package/dist/src/session/handlers/control.js +61 -1
  83. package/dist/src/session/handlers/fs.js +594 -1
  84. package/dist/src/session/handlers/message-status.js +15 -1
  85. package/dist/src/session/handlers/remote-access.js +43 -1
  86. package/dist/src/session/handlers/room-managed.js +101 -1
  87. package/dist/src/session/handlers/session-refresh.js +35 -1
  88. package/dist/src/session/handlers/title.js +79 -1
  89. package/dist/src/session/handlers/tool-detail.js +149 -1
  90. package/dist/src/session/handlers/voice-window.js +62 -1
  91. package/dist/src/session/history-subscriptions.js +470 -1
  92. package/dist/src/session/manager.js +658 -1
  93. package/dist/src/session/native-cleanup-outbox.js +287 -2
  94. package/dist/src/session/native-session-lifecycle.js +327 -6
  95. package/dist/src/session/queue.d.ts +5 -0
  96. package/dist/src/session/queue.js +8 -8
  97. package/dist/src/upgrade/scheduler.js +126 -1
  98. package/node_modules/@shennian/wire/dist/agent-workspace.js +1 -1
  99. package/package.json +1 -1
@@ -1,4 +1,660 @@
1
- import j from"node:fs";import R from"node:path";import{createHash as Z}from"node:crypto";import{buildApprovalPendingPayload as P,buildUserMessagePayload as U,isSystemControlPayload as A,isToolPayload as k}from"@shennian/wire";import{buildImagePayload as D,buildToolPayload as K,findJsonlLookbackOffset as O,inferMimeType as H,makeCursor as W,normalizeText as y,parseStructuredString as tt,readJsonlLines as et,readTimestamp as nt,safeParse as q,shouldIgnoreNativeScanPath as L}from"./parser-common.js";import{lookupCodexThreadName as z}from"./codex-files.js";import{parseCodexDelegationInput as rt,parseCodexResponseUserInput as ot,parseCodexTextElements as B,stripCodexUserMessageWrapper as it}from"./codex-user-input.js";import{listCodexRolloutFiles as Mt,lookupCodexThreadName as At}from"./codex-files.js";function N(t){if(typeof t=="string"){const i=t.trim();return i?{path:i,name:R.basename(i)||"image.png",mimeType:H(i),kind:"image"}:null}if(typeof t!="object"||t===null)return null;const n=t,r=typeof n.path=="string"?n.path:typeof n.file_path=="string"?n.file_path:typeof n.saved_path=="string"?n.saved_path:typeof n.image_path=="string"?n.image_path:"";if(!r.trim())return null;const e=typeof n.name=="string"&&n.name.trim()?n.name:R.basename(r)||"image.png",o=typeof n.mimeType=="string"&&n.mimeType.trim()?n.mimeType:H(r);return{path:r,name:e,mimeType:o,kind:"image"}}function F(t){const n=y(t);if(!n)return!1;if(A(n)||/^<environment_context>[\s\S]*<\/environment_context>$/i.test(n)&&n.includes("<current_date>")&&n.includes("<timezone>")&&n.includes("<workspace_roots>"))return!0;const r=n.startsWith("<recommended_plugins>")&&n.includes("</recommended_plugins>"),e=n.includes("# AGENTS.md instructions for ")&&n.includes("<INSTRUCTIONS>")&&n.includes("</INSTRUCTIONS>"),o=n.includes("<environment_context>")&&n.includes("</environment_context>");return e&&o&&(n.startsWith("# AGENTS.md instructions for ")||r)}function w(t){const n=B(t.text_elements),r=Array.isArray(t.input)?B(t.input):"",e=typeof t.message=="string"?it(t.message):{text:"",strippedWrapper:!1},o=e.text,i=n||r||o,s=e.strippedWrapper?"codex_app_context_wrapper":void 0,a=Array.isArray(t.local_images)?t.local_images.map(N).filter(l=>l!=null):[];return!i&&a.length===0||A(i)&&a.length===0||F(i)&&a.length===0?null:a.length>0?{payload:U(i,a),titleText:i,repairHint:s}:{payload:i,titleText:i,repairHint:s}}function ut(t){return t==="turn_aborted"?"aborted":t==="turn_completed"||t==="turn_complete"||t==="task_complete"?"completed":null}function ct(t){return/approval|permission|confirm|user[_-]?input|input[_-]?required/i.test(t)}function st(t){const n=typeof t.command=="string"?t.command:Array.isArray(t.command)?t.command.join(" "):"",r=typeof t.prompt=="string"?t.prompt:typeof t.reason=="string"?t.reason:typeof t.message=="string"?t.message:n?`Codex \u8BF7\u6C42\u6267\u884C\u547D\u4EE4\uFF1A${n}`:"Codex \u6B63\u5728\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u540E\u7EE7\u7EED\u3002";return P({title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",content:r,source:"codex",actionHint:"\u5F53\u524D\u795E\u5FF5\u6682\u4E0D\u652F\u6301\u8FDC\u7A0B\u786E\u8BA4\uFF0C\u8BF7\u56DE\u5230\u8FD0\u884C Codex \u7684\u7535\u8111/\u7EC8\u7AEF\u5B8C\u6210\u786E\u8BA4\u3002"})}function at(t,n){return Array.isArray(t)?y(t.map(r=>{if(typeof r=="string")return r;if(typeof r!="object"||r===null)return"";const e=r;return e.type!==n?"":typeof e.text=="string"?e.text:""}).filter(Boolean).join(`
2
-
3
- `)):""}function lt(t){if(!Array.isArray(t))return[];const n=[];for(const r of t){if(typeof r!="object"||r===null)continue;const e=r;if(e.type!=="input_image")continue;const o=N(e);o&&n.push(o)}return n}function J(t){if(t.type!=="message")return null;const n=t.role==="assistant"?"agent":t.role==="user"?"user":null;if(!n)return null;if(n==="agent"){const i=at(t.content,"output_text");return A(i)?null:i?{role:n,payload:i,titleText:i}:null}const r=ot(t.content),e=r.text,o=[...lt(t.content)];for(const i of r.imagePaths){const s=N(i);s&&(o.some(a=>a.path===s.path)||o.push(s))}return!e&&o.length===0||A(e)&&o.length===0||F(e)&&o.length===0?null:{role:n,payload:o.length>0?U(e,o):e,titleText:e,repairHint:r.strippedWrapper?"codex_app_context_wrapper":void 0}}function G(t){if(!t)return"";try{const n=JSON.parse(t);if(typeof n=="object"&&n!==null){const r=n;if(r.type==="user"&&typeof r.content=="string")return y(r.content)}}catch{}return y(t)}function mt(t,n,r,e){return t.agentType!=="codex"||t.role!==n||k(t.payload)||Math.abs(t.ts-e)>300*1e3?!1:G(t.payload)===G(r)}function $(t,n,r,e){for(let o=t.length-1;o>=0;o-=1){const i=t[o];if(i){if(mt(i,n,r,e))return o;if(i.agentType==="codex"&&Math.abs(i.ts-e)>300*1e3)break}}return-1}function v(t,n,r,e,o,i,s,a,l,c,u="agent",m=!1,p){s&&t.push({agentType:"codex",sourceMode:"codex_rollout_import",sourceSessionKey:o,sourceEventKey:Q(o,r,e),cursor:W(n,r),role:u,ts:i,payload:s,title:a,modelId:l,workDir:c,terminal:m,...m?{terminalState:e==="turn_aborted"?"aborted":e==="error"?"failed":"completed"}:{},repairHint:p})}function b(t,n,r,e,o,i,s,a,l,c,u,m,p){const T=Q(o,r,e),I=typeof p?.callId=="string"&&p.callId.trim()?p.callId.trim():null,_=I?Z("sha256").update(`${o}\0${I}`).digest("hex").slice(0,32):null,M=_?`tool:${_}`:void 0,f=_?`native:tool:${_}`:`native:${T}`,C=p?.completed&&_?2:1,g=m!==void 0?"completed":"running";t.push({agentType:"codex",sourceMode:"codex_rollout_import",sourceSessionKey:o,sourceEventKey:T,cursor:W(n,r),role:"agent",ts:i,payload:K({name:s,toolCallId:f,detailRevision:C,status:g}),title:a,modelId:l,workDir:c,...M?{canonicalMessageKey:M,canonicalMutation:p?.completed?"upsert":"append"}:{},localToolDetail:{toolCallId:f,detailRevision:C,name:s,...u!==void 0?{args:u}:{},...m!==void 0?{result:m}:{},status:g}})}function Q(t,n,r){return`codex:${t}:${n}:${r}`}function ft(t,n,r,e,o,i,s,a,l){const c=typeof e.type=="string"?e.type:"";if(c){if(c==="message"){const u=J(e);if(!u||$(t,u.role,u.payload,i)>=0)return;v(t,n,r,`${c}:${u.role}`,o,i,u.payload,s,a,l,u.role,!1,u.repairHint);return}if(c==="function_call"){const u=typeof e.name=="string"?e.name:"function_call";b(t,n,r,c,o,i,u,s,a,l,tt(e.arguments),void 0,{callId:e.call_id});return}if(c==="function_call_output"){const u=rt(e);if(u){v(t,n,r,"delegation:user",o,i,u,s||u.slice(0,80),a,l,"user");return}b(t,n,r,c,o,i,"function_call_output",s,a,l,void 0,e.output,{callId:e.call_id,completed:!0});return}if(c==="custom_tool_call"){const u=typeof e.name=="string"?e.name:"custom_tool_call",m=e.input!==void 0?{input:e.input}:void 0;b(t,n,r,c,o,i,u,s,a,l,m,void 0,{callId:e.call_id});return}if(c==="custom_tool_call_output"){const u=typeof e.name=="string"?e.name:"custom_tool_call_output",m=e.output??e.result??e.content;b(t,n,r,c,o,i,u,s,a,l,void 0,m,{callId:e.call_id,completed:!0});return}if(c==="web_search_call"){const u=typeof e.action=="object"&&e.action!==null?e.action:null;b(t,n,r,c,o,i,"web_search",s,a,l,u??e.query);return}}}function pt(t,n,r,e,o,i,s,a,l){const c=typeof e.type=="string"?e.type:"";if(c){if(c==="user_message"){const u=w(e);if(!u)return;v(t,n,r,c,o,i,u.payload,s,a,l,"user",!1,u.repairHint);return}if(c==="agent_message"){const u=typeof e.message=="string"?y(e.message):"";if(!u||A(u))return;v(t,n,r,c,o,i,u,s,a,l);return}if(c==="turn_aborted"||c==="error"){const u=typeof e.message=="string"?y(e.message):typeof e.error=="string"?y(e.error):"";if(!u)return;v(t,n,r,c,o,i,u,s,a,l,"agent",!0);return}if(ct(c)){v(t,n,r,c,o,i,st(e),s,a,l,"agent");return}if(c==="exec_command_end"){const u=Array.isArray(e.command)?e.command:e.parsed_cmd??e.command,m=e.exit_code??e.exitCode,p=[typeof e.aggregated_output=="string"?e.aggregated_output:"",m!=null?`
4
- (exit ${String(m)})`:""].join("");b(t,n,r,c,o,i,"command_execution",s,a,l,{command:u},p);return}if(c==="patch_apply_end"){b(t,n,r,c,o,i,"apply_patch",s,a,l,e.changes,{success:e.success,stdout:e.stdout,stderr:e.stderr});return}if(c==="mcp_tool_call_end"){const u=typeof e.invocation=="object"&&e.invocation!==null?e.invocation:null,m=typeof u?.tool=="string"?u.tool:"mcp_tool_call",p={server:u?.server,...typeof u?.arguments=="object"&&u.arguments!==null?u.arguments:{}};b(t,n,r,c,o,i,m,s,a,l,p,e.result);return}if(c==="dynamic_tool_call_request"){const u=typeof e.tool=="string"?e.tool:"dynamic_tool_call";b(t,n,r,c,o,i,u,s,a,l,e.arguments,void 0,{callId:e.callId??e.call_id});return}if(c==="dynamic_tool_call_response"){const u=typeof e.tool=="string"?e.tool:"dynamic_tool_call";b(t,n,r,c,o,i,u,s,a,l,void 0,e.result??e.output,{callId:e.callId??e.call_id,completed:!0});return}if(c==="web_search_end"){const u=typeof e.action=="object"&&e.action!==null?e.action:null;b(t,n,r,c,o,i,"web_search",s,a,l,u??e.query,e.query);return}if(c==="image_generation_end"){const u=typeof e.saved_path=="string"?e.saved_path.trim():"";if(!u)return;v(t,n,r,c,o,i,D(u),s,a,l);return}c==="view_image_tool_call"&&b(t,n,r,c,o,i,"view_image",s,a,l,e.path??e.image_path??e)}}function E(t=!1){return{sourceSessionKey:"",workDir:"",modelId:null,title:"",skipImport:t}}function V(t){if((typeof t.thread_source=="string"?t.thread_source.trim().toLowerCase():"")==="subagent")return!0;const r=typeof t.source=="object"&&t.source!==null?t.source:null;if(r&&typeof r.subagent=="object"&&r.subagent!==null)return!0;const e=typeof t.parent_thread_id=="string"&&t.parent_thread_id.trim().length>0,o=typeof t.agent_nickname=="string"||typeof t.agent_role=="string"||typeof t.multi_agent_version=="string";return e&&o}function gt(t){const n=j.openSync(t,"r");try{const r=[];let e=0,o="";for(;e<1024*1024;){const l=Buffer.alloc(16384),c=j.readSync(n,l,0,l.length,e);if(c<=0)break;const u=l.subarray(0,c);r.push(u),e+=c;const m=Buffer.concat(r),p=m.indexOf(10);if(p>=0){o=m.subarray(0,p).toString("utf8");break}}!o&&r.length>0&&(o=Buffer.concat(r).toString("utf8"));const i=q(o);if(!i||i.type!=="session_meta")return E();const s=typeof i.payload=="object"&&i.payload!==null?i.payload:null;if(!s)return E();if(V(s))return E(!0);const a=typeof s.id=="string"?s.id:"";return{sourceSessionKey:a,workDir:typeof s.cwd=="string"?s.cwd:"",modelId:X(s),title:z(a)??"",skipImport:!1}}finally{j.closeSync(n)}return E()}function X(t){return(typeof t.model=="string"?t.model.trim():"")||null}function yt(t,n){const r=[],e=gt(t);let{sourceSessionKey:o,workDir:i,modelId:s,title:a,skipImport:l}=e,c=e.title;if(l||L(i))return{nextOffset:j.statSync(t).size,events:[]};let u=null;const m=new Set,p=et(t,O(t,n),(T,I)=>{const _=q(T);if(!_)return;const M=typeof _.type=="string"?_.type:"",f=typeof _.payload=="object"&&_.payload!==null?_.payload:null,C=nt(_.timestamp);if(!(!f||!C)){if(M==="session_meta"){if(V(f)){r.length=0,l=!0;return}if(o=typeof f.id=="string"?f.id:o,i=typeof f.cwd=="string"?f.cwd:i,L(i)){r.length=0,l=!0;return}s=X(f)??s;const g=z(o);g&&(c=g,a=g);return}if(!l&&o){if(M==="response_item"){const g=J(f);g?.role==="user"&&g.titleText&&!a&&(a=g.titleText.slice(0,80)),ft(r,t,I,f,o,C,a,s,i);return}if(M==="event_msg"){const g=typeof f.type=="string"?f.type:"",S=ut(g);if(S){let x=!1;for(let h=r.length-1;h>=0;h-=1){const d=r[h];if(d?.agentType==="codex"&&d.role==="agent"&&!k(d.payload)){d.terminal=!0,d.terminalState=S,I>=n&&Number(d.cursor?.slice(t.length+1))<n&&m.add(d.sourceEventKey),x=!0,u=null;break}}if(!x)for(let h=r.length-1;h>=0;h-=1){const d=r[h];if(!(d?.agentType!=="codex"||d.role!=="agent")){d.terminal=!0,d.terminalState=S,I>=n&&Number(d.cursor?.slice(t.length+1))<n&&m.add(d.sourceEventKey),x=!0,u=null;break}}x||(u=S);return}if(g==="user_message"){const x=w(f);if(x?.titleText&&!a&&(a=x.titleText.slice(0,80)),x){const h=$(r,"user",x.payload,C);h>=0&&r.splice(h,1)}}else if(g==="agent_message"){const x=typeof f.message=="string"?y(f.message):"";if(x&&!A(x)){const h=$(r,"agent",x,C);h>=0&&r.splice(h,1)}}const Y=r.length;pt(r,t,I,f,o,C,a,s,i),u&&r.length>Y&&(r[r.length-1].terminal=!0,r[r.length-1].terminalState=u,u=null)}}}});for(const T of r)T.titleSource=c&&T.title===c?"agent_native":"first_user_fallback";return{nextOffset:p,events:r.filter(T=>m.has(T.sourceEventKey)||Number(T.cursor?.slice(t.length+1))>=n)}}export{Mt as listCodexRolloutFiles,At as lookupCodexThreadName,yt as parseCodexRolloutChunk};
1
+ // @arch docs/architecture/cli/native-session-fusion.md
2
+ // @test src/__tests__/native-fusion-parsers.test.ts
3
+ // @test src/__tests__/native-fusion-codex-current-input.test.ts
4
+ // @test src/__tests__/native-fusion-codex-subagent.test.ts
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { createHash } from 'node:crypto';
8
+ import { buildApprovalPendingPayload, buildUserMessagePayload, isSystemControlPayload, isToolPayload } from '@shennian/wire';
9
+ import { buildImagePayload, buildToolPayload, findJsonlLookbackOffset, inferMimeType, makeCursor, normalizeText, parseStructuredString, readJsonlLines, readTimestamp, safeParse, shouldIgnoreNativeScanPath, } from './parser-common.js';
10
+ import { lookupCodexThreadName } from './codex-files.js';
11
+ import { parseCodexDelegationInput, parseCodexResponseUserInput, parseCodexTextElements, stripCodexUserMessageWrapper, } from './codex-user-input.js';
12
+ export { listCodexRolloutFiles, lookupCodexThreadName } from './codex-files.js';
13
+ function parseCodexLocalImageAttachment(value) {
14
+ if (typeof value === 'string') {
15
+ const imagePath = value.trim();
16
+ if (!imagePath)
17
+ return null;
18
+ return {
19
+ path: imagePath,
20
+ name: path.basename(imagePath) || 'image.png',
21
+ mimeType: inferMimeType(imagePath),
22
+ kind: 'image',
23
+ };
24
+ }
25
+ if (typeof value !== 'object' || value === null)
26
+ return null;
27
+ const entry = value;
28
+ const imagePath = typeof entry.path === 'string' ? entry.path
29
+ : typeof entry.file_path === 'string' ? entry.file_path
30
+ : typeof entry.saved_path === 'string' ? entry.saved_path
31
+ : typeof entry.image_path === 'string' ? entry.image_path
32
+ : '';
33
+ if (!imagePath.trim())
34
+ return null;
35
+ const name = typeof entry.name === 'string' && entry.name.trim()
36
+ ? entry.name
37
+ : path.basename(imagePath) || 'image.png';
38
+ const mimeType = typeof entry.mimeType === 'string' && entry.mimeType.trim()
39
+ ? entry.mimeType
40
+ : inferMimeType(imagePath);
41
+ return {
42
+ path: imagePath,
43
+ name,
44
+ mimeType,
45
+ kind: 'image',
46
+ };
47
+ }
48
+ function isCodexInjectedContextText(text) {
49
+ const normalized = normalizeText(text);
50
+ if (!normalized)
51
+ return false;
52
+ if (isSystemControlPayload(normalized))
53
+ return true;
54
+ if (/^<environment_context>[\s\S]*<\/environment_context>$/i.test(normalized) &&
55
+ normalized.includes('<current_date>') &&
56
+ normalized.includes('<timezone>') &&
57
+ normalized.includes('<workspace_roots>')) {
58
+ return true;
59
+ }
60
+ const hasRecommendedPluginsPrefix = normalized.startsWith('<recommended_plugins>') &&
61
+ normalized.includes('</recommended_plugins>');
62
+ const hasProjectInstructions = normalized.includes('# AGENTS.md instructions for ') &&
63
+ normalized.includes('<INSTRUCTIONS>') && normalized.includes('</INSTRUCTIONS>');
64
+ const hasEnvironmentContext = normalized.includes('<environment_context>') &&
65
+ normalized.includes('</environment_context>');
66
+ return hasProjectInstructions && hasEnvironmentContext &&
67
+ (normalized.startsWith('# AGENTS.md instructions for ') || hasRecommendedPluginsPrefix);
68
+ }
69
+ function parseCodexUserMessage(payload) {
70
+ const textFromElements = parseCodexTextElements(payload.text_elements);
71
+ const textFromInput = Array.isArray(payload.input)
72
+ ? parseCodexTextElements(payload.input)
73
+ : '';
74
+ const strippedMessage = typeof payload.message === 'string'
75
+ ? stripCodexUserMessageWrapper(payload.message)
76
+ : { text: '', strippedWrapper: false };
77
+ const textFromMessage = strippedMessage.text;
78
+ const text = textFromElements || textFromInput || textFromMessage;
79
+ const repairHint = strippedMessage.strippedWrapper ? 'codex_app_context_wrapper' : undefined;
80
+ const attachments = Array.isArray(payload.local_images)
81
+ ? payload.local_images
82
+ .map(parseCodexLocalImageAttachment)
83
+ .filter((item) => item != null)
84
+ : [];
85
+ if (!text && attachments.length === 0)
86
+ return null;
87
+ if (isSystemControlPayload(text) && attachments.length === 0)
88
+ return null;
89
+ if (isCodexInjectedContextText(text) && attachments.length === 0)
90
+ return null;
91
+ if (attachments.length > 0) {
92
+ return {
93
+ payload: buildUserMessagePayload(text, attachments),
94
+ titleText: text,
95
+ repairHint,
96
+ };
97
+ }
98
+ return { payload: text, titleText: text, repairHint };
99
+ }
100
+ function codexTerminalState(eventType) {
101
+ if (eventType === 'turn_aborted')
102
+ return 'aborted';
103
+ if (eventType === 'turn_completed' || eventType === 'turn_complete' || eventType === 'task_complete') {
104
+ return 'completed';
105
+ }
106
+ return null;
107
+ }
108
+ function isCodexApprovalEventType(eventType) {
109
+ return /approval|permission|confirm|user[_-]?input|input[_-]?required/i.test(eventType);
110
+ }
111
+ function buildCodexApprovalPayload(payload) {
112
+ const command = typeof payload.command === 'string'
113
+ ? payload.command
114
+ : Array.isArray(payload.command)
115
+ ? payload.command.join(' ')
116
+ : '';
117
+ const content = typeof payload.prompt === 'string' ? payload.prompt
118
+ : typeof payload.reason === 'string' ? payload.reason
119
+ : typeof payload.message === 'string' ? payload.message
120
+ : command ? `Codex 请求执行命令:${command}`
121
+ : 'Codex 正在等待用户确认后继续。';
122
+ return buildApprovalPendingPayload({
123
+ title: '需要用户确认',
124
+ content,
125
+ source: 'codex',
126
+ actionHint: '当前神念暂不支持远程确认,请回到运行 Codex 的电脑/终端完成确认。',
127
+ });
128
+ }
129
+ function codexMessageContentText(content, textType) {
130
+ if (!Array.isArray(content))
131
+ return '';
132
+ return normalizeText(content
133
+ .map((part) => {
134
+ if (typeof part === 'string')
135
+ return part;
136
+ if (typeof part !== 'object' || part === null)
137
+ return '';
138
+ const record = part;
139
+ if (record.type !== textType)
140
+ return '';
141
+ return typeof record.text === 'string' ? record.text : '';
142
+ })
143
+ .filter(Boolean)
144
+ .join('\n\n'));
145
+ }
146
+ function codexMessageInputImageAttachments(content) {
147
+ if (!Array.isArray(content))
148
+ return [];
149
+ const attachments = [];
150
+ for (const part of content) {
151
+ if (typeof part !== 'object' || part === null)
152
+ continue;
153
+ const record = part;
154
+ if (record.type !== 'input_image')
155
+ continue;
156
+ const attachment = parseCodexLocalImageAttachment(record);
157
+ if (attachment)
158
+ attachments.push(attachment);
159
+ }
160
+ return attachments;
161
+ }
162
+ function parseCodexResponseMessage(payload) {
163
+ if (payload.type !== 'message')
164
+ return null;
165
+ const role = payload.role === 'assistant' ? 'agent' : payload.role === 'user' ? 'user' : null;
166
+ if (!role)
167
+ return null;
168
+ if (role === 'agent') {
169
+ const text = codexMessageContentText(payload.content, 'output_text');
170
+ if (isSystemControlPayload(text))
171
+ return null;
172
+ return text ? { role, payload: text, titleText: text } : null;
173
+ }
174
+ const parsedInput = parseCodexResponseUserInput(payload.content);
175
+ const text = parsedInput.text;
176
+ const attachments = [...codexMessageInputImageAttachments(payload.content)];
177
+ for (const imagePath of parsedInput.imagePaths) {
178
+ const attachment = parseCodexLocalImageAttachment(imagePath);
179
+ if (!attachment)
180
+ continue;
181
+ if (!attachments.some((existing) => existing.path === attachment.path))
182
+ attachments.push(attachment);
183
+ }
184
+ if (!text && attachments.length === 0)
185
+ return null;
186
+ if (isSystemControlPayload(text) && attachments.length === 0)
187
+ return null;
188
+ if (isCodexInjectedContextText(text) && attachments.length === 0)
189
+ return null;
190
+ return {
191
+ role,
192
+ payload: attachments.length > 0 ? buildUserMessagePayload(text, attachments) : text,
193
+ titleText: text,
194
+ repairHint: parsedInput.strippedWrapper ? 'codex_app_context_wrapper' : undefined,
195
+ };
196
+ }
197
+ function codexDedupeText(payload) {
198
+ if (!payload)
199
+ return '';
200
+ try {
201
+ const parsed = JSON.parse(payload);
202
+ if (typeof parsed === 'object' && parsed !== null) {
203
+ const record = parsed;
204
+ if (record.type === 'user' && typeof record.content === 'string')
205
+ return normalizeText(record.content);
206
+ }
207
+ }
208
+ catch {
209
+ /* plain text payload */
210
+ }
211
+ return normalizeText(payload);
212
+ }
213
+ function isDuplicateCodexChatEvent(event, role, payload, ts) {
214
+ if (event.agentType !== 'codex')
215
+ return false;
216
+ if (event.role !== role)
217
+ return false;
218
+ if (isToolPayload(event.payload))
219
+ return false;
220
+ if (Math.abs(event.ts - ts) > 5 * 60 * 1000)
221
+ return false;
222
+ return codexDedupeText(event.payload) === codexDedupeText(payload);
223
+ }
224
+ function findDuplicateCodexChatEventIndex(events, role, payload, ts) {
225
+ for (let i = events.length - 1; i >= 0; i -= 1) {
226
+ const event = events[i];
227
+ if (!event)
228
+ continue;
229
+ if (isDuplicateCodexChatEvent(event, role, payload, ts))
230
+ return i;
231
+ if (event.agentType === 'codex' && Math.abs(event.ts - ts) > 5 * 60 * 1000)
232
+ break;
233
+ }
234
+ return -1;
235
+ }
236
+ function pushCodexEvent(events, filePath, lineOffset, kind, sourceSessionKey, ts, payload, title, modelId, workDir, role = 'agent', terminal = false, repairHint) {
237
+ if (!payload)
238
+ return;
239
+ events.push({
240
+ agentType: 'codex',
241
+ sourceMode: 'codex_rollout_import',
242
+ sourceSessionKey,
243
+ sourceEventKey: makeCodexEventKey(sourceSessionKey, lineOffset, kind),
244
+ cursor: makeCursor(filePath, lineOffset),
245
+ role,
246
+ ts,
247
+ payload,
248
+ title,
249
+ modelId,
250
+ workDir,
251
+ terminal,
252
+ ...(terminal
253
+ ? {
254
+ terminalState: kind === 'turn_aborted' ? 'aborted'
255
+ : kind === 'error' ? 'failed'
256
+ : 'completed',
257
+ }
258
+ : {}),
259
+ repairHint,
260
+ });
261
+ }
262
+ function pushCodexToolEvent(events, filePath, lineOffset, kind, sourceSessionKey, ts, toolName, title, modelId, workDir, args, result, logical) {
263
+ const sourceEventKey = makeCodexEventKey(sourceSessionKey, lineOffset, kind);
264
+ const callId = typeof logical?.callId === 'string' && logical.callId.trim()
265
+ ? logical.callId.trim()
266
+ : null;
267
+ const logicalDigest = callId
268
+ ? createHash('sha256').update(`${sourceSessionKey}\0${callId}`).digest('hex').slice(0, 32)
269
+ : null;
270
+ const canonicalMessageKey = logicalDigest ? `tool:${logicalDigest}` : undefined;
271
+ const toolCallId = logicalDigest ? `native:tool:${logicalDigest}` : `native:${sourceEventKey}`;
272
+ const detailRevision = logical?.completed && logicalDigest ? 2 : 1;
273
+ const status = result !== undefined ? 'completed' : 'running';
274
+ events.push({
275
+ agentType: 'codex',
276
+ sourceMode: 'codex_rollout_import',
277
+ sourceSessionKey,
278
+ sourceEventKey,
279
+ cursor: makeCursor(filePath, lineOffset),
280
+ role: 'agent',
281
+ ts,
282
+ payload: buildToolPayload({ name: toolName, toolCallId, detailRevision, status }),
283
+ title,
284
+ modelId,
285
+ workDir,
286
+ ...(canonicalMessageKey ? {
287
+ canonicalMessageKey,
288
+ canonicalMutation: logical?.completed ? 'upsert' : 'append',
289
+ } : {}),
290
+ localToolDetail: {
291
+ toolCallId,
292
+ detailRevision,
293
+ name: toolName,
294
+ ...(args !== undefined ? { args } : {}),
295
+ ...(result !== undefined ? { result } : {}),
296
+ status,
297
+ },
298
+ });
299
+ }
300
+ function makeCodexEventKey(sourceSessionKey, offset, kind) {
301
+ // Active/archive are discovery locations, never provider identity. Codex
302
+ // preserves the rollout bytes when moving a thread, so stable thread ID +
303
+ // byte offset + event kind survives rename, move, touch and unarchive.
304
+ return `codex:${sourceSessionKey}:${offset}:${kind}`;
305
+ }
306
+ function parseCodexResponseItem(events, filePath, lineOffset, payload, sourceSessionKey, ts, title, modelId, workDir) {
307
+ const itemType = typeof payload.type === 'string' ? payload.type : '';
308
+ if (!itemType)
309
+ return;
310
+ if (itemType === 'message') {
311
+ const parsedMessage = parseCodexResponseMessage(payload);
312
+ if (!parsedMessage)
313
+ return;
314
+ const duplicateIndex = findDuplicateCodexChatEventIndex(events, parsedMessage.role, parsedMessage.payload, ts);
315
+ if (duplicateIndex >= 0)
316
+ return;
317
+ pushCodexEvent(events, filePath, lineOffset, `${itemType}:${parsedMessage.role}`, sourceSessionKey, ts, parsedMessage.payload, title, modelId, workDir, parsedMessage.role, false, parsedMessage.repairHint);
318
+ return;
319
+ }
320
+ if (itemType === 'function_call') {
321
+ const name = typeof payload.name === 'string' ? payload.name : 'function_call';
322
+ pushCodexToolEvent(events, filePath, lineOffset, itemType, sourceSessionKey, ts, name, title, modelId, workDir, parseStructuredString(payload.arguments), undefined, { callId: payload.call_id });
323
+ return;
324
+ }
325
+ if (itemType === 'function_call_output') {
326
+ const delegationInput = parseCodexDelegationInput(payload);
327
+ if (delegationInput) {
328
+ pushCodexEvent(events, filePath, lineOffset, 'delegation:user', sourceSessionKey, ts, delegationInput, title || delegationInput.slice(0, 80), modelId, workDir, 'user');
329
+ return;
330
+ }
331
+ pushCodexToolEvent(events, filePath, lineOffset, itemType, sourceSessionKey, ts, 'function_call_output', title, modelId, workDir, undefined, payload.output, { callId: payload.call_id, completed: true });
332
+ return;
333
+ }
334
+ if (itemType === 'custom_tool_call') {
335
+ const name = typeof payload.name === 'string' ? payload.name : 'custom_tool_call';
336
+ const args = payload.input !== undefined ? { input: payload.input } : undefined;
337
+ pushCodexToolEvent(events, filePath, lineOffset, itemType, sourceSessionKey, ts, name, title, modelId, workDir, args, undefined, { callId: payload.call_id });
338
+ return;
339
+ }
340
+ if (itemType === 'custom_tool_call_output') {
341
+ const name = typeof payload.name === 'string' ? payload.name : 'custom_tool_call_output';
342
+ const result = payload.output ?? payload.result ?? payload.content;
343
+ pushCodexToolEvent(events, filePath, lineOffset, itemType, sourceSessionKey, ts, name, title, modelId, workDir, undefined, result, { callId: payload.call_id, completed: true });
344
+ return;
345
+ }
346
+ if (itemType === 'web_search_call') {
347
+ const action = typeof payload.action === 'object' && payload.action !== null
348
+ ? payload.action
349
+ : null;
350
+ pushCodexToolEvent(events, filePath, lineOffset, itemType, sourceSessionKey, ts, 'web_search', title, modelId, workDir, action ?? payload.query);
351
+ return;
352
+ }
353
+ }
354
+ function parseCodexEventMessage(events, filePath, lineOffset, payload, sourceSessionKey, ts, title, modelId, workDir) {
355
+ const eventType = typeof payload.type === 'string' ? payload.type : '';
356
+ if (!eventType)
357
+ return;
358
+ if (eventType === 'user_message') {
359
+ const parsedUser = parseCodexUserMessage(payload);
360
+ if (!parsedUser)
361
+ return;
362
+ pushCodexEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, parsedUser.payload, title, modelId, workDir, 'user', false, parsedUser.repairHint);
363
+ return;
364
+ }
365
+ if (eventType === 'agent_message') {
366
+ const text = typeof payload.message === 'string' ? normalizeText(payload.message) : '';
367
+ if (!text || isSystemControlPayload(text))
368
+ return;
369
+ pushCodexEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, text, title, modelId, workDir);
370
+ return;
371
+ }
372
+ if (eventType === 'turn_aborted' || eventType === 'error') {
373
+ const text = typeof payload.message === 'string'
374
+ ? normalizeText(payload.message)
375
+ : typeof payload.error === 'string'
376
+ ? normalizeText(payload.error)
377
+ : '';
378
+ if (!text)
379
+ return;
380
+ pushCodexEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, text, title, modelId, workDir, 'agent', true);
381
+ return;
382
+ }
383
+ if (isCodexApprovalEventType(eventType)) {
384
+ pushCodexEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, buildCodexApprovalPayload(payload), title, modelId, workDir, 'agent');
385
+ return;
386
+ }
387
+ if (eventType === 'exec_command_end') {
388
+ const command = Array.isArray(payload.command) ? payload.command : payload.parsed_cmd ?? payload.command;
389
+ const exitCode = payload.exit_code ?? payload.exitCode;
390
+ const output = [
391
+ typeof payload.aggregated_output === 'string' ? payload.aggregated_output : '',
392
+ exitCode != null ? `\n(exit ${String(exitCode)})` : '',
393
+ ].join('');
394
+ pushCodexToolEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, 'command_execution', title, modelId, workDir, { command }, output);
395
+ return;
396
+ }
397
+ if (eventType === 'patch_apply_end') {
398
+ pushCodexToolEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, 'apply_patch', title, modelId, workDir, payload.changes, {
399
+ success: payload.success,
400
+ stdout: payload.stdout,
401
+ stderr: payload.stderr,
402
+ });
403
+ return;
404
+ }
405
+ if (eventType === 'mcp_tool_call_end') {
406
+ const invocation = typeof payload.invocation === 'object' && payload.invocation !== null
407
+ ? payload.invocation
408
+ : null;
409
+ const tool = typeof invocation?.tool === 'string' ? invocation.tool : 'mcp_tool_call';
410
+ const args = {
411
+ server: invocation?.server,
412
+ ...(typeof invocation?.arguments === 'object' && invocation.arguments !== null
413
+ ? invocation.arguments
414
+ : {}),
415
+ };
416
+ pushCodexToolEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, tool, title, modelId, workDir, args, payload.result);
417
+ return;
418
+ }
419
+ if (eventType === 'dynamic_tool_call_request') {
420
+ const tool = typeof payload.tool === 'string' ? payload.tool : 'dynamic_tool_call';
421
+ pushCodexToolEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, tool, title, modelId, workDir, payload.arguments, undefined, { callId: payload.callId ?? payload.call_id });
422
+ return;
423
+ }
424
+ if (eventType === 'dynamic_tool_call_response') {
425
+ const tool = typeof payload.tool === 'string' ? payload.tool : 'dynamic_tool_call';
426
+ pushCodexToolEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, tool, title, modelId, workDir, undefined, payload.result ?? payload.output, { callId: payload.callId ?? payload.call_id, completed: true });
427
+ return;
428
+ }
429
+ if (eventType === 'web_search_end') {
430
+ const action = typeof payload.action === 'object' && payload.action !== null
431
+ ? payload.action
432
+ : null;
433
+ pushCodexToolEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, 'web_search', title, modelId, workDir, action ?? payload.query, payload.query);
434
+ return;
435
+ }
436
+ if (eventType === 'image_generation_end') {
437
+ const savedPath = typeof payload.saved_path === 'string' ? payload.saved_path.trim() : '';
438
+ if (!savedPath)
439
+ return;
440
+ pushCodexEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, buildImagePayload(savedPath), title, modelId, workDir);
441
+ return;
442
+ }
443
+ if (eventType === 'view_image_tool_call') {
444
+ pushCodexToolEvent(events, filePath, lineOffset, eventType, sourceSessionKey, ts, 'view_image', title, modelId, workDir, payload.path ?? payload.image_path ?? payload);
445
+ }
446
+ }
447
+ function emptyCodexSessionMeta(skipImport = false) {
448
+ return {
449
+ sourceSessionKey: '',
450
+ workDir: '',
451
+ modelId: null,
452
+ title: '',
453
+ skipImport,
454
+ };
455
+ }
456
+ function isCodexSubagentSessionMeta(payload) {
457
+ const threadSource = typeof payload.thread_source === 'string'
458
+ ? payload.thread_source.trim().toLowerCase()
459
+ : '';
460
+ if (threadSource === 'subagent')
461
+ return true;
462
+ const source = typeof payload.source === 'object' && payload.source !== null
463
+ ? payload.source
464
+ : null;
465
+ if (source && typeof source.subagent === 'object' && source.subagent !== null)
466
+ return true;
467
+ const hasParentThread = typeof payload.parent_thread_id === 'string'
468
+ && payload.parent_thread_id.trim().length > 0;
469
+ const hasSubagentIdentity = typeof payload.agent_nickname === 'string'
470
+ || typeof payload.agent_role === 'string'
471
+ || typeof payload.multi_agent_version === 'string';
472
+ return hasParentThread && hasSubagentIdentity;
473
+ }
474
+ function readCodexSessionMeta(filePath) {
475
+ const fd = fs.openSync(filePath, 'r');
476
+ try {
477
+ const chunks = [];
478
+ let position = 0;
479
+ let firstLine = '';
480
+ while (position < 1024 * 1024) {
481
+ const buffer = Buffer.alloc(16 * 1024);
482
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position);
483
+ if (bytesRead <= 0)
484
+ break;
485
+ const chunk = buffer.subarray(0, bytesRead);
486
+ chunks.push(chunk);
487
+ position += bytesRead;
488
+ const combined = Buffer.concat(chunks);
489
+ const newlineIndex = combined.indexOf(0x0a);
490
+ if (newlineIndex >= 0) {
491
+ firstLine = combined.subarray(0, newlineIndex).toString('utf8');
492
+ break;
493
+ }
494
+ }
495
+ if (!firstLine && chunks.length > 0) {
496
+ firstLine = Buffer.concat(chunks).toString('utf8');
497
+ }
498
+ const parsed = safeParse(firstLine);
499
+ if (!parsed || parsed.type !== 'session_meta') {
500
+ return emptyCodexSessionMeta();
501
+ }
502
+ const payload = typeof parsed.payload === 'object' && parsed.payload !== null
503
+ ? parsed.payload
504
+ : null;
505
+ if (!payload) {
506
+ return emptyCodexSessionMeta();
507
+ }
508
+ if (isCodexSubagentSessionMeta(payload)) {
509
+ return emptyCodexSessionMeta(true);
510
+ }
511
+ const sourceSessionKey = typeof payload.id === 'string' ? payload.id : '';
512
+ return {
513
+ sourceSessionKey,
514
+ workDir: typeof payload.cwd === 'string' ? payload.cwd : '',
515
+ modelId: readCodexModelId(payload),
516
+ title: lookupCodexThreadName(sourceSessionKey) ?? '',
517
+ skipImport: false,
518
+ };
519
+ }
520
+ finally {
521
+ fs.closeSync(fd);
522
+ }
523
+ return emptyCodexSessionMeta();
524
+ }
525
+ function readCodexModelId(payload) {
526
+ const model = typeof payload.model === 'string' ? payload.model.trim() : '';
527
+ return model || null;
528
+ }
529
+ export function parseCodexRolloutChunk(filePath, startOffset) {
530
+ const events = [];
531
+ const metadata = readCodexSessionMeta(filePath);
532
+ let { sourceSessionKey, workDir, modelId, title, skipImport } = metadata;
533
+ let nativeTitle = metadata.title;
534
+ if (skipImport || shouldIgnoreNativeScanPath(workDir)) {
535
+ return { nextOffset: fs.statSync(filePath).size, events: [] };
536
+ }
537
+ let pendingTerminalState = null;
538
+ const terminalReplayKeys = new Set();
539
+ const nextOffset = readJsonlLines(filePath, findJsonlLookbackOffset(filePath, startOffset), (line, lineOffset) => {
540
+ const parsed = safeParse(line);
541
+ if (!parsed)
542
+ return;
543
+ const type = typeof parsed.type === 'string' ? parsed.type : '';
544
+ const payload = typeof parsed.payload === 'object' && parsed.payload !== null
545
+ ? parsed.payload
546
+ : null;
547
+ const ts = readTimestamp(parsed.timestamp);
548
+ if (!payload || !ts)
549
+ return;
550
+ if (type === 'session_meta') {
551
+ if (isCodexSubagentSessionMeta(payload)) {
552
+ events.length = 0;
553
+ skipImport = true;
554
+ return;
555
+ }
556
+ sourceSessionKey = typeof payload.id === 'string' ? payload.id : sourceSessionKey;
557
+ workDir = typeof payload.cwd === 'string' ? payload.cwd : workDir;
558
+ if (shouldIgnoreNativeScanPath(workDir)) {
559
+ events.length = 0;
560
+ skipImport = true;
561
+ return;
562
+ }
563
+ modelId = readCodexModelId(payload) ?? modelId;
564
+ const observedNativeTitle = lookupCodexThreadName(sourceSessionKey);
565
+ if (observedNativeTitle) {
566
+ nativeTitle = observedNativeTitle;
567
+ title = observedNativeTitle;
568
+ }
569
+ return;
570
+ }
571
+ if (skipImport)
572
+ return;
573
+ if (!sourceSessionKey)
574
+ return;
575
+ if (type === 'response_item') {
576
+ const parsedMessage = parseCodexResponseMessage(payload);
577
+ if (parsedMessage?.role === 'user' && parsedMessage.titleText && !title) {
578
+ title = parsedMessage.titleText.slice(0, 80);
579
+ }
580
+ parseCodexResponseItem(events, filePath, lineOffset, payload, sourceSessionKey, ts, title, modelId, workDir);
581
+ return;
582
+ }
583
+ if (type === 'event_msg') {
584
+ const eventType = typeof payload.type === 'string' ? payload.type : '';
585
+ const terminalState = codexTerminalState(eventType);
586
+ if (terminalState) {
587
+ let markedTerminal = false;
588
+ for (let i = events.length - 1; i >= 0; i -= 1) {
589
+ const event = events[i];
590
+ if (event?.agentType !== 'codex')
591
+ continue;
592
+ if (event.role !== 'agent')
593
+ continue;
594
+ if (isToolPayload(event.payload))
595
+ continue;
596
+ event.terminal = true;
597
+ event.terminalState = terminalState;
598
+ if (lineOffset >= startOffset && Number(event.cursor?.slice(filePath.length + 1)) < startOffset) {
599
+ terminalReplayKeys.add(event.sourceEventKey);
600
+ }
601
+ markedTerminal = true;
602
+ pendingTerminalState = null;
603
+ break;
604
+ }
605
+ if (!markedTerminal) {
606
+ for (let i = events.length - 1; i >= 0; i -= 1) {
607
+ const event = events[i];
608
+ if (event?.agentType !== 'codex' || event.role !== 'agent')
609
+ continue;
610
+ event.terminal = true;
611
+ event.terminalState = terminalState;
612
+ if (lineOffset >= startOffset && Number(event.cursor?.slice(filePath.length + 1)) < startOffset)
613
+ terminalReplayKeys.add(event.sourceEventKey);
614
+ markedTerminal = true;
615
+ pendingTerminalState = null;
616
+ break;
617
+ }
618
+ }
619
+ if (!markedTerminal)
620
+ pendingTerminalState = terminalState;
621
+ return;
622
+ }
623
+ if (eventType === 'user_message') {
624
+ const parsedUser = parseCodexUserMessage(payload);
625
+ if (parsedUser?.titleText && !title)
626
+ title = parsedUser.titleText.slice(0, 80);
627
+ if (parsedUser) {
628
+ const duplicateIndex = findDuplicateCodexChatEventIndex(events, 'user', parsedUser.payload, ts);
629
+ if (duplicateIndex >= 0)
630
+ events.splice(duplicateIndex, 1);
631
+ }
632
+ }
633
+ else if (eventType === 'agent_message') {
634
+ const text = typeof payload.message === 'string' ? normalizeText(payload.message) : '';
635
+ if (text && !isSystemControlPayload(text)) {
636
+ const duplicateIndex = findDuplicateCodexChatEventIndex(events, 'agent', text, ts);
637
+ if (duplicateIndex >= 0)
638
+ events.splice(duplicateIndex, 1);
639
+ }
640
+ }
641
+ const beforeCount = events.length;
642
+ parseCodexEventMessage(events, filePath, lineOffset, payload, sourceSessionKey, ts, title, modelId, workDir);
643
+ if (pendingTerminalState && events.length > beforeCount) {
644
+ events[events.length - 1].terminal = true;
645
+ events[events.length - 1].terminalState = pendingTerminalState;
646
+ pendingTerminalState = null;
647
+ }
648
+ }
649
+ });
650
+ for (const event of events) {
651
+ event.titleSource = nativeTitle && event.title === nativeTitle
652
+ ? 'agent_native'
653
+ : 'first_user_fallback';
654
+ }
655
+ return {
656
+ nextOffset,
657
+ events: events.filter((event) => terminalReplayKeys.has(event.sourceEventKey)
658
+ || Number(event.cursor?.slice(filePath.length + 1)) >= startOffset),
659
+ };
660
+ }