shennian 0.4.48 → 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":
|
|
520
|
-
"afterBytes":
|
|
519
|
+
"beforeBytes": 26330,
|
|
520
|
+
"afterBytes": 12881
|
|
521
521
|
},
|
|
522
522
|
{
|
|
523
523
|
"file": "src/native-fusion/native-source-deletions.js",
|
|
@@ -796,8 +796,8 @@
|
|
|
796
796
|
},
|
|
797
797
|
{
|
|
798
798
|
"file": "src/session/handlers/chat.js",
|
|
799
|
-
"beforeBytes":
|
|
800
|
-
"afterBytes":
|
|
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":
|
|
910
|
-
"afterBytes":
|
|
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 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 C}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,"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)},v=F?p.upsert(l,_):p.append(l,_);if(f=v.bodyRevision,T=v.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&&C("sourceCursorAdvanced"),!r&&!M&&!I&&!T&&C("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)}
|
|
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
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 +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};
|