shennian 0.3.66 → 0.3.67
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.
- package/dist/publish-build-manifest.json +6 -6
- package/dist/src/native-fusion/codex-parser.js +3 -3
- package/dist/src/native-fusion/local-repository.js +1 -1
- package/dist/src/native-fusion/service.js +2 -2
- package/dist/src/native-fusion/types.d.ts +2 -0
- package/node_modules/@shennian/wire/dist/agent-workspace.d.ts +7 -1
- package/node_modules/@shennian/wire/dist/room.d.ts +7 -0
- package/package.json +1 -1
|
@@ -311,8 +311,8 @@
|
|
|
311
311
|
},
|
|
312
312
|
{
|
|
313
313
|
"file": "src/native-fusion/codex-parser.js",
|
|
314
|
-
"beforeBytes":
|
|
315
|
-
"afterBytes":
|
|
314
|
+
"beforeBytes": 29358,
|
|
315
|
+
"afterBytes": 12136
|
|
316
316
|
},
|
|
317
317
|
{
|
|
318
318
|
"file": "src/native-fusion/codex-user-input.js",
|
|
@@ -331,8 +331,8 @@
|
|
|
331
331
|
},
|
|
332
332
|
{
|
|
333
333
|
"file": "src/native-fusion/local-repository.js",
|
|
334
|
-
"beforeBytes":
|
|
335
|
-
"afterBytes":
|
|
334
|
+
"beforeBytes": 25396,
|
|
335
|
+
"afterBytes": 12474
|
|
336
336
|
},
|
|
337
337
|
{
|
|
338
338
|
"file": "src/native-fusion/native-source-deletions.js",
|
|
@@ -356,8 +356,8 @@
|
|
|
356
356
|
},
|
|
357
357
|
{
|
|
358
358
|
"file": "src/native-fusion/service.js",
|
|
359
|
-
"beforeBytes":
|
|
360
|
-
"afterBytes":
|
|
359
|
+
"beforeBytes": 30151,
|
|
360
|
+
"afterBytes": 14006
|
|
361
361
|
},
|
|
362
362
|
{
|
|
363
363
|
"file": "src/native-fusion/session-header.js",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
2
|
|
|
3
|
-
`)):""}function
|
|
4
|
-
(exit ${String(m)})`:""].join("");b(
|
|
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,2 +1,2 @@
|
|
|
1
|
-
import p from"node:crypto";import l from"node:fs";import h from"node:path";import{sessionPreviewFromPayload as L}from"@shennian/wire";import{resolvePersonalSyncV1Path as R}from"../personal-sync/paths.js";import{getCanonicalSessionStore as P,getDaemonInstallationId as H,listSessionRecords as F,recordSession as w}from"../session/store.js";import{readToolDetail as U,writeToolDetail as J}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as V}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as A}from"../personal-sync/metrics.js";const I=1;class q extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class W extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class le{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}updateMessageDeliveryState(e,s,o){if(!this.canonicalStore().updateMessageDeliveryState(e,s,o))return;const i=(this.options.listLocalSessions??F)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(z(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new W(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=X(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=l.existsSync(a)?D(a):null;if(r&&r.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(r&&r.eventFingerprint!==c)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!i&&!r&&e.role!=="user")throw new q(e.sourceSessionKey);const m=e.claimSessionId??null,d=i?.sessionId??r?.sessionId??m??G(s,e.agentType,e.sourceSessionKey);if(m&&i&&i.sessionId!==m)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const f=this.canonicalStore(),S=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",u=r?.canonicalMessageId??(S?null:e.claimCanonicalMessageId??Q(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let y=f.getManifest(d)?.bodyRevision??0,T=!!r;const M=!S&&!!e.claimCanonicalMessageId;if(M){if(!u)throw new Error("managed echo claim requires a canonical message");const E=f.getMessageState(d,u);if(!E||E.tombstoned)throw new Error(`managed echo claim target is not durable: ${u}`)}if(!r&&!M&&!S){if(!u)throw new Error("native event requires a canonical message");const E=e.canonicalMessageKey?f.getMessageState(d,u):null,x=e.canonicalMutation==="upsert"||!!E,$=x?E??f.getMessageState(d,u):null,_={id:u,sessionId:d,role:e.role,ts:$?.message?.ts??e.ts,payload:Y($?.message?.payload,e.payload)},C=x?f.upsert(d,_):f.append(d,_);if(y=C.bodyRevision,T=C.duplicate,e.localToolDetail){const j=this.options.writeLocalToolDetail??J,N=this.options.readLocalToolDetail??U,B=Z(N(d,e.localToolDetail.toolCallId),e.localToolDetail);j({sessionId:d,...B,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(y=f.completeStartedUserMessages(d,e.ts).at(-1)?.bodyRevision??y),(!r||e.terminal)&&!S&&this.recordIndex(e,d,u,y);const K=new Date().toISOString();return r||g(a,{schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:u,canonicalRevision:y,cursor:e.cursor??null,mappedBy:S?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:K}),o.sources[n]={schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:r?i?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:r?i?.lastCursor??r.cursor:e.cursor??i?.lastCursor??null,bodyRevision:y,createdAt:i?.createdAt??K,updatedAt:K},g(this.sourcesFile(),o),!r&&e.cursor&&A("sourceCursorAdvanced"),!r&&!M&&!S&&!T&&A("canonicalCommits"),{sessionId:d,messageId:u,bodyRevision:y,duplicate:T,claimed:M,suppressed:S}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??F,s=this.options.recordLocalSession??w,o=this.canonicalStore();let n=0;for(const i of e()){if(i.localReady)continue;const a=o.getManifest(i.sessionId)?.firstAvailableSeq;if(a==null)continue;const r=o.readPage(i.sessionId,{limit:1,beforeSequence:a+1}).entries[0];!r||r.sequence!==a||r.message.role!=="user"||(s({sessionId:i.sessionId,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId,status:i.status,lastActivityAt:i.lastActivityAt,lastMessagePreview:i.lastMessagePreview,localReady:!0,firstUserMessageId:r.message.id,firstUserBodyRevision:r.revision}),n+=1)}return n}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){k(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,g(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&g(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],g(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(O(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),g(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],g(this.managedEchoStateFile(),o))}listSourceBindings(){return Object.values(this.readSources().sources).filter(e=>!this.isSourceDeleted(e.agentType,e.sourceSessionKey))}purgeSession(e){if(!e)throw new Error("sessionId is required");const s=this.readSources();let o=!1;for(const[a,r]of Object.entries(s.sources))r.sessionId===e&&(delete s.sources[a],o=!0);o&&g(this.sourcesFile(),s);const n=this.readManagedEchoState();let i=!1;for(const[a,r]of Object.entries(n.claims))r.sessionId===e&&(delete n.claims[a],i=!0);for(const[a,r]of Object.entries(n.suppressions))r.sessionId===e&&(delete n.suppressions[a],i=!0);i&&g(this.managedEchoStateFile(),n);const c=h.join(this.rootDir(),"ledger");if(l.existsSync(c))for(const a of l.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=h.join(c,a.name),m=D(r);if(m.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");m.sessionId===e&&l.unlinkSync(r)}}isSourceDeleted(e,s){return(this.options.deletionStore??new V({rootDir:h.basename(this.rootDir())==="native-fusion"?h.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const n=o.trim().slice(0,120);if(!n)return!1;const i=this.readSources().sources[b(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??F)().find(m=>m.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=p.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??w)({sessionId:i.sessionId,agentType:i.agentType,workDir:c?.workDir??"",agentSessionId:s,title:n,titleSource:"agent_native",titleOriginEventId:`agent-native:${a}`}),!0}recordIndex(e,s,o,n){const i=this.options.recordLocalSession??w,c=e.terminal?`terminal:${p.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?v(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{};i({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?"completed":"active",attentionId:c,lastActivityAt:new Date(e.ts).toISOString(),...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?v(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!l.existsSync(e))return{schemaVersion:I,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==I||s.daemonInstallationId!==this.installationId()||!s.sources)throw new Error("invalid native source binding store");for(const o of Object.values(s.sources))if(o.daemonInstallationId!==this.installationId())throw new Error("native source binding belongs to another daemon installation");return s}installationId(){return this.options.daemonInstallationId??H()}canonicalStore(){return this.options.canonicalStore??P()}rootDir(){return this.options.rootDir??R("native-fusion")}sourcesFile(){return h.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return h.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:I,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==I||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))k(o);for(const o of Object.values(s.suppressions))O(o);return s}ledgerFile(e,s){const o=p.createHash("sha256").update(`${e}\0${s}`).digest("hex");return h.join(this.rootDir(),"ledger",`${o}.json`)}}function z(t){if(!t.agentType||!t.sourceSessionKey||!t.sourceEventKey||!t.payload)throw new Error("invalid native event");if(!Number.isFinite(t.ts))throw new Error("invalid native event timestamp")}function b(t,e){return p.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function G(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Q(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function X(t){return p.createHash("sha256").update(JSON.stringify({agentType:t.agentType,sourceSessionKey:t.sourceSessionKey,sourceEventKey:t.sourceEventKey,role:t.role,ts:t.ts,payload:t.payload,canonicalMessageKey:t.canonicalMessageKey??null,canonicalMutation:t.canonicalMutation??"append"})).digest("hex")}function k(t){if(!t.sessionId||!t.canonicalMessageId||!t.textHash||!Number.isFinite(t.createdAt)||!["codex","claude","opencode"].includes(t.sourceAgentType)||!["suppress_following","import_following"].includes(t.managedEchoPolicy))throw new Error("invalid managed echo claim")}function O(t){if(!t.sessionId||!t.sourceSessionKey||!t.claimSourceEventKey||!["codex","claude","opencode"].includes(t.agentType)||!Number.isFinite(t.startedAt)||!Number.isFinite(t.endsAt)||t.endsAt<t.startedAt)throw new Error("invalid managed echo suppression")}function Y(t,e){if(!t)return e;try{const s=JSON.parse(t),o=JSON.parse(e);if(s.type!=="tool"||o.type!=="tool"||s.toolCallId!==o.toolCallId)return e;const n=typeof o.name=="string"?o.name:"",i=/(?:_output|_result)$/.test(n);return JSON.stringify({...s,...o,...i&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function Z(t,e){if(!t||t.toolCallId!==e.toolCallId)return e;const s=/(?:_output|_result)$/.test(e.name);return{...e,name:s?t.name:e.name,...e.args===void 0&&t.args!==void 0?{args:t.args}:{},...e.result===void 0&&t.result!==void 0?{result:t.result}:{}}}function v(t){return L(t,120)}function g(t,e){const s=h.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let n;try{n=l.openSync(o,"wx",384),l.writeFileSync(n,`${JSON.stringify(e,null,2)}
|
|
1
|
+
import p from"node:crypto";import l from"node:fs";import h from"node:path";import{sessionPreviewFromPayload as L}from"@shennian/wire";import{resolvePersonalSyncV1Path as R}from"../personal-sync/paths.js";import{getCanonicalSessionStore as P,getDaemonInstallationId as H,listSessionRecords as F,recordSession as w}from"../session/store.js";import{readToolDetail as U,writeToolDetail as J}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as V}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as A}from"../personal-sync/metrics.js";const I=1;class q extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class W extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class le{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}updateMessageDeliveryState(e,s,o){if(!this.canonicalStore().updateMessageDeliveryState(e,s,o))return;const i=(this.options.listLocalSessions??F)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(z(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new W(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=X(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=l.existsSync(a)?D(a):null;if(r&&r.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(r&&r.eventFingerprint!==c)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!i&&!r&&e.role!=="user")throw new q(e.sourceSessionKey);const m=e.claimSessionId??null,d=i?.sessionId??r?.sessionId??m??G(s,e.agentType,e.sourceSessionKey);if(m&&i&&i.sessionId!==m)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const f=this.canonicalStore(),S=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",u=r?.canonicalMessageId??(S?null:e.claimCanonicalMessageId??Q(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let y=f.getManifest(d)?.bodyRevision??0,T=!!r;const M=!S&&!!e.claimCanonicalMessageId;if(M){if(!u)throw new Error("managed echo claim requires a canonical message");const E=f.getMessageState(d,u);if(!E||E.tombstoned)throw new Error(`managed echo claim target is not durable: ${u}`)}if(!r&&!M&&!S){if(!u)throw new Error("native event requires a canonical message");const E=e.canonicalMessageKey?f.getMessageState(d,u):null,x=e.canonicalMutation==="upsert"||!!E,$=x?E??f.getMessageState(d,u):null,_={id:u,sessionId:d,role:e.role,ts:$?.message?.ts??e.ts,payload:Y($?.message?.payload,e.payload)},C=x?f.upsert(d,_):f.append(d,_);if(y=C.bodyRevision,T=C.duplicate,e.localToolDetail){const N=this.options.writeLocalToolDetail??J,v=this.options.readLocalToolDetail??U,B=Z(v(d,e.localToolDetail.toolCallId),e.localToolDetail);N({sessionId:d,...B,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(y=f.completeStartedUserMessages(d,e.ts).at(-1)?.bodyRevision??y),(!r||e.terminal)&&!S&&this.recordIndex(e,d,u,y);const K=new Date().toISOString();return r||g(a,{schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:u,canonicalRevision:y,cursor:e.cursor??null,mappedBy:S?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:K}),o.sources[n]={schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:r?i?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:r?i?.lastCursor??r.cursor:e.cursor??i?.lastCursor??null,bodyRevision:y,createdAt:i?.createdAt??K,updatedAt:K},g(this.sourcesFile(),o),!r&&e.cursor&&A("sourceCursorAdvanced"),!r&&!M&&!S&&!T&&A("canonicalCommits"),{sessionId:d,messageId:u,bodyRevision:y,duplicate:T,claimed:M,suppressed:S}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??F,s=this.options.recordLocalSession??w,o=this.canonicalStore();let n=0;for(const i of e()){if(i.localReady)continue;const a=o.getManifest(i.sessionId)?.firstAvailableSeq;if(a==null)continue;const r=o.readPage(i.sessionId,{limit:1,beforeSequence:a+1}).entries[0];!r||r.sequence!==a||r.message.role!=="user"||(s({sessionId:i.sessionId,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId,status:i.status,lastActivityAt:i.lastActivityAt,lastMessagePreview:i.lastMessagePreview,localReady:!0,firstUserMessageId:r.message.id,firstUserBodyRevision:r.revision}),n+=1)}return n}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){k(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,g(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&g(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],g(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(O(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),g(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],g(this.managedEchoStateFile(),o))}listSourceBindings(){return Object.values(this.readSources().sources).filter(e=>!this.isSourceDeleted(e.agentType,e.sourceSessionKey))}purgeSession(e){if(!e)throw new Error("sessionId is required");const s=this.readSources();let o=!1;for(const[a,r]of Object.entries(s.sources))r.sessionId===e&&(delete s.sources[a],o=!0);o&&g(this.sourcesFile(),s);const n=this.readManagedEchoState();let i=!1;for(const[a,r]of Object.entries(n.claims))r.sessionId===e&&(delete n.claims[a],i=!0);for(const[a,r]of Object.entries(n.suppressions))r.sessionId===e&&(delete n.suppressions[a],i=!0);i&&g(this.managedEchoStateFile(),n);const c=h.join(this.rootDir(),"ledger");if(l.existsSync(c))for(const a of l.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=h.join(c,a.name),m=D(r);if(m.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");m.sessionId===e&&l.unlinkSync(r)}}isSourceDeleted(e,s){return(this.options.deletionStore??new V({rootDir:h.basename(this.rootDir())==="native-fusion"?h.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const n=o.trim().slice(0,120);if(!n)return!1;const i=this.readSources().sources[b(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??F)().find(m=>m.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=p.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??w)({sessionId:i.sessionId,agentType:i.agentType,workDir:c?.workDir??"",agentSessionId:s,title:n,titleSource:"agent_native",titleOriginEventId:`agent-native:${a}`}),!0}recordIndex(e,s,o,n){const i=this.options.recordLocalSession??w,c=e.terminal?`terminal:${p.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?j(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{};i({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?e.terminalState==="failed"||e.terminalState==="aborted"?"failed":"completed":"active",attentionId:c,lastActivityAt:new Date(e.ts).toISOString(),...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?j(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!l.existsSync(e))return{schemaVersion:I,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==I||s.daemonInstallationId!==this.installationId()||!s.sources)throw new Error("invalid native source binding store");for(const o of Object.values(s.sources))if(o.daemonInstallationId!==this.installationId())throw new Error("native source binding belongs to another daemon installation");return s}installationId(){return this.options.daemonInstallationId??H()}canonicalStore(){return this.options.canonicalStore??P()}rootDir(){return this.options.rootDir??R("native-fusion")}sourcesFile(){return h.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return h.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:I,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==I||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))k(o);for(const o of Object.values(s.suppressions))O(o);return s}ledgerFile(e,s){const o=p.createHash("sha256").update(`${e}\0${s}`).digest("hex");return h.join(this.rootDir(),"ledger",`${o}.json`)}}function z(t){if(!t.agentType||!t.sourceSessionKey||!t.sourceEventKey||!t.payload)throw new Error("invalid native event");if(!Number.isFinite(t.ts))throw new Error("invalid native event timestamp")}function b(t,e){return p.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function G(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Q(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function X(t){return p.createHash("sha256").update(JSON.stringify({agentType:t.agentType,sourceSessionKey:t.sourceSessionKey,sourceEventKey:t.sourceEventKey,role:t.role,ts:t.ts,payload:t.payload,canonicalMessageKey:t.canonicalMessageKey??null,canonicalMutation:t.canonicalMutation??"append"})).digest("hex")}function k(t){if(!t.sessionId||!t.canonicalMessageId||!t.textHash||!Number.isFinite(t.createdAt)||!["codex","claude","opencode"].includes(t.sourceAgentType)||!["suppress_following","import_following"].includes(t.managedEchoPolicy))throw new Error("invalid managed echo claim")}function O(t){if(!t.sessionId||!t.sourceSessionKey||!t.claimSourceEventKey||!["codex","claude","opencode"].includes(t.agentType)||!Number.isFinite(t.startedAt)||!Number.isFinite(t.endsAt)||t.endsAt<t.startedAt)throw new Error("invalid managed echo suppression")}function Y(t,e){if(!t)return e;try{const s=JSON.parse(t),o=JSON.parse(e);if(s.type!=="tool"||o.type!=="tool"||s.toolCallId!==o.toolCallId)return e;const n=typeof o.name=="string"?o.name:"",i=/(?:_output|_result)$/.test(n);return JSON.stringify({...s,...o,...i&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function Z(t,e){if(!t||t.toolCallId!==e.toolCallId)return e;const s=/(?:_output|_result)$/.test(e.name);return{...e,name:s?t.name:e.name,...e.args===void 0&&t.args!==void 0?{args:t.args}:{},...e.result===void 0&&t.result!==void 0?{result:t.result}:{}}}function j(t){return L(t,120)}function g(t,e){const s=h.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let n;try{n=l.openSync(o,"wx",384),l.writeFileSync(n,`${JSON.stringify(e,null,2)}
|
|
2
2
|
`,"utf8"),l.fsyncSync(n),l.closeSync(n),n=void 0,l.renameSync(o,t)}finally{n!==void 0&&l.closeSync(n);try{l.unlinkSync(o)}catch{}}}function D(t){return JSON.parse(l.readFileSync(t,"utf8"))}export{q as MissingUserBoundaryError,le as NativeFusionLocalRepository,W as NativeSourceDeletedError};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import K from"node:fs";import B from"node:crypto";import{publishPendingSessionIndexes as N}from"../session/index-publisher.js";import{NativeFusionFileWatcher as P}from"./file-watcher.js";import{NativeFusionLocalRepository as
|
|
2
|
-
`).trim()}function F(){return new Promise(u=>setImmediate(u))}function ee(u){return u.split(/[\\/]+/).includes(".codex")}class de{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new P(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;constructor(e,s=new O){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[T(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},Q))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,i=this.pendingClaims.get(t),c=_(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==c))throw new Error(`managed echo claim identity conflict: ${t}`);const l={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:c,createdAt:i?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??i?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(l),this.pendingClaims.set(t,l),e.externalRunId&&e.sourceSessionKey){const y=`${s}:${e.sourceSessionKey}`,g=this.externalObservations.get(y)??[];g.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(y,g),this.emitExternalActivity(g[g.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const i of this.pendingClaims.values())i.sessionId===e&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const i=t.filter(c=>c.sessionId!==e);i.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,i)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},X),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-final`,payload:{state:"final",sessionId:e.sessionId,runId:e.runId,seq:2,owner:"external",ownerLabel:"Codex Desktop"}})}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=V(),t={...s.files},i=[],c=new Map,l=new Map,y=new Map,g=e?[...e]:[...R(),...k(),...L()];!e&&this.watchEnabled&&this.watcher.refresh(g,D());for(let o=0;o<g.length;o+=1){const n=g[o];if(o>0&&o%G===0&&await F(),!K.existsSync(n)){this.watchEnabled&&this.watcher.untrackFile(n),this.pendingBoundaryFiles.delete(n);for(const[r,p]of this.sourceFiles.entries())p===n&&this.sourceFiles.delete(r);continue}this.watchEnabled&&this.watcher.trackFile(n);const d=K.statSync(n),h=ee(n),f=n.endsWith(".opencode-session.json");let a=s.files[n];const I=`${String(d.dev)}:${String(d.ino)}:${String(d.birthtimeMs)}`;let x=!a;if(a&&!f&&(d.size<a.offset||a.fileIdentity&&a.fileIdentity!==I)&&(a=void 0,x=!0),!!(a&&a.fileIdentity===I&&a.mtimeMs===d.mtimeMs&&a.offset>=d.size&&a.sources?.length)&&a){for(const r of a.sources??[])this.sourceFiles.set(T(r.agentType,r.sourceSessionKey),n);a.classification==="live"&&a.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);continue}const m=a?.sources?.length&&!f?{status:"ready",createdAtMs:a.sessionCreatedAtMs??d.birthtimeMs,sources:a.sources}:q(n);if(m.status==="pending"){d.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(n);continue}for(const r of m.sources)this.sourceFiles.set(T(r.agentType,r.sourceSessionKey),n);if(!f&&m.sources.length>0&&m.sources.every(r=>this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))){t[n]={...a??{daemonInstallationId:s.daemonInstallationId,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}},offset:d.size,mtimeMs:d.mtimeMs,fileIdentity:I};continue}if(!a)a={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:d.mtimeMs,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}};else if(f&&a.mtimeMs===d.mtimeMs)continue;const E=f?0:a.offset,w=f?z(n,E):h?U(n,E):$(n,E);for(const r of w.events)this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey)||r.title?.trim()&&r.titleSource==="agent_native"&&y.set(`${r.agentType}:${r.sourceSessionKey}`,r);let M,S;if(f){const r=te(w.events,a,s.fusionEpochMs,x,d.mtimeMs,w.nextOffset);M=r.events,S=r.cursor}else if(x&&a.classification==="historical")M=[],S={...a,offset:w.nextOffset,mtimeMs:d.mtimeMs,awaitingUserBoundary:!0};else{const r=se(w.events,a,w.nextOffset,d.mtimeMs);M=r.events,S=r.cursor}S.fileIdentity=I,S.sources=m.sources,t[n]=S,S.classification==="live"&&S.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);for(const r of M){if(this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))continue;const p=this.tryClaimManagedEcho(r);if(p){i.push(p.event),c.set(p.claimKey,p.suppression??null),p.suppression&&l.set(T(p.suppression.agentType,p.suppression.sourceSessionKey),{window:p.suppression,claimKey:p.claimKey});continue}const b=this.suppressionFor(r,l,c);if(b){i.push({...r,claimSessionId:b.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(r)}}const v=[...i].sort((o,n)=>o.ts-n.ts);v.length===0&&this.repository.commitEvents([]);for(let o=0;o<v.length;o+=A)this.repository.commitEvents(v.slice(o,o+A)),o+A<v.length&&await F();for(const[o,n]of c)this.repository.completeManagedClaim?.(o,n??void 0),this.pendingClaims.delete(o),n&&this.suppressionWindows.set(T(n.agentType,n.sourceSessionKey),n);for(const o of y.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const n=W(o.sourceSessionKey);n&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,n)}for(const o of v)this.observeExternalLifecycle(o);s.files=t,H(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await N(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(l=>e-l.createdAt<C);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const c=i[0];e-c.lastPublishedAt<J||(c.lastPublishedAt=e,this.emitExternalActivity(c,c.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries()){if(i.sourceAgentType!==e.agentType||e.role!=="user"||!i.sourceSessionKey||i.sourceSessionKey!==e.sourceSessionKey)continue;const c=i.externalRunId?C:Y;if(Math.abs(e.ts-i.createdAt)>c||_(e.payload)!==i.textHash)continue;const l=i.managedEchoPolicy==="suppress_following"?{sessionId:i.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+j}:void 0;return{claimKey:t,suppression:l,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId,claimSessionId:i.sessionId,managedEchoPolicy:i.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const i=t[0];e.claimCanonicalMessageId===i.canonicalMessageId&&i.state==="queued"&&(this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,"executing"),i.state="running",i.lastPublishedAt=Date.now(),this.emitExternalActivity(i,"running")),!(!e.terminal||i.state!=="running")&&(this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,"completed"),this.emitExternalTerminal(i),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued")))}suppressionFor(e,s,t){const i=T(e.agentType,e.sourceSessionKey),c=s.get(i),l=c?.window??this.suppressionWindows.get(i);return!l||e.ts<l.startedAt?null:e.ts>l.endsAt?(this.clearSuppression(i,l,c,s,t),null):e.role==="user"?(e.sourceEventKey===l.claimSourceEventKey||this.clearSuppression(i,l,c,s,t),null):l}clearSuppression(e,s,t,i,c){if(t){i.delete(e),c.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function _(u){return B.createHash("sha256").update(Z(u)).digest("hex")}function T(u,e){return`${u}:${e}`}function se(u,e,s,t){if(!e.awaitingUserBoundary)return{events:u,cursor:{...e,offset:s,mtimeMs:t}};if(u.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=u.findIndex(c=>c.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:u.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function te(u,e,s,t,i,c){const l={...e.sourceBoundaryEstablished??{}},y=e.eventWatermarkTs??-1,g=new Set(e.eventKeysAtWatermark??[]),v=t?u:u.filter(h=>h.ts>y||h.ts===y&&!g.has(h.sourceEventKey)),o=[];for(const h of v){const f=h.sourceSessionKey;if(l[f]){o.push(h);continue}!(!t||(h.sourceSessionCreatedAtMs??0)>=s)||h.role!=="user"||(l[f]=!0,o.push(h))}const n=u.reduce((h,f)=>Math.max(h,f.ts),y),d=new Set(n===y?e.eventKeysAtWatermark??[]:[]);for(const h of u)h.ts===n&&d.add(h.sourceEventKey);return{events:o,cursor:{...e,offset:c,mtimeMs:i,eventWatermarkTs:n>=0?n:void 0,eventKeysAtWatermark:n>=0?[...d]:void 0,sourceBoundaryEstablished:l}}}export{de as NativeSessionFusionService};
|
|
1
|
+
import K from"node:fs";import B from"node:crypto";import{publishPendingSessionIndexes as N}from"../session/index-publisher.js";import{NativeFusionFileWatcher as P}from"./file-watcher.js";import{NativeFusionLocalRepository as k}from"./local-repository.js";import{listClaudeTranscriptFiles as O,listCodexRolloutFiles as R,listOpenCodeSessionFiles as D,listNativeTranscriptRoots as L,inspectNativeSessionFile as q,lookupCodexThreadName as W,parseClaudeTranscriptChunk as $,parseCodexRolloutChunk as U,parseOpenCodeSessionFile as z}from"./parsers.js";import{loadNativeScannerState as V,saveNativeScannerState as H}from"./state.js";const Q=5*6e4,Y=2*6e4,j=30*6e4,X=2e3,G=32,A=16,J=3e4,C=240*6e4;function Z(u){return u.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function F(){return new Promise(u=>setImmediate(u))}function ee(u){return u.split(/[\\/]+/).includes(".codex")}class de{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new P(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;constructor(e,s=new k){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[T(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},Q))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,i=this.pendingClaims.get(t),a=_(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==a))throw new Error(`managed echo claim identity conflict: ${t}`);const l={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:a,createdAt:i?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??i?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(l),this.pendingClaims.set(t,l),e.externalRunId&&e.sourceSessionKey){const y=`${s}:${e.sourceSessionKey}`,g=this.externalObservations.get(y)??[];g.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(y,g),this.emitExternalActivity(g[g.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const i of this.pendingClaims.values())i.sessionId===e&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const i=t.filter(a=>a.sessionId!==e);i.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,i)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},X),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e,s="completed"){const t=s==="completed"?"final":s==="aborted"?"aborted":"error";this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}`,payload:{state:t,sessionId:e.sessionId,runId:e.runId,seq:2,...t==="error"?{message:"Codex Desktop turn failed"}:{},owner:"external",ownerLabel:"Codex Desktop"}})}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=V(),t={...s.files},i=[],a=new Map,l=new Map,y=new Map,g=e?[...e]:[...R(),...O(),...D()];!e&&this.watchEnabled&&this.watcher.refresh(g,L());for(let o=0;o<g.length;o+=1){const n=g[o];if(o>0&&o%G===0&&await F(),!K.existsSync(n)){this.watchEnabled&&this.watcher.untrackFile(n),this.pendingBoundaryFiles.delete(n);for(const[r,p]of this.sourceFiles.entries())p===n&&this.sourceFiles.delete(r);continue}this.watchEnabled&&this.watcher.trackFile(n);const d=K.statSync(n),h=ee(n),f=n.endsWith(".opencode-session.json");let c=s.files[n];const I=`${String(d.dev)}:${String(d.ino)}:${String(d.birthtimeMs)}`;let x=!c;if(c&&!f&&(d.size<c.offset||c.fileIdentity&&c.fileIdentity!==I)&&(c=void 0,x=!0),!!(c&&c.fileIdentity===I&&c.mtimeMs===d.mtimeMs&&c.offset>=d.size&&c.sources?.length)&&c){for(const r of c.sources??[])this.sourceFiles.set(T(r.agentType,r.sourceSessionKey),n);c.classification==="live"&&c.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);continue}const m=c?.sources?.length&&!f?{status:"ready",createdAtMs:c.sessionCreatedAtMs??d.birthtimeMs,sources:c.sources}:q(n);if(m.status==="pending"){d.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(n);continue}for(const r of m.sources)this.sourceFiles.set(T(r.agentType,r.sourceSessionKey),n);if(!f&&m.sources.length>0&&m.sources.every(r=>this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))){t[n]={...c??{daemonInstallationId:s.daemonInstallationId,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}},offset:d.size,mtimeMs:d.mtimeMs,fileIdentity:I};continue}if(!c)c={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:d.mtimeMs,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}};else if(f&&c.mtimeMs===d.mtimeMs)continue;const E=f?0:c.offset,w=f?z(n,E):h?U(n,E):$(n,E);for(const r of w.events)this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey)||r.title?.trim()&&r.titleSource==="agent_native"&&y.set(`${r.agentType}:${r.sourceSessionKey}`,r);let M,S;if(f){const r=te(w.events,c,s.fusionEpochMs,x,d.mtimeMs,w.nextOffset);M=r.events,S=r.cursor}else if(x&&c.classification==="historical")M=[],S={...c,offset:w.nextOffset,mtimeMs:d.mtimeMs,awaitingUserBoundary:!0};else{const r=se(w.events,c,w.nextOffset,d.mtimeMs);M=r.events,S=r.cursor}S.fileIdentity=I,S.sources=m.sources,t[n]=S,S.classification==="live"&&S.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);for(const r of M){if(this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))continue;const p=this.tryClaimManagedEcho(r);if(p){i.push(p.event),a.set(p.claimKey,p.suppression??null),p.suppression&&l.set(T(p.suppression.agentType,p.suppression.sourceSessionKey),{window:p.suppression,claimKey:p.claimKey});continue}const b=this.suppressionFor(r,l,a);if(b){i.push({...r,claimSessionId:b.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(r)}}const v=[...i].sort((o,n)=>o.ts-n.ts);v.length===0&&this.repository.commitEvents([]);for(let o=0;o<v.length;o+=A)this.repository.commitEvents(v.slice(o,o+A)),o+A<v.length&&await F();for(const[o,n]of a)this.repository.completeManagedClaim?.(o,n??void 0),this.pendingClaims.delete(o),n&&this.suppressionWindows.set(T(n.agentType,n.sourceSessionKey),n);for(const o of y.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const n=W(o.sourceSessionKey);n&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,n)}for(const o of v)this.observeExternalLifecycle(o);s.files=t,H(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await N(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(l=>e-l.createdAt<C);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const a=i[0];e-a.lastPublishedAt<J||(a.lastPublishedAt=e,this.emitExternalActivity(a,a.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries()){if(i.sourceAgentType!==e.agentType||e.role!=="user"||!i.sourceSessionKey||i.sourceSessionKey!==e.sourceSessionKey)continue;const a=i.externalRunId?C:Y;if(Math.abs(e.ts-i.createdAt)>a||_(e.payload)!==i.textHash)continue;const l=i.managedEchoPolicy==="suppress_following"?{sessionId:i.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+j}:void 0;return{claimKey:t,suppression:l,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId,claimSessionId:i.sessionId,managedEchoPolicy:i.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const i=t[0];if(e.claimCanonicalMessageId===i.canonicalMessageId&&i.state==="queued"&&(this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,"executing"),i.state="running",i.lastPublishedAt=Date.now(),this.emitExternalActivity(i,"running")),!e.terminal||i.state!=="running")return;const a=e.terminalState??"completed";this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,a==="aborted"?"cancelled":a==="failed"?"failed":"completed"),this.emitExternalTerminal(i,a),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued"))}suppressionFor(e,s,t){const i=T(e.agentType,e.sourceSessionKey),a=s.get(i),l=a?.window??this.suppressionWindows.get(i);return!l||e.ts<l.startedAt?null:e.ts>l.endsAt?(this.clearSuppression(i,l,a,s,t),null):e.role==="user"?(e.sourceEventKey===l.claimSourceEventKey||this.clearSuppression(i,l,a,s,t),null):l}clearSuppression(e,s,t,i,a){if(t){i.delete(e),a.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function _(u){return B.createHash("sha256").update(Z(u)).digest("hex")}function T(u,e){return`${u}:${e}`}function se(u,e,s,t){if(!e.awaitingUserBoundary)return{events:u,cursor:{...e,offset:s,mtimeMs:t}};if(u.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=u.findIndex(a=>a.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:u.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function te(u,e,s,t,i,a){const l={...e.sourceBoundaryEstablished??{}},y=e.eventWatermarkTs??-1,g=new Set(e.eventKeysAtWatermark??[]),v=t?u:u.filter(h=>h.ts>y||h.ts===y&&!g.has(h.sourceEventKey)),o=[];for(const h of v){const f=h.sourceSessionKey;if(l[f]){o.push(h);continue}!(!t||(h.sourceSessionCreatedAtMs??0)>=s)||h.role!=="user"||(l[f]=!0,o.push(h))}const n=u.reduce((h,f)=>Math.max(h,f.ts),y),d=new Set(n===y?e.eventKeysAtWatermark??[]:[]);for(const h of u)h.ts===n&&d.add(h.sourceEventKey);return{events:o,cursor:{...e,offset:a,mtimeMs:i,eventWatermarkTs:n>=0?n:void 0,eventKeysAtWatermark:n>=0?[...d]:void 0,sourceBoundaryEstablished:l}}}export{de as NativeSessionFusionService};
|
|
@@ -26,6 +26,8 @@ export type NativeScannerState = {
|
|
|
26
26
|
codexAppContextWrapperFixed?: boolean;
|
|
27
27
|
};
|
|
28
28
|
export type ParsedNativeEvent = NativeSessionEventPayload & {
|
|
29
|
+
/** Terminal outcome observed in the native source; `terminal` remains the wire-compatible flag. */
|
|
30
|
+
terminalState?: 'completed' | 'failed' | 'aborted';
|
|
29
31
|
repairHint?: NativeRepairHint;
|
|
30
32
|
sourceSessionCreatedAtMs?: number;
|
|
31
33
|
claimSessionId?: string | null;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { AgentType, ManagerConfig, SessionMode } from './session.ts';
|
|
2
|
-
import type { RoomActivationMode } from './room.ts';
|
|
2
|
+
import type { AgentRepairAction, AgentRuntimeAvailability, RoomActivationMode } from './room.ts';
|
|
3
3
|
export type AgentWorkspaceRoomLink = {
|
|
4
4
|
bindingId: string;
|
|
5
5
|
status: string;
|
|
6
6
|
roomId: string;
|
|
7
7
|
roomName: string;
|
|
8
8
|
activationMode: RoomActivationMode;
|
|
9
|
+
/** Optional for compatibility with servers released before machine recovery. */
|
|
10
|
+
availability?: AgentRuntimeAvailability;
|
|
11
|
+
repairAction?: AgentRepairAction;
|
|
9
12
|
};
|
|
10
13
|
export type AgentWorkspaceSession = {
|
|
11
14
|
id: string;
|
|
@@ -31,6 +34,9 @@ export type AgentWorkspace = {
|
|
|
31
34
|
updatedAt: string;
|
|
32
35
|
} | null;
|
|
33
36
|
status: string;
|
|
37
|
+
/** Optional additive field; old cached projections safely treat absence as available. */
|
|
38
|
+
availability?: AgentRuntimeAvailability;
|
|
39
|
+
repairAction?: AgentRepairAction;
|
|
34
40
|
pinnedAt?: string | null;
|
|
35
41
|
machineId: string | null;
|
|
36
42
|
machineName: string | null;
|
|
@@ -7,6 +7,13 @@ export type RoomMembershipRole = 'owner' | 'admin' | 'member';
|
|
|
7
7
|
export type RoomMembershipStatus = 'active' | 'removed' | 'left';
|
|
8
8
|
export type RoomMessageKind = 'text' | 'attachment' | 'mixed' | 'system';
|
|
9
9
|
export type RoomActivationMode = 'auto_follow' | 'mention_only' | 'paused';
|
|
10
|
+
/** Runtime reachability is orthogonal to the owner's persisted activation mode. */
|
|
11
|
+
export type AgentRuntimeAvailability = 'available' | 'unavailable' | 'recovering';
|
|
12
|
+
export type AgentRepairAction = 'configure' | 'retry' | null;
|
|
13
|
+
export type RoomAgentParticipation = {
|
|
14
|
+
activationMode: RoomActivationMode;
|
|
15
|
+
availability: AgentRuntimeAvailability;
|
|
16
|
+
};
|
|
10
17
|
export type RoomActivationCapability = 'managed' | 'idle_resume' | 'notify_only';
|
|
11
18
|
export type RoomBindingMode = 'managed' | 'hook_native';
|
|
12
19
|
export type RoomBindingStatus = 'pending_approval' | 'active' | 'paused' | 'detached';
|