shennian 0.4.47 → 0.4.49

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.
@@ -516,8 +516,8 @@
516
516
  },
517
517
  {
518
518
  "file": "src/native-fusion/local-repository.js",
519
- "beforeBytes": 25938,
520
- "afterBytes": 12778
519
+ "beforeBytes": 26330,
520
+ "afterBytes": 12881
521
521
  },
522
522
  {
523
523
  "file": "src/native-fusion/native-source-deletions.js",
@@ -736,8 +736,8 @@
736
736
  },
737
737
  {
738
738
  "file": "src/session/canonical-store.js",
739
- "beforeBytes": 31550,
740
- "afterBytes": 15208
739
+ "beforeBytes": 31107,
740
+ "afterBytes": 15127
741
741
  },
742
742
  {
743
743
  "file": "src/session/deletion-sync-state.js",
@@ -796,8 +796,8 @@
796
796
  },
797
797
  {
798
798
  "file": "src/session/handlers/chat.js",
799
- "beforeBytes": 32547,
800
- "afterBytes": 13978
799
+ "beforeBytes": 32729,
800
+ "afterBytes": 14003
801
801
  },
802
802
  {
803
803
  "file": "src/session/handlers/control.js",
@@ -906,8 +906,8 @@
906
906
  },
907
907
  {
908
908
  "file": "src/session/queue.js",
909
- "beforeBytes": 41758,
910
- "afterBytes": 18891
909
+ "beforeBytes": 41860,
910
+ "afterBytes": 18920
911
911
  },
