shennian 0.4.26 → 0.4.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/publish-build-manifest.json +8 -8
- package/dist/src/native-fusion/local-repository.js +2 -2
- package/dist/src/session/manager.js +2 -2
- package/dist/src/session/run-recovery/coordinator.d.ts +1 -1
- package/dist/src/session/run-recovery/coordinator.js +1 -1
- package/dist/src/session/store.d.ts +2 -0
- package/dist/src/session/store.js +2 -2
- package/node_modules/@shennian/wire/dist/session-sync.d.ts +2 -0
- package/node_modules/@shennian/wire/dist/session.d.ts +3 -1
- package/package.json +1 -1
|
@@ -496,8 +496,8 @@
|
|
|
496
496
|
},
|
|
497
497
|
{
|
|
498
498
|
"file": "src/native-fusion/local-repository.js",
|
|
499
|
-
"beforeBytes":
|
|
500
|
-
"afterBytes":
|
|
499
|
+
"beforeBytes": 25938,
|
|
500
|
+
"afterBytes": 12778
|
|
501
501
|
},
|
|
502
502
|
{
|
|
503
503
|
"file": "src/native-fusion/native-source-deletions.js",
|
|
@@ -796,8 +796,8 @@
|
|
|
796
796
|
},
|
|
797
797
|
{
|
|
798
798
|
"file": "src/session/manager.js",
|
|
799
|
-
"beforeBytes":
|
|
800
|
-
"afterBytes":
|
|
799
|
+
"beforeBytes": 49531,
|
|
800
|
+
"afterBytes": 26071
|
|
801
801
|
},
|
|
802
802
|
{
|
|
803
803
|
"file": "src/session/native-cleanup-outbox.js",
|
|
@@ -826,8 +826,8 @@
|
|
|
826
826
|
},
|
|
827
827
|
{
|
|
828
828
|
"file": "src/session/run-recovery/coordinator.js",
|
|
829
|
-
"beforeBytes":
|
|
830
|
-
"afterBytes":
|
|
829
|
+
"beforeBytes": 8103,
|
|
830
|
+
"afterBytes": 3507
|
|
831
831
|
},
|
|
832
832
|
{
|
|
833
833
|
"file": "src/session/run-recovery/journal.js",
|
|
@@ -841,8 +841,8 @@
|
|
|
841
841
|
},
|
|
842
842
|
{
|
|
843
843
|
"file": "src/session/store.js",
|
|
844
|
-
"beforeBytes":
|
|
845
|
-
"afterBytes":
|
|
844
|
+
"beforeBytes": 32176,
|
|
845
|
+
"afterBytes": 16319
|
|
846
846
|
},
|
|
847
847
|
{
|
|
848
848
|
"file": "src/session/tool-detail-store.js",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import p from"node:crypto";import l from"node:fs";import f from"node:path";import{sessionPreviewFromPayload as L}from"@shennian/wire";import{resolvePersonalSyncV1Path as R}from"../personal-sync/paths.js";import{getCanonicalSessionStore as P,getDaemonInstallationId as H,listSessionRecords as x,recordSession as w}from"../session/store.js";import{readToolDetail as U,writeToolDetail as J}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as V}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as v}from"../personal-sync/metrics.js";const I=1;class q extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class W extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class z extends Error{sourceEventKey;code="native_event_identity_conflict";constructor(e){super(`native event identity conflict: ${e}`),this.sourceEventKey=e,this.name="NativeEventIdentityConflictError"}}class de{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(G(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new W(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=Y(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=l.existsSync(a)?D(a):null;if(r&&r.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(r&&r.eventFingerprint!==c)throw new z(e.sourceEventKey);if(!i&&!r&&e.role!=="user")throw new q(e.sourceSessionKey);const m=e.claimSessionId??null,d=i?.sessionId??r?.sessionId??m??Q(s,e.agentType,e.sourceSessionKey);if(m&&i&&i.sessionId!==m)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const h=this.canonicalStore(),y=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",u=r?.canonicalMessageId??(y?null:e.claimCanonicalMessageId??X(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let S=h.getManifest(d)?.bodyRevision??0,T=!!r;const E=!y&&!!e.claimCanonicalMessageId;if(E){if(!u)throw new Error("managed echo claim requires a canonical message");const M=h.getMessageState(d,u);if(!M||M.tombstoned)throw new Error(`managed echo claim target is not durable: ${u}`)}if(!r&&!E&&!y){if(!u)throw new Error("native event requires a canonical message");const M=e.canonicalMessageKey?h.getMessageState(d,u):null,F=e.canonicalMutation==="upsert"||!!M,$=F?M??h.getMessageState(d,u):null,_={id:u,sessionId:d,role:e.role,ts:$?.message?.ts??e.ts,payload:Z($?.message?.payload,e.payload)},C=F?h.upsert(d,_):h.append(d,_);if(S=C.bodyRevision,T=C.duplicate,e.localToolDetail){const N=this.options.writeLocalToolDetail??J,j=this.options.readLocalToolDetail??U,B=ee(j(d,e.localToolDetail.toolCallId),e.localToolDetail);N({sessionId:d,...B,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(S=h.completeStartedUserMessages(d,e.ts).at(-1)?.bodyRevision??S),(!r||e.terminal)&&!y&&this.recordIndex(e,d,u,S);const K=new Date().toISOString();return r||g(a,{schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:u,canonicalRevision:S,cursor:e.cursor??null,mappedBy:y?"managed_echo_suppressed":E?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:K}),o.sources[n]={schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:r?i?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:r?i?.lastCursor??r.cursor:e.cursor??i?.lastCursor??null,bodyRevision:S,createdAt:i?.createdAt??K,updatedAt:K},g(this.sourcesFile(),o),!r&&e.cursor&&v("sourceCursorAdvanced"),!r&&!E&&!y&&!T&&v("canonicalCommits"),{sessionId:d,messageId:u,bodyRevision:S,duplicate:T,claimed:E,suppressed:y}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??x,s=this.options.recordLocalSession??w,o=this.canonicalStore();let n=0;for(const i of e()){if(i.localReady)continue;const a=o.getManifest(i.sessionId)?.firstAvailableSeq;if(a==null)continue;const r=o.readPage(i.sessionId,{limit:1,beforeSequence:a+1}).entries[0];!r||r.sequence!==a||r.message.role!=="user"||(s({sessionId:i.sessionId,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId,status:i.status,lastActivityAt:i.lastActivityAt,lastMessagePreview:i.lastMessagePreview,localReady:!0,firstUserMessageId:r.message.id,firstUserBodyRevision:r.revision}),n+=1)}return n}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){A(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,g(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&g(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],g(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(k(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),g(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],g(this.managedEchoStateFile(),o))}listSourceBindings(){return Object.values(this.readSources().sources).filter(e=>!this.isSourceDeleted(e.agentType,e.sourceSessionKey))}purgeSession(e){if(!e)throw new Error("sessionId is required");const s=this.readSources();let o=!1;for(const[a,r]of Object.entries(s.sources))r.sessionId===e&&(delete s.sources[a],o=!0);o&&g(this.sourcesFile(),s);const n=this.readManagedEchoState();let i=!1;for(const[a,r]of Object.entries(n.claims))r.sessionId===e&&(delete n.claims[a],i=!0);for(const[a,r]of Object.entries(n.suppressions))r.sessionId===e&&(delete n.suppressions[a],i=!0);i&&g(this.managedEchoStateFile(),n);const c=f.join(this.rootDir(),"ledger");if(l.existsSync(c))for(const a of l.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=f.join(c,a.name),m=D(r);if(m.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");m.sessionId===e&&l.unlinkSync(r)}}isSourceDeleted(e,s){return(this.options.deletionStore??new V({rootDir:f.basename(this.rootDir())==="native-fusion"?f.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const n=o.trim().slice(0,120);if(!n)return!1;const i=this.readSources().sources[b(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??x)().find(m=>m.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=p.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??w)({sessionId:i.sessionId,agentType:i.agentType,workDir:c?.workDir??"",agentSessionId:s,title:n,titleSource:"agent_native",titleOriginEventId:`agent-native:${a}`}),!0}recordIndex(e,s,o,n){const i=this.options.recordLocalSession??w,c=e.terminal?`terminal:${p.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?O(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{};i({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?e.terminalState==="failed"||e.terminalState==="aborted"?"failed":"completed":"active",attentionId:c,lastActivityAt:new Date(e.ts).toISOString(),...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?O(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!l.existsSync(e))return{schemaVersion:I,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==I||s.daemonInstallationId!==this.installationId()||!s.sources)throw new Error("invalid native source binding store");for(const o of Object.values(s.sources))if(o.daemonInstallationId!==this.installationId())throw new Error("native source binding belongs to another daemon installation");return s}installationId(){return this.options.daemonInstallationId??H()}canonicalStore(){return this.options.canonicalStore??P()}rootDir(){return this.options.rootDir??R("native-fusion")}sourcesFile(){return f.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return f.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:I,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==I||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))A(o);for(const o of Object.values(s.suppressions))k(o);return s}ledgerFile(e,s){const o=p.createHash("sha256").update(`${e}\0${s}`).digest("hex");return f.join(this.rootDir(),"ledger",`${o}.json`)}}function G(t){if(!t.agentType||!t.sourceSessionKey||!t.sourceEventKey||!t.payload)throw new Error("invalid native event");if(!Number.isFinite(t.ts))throw new Error("invalid native event timestamp")}function b(t,e){return p.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function Q(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function X(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Y(t){return p.createHash("sha256").update(JSON.stringify({agentType:t.agentType,sourceSessionKey:t.sourceSessionKey,sourceEventKey:t.sourceEventKey,role:t.role,ts:t.ts,payload:t.payload,canonicalMessageKey:t.canonicalMessageKey??null,canonicalMutation:t.canonicalMutation??"append"})).digest("hex")}function A(t){if(!t.sessionId||!t.canonicalMessageId||!t.textHash||!Number.isFinite(t.createdAt)||!["codex","claude","opencode"].includes(t.sourceAgentType)||!["suppress_following","import_following"].includes(t.managedEchoPolicy))throw new Error("invalid managed echo claim")}function k(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 Z(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 ee(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 O(t){return L(t,120)}function g(t,e){const s=f.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let n;try{n=l.openSync(o,"wx",384),l.writeFileSync(n,`${JSON.stringify(e,null,2)}
|
|
2
|
-
`,"utf8"),l.fsyncSync(n),l.closeSync(n),n=void 0,l.renameSync(o,t)}finally{n!==void 0&&l.closeSync(n);try{l.unlinkSync(o)}catch{}}}function D(t){return JSON.parse(l.readFileSync(t,"utf8"))}export{
|
|
1
|
+
import p from"node:crypto";import l from"node:fs";import f from"node:path";import{isToolPayload as L,sessionPreviewFromPayload as R}from"@shennian/wire";import{resolvePersonalSyncV1Path as P}from"../personal-sync/paths.js";import{getCanonicalSessionStore as H,getDaemonInstallationId as U,listSessionRecords as x,recordSession as w}from"../session/store.js";import{readToolDetail as V,writeToolDetail as J}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as q}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as v}from"../personal-sync/metrics.js";const I=1;class W extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class z extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class G extends Error{sourceEventKey;code="native_event_identity_conflict";constructor(e){super(`native event identity conflict: ${e}`),this.sourceEventKey=e,this.name="NativeEventIdentityConflictError"}}class ue{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}updateMessageDeliveryState(e,s,o){if(!this.canonicalStore().updateMessageDeliveryState(e,s,o))return;const i=(this.options.listLocalSessions??x)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(Q(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new z(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=Z(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=l.existsSync(a)?D(a):null;if(r&&r.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(r&&r.eventFingerprint!==c)throw new G(e.sourceEventKey);if(!i&&!r&&e.role!=="user")throw new W(e.sourceSessionKey);const u=e.claimSessionId??null,d=i?.sessionId??r?.sessionId??u??X(s,e.agentType,e.sourceSessionKey);if(u&&i&&i.sessionId!==u)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const h=this.canonicalStore(),y=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",g=r?.canonicalMessageId??(y?null:e.claimCanonicalMessageId??Y(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let S=h.getManifest(d)?.bodyRevision??0,T=!!r;const E=!y&&!!e.claimCanonicalMessageId;if(E){if(!g)throw new Error("managed echo claim requires a canonical message");const M=h.getMessageState(d,g);if(!M||M.tombstoned)throw new Error(`managed echo claim target is not durable: ${g}`)}if(!r&&!E&&!y){if(!g)throw new Error("native event requires a canonical message");const M=e.canonicalMessageKey?h.getMessageState(d,g):null,F=e.canonicalMutation==="upsert"||!!M,$=F?M??h.getMessageState(d,g):null,_={id:g,sessionId:d,role:e.role,ts:$?.message?.ts??e.ts,payload:ee($?.message?.payload,e.payload)},C=F?h.upsert(d,_):h.append(d,_);if(S=C.bodyRevision,T=C.duplicate,e.localToolDetail){const N=this.options.writeLocalToolDetail??J,j=this.options.readLocalToolDetail??V,B=se(j(d,e.localToolDetail.toolCallId),e.localToolDetail);N({sessionId:d,...B,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(S=h.completeStartedUserMessages(d,e.ts).at(-1)?.bodyRevision??S),(!r||e.terminal)&&!y&&this.recordIndex(e,d,g,S);const K=new Date().toISOString();return r||m(a,{schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:g,canonicalRevision:S,cursor:e.cursor??null,mappedBy:y?"managed_echo_suppressed":E?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:K}),o.sources[n]={schemaVersion:I,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:r?i?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:r?i?.lastCursor??r.cursor:e.cursor??i?.lastCursor??null,bodyRevision:S,createdAt:i?.createdAt??K,updatedAt:K},m(this.sourcesFile(),o),!r&&e.cursor&&v("sourceCursorAdvanced"),!r&&!E&&!y&&!T&&v("canonicalCommits"),{sessionId:d,messageId:g,bodyRevision:S,duplicate:T,claimed:E,suppressed:y}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??x,s=this.options.recordLocalSession??w,o=this.canonicalStore();let n=0;for(const i of e()){if(i.localReady)continue;const a=o.getManifest(i.sessionId)?.firstAvailableSeq;if(a==null)continue;const r=o.readPage(i.sessionId,{limit:1,beforeSequence:a+1}).entries[0];!r||r.sequence!==a||r.message.role!=="user"||(s({sessionId:i.sessionId,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId,status:i.status,lastActivityAt:i.lastActivityAt,lastMessagePreview:i.lastMessagePreview,localReady:!0,firstUserMessageId:r.message.id,firstUserBodyRevision:r.revision}),n+=1)}return n}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){A(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,m(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&m(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],m(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(O(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),m(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],m(this.managedEchoStateFile(),o))}listSourceBindings(){return Object.values(this.readSources().sources).filter(e=>!this.isSourceDeleted(e.agentType,e.sourceSessionKey))}purgeSession(e){if(!e)throw new Error("sessionId is required");const s=this.readSources();let o=!1;for(const[a,r]of Object.entries(s.sources))r.sessionId===e&&(delete s.sources[a],o=!0);o&&m(this.sourcesFile(),s);const n=this.readManagedEchoState();let i=!1;for(const[a,r]of Object.entries(n.claims))r.sessionId===e&&(delete n.claims[a],i=!0);for(const[a,r]of Object.entries(n.suppressions))r.sessionId===e&&(delete n.suppressions[a],i=!0);i&&m(this.managedEchoStateFile(),n);const c=f.join(this.rootDir(),"ledger");if(l.existsSync(c))for(const a of l.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=f.join(c,a.name),u=D(r);if(u.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");u.sessionId===e&&l.unlinkSync(r)}}isSourceDeleted(e,s){return(this.options.deletionStore??new q({rootDir:f.basename(this.rootDir())==="native-fusion"?f.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const n=o.trim().slice(0,120);if(!n)return!1;const i=this.readSources().sources[b(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??x)().find(u=>u.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=p.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??w)({sessionId:i.sessionId,agentType:i.agentType,workDir:c?.workDir??"",agentSessionId:s,title:n,titleSource:"agent_native",titleOriginEventId:`agent-native:${a}`}),!0}recordIndex(e,s,o,n){const i=this.options.recordLocalSession??w,c=e.terminal?`terminal:${p.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?k(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{},u=o&&(a||L(e.payload))?new Date(e.ts).toISOString():void 0;i({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?e.terminalState==="failed"||e.terminalState==="aborted"?"failed":"completed":"active",attentionId:c,lastActivityAt:new Date(e.ts).toISOString(),lastVisibleEventAt:u,...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?k(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!l.existsSync(e))return{schemaVersion:I,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==I||s.daemonInstallationId!==this.installationId()||!s.sources)throw new Error("invalid native source binding store");for(const o of Object.values(s.sources))if(o.daemonInstallationId!==this.installationId())throw new Error("native source binding belongs to another daemon installation");return s}installationId(){return this.options.daemonInstallationId??U()}canonicalStore(){return this.options.canonicalStore??H()}rootDir(){return this.options.rootDir??P("native-fusion")}sourcesFile(){return f.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return f.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:I,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==I||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))A(o);for(const o of Object.values(s.suppressions))O(o);return s}ledgerFile(e,s){const o=p.createHash("sha256").update(`${e}\0${s}`).digest("hex");return f.join(this.rootDir(),"ledger",`${o}.json`)}}function Q(t){if(!t.agentType||!t.sourceSessionKey||!t.sourceEventKey||!t.payload)throw new Error("invalid native event");if(!Number.isFinite(t.ts))throw new Error("invalid native event timestamp")}function b(t,e){return p.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function X(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Y(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Z(t){return p.createHash("sha256").update(JSON.stringify({agentType:t.agentType,sourceSessionKey:t.sourceSessionKey,sourceEventKey:t.sourceEventKey,role:t.role,ts:t.ts,payload:t.payload,canonicalMessageKey:t.canonicalMessageKey??null,canonicalMutation:t.canonicalMutation??"append"})).digest("hex")}function A(t){if(!t.sessionId||!t.canonicalMessageId||!t.textHash||!Number.isFinite(t.createdAt)||!["codex","claude","opencode"].includes(t.sourceAgentType)||!["suppress_following","import_following"].includes(t.managedEchoPolicy))throw new Error("invalid managed echo claim")}function O(t){if(!t.sessionId||!t.sourceSessionKey||!t.claimSourceEventKey||!["codex","claude","opencode"].includes(t.agentType)||!Number.isFinite(t.startedAt)||!Number.isFinite(t.endsAt)||t.endsAt<t.startedAt)throw new Error("invalid managed echo suppression")}function ee(t,e){if(!t)return e;try{const s=JSON.parse(t),o=JSON.parse(e);if(s.type!=="tool"||o.type!=="tool"||s.toolCallId!==o.toolCallId)return e;const n=typeof o.name=="string"?o.name:"",i=/(?:_output|_result)$/.test(n);return JSON.stringify({...s,...o,...i&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function se(t,e){if(!t||t.toolCallId!==e.toolCallId)return e;const s=/(?:_output|_result)$/.test(e.name);return{...e,name:s?t.name:e.name,...e.args===void 0&&t.args!==void 0?{args:t.args}:{},...e.result===void 0&&t.result!==void 0?{result:t.result}:{}}}function k(t){return R(t,120)}function m(t,e){const s=f.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let n;try{n=l.openSync(o,"wx",384),l.writeFileSync(n,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8"),l.fsyncSync(n),l.closeSync(n),n=void 0,l.renameSync(o,t)}finally{n!==void 0&&l.closeSync(n);try{l.unlinkSync(o)}catch{}}}function D(t){return JSON.parse(l.readFileSync(t,"utf8"))}export{W as MissingUserBoundaryError,G as NativeEventIdentityConflictError,ue as NativeFusionLocalRepository,z as NativeSourceDeletedError};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{SESSION_SYNC_PROTOCOL_VERSION as R,SESSION_SYNC_DATA_EPOCH as b,isAvailableAgentType as S}from"@shennian/wire";import g from"node:crypto";import{createAgent as E,getRegisteredAgents as x,unregisterAgent as M}from"../agents/adapter.js";import{resetCommandSpecCache as D}from"../agents/command-spec.js";import{loadConfig as _}from"../config/index.js";import{handleUpgradeStart as O,handleUpgradeStatus as F}from"../commands/upgrade.js";import{handleAgentsRefresh as $,handleModelsRefresh as U,refreshMachineAgents as Q}from"./handlers/agents.js";import{AcpDiscoveryWatcher as z}from"../agents/acp/watcher.js";import{handleAgentCapabilitiesList as B}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as N,handleAgentConfigGet as W,handleAgentConfigTest as J,handleAgentConfigUpsert as V}from"./handlers/agent-config.js";import{handleChatAbort as j,handleChatSend as f,recoverJournaledRun as H}from"./handlers/chat.js";import{handleSessionRefresh as L}from"./handlers/session-refresh.js";import{handleSessionToolDetail as G}from"./handlers/tool-detail.js";import{handleSessionTitleSet as Y}from"./handlers/title.js";import{handleSessionMessageStatus as K}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as X}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as Z}from"./handlers/voice-window.js";import{handleRemoteAccessPause as q}from"./handlers/remote-access.js";import{cleanupPendingTransfers as ee,handleFsLs as se,handleFsRead as te,handleFsRename as ae,handleFsWrite as ne,handleFsTransferAbort as ie,handleFsTransferChunk as re,handleFsTransferFinish as oe,handleFsTransferStart as ce,handleFsExportMarkdownPdf as de,handleFsExportMarkdownPdfSetup as le,handleFsArchiveZip as he}from"./handlers/fs.js";import{handleRegionProbe as ue,handleRegionSwitch as pe,handleUpgradeSetPolicy as me}from"./handlers/control.js";import{ManagerRuntimeService as ge,setManagerRuntimeService as I}from"../manager/runtime.js";import{ChatQueueManager as fe,TerminalQueuedChatDispatchError as ye}from"./queue.js";import{createAuthorizedFsRoot as ve,createAuthorizedFsRootAsync as we,resolveAuthorizedPath as Re,resolveAuthorizedPathAsync as be,resolveSessionWorkDir as Se}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as Ie}from"../agents/custom.js";import{handleManagedRoomBind as ke,handleManagedRoomRestart as Ae}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as v,purgeCanonicalSession as Te,purgeSessionRecord as Ce,recordSession as k,upsertMessage as A}from"./store.js";import{requestSessionIndexPublish as T}from"./index-publisher.js";import{reportUpgradeAggregateMetric as Pe}from"../log-reporter.js";import{PersonalHistorySubscriptions as Ee}from"./history-subscriptions.js";import{sweepAttachmentTransfers as xe}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as Me}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as De}from"./tool-detail-store.js";import{readDeletionSyncRevision as _e,writeDeletionSyncRevision as C}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as Oe,clearDeletionSyncFailure as Fe,DeletionSyncRetryWaitError as $e,DeletionSyncTerminalError as Ue,recordDeletionSyncFailure as w}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Qe}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as u}from"../personal-sync/epoch.js";import{ActiveRunJournal as ze}from"./run-recovery/journal.js";import{RunRecoveryBarrier as Be}from"./run-recovery/barrier.js";import{RunRecoveryCoordinator as Ne}from"./run-recovery/coordinator.js";const We=50;function Je(l,e){if(!l||!Number.isSafeInteger(l.baselineRevision)||l.baselineRevision<e||!Number.isSafeInteger(l.latestRevision)||l.latestRevision<l.baselineRevision||!Array.isArray(l.deletions))throw new Error("invalid deletion sync response");let t=l.baselineRevision;for(const s of l.deletions){if(!s?.sessionId||!Number.isSafeInteger(s.deletionRevision)||s.deletionRevision!==t+1||!Array.isArray(s.sources)||Number.isNaN(Date.parse(s.deletedAt))||s.nativeCleanupPolicy!==void 0&&s.nativeCleanupPolicy!=="none"&&s.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of s.sources)if(!a?.sourceSessionKey||!S(a.agentType))throw new Error("invalid native source deletion command");t=s.deletionRevision}if(t!==l.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as Bs}from"../fs/boundary.js";class Us{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;acpDiscoveryWatcher;runJournal;recoveryBarrier=new Be;recoveryCoordinator;recoveryPass=null;prepareManagedRecovery;constructor(e,t=null,s,a=null,i=null,d=null,r=null,o=null,h=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=d,this.managedRoomContextRevoker=r,this.managedRoomExecutor=o,this.runJournal=h??new ze(y());for(const n of this.runJournal.loadPendingReconciliation().records)this.recoveryBarrier.begin(n.sessionId);this.recoveryCoordinator=new Ne({journal:this.runJournal,barrier:this.recoveryBarrier,recover:(n,c)=>H(this.getRuntime(),n,c),commitTerminal:(n,c)=>this.commitRecoveryTerminal(n,c),isCanonicalTerminal:n=>{const c=v().find(m=>m.sessionId===n.sessionId),p=`terminal:${g.createHash("sha256").update(`${n.runId}\0`).digest("hex")}`;return c?.lastAttentionId===p},onOutcome:n=>Pe({kind:"recovery",adapterType:n.adapterType,result:n.result,stage:n.result==="continued"?"continue":n.result==="reattached"?"reattach":n.result==="resumed"?"resume":n.result==="terminal_reconciled"?"inspect":"terminal",durationMs:n.durationMs,retries:Math.max(0,n.attempts-1),...n.reason?{errorCode:n.reason}:{}})}),this.managerRuntime=new ge({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n,{propagateErrors:!0})}),this.chatQueue=new fe({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n),dispatchManagedReq:(n,c)=>this.dispatchManagedChat(n,c),dispatchQueuedReq:(n,c)=>this.dispatchQueuedChat(n,c)}),this.historySubscriptions=new Ee(this.client),this.nativeCleanupOutbox=new Qe({logger:console.info}),this.acpDiscoveryWatcher=new z(async()=>{D(),await Q(this.getRuntime())}),this.nativeFusion?.setExternalTerminalHandler?.(n=>{this.handleExternalTerminal(n)}),this.nativeCleanupOutbox.start(),I(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents();try{this.acpDiscoveryWatcher.start()}catch(n){console.warn(`[acp.discovery] watcher unavailable: ${n instanceof Error?n.message:String(n)}`)}this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...v().map(c=>c.workDir),...Array.from(this.pendingTransfers.values()).map(c=>c.rootPath)]);for(const c of n)try{xe(this.resolvePath(c))}catch(p){console.error("[attachment.cleanup] failed",p instanceof Error?p.message:String(p))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),resolveAuthorizedPathAsync:(e,t)=>this.resolveAuthorizedPathAsync(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor,runJournal:this.runJournal,recoveryBarrier:this.recoveryBarrier,prepareManagedRecovery:this.prepareManagedRecovery}}async resumePendingAcpAuthentication(e){const t=[...this.sessions.values()].find(s=>s.agentType===e&&s.adapter.hasPendingAuthenticationRetry?.());return t?.adapter.resumeAfterExternalAuthentication?(await t.adapter.resumeAfterExternalAuthentication(),!0):!1}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}prepareForUpgrade(e){this.recoveryBarrier.freezeDispatch();const t=this.runJournal.markPlannedUpgrade(e);return this.runJournal.sync(),{activeRunCount:t}}cancelUpgradePreparation(){this.recoveryBarrier.unfreezeDispatch()}async recoverInterruptedRuns(){this.recoveryPass??=this.recoveryCoordinator.recoverAll(),await this.recoveryPass}getPendingRecoveryCount(){return this.runJournal.loadPendingReconciliation().records.length}async commitRecoveryTerminal(e,t){const s=this.sessions.get(e.sessionId);s&&(s.currentRunId=null,s.currentRunPhase=null,s.journalRunId=null,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null)),this.runTextAcc.delete(`${e.sessionId}:${e.providerRunId??e.runId}`),this.publishSessionActivity(e.sessionId,null);const a=t.result==="interrupted_by_upgrade"?`\u4EFB\u52A1\u56E0\u795E\u5FF5\u5347\u7EA7\u800C\u4E2D\u65AD\uFF08${t.reason}\uFF09\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002
|
|
1
|
+
import{SESSION_SYNC_PROTOCOL_VERSION as w,SESSION_SYNC_DATA_EPOCH as b,isAvailableAgentType as S}from"@shennian/wire";import g from"node:crypto";import{createAgent as E,getRegisteredAgents as x,unregisterAgent as M}from"../agents/adapter.js";import{resetCommandSpecCache as D}from"../agents/command-spec.js";import{loadConfig as _}from"../config/index.js";import{handleUpgradeStart as O,handleUpgradeStatus as F}from"../commands/upgrade.js";import{handleAgentsRefresh as $,handleModelsRefresh as U,refreshMachineAgents as Q}from"./handlers/agents.js";import{AcpDiscoveryWatcher as z}from"../agents/acp/watcher.js";import{handleAgentCapabilitiesList as B}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as N,handleAgentConfigGet as W,handleAgentConfigTest as J,handleAgentConfigUpsert as j}from"./handlers/agent-config.js";import{handleChatAbort as V,handleChatSend as f,recoverJournaledRun as H}from"./handlers/chat.js";import{handleSessionRefresh as L}from"./handlers/session-refresh.js";import{handleSessionToolDetail as G}from"./handlers/tool-detail.js";import{handleSessionTitleSet as Y}from"./handlers/title.js";import{handleSessionMessageStatus as K}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as X}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as Z}from"./handlers/voice-window.js";import{handleRemoteAccessPause as q}from"./handlers/remote-access.js";import{cleanupPendingTransfers as ee,handleFsLs as se,handleFsRead as te,handleFsRename as ae,handleFsWrite as ne,handleFsTransferAbort as ie,handleFsTransferChunk as re,handleFsTransferFinish as oe,handleFsTransferStart as ce,handleFsExportMarkdownPdf as de,handleFsExportMarkdownPdfSetup as le,handleFsArchiveZip as he}from"./handlers/fs.js";import{handleRegionProbe as ue,handleRegionSwitch as pe,handleUpgradeSetPolicy as me}from"./handlers/control.js";import{ManagerRuntimeService as ge,setManagerRuntimeService as I}from"../manager/runtime.js";import{ChatQueueManager as fe,TerminalQueuedChatDispatchError as ye}from"./queue.js";import{createAuthorizedFsRoot as ve,createAuthorizedFsRootAsync as Re,resolveAuthorizedPath as we,resolveAuthorizedPathAsync as be,resolveSessionWorkDir as Se}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as Ie}from"../agents/custom.js";import{handleManagedRoomBind as ke,handleManagedRoomRestart as Ae}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as v,purgeCanonicalSession as Te,purgeSessionRecord as Ce,recordSession as k,upsertMessage as A}from"./store.js";import{requestSessionIndexPublish as T}from"./index-publisher.js";import{reportUpgradeAggregateMetric as Pe}from"../log-reporter.js";import{PersonalHistorySubscriptions as Ee}from"./history-subscriptions.js";import{sweepAttachmentTransfers as xe}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as Me}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as De}from"./tool-detail-store.js";import{readDeletionSyncRevision as _e,writeDeletionSyncRevision as C}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as Oe,clearDeletionSyncFailure as Fe,DeletionSyncRetryWaitError as $e,DeletionSyncTerminalError as Ue,recordDeletionSyncFailure as R}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Qe}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as u}from"../personal-sync/epoch.js";import{ActiveRunJournal as ze}from"./run-recovery/journal.js";import{RunRecoveryBarrier as Be}from"./run-recovery/barrier.js";import{RunRecoveryCoordinator as Ne}from"./run-recovery/coordinator.js";const We=50;function Je(l,e){if(!l||!Number.isSafeInteger(l.baselineRevision)||l.baselineRevision<e||!Number.isSafeInteger(l.latestRevision)||l.latestRevision<l.baselineRevision||!Array.isArray(l.deletions))throw new Error("invalid deletion sync response");let t=l.baselineRevision;for(const s of l.deletions){if(!s?.sessionId||!Number.isSafeInteger(s.deletionRevision)||s.deletionRevision!==t+1||!Array.isArray(s.sources)||Number.isNaN(Date.parse(s.deletedAt))||s.nativeCleanupPolicy!==void 0&&s.nativeCleanupPolicy!=="none"&&s.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of s.sources)if(!a?.sourceSessionKey||!S(a.agentType))throw new Error("invalid native source deletion command");t=s.deletionRevision}if(t!==l.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as Bs}from"../fs/boundary.js";class Us{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;acpDiscoveryWatcher;runJournal;recoveryBarrier=new Be;recoveryCoordinator;recoveryPass=null;prepareManagedRecovery;constructor(e,t=null,s,a=null,i=null,d=null,r=null,o=null,h=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=d,this.managedRoomContextRevoker=r,this.managedRoomExecutor=o,this.runJournal=h??new ze(y());for(const n of this.runJournal.loadPendingReconciliation().records)this.recoveryBarrier.begin(n.sessionId);this.recoveryCoordinator=new Ne({journal:this.runJournal,barrier:this.recoveryBarrier,recover:(n,c)=>H(this.getRuntime(),n,c),commitTerminal:(n,c)=>this.commitRecoveryTerminal(n,c),isCanonicalTerminal:n=>{const c=v().find(m=>m.sessionId===n.sessionId),p=`terminal:${g.createHash("sha256").update(`${n.runId}\0`).digest("hex")}`;return c?.lastAttentionId===p},onOutcome:n=>Pe({kind:"recovery",adapterType:n.adapterType,result:n.result,stage:n.result==="continued"?"continue":n.result==="reattached"?"reattach":n.result==="resumed"?"resume":n.result==="terminal_reconciled"?"inspect":"terminal",durationMs:n.durationMs,retries:Math.max(0,n.attempts-1),...n.reason?{errorCode:n.reason}:{}})}),this.managerRuntime=new ge({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n,{propagateErrors:!0})}),this.chatQueue=new fe({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n),dispatchManagedReq:(n,c)=>this.dispatchManagedChat(n,c),dispatchQueuedReq:(n,c)=>this.dispatchQueuedChat(n,c)}),this.historySubscriptions=new Ee(this.client),this.nativeCleanupOutbox=new Qe({logger:console.info}),this.acpDiscoveryWatcher=new z(async()=>{D(),await Q(this.getRuntime())}),this.nativeFusion?.setExternalTerminalHandler?.(n=>{this.handleExternalTerminal(n)}),this.nativeCleanupOutbox.start(),I(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents();try{this.acpDiscoveryWatcher.start()}catch(n){console.warn(`[acp.discovery] watcher unavailable: ${n instanceof Error?n.message:String(n)}`)}this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...v().map(c=>c.workDir),...Array.from(this.pendingTransfers.values()).map(c=>c.rootPath)]);for(const c of n)try{xe(this.resolvePath(c))}catch(p){console.error("[attachment.cleanup] failed",p instanceof Error?p.message:String(p))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),resolveAuthorizedPathAsync:(e,t)=>this.resolveAuthorizedPathAsync(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor,runJournal:this.runJournal,recoveryBarrier:this.recoveryBarrier,prepareManagedRecovery:this.prepareManagedRecovery}}async resumePendingAcpAuthentication(e){const t=[...this.sessions.values()].find(s=>s.agentType===e&&s.adapter.hasPendingAuthenticationRetry?.());return t?.adapter.resumeAfterExternalAuthentication?(await t.adapter.resumeAfterExternalAuthentication(),!0):!1}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}prepareForUpgrade(e){this.recoveryBarrier.freezeDispatch();const t=this.runJournal.markPlannedUpgrade(e);return this.runJournal.sync(),{activeRunCount:t}}cancelUpgradePreparation(){this.recoveryBarrier.unfreezeDispatch()}async recoverInterruptedRuns(){this.recoveryPass??=this.recoveryCoordinator.recoverAll(),await this.recoveryPass}getPendingRecoveryCount(){return this.runJournal.loadPendingReconciliation().records.length}async commitRecoveryTerminal(e,t){const s=this.sessions.get(e.sessionId);s&&(s.currentRunId=null,s.currentRunPhase=null,s.journalRunId=null,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null)),this.runTextAcc.delete(`${e.sessionId}:${e.providerRunId??e.runId}`),this.publishSessionActivity(e.sessionId,null);const a=t.result==="interrupted_by_upgrade"?`\u4EFB\u52A1\u56E0\u795E\u5FF5\u5347\u7EA7\u800C\u4E2D\u65AD\uFF08${t.reason}\uFF09\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002
|
|
2
2
|
Task interrupted by the Shennian upgrade (${t.reason}). The session is preserved and ready for a new message.`:`\u795E\u5FF5\u5347\u7EA7\u540E\u672A\u80FD\u6062\u590D\u4EFB\u52A1\uFF08${t.reason}\uFF09\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u91CD\u8BD5\u6216\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002
|
|
3
|
-
The task could not be recovered after the Shennian upgrade (${t.reason}). The session is preserved; retry or send a new message.`;A(e.sessionId,{id:`upgrade-recovery-terminal-${e.runId}`,sessionId:e.sessionId,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:"error",content:a,error:!0,recoveryState:t.result,recoveryReason:t.reason})}),k({sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,status:"failed",attentionId:`upgrade-recovery:${e.runId}`,lastActivityAt:new Date().toISOString()}),T(this.client);const i=e.lastObservedEventSeq+1;this.client.sendAgentEvent({type:"event",event:"agent",payload:t.result==="interrupted_by_upgrade"?{state:"interrupted_by_upgrade",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:i,reason:t.reason}:{state:"recovery_failed",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:i,reason:t.reason,...t.message?{message:t.message}:{}},seq:i,id:`agent-recovery-terminal-${e.runId}`}),this.chatQueue.noteTerminal(e.sessionId)}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}setManagedAgentDirectTurnPreparer(e){this.chatQueue.setManagedAgentDirectTurnPreparer(e)}setManagedRecoveryPreparer(e){this.prepareManagedRecovery=e}async enqueueManagedRoomBatch(e){const t=e.map(s=>{const a=v().find(i=>i.sessionId===s.params.sessionId);return{context:s.context,params:{...s.params,modelId:a?.modelId??s.params.modelId,sessionMode:a?.sessionMode??s.params.sessionMode,managerConfig:a?.managerConfig??s.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??s.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??s.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(t)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}managedChatContext(e){return{source:"room_activation",managedAgentPolicy:e.managedAgentPolicy,managedRoomPromptSnapshot:e.managedRoomPromptSnapshot}}async dispatchManagedChat(e,t){await f(this.getRuntime(),e,this.managedChatContext(t))}async dispatchQueuedChat(e,t){const s={value:null},a=new Proxy(this.client,{get:(i,d)=>{if(d==="sendRes")return o=>{s.value=o};const r=Reflect.get(i,d,i);return typeof r=="function"?r.bind(i):r}});if(await f({...this.getRuntime(),client:a},e,t?this.managedChatContext(t):void 0),s.value?.payload?.deliveryState==="failed")throw new ye(s.value.payload.failureMessage??s.value.error??"queued_chat_dispatch_failed");if(!s.value?.ok)throw new Error(s.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){Oe();try{await this.synchronizeSessionDeletionsOnce(),Fe()}catch(e){if(e instanceof $e||e instanceof Ue)throw e;const t=e instanceof Error?e.message:String(e),s=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(t)?"transport_unavailable":/invalid (?:deletion sync|session deletion)|deletion revision gap/i.test(t)?"invalid_deletion_sync_response":"deletion_sync_failed";throw console.warn("[session.deletions] retrying failed synchronization",{code:s}),w({code:s,retryable:!0}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),t=_e(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:R,dataEpoch:b,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const r=s.rejection;throw r?(w({code:r.code,retryable:r.retryable&&r.requiredAction==="retry"}),new Error(r.code)):(w({code:"unstructured_rejection",retryable:!0}),new Error(s.error??"unstructured_rejection"))}const a=s.payload;u(a),Je(a,t);let i=a.baselineRevision;i>t&&C(i);for(const r of a.deletions)r.deletionRevision<=i||(await this.applySessionDeletion(r),i=r.deletionRevision,C(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const d=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:R,dataEpoch:b,daemonInstallationId:e,deletionRevision:i}});if(!d.ok)throw new Error(d.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new Me({daemonInstallationId:y()});t.recordSession(e);for(const a of e.sources){const i=t.find(a.agentType,a.sourceSessionKey);i&&i.sessionId!==e.sessionId||t.record({...e,...a})}this.runJournal.markSessionAborted(e.sessionId),this.recoveryBarrier.end(e.sessionId);const s=this.sessions.get(e.sessionId);if(s){this.revokeManagedRoomToken(s);try{await s.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),De(e.sessionId),Te(e.sessionId),Ce(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async handleExternalTerminal(e){const t=this.sessions.get(e);t&&(this.sessions.delete(e),this.revokeManagedRoomToken(t),t.heartbeatTimer&&clearInterval(t.heartbeatTimer),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:a,canStop:s.canStop??!0});const i=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:i,runPhase:s.runPhase,canStop:s.canStop??!0},seq:i,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of x())t.startsWith("custom:")&&M(t);const e=_();for(const[t,s]of Object.entries(e.customAgents??{}))Ie(t,s)}async handleAgentPermissionResponse(e){const t=typeof e.params.sessionId=="string"?e.params.sessionId:"",s=typeof e.params.permissionId=="string"?e.params.permissionId:"",a=typeof e.params.optionId=="string"?e.params.optionId:"",i=this.sessions.get(t);if(!t||!s||!a||!i?.adapter.respondToPermission){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"permission_request_unavailable"});return}try{await i.adapter.respondToPermission(s,a),this.client.sendRes({type:"res",id:e.id,ok:!0,payload:{accepted:!0}})}catch(d){this.client.sendRes({type:"res",id:e.id,ok:!1,error:d instanceof Error?d.message:"permission_response_failed"})}}async handleAcpAgentRequest(e){const t=typeof e.params.agentType=="string"?e.params.agentType:"";if(!t.startsWith("acp:")||!S(t)){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"invalid_acp_agent"});return}const s=e.method==="agent.auth.start"?[...this.sessions.values()].find(r=>r.agentType===t&&r.adapter.hasPendingAuthenticationRetry?.())?.adapter:void 0;if(String(e.method)==="agent.session-options.get"){const r=typeof e.params.sessionId=="string"?e.params.sessionId:"",o=this.sessions.get(r);if(!o||o.agentType!==t||!o.adapter.getSessionOptions){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"acp_session_not_active"});return}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:o.adapter.getSessionOptions()});return}const a=s??E(t);if(!a){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"acp_agent_not_installed"});return}const i=typeof e.params.cwd=="string"&&e.params.cwd?e.params.cwd:process.cwd(),d=!s;try{d&&await a.start(`acp-control-${g.randomUUID()}`,i);let r={ok:!0};if(e.method==="agent.external-sessions.list")r={sessions:await a.listExternalSessions?.({cwd:typeof e.params.cwd=="string"?e.params.cwd:void 0})??[]};else if(e.method==="agent.external-sessions.import"){if(!a.loadExternalSession)throw new Error("ACP session history import is unsupported");const o=String(e.params.agentSessionId??""),h=String(e.params.sessionId??"");if(!o||!h)throw new Error("ACP and Shennian session ids are required");const n=await a.loadExternalSession(o),c=Date.now();for(const[p,m]of n.entries()){const P=`acp-import-${g.createHash("sha256").update(`${t}\0${o}\0${m.sourceId}`).digest("hex")}`;A(h,{id:P,sessionId:h,role:m.role,ts:c+p,payload:m.payload})}k({sessionId:h,agentType:t,workDir:i,agentSessionId:o,title:typeof e.params.title=="string"?e.params.title.slice(0,120):void 0,titleSource:typeof e.params.title=="string"?"agent_native":void 0,status:"active"}),T(this.client),this.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:h,agentType:t,agentSessionId:o,workDir:i,status:"active"}}}),r={sessionId:h,agentSessionId:o,importedMessageCount:n.length}}else if(e.method==="agent.auth.start"){if(!a.authenticate)throw new Error("ACP authentication is unsupported");await a.authenticate(String(e.params.methodId??""))}else if(e.method==="agent.auth.logout"){if(!a.logout)throw new Error("ACP logout is unsupported");await a.logout()}else{const o=String(e.params.agentSessionId??"");if(!o)throw new Error("ACP session id is required");if(e.method==="agent.external-sessions.close"){if(!a.closeExternalSession)throw new Error("ACP session close is unsupported");await a.closeExternalSession(o)}else{if(!a.deleteExternalSession)throw new Error("ACP session delete is unsupported");await a.deleteExternalSession(o)}}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:r})}catch(r){this.client.sendRes({type:"res",id:e.id,ok:!1,error:r instanceof Error?r.message:"acp_request_failed"})}finally{d&&await a.stop().catch(()=>{})}}async handleReq(e,t={}){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":u(e.params),this.recoveryBarrier.isDispatchFrozen()||this.recoveryBarrier.isRecovering(String(e.params.sessionId??""))?await this.chatQueue.handleEnqueue({...e,method:"chat.enqueue"}):await f(s,e);break;case"chat.enqueue":u(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":u(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":u(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":u(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":u(e.params),await j(s,e);break;case"session.refresh":u(e.params),await L(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":K(s,e);break;case"session.tool.detail":await G(s,e);break;case"voice.session.body.window":Z(s,e);break;case"session.title.set":await Y(s,e);break;case"room.agent.bind-managed":await ke(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await Ae(s,this.managedRoomBinder,e);break;case"fs.ls":await se(s,e);break;case"fs.read":await te(s,e);break;case"fs.write":await ne(s,e);break;case"fs.export.markdown-pdf":await de(s,e);break;case"fs.export.markdown-pdf.setup":await le(s,e);break;case"fs.archive.zip":await he(s,e);break;case"fs.rename":await ae(s,e);break;case"fs.transfer.start":await ce(s,e);break;case"fs.transfer.chunk":await re(s,e);break;case"fs.transfer.finish":await oe(s,e);break;case"fs.transfer.abort":await ie(s,e);break;case"region.probe":await ue(s,e);break;case"region.switch":await pe(s,e);break;case"upgrade.start":await O(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0,prepareForUpgrade:a=>this.prepareForUpgrade(a),cancelUpgradePreparation:()=>this.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()}});break;case"upgrade.status":await F(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await me(s,e);break;case"machine.remote-access.pause":await q(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await $(s,e);break;case"agent.permission.respond":await this.handleAgentPermissionResponse(e);break;case"agent.auth.start":case"agent.auth.logout":case"agent.session-options.get":case"agent.external-sessions.list":case"agent.external-sessions.import":case"agent.external-sessions.close":case"agent.external-sessions.delete":await this.handleAcpAgentRequest(e);break;case"models.refresh":await U(s,e);break;case"agent.config.get":await W(s,e);break;case"agent.config.upsert":await V(s,e);break;case"agent.config.clear":await N(s,e);break;case"agent.config.test":await J(s,e);break;case"agent.workspace.validate":await X(s,e);break;case"agent.capabilities.list":await B(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(a){if(t.propagateErrors)throw a;this.client.sendRes({type:"res",id:e.id,ok:!1,error:a instanceof Error?a.message:String(a)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<We)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(this.revokeManagedRoomToken(e.session),e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return Se(e)}resolveAuthorizedPath(e,t){return Re(e,ve(t))}async resolveAuthorizedPathAsync(e,t){return be(e,await we(t))}async cleanup(){this.acpDiscoveryWatcher.stop(),this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),ee(this.getRuntime()),await this.managerRuntime.stop(),I(null)}revokeManagedRoomToken(e){const t=e.managedRoomContextToken;e.managedRoomContextToken=null,t&&this.managedRoomContextRevoker?.(t)}}export{Us as SessionManager,Bs as resolveSessionWorkDir};
|
|
3
|
+
The task could not be recovered after the Shennian upgrade (${t.reason}). The session is preserved; retry or send a new message.`;A(e.sessionId,{id:`upgrade-recovery-terminal-${e.runId}`,sessionId:e.sessionId,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:"error",content:a,error:!0,recoveryState:t.result,recoveryReason:t.reason})}),k({sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,status:"failed",attentionId:`upgrade-recovery:${e.runId}`,lastActivityAt:new Date().toISOString()}),T(this.client);const i=e.lastObservedEventSeq+1;this.client.sendAgentEvent({type:"event",event:"agent",payload:t.result==="interrupted_by_upgrade"?{state:"interrupted_by_upgrade",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:i,reason:t.reason}:{state:"recovery_failed",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:i,reason:t.reason,...t.message?{message:t.message}:{}},seq:i,id:`agent-recovery-terminal-${e.runId}`}),this.chatQueue.noteTerminal(e.sessionId)}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}setManagedAgentDirectTurnPreparer(e){this.chatQueue.setManagedAgentDirectTurnPreparer(e)}setManagedRecoveryPreparer(e){this.prepareManagedRecovery=e}async enqueueManagedRoomBatch(e){const t=e.map(s=>{const a=v().find(i=>i.sessionId===s.params.sessionId);return{context:s.context,params:{...s.params,modelId:a?.modelId??s.params.modelId,sessionMode:a?.sessionMode??s.params.sessionMode,managerConfig:a?.managerConfig??s.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??s.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??s.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(t)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}managedChatContext(e){return{source:"room_activation",managedAgentPolicy:e.managedAgentPolicy,managedRoomPromptSnapshot:e.managedRoomPromptSnapshot}}async dispatchManagedChat(e,t){await f(this.getRuntime(),e,this.managedChatContext(t))}async dispatchQueuedChat(e,t){const s={value:null},a=new Proxy(this.client,{get:(i,d)=>{if(d==="sendRes")return o=>{s.value=o};const r=Reflect.get(i,d,i);return typeof r=="function"?r.bind(i):r}});if(await f({...this.getRuntime(),client:a},e,t?this.managedChatContext(t):void 0),s.value?.payload?.deliveryState==="failed")throw new ye(s.value.payload.failureMessage??s.value.error??"queued_chat_dispatch_failed");if(!s.value?.ok)throw new Error(s.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){Oe();try{await this.synchronizeSessionDeletionsOnce(),Fe()}catch(e){if(e instanceof $e||e instanceof Ue)throw e;const t=e instanceof Error?e.message:String(e),s=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(t)?"transport_unavailable":/invalid (?:deletion sync|session deletion)|deletion revision gap/i.test(t)?"invalid_deletion_sync_response":"deletion_sync_failed";throw console.warn("[session.deletions] retrying failed synchronization",{code:s}),R({code:s,retryable:!0}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),t=_e(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:w,dataEpoch:b,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const r=s.rejection;throw r?(R({code:r.code,retryable:r.retryable&&r.requiredAction==="retry"}),new Error(r.code)):(R({code:"unstructured_rejection",retryable:!0}),new Error(s.error??"unstructured_rejection"))}const a=s.payload;u(a),Je(a,t);let i=a.baselineRevision;i>t&&C(i);for(const r of a.deletions)r.deletionRevision<=i||(await this.applySessionDeletion(r),i=r.deletionRevision,C(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const d=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:w,dataEpoch:b,daemonInstallationId:e,deletionRevision:i}});if(!d.ok)throw new Error(d.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new Me({daemonInstallationId:y()});t.recordSession(e);for(const a of e.sources){const i=t.find(a.agentType,a.sourceSessionKey);i&&i.sessionId!==e.sessionId||t.record({...e,...a})}this.runJournal.markSessionAborted(e.sessionId),this.recoveryBarrier.end(e.sessionId);const s=this.sessions.get(e.sessionId);if(s){this.revokeManagedRoomToken(s);try{await s.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),De(e.sessionId),Te(e.sessionId),Ce(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async handleExternalTerminal(e){const t=this.sessions.get(e);t&&(t.journalRunId&&(this.recoveryCoordinator.completeFromProvider(e,t.journalRunId),t.journalRunId=null),this.sessions.delete(e),this.revokeManagedRoomToken(t),t.heartbeatTimer&&clearInterval(t.heartbeatTimer),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:a,canStop:s.canStop??!0});const i=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:i,runPhase:s.runPhase,canStop:s.canStop??!0},seq:i,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of x())t.startsWith("custom:")&&M(t);const e=_();for(const[t,s]of Object.entries(e.customAgents??{}))Ie(t,s)}async handleAgentPermissionResponse(e){const t=typeof e.params.sessionId=="string"?e.params.sessionId:"",s=typeof e.params.permissionId=="string"?e.params.permissionId:"",a=typeof e.params.optionId=="string"?e.params.optionId:"",i=this.sessions.get(t);if(!t||!s||!a||!i?.adapter.respondToPermission){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"permission_request_unavailable"});return}try{await i.adapter.respondToPermission(s,a),this.client.sendRes({type:"res",id:e.id,ok:!0,payload:{accepted:!0}})}catch(d){this.client.sendRes({type:"res",id:e.id,ok:!1,error:d instanceof Error?d.message:"permission_response_failed"})}}async handleAcpAgentRequest(e){const t=typeof e.params.agentType=="string"?e.params.agentType:"";if(!t.startsWith("acp:")||!S(t)){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"invalid_acp_agent"});return}const s=e.method==="agent.auth.start"?[...this.sessions.values()].find(r=>r.agentType===t&&r.adapter.hasPendingAuthenticationRetry?.())?.adapter:void 0;if(String(e.method)==="agent.session-options.get"){const r=typeof e.params.sessionId=="string"?e.params.sessionId:"",o=this.sessions.get(r);if(!o||o.agentType!==t||!o.adapter.getSessionOptions){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"acp_session_not_active"});return}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:o.adapter.getSessionOptions()});return}const a=s??E(t);if(!a){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"acp_agent_not_installed"});return}const i=typeof e.params.cwd=="string"&&e.params.cwd?e.params.cwd:process.cwd(),d=!s;try{d&&await a.start(`acp-control-${g.randomUUID()}`,i);let r={ok:!0};if(e.method==="agent.external-sessions.list")r={sessions:await a.listExternalSessions?.({cwd:typeof e.params.cwd=="string"?e.params.cwd:void 0})??[]};else if(e.method==="agent.external-sessions.import"){if(!a.loadExternalSession)throw new Error("ACP session history import is unsupported");const o=String(e.params.agentSessionId??""),h=String(e.params.sessionId??"");if(!o||!h)throw new Error("ACP and Shennian session ids are required");const n=await a.loadExternalSession(o),c=Date.now();for(const[p,m]of n.entries()){const P=`acp-import-${g.createHash("sha256").update(`${t}\0${o}\0${m.sourceId}`).digest("hex")}`;A(h,{id:P,sessionId:h,role:m.role,ts:c+p,payload:m.payload})}k({sessionId:h,agentType:t,workDir:i,agentSessionId:o,title:typeof e.params.title=="string"?e.params.title.slice(0,120):void 0,titleSource:typeof e.params.title=="string"?"agent_native":void 0,status:"active"}),T(this.client),this.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:h,agentType:t,agentSessionId:o,workDir:i,status:"active"}}}),r={sessionId:h,agentSessionId:o,importedMessageCount:n.length}}else if(e.method==="agent.auth.start"){if(!a.authenticate)throw new Error("ACP authentication is unsupported");await a.authenticate(String(e.params.methodId??""))}else if(e.method==="agent.auth.logout"){if(!a.logout)throw new Error("ACP logout is unsupported");await a.logout()}else{const o=String(e.params.agentSessionId??"");if(!o)throw new Error("ACP session id is required");if(e.method==="agent.external-sessions.close"){if(!a.closeExternalSession)throw new Error("ACP session close is unsupported");await a.closeExternalSession(o)}else{if(!a.deleteExternalSession)throw new Error("ACP session delete is unsupported");await a.deleteExternalSession(o)}}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:r})}catch(r){this.client.sendRes({type:"res",id:e.id,ok:!1,error:r instanceof Error?r.message:"acp_request_failed"})}finally{d&&await a.stop().catch(()=>{})}}async handleReq(e,t={}){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":u(e.params),this.recoveryBarrier.isDispatchFrozen()||this.recoveryBarrier.isRecovering(String(e.params.sessionId??""))?await this.chatQueue.handleEnqueue({...e,method:"chat.enqueue"}):await f(s,e);break;case"chat.enqueue":u(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":u(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":u(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":u(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":u(e.params),await V(s,e);break;case"session.refresh":u(e.params),await L(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":K(s,e);break;case"session.tool.detail":await G(s,e);break;case"voice.session.body.window":Z(s,e);break;case"session.title.set":await Y(s,e);break;case"room.agent.bind-managed":await ke(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await Ae(s,this.managedRoomBinder,e);break;case"fs.ls":await se(s,e);break;case"fs.read":await te(s,e);break;case"fs.write":await ne(s,e);break;case"fs.export.markdown-pdf":await de(s,e);break;case"fs.export.markdown-pdf.setup":await le(s,e);break;case"fs.archive.zip":await he(s,e);break;case"fs.rename":await ae(s,e);break;case"fs.transfer.start":await ce(s,e);break;case"fs.transfer.chunk":await re(s,e);break;case"fs.transfer.finish":await oe(s,e);break;case"fs.transfer.abort":await ie(s,e);break;case"region.probe":await ue(s,e);break;case"region.switch":await pe(s,e);break;case"upgrade.start":await O(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0,prepareForUpgrade:a=>this.prepareForUpgrade(a),cancelUpgradePreparation:()=>this.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()}});break;case"upgrade.status":await F(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await me(s,e);break;case"machine.remote-access.pause":await q(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await $(s,e);break;case"agent.permission.respond":await this.handleAgentPermissionResponse(e);break;case"agent.auth.start":case"agent.auth.logout":case"agent.session-options.get":case"agent.external-sessions.list":case"agent.external-sessions.import":case"agent.external-sessions.close":case"agent.external-sessions.delete":await this.handleAcpAgentRequest(e);break;case"models.refresh":await U(s,e);break;case"agent.config.get":await W(s,e);break;case"agent.config.upsert":await j(s,e);break;case"agent.config.clear":await N(s,e);break;case"agent.config.test":await J(s,e);break;case"agent.workspace.validate":await X(s,e);break;case"agent.capabilities.list":await B(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(a){if(t.propagateErrors)throw a;this.client.sendRes({type:"res",id:e.id,ok:!1,error:a instanceof Error?a.message:String(a)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<We)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(this.revokeManagedRoomToken(e.session),e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return Se(e)}resolveAuthorizedPath(e,t){return we(e,ve(t))}async resolveAuthorizedPathAsync(e,t){return be(e,await Re(t))}async cleanup(){this.acpDiscoveryWatcher.stop(),this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),ee(this.getRuntime()),await this.managerRuntime.stop(),I(null)}revokeManagedRoomToken(e){const t=e.managedRoomContextToken;e.managedRoomContextToken=null,t&&this.managedRoomContextRevoker?.(t)}}export{Us as SessionManager,Bs as resolveSessionWorkDir};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentRecoveryResult } from '../../agents/adapter.js';
|
|
2
2
|
import type { ActiveRunJournal } from './journal.js';
|
|
3
3
|
import type { RunRecoveryBarrier } from './barrier.js';
|
|
4
|
-
import type
|
|
4
|
+
import { type RunJournalRecord } from './schema.js';
|
|
5
5
|
export type RecoveryTerminal = Extract<AgentRecoveryResult, {
|
|
6
6
|
result: 'interrupted_by_upgrade' | 'recovery_failed';
|
|
7
7
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{isRecoverableJournalPhase as l}from"./schema.js";const h=new Set(["continue_dispatch_outcome_unknown","provider_session_missing","provider_identity_mismatch","authentication_required","permission_context_unavailable","provider_error"]);function c(d){return typeof d=="string"&&h.has(d)}class v{deps;pendingBySession=new Map;constructor(e){this.deps=e}async recoverAll(){const e=this.deps.journal.loadPendingReconciliation().records,t=new Map;for(const s of e){this.deps.barrier.begin(s.sessionId);const i=this.pendingBySession.get(s.sessionId)??new Set;i.add(s.runId),this.pendingBySession.set(s.sessionId,i);const a=t.get(s.sessionId)??[];a.push(s),t.set(s.sessionId,a)}const r=[...t.values()].filter(s=>s.some(i=>i.source!=="manager_worker")),p=[...t.values()].filter(s=>s.every(i=>i.source==="manager_worker")),n=s=>Promise.all(s.map(async i=>{const a=i.filter(o=>l(o.phase)),u=a.at(-1);for(const o of a.slice(0,-1))this.completeFromProvider(o.sessionId,o.runId);for(const o of i)l(o.phase)&&o!==u||await this.recoverOne(o)}));await n(r),await n(p)}completeFromProvider(e,t){this.deps.journal.remove(e,t);const r=this.pendingBySession.get(e);r?.delete(t),(!r||r.size===0)&&(this.pendingBySession.delete(e),this.deps.barrier.end(e))}async recoverOne(e){if(this.deps.isCanonicalTerminal?.(e)){this.completeFromProvider(e.sessionId,e.runId);return}if(e.phase==="aborted"){this.completeFromProvider(e.sessionId,e.runId);return}if(e.phase==="interrupted_by_upgrade"||e.phase==="recovery_failed"){const s=e.phase==="interrupted_by_upgrade"?{result:"interrupted_by_upgrade",reason:e.recoveryReason==="missing_agent_session"?"missing_agent_session":"resume_unsupported"}:{result:"recovery_failed",reason:c(e.recoveryReason)?e.recoveryReason:"provider_error"};await this.deps.commitTerminal(e,s),this.completeFromProvider(e.sessionId,e.runId);return}const t=e.recoveryAttempts+1,r=Date.now();let p=this.deps.journal.update(e.sessionId,e.runId,{phase:"recovering",recoveryAttempts:t})??e,n;try{n=await this.deps.recover(p,()=>{p=this.deps.journal.update(e.sessionId,e.runId,{continueDispatchState:"intent"})??p})}catch(s){const i=this.deps.journal.get(e.sessionId,e.runId);if(i?.phase==="interrupted_by_upgrade"||i?.phase==="recovery_failed")throw s;const a=this.deps.maxAttempts??3;if(t<a){this.deps.journal.update(e.sessionId,e.runId,{phase:"running"});const u=this.deps.journal.get(e.sessionId,e.runId);if(u)return this.recoverOne(u)}this.deps.onOutcome?.({adapterType:e.agentType,source:e.source,result:"recovery_failed",attempts:t,durationMs:Date.now()-r,reason:"provider_error"}),await this.finishTerminal(e,{result:"recovery_failed",reason:"provider_error",message:s instanceof Error?s.message.slice(0,500):String(s).slice(0,500)});return}if(this.deps.onOutcome?.({adapterType:e.agentType,source:e.source,result:n.result,attempts:t,durationMs:Date.now()-r,..."reason"in n?{reason:n.reason}:{}}),n.result==="continued"){this.deps.journal.update(e.sessionId,e.runId,{continueDispatchState:"accepted",phase:"running"});return}if(n.result==="reattached"||n.result==="resumed"){this.deps.journal.update(e.sessionId,e.runId,{phase:"running"});return}if(n.result==="terminal_reconciled"){this.completeFromProvider(e.sessionId,e.runId);return}await this.finishTerminal(e,n)}async finishTerminal(e,t){this.deps.journal.update(e.sessionId,e.runId,{phase:t.result,recoveryReason:t.reason}),await this.deps.commitTerminal(e,t),this.completeFromProvider(e.sessionId,e.runId)}}export{v as RunRecoveryCoordinator};
|
|
@@ -21,6 +21,7 @@ export type LocalSessionRecord = {
|
|
|
21
21
|
lastMessagePreview?: string | null;
|
|
22
22
|
lastMessagePreviewMessageId: string | null;
|
|
23
23
|
lastMessagePreviewAt: string | null;
|
|
24
|
+
lastVisibleEventAt?: string | null;
|
|
24
25
|
localReady: boolean;
|
|
25
26
|
firstUserMessageId: string | null;
|
|
26
27
|
firstUserBodyRevision: number | null;
|
|
@@ -70,6 +71,7 @@ export declare function recordSession(input: {
|
|
|
70
71
|
lastMessagePreview?: string | null;
|
|
71
72
|
lastMessagePreviewMessageId?: string | null;
|
|
72
73
|
lastMessagePreviewAt?: string | null;
|
|
74
|
+
lastVisibleEventAt?: string | null;
|
|
73
75
|
localReady?: boolean;
|
|
74
76
|
firstUserMessageId?: string | null;
|
|
75
77
|
firstUserBodyRevision?: number | null;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import b from"node:crypto";import d from"node:fs";import x from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as A,SESSION_SYNC_DATA_EPOCH as I,SESSION_TITLE_SOURCE_PRIORITY as v,sessionPreviewFromPayload as C}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as k,resolvePersonalSyncV1Path as T}from"../personal-sync/paths.js";import{CanonicalSessionStore as N}from"./canonical-store.js";import{getDaemonInstallationId as u}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as B}from"../native-fusion/native-source-deletions.js";const P=120;function p(t){if(t.length<=P)return t;let e=t.slice(0,P);const n=e.charCodeAt(e.length-1);return n>=55296&&n<=56319&&(e=e.slice(0,-1)),e}let w=null,O=null;function R(){return T()}function g(){const t=x.join(R(),"canonical");return(!w||O!==t)&&(w=new N(t,u()),O=t),w}function h(){return x.join(R(),"session-index.json")}function c(){const t=h();if(!d.existsSync(t))return{};const e=JSON.parse(d.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);const n=u();for(const o of Object.values(e))if(o.daemonInstallationId!==n)throw new Error(`personal session index belongs to another daemon installation: ${t}`);return e}function S(t){W(h(),t)}function E(t){return C(t.payload,120)}function y(t){if(new B({rootDir:R(),daemonInstallationId:u()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function U(t){y(t.sessionId),u();const e=c(),n=e[t.sessionId],o=new Date().toISOString(),i=g().getManifest(t.sessionId)?.bodyRevision??n?.bodyRevision??0,s=g().getManifest(t.sessionId),a=j(n,t),l=t.attentionId?.trim()||null,r=Array.from(new Set([...n?.seenAttentionIds??[],...n?.lastAttentionId?[n.lastAttentionId]:[]])),f=!!(l&&!r.includes(l)),m=t.lastMessagePreview===null?{preview:null,messageId:null,at:null}:t.lastMessagePreview!==void 0&&t.lastMessagePreviewMessageId&&t.lastMessagePreviewAt?{preview:t.lastMessagePreview,messageId:t.lastMessagePreviewMessageId,at:t.lastMessagePreviewAt}:{preview:n?.lastMessagePreview??null,messageId:n?.lastMessagePreviewMessageId??null,at:n?.lastMessagePreviewAt??null},M={daemonInstallationId:u(),sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??n?.sessionMode,managerConfig:t.managerConfig??n?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??n?.agentSessionId??null,modelId:t.modelId??n?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??n?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??n?.managerDefaultWorkerModelId??null,status:t.status??n?.status??"active",createdAt:n?.createdAt??o,updatedAt:o,lastActivityAt:t.lastActivityAt??n?.lastActivityAt??o,lastMessagePreview:m.preview,lastMessagePreviewMessageId:m.messageId,lastMessagePreviewAt:m.at,localReady:n?.localReady===!0||t.localReady===!0,firstUserMessageId:n?.firstUserMessageId??t.firstUserMessageId??null,firstUserBodyRevision:n?.firstUserBodyRevision??t.firstUserBodyRevision??null,bodyRevision:i,indexRevision:(n?.indexRevision??0)+1,...a,firstAvailableSeq:s?.firstAvailableSeq??n?.firstAvailableSeq??null,visibleMessageCount:s?.visibleMessageCount??n?.visibleMessageCount??0,lastVisibleMessageId:s?.lastVisibleMessageId??n?.lastVisibleMessageId??null,attentionCount:(n?.attentionCount??0)+(f?1:0),lastAttentionId:f?l:n?.lastAttentionId??null,seenAttentionIds:f&&l?[...r,l]:r,lastBodyCommittedAt:s?.lastBodyCommittedAt??n?.lastBodyCommittedAt??null,deletionRevision:n?.deletionRevision??0,deletedAt:t.deletedAt??n?.deletedAt??null,syncedIndexRevision:n?.syncedIndexRevision??0,indexSyncState:"pending",indexSyncStateRevision:(n?.indexRevision??0)+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,indexQuarantineCode:null,titleGenerationAttemptedAt:n?.titleGenerationAttemptedAt&&n.titleGenerationNextAttemptAt!==void 0?n.titleGenerationAttemptedAt:null,titleGenerationNextAttemptAt:n?.titleGenerationNextAttemptAt??null,titleGenerationAttempts:n?.titleGenerationAttempts??0};return e[t.sessionId]=M,S(e),M}function G(){return Object.values(c())}function K(t){if(!t)throw new Error("sessionId is required");const e=c();e[t]&&(delete e[t],S(e))}function Z(t){g().purgeSession(t)}function L(){const t=c();let e=!1;const n=new Date().toISOString();for(const[o,i]of Object.entries(t)){const s=p(i.title);if(s===i.title)continue;const a=i.indexRevision+1;t[o]={...i,title:s,indexRevision:a,indexSyncState:"pending",indexSyncStateRevision:a,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,indexQuarantineCode:null,updatedAt:n},e=!0}e&&S(t)}function ee(){const t=u();return V(),L(),G().filter(e=>{const n=e.indexSyncStateRevision===e.indexRevision;return e.indexQuarantineCode||n&&(e.indexSyncState==="quarantined"||e.indexSyncState==="terminal")||n&&e.indexSyncNextAttemptAt&&Date.parse(e.indexSyncNextAttemptAt)>Date.now()?!1:e.indexRevision>e.syncedIndexRevision&&e.localReady&&!!e.firstUserMessageId&&e.firstUserBodyRevision!=null}).sort((e,n)=>e.indexRevision-n.indexRevision).map(e=>{const n=!!(e.lastMessagePreview&&e.lastMessagePreviewMessageId&&e.lastMessagePreviewAt);return{protocolVersion:A,dataEpoch:I,daemonInstallationId:t,session:{id:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId??null,title:e.title,titleSource:e.titleSource,titleRevision:e.titleRevision,titleLockedByUser:e.titleLockedByUser,titleOriginEventId:e.titleOriginEventId,status:e.status??"active",workDir:e.workDir,lastMessagePreview:n?e.lastMessagePreview:null,lastMessagePreviewMessageId:n?e.lastMessagePreviewMessageId:null,lastMessagePreviewAt:n?e.lastMessagePreviewAt:null,bodyStorageMode:"machine_local",localReady:!0,firstUserBoundary:{role:"user",messageId:e.firstUserMessageId,bodyRevision:e.firstUserBodyRevision},bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,attentionCount:e.attentionCount??0,lastAttentionId:e.lastAttentionId??null,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}})}function te(t,e){const n=c(),o=n[t];if(!(!o||e<=o.syncedIndexRevision)){if(e>o.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);n[t]={...o,syncedIndexRevision:e,indexSyncState:"applied",indexSyncStateRevision:e,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null},S(n)}}function ne(){const t=c();let e=0;const n=new Date().toISOString();for(const[o,i]of Object.entries(t)){if(i.deletedAt||i.indexQuarantineCode||!i.localReady)continue;const s=i.indexRevision+1;t[o]={...i,indexRevision:s,indexSyncState:"pending",indexSyncStateRevision:s,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e+=1}return e>0&&S(t),e}function ie(t){const e=c(),n=e[t.sessionId];if(!n||n.indexRevision!==t.indexRevision)return"terminal";const i=(n.indexSyncStateRevision===t.indexRevision?n.indexSyncAttempts??0:0)+1;let s=t.disposition;s==="retry"&&i>=(t.maxAttempts??8)&&(s="terminal");const a=t.now??Date.now(),l=Math.min(5e3*2**Math.max(0,i-1),360*60*1e3),r=Math.round(l*.2*(Math.random()*2-1));return e[t.sessionId]={...n,indexSyncState:s==="retry"?"pending":s,indexSyncStateRevision:t.indexRevision,indexSyncAttempts:i,indexSyncNextAttemptAt:s==="retry"?new Date(a+l+r).toISOString():null,indexSyncErrorCode:t.code,indexQuarantineCode:s==="quarantined"?t.code:n.indexQuarantineCode??null},S(e),s}const q=2*6e4;function se(t=Date.now(),e=q){const n=c(),o=[],i=new Date(t).toISOString();for(const s of Object.values(n)){if(s.titleSource!=="first_user_fallback"||s.titleLockedByUser||s.titleGenerationAttemptedAt||!s.title.trim()||s.syncedIndexRevision<s.indexRevision||s.titleGenerationNextAttemptAt&&Date.parse(s.titleGenerationNextAttemptAt)>t)continue;const a=s.status==="completed"||s.status==="failed",l=s.visibleMessageCount>=2;let r=null;if(s.agentType==="claude"&&l?r="agent_native_unavailable":a?r="terminal":l&&t-Date.parse(s.createdAt)>=e&&(r="grace_elapsed"),!r)continue;const f=C(s.title,240);if(!f)continue;const m=b.randomUUID();n[s.sessionId]={...s,titleGenerationAttemptedAt:i,titleGenerationNextAttemptAt:null,titleGenerationAttempts:(s.titleGenerationAttempts??0)+1},o.push({sessionId:s.sessionId,requestId:m,titleSeed:f,baseTitleRevision:s.titleRevision,eligibility:r})}return o.length>0&&S(n),o}function oe(t,e=Date.now()){const n=c(),o=n[t];if(!o?.titleGenerationAttemptedAt||o.titleSource!=="first_user_fallback")return null;const i=Math.max(1,o.titleGenerationAttempts??1),s=Math.min(10*6e4,3e4*2**Math.min(i-1,5)),a=new Date(e+s).toISOString();return n[t]={...o,titleGenerationAttemptedAt:null,titleGenerationNextAttemptAt:a},S(n),a}function ae(t,e){const o=c()[t];return!o||o.titleLockedByUser||v[o.titleSource]>=v.shennian_generated||e.titleRevision<=o.titleRevision?!1:(U({sessionId:t,agentType:o.agentType,sessionMode:o.sessionMode,managerConfig:o.managerConfig,workDir:o.workDir,agentSessionId:o.agentSessionId,modelId:o.modelId,status:o.status,title:e.title,titleSource:"shennian_generated",titleRevision:e.titleRevision,titleLockedByUser:!1,titleOriginEventId:e.titleOriginEventId}),!0)}function le(t,e,n){y(t),u();const o=g().append(t,e,n);return D(t,e,o.bodyRevision),o.bodyRevision}function re(t,e,n){y(t),u();const o=g().upsert(t,e,n);return D(t,e,o.bodyRevision),o.bodyRevision}function de(t,e){const n=g().getMessageState(t,e);return!n||n.tombstoned||!n.message||!n.deliveryState?{protocolVersion:A,dataEpoch:I,daemonInstallationId:u(),state:"not_found",sessionId:t,clientMessageId:e}:{protocolVersion:A,dataEpoch:I,daemonInstallationId:u(),state:n.deliveryState,sessionId:t,clientMessageId:e,messageId:n.message.id,deliveryState:n.deliveryState,bodyRevision:n.lastRevision,committedAt:new Date(n.message.ts).toISOString()}}function ce(t,e){y(t),u();const n=g().tombstone(t,e),o=c(),i=o[t];if(i){const s=e===i.lastMessagePreviewMessageId?_(t):{preview:i.lastMessagePreview??null,messageId:i.lastMessagePreviewMessageId??null,at:i.lastMessagePreviewAt??null};o[t]={...i,bodyRevision:n.bodyRevision,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastMessagePreview:s.preview,lastMessagePreviewMessageId:s.messageId,lastMessagePreviewAt:s.at},S(o)}return n.bodyRevision}function D(t,e,n){const o=c(),i=o[t];if(!i)return;const s=E(e),a=s?{lastMessagePreview:s,lastMessagePreviewMessageId:e.id,lastMessagePreviewAt:new Date(e.ts).toISOString()}:null,l=g().getManifest(t),r=e.role==="user"&&s&&!i.title?{title:p(s),titleSource:"first_user_fallback",titleRevision:i.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null,f=e.role==="user"&&!i.localReady?{localReady:!0,firstUserMessageId:e.id,firstUserBodyRevision:n}:null;o[t]={...i,...r??{},...f??{},bodyRevision:n,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),...a??{},firstAvailableSeq:l?.firstAvailableSeq??i.firstAvailableSeq,visibleMessageCount:l?.visibleMessageCount??i.visibleMessageCount,lastVisibleMessageId:l?.lastVisibleMessageId??i.lastVisibleMessageId,attentionCount:i.attentionCount??0,lastAttentionId:i.lastAttentionId??null,lastBodyCommittedAt:l?.lastBodyCommittedAt??i.lastBodyCommittedAt},S(o)}function _(t){let e;do{const n=g().readPage(t,{limit:500,beforeSequence:e});for(let o=n.entries.length-1;o>=0;o-=1){const i=n.entries[o],s=E(i.message);if(s)return{preview:s,messageId:i.message.id,at:new Date(i.message.ts).toISOString()}}if(!n.hasOlder||n.entries.length===0)break;e=n.entries[0].sequence}while(e!==void 0);return{preview:null,messageId:null,at:null}}function V(){const t=c();let e=!1;const n=new Date().toISOString();for(const[o,i]of Object.entries(t)){if(Object.prototype.hasOwnProperty.call(i,"lastMessagePreviewMessageId")&&Object.prototype.hasOwnProperty.call(i,"lastMessagePreviewAt"))continue;const s=_(o);t[o]={...i,lastMessagePreview:s.preview,lastMessagePreviewMessageId:s.messageId,lastMessagePreviewAt:s.at,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e=!0}e&&S(t)}function j(t,e){const n={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},o=p(e.title?.trim()??"");if(!o)return n;if(!e.titleSource)throw new Error("titleSource is required with title");if(n.title&&n.titleSource==="first_user_fallback"&&e.titleSource==="first_user_fallback")return n;const i=e.titleSource==="shennian_manual";if(e.titleLockedByUser!==void 0&&e.titleLockedByUser!==i)throw new Error("titleLockedByUser conflicts with titleSource");const s=o===n.title&&e.titleSource===n.titleSource,a=e.titleRevision??n.titleRevision+(s?0:1);if(!Number.isSafeInteger(a)||a<0)throw new Error("invalid titleRevision");const l=v[e.titleSource],r=v[n.titleSource];return l<r||a<n.titleRevision||s&&a===n.titleRevision||a===n.titleRevision&&!s?n:{title:o,titleSource:e.titleSource,titleRevision:a,titleLockedByUser:i,titleOriginEventId:e.titleOriginEventId??n.titleOriginEventId}}function ue(t,e){const n=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let i=g().readRecent(t,n).messages;return e?.before!==void 0&&(i=i.filter(s=>s.ts<e.before)),[...i].sort((s,a)=>a.ts-s.ts)}function ge(){return g().listSessionIds()}function Se(){return g()}function W(t,e){const n=x.dirname(t);d.mkdirSync(n,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${b.randomBytes(6).toString("hex")}`;let i;try{if(i=d.openSync(o,"wx",384),d.writeFileSync(i,`${JSON.stringify(e,null,2)}
|
|
2
|
-
`,"utf8"),
|
|
1
|
+
import O from"node:crypto";import c from"node:fs";import A from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as x,SESSION_SYNC_DATA_EPOCH as I,SESSION_TITLE_SOURCE_PRIORITY as m,isToolPayload as P,sessionPreviewFromPayload as h}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as V,resolvePersonalSyncV1Path as G}from"../personal-sync/paths.js";import{CanonicalSessionStore as L}from"./canonical-store.js";import{getDaemonInstallationId as g}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as q}from"../native-fusion/native-source-deletions.js";const D=120;function p(t){if(t.length<=D)return t;let e=t.slice(0,D);const n=e.charCodeAt(e.length-1);return n>=55296&&n<=56319&&(e=e.slice(0,-1)),e}let w=null,_=null;function R(){return G()}function u(){const t=A.join(R(),"canonical");return(!w||_!==t)&&(w=new L(t,g()),_=t),w}function k(){return A.join(R(),"session-index.json")}function d(){const t=k();if(!c.existsSync(t))return{};const e=JSON.parse(c.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);const n=g();for(const s of Object.values(e))if(s.daemonInstallationId!==n)throw new Error(`personal session index belongs to another daemon installation: ${t}`);return e}function S(t){J(k(),t)}function M(t){return h(t.payload,120)}function y(t){if(new q({rootDir:R(),daemonInstallationId:g()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function j(t){y(t.sessionId),g();const e=d(),n=e[t.sessionId],s=new Date().toISOString(),i=u().getManifest(t.sessionId)?.bodyRevision??n?.bodyRevision??0,o=u().getManifest(t.sessionId),a=z(n,t),r=t.attentionId?.trim()||null,l=Array.from(new Set([...n?.seenAttentionIds??[],...n?.lastAttentionId?[n.lastAttentionId]:[]])),f=!!(r&&!l.includes(r)),v=t.lastMessagePreview===null?{preview:null,messageId:null,at:null}:t.lastMessagePreview!==void 0&&t.lastMessagePreviewMessageId&&t.lastMessagePreviewAt?{preview:t.lastMessagePreview,messageId:t.lastMessagePreviewMessageId,at:t.lastMessagePreviewAt}:{preview:n?.lastMessagePreview??null,messageId:n?.lastMessagePreviewMessageId??null,at:n?.lastMessagePreviewAt??null},b=Date.parse(t.lastVisibleEventAt??""),E=Date.parse(n?.lastVisibleEventAt??""),U=Number.isFinite(b)&&(!Number.isFinite(E)||b>=E)?t.lastVisibleEventAt:n?.lastVisibleEventAt??null,C={daemonInstallationId:g(),sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??n?.sessionMode,managerConfig:t.managerConfig??n?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??n?.agentSessionId??null,modelId:t.modelId??n?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??n?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??n?.managerDefaultWorkerModelId??null,status:t.status??n?.status??"active",createdAt:n?.createdAt??s,updatedAt:s,lastActivityAt:t.lastActivityAt??n?.lastActivityAt??s,lastMessagePreview:v.preview,lastMessagePreviewMessageId:v.messageId,lastMessagePreviewAt:v.at,lastVisibleEventAt:U,localReady:n?.localReady===!0||t.localReady===!0,firstUserMessageId:n?.firstUserMessageId??t.firstUserMessageId??null,firstUserBodyRevision:n?.firstUserBodyRevision??t.firstUserBodyRevision??null,bodyRevision:i,indexRevision:(n?.indexRevision??0)+1,...a,firstAvailableSeq:o?.firstAvailableSeq??n?.firstAvailableSeq??null,visibleMessageCount:o?.visibleMessageCount??n?.visibleMessageCount??0,lastVisibleMessageId:o?.lastVisibleMessageId??n?.lastVisibleMessageId??null,attentionCount:(n?.attentionCount??0)+(f?1:0),lastAttentionId:f?r:n?.lastAttentionId??null,seenAttentionIds:f&&r?[...l,r]:l,lastBodyCommittedAt:o?.lastBodyCommittedAt??n?.lastBodyCommittedAt??null,deletionRevision:n?.deletionRevision??0,deletedAt:t.deletedAt??n?.deletedAt??null,syncedIndexRevision:n?.syncedIndexRevision??0,indexSyncState:"pending",indexSyncStateRevision:(n?.indexRevision??0)+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,indexQuarantineCode:null,titleGenerationAttemptedAt:n?.titleGenerationAttemptedAt&&n.titleGenerationNextAttemptAt!==void 0?n.titleGenerationAttemptedAt:null,titleGenerationNextAttemptAt:n?.titleGenerationNextAttemptAt??null,titleGenerationAttempts:n?.titleGenerationAttempts??0};return e[t.sessionId]=C,S(e),C}function F(){return Object.values(d())}function se(t){if(!t)throw new Error("sessionId is required");const e=d();e[t]&&(delete e[t],S(e))}function oe(t){u().purgeSession(t)}function W(){const t=d();let e=!1;const n=new Date().toISOString();for(const[s,i]of Object.entries(t)){const o=p(i.title);if(o===i.title)continue;const a=i.indexRevision+1;t[s]={...i,title:o,indexRevision:a,indexSyncState:"pending",indexSyncStateRevision:a,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,indexQuarantineCode:null,updatedAt:n},e=!0}e&&S(t)}function ae(){const t=g();return Y(),Q(),W(),F().filter(e=>{const n=e.indexSyncStateRevision===e.indexRevision;return e.indexQuarantineCode||n&&(e.indexSyncState==="quarantined"||e.indexSyncState==="terminal")||n&&e.indexSyncNextAttemptAt&&Date.parse(e.indexSyncNextAttemptAt)>Date.now()?!1:e.indexRevision>e.syncedIndexRevision&&e.localReady&&!!e.firstUserMessageId&&e.firstUserBodyRevision!=null}).sort((e,n)=>e.indexRevision-n.indexRevision).map(e=>{const n=!!(e.lastMessagePreview&&e.lastMessagePreviewMessageId&&e.lastMessagePreviewAt);return{protocolVersion:x,dataEpoch:I,daemonInstallationId:t,session:{id:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId??null,title:e.title,titleSource:e.titleSource,titleRevision:e.titleRevision,titleLockedByUser:e.titleLockedByUser,titleOriginEventId:e.titleOriginEventId,status:e.status??"active",workDir:e.workDir,lastMessagePreview:n?e.lastMessagePreview:null,lastMessagePreviewMessageId:n?e.lastMessagePreviewMessageId:null,lastMessagePreviewAt:n?e.lastMessagePreviewAt:null,lastVisibleEventAt:e.lastVisibleEventAt??e.lastMessagePreviewAt??null,bodyStorageMode:"machine_local",localReady:!0,firstUserBoundary:{role:"user",messageId:e.firstUserMessageId,bodyRevision:e.firstUserBodyRevision},bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,attentionCount:e.attentionCount??0,lastAttentionId:e.lastAttentionId??null,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}})}function le(t,e){const n=d(),s=n[t];if(!(!s||e<=s.syncedIndexRevision)){if(e>s.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);n[t]={...s,syncedIndexRevision:e,indexSyncState:"applied",indexSyncStateRevision:e,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null},S(n)}}function re(){const t=d();let e=0;const n=new Date().toISOString();for(const[s,i]of Object.entries(t)){if(i.deletedAt||i.indexQuarantineCode||!i.localReady)continue;const o=i.indexRevision+1;t[s]={...i,indexRevision:o,indexSyncState:"pending",indexSyncStateRevision:o,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e+=1}return e>0&&S(t),e}function de(t){const e=d(),n=e[t.sessionId];if(!n||n.indexRevision!==t.indexRevision)return"terminal";const i=(n.indexSyncStateRevision===t.indexRevision?n.indexSyncAttempts??0:0)+1;let o=t.disposition;o==="retry"&&i>=(t.maxAttempts??8)&&(o="terminal");const a=t.now??Date.now(),r=Math.min(5e3*2**Math.max(0,i-1),360*60*1e3),l=Math.round(r*.2*(Math.random()*2-1));return e[t.sessionId]={...n,indexSyncState:o==="retry"?"pending":o,indexSyncStateRevision:t.indexRevision,indexSyncAttempts:i,indexSyncNextAttemptAt:o==="retry"?new Date(a+r+l).toISOString():null,indexSyncErrorCode:t.code,indexQuarantineCode:o==="quarantined"?t.code:n.indexQuarantineCode??null},S(e),o}const $=2*6e4;function ce(t=Date.now(),e=$){const n=d(),s=[],i=new Date(t).toISOString();for(const o of Object.values(n)){if(o.titleSource!=="first_user_fallback"||o.titleLockedByUser||o.titleGenerationAttemptedAt||!o.title.trim()||o.syncedIndexRevision<o.indexRevision||o.titleGenerationNextAttemptAt&&Date.parse(o.titleGenerationNextAttemptAt)>t)continue;const a=o.status==="completed"||o.status==="failed",r=o.visibleMessageCount>=2;let l=null;if(o.agentType==="claude"&&r?l="agent_native_unavailable":a?l="terminal":r&&t-Date.parse(o.createdAt)>=e&&(l="grace_elapsed"),!l)continue;const f=h(o.title,240);if(!f)continue;const v=O.randomUUID();n[o.sessionId]={...o,titleGenerationAttemptedAt:i,titleGenerationNextAttemptAt:null,titleGenerationAttempts:(o.titleGenerationAttempts??0)+1},s.push({sessionId:o.sessionId,requestId:v,titleSeed:f,baseTitleRevision:o.titleRevision,eligibility:l})}return s.length>0&&S(n),s}function ue(t,e=Date.now()){const n=d(),s=n[t];if(!s?.titleGenerationAttemptedAt||s.titleSource!=="first_user_fallback")return null;const i=Math.max(1,s.titleGenerationAttempts??1),o=Math.min(10*6e4,3e4*2**Math.min(i-1,5)),a=new Date(e+o).toISOString();return n[t]={...s,titleGenerationAttemptedAt:null,titleGenerationNextAttemptAt:a},S(n),a}function ge(t,e){const s=d()[t];return!s||s.titleLockedByUser||m[s.titleSource]>=m.shennian_generated||e.titleRevision<=s.titleRevision?!1:(j({sessionId:t,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId,modelId:s.modelId,status:s.status,title:e.title,titleSource:"shennian_generated",titleRevision:e.titleRevision,titleLockedByUser:!1,titleOriginEventId:e.titleOriginEventId}),!0)}function Se(t,e,n){y(t),g();const s=u().append(t,e,n);return T(t,e,s.bodyRevision),s.bodyRevision}function fe(t,e,n){y(t),g();const s=u().upsert(t,e,n);return T(t,e,s.bodyRevision),s.bodyRevision}function ve(t,e){const n=u().getMessageState(t,e);return!n||n.tombstoned||!n.message||!n.deliveryState?{protocolVersion:x,dataEpoch:I,daemonInstallationId:g(),state:"not_found",sessionId:t,clientMessageId:e}:{protocolVersion:x,dataEpoch:I,daemonInstallationId:g(),state:n.deliveryState,sessionId:t,clientMessageId:e,messageId:n.message.id,deliveryState:n.deliveryState,bodyRevision:n.lastRevision,committedAt:new Date(n.message.ts).toISOString()}}function me(t,e){y(t),g();const n=u().tombstone(t,e),s=d(),i=s[t];if(i){const o=e===i.lastMessagePreviewMessageId?N(t):{preview:i.lastMessagePreview??null,messageId:i.lastMessagePreviewMessageId??null,at:i.lastMessagePreviewAt??null};s[t]={...i,bodyRevision:n.bodyRevision,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at,lastVisibleEventAt:B(t)},S(s)}return n.bodyRevision}function T(t,e,n){const s=d(),i=s[t];if(!i)return;const o=M(e),a=o?{lastMessagePreview:o,lastMessagePreviewMessageId:e.id,lastMessagePreviewAt:new Date(e.ts).toISOString()}:null,r=o||P(e.payload)?new Date(e.ts).toISOString():i.lastVisibleEventAt,l=u().getManifest(t),f=e.role==="user"&&o&&!i.title?{title:p(o),titleSource:"first_user_fallback",titleRevision:i.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null,v=e.role==="user"&&!i.localReady?{localReady:!0,firstUserMessageId:e.id,firstUserBodyRevision:n}:null;s[t]={...i,...f??{},...v??{},bodyRevision:n,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),lastVisibleEventAt:r,...a??{},firstAvailableSeq:l?.firstAvailableSeq??i.firstAvailableSeq,visibleMessageCount:l?.visibleMessageCount??i.visibleMessageCount,lastVisibleMessageId:l?.lastVisibleMessageId??i.lastVisibleMessageId,attentionCount:i.attentionCount??0,lastAttentionId:i.lastAttentionId??null,lastBodyCommittedAt:l?.lastBodyCommittedAt??i.lastBodyCommittedAt},S(s)}function N(t){let e;do{const n=u().readPage(t,{limit:500,beforeSequence:e});for(let s=n.entries.length-1;s>=0;s-=1){const i=n.entries[s],o=M(i.message);if(o)return{preview:o,messageId:i.message.id,at:new Date(i.message.ts).toISOString()}}if(!n.hasOlder||n.entries.length===0)break;e=n.entries[0].sequence}while(e!==void 0);return{preview:null,messageId:null,at:null}}function B(t){let e;do{const n=u().readPage(t,{limit:500,beforeSequence:e});for(let s=n.entries.length-1;s>=0;s-=1){const i=n.entries[s].message;if(!(!M(i)&&!P(i.payload)))return new Date(i.ts).toISOString()}if(!n.hasOlder||n.entries.length===0)break;e=n.entries[0].sequence}while(e!==void 0);return null}function Q(){const t=d();let e=!1;const n=new Date().toISOString();for(const[s,i]of Object.entries(t)){if(Object.prototype.hasOwnProperty.call(i,"lastVisibleEventAt"))continue;const o=i.indexRevision+1;t[s]={...i,lastVisibleEventAt:B(s)??i.lastMessagePreviewAt??null,indexRevision:o,indexSyncState:"pending",indexSyncStateRevision:o,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e=!0}e&&S(t)}function Y(){const t=d();let e=!1;const n=new Date().toISOString();for(const[s,i]of Object.entries(t)){if(Object.prototype.hasOwnProperty.call(i,"lastMessagePreviewMessageId")&&Object.prototype.hasOwnProperty.call(i,"lastMessagePreviewAt"))continue;const o=N(s);t[s]={...i,lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e=!0}e&&S(t)}function z(t,e){const n={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},s=p(e.title?.trim()??"");if(!s)return n;if(!e.titleSource)throw new Error("titleSource is required with title");if(n.title&&n.titleSource==="first_user_fallback"&&e.titleSource==="first_user_fallback")return n;const i=e.titleSource==="shennian_manual";if(e.titleLockedByUser!==void 0&&e.titleLockedByUser!==i)throw new Error("titleLockedByUser conflicts with titleSource");const o=s===n.title&&e.titleSource===n.titleSource,a=e.titleRevision??n.titleRevision+(o?0:1);if(!Number.isSafeInteger(a)||a<0)throw new Error("invalid titleRevision");const r=m[e.titleSource],l=m[n.titleSource];return r<l||a<n.titleRevision||o&&a===n.titleRevision||a===n.titleRevision&&!o?n:{title:s,titleSource:e.titleSource,titleRevision:a,titleLockedByUser:i,titleOriginEventId:e.titleOriginEventId??n.titleOriginEventId}}function ye(t,e){const n=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let i=u().readRecent(t,n).messages;return e?.before!==void 0&&(i=i.filter(o=>o.ts<e.before)),[...i].sort((o,a)=>a.ts-o.ts)}function Ae(){return u().listSessionIds()}function xe(){return u()}function J(t,e){const n=A.dirname(t);c.mkdirSync(n,{recursive:!0,mode:448});const s=`${t}.tmp-${process.pid}-${O.randomBytes(6).toString("hex")}`;let i;try{if(i=c.openSync(s,"wx",384),c.writeFileSync(i,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8"),c.fsyncSync(i),c.closeSync(i),i=void 0,c.renameSync(s,t),process.platform!=="win32"){const o=c.openSync(n,"r");try{c.fsyncSync(o)}finally{c.closeSync(o)}}}finally{i!==void 0&&c.closeSync(i);try{c.unlinkSync(s)}catch{}}}export{V as PERSONAL_SYNC_V1_ROOT_NAME,le as acknowledgeSessionIndexUpdate,Se as appendMessage,ge as applyGeneratedSessionTitle,ce as claimSessionTitleGenerationCandidates,ue as deferSessionTitleGeneration,xe as getCanonicalSessionStore,g as getDaemonInstallationId,ve as getMessageDeliveryStatus,ae as listPendingSessionIndexUpdates,F as listSessionRecords,Ae as listSessions,re as markAllSessionIndexesForRepublish,oe as purgeCanonicalSession,se as purgeSessionRecord,ye as readMessages,j as recordSession,de as recordSessionIndexRejection,me as tombstoneMessage,fe as upsertMessage};
|
|
@@ -332,6 +332,8 @@ export type DaemonSessionIndexProjection = {
|
|
|
332
332
|
/** Stable identity and canonical timestamp of lastMessagePreview. */
|
|
333
333
|
lastMessagePreviewMessageId: string | null;
|
|
334
334
|
lastMessagePreviewAt: string | null;
|
|
335
|
+
/** Latest canonical user-visible text or tool event; optional for older daemons. */
|
|
336
|
+
lastVisibleEventAt?: string | null;
|
|
335
337
|
bodyStorageMode: SessionBodyStorageMode;
|
|
336
338
|
/**
|
|
337
339
|
* Set only after the daemon has durably committed the first canonical user
|
|
@@ -296,6 +296,8 @@ export type NianSession = {
|
|
|
296
296
|
/** Stable identity and canonical timestamp of the displayed list preview. */
|
|
297
297
|
lastMessagePreviewMessageId?: string | null;
|
|
298
298
|
lastMessagePreviewAt?: string | null;
|
|
299
|
+
/** Latest canonical timeline event visible to the user, including tool activity. */
|
|
300
|
+
lastVisibleEventAt?: string | null;
|
|
299
301
|
bodyStorageMode?: SessionBodyStorageMode;
|
|
300
302
|
/**
|
|
301
303
|
* False only for a Server-created shell that has not yet committed its first
|
|
@@ -328,7 +330,7 @@ export type NianSession = {
|
|
|
328
330
|
activity?: SessionActivitySnapshot | null;
|
|
329
331
|
externalChannel?: ExternalChannelSessionStatus | null;
|
|
330
332
|
};
|
|
331
|
-
export type SessionListProjection = Pick<NianSession, 'id' | 'machineId' | 'userId' | 'agentType' | 'sessionMode' | 'managerConfig' | 'agentSessionId' | 'modelId' | 'managerDefaultWorkerAgentType' | 'managerDefaultWorkerModelId' | 'title' | 'titleSource' | 'titleRevision' | 'titleLockedByUser' | 'titleOriginEventId' | 'status' | 'workDir' | 'lastMessagePreview' | 'lastMessagePreviewMessageId' | 'lastMessagePreviewAt' | 'bodyStorageMode' | 'bodyRevision' | 'indexRevision' | 'dataEpoch' | 'firstAvailableSeq' | 'visibleMessageCount' | 'lastVisibleMessageId' | 'attentionCount' | 'lastAttentionId' | 'lastBodyCommittedAt' | 'deletionRevision' | 'lastActivityAt' | 'createdAt' | 'updatedAt' | 'deletedAt' | 'machineName' | 'ownerName' | 'ownerPhone' | 'ownerEmail' | 'canManage' | 'sharedMachineSession' | 'activity' | 'externalChannel'>;
|
|
333
|
+
export type SessionListProjection = Pick<NianSession, 'id' | 'machineId' | 'userId' | 'agentType' | 'sessionMode' | 'managerConfig' | 'agentSessionId' | 'modelId' | 'managerDefaultWorkerAgentType' | 'managerDefaultWorkerModelId' | 'title' | 'titleSource' | 'titleRevision' | 'titleLockedByUser' | 'titleOriginEventId' | 'status' | 'workDir' | 'lastMessagePreview' | 'lastMessagePreviewMessageId' | 'lastMessagePreviewAt' | 'lastVisibleEventAt' | 'bodyStorageMode' | 'bodyRevision' | 'indexRevision' | 'dataEpoch' | 'firstAvailableSeq' | 'visibleMessageCount' | 'lastVisibleMessageId' | 'attentionCount' | 'lastAttentionId' | 'lastBodyCommittedAt' | 'deletionRevision' | 'lastActivityAt' | 'createdAt' | 'updatedAt' | 'deletedAt' | 'machineName' | 'ownerName' | 'ownerPhone' | 'ownerEmail' | 'canManage' | 'sharedMachineSession' | 'activity' | 'externalChannel'>;
|
|
332
334
|
export type ChatSendParams = PersonalSyncIdentity & {
|
|
333
335
|
sessionId: string;
|
|
334
336
|
text: string;
|