shennian 0.3.61 → 0.3.63
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.
|
@@ -321,8 +321,8 @@
|
|
|
321
321
|
},
|
|
322
322
|
{
|
|
323
323
|
"file": "src/native-fusion/local-repository.js",
|
|
324
|
-
"beforeBytes":
|
|
325
|
-
"afterBytes":
|
|
324
|
+
"beforeBytes": 24537,
|
|
325
|
+
"afterBytes": 12150
|
|
326
326
|
},
|
|
327
327
|
{
|
|
328
328
|
"file": "src/native-fusion/native-source-deletions.js",
|
|
@@ -346,8 +346,8 @@
|
|
|
346
346
|
},
|
|
347
347
|
{
|
|
348
348
|
"file": "src/native-fusion/service.js",
|
|
349
|
-
"beforeBytes":
|
|
350
|
-
"afterBytes":
|
|
349
|
+
"beforeBytes": 26155,
|
|
350
|
+
"afterBytes": 12343
|
|
351
351
|
},
|
|
352
352
|
{
|
|
353
353
|
"file": "src/native-fusion/session-header.js",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import h from"node:crypto";import l from"node:fs";import f from"node:path";import{sessionPreviewFromPayload as R}from"@shennian/wire";import{resolvePersonalSyncV1Path as L}from"../personal-sync/paths.js";import{getCanonicalSessionStore as H,getDaemonInstallationId as U,listSessionRecords as C,recordSession as K}from"../session/store.js";import{readToolDetail as J,writeToolDetail as P}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as V}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as k}from"../personal-sync/metrics.js";const S=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))}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)?w(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 y=this.canonicalStore(),p=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",u=r?.canonicalMessageId??(p?null:e.claimCanonicalMessageId??Q(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let I=y.getManifest(d)?.bodyRevision??0,D=!!r;const E=!p&&!!e.claimCanonicalMessageId;if(E){if(!u)throw new Error("managed echo claim requires a canonical message");const M=y.getMessageState(d,u);if(!M||M.tombstoned)throw new Error(`managed echo claim target is not durable: ${u}`)}if(!r&&!E&&!p){if(!u)throw new Error("native event requires a canonical message");const M=e.canonicalMessageKey?y.getMessageState(d,u):null,F=e.canonicalMutation==="upsert"||!!M,$=F?M??y.getMessageState(d,u):null,x={id:u,sessionId:d,role:e.role,ts:$?.message?.ts??e.ts,payload:Y($?.message?.payload,e.payload)},_=F?y.upsert(d,x):y.append(d,x);if(I=_.bodyRevision,D=_.duplicate,e.localToolDetail){const N=this.options.writeLocalToolDetail??P,v=this.options.readLocalToolDetail??J,B=Z(v(d,e.localToolDetail.toolCallId),e.localToolDetail);N({sessionId:d,...B,ts:e.ts,detailRef:{toolIndex:0}})}}!r&&!p&&this.recordIndex(e,d,u,I);const T=new Date().toISOString();return r||g(a,{schemaVersion:S,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:u,canonicalRevision:I,cursor:e.cursor??null,mappedBy:p?"managed_echo_suppressed":E?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:T}),o.sources[n]={schemaVersion:S,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:I,createdAt:i?.createdAt??T,updatedAt:T},g(this.sourcesFile(),o),!r&&e.cursor&&k("sourceCursorAdvanced"),!r&&!E&&!p&&!D&&k("canonicalCommits"),{sessionId:d,messageId:u,bodyRevision:I,duplicate:D,claimed:E,suppressed:p}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??C,s=this.options.recordLocalSession??K,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){A(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=f.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=f.join(c,a.name),m=w(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:f.basename(this.rootDir())==="native-fusion"?f.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??C)().find(m=>m.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=h.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??K)({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??K,c=e.terminal?`terminal:${h.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0;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(),lastMessagePreview:e.role==="user"||e.role==="agent"?j(e.payload):void 0,...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:S,daemonInstallationId:this.installationId(),sources:{}};const s=w(e);if(s.schemaVersion!==S||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??U()}canonicalStore(){return this.options.canonicalStore??H()}rootDir(){return this.options.rootDir??L("native-fusion")}sourcesFile(){return f.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return f.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:S,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=w(e);if(s.schemaVersion!==S||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))A(o);for(const o of Object.values(s.suppressions))O(o);return s}ledgerFile(e,s){const o=h.createHash("sha256").update(`${e}\0${s}`).digest("hex");return f.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 h.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function G(t,e,s){return`native-session-${h.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Q(t,e,s){return`native-${h.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function X(t){return h.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 A(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 R(t,120)}function g(t,e){const s=f.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${h.randomBytes(6).toString("hex")}`;let n;try{n=l.openSync(o,"wx",384),l.writeFileSync(n,`${JSON.stringify(e,null,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
|
|
1
|
+
import h from"node:crypto";import l from"node:fs";import f from"node:path";import{sessionPreviewFromPayload as R}from"@shennian/wire";import{resolvePersonalSyncV1Path as L}from"../personal-sync/paths.js";import{getCanonicalSessionStore as H,getDaemonInstallationId as P,listSessionRecords as C,recordSession as K}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 S=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))}commitEvent(e){if(z(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new W(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=w(e.agentType,e.sourceSessionKey),i=o.sources[n],c=X(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=l.existsSync(a)?b(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 y=this.canonicalStore(),p=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",u=r?.canonicalMessageId??(p?null:e.claimCanonicalMessageId??Q(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let I=y.getManifest(d)?.bodyRevision??0,D=!!r;const E=!p&&!!e.claimCanonicalMessageId;if(E){if(!u)throw new Error("managed echo claim requires a canonical message");const M=y.getMessageState(d,u);if(!M||M.tombstoned)throw new Error(`managed echo claim target is not durable: ${u}`)}if(!r&&!E&&!p){if(!u)throw new Error("native event requires a canonical message");const M=e.canonicalMessageKey?y.getMessageState(d,u):null,F=e.canonicalMutation==="upsert"||!!M,$=F?M??y.getMessageState(d,u):null,x={id:u,sessionId:d,role:e.role,ts:$?.message?.ts??e.ts,payload:Y($?.message?.payload,e.payload)},_=F?y.upsert(d,x):y.append(d,x);if(I=_.bodyRevision,D=_.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}})}}!r&&!p&&this.recordIndex(e,d,u,I);const T=new Date().toISOString();return r||g(a,{schemaVersion:S,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:u,canonicalRevision:I,cursor:e.cursor??null,mappedBy:p?"managed_echo_suppressed":E?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:T}),o.sources[n]={schemaVersion:S,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:I,createdAt:i?.createdAt??T,updatedAt:T},g(this.sourcesFile(),o),!r&&e.cursor&&A("sourceCursorAdvanced"),!r&&!E&&!p&&!D&&A("canonicalCommits"),{sessionId:d,messageId:u,bodyRevision:I,duplicate:D,claimed:E,suppressed:p}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??C,s=this.options.recordLocalSession??K,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[w(s.agentType,s.sourceSessionKey)]=s),g(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=w(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=f.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=f.join(c,a.name),m=b(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:f.basename(this.rootDir())==="native-fusion"?f.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[w(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??C)().find(m=>m.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=h.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??K)({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??K,c=e.terminal?`terminal:${h.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0;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(),lastMessagePreview:e.role==="user"||e.role==="agent"?j(e.payload):void 0,lastMessagePreviewMessageId:e.role==="user"||e.role==="agent"?o:void 0,lastMessagePreviewAt:e.role==="user"||e.role==="agent"?new Date(e.ts).toISOString():void 0,...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:S,daemonInstallationId:this.installationId(),sources:{}};const s=b(e);if(s.schemaVersion!==S||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??P()}canonicalStore(){return this.options.canonicalStore??H()}rootDir(){return this.options.rootDir??L("native-fusion")}sourcesFile(){return f.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return f.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:S,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=b(e);if(s.schemaVersion!==S||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=h.createHash("sha256").update(`${e}\0${s}`).digest("hex");return f.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 w(t,e){return h.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function G(t,e,s){return`native-session-${h.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Q(t,e,s){return`native-${h.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function X(t){return h.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 R(t,120)}function g(t,e){const s=f.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${h.randomBytes(6).toString("hex")}`;let n;try{n=l.openSync(o,"wx",384),l.writeFileSync(n,`${JSON.stringify(e,null,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 b(t){return JSON.parse(l.readFileSync(t,"utf8"))}export{q as MissingUserBoundaryError,le as NativeFusionLocalRepository,W as NativeSourceDeletedError};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import A from"node:fs";import C from"node:crypto";import{publishPendingSessionIndexes as _}from"../session/index-publisher.js";import{NativeFusionFileWatcher as
|
|
2
|
-
`).trim()}function j(d){return d.split(/[\\/]+/).includes(".codex")}class ie{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new N(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)})},E))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,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,n=this.pendingClaims.get(t),o=b(e.text);if(n&&(n.sessionId!==e.sessionId||n.sourceAgentType!==s||n.textHash!==o))throw new Error(`managed echo claim identity conflict: ${t}`);const l={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:o,createdAt:n?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??n?.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 h=`${s}:${e.sourceSessionKey}`,f=this.externalObservations.get(h)??[];f.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(h,f),this.emitExternalActivity(f[f.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 n of this.pendingClaims.values())n.sessionId===e&&n.sourceAgentType===s&&(n.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 n=t.filter(o=>o.sessionId!==e);n.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,n)}this.repository.purgeSession(e)}ensureActiveScanning(){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanNow().catch(e=>{console.error("[native-fusion] active observation scan failed",e)})},H),this.scanNow().catch(()=>{}))}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=D(),t={...s.files},n=[],o=new Map,l=new Map,h=new Map,f=e?[...e]:[...R(),...P(),...k()];!e&&this.watchEnabled&&this.watcher.refresh(f);for(const i of f){if(!A.existsSync(i))continue;const r=A.statSync(i),m=j(i),p=i.endsWith(".opencode-session.json"),c=q(i);if(c.status==="pending")continue;let u=s.files[i];const v=`${String(r.dev)}:${String(r.ino)}:${String(r.birthtimeMs)}`;let I=!u;if(u&&!p&&(r.size<u.offset||u.fileIdentity&&u.fileIdentity!==v)&&(u=void 0,I=!0),!p&&c.sources.length>0&&c.sources.every(a=>this.repository.isSourceDeleted?.(a.agentType,a.sourceSessionKey))){t[i]={...u??{daemonInstallationId:s.daemonInstallationId,classification:c.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:c.createdAtMs,sourceBoundaryEstablished:{}},offset:r.size,mtimeMs:r.mtimeMs,fileIdentity:v};continue}if(!u)u={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:r.mtimeMs,classification:c.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:c.createdAtMs,sourceBoundaryEstablished:{}};else if(p&&u.mtimeMs===r.mtimeMs)continue;const x=p?0:u.offset,g=p?B(i,x):m?L(i,x):W(i,x);for(const a of g.events)this.repository.isSourceDeleted?.(a.agentType,a.sourceSessionKey)||a.title?.trim()&&a.titleSource==="agent_native"&&h.set(`${a.agentType}:${a.sourceSessionKey}`,a);let w,S;if(p){const a=G(g.events,u,s.fusionEpochMs,I,r.mtimeMs,g.nextOffset);w=a.events,S=a.cursor}else if(I&&u.classification==="historical")w=[],S={...u,offset:g.nextOffset,mtimeMs:r.mtimeMs,awaitingUserBoundary:!0};else{const a=X(g.events,u,g.nextOffset,r.mtimeMs);w=a.events,S=a.cursor}S.fileIdentity=v,t[i]=S;for(const a of w){if(this.repository.isSourceDeleted?.(a.agentType,a.sourceSessionKey))continue;const y=this.tryClaimManagedEcho(a);if(y){n.push(y.event),o.set(y.claimKey,y.suppression??null),y.suppression&&l.set(T(y.suppression.agentType,y.suppression.sourceSessionKey),{window:y.suppression,claimKey:y.claimKey});continue}const M=this.suppressionFor(a,l,o);if(M){n.push({...a,claimSessionId:M.sessionId,localDisposition:"managed_echo_suppressed"});continue}n.push(a)}}this.repository.commitEvents(n);for(const[i,r]of o)this.repository.completeManagedClaim?.(i,r??void 0),this.pendingClaims.delete(i),r&&this.suppressionWindows.set(T(r.agentType,r.sourceSessionKey),r);for(const i of h.values())this.repository.observeNativeTitle(i.agentType,i.sourceSessionKey,i.title??"");for(const i of this.repository.listSourceBindings()){if(i.agentType!=="codex")continue;const r=F(i.sourceSessionKey);r&&this.repository.observeNativeTitle("codex",i.sourceSessionKey,r)}for(const i of n)this.observeExternalLifecycle(i);s.files=t,$(s),this.repository.repairMissingUserBoundaries?.(),await _(this.client).catch(i=>{console.error("[native-fusion] index projection sync failed; retained for retry",i)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const n=t.filter(l=>e-l.createdAt<K);if(n.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,n);const o=n[0];e-o.lastPublishedAt<Q||(o.lastPublishedAt=e,this.emitExternalActivity(o,o.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,n]of this.pendingClaims.entries()){if(n.sourceAgentType!==e.agentType||e.role!=="user"||!n.sourceSessionKey||n.sourceSessionKey!==e.sourceSessionKey)continue;const o=n.externalRunId?K:U;if(Math.abs(e.ts-n.createdAt)>o||b(e.payload)!==n.textHash)continue;const l=n.managedEchoPolicy==="suppress_following"?{sessionId:n.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+z}:void 0;return{claimKey:t,suppression:l,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:n.canonicalMessageId,claimSessionId:n.sessionId,managedEchoPolicy:n.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const n=t[0];e.claimCanonicalMessageId===n.canonicalMessageId&&n.state==="queued"&&(n.state="running",n.lastPublishedAt=Date.now(),this.emitExternalActivity(n,"running")),!(!e.terminal||n.state!=="running")&&(this.emitExternalTerminal(n),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 n=T(e.agentType,e.sourceSessionKey),o=s.get(n),l=o?.window??this.suppressionWindows.get(n);return!l||e.ts<l.startedAt?null:e.ts>l.endsAt?(this.clearSuppression(n,l,o,s,t),null):e.role==="user"?(e.sourceEventKey===l.claimSourceEventKey||this.clearSuppression(n,l,o,s,t),null):l}clearSuppression(e,s,t,n,o){if(t){n.delete(e),o.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function b(d){return C.createHash("sha256").update(V(d)).digest("hex")}function T(d,e){return`${d}:${e}`}function X(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 n=d.findIndex(o=>o.role==="user");return n<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:d.slice(n),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function G(d,e,s,t,n,o){const l={...e.sourceBoundaryEstablished??{}},h=e.eventWatermarkTs??-1,f=new Set(e.eventKeysAtWatermark??[]),i=t?d:d.filter(c=>c.ts>h||c.ts===h&&!f.has(c.sourceEventKey)),r=[];for(const c of i){const u=c.sourceSessionKey;if(l[u]){r.push(c);continue}!(!t||(c.sourceSessionCreatedAtMs??0)>=s)||c.role!=="user"||(l[u]=!0,r.push(c))}const m=d.reduce((c,u)=>Math.max(c,u.ts),h),p=new Set(m===h?e.eventKeysAtWatermark??[]:[]);for(const c of d)c.ts===m&&p.add(c.sourceEventKey);return{events:r,cursor:{...e,offset:o,mtimeMs:n,eventWatermarkTs:m>=0?m:void 0,eventKeysAtWatermark:m>=0?[...p]:void 0,sourceBoundaryEstablished:l}}}export{ie as NativeSessionFusionService};
|
|
1
|
+
import A from"node:fs";import C from"node:crypto";import{publishPendingSessionIndexes as _}from"../session/index-publisher.js";import{NativeFusionFileWatcher as F}from"./file-watcher.js";import{NativeFusionLocalRepository as N}from"./local-repository.js";import{listClaudeTranscriptFiles as B,listCodexRolloutFiles as O,listOpenCodeSessionFiles as P,inspectNativeSessionFile as R,lookupCodexThreadName as k,parseClaudeTranscriptChunk as q,parseCodexRolloutChunk as W,parseOpenCodeSessionFile as L}from"./parsers.js";import{loadNativeScannerState as D,saveNativeScannerState as $}from"./state.js";const E=6e4,U=E*2,z=30*6e4,H=2e3,Q=3e4,b=240*6e4;function V(d){return d.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function j(d){return d.split(/[\\/]+/).includes(".codex")}class ne{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new F(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;constructor(e,s=new N){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)})},E))}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),o=K(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==o))throw new Error(`managed echo claim identity conflict: ${t}`);const l={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:o,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 h=`${s}:${e.sourceSessionKey}`,f=this.externalObservations.get(h)??[];f.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(h,f),this.emitExternalActivity(f[f.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(o=>o.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}const s=this.externalObservations.size>0?void 0:[...this.pendingBoundaryFiles];this.scanNow(s).catch(t=>{console.error("[native-fusion] active observation scan failed",t)})},H),e&&this.scanNow().catch(()=>{}))}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=D(),t={...s.files},i=[],o=new Map,l=new Map,h=new Map,f=e?[...e]:[...O(),...B(),...P()];!e&&this.watchEnabled&&this.watcher.refresh(f);for(const n of f){if(!A.existsSync(n)){this.pendingBoundaryFiles.delete(n);continue}const r=A.statSync(n),g=j(n),p=n.endsWith(".opencode-session.json"),c=R(n);if(c.status==="pending"){r.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(n);continue}let u=s.files[n];const v=`${String(r.dev)}:${String(r.ino)}:${String(r.birthtimeMs)}`;let I=!u;if(u&&!p&&(r.size<u.offset||u.fileIdentity&&u.fileIdentity!==v)&&(u=void 0,I=!0),!p&&c.sources.length>0&&c.sources.every(a=>this.repository.isSourceDeleted?.(a.agentType,a.sourceSessionKey))){t[n]={...u??{daemonInstallationId:s.daemonInstallationId,classification:c.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:c.createdAtMs,sourceBoundaryEstablished:{}},offset:r.size,mtimeMs:r.mtimeMs,fileIdentity:v};continue}if(!u)u={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:r.mtimeMs,classification:c.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:c.createdAtMs,sourceBoundaryEstablished:{}};else if(p&&u.mtimeMs===r.mtimeMs)continue;const x=p?0:u.offset,S=p?L(n,x):g?W(n,x):q(n,x);for(const a of S.events)this.repository.isSourceDeleted?.(a.agentType,a.sourceSessionKey)||a.title?.trim()&&a.titleSource==="agent_native"&&h.set(`${a.agentType}:${a.sourceSessionKey}`,a);let w,m;if(p){const a=G(S.events,u,s.fusionEpochMs,I,r.mtimeMs,S.nextOffset);w=a.events,m=a.cursor}else if(I&&u.classification==="historical")w=[],m={...u,offset:S.nextOffset,mtimeMs:r.mtimeMs,awaitingUserBoundary:!0};else{const a=X(S.events,u,S.nextOffset,r.mtimeMs);w=a.events,m=a.cursor}m.fileIdentity=v,t[n]=m,m.classification==="live"&&m.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);for(const a of w){if(this.repository.isSourceDeleted?.(a.agentType,a.sourceSessionKey))continue;const y=this.tryClaimManagedEcho(a);if(y){i.push(y.event),o.set(y.claimKey,y.suppression??null),y.suppression&&l.set(T(y.suppression.agentType,y.suppression.sourceSessionKey),{window:y.suppression,claimKey:y.claimKey});continue}const M=this.suppressionFor(a,l,o);if(M){i.push({...a,claimSessionId:M.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(a)}}this.repository.commitEvents(i);for(const[n,r]of o)this.repository.completeManagedClaim?.(n,r??void 0),this.pendingClaims.delete(n),r&&this.suppressionWindows.set(T(r.agentType,r.sourceSessionKey),r);for(const n of h.values())this.repository.observeNativeTitle(n.agentType,n.sourceSessionKey,n.title??"");for(const n of this.repository.listSourceBindings()){if(n.agentType!=="codex")continue;const r=k(n.sourceSessionKey);r&&this.repository.observeNativeTitle("codex",n.sourceSessionKey,r)}for(const n of i)this.observeExternalLifecycle(n);s.files=t,$(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await _(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(l=>e-l.createdAt<b);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const o=i[0];e-o.lastPublishedAt<Q||(o.lastPublishedAt=e,this.emitExternalActivity(o,o.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 o=i.externalRunId?b:U;if(Math.abs(e.ts-i.createdAt)>o||K(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)+z}: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"&&(i.state="running",i.lastPublishedAt=Date.now(),this.emitExternalActivity(i,"running")),!(!e.terminal||i.state!=="running")&&(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),o=s.get(i),l=o?.window??this.suppressionWindows.get(i);return!l||e.ts<l.startedAt?null:e.ts>l.endsAt?(this.clearSuppression(i,l,o,s,t),null):e.role==="user"?(e.sourceEventKey===l.claimSourceEventKey||this.clearSuppression(i,l,o,s,t),null):l}clearSuppression(e,s,t,i,o){if(t){i.delete(e),o.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function K(d){return C.createHash("sha256").update(V(d)).digest("hex")}function T(d,e){return`${d}:${e}`}function X(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(o=>o.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 G(d,e,s,t,i,o){const l={...e.sourceBoundaryEstablished??{}},h=e.eventWatermarkTs??-1,f=new Set(e.eventKeysAtWatermark??[]),n=t?d:d.filter(c=>c.ts>h||c.ts===h&&!f.has(c.sourceEventKey)),r=[];for(const c of n){const u=c.sourceSessionKey;if(l[u]){r.push(c);continue}!(!t||(c.sourceSessionCreatedAtMs??0)>=s)||c.role!=="user"||(l[u]=!0,r.push(c))}const g=d.reduce((c,u)=>Math.max(c,u.ts),h),p=new Set(g===h?e.eventKeysAtWatermark??[]:[]);for(const c of d)c.ts===g&&p.add(c.sourceEventKey);return{events:r,cursor:{...e,offset:o,mtimeMs:i,eventWatermarkTs:g>=0?g:void 0,eventKeysAtWatermark:g>=0?[...p]:void 0,sourceBoundaryEstablished:l}}}export{ne as NativeSessionFusionService};
|