912
912
  {
913
913
  "file": "src/session/queued-chat-dispatch.js",
@@ -1,2 +1,2 @@
1
- import p from"node:crypto";import l from"node:fs";import f from"node:path";import{isToolPayload as L,sessionPreviewFromPayload as R}from"@shennian/wire";import{resolvePersonalSyncV1Path as P}from"../personal-sync/paths.js";import{getCanonicalSessionStore as H,getDaemonInstallationId as U,listSessionRecords as x,recordSession as w}from"../session/store.js";import{readToolDetail as V,writeToolDetail as J}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as q}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as v}from"../personal-sync/metrics.js";const I=1;class W extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class z extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class G extends Error{sourceEventKey;code="native_event_identity_conflict";constructor(e){super(`native event identity conflict: ${e}`),this.sourceEventKey=e,this.name="NativeEventIdentityConflictError"}}class ue{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??x)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(Q(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new z(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=Z(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 G(e.sourceEventKey);if(!i&&!r&&e.role!=="user")throw new W(e.sourceSessionKey);const u=e.claimSessionId??null,d=i?.sessionId??r?.sessionId??u??X(s,e.agentType,e.sourceSessionKey);if(u&&i&&i.sessionId!==u)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const h=this.canonicalStore(),y=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",g=r?.canonicalMessageId??(y?null:e.claimCanonicalMessageId??Y(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let S=h.getManifest(d)?.bodyRevision??0,T=!!r;const E=!y&&!!e.claimCanonicalMessageId;if(E){if(!g)throw new Error("managed echo claim requires a canonical message");const M=h.getMessageState(d,g);if(!M||M.tombstoned)throw new Error(`managed echo claim target is not durable: ${g}`)}if(!r&&!E&&!y){if(!g)throw new Error("native event requires a canonical message");const M=e.canonicalMessageKey?h.getMessageState(d,g):null,F=e.canonicalMutation==="upsert"||!!M,$=F?M??h.getMessageState(d,g):null,_={id:g,sessionId:d,role:e.role,ts:$?.message?.ts??e.ts,payload:ee($?.message?.payload,e.payload)},C=F?h.upsert(d,_):h.append(d,_);if(S=C.bodyRevision,T=C.duplicate,e.localToolDetail){const N=this.options.writeLocalToolDetail??J,j=this.options.readLocalToolDetail??V,B=se(j(d,e.localToolDetail.toolCallId),e.localToolDetail);N({sessionId:d,...B,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(S=h.completeStartedUserMessages(d,e.ts).at(-1)?.bodyRevision??S),(!r||e.terminal)&&!y&&this.recordIndex(e,d,g,S);const K=new Date().toISOString();return r||m(a,{schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:g,canonicalRevision:S,cursor:e.cursor??null,mappedBy:y?"managed_echo_suppressed":E?"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:S,createdAt:i?.createdAt??K,updatedAt:K},m(this.sourcesFile(),o),!r&&e.cursor&&v("sourceCursorAdvanced"),!r&&!E&&!y&&!T&&v("canonicalCommits"),{sessionId:d,messageId:g,bodyRevision:S,duplicate:T,claimed:E,suppressed:y}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??x,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){A(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,m(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&&m(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],m(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),m(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],m(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&&m(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&&m(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),u=D(r);if(u.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");u.sessionId===e&&l.unlinkSync(r)}}isSourceDeleted(e,s){return(this.options.deletionStore??new q({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??x)().find(u=>u.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"?k(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{},u=o&&(a||L(e.payload))?new Date(e.ts).toISOString():void 0;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(),lastVisibleEventAt:u,...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?k(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??U()}canonicalStore(){return this.options.canonicalStore??H()}rootDir(){return this.options.rootDir??P("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: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))A(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 f.join(this.rootDir(),"ledger",`${o}.json`)}}function Q(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 X(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Y(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Z(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 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 ee(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 se(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 k(t){return R(t,120)}function m(t,e){const s=f.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
- `,"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{W as MissingUserBoundaryError,G as NativeEventIdentityConflictError,ue as NativeFusionLocalRepository,z as NativeSourceDeletedError};
1
+ import y from"node:crypto";import d from"node:fs";import S from"node:path";import{isToolPayload as B,sessionPreviewFromPayload as L}from"@shennian/wire";import{resolvePersonalSyncV1Path as P}from"../personal-sync/paths.js";import{getCanonicalSessionStore as H,getDaemonInstallationId as U,listSessionRecords as x,recordSession as w}from"../session/store.js";import{readToolDetail as V,writeToolDetail as q}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as J}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as v}from"../personal-sync/metrics.js";const E=1;class W extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class z extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class G extends Error{sourceEventKey;code="native_event_identity_conflict";constructor(e){super(`native event identity conflict: ${e}`),this.sourceEventKey=e,this.name="NativeEventIdentityConflictError"}}class ue{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??x)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(Q(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new z(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=Z(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=d.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 G(e.sourceEventKey);if(!i&&!r&&e.role!=="user")throw new W(e.sourceSessionKey);const u=e.claimSessionId??null,l=i?.sessionId??r?.sessionId??u??X(s,e.agentType,e.sourceSessionKey);if(u&&i&&i.sessionId!==u)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const p=this.canonicalStore(),I=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",g=r?.canonicalMessageId??(I?null:e.claimCanonicalMessageId??Y(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let f=p.getManifest(l)?.bodyRevision??0,T=!!r;const M=!I&&!!e.claimCanonicalMessageId;if(M){if(!g)throw new Error("managed echo claim requires a canonical message");const h=p.getMessageState(l,g);if(!h||h.tombstoned)throw new Error(`managed echo claim target is not durable: ${g}`);h.deliveryState==="queued"&&h.message&&(f=p.upsert(l,{...h.message,ts:e.ts},"executing").bodyRevision)}if(!r&&!M&&!I){if(!g)throw new Error("native event requires a canonical message");const h=e.canonicalMessageKey?p.getMessageState(l,g):null,F=e.canonicalMutation==="upsert"||!!h,$=F?h??p.getMessageState(l,g):null,_={id:g,sessionId:l,role:e.role,ts:$?.message?.ts??e.ts,payload:ee($?.message?.payload,e.payload)},C=F?p.upsert(l,_):p.append(l,_);if(f=C.bodyRevision,T=C.duplicate,e.localToolDetail){const N=this.options.writeLocalToolDetail??q,j=this.options.readLocalToolDetail??V,R=se(j(l,e.localToolDetail.toolCallId),e.localToolDetail);N({sessionId:l,...R,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(f=p.completeStartedUserMessages(l,e.ts).at(-1)?.bodyRevision??f),(!r||e.terminal)&&!I&&this.recordIndex(e,l,g,f);const K=new Date().toISOString();return r||m(a,{schemaVersion:E,daemonInstallationId:s,sessionId:l,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:g,canonicalRevision:f,cursor:e.cursor??null,mappedBy:I?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:K}),o.sources[n]={schemaVersion:E,daemonInstallationId:s,sessionId:l,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:f,createdAt:i?.createdAt??K,updatedAt:K},m(this.sourcesFile(),o),!r&&e.cursor&&v("sourceCursorAdvanced"),!r&&!M&&!I&&!T&&v("canonicalCommits"),{sessionId:l,messageId:g,bodyRevision:f,duplicate:T,claimed:M,suppressed:I}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??x,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){A(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,m(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&&m(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],m(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),m(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],m(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&&m(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&&m(this.managedEchoStateFile(),n);const c=S.join(this.rootDir(),"ledger");if(d.existsSync(c))for(const a of d.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=S.join(c,a.name),u=D(r);if(u.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");u.sessionId===e&&d.unlinkSync(r)}}isSourceDeleted(e,s){return(this.options.deletionStore??new J({rootDir:S.basename(this.rootDir())==="native-fusion"?S.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??x)().find(u=>u.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=y.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:${y.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?k(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{},u=o&&(a||B(e.payload))?new Date(e.ts).toISOString():void 0;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(),lastVisibleEventAt:u,...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?k(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(!d.existsSync(e))return{schemaVersion:E,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==E||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??P("native-fusion")}sourcesFile(){return S.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return S.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!d.existsSync(e))return{schemaVersion:E,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==E||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=y.createHash("sha256").update(`${e}\0${s}`).digest("hex");return S.join(this.rootDir(),"ledger",`${o}.json`)}}function Q(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 y.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function X(t,e,s){return`native-session-${y.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Y(t,e,s){return`native-${y.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Z(t){return y.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 ee(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 se(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 k(t){return L(t,120)}function m(t,e){const s=S.dirname(t);d.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${y.randomBytes(6).toString("hex")}`;let n;try{n=d.openSync(o,"wx",384),d.writeFileSync(n,`${JSON.stringify(e,null,2)}
2
+ `,"utf8"),d.fsyncSync(n),d.closeSync(n),n=void 0,d.renameSync(o,t)}finally{n!==void 0&&d.closeSync(n);try{d.unlinkSync(o)}catch{}}}function D(t){return JSON.parse(d.readFileSync(t,"utf8"))}export{W as MissingUserBoundaryError,G as NativeEventIdentityConflictError,ue as NativeFusionLocalRepository,z as NativeSourceDeletedError};
@@ -1,5 +1,5 @@
1
- import M from"node:crypto";import o from"node:fs";import c from"node:path";import{SESSION_SYNC_DATA_EPOCH as k,SESSION_SYNC_PROTOCOL_VERSION as _}from"@shennian/wire";const v=1,E=64,b="mutations-000001.jsonl";class q extends Error{constructor(e,t){super(e,t),this.name="CanonicalStoreError"}}class I extends q{constructor(e,t){super(e,t),this.name="CanonicalStoreCorruptionError"}}class D extends q{constructor(e){super(e),this.name="CanonicalStoreConflictError"}}class Z{rootDir;daemonInstallationId;mutationListeners=new Set;constructor(e,t){if(!t)throw new q("daemonInstallationId is required");this.rootDir=c.resolve(e),this.daemonInstallationId=t}commit(e){J(e);const t=this.sessionDir(e.sessionId);this.assertHealthy(t),o.mkdirSync(this.messageStateDir(t),{recursive:!0,mode:448});let s=this.loadManifestOrRebuild(e.sessionId);const n=this.readMessageState(t,e.messageId),a=z(e);if(n&&n.contentHash===a&&n.tombstoned===(e.mutation==="tombstone"))return{bodyRevision:s.bodyRevision,sequence:n.sequence,mutation:this.findMutationByRevision(e.sessionId,n.lastRevision),duplicate:!0};if(e.mutation==="append"&&n)throw new D(`message ${e.messageId} already exists in session ${e.sessionId}; use upsert`);const m=n?.sequence??s.nextSequence,l=s.bodyRevision+1,h=e.committedAt??new Date().toISOString(),y={protocolVersion:_,dataEpoch:k,daemonInstallationId:this.daemonInstallationId,schemaVersion:v,mutationId:U(e.sessionId,e.messageId,e.mutation,n?.contentHash??"none",a),sessionId:e.sessionId,revision:l,mutation:e.mutation,messageId:e.messageId,sequence:m,...e.message?{message:e.message}:{},...e.deliveryState?{deliveryState:e.deliveryState}:{},committedAt:h,contentHash:a},d=c.join(t,b),f=W(d,`${JSON.stringify(y)}
2
- `),r={schemaVersion:v,sessionId:e.sessionId,messageId:e.messageId,sequence:m,lastRevision:l,contentHash:a,tombstoned:e.mutation==="tombstone",...e.message?{message:e.message}:{},...e.deliveryState?{deliveryState:e.deliveryState}:{}};w(this.messageStateFile(t,m,e.messageId),r);const g=this.readSparseIndex(t,e.sessionId);(l===1||(l-1)%E===0)&&(g.entries.push({revision:l,offset:f}),w(c.join(t,"sparse-index.json"),g)),s={...s,bodyRevision:l,nextSequence:n?s.nextSequence:m+1,firstAvailableSeq:s.firstAvailableSeq??m,lastAvailableSeq:Math.max(s.lastAvailableSeq??m,m),mutationCount:s.mutationCount+1,visibleMessageCount:s.visibleMessageCount+(!n&&e.mutation!=="tombstone"?1:0)-(n&&!n.tombstoned&&e.mutation==="tombstone"?1:0),lastVisibleMessageId:e.mutation==="tombstone"?s.lastVisibleMessageId===e.messageId?this.findLastVisibleMessageId(t,e.messageId):s.lastVisibleMessageId:!n||n.tombstoned?e.messageId:s.lastVisibleMessageId,lastBodyCommittedAt:h,logByteLength:o.statSync(d).size,updatedAt:h},w(c.join(t,"manifest.json"),s);for(const x of this.mutationListeners)try{x(y)}catch(R){console.error("[canonical-store] mutation listener failed",R)}return{bodyRevision:l,sequence:m,mutation:y,duplicate:!1}}subscribe(e){return this.mutationListeners.add(e),()=>this.mutationListeners.delete(e)}append(e,t,s){return this.commit({sessionId:e,mutation:"append",messageId:t.id,message:t,deliveryState:s})}upsert(e,t,s){return this.commit({sessionId:e,mutation:"upsert",messageId:t.id,message:t,deliveryState:s})}tombstone(e,t,s){return this.commit({sessionId:e,mutation:"tombstone",messageId:t,deliveryState:s})}readRecent(e,t){return this.readPage(e,{limit:t})}readPage(e,t){const s=O(t.limit),n=this.sessionDir(e);if(!o.existsSync(n))return H();const a=this.loadManifestOrRebuild(e),m=this.listMessageStateFiles(n).filter(d=>t.beforeSequence===void 0||d.sequence<t.beforeSequence).sort((d,f)=>f.sequence-d.sequence),l=[];let h=0;for(const d of m){h+=1;const f=p(c.join(this.messageStateDir(n),d.name));if(!f.tombstoned&&f.message&&l.push(f),l.length>=s)break}l.sort((d,f)=>d.sequence-f.sequence);const y=l.length>0&&m.slice(h).some(d=>{const f=p(c.join(this.messageStateDir(n),d.name));return!f.tombstoned&&!!f.message});return{bodyRevision:a.bodyRevision,firstAvailableSeq:a.firstAvailableSeq,lastAvailableSeq:a.lastAvailableSeq,hasOlder:y,messages:l.map(d=>d.message),entries:l.map(d=>({sequence:d.sequence,revision:d.lastRevision,message:d.message,...d.deliveryState?{deliveryState:d.deliveryState}:{}}))}}readPageAtRevision(e,t){const s=O(t.limit),n=this.sessionDir(e);if(!o.existsSync(n))return H();const a=this.loadManifestOrRebuild(e);if(!Number.isSafeInteger(t.revision)||t.revision<0||t.revision>a.bodyRevision)throw new q("snapshot revision is unavailable");const m=new Map,l=B(c.join(n,b),0);for(const r of l.split(`
3
- `)){if(!r)continue;const g=A(r,e);if(g.revision>t.revision)break;m.set(g.messageId,{schemaVersion:v,sessionId:e,messageId:g.messageId,sequence:g.sequence,lastRevision:g.revision,contentHash:g.contentHash,tombstoned:g.mutation==="tombstone",...g.message?{message:g.message}:{},...g.deliveryState?{deliveryState:g.deliveryState}:{}})}const h=[...m.values()].filter(r=>!r.tombstoned&&r.message&&(t.beforeSequence===void 0||r.sequence<t.beforeSequence)).sort((r,g)=>g.sequence-r.sequence),y=h.slice(0,s).sort((r,g)=>r.sequence-g.sequence),f=[...m.values()].filter(r=>!r.tombstoned&&r.message).map(r=>r.sequence);return{bodyRevision:t.revision,firstAvailableSeq:f.length?Math.min(...f):null,lastAvailableSeq:f.length?Math.max(...f):null,hasOlder:h.length>y.length,messages:y.map(r=>r.message),entries:y.map(r=>({sequence:r.sequence,revision:r.lastRevision,message:r.message,...r.deliveryState?{deliveryState:r.deliveryState}:{}}))}}readDeltas(e,t,s){if(!Number.isSafeInteger(t)||t<0)throw new q("afterRevision must be a non-negative safe integer");const n=O(s),a=this.sessionDir(e);if(!o.existsSync(a))return[];const m=this.loadManifestOrRebuild(e);if(t>=m.bodyRevision)return[];const l=this.readSparseIndex(a,e),h=t+1,y=[...l.entries].reverse().find(r=>r.revision<=h),d=B(c.join(a,b),y?.offset??0),f=[];for(const r of d.split(`
4
- `)){if(!r)continue;const g=A(r,e);if(g.revision>t&&f.push(g),f.length>=n)break}return f}getManifest(e){const t=this.sessionDir(e);return o.existsSync(t)?this.loadManifestOrRebuild(e):null}getMessageState(e,t){const s=this.sessionDir(e);if(!o.existsSync(s))return null;this.loadManifestOrRebuild(e);const n=this.readMessageState(s,t);return n?{sequence:n.sequence,lastRevision:n.lastRevision,tombstoned:n.tombstoned,message:n.message,deliveryState:n.deliveryState}:null}completeStartedUserMessages(e,t){const s=this.sessionDir(e);if(!o.existsSync(s))return[];this.loadManifestOrRebuild(e);const n=[];let a=!1;for(const m of this.listMessageStateFiles(s).sort((l,h)=>h.sequence-l.sequence)){const l=p(c.join(this.messageStateDir(s),m.name));if(l.tombstoned||l.message?.role!=="user")continue;const h=l.deliveryState==="executing",y=!a&&l.deliveryState==="queued"&&l.message.ts<=t;!h&&!y||(y&&(a=!0),n.push(this.upsert(e,l.message,"completed")))}return n}updateMessageDeliveryState(e,t,s){const n=this.getMessageState(e,t);return!n?.message||n.tombstoned?null:this.upsert(e,n.message,s)}listSessionIds(){const e=c.join(this.rootDir,"sessions");if(!o.existsSync(e))return[];const t=[];for(const s of o.readdirSync(e,{withFileTypes:!0})){if(!s.isDirectory())continue;const n=c.join(e,s.name,"manifest.json");o.existsSync(n)&&t.push(p(n).sessionId)}return t.sort()}purgeSession(e){if(!e)throw new q("sessionId is required");const t=c.resolve(this.rootDir,"sessions"),s=c.resolve(t,N(e));if(c.dirname(s)!==t)throw new q("invalid session path");o.rmSync(s,{recursive:!0,force:!0})}findLastVisibleMessageId(e,t){for(const s of this.listMessageStateFiles(e).sort((n,a)=>a.sequence-n.sequence)){const n=p(c.join(this.messageStateDir(e),s.name));if(n.messageId!==t&&!n.tombstoned&&n.message)return n.messageId}return null}rebuild(e){const t=this.sessionDir(e);this.assertHealthy(t),o.mkdirSync(t,{recursive:!0,mode:448});const s=c.join(t,"manifest.json");if(o.existsSync(s))try{const u=JSON.parse(o.readFileSync(s,"utf8"));if(u.daemonInstallationId&&u.daemonInstallationId!==this.daemonInstallationId)throw new D(`canonical session ${e} belongs to a different daemon installation`)}catch(u){if(u instanceof D)throw u}const n=c.join(t,b);if(!o.existsSync(n)){const u=T(e,this.daemonInstallationId);return w(c.join(t,"manifest.json"),u),w(c.join(t,"sparse-index.json"),C(e)),u}const a=o.readFileSync(n);let m=a.length;if(a.length>0&&a[a.length-1]!==10){const u=a.lastIndexOf(10);m=u<0?0:u+1,o.truncateSync(n,m)}const l=a.subarray(0,m).toString("utf8"),h=new Map,y=C(e);let d=1,f=0,r=new Date(0).toISOString();try{for(const u of l.match(/.*\n/g)??[]){const j=u.slice(0,-1);if(!j){f+=Buffer.byteLength(u);continue}const S=A(j,e);if(S.revision!==d)throw new I(`non-contiguous revision in ${e}: expected ${d}, got ${S.revision}`);(S.revision===1||(S.revision-1)%E===0)&&y.entries.push({revision:S.revision,offset:f});const F=h.get(S.messageId);if(F&&F.sequence!==S.sequence)throw new I(`sequence changed for message ${S.messageId}`);h.set(S.messageId,{schemaVersion:v,sessionId:e,messageId:S.messageId,sequence:S.sequence,lastRevision:S.revision,contentHash:S.contentHash,tombstoned:S.mutation==="tombstone",...S.message?{message:S.message}:{},...S.deliveryState?{deliveryState:S.deliveryState}:{}}),d+=1,f+=Buffer.byteLength(u),r=S.committedAt}}catch(u){this.quarantineCorruptSegment(e,n,u)}const g=this.messageStateDir(t);o.rmSync(g,{recursive:!0,force:!0}),o.mkdirSync(g,{recursive:!0,mode:448});for(const u of h.values())w(this.messageStateFile(t,u.sequence,u.messageId),u);w(c.join(t,"sparse-index.json"),y);const x=[...h.values()].map(u=>u.sequence),R=[...h.values()].filter(u=>!u.tombstoned&&!!u.message).sort((u,j)=>u.sequence-j.sequence),$=d-1,V={schemaVersion:v,daemonInstallationId:this.daemonInstallationId,sessionId:e,bodyRevision:$,nextSequence:x.length===0?1:Math.max(...x)+1,firstAvailableSeq:x.length===0?null:Math.min(...x),lastAvailableSeq:x.length===0?null:Math.max(...x),mutationCount:$,visibleMessageCount:R.length,lastVisibleMessageId:R.at(-1)?.messageId??null,lastBodyCommittedAt:$>0?r:null,segment:b,logByteLength:m,updatedAt:r};return w(c.join(t,"manifest.json"),V),V}loadManifestOrRebuild(e){const t=this.sessionDir(e);this.assertHealthy(t);const s=c.join(t,"manifest.json"),n=c.join(t,b);if(!o.existsSync(s))return this.rebuild(e);try{const a=p(s);P(a,e,this.daemonInstallationId);const m=o.existsSync(n)?o.statSync(n).size:0;return a.logByteLength===m?a:this.rebuild(e)}catch(a){if(a instanceof D)throw a;return this.rebuild(e)}}findMutationByRevision(e,t){const s=this.readDeltas(e,t-1,1)[0];if(!s||s.revision!==t)throw new I(`missing mutation revision ${t} in ${e}`);return s}readSparseIndex(e,t){const s=c.join(e,"sparse-index.json");if(!o.existsSync(s))return C(t);const n=p(s);if(n.schemaVersion!==v||n.sessionId!==t||n.segment!==b)throw new I(`invalid sparse index for session ${t}`);return n}readMessageState(e,t){const s=`.${L(t)}.json`,n=this.listMessageStateFiles(e).find(m=>m.name.endsWith(s));if(!n)return null;const a=p(c.join(this.messageStateDir(e),n.name));if(a.messageId!==t)throw new I(`message state hash collision for ${t}`);return a}listMessageStateFiles(e){const t=this.messageStateDir(e);return o.existsSync(t)?o.readdirSync(t).map(s=>({name:s,match:/^(\d{16})\.[a-f0-9]{32}\.json$/.exec(s)})).filter(s=>!!s.match).map(s=>({name:s.name,sequence:Number(s.match[1])})):[]}quarantineCorruptSegment(e,t,s){const n=c.join(this.rootDir,"quarantine");o.mkdirSync(n,{recursive:!0,mode:448});const a=c.join(n,`${N(e)}-${Date.now()}-${b}`);throw o.renameSync(t,a),w(c.join(this.sessionDir(e),"CORRUPT.json"),{schemaVersion:v,sessionId:e,quarantined:a,detectedAt:new Date().toISOString(),reason:s instanceof Error?s.message:String(s)}),new I(`canonical segment for ${e} was quarantined at ${a}`,{cause:s})}assertHealthy(e){const t=c.join(e,"CORRUPT.json");if(o.existsSync(t))throw new I(`canonical session is quarantined: ${t}`)}sessionDir(e){return c.join(this.rootDir,"sessions",N(e))}messageStateDir(e){return c.join(e,"messages")}messageStateFile(e,t,s){return c.join(this.messageStateDir(e),`${String(t).padStart(16,"0")}.${L(s)}.json`)}}function J(i){if(!i.sessionId||!i.messageId)throw new q("sessionId and messageId are required");if(i.message&&(i.message.id!==i.messageId||i.message.sessionId!==i.sessionId))throw new q("message identity does not match commit identity");if(i.mutation!=="tombstone"&&!i.message)throw new q(`${i.mutation} mutation requires a message`)}function P(i,e,t){if(i.daemonInstallationId!==t)throw new D(`canonical session ${e} belongs to a different daemon installation`);if(i.schemaVersion!==v||i.sessionId!==e||i.segment!==b||!Number.isSafeInteger(i.bodyRevision)||i.bodyRevision<0)throw new Error(`invalid canonical manifest for ${e}`)}function A(i,e){let t;try{t=JSON.parse(i)}catch(s){throw new I(`malformed canonical mutation in ${e}`,{cause:s})}if(t.schemaVersion!==v||t.sessionId!==e||!Number.isSafeInteger(t.revision)||t.revision<1||!Number.isSafeInteger(t.sequence)||t.sequence<1||!["append","upsert","tombstone"].includes(t.mutation))throw new I(`invalid canonical mutation in ${e}`);return t}function T(i,e){return{schemaVersion:v,daemonInstallationId:e,sessionId:i,bodyRevision:0,nextSequence:1,firstAvailableSeq:null,lastAvailableSeq:null,mutationCount:0,visibleMessageCount:0,lastVisibleMessageId:null,lastBodyCommittedAt:null,segment:b,logByteLength:0,updatedAt:new Date(0).toISOString()}}function C(i){return{schemaVersion:v,sessionId:i,segment:b,entries:[]}}function H(){return{bodyRevision:0,firstAvailableSeq:null,lastAvailableSeq:null,hasOlder:!1,messages:[],entries:[]}}function O(i){if(!Number.isSafeInteger(i)||i<1||i>500)throw new q("limit must be an integer between 1 and 500");return i}function N(i){return M.createHash("sha256").update(i).digest("hex")}function L(i){return M.createHash("sha256").update(i).digest("hex").slice(0,32)}function z(i){return M.createHash("sha256").update(JSON.stringify({mutation:i.mutation,message:i.message??null,deliveryState:i.deliveryState??null})).digest("hex")}function U(i,e,t,s,n){return M.createHash("sha256").update(`${i}\0${e}\0${t}\0${s}\0${n}`).digest("hex")}function W(i,e){o.mkdirSync(c.dirname(i),{recursive:!0,mode:448});const t=o.openSync(i,"a",384);try{const s=o.fstatSync(t).size;return o.writeFileSync(t,e,"utf8"),o.fsyncSync(t),s}finally{o.closeSync(t)}}function w(i,e){const t=c.dirname(i);o.mkdirSync(t,{recursive:!0,mode:448});const s=`${i}.tmp-${process.pid}-${M.randomBytes(6).toString("hex")}`;let n;try{n=o.openSync(s,"wx",384),o.writeFileSync(n,`${JSON.stringify(e,null,2)}
1
+ import M from"node:crypto";import o from"node:fs";import l from"node:path";import{SESSION_SYNC_DATA_EPOCH as k,SESSION_SYNC_PROTOCOL_VERSION as _}from"@shennian/wire";const v=1,E=64,b="mutations-000001.jsonl";class q extends Error{constructor(e,t){super(e,t),this.name="CanonicalStoreError"}}class I extends q{constructor(e,t){super(e,t),this.name="CanonicalStoreCorruptionError"}}class D extends q{constructor(e){super(e),this.name="CanonicalStoreConflictError"}}class Z{rootDir;daemonInstallationId;mutationListeners=new Set;constructor(e,t){if(!t)throw new q("daemonInstallationId is required");this.rootDir=l.resolve(e),this.daemonInstallationId=t}commit(e){J(e);const t=this.sessionDir(e.sessionId);this.assertHealthy(t),o.mkdirSync(this.messageStateDir(t),{recursive:!0,mode:448});let s=this.loadManifestOrRebuild(e.sessionId);const n=this.readMessageState(t,e.messageId),a=z(e);if(n&&n.contentHash===a&&n.tombstoned===(e.mutation==="tombstone"))return{bodyRevision:s.bodyRevision,sequence:n.sequence,mutation:this.findMutationByRevision(e.sessionId,n.lastRevision),duplicate:!0};if(e.mutation==="append"&&n)throw new D(`message ${e.messageId} already exists in session ${e.sessionId}; use upsert`);const r=n?.sequence??s.nextSequence,g=s.bodyRevision+1,S=e.committedAt??new Date().toISOString(),y={protocolVersion:_,dataEpoch:k,daemonInstallationId:this.daemonInstallationId,schemaVersion:v,mutationId:U(e.sessionId,e.messageId,e.mutation,n?.contentHash??"none",a),sessionId:e.sessionId,revision:g,mutation:e.mutation,messageId:e.messageId,sequence:r,...e.message?{message:e.message}:{},...e.deliveryState?{deliveryState:e.deliveryState}:{},committedAt:S,contentHash:a},m=l.join(t,b),u=W(m,`${JSON.stringify(y)}
2
+ `),c={schemaVersion:v,sessionId:e.sessionId,messageId:e.messageId,sequence:r,lastRevision:g,contentHash:a,tombstoned:e.mutation==="tombstone",...e.message?{message:e.message}:{},...e.deliveryState?{deliveryState:e.deliveryState}:{}};w(this.messageStateFile(t,r,e.messageId),c);const f=this.readSparseIndex(t,e.sessionId);(g===1||(g-1)%E===0)&&(f.entries.push({revision:g,offset:u}),w(l.join(t,"sparse-index.json"),f)),s={...s,bodyRevision:g,nextSequence:n?s.nextSequence:r+1,firstAvailableSeq:s.firstAvailableSeq??r,lastAvailableSeq:Math.max(s.lastAvailableSeq??r,r),mutationCount:s.mutationCount+1,visibleMessageCount:s.visibleMessageCount+(!n&&e.mutation!=="tombstone"?1:0)-(n&&!n.tombstoned&&e.mutation==="tombstone"?1:0),lastVisibleMessageId:e.mutation==="tombstone"?s.lastVisibleMessageId===e.messageId?this.findLastVisibleMessageId(t,e.messageId):s.lastVisibleMessageId:!n||n.tombstoned?e.messageId:s.lastVisibleMessageId,lastBodyCommittedAt:S,logByteLength:o.statSync(m).size,updatedAt:S},w(l.join(t,"manifest.json"),s);for(const x of this.mutationListeners)try{x(y)}catch(R){console.error("[canonical-store] mutation listener failed",R)}return{bodyRevision:g,sequence:r,mutation:y,duplicate:!1}}subscribe(e){return this.mutationListeners.add(e),()=>this.mutationListeners.delete(e)}append(e,t,s){return this.commit({sessionId:e,mutation:"append",messageId:t.id,message:t,deliveryState:s})}upsert(e,t,s){return this.commit({sessionId:e,mutation:"upsert",messageId:t.id,message:t,deliveryState:s})}tombstone(e,t,s){return this.commit({sessionId:e,mutation:"tombstone",messageId:t,deliveryState:s})}readRecent(e,t){return this.readPage(e,{limit:t})}readPage(e,t){const s=O(t.limit),n=this.sessionDir(e);if(!o.existsSync(n))return H();const a=this.loadManifestOrRebuild(e),r=this.listMessageStateFiles(n).filter(m=>t.beforeSequence===void 0||m.sequence<t.beforeSequence).sort((m,u)=>u.sequence-m.sequence),g=[];let S=0;for(const m of r){S+=1;const u=p(l.join(this.messageStateDir(n),m.name));if(!u.tombstoned&&u.message&&g.push(u),g.length>=s)break}g.sort((m,u)=>m.sequence-u.sequence);const y=g.length>0&&r.slice(S).some(m=>{const u=p(l.join(this.messageStateDir(n),m.name));return!u.tombstoned&&!!u.message});return{bodyRevision:a.bodyRevision,firstAvailableSeq:a.firstAvailableSeq,lastAvailableSeq:a.lastAvailableSeq,hasOlder:y,messages:g.map(m=>m.message),entries:g.map(m=>({sequence:m.sequence,revision:m.lastRevision,message:m.message,...m.deliveryState?{deliveryState:m.deliveryState}:{}}))}}readPageAtRevision(e,t){const s=O(t.limit),n=this.sessionDir(e);if(!o.existsSync(n))return H();const a=this.loadManifestOrRebuild(e);if(!Number.isSafeInteger(t.revision)||t.revision<0||t.revision>a.bodyRevision)throw new q("snapshot revision is unavailable");const r=new Map,g=B(l.join(n,b),0);for(const c of g.split(`
3
+ `)){if(!c)continue;const f=A(c,e);if(f.revision>t.revision)break;r.set(f.messageId,{schemaVersion:v,sessionId:e,messageId:f.messageId,sequence:f.sequence,lastRevision:f.revision,contentHash:f.contentHash,tombstoned:f.mutation==="tombstone",...f.message?{message:f.message}:{},...f.deliveryState?{deliveryState:f.deliveryState}:{}})}const S=[...r.values()].filter(c=>!c.tombstoned&&c.message&&(t.beforeSequence===void 0||c.sequence<t.beforeSequence)).sort((c,f)=>f.sequence-c.sequence),y=S.slice(0,s).sort((c,f)=>c.sequence-f.sequence),u=[...r.values()].filter(c=>!c.tombstoned&&c.message).map(c=>c.sequence);return{bodyRevision:t.revision,firstAvailableSeq:u.length?Math.min(...u):null,lastAvailableSeq:u.length?Math.max(...u):null,hasOlder:S.length>y.length,messages:y.map(c=>c.message),entries:y.map(c=>({sequence:c.sequence,revision:c.lastRevision,message:c.message,...c.deliveryState?{deliveryState:c.deliveryState}:{}}))}}readDeltas(e,t,s){if(!Number.isSafeInteger(t)||t<0)throw new q("afterRevision must be a non-negative safe integer");const n=O(s),a=this.sessionDir(e);if(!o.existsSync(a))return[];const r=this.loadManifestOrRebuild(e);if(t>=r.bodyRevision)return[];const g=this.readSparseIndex(a,e),S=t+1,y=[...g.entries].reverse().find(c=>c.revision<=S),m=B(l.join(a,b),y?.offset??0),u=[];for(const c of m.split(`
4
+ `)){if(!c)continue;const f=A(c,e);if(f.revision>t&&u.push(f),u.length>=n)break}return u}getManifest(e){const t=this.sessionDir(e);return o.existsSync(t)?this.loadManifestOrRebuild(e):null}getMessageState(e,t){const s=this.sessionDir(e);if(!o.existsSync(s))return null;this.loadManifestOrRebuild(e);const n=this.readMessageState(s,t);return n?{sequence:n.sequence,lastRevision:n.lastRevision,tombstoned:n.tombstoned,message:n.message,deliveryState:n.deliveryState}:null}completeStartedUserMessages(e,t){const s=this.sessionDir(e);if(!o.existsSync(s))return[];this.loadManifestOrRebuild(e);const n=[];for(const a of this.listMessageStateFiles(s).sort((r,g)=>g.sequence-r.sequence)){const r=p(l.join(this.messageStateDir(s),a.name));r.tombstoned||r.message?.role!=="user"||r.deliveryState!=="executing"||r.message.ts>t||n.push(this.upsert(e,r.message,"completed"))}return n}updateMessageDeliveryState(e,t,s){const n=this.getMessageState(e,t);return!n?.message||n.tombstoned?null:this.upsert(e,n.message,s)}listSessionIds(){const e=l.join(this.rootDir,"sessions");if(!o.existsSync(e))return[];const t=[];for(const s of o.readdirSync(e,{withFileTypes:!0})){if(!s.isDirectory())continue;const n=l.join(e,s.name,"manifest.json");o.existsSync(n)&&t.push(p(n).sessionId)}return t.sort()}purgeSession(e){if(!e)throw new q("sessionId is required");const t=l.resolve(this.rootDir,"sessions"),s=l.resolve(t,N(e));if(l.dirname(s)!==t)throw new q("invalid session path");o.rmSync(s,{recursive:!0,force:!0})}findLastVisibleMessageId(e,t){for(const s of this.listMessageStateFiles(e).sort((n,a)=>a.sequence-n.sequence)){const n=p(l.join(this.messageStateDir(e),s.name));if(n.messageId!==t&&!n.tombstoned&&n.message)return n.messageId}return null}rebuild(e){const t=this.sessionDir(e);this.assertHealthy(t),o.mkdirSync(t,{recursive:!0,mode:448});const s=l.join(t,"manifest.json");if(o.existsSync(s))try{const d=JSON.parse(o.readFileSync(s,"utf8"));if(d.daemonInstallationId&&d.daemonInstallationId!==this.daemonInstallationId)throw new D(`canonical session ${e} belongs to a different daemon installation`)}catch(d){if(d instanceof D)throw d}const n=l.join(t,b);if(!o.existsSync(n)){const d=T(e,this.daemonInstallationId);return w(l.join(t,"manifest.json"),d),w(l.join(t,"sparse-index.json"),C(e)),d}const a=o.readFileSync(n);let r=a.length;if(a.length>0&&a[a.length-1]!==10){const d=a.lastIndexOf(10);r=d<0?0:d+1,o.truncateSync(n,r)}const g=a.subarray(0,r).toString("utf8"),S=new Map,y=C(e);let m=1,u=0,c=new Date(0).toISOString();try{for(const d of g.match(/.*\n/g)??[]){const j=d.slice(0,-1);if(!j){u+=Buffer.byteLength(d);continue}const h=A(j,e);if(h.revision!==m)throw new I(`non-contiguous revision in ${e}: expected ${m}, got ${h.revision}`);(h.revision===1||(h.revision-1)%E===0)&&y.entries.push({revision:h.revision,offset:u});const F=S.get(h.messageId);if(F&&F.sequence!==h.sequence)throw new I(`sequence changed for message ${h.messageId}`);S.set(h.messageId,{schemaVersion:v,sessionId:e,messageId:h.messageId,sequence:h.sequence,lastRevision:h.revision,contentHash:h.contentHash,tombstoned:h.mutation==="tombstone",...h.message?{message:h.message}:{},...h.deliveryState?{deliveryState:h.deliveryState}:{}}),m+=1,u+=Buffer.byteLength(d),c=h.committedAt}}catch(d){this.quarantineCorruptSegment(e,n,d)}const f=this.messageStateDir(t);o.rmSync(f,{recursive:!0,force:!0}),o.mkdirSync(f,{recursive:!0,mode:448});for(const d of S.values())w(this.messageStateFile(t,d.sequence,d.messageId),d);w(l.join(t,"sparse-index.json"),y);const x=[...S.values()].map(d=>d.sequence),R=[...S.values()].filter(d=>!d.tombstoned&&!!d.message).sort((d,j)=>d.sequence-j.sequence),$=m-1,V={schemaVersion:v,daemonInstallationId:this.daemonInstallationId,sessionId:e,bodyRevision:$,nextSequence:x.length===0?1:Math.max(...x)+1,firstAvailableSeq:x.length===0?null:Math.min(...x),lastAvailableSeq:x.length===0?null:Math.max(...x),mutationCount:$,visibleMessageCount:R.length,lastVisibleMessageId:R.at(-1)?.messageId??null,lastBodyCommittedAt:$>0?c:null,segment:b,logByteLength:r,updatedAt:c};return w(l.join(t,"manifest.json"),V),V}loadManifestOrRebuild(e){const t=this.sessionDir(e);this.assertHealthy(t);const s=l.join(t,"manifest.json"),n=l.join(t,b);if(!o.existsSync(s))return this.rebuild(e);try{const a=p(s);P(a,e,this.daemonInstallationId);const r=o.existsSync(n)?o.statSync(n).size:0;return a.logByteLength===r?a:this.rebuild(e)}catch(a){if(a instanceof D)throw a;return this.rebuild(e)}}findMutationByRevision(e,t){const s=this.readDeltas(e,t-1,1)[0];if(!s||s.revision!==t)throw new I(`missing mutation revision ${t} in ${e}`);return s}readSparseIndex(e,t){const s=l.join(e,"sparse-index.json");if(!o.existsSync(s))return C(t);const n=p(s);if(n.schemaVersion!==v||n.sessionId!==t||n.segment!==b)throw new I(`invalid sparse index for session ${t}`);return n}readMessageState(e,t){const s=`.${L(t)}.json`,n=this.listMessageStateFiles(e).find(r=>r.name.endsWith(s));if(!n)return null;const a=p(l.join(this.messageStateDir(e),n.name));if(a.messageId!==t)throw new I(`message state hash collision for ${t}`);return a}listMessageStateFiles(e){const t=this.messageStateDir(e);return o.existsSync(t)?o.readdirSync(t).map(s=>({name:s,match:/^(\d{16})\.[a-f0-9]{32}\.json$/.exec(s)})).filter(s=>!!s.match).map(s=>({name:s.name,sequence:Number(s.match[1])})):[]}quarantineCorruptSegment(e,t,s){const n=l.join(this.rootDir,"quarantine");o.mkdirSync(n,{recursive:!0,mode:448});const a=l.join(n,`${N(e)}-${Date.now()}-${b}`);throw o.renameSync(t,a),w(l.join(this.sessionDir(e),"CORRUPT.json"),{schemaVersion:v,sessionId:e,quarantined:a,detectedAt:new Date().toISOString(),reason:s instanceof Error?s.message:String(s)}),new I(`canonical segment for ${e} was quarantined at ${a}`,{cause:s})}assertHealthy(e){const t=l.join(e,"CORRUPT.json");if(o.existsSync(t))throw new I(`canonical session is quarantined: ${t}`)}sessionDir(e){return l.join(this.rootDir,"sessions",N(e))}messageStateDir(e){return l.join(e,"messages")}messageStateFile(e,t,s){return l.join(this.messageStateDir(e),`${String(t).padStart(16,"0")}.${L(s)}.json`)}}function J(i){if(!i.sessionId||!i.messageId)throw new q("sessionId and messageId are required");if(i.message&&(i.message.id!==i.messageId||i.message.sessionId!==i.sessionId))throw new q("message identity does not match commit identity");if(i.mutation!=="tombstone"&&!i.message)throw new q(`${i.mutation} mutation requires a message`)}function P(i,e,t){if(i.daemonInstallationId!==t)throw new D(`canonical session ${e} belongs to a different daemon installation`);if(i.schemaVersion!==v||i.sessionId!==e||i.segment!==b||!Number.isSafeInteger(i.bodyRevision)||i.bodyRevision<0)throw new Error(`invalid canonical manifest for ${e}`)}function A(i,e){let t;try{t=JSON.parse(i)}catch(s){throw new I(`malformed canonical mutation in ${e}`,{cause:s})}if(t.schemaVersion!==v||t.sessionId!==e||!Number.isSafeInteger(t.revision)||t.revision<1||!Number.isSafeInteger(t.sequence)||t.sequence<1||!["append","upsert","tombstone"].includes(t.mutation))throw new I(`invalid canonical mutation in ${e}`);return t}function T(i,e){return{schemaVersion:v,daemonInstallationId:e,sessionId:i,bodyRevision:0,nextSequence:1,firstAvailableSeq:null,lastAvailableSeq:null,mutationCount:0,visibleMessageCount:0,lastVisibleMessageId:null,lastBodyCommittedAt:null,segment:b,logByteLength:0,updatedAt:new Date(0).toISOString()}}function C(i){return{schemaVersion:v,sessionId:i,segment:b,entries:[]}}function H(){return{bodyRevision:0,firstAvailableSeq:null,lastAvailableSeq:null,hasOlder:!1,messages:[],entries:[]}}function O(i){if(!Number.isSafeInteger(i)||i<1||i>500)throw new q("limit must be an integer between 1 and 500");return i}function N(i){return M.createHash("sha256").update(i).digest("hex")}function L(i){return M.createHash("sha256").update(i).digest("hex").slice(0,32)}function z(i){return M.createHash("sha256").update(JSON.stringify({mutation:i.mutation,message:i.message??null,deliveryState:i.deliveryState??null})).digest("hex")}function U(i,e,t,s,n){return M.createHash("sha256").update(`${i}\0${e}\0${t}\0${s}\0${n}`).digest("hex")}function W(i,e){o.mkdirSync(l.dirname(i),{recursive:!0,mode:448});const t=o.openSync(i,"a",384);try{const s=o.fstatSync(t).size;return o.writeFileSync(t,e,"utf8"),o.fsyncSync(t),s}finally{o.closeSync(t)}}function w(i,e){const t=l.dirname(i);o.mkdirSync(t,{recursive:!0,mode:448});const s=`${i}.tmp-${process.pid}-${M.randomBytes(6).toString("hex")}`;let n;try{n=o.openSync(s,"wx",384),o.writeFileSync(n,`${JSON.stringify(e,null,2)}
5
5
  `,"utf8"),o.fsyncSync(n),o.closeSync(n),n=void 0,o.renameSync(s,i),Y(t)}finally{n!==void 0&&o.closeSync(n);try{o.unlinkSync(s)}catch{}}}function Y(i){if(process.platform==="win32")return;const e=o.openSync(i,"r");try{o.fsyncSync(e)}finally{o.closeSync(e)}}function p(i){try{return JSON.parse(o.readFileSync(i,"utf8"))}catch(e){throw new I(`failed to read canonical JSON file ${i}`,{cause:e})}}function B(i,e){const t=o.openSync(i,"r");try{const s=o.fstatSync(t).size;if(e>=s)return"";const n=Buffer.allocUnsafe(s-e);return o.readSync(t,n,0,n.length,e),n.toString("utf8")}finally{o.closeSync(t)}}export{Z as CanonicalSessionStore,D as CanonicalStoreConflictError,I as CanonicalStoreCorruptionError,q as CanonicalStoreError};
@@ -1 +1 @@
1
- import ie from"node:os";import{createHash as V,randomUUID as Ae}from"node:crypto";import{buildUserMessagePayload as de}from"@shennian/wire";import{reportLog as x}from"../../log-reporter.js";import{appendMessage as le,getDaemonInstallationId as L,getMessageDeliveryStatus as Me,recordSession as Pe,upsertMessage as k}from"../store.js";import{mergeProjectedSessions as De}from"../projection.js";import{parsePersonalAttachmentReferences as Ee}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as xe,markAttachmentReferences as be}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as R}from"../index-publisher.js";import{getPersonalSyncIdentity as ce}from"../../personal-sync/epoch.js";import{buildManagedAgentPermissionPrompt as Te}from"../../room/managed-prompt-composer.js";import{getNativeSourceAgentType as Ce}from"./chat-events.js";import{configureAdapterForSession as ge,maybeResolveClaudeImportedWorkDir as $e,replaceManagedRoomContextToken as ue,revokeManagedRoomContextToken as me}from"./chat-adapter-config.js";import{createActiveSession as qe,disposeSession as $,prepareAdapterTurn as We,rememberProcessedReqId as je,sendAdapterTurn as pe}from"./chat-session-lifecycle.js";function Fe(e,n,t){if(!e.managedAgentPolicy)throw new Error("managed_agent_policy_missing");const l=e.managedRoomPromptSnapshot;if(!l)throw new Error("managed_room_prompt_snapshot_missing");if(!n?.trim())throw new Error("managed_room_prompt_incomplete");if(!t||!/^mrc_[A-Za-z0-9_-]{43}$/.test(t))throw new Error("managed_room_context_unavailable");const a=V("sha256").update(JSON.stringify({requestedPolicy:e.managedAgentPolicy.requestedPolicy,enforcement:e.managedAgentPolicy.enforcement})).digest("hex"),b=V("sha256").update(e.managedAgentPolicy.adapterPolicy.workDir).digest("hex");if(l.runtimePermissionPolicyHash!==a||l.resolvedWorkDirHash!==b||l.enforcementLevel!==e.managedAgentPolicy.enforcement.level)throw new Error("managed_room_prompt_snapshot_mismatch")}function fe(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function Je(e,n){Pe({sessionId:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:n.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,workDir:n.workDir,status:"active",externalChannel:null}}})}function Be(e,n){const t=e.sessions.get(n),l=t?.currentRunId;if(!t||!l)return;const a=t.nextEventSeq;e.runTextAcc.delete(`${n}:${l}`),me(e,t),t.pendingTextEvent=null,t.currentRunId=null,t.currentRunPhase=null,t.nextEventSeq=0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:l,seq:a},seq:a,id:`agent-evt-${l}-${a}`}),t.journalRunId&&(e.runJournal?.remove(n,t.journalRunId),e.recoveryBarrier?.end(n),t.journalRunId=null),e.chatQueue?.noteTerminal(n)}async function on(e,n,t){const l=t?n.params.managedRoomContextToken??null:null;let a=!1;try{await He(e,n,t,()=>{a=!0})}finally{l&&!a&&e.revokeManagedRoomContextToken?.(l)}}async function He(e,n,t,l){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}const{sessionId:a,text:b,agentType:_,sessionMode:ye,managerConfig:Q,workDir:ve,agentSessionId:u,modelId:v,systemPrompt:Z,managerDefaultWorkerAgentType:q,managerDefaultWorkerModelId:W,reasoningEffort:j,clientMessageId:he,sessionListProjection:Ie,waitForDispatch:G,responseId:Re,managedRoomContextToken:A,suppressExternalOwnerEcho:Se,queuedBatchItems:F,managedWorkerParentSessionId:h,managerWorkerNativeFull:X,managedRecoverySource:J}=n.params,c=Re||n.id;De(Ie);const T=null,B="";if(t?.source==="room_activation"&&(Fe(t,Z,A),_==="manager"))throw new Error("managed_agent_manager_mode_unsupported");if(je(e,n.id),!a||!b){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:"sessionId and text are required"});return}if(e.recoveryBarrier?.isDispatchFrozen()){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:"upgrade_in_progress"});return}e.recoveryBarrier?.isRecovering(a)&&await e.recoveryBarrier.wait(a);const i=_,S=i==="manager"||ye==="manager"?"manager":"standard";S==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(a,q??null,W??null);const M=(i==="claude"||i==="codex"||i==="workbuddy"||i==="pi-coding-agent"||i.startsWith("acp:"))&&typeof j=="string"&&j.trim()?j.trim():void 0,p=e.resolvePath($e(i,ve||ie.homedir(),u)||ie.homedir()),f=t?.managedAgentPolicy??(h?e.managerRuntime?.getManagedAgentPolicyForWorker(h,p)??null:null);if(h&&!X&&!f)throw new Error("managed_worker_permission_context_unavailable");const H=i==="codex"&&(t?.source==="room_activation"||h)?"internal":"user",z=h&&f?Te({requestedPolicy:f.requestedPolicy,enforcement:f.enforcement,resolvedWorkDir:f.adapterPolicy.workDir}):Z,we=p,C=Ee(n.params.attachments);C?.length&&xe(p,C);const m={text:b,attachments:C,localized:!1};let s=e.sessions.get(a);if(s){if(s.lastActiveAt=Date.now(),s.agentType!==i||(s.sessionMode??(s.agentType==="manager"?"manager":"standard"))!==S||s.workDir!==p||(s.nativeSessionVisibility??"user")!==H||JSON.stringify(s.externalChannel??null)!==JSON.stringify(T??null)){e.sessions.delete(a);try{await $(e,s)}catch{e.processedReqIds.delete(n.id)}s=void 0}else if(u&&s.agentSessionId!==u)try{await s.adapter.resume(u),s.agentSessionId=u}catch{e.sessions.delete(a);try{await $(e,s)}catch{e.processedReqIds.delete(n.id)}s=void 0}}if(!s)try{s=await qe(e,a,i,S,p,u,T,B,f,H)}catch(o){const d=o instanceof Error&&o.message.startsWith("Unsupported agent:")?o.message:`Failed to start ${_}: ${o instanceof Error?o.message:String(o)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${a} agentType=${_} workDir=${p} agentSessionId=${u??""}: ${d}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:d,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:d});return}const g={id:he??`user-${n.id}`,sessionId:a,role:"user",ts:Date.now(),payload:de(m.text,m.attachments)},Y=Array.isArray(F)&&F.length>0?F.map(o=>({id:o.clientMessageId??o.id,sessionId:a,role:J==="manager_event"?"agent":"user",ts:Date.parse(o.createdAt),payload:de(o.text,o.attachments)})):null,P=Y??[g],y=Me(a,g.id),ee=y.state==="queued";if(y.state!=="not_found"&&!ee){e.client.sendRes({type:"res",id:c,ok:!0,payload:y});return}if(G){t?.source==="room_activation"&&ge(s.adapter,a,i,T,B,A,t.managedAgentPolicy,e.managedRoomExecutor);try{await We(s.adapter,m.text,v,M,m.attachments,z,t?{source:t.source}:void 0)}catch(o){e.processedReqIds.delete(n.id),e.sessions.delete(a),await $(e,s),e.client.sendRes({type:"res",id:c,ok:!1,error:o instanceof Error?o.message:String(o)});return}}x({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:i,sessionMode:S,managerConfig:Q??null,modelId:v,managerDefaultWorkerAgentType:q??null,managerDefaultWorkerModelId:W??null,reasoningEffort:M,...t?.source==="room_activation"?{managedRoomPromptSnapshot:t.managedRoomPromptSnapshot}:{}}});const D=(o,d)=>({...ce(L()),sessionId:a,clientMessageId:g.id,messageId:g.id,deliveryState:o,bodyRevision:d,committedAt:new Date(g.ts).toISOString()}),N=o=>{e.nativeFusion?.registerManagedSend({sessionId:a,agentType:i,sourceAgentType:Ce(i,v),canonicalMessageId:g.id,sourceSessionKey:s.agentSessionId??u??null,text:m.text,managedEchoPolicy:o?.deliveryMode==="external_owner_queue"&&!Se?"import_following":"suppress_following",externalRunId:o?.deliveryMode==="external_owner_queue"?`codex-external-${o.queuedSubmissionId??g.id}`:null})},ne=()=>{s.currentRunId||(s.currentRunId=`dispatch:${n.id}`,s.currentRunPhase="thinking",s.nextEventSeq=0)},ae=()=>{if(!e.runJournal||s.journalRunId)return;const o=`run:${Ae()}`;e.runJournal.begin({daemonInstallationId:L(),sessionId:a,runId:o,clientMessageId:g.id,agentType:i,sessionMode:S,workDir:p,agentSessionId:s.agentSessionId??u??null,modelId:v??null,reasoningEffort:M??null,nativeSessionVisibility:H,providerRunId:null,source:J==="manager_event"?"manager_event":t?.source==="room_activation"?J==="managed_direct"?"managed_direct":"managed_room":h?"manager_worker":"personal",parentSessionId:h??null,managerWorkerNativeFull:X===!0,roomContextRegistryKey:typeof n.params.roomContextRegistryKey=="string"?n.params.roomContextRegistryKey:null,activationCauseMessageId:typeof n.params.activationCauseMessageId=="string"?n.params.activationCauseMessageId:null,rootCauseMessageId:typeof n.params.rootCauseMessageId=="string"?n.params.rootCauseMessageId:null,activationSequence:typeof n.params.activationSequence=="string"?n.params.activationSequence:null,activationHopCount:typeof n.params.activationHopCount=="number"?n.params.activationHopCount:null,managedPolicyVersion:t?.managedRoomPromptSnapshot.managedRoomPolicyVersion??null,managedRuntimePolicyHash:t?.managedRoomPromptSnapshot.runtimePermissionPolicyHash??(f?V("sha256").update(JSON.stringify({requestedPolicy:f.requestedPolicy,enforcement:f.enforcement})).digest("hex"):null)}),s.journalRunId=o},oe=()=>{if(N(),ee)return{...ce(L()),sessionId:a,clientMessageId:y.clientMessageId,messageId:y.messageId,deliveryState:y.deliveryState,bodyRevision:y.bodyRevision,committedAt:y.committedAt};try{Je(e,{sessionId:a,agentType:i,sessionMode:S,managerConfig:Q??null,workDir:we,agentSessionId:s.agentSessionId??u??null,modelId:v,managerDefaultWorkerAgentType:q??null,managerDefaultWorkerModelId:W??null});let o=0;for(const[d,r]of P.entries()){const I=Y?k(a,r,"committed"):le(a,r,"committed");d===0&&(o=I)}return be(p,C??[]),R(e.client),D("committed",o)}catch(o){throw e.nativeFusion?.cancelManagedSend?.(g.id),o}},te=o=>{s.journalRunId&&e.runJournal?.update(a,s.journalRunId,{deliveryMode:o?.deliveryMode==="external_owner_queue"?"external_owner_queue":"direct",externalRunId:o?.deliveryMode==="external_owner_queue"?`codex-external-${o.queuedSubmissionId??g.id}`:null})},se=o=>{s.currentRunId||(s.nextEventSeq=0),te(o),N(o);const d=o?.deliveryMode==="external_owner_queue"?"queued":"executing";let r=0;for(const[I,E]of P.entries()){const w=k(a,E,d);I===0&&(r=w)}return R(e.client),D(d,r)},U=async(o,d)=>{e.nativeFusion?.cancelManagedSend?.(g.id);const r=fe(i,o);if(console.error(`[chat.send] send failed reqId=${n.id} sessionId=${a} agentType=${_} workDir=${p} agentSessionId=${s.agentSessionId??u??""}: ${r}`),s.adapter.hasPendingAuthenticationRetry?.()===!0){d&&e.client.sendRes({type:"res",id:c,ok:!0,payload:D("queued",0)});return}e.sessions.delete(a),s.journalRunId&&(e.runJournal?.update(a,s.journalRunId,{phase:"terminal"}),e.runJournal?.remove(a,s.journalRunId),e.recoveryBarrier?.end(a),s.journalRunId=null);try{await $(e,s)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:r,runId:"",seq:0}}),!d){const E={id:`agent-error-${n.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:r};le(a,E),R(e.client)}d&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:r}))};if(G){try{oe()}catch(r){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:`Durable message commit failed: ${r instanceof Error?r.message:String(r)}`});return}ae(),ne();let o;try{t?.source==="room_activation"&&(ue(e,s,A),l());const r=m.attachments;o=await pe(s.adapter,m.text,v,M,r,z,t?{source:t.source}:void 0),x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(r){if(s.adapter.hasPendingAuthenticationRetry?.()){let w=0;for(const[O,K]of P.entries()){const _e=k(a,K,"queued");O===0&&(w=_e)}R(e.client),await U(r,!1),e.client.sendRes({type:"res",id:c,ok:!0,payload:D("queued",w)});return}let I=0;for(const[w,O]of P.entries()){const K=k(a,O,"failed");w===0&&(I=K)}R(e.client);const E=fe(i,r);await U(r,!1),e.client.sendRes({type:"res",id:c,ok:!0,payload:{...D("failed",I),failureMessage:E}});return}const d=se(o??void 0);e.client.sendRes({type:"res",id:c,ok:!0,payload:{...d,...m.localized?{localizedAttachments:!0}:{},...o?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:o.deliveryMode,queuedSubmissionId:o.queuedSubmissionId}:{}}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let re;try{re=oe()}catch(o){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:`Durable message commit failed: ${o instanceof Error?o.message:String(o)}`});return}ae(),ne(),t?.source==="room_activation"&&(ge(s.adapter,a,i,T,B,A,t.managedAgentPolicy,e.managedRoomExecutor),ue(e,s,A),l()),se(),e.client.sendRes({type:"res",id:c,ok:!0,payload:{...re,...m.localized?{localizedAttachments:!0}:{}}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const ke=m.attachments;pe(s.adapter,m.text,v,M,ke,z,t?{source:t.source}:void 0).then(o=>{if(o?.deliveryMode==="external_owner_queue"){te(o),N(o);for(const d of P)k(a,d,"queued");R(e.client)}x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(o=>{k(a,g,s.adapter.hasPendingAuthenticationRetry?.()?"queued":"failed"),R(e.client),U(o,!1)})}async function tn(e,n){const{sessionId:t,discardPending:l}=n.params,a=e.sessions.get(t);if(a){a.journalRunId&&e.runJournal?.markAborted(t,a.journalRunId),me(e,a);try{await a.adapter.stop()}catch{e.client.sendRes({type:"res",id:n.id,ok:!1,error:"agent_stop_failed"});return}Be(e,t),e.activityPublisher?.publish(t,null)}l===!0&&e.chatQueue?.purgeSession(t),e.client.sendRes({type:"res",id:n.id,ok:!0})}export{tn as handleChatAbort,on as handleChatSend,Je as sendSessionUpdateEvent,Fe as validateManagedRoomExecutionContext};
1
+ import ie from"node:os";import{createHash as V,randomUUID as Ae}from"node:crypto";import{buildUserMessagePayload as de}from"@shennian/wire";import{reportLog as x}from"../../log-reporter.js";import{appendMessage as le,getDaemonInstallationId as L,getMessageDeliveryStatus as Me,recordSession as Pe,upsertMessage as k}from"../store.js";import{mergeProjectedSessions as De}from"../projection.js";import{parsePersonalAttachmentReferences as Ee}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as xe,markAttachmentReferences as be}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as R}from"../index-publisher.js";import{getPersonalSyncIdentity as ce}from"../../personal-sync/epoch.js";import{buildManagedAgentPermissionPrompt as Te}from"../../room/managed-prompt-composer.js";import{getNativeSourceAgentType as Ce}from"./chat-events.js";import{configureAdapterForSession as ge,maybeResolveClaudeImportedWorkDir as $e,replaceManagedRoomContextToken as ue,revokeManagedRoomContextToken as me}from"./chat-adapter-config.js";import{createActiveSession as qe,disposeSession as q,prepareAdapterTurn as We,rememberProcessedReqId as je,sendAdapterTurn as pe}from"./chat-session-lifecycle.js";function Fe(e,n,t){if(!e.managedAgentPolicy)throw new Error("managed_agent_policy_missing");const l=e.managedRoomPromptSnapshot;if(!l)throw new Error("managed_room_prompt_snapshot_missing");if(!n?.trim())throw new Error("managed_room_prompt_incomplete");if(!t||!/^mrc_[A-Za-z0-9_-]{43}$/.test(t))throw new Error("managed_room_context_unavailable");const a=V("sha256").update(JSON.stringify({requestedPolicy:e.managedAgentPolicy.requestedPolicy,enforcement:e.managedAgentPolicy.enforcement})).digest("hex"),b=V("sha256").update(e.managedAgentPolicy.adapterPolicy.workDir).digest("hex");if(l.runtimePermissionPolicyHash!==a||l.resolvedWorkDirHash!==b||l.enforcementLevel!==e.managedAgentPolicy.enforcement.level)throw new Error("managed_room_prompt_snapshot_mismatch")}function fe(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function Je(e,n){Pe({sessionId:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:n.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,workDir:n.workDir,status:"active",externalChannel:null}}})}function Be(e,n){const t=e.sessions.get(n),l=t?.currentRunId;if(!t||!l)return;const a=t.nextEventSeq;e.runTextAcc.delete(`${n}:${l}`),me(e,t),t.pendingTextEvent=null,t.currentRunId=null,t.currentRunPhase=null,t.nextEventSeq=0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:l,seq:a},seq:a,id:`agent-evt-${l}-${a}`}),t.journalRunId&&(e.runJournal?.remove(n,t.journalRunId),e.recoveryBarrier?.end(n),t.journalRunId=null),e.chatQueue?.noteTerminal(n)}async function on(e,n,t){const l=t?n.params.managedRoomContextToken??null:null;let a=!1;try{await He(e,n,t,()=>{a=!0})}finally{l&&!a&&e.revokeManagedRoomContextToken?.(l)}}async function He(e,n,t,l){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}const{sessionId:a,text:b,agentType:_,sessionMode:ye,managerConfig:Q,workDir:ve,agentSessionId:u,modelId:v,systemPrompt:Z,managerDefaultWorkerAgentType:W,managerDefaultWorkerModelId:j,reasoningEffort:F,clientMessageId:he,sessionListProjection:Ie,waitForDispatch:G,responseId:Re,managedRoomContextToken:A,suppressExternalOwnerEcho:Se,queuedBatchItems:J,managedWorkerParentSessionId:h,managerWorkerNativeFull:X,managedRecoverySource:T}=n.params,c=Re||n.id;De(Ie);const C=null,B="";if(t?.source==="room_activation"&&(Fe(t,Z,A),_==="manager"))throw new Error("managed_agent_manager_mode_unsupported");if(je(e,n.id),!a||!b){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:"sessionId and text are required"});return}if(e.recoveryBarrier?.isDispatchFrozen()){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:"upgrade_in_progress"});return}e.recoveryBarrier?.isRecovering(a)&&await e.recoveryBarrier.wait(a);const i=_,S=i==="manager"||ye==="manager"?"manager":"standard";S==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(a,W??null,j??null);const M=(i==="claude"||i==="codex"||i==="workbuddy"||i==="pi-coding-agent"||i.startsWith("acp:"))&&typeof F=="string"&&F.trim()?F.trim():void 0,p=e.resolvePath($e(i,ve||ie.homedir(),u)||ie.homedir()),f=t?.managedAgentPolicy??(h?e.managerRuntime?.getManagedAgentPolicyForWorker(h,p)??null:null);if(h&&!X&&!f)throw new Error("managed_worker_permission_context_unavailable");const H=i==="codex"&&(t?.source==="room_activation"||h)?"internal":"user",z=h&&f?Te({requestedPolicy:f.requestedPolicy,enforcement:f.enforcement,resolvedWorkDir:f.adapterPolicy.workDir}):Z,we=p,$=Ee(n.params.attachments);$?.length&&xe(p,$);const m={text:b,attachments:$,localized:!1};let s=e.sessions.get(a);if(s){if(s.lastActiveAt=Date.now(),s.agentType!==i||(s.sessionMode??(s.agentType==="manager"?"manager":"standard"))!==S||s.workDir!==p||(s.nativeSessionVisibility??"user")!==H||JSON.stringify(s.externalChannel??null)!==JSON.stringify(C??null)){e.sessions.delete(a);try{await q(e,s)}catch{e.processedReqIds.delete(n.id)}s=void 0}else if(u&&s.agentSessionId!==u)try{await s.adapter.resume(u),s.agentSessionId=u}catch{e.sessions.delete(a);try{await q(e,s)}catch{e.processedReqIds.delete(n.id)}s=void 0}}if(!s)try{s=await qe(e,a,i,S,p,u,C,B,f,H)}catch(o){const d=o instanceof Error&&o.message.startsWith("Unsupported agent:")?o.message:`Failed to start ${_}: ${o instanceof Error?o.message:String(o)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${a} agentType=${_} workDir=${p} agentSessionId=${u??""}: ${d}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:d,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:d});return}const g={id:he??`user-${n.id}`,sessionId:a,role:"user",ts:Date.now(),payload:de(m.text,m.attachments)},Y=Array.isArray(J)&&J.length>0?J.map(o=>({id:o.clientMessageId??o.id,sessionId:a,role:T==="manager_event"?"agent":"user",ts:T==="manager_event"?Date.parse(o.createdAt):g.ts,payload:de(o.text,o.attachments)})):null,P=Y??[g],y=Me(a,g.id),ee=y.state==="queued";if(y.state!=="not_found"&&!ee){e.client.sendRes({type:"res",id:c,ok:!0,payload:y});return}if(G){t?.source==="room_activation"&&ge(s.adapter,a,i,C,B,A,t.managedAgentPolicy,e.managedRoomExecutor);try{await We(s.adapter,m.text,v,M,m.attachments,z,t?{source:t.source}:void 0)}catch(o){e.processedReqIds.delete(n.id),e.sessions.delete(a),await q(e,s),e.client.sendRes({type:"res",id:c,ok:!1,error:o instanceof Error?o.message:String(o)});return}}x({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:i,sessionMode:S,managerConfig:Q??null,modelId:v,managerDefaultWorkerAgentType:W??null,managerDefaultWorkerModelId:j??null,reasoningEffort:M,...t?.source==="room_activation"?{managedRoomPromptSnapshot:t.managedRoomPromptSnapshot}:{}}});const D=(o,d)=>({...ce(L()),sessionId:a,clientMessageId:g.id,messageId:g.id,deliveryState:o,bodyRevision:d,committedAt:new Date(g.ts).toISOString()}),N=o=>{e.nativeFusion?.registerManagedSend({sessionId:a,agentType:i,sourceAgentType:Ce(i,v),canonicalMessageId:g.id,sourceSessionKey:s.agentSessionId??u??null,text:m.text,managedEchoPolicy:o?.deliveryMode==="external_owner_queue"&&!Se?"import_following":"suppress_following",externalRunId:o?.deliveryMode==="external_owner_queue"?`codex-external-${o.queuedSubmissionId??g.id}`:null})},ne=()=>{s.currentRunId||(s.currentRunId=`dispatch:${n.id}`,s.currentRunPhase="thinking",s.nextEventSeq=0)},ae=()=>{if(!e.runJournal||s.journalRunId)return;const o=`run:${Ae()}`;e.runJournal.begin({daemonInstallationId:L(),sessionId:a,runId:o,clientMessageId:g.id,agentType:i,sessionMode:S,workDir:p,agentSessionId:s.agentSessionId??u??null,modelId:v??null,reasoningEffort:M??null,nativeSessionVisibility:H,providerRunId:null,source:T==="manager_event"?"manager_event":t?.source==="room_activation"?T==="managed_direct"?"managed_direct":"managed_room":h?"manager_worker":"personal",parentSessionId:h??null,managerWorkerNativeFull:X===!0,roomContextRegistryKey:typeof n.params.roomContextRegistryKey=="string"?n.params.roomContextRegistryKey:null,activationCauseMessageId:typeof n.params.activationCauseMessageId=="string"?n.params.activationCauseMessageId:null,rootCauseMessageId:typeof n.params.rootCauseMessageId=="string"?n.params.rootCauseMessageId:null,activationSequence:typeof n.params.activationSequence=="string"?n.params.activationSequence:null,activationHopCount:typeof n.params.activationHopCount=="number"?n.params.activationHopCount:null,managedPolicyVersion:t?.managedRoomPromptSnapshot.managedRoomPolicyVersion??null,managedRuntimePolicyHash:t?.managedRoomPromptSnapshot.runtimePermissionPolicyHash??(f?V("sha256").update(JSON.stringify({requestedPolicy:f.requestedPolicy,enforcement:f.enforcement})).digest("hex"):null)}),s.journalRunId=o},oe=()=>{if(N(),ee)return{...ce(L()),sessionId:a,clientMessageId:y.clientMessageId,messageId:y.messageId,deliveryState:y.deliveryState,bodyRevision:y.bodyRevision,committedAt:y.committedAt};try{Je(e,{sessionId:a,agentType:i,sessionMode:S,managerConfig:Q??null,workDir:we,agentSessionId:s.agentSessionId??u??null,modelId:v,managerDefaultWorkerAgentType:W??null,managerDefaultWorkerModelId:j??null});let o=0;for(const[d,r]of P.entries()){const I=Y?k(a,r,"committed"):le(a,r,"committed");d===0&&(o=I)}return be(p,$??[]),R(e.client),D("committed",o)}catch(o){throw e.nativeFusion?.cancelManagedSend?.(g.id),o}},te=o=>{s.journalRunId&&e.runJournal?.update(a,s.journalRunId,{deliveryMode:o?.deliveryMode==="external_owner_queue"?"external_owner_queue":"direct",externalRunId:o?.deliveryMode==="external_owner_queue"?`codex-external-${o.queuedSubmissionId??g.id}`:null})},se=o=>{s.currentRunId||(s.nextEventSeq=0),te(o),N(o);const d=o?.deliveryMode==="external_owner_queue"?"queued":"executing";let r=0;for(const[I,E]of P.entries()){const w=k(a,E,d);I===0&&(r=w)}return R(e.client),D(d,r)},U=async(o,d)=>{e.nativeFusion?.cancelManagedSend?.(g.id);const r=fe(i,o);if(console.error(`[chat.send] send failed reqId=${n.id} sessionId=${a} agentType=${_} workDir=${p} agentSessionId=${s.agentSessionId??u??""}: ${r}`),s.adapter.hasPendingAuthenticationRetry?.()===!0){d&&e.client.sendRes({type:"res",id:c,ok:!0,payload:D("queued",0)});return}e.sessions.delete(a),s.journalRunId&&(e.runJournal?.update(a,s.journalRunId,{phase:"terminal"}),e.runJournal?.remove(a,s.journalRunId),e.recoveryBarrier?.end(a),s.journalRunId=null);try{await q(e,s)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:r,runId:"",seq:0}}),!d){const E={id:`agent-error-${n.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:r};le(a,E),R(e.client)}d&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:r}))};if(G){try{oe()}catch(r){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:`Durable message commit failed: ${r instanceof Error?r.message:String(r)}`});return}ae(),ne();let o;try{t?.source==="room_activation"&&(ue(e,s,A),l());const r=m.attachments;o=await pe(s.adapter,m.text,v,M,r,z,t?{source:t.source}:void 0),x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(r){if(s.adapter.hasPendingAuthenticationRetry?.()){let w=0;for(const[O,K]of P.entries()){const _e=k(a,K,"queued");O===0&&(w=_e)}R(e.client),await U(r,!1),e.client.sendRes({type:"res",id:c,ok:!0,payload:D("queued",w)});return}let I=0;for(const[w,O]of P.entries()){const K=k(a,O,"failed");w===0&&(I=K)}R(e.client);const E=fe(i,r);await U(r,!1),e.client.sendRes({type:"res",id:c,ok:!0,payload:{...D("failed",I),failureMessage:E}});return}const d=se(o??void 0);e.client.sendRes({type:"res",id:c,ok:!0,payload:{...d,...m.localized?{localizedAttachments:!0}:{},...o?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:o.deliveryMode,queuedSubmissionId:o.queuedSubmissionId}:{}}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let re;try{re=oe()}catch(o){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:`Durable message commit failed: ${o instanceof Error?o.message:String(o)}`});return}ae(),ne(),t?.source==="room_activation"&&(ge(s.adapter,a,i,C,B,A,t.managedAgentPolicy,e.managedRoomExecutor),ue(e,s,A),l()),se(),e.client.sendRes({type:"res",id:c,ok:!0,payload:{...re,...m.localized?{localizedAttachments:!0}:{}}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const ke=m.attachments;pe(s.adapter,m.text,v,M,ke,z,t?{source:t.source}:void 0).then(o=>{if(o?.deliveryMode==="external_owner_queue"){te(o),N(o);for(const d of P)k(a,d,"queued");R(e.client)}x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(o=>{k(a,g,s.adapter.hasPendingAuthenticationRetry?.()?"queued":"failed"),R(e.client),U(o,!1)})}async function tn(e,n){const{sessionId:t,discardPending:l}=n.params,a=e.sessions.get(t);if(a){a.journalRunId&&e.runJournal?.markAborted(t,a.journalRunId),me(e,a);try{await a.adapter.stop()}catch{e.client.sendRes({type:"res",id:n.id,ok:!1,error:"agent_stop_failed"});return}Be(e,t),e.activityPublisher?.publish(t,null)}l===!0&&e.chatQueue?.purgeSession(t),e.client.sendRes({type:"res",id:n.id,ok:!0})}export{tn as handleChatAbort,on as handleChatSend,Je as sendSessionUpdateEvent,Fe as validateManagedRoomExecutionContext};
@@ -1 +1 @@
1
- import{NativeSourceDeletionStore as K}from"../native-fusion/native-source-deletions.js";import{QueueRetryScheduler as U}from"./queue-retry.js";import{findDuplicateQueuedMessage as P,stripLocalQueueFields as _,takeCompatibleBatch as J,formatQueuedBatch as E,dedupeQueuedAttachments as Q,classifyQueueError as T,isControllableQueueMessage as q,takeControllableMessage as G,resolveManagedRoomNativeSessionId as B}from"./queue-helpers.js";import{randomUUID as H}from"node:crypto";import{CODEX_DESKTOP_SEND_UNAVAILABLE as V,buildUserMessagePayload as X}from"@shennian/wire";import{isCodexThreadTailInterrupted as Y}from"../native-fusion/codex-files.js";import{mergeProjectedSessions as Z}from"./projection.js";import{parsePersonalAttachmentReferences as C}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as L,markAttachmentReferences as W}from"./attachment-transfer-store.js";import{getPersonalSyncIdentity as R}from"../personal-sync/epoch.js";import{getDaemonInstallationId as D}from"../personal-sync/identity.js";import{appendMessage as F,getMessageDeliveryStatus as v,listSessionRecords as b,recordSession as j,tombstoneMessage as N,upsertMessage as x}from"./store.js";import{requestSessionIndexPublish as S}from"./index-publisher.js";import{purgeQueuedSessionData as ee,readQueue as m,writeQueue as M}from"./queue-storage.js";import{getNativeSourceAgentType as te}from"./handlers/chat-events.js";import{purgeQueuedSessionData as ke}from"./queue-storage.js";class $ extends Error{constructor(e){super(e),this.name="TerminalQueuedChatDispatchError"}}function A(){return new Date().toISOString()}function z(u){const e=A(),s=u.managedWorkerParentSessionId?{kind:"manager_worker",parentSessionId:u.managedWorkerParentSessionId}:u.managerEventId?{kind:"manager_event",eventId:u.managerEventId}:u.managedAgentDirect?{kind:"managed_direct"}:{kind:"personal"};return{...R(D()),id:u.queueMessageId||u.clientMessageId||`queue-${H()}`,sessionId:u.sessionId,text:u.text,agentType:u.agentType,sessionMode:u.sessionMode,managerConfig:u.managerConfig,workDir:u.workDir,agentSessionId:u.agentSessionId??null,modelId:u.modelId??null,systemPrompt:u.systemPrompt??null,managerDefaultWorkerAgentType:u.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:u.managerDefaultWorkerModelId??null,reasoningEffort:u.reasoningEffort??null,clientMessageId:u.clientMessageId??null,attachments:C(u.attachments),externalChannel:u.externalChannel??null,replyTarget:u.replyTarget??null,origin:u.origin,createdAt:e,updatedAt:e,trigger:s}}function w(u){return{id:u.clientMessageId??u.id,sessionId:u.sessionId,role:u.trigger?.kind==="manager_event"?"agent":"user",ts:Date.parse(u.createdAt),payload:X(u.text,u.attachments)}}class Ie{opts;draining=new Set;promoting=new Set;disposed=!1;retries=new U(e=>{this.wakeSession(e)});deletions=new K;inFlightMessageIds=new Map;deferredLogState=new Map;prepareManagedRoomTurn=null;prepareManagedAgentDirectTurn=null;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}stop(){this.disposed=!0,this.retries.stop()}getSnapshot(e){const s=m().sessions[e]??[],t=this.inFlightMessageIds.get(e);return{...R(D()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:s.map(_),controllablePending:s.filter(o=>q(o)&&!t?.has(o.id)).map(_)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}setManagedAgentDirectTurnPreparer(e){this.prepareManagedAgentDirectTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const s=this.opts.getRuntime(),t=m(),o=new Set,i=new Map;let n=!1;for(const d of e){const r=d.params;if(this.deletions.hasSession(r.sessionId)){delete t.sessions[r.sessionId],n=!0;continue}if(!r.sessionId||!r.text||!r.agentType||!r.workDir)throw new Error("sessionId, text, agentType and workDir are required");const g=z({...R(D()),...r});g.origin="room",g.managedRoom=d.context,g.trigger={kind:"room_activation",...d.context};const p=t.sessions[r.sessionId]??[],c=P(p,g),I=g.clientMessageId??g.id,h=v(r.sessionId,I);if(!c&&h.state==="not_found")p.push(g),t.sessions[r.sessionId]=p,i.set(`${g.sessionId}\0${I}`,g),o.add(r.sessionId),n=!0;else if(c&&h.state==="not_found")i.set(`${c.sessionId}\0${I}`,c),o.add(r.sessionId);else if(c&&h.state==="queued")o.add(r.sessionId);else if(c){const l=p.filter(k=>k.id!==c.id);l.length?t.sessions[r.sessionId]=l:delete t.sessions[r.sessionId],n=!0}}n&&M(t);let a;for(const d of o)try{for(const r of i.values())r.sessionId===d&&(F(d,w(r),"queued"),j({sessionId:d,agentType:r.agentType,sessionMode:r.sessionMode,managerConfig:r.managerConfig,workDir:r.workDir,agentSessionId:B(s,r),modelId:r.modelId}));this.logLifecycle("enqueue.persisted",d,{origin:"room",pendingCount:m().sessions[d]?.length??0}),this.broadcast(d),this.wakeSession(d)}catch(r){a=r,this.logLifecycle("enqueue.commit-failed",d,{failureClass:T(r instanceof Error?r.message:"","canonical_commit_failed")},"error"),this.retries.schedule(d,!0)}if(o.size&&S(s.client),a)throw a}async handleEnqueue(e){const s=this.opts.getRuntime(),t=e.params;if(Z(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir)return s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"}),!1;const o=t.managedAgentDirect?await this.prepareManagedAgentDirectTurn?.(t.sessionId):null;if(t.managedAgentDirect&&!o)throw new Error("managed_agent_context_unavailable");const i=o?b().find(O=>O.sessionId===t.sessionId):null,n=o?{...t,...o,agentType:o.agentType,sessionMode:o.sessionMode,managerConfig:o.managerConfig,workDir:o.workDir,agentSessionId:o.agentSessionId,modelId:i?.modelId??void 0,systemPrompt:o.systemPrompt,managerDefaultWorkerAgentType:o.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:o.managerDefaultWorkerModelId}:t,a=s.resolvePath(n.workDir),d=C(n.attachments);d?.length&&L(a,d);const r={text:n.text,attachments:d,localized:!1},g=s.sessions.get(n.sessionId);if(n.agentType==="codex"&&n.agentSessionId&&g?.currentRunId?.startsWith("dispatch:")&&Y(n.agentSessionId))return s.client.sendRes({type:"res",id:e.id,ok:!1,error:V}),!1;o?.managedRoomContextToken&&s.revokeManagedRoomContextToken?.(o.managedRoomContextToken);const p=await this.isSessionBusy(n.sessionId),c=m(),I=c.sessions[n.sessionId]??[],h=z({...n,text:r.text,attachments:r.attachments,systemPrompt:o?null:n.systemPrompt});o&&(h.managedAgentDirect=!0,h.trigger?.kind!=="manager_event"&&(h.trigger={kind:"managed_direct"}));const l=h.clientMessageId??h.id,k=v(n.sessionId,l);if(k.state!=="not_found")return s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:k.state==="queued",queueMessageId:h.id,...k,queue:this.getSnapshot(n.sessionId)}}),!0;const y=P(I,h);y||(c.sessions[n.sessionId]=[...I,h],M(c));const f=y??h;return j({sessionId:f.sessionId,agentType:f.agentType,sessionMode:f.sessionMode,managerConfig:f.managerConfig,workDir:f.workDir,agentSessionId:f.agentSessionId,modelId:f.modelId}),F(f.sessionId,w(f),"queued"),W(s.resolvePath(f.workDir),f.attachments??[]),S(s.client),this.logLifecycle("enqueue.persisted",n.sessionId,{origin:n.origin??"app",queueMessageId:f.id,pendingCount:(m().sessions[n.sessionId]??[]).length}),this.broadcast(n.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:y?.id??h.id,...v(n.sessionId,y?.clientMessageId??y?.id??h.clientMessageId??h.id),queue:this.getSnapshot(n.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),p?this.retries.schedule(n.sessionId):this.wakeSession(n.sessionId),!0}async handleGet(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleEdit(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const o=m(),i=o.sessions[t.sessionId]??[],n=i.findIndex(r=>r.id===t.queueMessageId);if(n<0){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}if(!q(i[n])||this.inFlightMessageIds.get(t.sessionId)?.has(t.queueMessageId)){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const a=C(t.attachments),d=s.resolvePath(i[n].workDir);a?.length&&L(d,a),i[n]={...i[n],text:t.text,attachments:a,updatedAt:A()},o.sessions[t.sessionId]=i,M(o),x(t.sessionId,w(i[n]),"queued"),i[n].attachments?.length&&W(d,i[n].attachments??[]),S(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const o=m(),i=o.sessions[t.sessionId]??[],n=i.find(d=>d.id===t.queueMessageId);if(n&&(!q(n)||this.inFlightMessageIds.get(t.sessionId)?.has(t.queueMessageId))){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const a=i.filter(d=>d.id!==t.queueMessageId);if(a.length===i.length){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?o.sessions[t.sessionId]=a:delete o.sessions[t.sessionId],M(o),N(t.sessionId,n?.clientMessageId??n?.id??t.queueMessageId),S(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handlePromote(e){const s=this.opts.getRuntime(),t=e.params,o=n=>s.client.sendRes({type:"res",id:e.id,ok:!1,error:n});if(!t.sessionId||!t.queueMessageId){o("sessionId and queueMessageId are required");return}if(this.promoting.has(t.sessionId)){o("queue_promotion_in_progress");return}this.promoting.add(t.sessionId);let i=[];try{for(;this.draining.has(t.sessionId);)await new Promise(d=>setTimeout(d,5));if(i=G(m().sessions[t.sessionId]??[],t.queueMessageId),!i.length){o("Queued message not found");return}this.inFlightMessageIds.set(t.sessionId,new Set(i.map(d=>d.id)));const n=s.sessions.get(t.sessionId);let a="idle";if(n?.currentRunId)if(this.canInjectActiveBatch(n,i)){const d=i.length===1?i[0].text:E(i),r=i[0].clientMessageId??i[0].id;s.nativeFusion?.registerManagedSend({sessionId:t.sessionId,agentType:i[0].agentType,sourceAgentType:te(i[0].agentType,i[0].modelId),canonicalMessageId:r,sourceSessionKey:n.agentSessionId,text:d,managedEchoPolicy:"suppress_following"});try{await n.adapter.injectActiveInput({text:d,attachments:Q(i.flatMap(g=>g.attachments??[])),clientMessageId:r})}catch(g){throw s.nativeFusion?.cancelManagedSend(r),g}for(const g of i)x(t.sessionId,w(g),"executing");this.removeBatch(t.sessionId,i),a="injected"}else{if(!this.opts.interruptQueuedSession)throw new Error("queue_interrupt_unavailable");await this.opts.interruptQueuedSession(t.sessionId),await this.dispatchQueuedMessages(i),this.removeBatch(t.sessionId,i),a="restarted"}else await this.dispatchQueuedMessages(i),this.removeBatch(t.sessionId,i);S(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId),promotedMessageIds:i.map(d=>d.id),mode:a}})}catch(n){o(n instanceof Error?n.message:"queue_promotion_failed"),this.broadcast(t.sessionId)}finally{this.inFlightMessageIds.delete(t.sessionId),this.promoting.delete(t.sessionId),m().sessions[t.sessionId]?.length&&this.wakeSession(t.sessionId)}}noteTerminal(e){this.wakeSession(e)}purgeSession(e){ee(e),this.retries.cancel(e)}discardPendingManagedRoomMessages(e){if(this.deletions.hasSession(e)){const a=m().sessions[e]?.length??0;return this.purgeSession(e),a}const s=m(),t=s.sessions[e]??[],o=this.inFlightMessageIds.get(e)??new Set,i=t.filter(a=>a.managedRoom&&!o.has(a.id));if(i.length===0)return 0;const n=t.filter(a=>!i.includes(a));n.length?s.sessions[e]=n:delete s.sessions[e],M(s);for(const a of i)N(e,a.clientMessageId??a.id);return S(this.opts.getRuntime().client),this.logLifecycle("managed-room.discarded-paused",e,{discardedCount:i.length,pendingCount:n.length}),this.broadcast(e),i.length}wakeSession(e){this.drainNext(e).catch(()=>{this.logLifecycle("drain.retry-required",e,{},"error"),this.retries.schedule(e,!0)})}drainIdleQueues(){if(this.disposed)return;const e=m();for(const s of Object.keys(e.sessions))this.wakeSession(s)}async drainNext(e){if(this.disposed||this.draining.has(e)||this.promoting.has(e))return;this.draining.add(e);let s=!1,t=!1,o=[];try{if(this.deletions.hasSession(e)){this.purgeSession(e);return}if(await this.isSessionBusy(e)){const g=m().sessions[e]?.length??0,p=this.deferredLogState.get(e),c=Date.now();(!p||p.pendingCount!==g||c-p.at>=3e4)&&(this.logLifecycle("drain.deferred",e,{pendingCount:g}),this.deferredLogState.set(e,{at:c,pendingCount:g}));return}this.deferredLogState.delete(e);const i=m(),n=i.sessions[e]??[],a=n.filter(g=>{const p=g.clientMessageId??g.id,c=v(e,p).state;return c==="queued"||c==="not_found"});a.length!==n.length&&(a.length?i.sessions[e]=a:delete i.sessions[e],M(i),this.logLifecycle("drain.pruned-terminal",e,{prunedCount:n.length-a.length,pendingCount:a.length}));const d=J(a);if(d.length===0){this.broadcast(e);return}o=d,this.inFlightMessageIds.set(e,new Set(d.map(g=>g.id))),this.logLifecycle("dispatch.start",e,{batchCount:d.length,queueMessageId:d[0].id,origin:d[0].origin??"app"});for(const g of d)x(e,w(g),"queued");await this.dispatchQueuedMessages(d);const r=this.removeBatch(e,d);this.logLifecycle("dispatch.accepted",e,{batchCount:d.length,queueMessageId:d[0].id,pendingCount:r}),this.deferredLogState.delete(e),s=r>0,this.broadcast(e)}catch(i){if(i instanceof $){const n=this.removeBatch(e,o);this.logLifecycle("dispatch.terminal-failure",e,{batchCount:o.length,queueMessageId:o[0]?.id,pendingCount:n,failureClass:T(i.message,"terminal_dispatch_failed")},"error"),s=n>0}else t=!0,this.logLifecycle("dispatch.transient-failure",e,{failureClass:T(i instanceof Error?i.message:String(i),"transient_dispatch_failed")},"error");this.broadcast(e)}finally{this.inFlightMessageIds.delete(e),this.draining.delete(e),m().sessions[e]?.length?s?queueMicrotask(()=>this.wakeSession(e)):this.retries.schedule(e,t):this.retries.cancel(e)}}canInjectActiveBatch(e,s){if(!e.adapter.supportsActiveInput||!e.adapter.injectActiveInput||!e.journalRunId)return!1;const t=this.opts.getRuntime().runJournal?.get(s[0].sessionId,e.journalRunId),o=s[0].trigger?.kind==="managed_direct"||s[0].managedAgentDirect?null:"personal";return!!(o&&s[0].systemPrompt==null&&t&&t.source===o&&t.deliveryMode==="direct"&&t.agentType===s[0].agentType&&t.workDir===e.workDir&&t.agentSessionId===e.agentSessionId&&t.modelId===(s[0].modelId??null)&&t.reasoningEffort===(s[0].reasoningEffort??null))}removeBatch(e,s){if(s.length===0)return m().sessions[e]?.length??0;const t=m(),o=new Set(s.map(n=>n.id)),i=(t.sessions[e]??[]).filter(n=>!o.has(n.id));return i.length?t.sessions[e]=i:delete t.sessions[e],M(t),i.length}logLifecycle(e,s,t,o="info"){const i=`[chat.queue] ${JSON.stringify({at:A(),event:e,sessionId:s,...t})}`;o==="error"?console.error(i):console.info(i)}async dispatchQueuedMessages(e){const s=e[0],t=e.flatMap(l=>{if(l.trigger?.kind==="room_activation"){const{kind:k,...y}=l.trigger;return[y]}return l.managedRoom?[l.managedRoom]:[]}),o=e.some(l=>l.trigger?.kind==="managed_direct"||!!l.managedAgentDirect),i=e.find(l=>l.trigger?.kind==="manager_event")?.trigger,n=e.find(l=>l.trigger?.kind==="manager_worker")?.trigger,a=t.length>0?await this.prepareManagedRoomTurn?.(t):o?await this.prepareManagedAgentDirectTurn?.(s.sessionId):void 0;if(t.length>0&&!a)throw new Error("managed_room_turn_preparer_unavailable");if(o&&!a)throw new Error("managed_agent_context_unavailable");const d=new Set((m().sessions[s.sessionId]??[]).map(l=>l.id));if(this.disposed||this.deletions.hasSession(s.sessionId)||e.some(l=>!d.has(l.id)))throw a?.managedRoomContextToken&&this.opts.getRuntime().revokeManagedRoomContextToken?.(a.managedRoomContextToken),this.disposed?new Error("queue_stopped"):new $("queued_dispatch_cancelled");const r=a??null,g=r?.sessionMode==="manager"?r.managerConfig:null,p=e.length===1?s.text:E(e),c=Q(e.flatMap(l=>l.attachments??[])),I=o?a.agentSessionId:t.length>0?B(this.opts.getRuntime(),s):s.agentSessionId??null,h=a?b().find(l=>l.sessionId===s.sessionId):null;await this.opts.dispatchQueuedReq({type:"req",id:`queue-send-${s.id}-${Date.now()}`,method:"chat.send",params:{...R(D()),sessionId:s.sessionId,text:p,agentType:o?a.agentType:s.agentType,sessionMode:r?.sessionMode??s.sessionMode,managerConfig:r?g:s.managerConfig,workDir:o?a.workDir:s.workDir,agentSessionId:I,modelId:h?.modelId??s.modelId??void 0,systemPrompt:a?.systemPrompt??s.systemPrompt??null,managerDefaultWorkerAgentType:r?r.managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:r?r.managerDefaultWorkerModelId:s.managerDefaultWorkerModelId,reasoningEffort:s.reasoningEffort??void 0,clientMessageId:s.clientMessageId??s.id,attachments:c,externalChannel:s.externalChannel,replyTarget:s.replyTarget,roomContextRegistryKey:a?.roomContextRegistryKey,activationCauseMessageId:t.at(-1)?.causeMessageId,rootCauseMessageId:t.at(-1)?.rootCauseMessageId,activationSequence:t.at(-1)?.sequence,activationHopCount:t.at(-1)?.hopCount,managedRecoverySource:i?.kind==="manager_event"?"manager_event":o?"managed_direct":t.length>0?"managed_room":void 0,managerEventId:i?.kind==="manager_event"?i.eventId:void 0,managedWorkerParentSessionId:n?.kind==="manager_worker"?n.parentSessionId:void 0,managerWorkerNativeFull:n?.kind==="manager_worker"&&this.opts.getRuntime().managerRuntime?.isNativeSkillManager(n.parentSessionId,s.workDir)||void 0,managedRoomContextToken:a?.managedRoomContextToken,suppressExternalOwnerEcho:t.length>0||void 0,queuedBatchItems:e.map(_),waitForDispatch:!0}},a??void 0)}async isSessionBusy(e){const s=this.opts.getRuntime().recoveryBarrier;if(s?.isDispatchFrozen()||s?.isRecovering(e))return!0;const t=this.opts.getRuntime().sessions.get(e);if(!t)return!1;if(t.currentRunId){if(!(t.currentRunId.startsWith("dispatch:")&&!!this.opts.getRuntime().runJournal&&!t.journalRunId)||(t.adapter.getStatus?await t.adapter.getStatus().catch(()=>null):null)?.active)return!0;this.logLifecycle("drain.recovered-stale-dispatch",e,{staleRunId:t.currentRunId}),t.currentRunId=null,t.currentRunPhase=null}if(!t.adapter.getStatus)return!1;const o=await t.adapter.getStatus().catch(()=>null);return o?.active?(o.runId&&(t.currentRunId=o.runId,t.currentRunPhase=o.runPhase??t.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}export{Ie as ChatQueueManager,$ as TerminalQueuedChatDispatchError,ke as purgeQueuedSessionData};
1
+ import{NativeSourceDeletionStore as K}from"../native-fusion/native-source-deletions.js";import{QueueRetryScheduler as U}from"./queue-retry.js";import{findDuplicateQueuedMessage as P,stripLocalQueueFields as _,takeCompatibleBatch as J,formatQueuedBatch as E,dedupeQueuedAttachments as Q,classifyQueueError as T,isControllableQueueMessage as q,takeControllableMessage as G,resolveManagedRoomNativeSessionId as B}from"./queue-helpers.js";import{randomUUID as H}from"node:crypto";import{CODEX_DESKTOP_SEND_UNAVAILABLE as V,buildUserMessagePayload as X}from"@shennian/wire";import{isCodexThreadTailInterrupted as Y}from"../native-fusion/codex-files.js";import{mergeProjectedSessions as Z}from"./projection.js";import{parsePersonalAttachmentReferences as C}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as L,markAttachmentReferences as W}from"./attachment-transfer-store.js";import{getPersonalSyncIdentity as R}from"../personal-sync/epoch.js";import{getDaemonInstallationId as D}from"../personal-sync/identity.js";import{appendMessage as F,getMessageDeliveryStatus as v,listSessionRecords as b,recordSession as j,tombstoneMessage as N,upsertMessage as x}from"./store.js";import{requestSessionIndexPublish as S}from"./index-publisher.js";import{purgeQueuedSessionData as ee,readQueue as p,writeQueue as M}from"./queue-storage.js";import{getNativeSourceAgentType as te}from"./handlers/chat-events.js";import{purgeQueuedSessionData as ke}from"./queue-storage.js";class $ extends Error{constructor(e){super(e),this.name="TerminalQueuedChatDispatchError"}}function A(){return new Date().toISOString()}function z(u){const e=A(),s=u.managedWorkerParentSessionId?{kind:"manager_worker",parentSessionId:u.managedWorkerParentSessionId}:u.managerEventId?{kind:"manager_event",eventId:u.managerEventId}:u.managedAgentDirect?{kind:"managed_direct"}:{kind:"personal"};return{...R(D()),id:u.queueMessageId||u.clientMessageId||`queue-${H()}`,sessionId:u.sessionId,text:u.text,agentType:u.agentType,sessionMode:u.sessionMode,managerConfig:u.managerConfig,workDir:u.workDir,agentSessionId:u.agentSessionId??null,modelId:u.modelId??null,systemPrompt:u.systemPrompt??null,managerDefaultWorkerAgentType:u.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:u.managerDefaultWorkerModelId??null,reasoningEffort:u.reasoningEffort??null,clientMessageId:u.clientMessageId??null,attachments:C(u.attachments),externalChannel:u.externalChannel??null,replyTarget:u.replyTarget??null,origin:u.origin,createdAt:e,updatedAt:e,trigger:s}}function w(u){return{id:u.clientMessageId??u.id,sessionId:u.sessionId,role:u.trigger?.kind==="manager_event"?"agent":"user",ts:Date.parse(u.createdAt),payload:X(u.text,u.attachments)}}class Ie{opts;draining=new Set;promoting=new Set;disposed=!1;retries=new U(e=>{this.wakeSession(e)});deletions=new K;inFlightMessageIds=new Map;deferredLogState=new Map;prepareManagedRoomTurn=null;prepareManagedAgentDirectTurn=null;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}stop(){this.disposed=!0,this.retries.stop()}getSnapshot(e){const s=p().sessions[e]??[],t=this.inFlightMessageIds.get(e);return{...R(D()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:s.map(_),controllablePending:s.filter(o=>q(o)&&!t?.has(o.id)).map(_)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}setManagedAgentDirectTurnPreparer(e){this.prepareManagedAgentDirectTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const s=this.opts.getRuntime(),t=p(),o=new Set,i=new Map;let n=!1;for(const d of e){const r=d.params;if(this.deletions.hasSession(r.sessionId)){delete t.sessions[r.sessionId],n=!0;continue}if(!r.sessionId||!r.text||!r.agentType||!r.workDir)throw new Error("sessionId, text, agentType and workDir are required");const g=z({...R(D()),...r});g.origin="room",g.managedRoom=d.context,g.trigger={kind:"room_activation",...d.context};const c=t.sessions[r.sessionId]??[],h=P(c,g),I=g.clientMessageId??g.id,m=v(r.sessionId,I);if(!h&&m.state==="not_found")c.push(g),t.sessions[r.sessionId]=c,i.set(`${g.sessionId}\0${I}`,g),o.add(r.sessionId),n=!0;else if(h&&m.state==="not_found")i.set(`${h.sessionId}\0${I}`,h),o.add(r.sessionId);else if(h&&m.state==="queued")o.add(r.sessionId);else if(h){const l=c.filter(k=>k.id!==h.id);l.length?t.sessions[r.sessionId]=l:delete t.sessions[r.sessionId],n=!0}}n&&M(t);let a;for(const d of o)try{for(const r of i.values())r.sessionId===d&&(F(d,w(r),"queued"),j({sessionId:d,agentType:r.agentType,sessionMode:r.sessionMode,managerConfig:r.managerConfig,workDir:r.workDir,agentSessionId:B(s,r),modelId:r.modelId}));this.logLifecycle("enqueue.persisted",d,{origin:"room",pendingCount:p().sessions[d]?.length??0}),this.broadcast(d),this.wakeSession(d)}catch(r){a=r,this.logLifecycle("enqueue.commit-failed",d,{failureClass:T(r instanceof Error?r.message:"","canonical_commit_failed")},"error"),this.retries.schedule(d,!0)}if(o.size&&S(s.client),a)throw a}async handleEnqueue(e){const s=this.opts.getRuntime(),t=e.params;if(Z(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir)return s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"}),!1;const o=t.managedAgentDirect?await this.prepareManagedAgentDirectTurn?.(t.sessionId):null;if(t.managedAgentDirect&&!o)throw new Error("managed_agent_context_unavailable");const i=o?b().find(O=>O.sessionId===t.sessionId):null,n=o?{...t,...o,agentType:o.agentType,sessionMode:o.sessionMode,managerConfig:o.managerConfig,workDir:o.workDir,agentSessionId:o.agentSessionId,modelId:i?.modelId??void 0,systemPrompt:o.systemPrompt,managerDefaultWorkerAgentType:o.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:o.managerDefaultWorkerModelId}:t,a=s.resolvePath(n.workDir),d=C(n.attachments);d?.length&&L(a,d);const r={text:n.text,attachments:d,localized:!1},g=s.sessions.get(n.sessionId);if(n.agentType==="codex"&&n.agentSessionId&&g?.currentRunId?.startsWith("dispatch:")&&Y(n.agentSessionId))return s.client.sendRes({type:"res",id:e.id,ok:!1,error:V}),!1;o?.managedRoomContextToken&&s.revokeManagedRoomContextToken?.(o.managedRoomContextToken);const c=await this.isSessionBusy(n.sessionId),h=p(),I=h.sessions[n.sessionId]??[],m=z({...n,text:r.text,attachments:r.attachments,systemPrompt:o?null:n.systemPrompt});o&&(m.managedAgentDirect=!0,m.trigger?.kind!=="manager_event"&&(m.trigger={kind:"managed_direct"}));const l=m.clientMessageId??m.id,k=v(n.sessionId,l);if(k.state!=="not_found")return s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:k.state==="queued",queueMessageId:m.id,...k,queue:this.getSnapshot(n.sessionId)}}),!0;const y=P(I,m);y||(h.sessions[n.sessionId]=[...I,m],M(h));const f=y??m;return j({sessionId:f.sessionId,agentType:f.agentType,sessionMode:f.sessionMode,managerConfig:f.managerConfig,workDir:f.workDir,agentSessionId:f.agentSessionId,modelId:f.modelId}),F(f.sessionId,w(f),"queued"),W(s.resolvePath(f.workDir),f.attachments??[]),S(s.client),this.logLifecycle("enqueue.persisted",n.sessionId,{origin:n.origin??"app",queueMessageId:f.id,pendingCount:(p().sessions[n.sessionId]??[]).length}),this.broadcast(n.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:y?.id??m.id,...v(n.sessionId,y?.clientMessageId??y?.id??m.clientMessageId??m.id),queue:this.getSnapshot(n.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),c?this.retries.schedule(n.sessionId):this.wakeSession(n.sessionId),!0}async handleGet(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleEdit(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const o=p(),i=o.sessions[t.sessionId]??[],n=i.findIndex(r=>r.id===t.queueMessageId);if(n<0){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}if(!q(i[n])||this.inFlightMessageIds.get(t.sessionId)?.has(t.queueMessageId)){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const a=C(t.attachments),d=s.resolvePath(i[n].workDir);a?.length&&L(d,a),i[n]={...i[n],text:t.text,attachments:a,updatedAt:A()},o.sessions[t.sessionId]=i,M(o),x(t.sessionId,w(i[n]),"queued"),i[n].attachments?.length&&W(d,i[n].attachments??[]),S(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const o=p(),i=o.sessions[t.sessionId]??[],n=i.find(d=>d.id===t.queueMessageId);if(n&&(!q(n)||this.inFlightMessageIds.get(t.sessionId)?.has(t.queueMessageId))){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const a=i.filter(d=>d.id!==t.queueMessageId);if(a.length===i.length){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?o.sessions[t.sessionId]=a:delete o.sessions[t.sessionId],M(o),N(t.sessionId,n?.clientMessageId??n?.id??t.queueMessageId),S(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handlePromote(e){const s=this.opts.getRuntime(),t=e.params,o=n=>s.client.sendRes({type:"res",id:e.id,ok:!1,error:n});if(!t.sessionId||!t.queueMessageId){o("sessionId and queueMessageId are required");return}if(this.promoting.has(t.sessionId)){o("queue_promotion_in_progress");return}this.promoting.add(t.sessionId);let i=[];try{for(;this.draining.has(t.sessionId);)await new Promise(d=>setTimeout(d,5));if(i=G(p().sessions[t.sessionId]??[],t.queueMessageId),!i.length){o("Queued message not found");return}this.inFlightMessageIds.set(t.sessionId,new Set(i.map(d=>d.id)));const n=s.sessions.get(t.sessionId);let a="idle";if(n?.currentRunId)if(this.canInjectActiveBatch(n,i)){const d=i.length===1?i[0].text:E(i),r=i[0].clientMessageId??i[0].id;s.nativeFusion?.registerManagedSend({sessionId:t.sessionId,agentType:i[0].agentType,sourceAgentType:te(i[0].agentType,i[0].modelId),canonicalMessageId:r,sourceSessionKey:n.agentSessionId,text:d,managedEchoPolicy:"suppress_following"});const g=Date.now();try{await n.adapter.injectActiveInput({text:d,attachments:Q(i.flatMap(c=>c.attachments??[])),clientMessageId:r})}catch(c){throw s.nativeFusion?.cancelManagedSend(r),c}for(const c of i)x(t.sessionId,{...w(c),ts:g},"executing");this.removeBatch(t.sessionId,i),a="injected"}else{if(!this.opts.interruptQueuedSession)throw new Error("queue_interrupt_unavailable");await this.opts.interruptQueuedSession(t.sessionId),await this.dispatchQueuedMessages(i),this.removeBatch(t.sessionId,i),a="restarted"}else await this.dispatchQueuedMessages(i),this.removeBatch(t.sessionId,i);S(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId),promotedMessageIds:i.map(d=>d.id),mode:a}})}catch(n){o(n instanceof Error?n.message:"queue_promotion_failed"),this.broadcast(t.sessionId)}finally{this.inFlightMessageIds.delete(t.sessionId),this.promoting.delete(t.sessionId),p().sessions[t.sessionId]?.length&&this.wakeSession(t.sessionId)}}noteTerminal(e){this.wakeSession(e)}purgeSession(e){ee(e),this.retries.cancel(e)}discardPendingManagedRoomMessages(e){if(this.deletions.hasSession(e)){const a=p().sessions[e]?.length??0;return this.purgeSession(e),a}const s=p(),t=s.sessions[e]??[],o=this.inFlightMessageIds.get(e)??new Set,i=t.filter(a=>a.managedRoom&&!o.has(a.id));if(i.length===0)return 0;const n=t.filter(a=>!i.includes(a));n.length?s.sessions[e]=n:delete s.sessions[e],M(s);for(const a of i)N(e,a.clientMessageId??a.id);return S(this.opts.getRuntime().client),this.logLifecycle("managed-room.discarded-paused",e,{discardedCount:i.length,pendingCount:n.length}),this.broadcast(e),i.length}wakeSession(e){this.drainNext(e).catch(()=>{this.logLifecycle("drain.retry-required",e,{},"error"),this.retries.schedule(e,!0)})}drainIdleQueues(){if(this.disposed)return;const e=p();for(const s of Object.keys(e.sessions))this.wakeSession(s)}async drainNext(e){if(this.disposed||this.draining.has(e)||this.promoting.has(e))return;this.draining.add(e);let s=!1,t=!1,o=[];try{if(this.deletions.hasSession(e)){this.purgeSession(e);return}if(await this.isSessionBusy(e)){const g=p().sessions[e]?.length??0,c=this.deferredLogState.get(e),h=Date.now();(!c||c.pendingCount!==g||h-c.at>=3e4)&&(this.logLifecycle("drain.deferred",e,{pendingCount:g}),this.deferredLogState.set(e,{at:h,pendingCount:g}));return}this.deferredLogState.delete(e);const i=p(),n=i.sessions[e]??[],a=n.filter(g=>{const c=g.clientMessageId??g.id,h=v(e,c).state;return h==="queued"||h==="not_found"});a.length!==n.length&&(a.length?i.sessions[e]=a:delete i.sessions[e],M(i),this.logLifecycle("drain.pruned-terminal",e,{prunedCount:n.length-a.length,pendingCount:a.length}));const d=J(a);if(d.length===0){this.broadcast(e);return}o=d,this.inFlightMessageIds.set(e,new Set(d.map(g=>g.id))),this.logLifecycle("dispatch.start",e,{batchCount:d.length,queueMessageId:d[0].id,origin:d[0].origin??"app"});for(const g of d)x(e,w(g),"queued");await this.dispatchQueuedMessages(d);const r=this.removeBatch(e,d);this.logLifecycle("dispatch.accepted",e,{batchCount:d.length,queueMessageId:d[0].id,pendingCount:r}),this.deferredLogState.delete(e),s=r>0,this.broadcast(e)}catch(i){if(i instanceof $){const n=this.removeBatch(e,o);this.logLifecycle("dispatch.terminal-failure",e,{batchCount:o.length,queueMessageId:o[0]?.id,pendingCount:n,failureClass:T(i.message,"terminal_dispatch_failed")},"error"),s=n>0}else t=!0,this.logLifecycle("dispatch.transient-failure",e,{failureClass:T(i instanceof Error?i.message:String(i),"transient_dispatch_failed")},"error");this.broadcast(e)}finally{this.inFlightMessageIds.delete(e),this.draining.delete(e),p().sessions[e]?.length?s?queueMicrotask(()=>this.wakeSession(e)):this.retries.schedule(e,t):this.retries.cancel(e)}}canInjectActiveBatch(e,s){if(!e.adapter.supportsActiveInput||!e.adapter.injectActiveInput||!e.journalRunId)return!1;const t=this.opts.getRuntime().runJournal?.get(s[0].sessionId,e.journalRunId),o=s[0].trigger?.kind==="managed_direct"||s[0].managedAgentDirect?null:"personal";return!!(o&&s[0].systemPrompt==null&&t&&t.source===o&&t.deliveryMode==="direct"&&t.agentType===s[0].agentType&&t.workDir===e.workDir&&t.agentSessionId===e.agentSessionId&&t.modelId===(s[0].modelId??null)&&t.reasoningEffort===(s[0].reasoningEffort??null))}removeBatch(e,s){if(s.length===0)return p().sessions[e]?.length??0;const t=p(),o=new Set(s.map(n=>n.id)),i=(t.sessions[e]??[]).filter(n=>!o.has(n.id));return i.length?t.sessions[e]=i:delete t.sessions[e],M(t),i.length}logLifecycle(e,s,t,o="info"){const i=`[chat.queue] ${JSON.stringify({at:A(),event:e,sessionId:s,...t})}`;o==="error"?console.error(i):console.info(i)}async dispatchQueuedMessages(e){const s=e[0],t=e.flatMap(l=>{if(l.trigger?.kind==="room_activation"){const{kind:k,...y}=l.trigger;return[y]}return l.managedRoom?[l.managedRoom]:[]}),o=e.some(l=>l.trigger?.kind==="managed_direct"||!!l.managedAgentDirect),i=e.find(l=>l.trigger?.kind==="manager_event")?.trigger,n=e.find(l=>l.trigger?.kind==="manager_worker")?.trigger,a=t.length>0?await this.prepareManagedRoomTurn?.(t):o?await this.prepareManagedAgentDirectTurn?.(s.sessionId):void 0;if(t.length>0&&!a)throw new Error("managed_room_turn_preparer_unavailable");if(o&&!a)throw new Error("managed_agent_context_unavailable");const d=new Set((p().sessions[s.sessionId]??[]).map(l=>l.id));if(this.disposed||this.deletions.hasSession(s.sessionId)||e.some(l=>!d.has(l.id)))throw a?.managedRoomContextToken&&this.opts.getRuntime().revokeManagedRoomContextToken?.(a.managedRoomContextToken),this.disposed?new Error("queue_stopped"):new $("queued_dispatch_cancelled");const r=a??null,g=r?.sessionMode==="manager"?r.managerConfig:null,c=e.length===1?s.text:E(e),h=Q(e.flatMap(l=>l.attachments??[])),I=o?a.agentSessionId:t.length>0?B(this.opts.getRuntime(),s):s.agentSessionId??null,m=a?b().find(l=>l.sessionId===s.sessionId):null;await this.opts.dispatchQueuedReq({type:"req",id:`queue-send-${s.id}-${Date.now()}`,method:"chat.send",params:{...R(D()),sessionId:s.sessionId,text:c,agentType:o?a.agentType:s.agentType,sessionMode:r?.sessionMode??s.sessionMode,managerConfig:r?g:s.managerConfig,workDir:o?a.workDir:s.workDir,agentSessionId:I,modelId:m?.modelId??s.modelId??void 0,systemPrompt:a?.systemPrompt??s.systemPrompt??null,managerDefaultWorkerAgentType:r?r.managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:r?r.managerDefaultWorkerModelId:s.managerDefaultWorkerModelId,reasoningEffort:s.reasoningEffort??void 0,clientMessageId:s.clientMessageId??s.id,attachments:h,externalChannel:s.externalChannel,replyTarget:s.replyTarget,roomContextRegistryKey:a?.roomContextRegistryKey,activationCauseMessageId:t.at(-1)?.causeMessageId,rootCauseMessageId:t.at(-1)?.rootCauseMessageId,activationSequence:t.at(-1)?.sequence,activationHopCount:t.at(-1)?.hopCount,managedRecoverySource:i?.kind==="manager_event"?"manager_event":o?"managed_direct":t.length>0?"managed_room":void 0,managerEventId:i?.kind==="manager_event"?i.eventId:void 0,managedWorkerParentSessionId:n?.kind==="manager_worker"?n.parentSessionId:void 0,managerWorkerNativeFull:n?.kind==="manager_worker"&&this.opts.getRuntime().managerRuntime?.isNativeSkillManager(n.parentSessionId,s.workDir)||void 0,managedRoomContextToken:a?.managedRoomContextToken,suppressExternalOwnerEcho:t.length>0||void 0,queuedBatchItems:e.map(_),waitForDispatch:!0}},a??void 0)}async isSessionBusy(e){const s=this.opts.getRuntime().recoveryBarrier;if(s?.isDispatchFrozen()||s?.isRecovering(e))return!0;const t=this.opts.getRuntime().sessions.get(e);if(!t)return!1;if(t.currentRunId){if(!(t.currentRunId.startsWith("dispatch:")&&!!this.opts.getRuntime().runJournal&&!t.journalRunId)||(t.adapter.getStatus?await t.adapter.getStatus().catch(()=>null):null)?.active)return!0;this.logLifecycle("drain.recovered-stale-dispatch",e,{staleRunId:t.currentRunId}),t.currentRunId=null,t.currentRunPhase=null}if(!t.adapter.getStatus)return!1;const o=await t.adapter.getStatus().catch(()=>null);return o?.active?(o.runId&&(t.currentRunId=o.runId,t.currentRunPhase=o.runPhase??t.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}export{Ie as ChatQueueManager,$ as TerminalQueuedChatDispatchError,ke as purgeQueuedSessionData};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.4.47",
3
+ "version": "0.4.49",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {