shennian 0.3.82 → 0.3.83
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.
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import B from"node:fs";import O from"node:crypto";import{publishPendingSessionIndexes as k}from"../session/index-publisher.js";import{NativeFusionFileWatcher as R}from"./file-watcher.js";import{NativeEventIdentityConflictError as q,NativeFusionLocalRepository as L}from"./local-repository.js";import{listClaudeTranscriptFiles as D,listCodexRolloutFiles as W,listOpenCodeSessionFiles as $,listNativeTranscriptRoots as z,inspectNativeSessionFile as U,lookupCodexThreadName as H,parseClaudeTranscriptChunk as V,parseCodexRolloutChunk as Q,parseOpenCodeSessionFile as Y}from"./parsers.js";import{MAX_JSONL_SCAN_BYTES_PER_PASS as X}from"./parser-common.js";import{loadNativeScannerState as j,saveNativeScannerState as J}from"./state.js";const G=5*6e4,Z=2*6e4,ee=30*6e4,se=2e3,te=1,F=16,ie=3e4,N=240*6e4;function ne(d){return d.replace(/\r\n/g,`
|
|
2
|
-
`).trim()}function C(){return new Promise(d=>setImmediate(d))}function oe(d){return d.split(/[\\/]+/).includes(".codex")}class me{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 R(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;externalTerminalHandler=null;constructor(e,s=new L){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=>[I(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},G))}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()}setExternalTerminalHandler(e){this.externalTerminalHandler=e}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=P(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==a))throw new Error(`managed echo claim identity conflict: ${t}`);const u={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?.(u),this.pendingClaims.set(t,u),e.externalRunId&&e.sourceSessionKey){const p=`${s}:${e.sourceSessionKey}`,y=this.externalObservations.get(p)??[];y.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(p,y),this.emitExternalActivity(y[y.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()},se),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"}}),this.externalTerminalHandler?.(e.sessionId)}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){await C(),this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){await C(),s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=j(),t={...s.files},i=[],a=new Map,u=new Map,p=new Map,y=e?[...e]:[...W(),...D(),...$()];!e&&this.watchEnabled&&this.watcher.refresh(y,z());for(let n=0;n<y.length;n+=1){const o=y[n];if(n>0&&n%te===0&&await C(),!B.existsSync(o)){this.watchEnabled&&this.watcher.untrackFile(o),this.pendingBoundaryFiles.delete(o);for(const[r,f]of this.sourceFiles.entries())f===o&&this.sourceFiles.delete(r);continue}this.watchEnabled&&this.watcher.trackFile(o);const l=B.statSync(o),E=oe(o),h=o.endsWith(".opencode-session.json");let c=s.files[o];const x=`${String(l.dev)}:${String(l.ino)}:${String(l.birthtimeMs)}`;let b=!c;if(c&&!h&&(l.size<c.offset||c.fileIdentity&&c.fileIdentity!==x)&&(c=void 0,b=!0),!!(c&&c.fileIdentity===x&&c.mtimeMs===l.mtimeMs&&c.offset>=l.size&&c.sources?.length)&&c){for(const r of c.sources??[])this.sourceFiles.set(I(r.agentType,r.sourceSessionKey),o);c.classification==="live"&&c.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o);continue}const g=c?.sources?.length&&!h?{status:"ready",createdAtMs:c.sessionCreatedAtMs??l.birthtimeMs,sources:c.sources}:U(o);if(g.status==="pending"){l.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(o);continue}for(const r of g.sources)this.sourceFiles.set(I(r.agentType,r.sourceSessionKey),o);if(!h&&g.sources.length>0&&g.sources.every(r=>this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))){t[o]={...c??{daemonInstallationId:s.daemonInstallationId,classification:g.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:g.createdAtMs,sourceBoundaryEstablished:{}},offset:l.size,mtimeMs:l.mtimeMs,fileIdentity:x};continue}if(!c)c={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:l.mtimeMs,classification:g.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:g.createdAtMs,sourceBoundaryEstablished:{}};else if(h&&c.mtimeMs===l.mtimeMs)continue;const A=h?0:c.offset,w=h?Y(o,A):E?Q(o,A):V(o,A);for(const r of w.events)this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey)||r.title?.trim()&&r.titleSource==="agent_native"&&p.set(`${r.agentType}:${r.sourceSessionKey}`,r);let K,S;if(h){const r=ae(w.events,c,s.fusionEpochMs,b,l.mtimeMs,w.nextOffset);K=r.events,S=r.cursor}else if(b&&c.classification==="historical")K=[],S={...c,offset:w.nextOffset,mtimeMs:l.mtimeMs,awaitingUserBoundary:!0};else{const r=re(w.events,c,w.nextOffset,l.mtimeMs);K=r.events,S=r.cursor}S.fileIdentity=x,S.sources=g.sources,t[o]=S,w.nextOffset<l.size&&l.size-A>X&&this.queuedTargetFiles.add(o),S.classification==="live"&&S.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o);for(const r of K){if(this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))continue;const f=this.tryClaimManagedEcho(r);if(f){i.push(f.event),a.set(f.claimKey,f.suppression??null),f.suppression&&u.set(I(f.suppression.agentType,f.suppression.sourceSessionKey),{window:f.suppression,claimKey:f.claimKey});continue}const _=this.suppressionFor(r,u,a);if(_){i.push({...r,claimSessionId:_.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(r)}}const v=[...i].sort((n,o)=>n.ts-o.ts),T=new Set,m=new Set,M=n=>{try{this.repository.commitEvents(n)}catch(o){if(!(o instanceof q))throw o;const l=n.find(h=>h.sourceEventKey===o.sourceEventKey);if(!l)throw o;T.add(l.sourceEventKey),l.claimCanonicalMessageId&&m.add(l.claimCanonicalMessageId),console.warn(`[native-fusion] quarantined conflicting native event ${l.sourceEventKey}`);const E=n.filter(h=>h!==l);E.length>0&&M(E)}};v.length===0&&this.repository.commitEvents([]);for(let n=0;n<v.length;n+=F)M(v.slice(n,n+F)),n+F<v.length&&await C();for(const[n,o]of a)m.has(n)||(this.repository.completeManagedClaim?.(n,o??void 0),this.pendingClaims.delete(n),o&&this.suppressionWindows.set(I(o.agentType,o.sourceSessionKey),o));for(const n of p.values())this.repository.observeNativeTitle(n.agentType,n.sourceSessionKey,n.title??"");for(const n of this.repository.listSourceBindings()){if(n.agentType!=="codex")continue;const o=H(n.sourceSessionKey);o&&this.repository.observeNativeTitle("codex",n.sourceSessionKey,o)}for(const n of v)T.has(n.sourceEventKey)||this.observeExternalLifecycle(n);s.files=t,J(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await k(this.client).catch(n=>{console.error("[native-fusion] index projection sync failed; retained for retry",n)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(u=>e-u.createdAt<N);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const a=i[0];e-a.lastPublishedAt<ie||(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?N:Z;if(Math.abs(e.ts-i.createdAt)>a||P(e.payload)!==i.textHash)continue;const u=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)+ee}:void 0;return{claimKey:t,suppression:u,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=I(e.agentType,e.sourceSessionKey),a=s.get(i),u=a?.window??this.suppressionWindows.get(i);return!u||e.ts<u.startedAt?null:e.ts>u.endsAt?(this.clearSuppression(i,u,a,s,t),null):e.role==="user"?(e.sourceEventKey===u.claimSourceEventKey||this.clearSuppression(i,u,a,s,t),null):u}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 P(d){return O.createHash("sha256").update(ne(d)).digest("hex")}function I(d,e){return`${d}:${e}`}function re(d,e,s,t){if(!e.awaitingUserBoundary)return{events:d,cursor:{...e,offset:s,mtimeMs:t}};if(d.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=d.findIndex(a=>a.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:d.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function ae(d,e,s,t,i,a){const u={...e.sourceBoundaryEstablished??{}},p=e.eventWatermarkTs??-1,y=new Set(e.eventKeysAtWatermark??[]),v=t?d:d.filter(n=>n.ts>p||n.ts===p&&!y.has(n.sourceEventKey)),T=[];for(const n of v){const o=n.sourceSessionKey;if(u[o]){T.push(n);continue}!(!t||(n.sourceSessionCreatedAtMs??0)>=s)||n.role!=="user"||(u[o]=!0,T.push(n))}const m=d.reduce((n,o)=>Math.max(n,o.ts),p),M=new Set(m===p?e.eventKeysAtWatermark??[]:[]);for(const n of d)n.ts===m&&M.add(n.sourceEventKey);return{events:T,cursor:{...e,offset:a,mtimeMs:i,eventWatermarkTs:m>=0?m:void 0,eventKeysAtWatermark:m>=0?[...M]:void 0,sourceBoundaryEstablished:u}}}export{me as NativeSessionFusionService};
|
|
2
|
+
`).trim()}function C(){return new Promise(d=>setImmediate(d))}function oe(d){return d.split(/[\\/]+/).includes(".codex")}class me{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 R(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;externalTerminalHandler=null;constructor(e,s=new L){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=>[I(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},G))}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()}setExternalTerminalHandler(e){this.externalTerminalHandler=e}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=P(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==a))throw new Error(`managed echo claim identity conflict: ${t}`);const u={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?.(u),this.pendingClaims.set(t,u),e.externalRunId&&e.sourceSessionKey){const p=`${s}:${e.sourceSessionKey}`,y=this.externalObservations.get(p)??[];y.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(p,y),this.emitExternalActivity(y[y.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()},se),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"}}),this.externalTerminalHandler?.(e.sessionId)}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){await C(),this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){await C(),s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=j(),t={...s.files},i=[],a=new Map,u=new Map,p=new Map,y=e?[...e]:[...W(),...D(),...$()];!e&&this.watchEnabled&&this.watcher.refresh(y,z());for(let n=0;n<y.length;n+=1){const o=y[n];if(n>0&&n%te===0&&await C(),!B.existsSync(o)){this.watchEnabled&&this.watcher.untrackFile(o),this.pendingBoundaryFiles.delete(o);for(const[r,f]of this.sourceFiles.entries())f===o&&this.sourceFiles.delete(r);continue}this.watchEnabled&&this.watcher.trackFile(o);const l=B.statSync(o),E=oe(o),h=o.endsWith(".opencode-session.json");let c=s.files[o];const x=`${String(l.dev)}:${String(l.ino)}:${String(l.birthtimeMs)}`;let b=!c;if(c&&!h&&(l.size<c.offset||c.fileIdentity&&c.fileIdentity!==x)&&(c=void 0,b=!0),!!(c&&c.fileIdentity===x&&c.mtimeMs===l.mtimeMs&&c.offset>=l.size&&c.sources?.length)&&c){for(const r of c.sources??[])this.sourceFiles.set(I(r.agentType,r.sourceSessionKey),o);c.classification==="live"&&c.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o);continue}const g=c?.sources?.length&&!h?{status:"ready",createdAtMs:c.sessionCreatedAtMs??l.birthtimeMs,sources:c.sources}:U(o);if(g.status==="pending"){l.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(o);continue}for(const r of g.sources)this.sourceFiles.set(I(r.agentType,r.sourceSessionKey),o);if(!h&&g.sources.length>0&&g.sources.every(r=>this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))){t[o]={...c??{daemonInstallationId:s.daemonInstallationId,classification:g.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:g.createdAtMs,sourceBoundaryEstablished:{}},offset:l.size,mtimeMs:l.mtimeMs,fileIdentity:x};continue}if(!c)c={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:l.mtimeMs,classification:g.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:g.createdAtMs,sourceBoundaryEstablished:{}};else if(h&&c.mtimeMs===l.mtimeMs)continue;const A=h?0:c.offset,w=h?Y(o,A):E?Q(o,A):V(o,A);for(const r of w.events)this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey)||r.title?.trim()&&r.titleSource==="agent_native"&&p.set(`${r.agentType}:${r.sourceSessionKey}`,r);let K,S;if(h){const r=ae(w.events,c,s.fusionEpochMs,b,l.mtimeMs,w.nextOffset);K=r.events,S=r.cursor}else if(b&&c.classification==="historical")K=[],S={...c,offset:w.nextOffset,mtimeMs:l.mtimeMs,awaitingUserBoundary:!0};else{const r=re(w.events,c,w.nextOffset,l.mtimeMs);K=r.events,S=r.cursor}S.fileIdentity=x,S.sources=g.sources,t[o]=S,w.nextOffset<l.size&&l.size-A>X&&this.queuedTargetFiles.add(o),S.classification==="live"&&S.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o);for(const r of K){if(this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))continue;const f=this.tryClaimManagedEcho(r);if(f){i.push(f.event),a.set(f.claimKey,f.suppression??null),f.suppression&&u.set(I(f.suppression.agentType,f.suppression.sourceSessionKey),{window:f.suppression,claimKey:f.claimKey});continue}const _=this.suppressionFor(r,u,a);if(_){i.push({...r,claimSessionId:_.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(r)}}const v=[...i].sort((n,o)=>n.ts-o.ts),T=new Set,m=new Set,M=n=>{try{this.repository.commitEvents(n)}catch(o){if(!(o instanceof q))throw o;const l=n.find(h=>h.sourceEventKey===o.sourceEventKey);if(!l)throw o;T.add(l.sourceEventKey),l.claimCanonicalMessageId&&m.add(l.claimCanonicalMessageId),console.warn(`[native-fusion] quarantined conflicting native event ${l.sourceEventKey}`);const E=n.filter(h=>h!==l);E.length>0&&M(E)}};v.length===0&&this.repository.commitEvents([]);for(let n=0;n<v.length;n+=F)M(v.slice(n,n+F)),n+F<v.length&&await C();for(const[n,o]of a)m.has(n)||(this.repository.completeManagedClaim?.(n,o??void 0),this.pendingClaims.delete(n),o&&this.suppressionWindows.set(I(o.agentType,o.sourceSessionKey),o));for(const n of p.values())this.repository.observeNativeTitle(n.agentType,n.sourceSessionKey,n.title??"");for(const n of this.repository.listSourceBindings()){if(n.agentType!=="codex")continue;const o=H(n.sourceSessionKey);o&&this.repository.observeNativeTitle("codex",n.sourceSessionKey,o)}for(const n of v)T.has(n.sourceEventKey)||this.observeExternalLifecycle(n);s.files=t,J(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),!(this.fullScanQueued||this.queuedTargetFiles.size>0)&&(this.repository.repairMissingUserBoundaries?.(),await k(this.client).catch(n=>{console.error("[native-fusion] index projection sync failed; retained for retry",n)}))}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(u=>e-u.createdAt<N);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const a=i[0];e-a.lastPublishedAt<ie||(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?N:Z;if(Math.abs(e.ts-i.createdAt)>a||P(e.payload)!==i.textHash)continue;const u=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)+ee}:void 0;return{claimKey:t,suppression:u,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=I(e.agentType,e.sourceSessionKey),a=s.get(i),u=a?.window??this.suppressionWindows.get(i);return!u||e.ts<u.startedAt?null:e.ts>u.endsAt?(this.clearSuppression(i,u,a,s,t),null):e.role==="user"?(e.sourceEventKey===u.claimSourceEventKey||this.clearSuppression(i,u,a,s,t),null):u}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 P(d){return O.createHash("sha256").update(ne(d)).digest("hex")}function I(d,e){return`${d}:${e}`}function re(d,e,s,t){if(!e.awaitingUserBoundary)return{events:d,cursor:{...e,offset:s,mtimeMs:t}};if(d.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=d.findIndex(a=>a.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:d.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function ae(d,e,s,t,i,a){const u={...e.sourceBoundaryEstablished??{}},p=e.eventWatermarkTs??-1,y=new Set(e.eventKeysAtWatermark??[]),v=t?d:d.filter(n=>n.ts>p||n.ts===p&&!y.has(n.sourceEventKey)),T=[];for(const n of v){const o=n.sourceSessionKey;if(u[o]){T.push(n);continue}!(!t||(n.sourceSessionCreatedAtMs??0)>=s)||n.role!=="user"||(u[o]=!0,T.push(n))}const m=d.reduce((n,o)=>Math.max(n,o.ts),p),M=new Set(m===p?e.eventKeysAtWatermark??[]:[]);for(const n of d)n.ts===m&&M.add(n.sourceEventKey);return{events:T,cursor:{...e,offset:a,mtimeMs:i,eventWatermarkTs:m>=0?m:void 0,eventKeysAtWatermark:m>=0?[...M]:void 0,sourceBoundaryEstablished:u}}}export{me as NativeSessionFusionService};
|