shennian 0.3.79 → 0.3.81
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 +12 -12
- package/dist/src/fs/boundary.d.ts +2 -0
- package/dist/src/fs/boundary.js +1 -1
- package/dist/src/native-fusion/local-repository.d.ts +5 -0
- package/dist/src/native-fusion/local-repository.js +2 -2
- package/dist/src/native-fusion/service.js +2 -2
- package/dist/src/room/managed-room-binding.js +1 -1
- package/dist/src/session/handlers/fs.js +1 -1
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/manager.js +1 -1
- package/dist/src/session/types.d.ts +1 -0
- package/node_modules/@shennian/wire/dist/message-payload.js +10 -0
- package/package.json +1 -1
|
@@ -291,8 +291,8 @@
|
|
|
291
291
|
},
|
|
292
292
|
{
|
|
293
293
|
"file": "src/fs/boundary.js",
|
|
294
|
-
"beforeBytes":
|
|
295
|
-
"afterBytes":
|
|
294
|
+
"beforeBytes": 7102,
|
|
295
|
+
"afterBytes": 3049
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
298
|
"file": "src/fs/handler.js",
|
|
@@ -371,8 +371,8 @@
|
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"file": "src/native-fusion/local-repository.js",
|
|
374
|
-
"beforeBytes":
|
|
375
|
-
"afterBytes":
|
|
374
|
+
"beforeBytes": 25725,
|
|
375
|
+
"afterBytes": 12677
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
"file": "src/native-fusion/native-source-deletions.js",
|
|
@@ -396,8 +396,8 @@
|
|
|
396
396
|
},
|
|
397
397
|
{
|
|
398
398
|
"file": "src/native-fusion/service.js",
|
|
399
|
-
"beforeBytes":
|
|
400
|
-
"afterBytes":
|
|
399
|
+
"beforeBytes": 31812,
|
|
400
|
+
"afterBytes": 14584
|
|
401
401
|
},
|
|
402
402
|
{
|
|
403
403
|
"file": "src/native-fusion/session-header.js",
|
|
@@ -506,8 +506,8 @@
|
|
|
506
506
|
},
|
|
507
507
|
{
|
|
508
508
|
"file": "src/room/managed-room-binding.js",
|
|
509
|
-
"beforeBytes":
|
|
510
|
-
"afterBytes":
|
|
509
|
+
"beforeBytes": 7094,
|
|
510
|
+
"afterBytes": 3794
|
|
511
511
|
},
|
|
512
512
|
{
|
|
513
513
|
"file": "src/room/managed-room-ipc.js",
|
|
@@ -616,8 +616,8 @@
|
|
|
616
616
|
},
|
|
617
617
|
{
|
|
618
618
|
"file": "src/session/handlers/fs.js",
|
|
619
|
-
"beforeBytes":
|
|
620
|
-
"afterBytes":
|
|
619
|
+
"beforeBytes": 21946,
|
|
620
|
+
"afterBytes": 10405
|
|
621
621
|
},
|
|
622
622
|
{
|
|
623
623
|
"file": "src/session/handlers/message-status.js",
|
|
@@ -666,8 +666,8 @@
|
|
|
666
666
|
},
|
|
667
667
|
{
|
|
668
668
|
"file": "src/session/manager.js",
|
|
669
|
-
"beforeBytes":
|
|
670
|
-
"afterBytes":
|
|
669
|
+
"beforeBytes": 30775,
|
|
670
|
+
"afterBytes": 15951
|
|
671
671
|
},
|
|
672
672
|
{
|
|
673
673
|
"file": "src/session/native-cleanup-outbox.js",
|
|
@@ -17,5 +17,7 @@ export type ResolveAuthorizedPathResult = {
|
|
|
17
17
|
};
|
|
18
18
|
export declare function resolveSessionWorkDir(input: string): string;
|
|
19
19
|
export declare function createAuthorizedFsRoot(rawRoot: string): AuthorizedFsRoot;
|
|
20
|
+
export declare function createAuthorizedFsRootAsync(rawRoot: string): Promise<AuthorizedFsRoot>;
|
|
20
21
|
export declare function resolveAuthorizedPath(rawPath: string, root: AuthorizedFsRoot): ResolveAuthorizedPathResult;
|
|
22
|
+
export declare function resolveAuthorizedPathAsync(rawPath: string, root: AuthorizedFsRoot): Promise<ResolveAuthorizedPathResult>;
|
|
21
23
|
export declare function isAuthorizedRootPath(pathValue: string, root: AuthorizedFsRoot): boolean;
|
package/dist/src/fs/boundary.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import p from"node:fs";import m from"node:os";import u from"node:path";const R=/^[A-Za-z]:([\\/]|$)/,$=/^\\\\[^\\]+\\[^\\]+/,k=/^[A-Za-z]:(?![\\/])/;function b(n){return R.test(n)||$.test(n)}function W(n){return k.test(n)}function A(n){return n.replace(/^[/\\]([A-Za-z]:[\\/].*)$/,"$1")}function h(n){return n==="~"||n.startsWith("~/")||n.startsWith("~\\")}function f(n){return b(A(n))?"win32":"posix"}function l(n){return n==="win32"?u.win32:u.posix}function o(n,e){const i=l(e),r=i.normalize(n),t=r.length>i.parse(r).root.length?r.replace(/[\\/]+$/,""):r;return e==="win32"?t.toLowerCase():t}function d(n,e,i){const r=l(i),t=o(n,i),s=o(e,i);if(t===s)return!0;const a=r.relative(s,t);return!!a&&!a.startsWith("..")&&!r.isAbsolute(a)}function y(n){try{return p.realpathSync.native(n)}catch{return null}}async function z(n){try{return await p.promises.realpath(n)}catch{return null}}function v(n){if(!n)return m.homedir();const e=A(n),i=f(e),r=l(i);if(h(e))return u.join(m.homedir(),e.slice(1).replace(/^[/\\]+/,""));if(process.platform==="win32"){const t=e.replace(/\\/g,"/");if(t==="/tmp"||t.startsWith("/tmp/")){const s=t.slice(4).replace(/^\/+/,"");return s?u.win32.join(m.tmpdir(),...s.split("/")):m.tmpdir()}}return b(e)?u.win32.resolve(e):r.resolve(e)}function I(n){const e=v(n),i=f(e),r=S(e),t=typeof r?.isFile=="function"?r.isFile():!1,s=t?l(i).dirname(e):e;return{raw:n,normalized:e,boundary:s,real:y(s),flavor:i,file:t}}async function _(n){const e=v(n),i=f(e),r=await x(e),t=typeof r?.isFile=="function"?r.isFile():!1,s=t?l(i).dirname(e):e;return{raw:n,normalized:e,boundary:s,real:await z(s),flavor:i,file:t}}function g(n,e){const i=String(n||"").trim()||e.normalized,r=h(i)?v(i):A(i),t=l(e.flavor);if(W(r))return{ok:!1,error:`Access denied: ${n}`};const s=t.isAbsolute(r)?t.resolve(r):t.resolve(e.boundary,r);if(!d(s,e.boundary,e.flavor))return{ok:!1,error:`Access denied: ${n}`};if(e.file&&o(s,e.flavor)!==o(e.normalized,e.flavor))return{ok:!1,error:`Access denied: ${n}`};const a=y(s);if(a){const c=e.real??y(e.boundary);if(c&&!d(a,c,f(c)))return{ok:!1,error:`Access denied: ${n}`}}return{ok:!0,path:s,root:e}}async function C(n,e){const i=String(n||"").trim()||e.normalized,r=h(i)?v(i):A(i),t=l(e.flavor);if(W(r))return{ok:!1,error:`Access denied: ${n}`};const s=t.isAbsolute(r)?t.resolve(r):t.resolve(e.boundary,r);if(!d(s,e.boundary,e.flavor))return{ok:!1,error:`Access denied: ${n}`};if(e.file&&o(s,e.flavor)!==o(e.normalized,e.flavor))return{ok:!1,error:`Access denied: ${n}`};const a=await z(s);if(a){const c=e.real??await z(e.boundary);if(c&&!d(a,c,f(c)))return{ok:!1,error:`Access denied: ${n}`}}return{ok:!0,path:s,root:e}}function D(n,e){return d(n,e.boundary,e.flavor)&&o(n,e.flavor)===o(e.boundary,e.flavor)}function S(n){try{return p.statSync(n)}catch{return null}}async function x(n){try{return await p.promises.stat(n)}catch{return null}}export{I as createAuthorizedFsRoot,_ as createAuthorizedFsRootAsync,D as isAuthorizedRootPath,g as resolveAuthorizedPath,C as resolveAuthorizedPathAsync,v as resolveSessionWorkDir};
|
|
@@ -13,6 +13,11 @@ export declare class NativeSourceDeletedError extends Error {
|
|
|
13
13
|
readonly code = "native_source_deleted";
|
|
14
14
|
constructor(sourceSessionKey: string);
|
|
15
15
|
}
|
|
16
|
+
export declare class NativeEventIdentityConflictError extends Error {
|
|
17
|
+
readonly sourceEventKey: string;
|
|
18
|
+
readonly code = "native_event_identity_conflict";
|
|
19
|
+
constructor(sourceEventKey: string);
|
|
20
|
+
}
|
|
16
21
|
type SourceBinding = {
|
|
17
22
|
schemaVersion: typeof SCHEMA_VERSION;
|
|
18
23
|
daemonInstallationId: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import p from"node:crypto";import l from"node:fs";import h 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 F,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 A}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 le{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??F)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(z(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new W(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=X(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=l.existsSync(a)?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 Error(`native event identity conflict: ${e.sourceEventKey}`);if(!i&&!r&&e.role!=="user")throw new q(e.sourceSessionKey);const m=e.claimSessionId??null,d=i?.sessionId??r?.sessionId??m??G(s,e.agentType,e.sourceSessionKey);if(m&&i&&i.sessionId!==m)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const f=this.canonicalStore(),S=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",u=r?.canonicalMessageId??(S?null:e.claimCanonicalMessageId??Q(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let y=f.getManifest(d)?.bodyRevision??0,T=!!r;const M=!S&&!!e.claimCanonicalMessageId;if(M){if(!u)throw new Error("managed echo claim requires a canonical message");const E=f.getMessageState(d,u);if(!E||E.tombstoned)throw new Error(`managed echo claim target is not durable: ${u}`)}if(!r&&!M&&!S){if(!u)throw new Error("native event requires a canonical message");const E=e.canonicalMessageKey?f.getMessageState(d,u):null,x=e.canonicalMutation==="upsert"||!!E,$=x?E??f.getMessageState(d,u):null,_={id:u,sessionId:d,role:e.role,ts:$?.message?.ts??e.ts,payload:Y($?.message?.payload,e.payload)},C=x?f.upsert(d,_):f.append(d,_);if(y=C.bodyRevision,T=C.duplicate,e.localToolDetail){const N=this.options.writeLocalToolDetail??J,v=this.options.readLocalToolDetail??U,B=Z(v(d,e.localToolDetail.toolCallId),e.localToolDetail);N({sessionId:d,...B,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(y=f.completeStartedUserMessages(d,e.ts).at(-1)?.bodyRevision??y),(!r||e.terminal)&&!S&&this.recordIndex(e,d,u,y);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:y,cursor:e.cursor??null,mappedBy:S?"managed_echo_suppressed":M?"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:y,createdAt:i?.createdAt??K,updatedAt:K},g(this.sourcesFile(),o),!r&&e.cursor&&A("sourceCursorAdvanced"),!r&&!M&&!S&&!T&&A("canonicalCommits"),{sessionId:d,messageId:u,bodyRevision:y,duplicate:T,claimed:M,suppressed:S}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??F,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){k(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,g(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&g(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],g(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(O(s),o.suppressions[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=h.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=h.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:h.basename(this.rootDir())==="native-fusion"?h.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??F)().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"?j(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"?j(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!l.existsSync(e))return{schemaVersion: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 h.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return h.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))k(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 h.join(this.rootDir(),"ledger",`${o}.json`)}}function z(t){if(!t.agentType||!t.sourceSessionKey||!t.sourceEventKey||!t.payload)throw new Error("invalid native event");if(!Number.isFinite(t.ts))throw new Error("invalid native event timestamp")}function b(t,e){return p.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function G(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Q(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function X(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 k(t){if(!t.sessionId||!t.canonicalMessageId||!t.textHash||!Number.isFinite(t.createdAt)||!["codex","claude","opencode"].includes(t.sourceAgentType)||!["suppress_following","import_following"].includes(t.managedEchoPolicy))throw new Error("invalid managed echo claim")}function O(t){if(!t.sessionId||!t.sourceSessionKey||!t.claimSourceEventKey||!["codex","claude","opencode"].includes(t.agentType)||!Number.isFinite(t.startedAt)||!Number.isFinite(t.endsAt)||t.endsAt<t.startedAt)throw new Error("invalid managed echo suppression")}function Y(t,e){if(!t)return e;try{const s=JSON.parse(t),o=JSON.parse(e);if(s.type!=="tool"||o.type!=="tool"||s.toolCallId!==o.toolCallId)return e;const n=typeof o.name=="string"?o.name:"",i=/(?:_output|_result)$/.test(n);return JSON.stringify({...s,...o,...i&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function Z(t,e){if(!t||t.toolCallId!==e.toolCallId)return e;const s=/(?:_output|_result)$/.test(e.name);return{...e,name:s?t.name:e.name,...e.args===void 0&&t.args!==void 0?{args:t.args}:{},...e.result===void 0&&t.result!==void 0?{result:t.result}:{}}}function j(t){return L(t,120)}function g(t,e){const s=h.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{q as MissingUserBoundaryError,
|
|
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{q as MissingUserBoundaryError,z as NativeEventIdentityConflictError,de as NativeFusionLocalRepository,W as NativeSourceDeletedError};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`).trim()}function F(){return new Promise(u=>setImmediate(u))}function ee(u){return u.split(/[\\/]+/).includes(".codex")}class de{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new P(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;externalTerminalHandler=null;constructor(e,s=new k){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[w(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},Q))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}setExternalTerminalHandler(e){this.externalTerminalHandler=e}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,i=this.pendingClaims.get(t),a=_(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==a))throw new Error(`managed echo claim identity conflict: ${t}`);const l={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:a,createdAt:i?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??i?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(l),this.pendingClaims.set(t,l),e.externalRunId&&e.sourceSessionKey){const y=`${s}:${e.sourceSessionKey}`,g=this.externalObservations.get(y)??[];g.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(y,g),this.emitExternalActivity(g[g.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const i of this.pendingClaims.values())i.sessionId===e&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const i=t.filter(a=>a.sessionId!==e);i.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,i)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},X),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e,s="completed"){const t=s==="completed"?"final":s==="aborted"?"aborted":"error";this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}`,payload:{state:t,sessionId:e.sessionId,runId:e.runId,seq:2,...t==="error"?{message:"Codex Desktop turn failed"}:{},owner:"external",ownerLabel:"Codex Desktop"}}),this.externalTerminalHandler?.(e.sessionId)}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=H(),t={...s.files},i=[],a=new Map,l=new Map,y=new Map,g=e?[...e]:[...R(),...O(),...D()];!e&&this.watchEnabled&&this.watcher.refresh(g,L());for(let o=0;o<g.length;o+=1){const n=g[o];if(o>0&&o%G===0&&await F(),!K.existsSync(n)){this.watchEnabled&&this.watcher.untrackFile(n),this.pendingBoundaryFiles.delete(n);for(const[r,p]of this.sourceFiles.entries())p===n&&this.sourceFiles.delete(r);continue}this.watchEnabled&&this.watcher.trackFile(n);const d=K.statSync(n),h=ee(n),f=n.endsWith(".opencode-session.json");let c=s.files[n];const I=`${String(d.dev)}:${String(d.ino)}:${String(d.birthtimeMs)}`;let x=!c;if(c&&!f&&(d.size<c.offset||c.fileIdentity&&c.fileIdentity!==I)&&(c=void 0,x=!0),!!(c&&c.fileIdentity===I&&c.mtimeMs===d.mtimeMs&&c.offset>=d.size&&c.sources?.length)&&c){for(const r of c.sources??[])this.sourceFiles.set(w(r.agentType,r.sourceSessionKey),n);c.classification==="live"&&c.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);continue}const m=c?.sources?.length&&!f?{status:"ready",createdAtMs:c.sessionCreatedAtMs??d.birthtimeMs,sources:c.sources}:q(n);if(m.status==="pending"){d.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(n);continue}for(const r of m.sources)this.sourceFiles.set(w(r.agentType,r.sourceSessionKey),n);if(!f&&m.sources.length>0&&m.sources.every(r=>this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))){t[n]={...c??{daemonInstallationId:s.daemonInstallationId,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}},offset:d.size,mtimeMs:d.mtimeMs,fileIdentity:I};continue}if(!c)c={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:d.mtimeMs,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}};else if(f&&c.mtimeMs===d.mtimeMs)continue;const E=f?0:c.offset,T=f?z(n,E):h?U(n,E):$(n,E);for(const r of T.events)this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey)||r.title?.trim()&&r.titleSource==="agent_native"&&y.set(`${r.agentType}:${r.sourceSessionKey}`,r);let M,S;if(f){const r=te(T.events,c,s.fusionEpochMs,x,d.mtimeMs,T.nextOffset);M=r.events,S=r.cursor}else if(x&&c.classification==="historical")M=[],S={...c,offset:T.nextOffset,mtimeMs:d.mtimeMs,awaitingUserBoundary:!0};else{const r=se(T.events,c,T.nextOffset,d.mtimeMs);M=r.events,S=r.cursor}S.fileIdentity=I,S.sources=m.sources,t[n]=S,S.classification==="live"&&S.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);for(const r of M){if(this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))continue;const p=this.tryClaimManagedEcho(r);if(p){i.push(p.event),a.set(p.claimKey,p.suppression??null),p.suppression&&l.set(w(p.suppression.agentType,p.suppression.sourceSessionKey),{window:p.suppression,claimKey:p.claimKey});continue}const b=this.suppressionFor(r,l,a);if(b){i.push({...r,claimSessionId:b.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(r)}}const v=[...i].sort((o,n)=>o.ts-n.ts);v.length===0&&this.repository.commitEvents([]);for(let o=0;o<v.length;o+=A)this.repository.commitEvents(v.slice(o,o+A)),o+A<v.length&&await F();for(const[o,n]of a)this.repository.completeManagedClaim?.(o,n??void 0),this.pendingClaims.delete(o),n&&this.suppressionWindows.set(w(n.agentType,n.sourceSessionKey),n);for(const o of y.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const n=W(o.sourceSessionKey);n&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,n)}for(const o of v)this.observeExternalLifecycle(o);s.files=t,V(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await N(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(l=>e-l.createdAt<C);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const a=i[0];e-a.lastPublishedAt<J||(a.lastPublishedAt=e,this.emitExternalActivity(a,a.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries()){if(i.sourceAgentType!==e.agentType||e.role!=="user"||!i.sourceSessionKey||i.sourceSessionKey!==e.sourceSessionKey)continue;const a=i.externalRunId?C:Y;if(Math.abs(e.ts-i.createdAt)>a||_(e.payload)!==i.textHash)continue;const l=i.managedEchoPolicy==="suppress_following"?{sessionId:i.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+j}:void 0;return{claimKey:t,suppression:l,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId,claimSessionId:i.sessionId,managedEchoPolicy:i.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const i=t[0];if(e.claimCanonicalMessageId===i.canonicalMessageId&&i.state==="queued"&&(this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,"executing"),i.state="running",i.lastPublishedAt=Date.now(),this.emitExternalActivity(i,"running")),!e.terminal||i.state!=="running")return;const a=e.terminalState??"completed";this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,a==="aborted"?"cancelled":a==="failed"?"failed":"completed"),this.emitExternalTerminal(i,a),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued"))}suppressionFor(e,s,t){const i=w(e.agentType,e.sourceSessionKey),a=s.get(i),l=a?.window??this.suppressionWindows.get(i);return!l||e.ts<l.startedAt?null:e.ts>l.endsAt?(this.clearSuppression(i,l,a,s,t),null):e.role==="user"?(e.sourceEventKey===l.claimSourceEventKey||this.clearSuppression(i,l,a,s,t),null):l}clearSuppression(e,s,t,i,a){if(t){i.delete(e),a.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function _(u){return B.createHash("sha256").update(Z(u)).digest("hex")}function w(u,e){return`${u}:${e}`}function se(u,e,s,t){if(!e.awaitingUserBoundary)return{events:u,cursor:{...e,offset:s,mtimeMs:t}};if(u.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=u.findIndex(a=>a.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:u.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function te(u,e,s,t,i,a){const l={...e.sourceBoundaryEstablished??{}},y=e.eventWatermarkTs??-1,g=new Set(e.eventKeysAtWatermark??[]),v=t?u:u.filter(h=>h.ts>y||h.ts===y&&!g.has(h.sourceEventKey)),o=[];for(const h of v){const f=h.sourceSessionKey;if(l[f]){o.push(h);continue}!(!t||(h.sourceSessionCreatedAtMs??0)>=s)||h.role!=="user"||(l[f]=!0,o.push(h))}const n=u.reduce((h,f)=>Math.max(h,f.ts),y),d=new Set(n===y?e.eventKeysAtWatermark??[]:[]);for(const h of u)h.ts===n&&d.add(h.sourceEventKey);return{events:o,cursor:{...e,offset:a,mtimeMs:i,eventWatermarkTs:n>=0?n:void 0,eventKeysAtWatermark:n>=0?[...d]:void 0,sourceBoundaryEstablished:l}}}export{de as NativeSessionFusionService};
|
|
1
|
+
import _ from"node:fs";import k from"node:crypto";import{publishPendingSessionIndexes as O}from"../session/index-publisher.js";import{NativeFusionFileWatcher as R}from"./file-watcher.js";import{NativeEventIdentityConflictError as q,NativeFusionLocalRepository as D}from"./local-repository.js";import{listClaudeTranscriptFiles as L,listCodexRolloutFiles as W,listOpenCodeSessionFiles as $,listNativeTranscriptRoots as U,inspectNativeSessionFile as z,lookupCodexThreadName as H,parseClaudeTranscriptChunk as V,parseCodexRolloutChunk as Q,parseOpenCodeSessionFile as Y}from"./parsers.js";import{loadNativeScannerState as j,saveNativeScannerState as X}from"./state.js";const G=5*6e4,J=2*6e4,Z=30*6e4,ee=2e3,se=32,C=16,te=3e4,B=240*6e4;function ie(d){return d.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function N(){return new Promise(d=>setImmediate(d))}function ne(d){return d.split(/[\\/]+/).includes(".codex")}class ye{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new R(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;externalTerminalHandler=null;constructor(e,s=new D){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[I(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},G))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}setExternalTerminalHandler(e){this.externalTerminalHandler=e}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,i=this.pendingClaims.get(t),a=P(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==a))throw new Error(`managed echo claim identity conflict: ${t}`);const u={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:a,createdAt:i?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??i?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(u),this.pendingClaims.set(t,u),e.externalRunId&&e.sourceSessionKey){const p=`${s}:${e.sourceSessionKey}`,y=this.externalObservations.get(p)??[];y.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(p,y),this.emitExternalActivity(y[y.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const i of this.pendingClaims.values())i.sessionId===e&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const i=t.filter(a=>a.sessionId!==e);i.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,i)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},ee),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e,s="completed"){const t=s==="completed"?"final":s==="aborted"?"aborted":"error";this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}`,payload:{state:t,sessionId:e.sessionId,runId:e.runId,seq:2,...t==="error"?{message:"Codex Desktop turn failed"}:{},owner:"external",ownerLabel:"Codex Desktop"}}),this.externalTerminalHandler?.(e.sessionId)}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=j(),t={...s.files},i=[],a=new Map,u=new Map,p=new Map,y=e?[...e]:[...W(),...L(),...$()];!e&&this.watchEnabled&&this.watcher.refresh(y,U());for(let n=0;n<y.length;n+=1){const o=y[n];if(n>0&&n%se===0&&await N(),!_.existsSync(o)){this.watchEnabled&&this.watcher.untrackFile(o),this.pendingBoundaryFiles.delete(o);for(const[r,f]of this.sourceFiles.entries())f===o&&this.sourceFiles.delete(r);continue}this.watchEnabled&&this.watcher.trackFile(o);const l=_.statSync(o),E=ne(o),h=o.endsWith(".opencode-session.json");let c=s.files[o];const x=`${String(l.dev)}:${String(l.ino)}:${String(l.birthtimeMs)}`;let K=!c;if(c&&!h&&(l.size<c.offset||c.fileIdentity&&c.fileIdentity!==x)&&(c=void 0,K=!0),!!(c&&c.fileIdentity===x&&c.mtimeMs===l.mtimeMs&&c.offset>=l.size&&c.sources?.length)&&c){for(const r of c.sources??[])this.sourceFiles.set(I(r.agentType,r.sourceSessionKey),o);c.classification==="live"&&c.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o);continue}const g=c?.sources?.length&&!h?{status:"ready",createdAtMs:c.sessionCreatedAtMs??l.birthtimeMs,sources:c.sources}:z(o);if(g.status==="pending"){l.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(o);continue}for(const r of g.sources)this.sourceFiles.set(I(r.agentType,r.sourceSessionKey),o);if(!h&&g.sources.length>0&&g.sources.every(r=>this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))){t[o]={...c??{daemonInstallationId:s.daemonInstallationId,classification:g.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:g.createdAtMs,sourceBoundaryEstablished:{}},offset:l.size,mtimeMs:l.mtimeMs,fileIdentity:x};continue}if(!c)c={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:l.mtimeMs,classification:g.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:g.createdAtMs,sourceBoundaryEstablished:{}};else if(h&&c.mtimeMs===l.mtimeMs)continue;const b=h?0:c.offset,T=h?Y(o,b):E?Q(o,b):V(o,b);for(const r of T.events)this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey)||r.title?.trim()&&r.titleSource==="agent_native"&&p.set(`${r.agentType}:${r.sourceSessionKey}`,r);let A,S;if(h){const r=re(T.events,c,s.fusionEpochMs,K,l.mtimeMs,T.nextOffset);A=r.events,S=r.cursor}else if(K&&c.classification==="historical")A=[],S={...c,offset:T.nextOffset,mtimeMs:l.mtimeMs,awaitingUserBoundary:!0};else{const r=oe(T.events,c,T.nextOffset,l.mtimeMs);A=r.events,S=r.cursor}S.fileIdentity=x,S.sources=g.sources,t[o]=S,S.classification==="live"&&S.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o);for(const r of A){if(this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))continue;const f=this.tryClaimManagedEcho(r);if(f){i.push(f.event),a.set(f.claimKey,f.suppression??null),f.suppression&&u.set(I(f.suppression.agentType,f.suppression.sourceSessionKey),{window:f.suppression,claimKey:f.claimKey});continue}const F=this.suppressionFor(r,u,a);if(F){i.push({...r,claimSessionId:F.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(r)}}const v=[...i].sort((n,o)=>n.ts-o.ts),w=new Set,m=new Set,M=n=>{try{this.repository.commitEvents(n)}catch(o){if(!(o instanceof q))throw o;const l=n.find(h=>h.sourceEventKey===o.sourceEventKey);if(!l)throw o;w.add(l.sourceEventKey),l.claimCanonicalMessageId&&m.add(l.claimCanonicalMessageId),console.warn(`[native-fusion] quarantined conflicting native event ${l.sourceEventKey}`);const E=n.filter(h=>h!==l);E.length>0&&M(E)}};v.length===0&&this.repository.commitEvents([]);for(let n=0;n<v.length;n+=C)M(v.slice(n,n+C)),n+C<v.length&&await N();for(const[n,o]of a)m.has(n)||(this.repository.completeManagedClaim?.(n,o??void 0),this.pendingClaims.delete(n),o&&this.suppressionWindows.set(I(o.agentType,o.sourceSessionKey),o));for(const n of p.values())this.repository.observeNativeTitle(n.agentType,n.sourceSessionKey,n.title??"");for(const n of this.repository.listSourceBindings()){if(n.agentType!=="codex")continue;const o=H(n.sourceSessionKey);o&&this.repository.observeNativeTitle("codex",n.sourceSessionKey,o)}for(const n of v)w.has(n.sourceEventKey)||this.observeExternalLifecycle(n);s.files=t,X(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await O(this.client).catch(n=>{console.error("[native-fusion] index projection sync failed; retained for retry",n)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(u=>e-u.createdAt<B);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const a=i[0];e-a.lastPublishedAt<te||(a.lastPublishedAt=e,this.emitExternalActivity(a,a.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries()){if(i.sourceAgentType!==e.agentType||e.role!=="user"||!i.sourceSessionKey||i.sourceSessionKey!==e.sourceSessionKey)continue;const a=i.externalRunId?B:J;if(Math.abs(e.ts-i.createdAt)>a||P(e.payload)!==i.textHash)continue;const u=i.managedEchoPolicy==="suppress_following"?{sessionId:i.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+Z}:void 0;return{claimKey:t,suppression:u,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId,claimSessionId:i.sessionId,managedEchoPolicy:i.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const i=t[0];if(e.claimCanonicalMessageId===i.canonicalMessageId&&i.state==="queued"&&(this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,"executing"),i.state="running",i.lastPublishedAt=Date.now(),this.emitExternalActivity(i,"running")),!e.terminal||i.state!=="running")return;const a=e.terminalState??"completed";this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,a==="aborted"?"cancelled":a==="failed"?"failed":"completed"),this.emitExternalTerminal(i,a),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued"))}suppressionFor(e,s,t){const i=I(e.agentType,e.sourceSessionKey),a=s.get(i),u=a?.window??this.suppressionWindows.get(i);return!u||e.ts<u.startedAt?null:e.ts>u.endsAt?(this.clearSuppression(i,u,a,s,t),null):e.role==="user"?(e.sourceEventKey===u.claimSourceEventKey||this.clearSuppression(i,u,a,s,t),null):u}clearSuppression(e,s,t,i,a){if(t){i.delete(e),a.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function P(d){return k.createHash("sha256").update(ie(d)).digest("hex")}function I(d,e){return`${d}:${e}`}function oe(d,e,s,t){if(!e.awaitingUserBoundary)return{events:d,cursor:{...e,offset:s,mtimeMs:t}};if(d.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=d.findIndex(a=>a.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:d.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function re(d,e,s,t,i,a){const u={...e.sourceBoundaryEstablished??{}},p=e.eventWatermarkTs??-1,y=new Set(e.eventKeysAtWatermark??[]),v=t?d:d.filter(n=>n.ts>p||n.ts===p&&!y.has(n.sourceEventKey)),w=[];for(const n of v){const o=n.sourceSessionKey;if(u[o]){w.push(n);continue}!(!t||(n.sourceSessionCreatedAtMs??0)>=s)||n.role!=="user"||(u[o]=!0,w.push(n))}const m=d.reduce((n,o)=>Math.max(n,o.ts),p),M=new Set(m===p?e.eventKeysAtWatermark??[]:[]);for(const n of d)n.ts===m&&M.add(n.sourceEventKey);return{events:w,cursor:{...e,offset:a,mtimeMs:i,eventWatermarkTs:m>=0?m:void 0,eventKeysAtWatermark:m>=0?[...M]:void 0,sourceBoundaryEstablished:u}}}export{ye as NativeSessionFusionService};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{roomRuntimeAdapterForAgent as y}from"@shennian/wire";import h from"node:crypto";class u{deps;constructor(e){this.deps=e}async bind(e){const{machineId:i,machineToken:s}=this.deps.getMachineCredentials();if(!i||!s)throw new Error("Machine is not paired with Shennian.");const r=I(e.agentType),o=this.deps.hostProcessIdentity(),t=this.deps.registry.register({machineId:i,runtimeAdapter:r,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.create({machineToken:s,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:r,sessionRegistryKey:t.registryKey,activationCapability:"managed",activationMode:"auto_follow"}),a=await this.resolveServerOwnedWorkspace({machineToken:s,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:i,runtimeAdapter:r,sourceSessionKey:e.sourceSessionKey,hostContextHash:g(o),workspaceHash:g(a),workspacePath:a,lifecycleState:"active",eventName:"ManagedBind"}),
|
|
1
|
+
import{roomRuntimeAdapterForAgent as y}from"@shennian/wire";import h from"node:crypto";class u{deps;constructor(e){this.deps=e}async bind(e){const{machineId:i,machineToken:s}=this.deps.getMachineCredentials();if(!i||!s)throw new Error("Machine is not paired with Shennian.");const r=I(e.agentType),o=this.deps.hostProcessIdentity(),t=this.deps.registry.register({machineId:i,runtimeAdapter:r,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.create({machineToken:s,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:r,sessionRegistryKey:t.registryKey,activationCapability:"managed",activationMode:"auto_follow"});this.deps.registry.setBinding({registryKey:t.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode});const a=await this.resolveServerOwnedWorkspace({machineToken:s,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:i,runtimeAdapter:r,sourceSessionKey:e.sourceSessionKey,hostContextHash:g(o),workspaceHash:g(a),workspacePath:a,lifecycleState:"active",eventName:"ManagedBind"}),n}async restart(e){const{machineId:i,machineToken:s}=this.deps.getMachineCredentials();if(!i||!s)throw new Error("Machine is not paired with Shennian.");const r=I(e.agentType),o=this.deps.hostProcessIdentity(),t=this.deps.registry.register({machineId:i,runtimeAdapter:r,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.restart({machineToken:s,bindingId:e.bindingId,previousNianSessionId:e.previousNianSessionId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:r,sessionRegistryKey:t.registryKey});this.deps.registry.setBinding({registryKey:t.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode}),this.deps.registry.advanceCursor({registryKey:t.registryKey,cursor:"delivered",sequence:n.resumeSequence}),this.deps.registry.advanceCursor({registryKey:t.registryKey,cursor:"activated",sequence:n.resumeSequence});for(const c of this.deps.registry.list())c.registryKey!==t.registryKey&&c.nianSessionId===e.previousNianSessionId&&c.binding?.bindingId===e.bindingId&&this.deps.registry.detachBinding(c.registryKey);const a=await this.resolveServerOwnedWorkspace({machineToken:s,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:i,runtimeAdapter:r,sourceSessionKey:e.sourceSessionKey,hostContextHash:g(o),workspaceHash:g(a),workspacePath:a,lifecycleState:"active",eventName:"ManagedRestart"}),n}async resolveServerOwnedWorkspace(e){const i=await this.deps.bindingClient.status(e),s=i.managedWorkspacePath?.trim();if(i.agentIdentityId!==e.agentIdentityId||i.agent?.sessionMode!=="standard"||!s)throw new Error("managed_room_workspace_unavailable");return s}}function g(d){return h.createHash("sha256").update(d,"utf8").digest("hex")}function I(d){const e=y(d);if(e)return e;throw new Error(`Agent ${d} does not support managed Room binding.`)}export{u as TrustedManagedRoomBinder};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import l from"node:fs";import R from"node:os";import h from"node:path";import{convertMarkdownToPdf as w,defaultPdfOutputPath as E,findChromiumExecutable as T,installManagedChromium as b,managedChromiumBrowsersPath as I,MarkdownPdfBrowserMissingError as C}from"../../tools/markdown-to-pdf.js";import{createZipArchive as D}from"../archive-zip.js";import{BinaryTextPreviewError as M,decodeTextBufferAuto as N,isAutoTextEncoding as $}from"../../fs/text-decoder.js";import{abortAttachmentTransfer as _,appendAttachmentTransferChunk as O,finishAttachmentTransfer as j,startAttachmentTransfer as B}from"../attachment-transfer-store.js";import{assertPersonalSyncIdentity as k,getPersonalSyncIdentity as u}from"../../personal-sync/epoch.js";import{getDaemonInstallationId as P}from"../../personal-sync/identity.js";const x="__roots__",L=5e3,Z=1024*1024*1024;function v(e){return/^[A-Za-z]:([\\/]|$)/.test(e)||/^\\\\[^\\]+\\[^\\]+/.test(e)}function m(e){return v(e)?h.win32:h.posix}function g(e){const t=l.statSync(e);return{name:m(e).basename(e),path:e,isDir:t.isDirectory(),size:t.isFile()?t.size:void 0,modifiedAt:t.mtimeMs}}function H(){if(R.platform()==="win32"){const e=[];for(let t=65;t<=90;t+=1){const r=`${String.fromCharCode(t)}:\\`;l.existsSync(r)&&e.push({name:r,path:r,isDir:!0})}return e}return[{name:"/",path:"/",isDir:!0}]}function U(e,t){const r=typeof e=="object"&&e&&"code"in e?String(e.code):"";return r==="ENOENT"?`Directory not found: ${t}`:r==="EACCES"||r==="EPERM"?`Permission denied: ${t}`:e instanceof Error?e.message:String(e)}function W(e){const t=e.trim();if(!t||t!==e||e==="."||e===".."||X(e)||/[\\/:]/.test(e)||/[<>|"?*]/.test(e)||/[. ]$/.test(e))return!1;const r=e.split(".")[0]?.toUpperCase();return!(r&&/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/.test(r))}function X(e){return Array.from(e).some(t=>{const r=t.charCodeAt(0);return r>=0&&r<=31})}async function se(e,t){const r=t.params.path||R.homedir(),s=t.params.rootPath||r;if(r===x){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:x,entries:H()}});return}const a=e.resolveAuthorizedPath(r,s);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}const o=a.path;try{const n=l.readdirSync(o,{withFileTypes:!0}),c=v(o)?h.win32.join:h.join,d=n.map(p=>({name:p.name,path:c(o,p.name),isDir:p.isDirectory()})).sort((p,i)=>p.isDir!==i.isDir?p.isDir?-1:1:p.name.localeCompare(i.name));e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:o,entries:d}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:U(n,o)})}}async function oe(e,t){const r=t.params.path,s=t.params.rootPath||r,a=e.resolveAuthorizedPath(r,s);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}const o=a.path,n=t.params.encoding,c=typeof n=="string"?n:"utf8",d=t.params.offset,p=t.params.length;try{const i=l.statSync(o);if(!i.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}if(d!=null&&p!=null){const f=Math.min(p,Math.max(0,i.size-d));if(f<=0){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:"",offset:d,length:0,totalSize:i.size,path:o}});return}const y=l.openSync(o,"r");try{const A=Buffer.alloc(f),z=l.readSync(y,A,0,f,d);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:A.subarray(0,z).toString("base64"),offset:d,length:z,totalSize:i.size,path:o}})}finally{l.closeSync(y)}return}const S=t.params.maxSize||(c==="base64"?5*1024*1024:512*1024);if(i.size>S){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`File too large: ${i.size} bytes (max ${S})`,payload:{size:i.size,path:o}});return}if(c==="base64"){const f=l.readFileSync(o);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:f.toString("base64"),path:o,size:i.size}});return}if($(c)){const f=l.readFileSync(o),y=N(f);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:y.content,path:o,size:i.size,encoding:y.encoding,encodingFallback:y.fallback}});return}const F=l.readFileSync(o,"utf-8");e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:F,path:o,size:i.size}})}catch(i){e.client.sendRes({type:"res",id:t.id,ok:!1,error:i instanceof M?i.message:String(i)})}}async function ae(e,t){const r=t.params.path,s=t.params.content,a=t.params.rootPath||r;if(!r||typeof s!="string"){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and content are required"});return}const o=e.resolveAuthorizedPath(r,a);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}try{const n=l.existsSync(o.path)?l.statSync(o.path):null;if(n&&!n.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}l.writeFileSync(o.path,s,"utf-8");const c=l.statSync(o.path);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:o.path,size:c.size,modifiedAt:c.mtimeMs}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(n)})}}async function ne(e,t){const r=t.params.path,s=t.params.newName;if(!r||!s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and newName are required"});return}if(!W(s)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid newName"});return}const a=m(r),o=t.params.rootPath||a.dirname(r),n=e.resolveAuthorizedPath(r,o);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}try{const c=m(n.path),d=c.join(c.dirname(n.path),s),p=e.resolveAuthorizedPath(d,o);if(!p.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:p.error});return}if(l.existsSync(p.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Target already exists"});return}l.renameSync(n.path,p.path),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{oldPath:n.path,newPath:p.path,entry:g(p.path)}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function ie(e,t){const r=t.params.path,s=t.params.rootPath||h.dirname(r||"."),a=typeof t.params.title=="string"?t.params.title:void 0;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const o=e.resolveAuthorizedPath(r,s);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}if(!/\.mdx?$/i.test(o.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Only Markdown files can be exported to PDF"});return}const n=E(o.path),c=e.resolveAuthorizedPath(n,s);if(!c.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:c.error});return}try{const d=await w(o.path,{outputPath:c.path,title:a});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:o.path,outputPath:d.outputPath,entry:g(d.outputPath)}})}catch(d){if(d instanceof C){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"This machine needs the PDF export component before Markdown files can be exported to PDF.",payload:d.setup});return}e.client.sendRes({type:"res",id:t.id,ok:!1,error:d instanceof Error?d.message:String(d)})}}async function de(e,t){try{await b();const r=await T();e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{ready:!0,browserPath:r,managedPath:I()}})}catch(r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:r instanceof Error?r.message:String(r)})}}function V(e){let t="";for(const s of e.trim())s.charCodeAt(0)<=31||'<>:"/\\|?*'.includes(s)?t.endsWith("-")||(t+="-"):t+=s;return t.replace(/[. ]+$/g,"")||"folder"}async function ce(e,t){const r=t.params.path,s=t.params.rootPath||r;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const a=e.resolveAuthorizedPath(r,s);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}try{if(!l.statSync(a.path).isDirectory()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a directory"});return}const n=m(a.path),c=V(n.basename(a.path)),d=h.join(R.tmpdir(),"shennian-archives"),p=h.join(d,`${c}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),i=D(a.path,p,{maxFiles:L,maxTotalSize:Z});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:a.path,outputPath:i.outputPath,entry:g(i.outputPath),fileCount:i.fileCount,totalSize:i.totalSize}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o instanceof Error?o.message:String(o)})}}async function pe(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const a=B({...r,rootPath:s.path});a.reused||e.pendingTransfers.set(r.transferId,{transferId:r.transferId,rootPath:s.path,lastActivityAt:Date.now()}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),...a,...a.attachment?{attachment:{...u(P()),...a.attachment}}:{}}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function le(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const a=Buffer.from(r.data,"base64"),o=O({rootPath:s.path,transferId:r.transferId,relativePath:r.relativePath,offset:r.offset,data:a});e.pendingTransfers.set(r.transferId,{transferId:r.transferId,rootPath:s.path,lastActivityAt:Date.now()}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),transferId:r.transferId,...o}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function fe(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const a=j(s.path,r.transferId);e.pendingTransfers.delete(r.transferId),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),...a}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function he(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}_(s.path,r.transferId),e.pendingTransfers.delete(r.transferId),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),transferId:r.transferId,aborted:!0}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}function ye(e){e.pendingTransfers.clear()}export{ye as cleanupPendingTransfers,ce as handleFsArchiveZip,ie as handleFsExportMarkdownPdf,de as handleFsExportMarkdownPdfSetup,se as handleFsLs,oe as handleFsRead,ne as handleFsRename,he as handleFsTransferAbort,le as handleFsTransferChunk,fe as handleFsTransferFinish,pe as handleFsTransferStart,ae as handleFsWrite};
|
|
1
|
+
import l from"node:fs";import R from"node:os";import h from"node:path";import{convertMarkdownToPdf as F,defaultPdfOutputPath as E,findChromiumExecutable as T,installManagedChromium as b,managedChromiumBrowsersPath as I,MarkdownPdfBrowserMissingError as C}from"../../tools/markdown-to-pdf.js";import{createZipArchive as D}from"../archive-zip.js";import{BinaryTextPreviewError as M,decodeTextBufferAuto as N,isAutoTextEncoding as $}from"../../fs/text-decoder.js";import{abortAttachmentTransfer as _,appendAttachmentTransferChunk as O,finishAttachmentTransfer as j,startAttachmentTransfer as B}from"../attachment-transfer-store.js";import{assertPersonalSyncIdentity as k,getPersonalSyncIdentity as u}from"../../personal-sync/epoch.js";import{getDaemonInstallationId as P}from"../../personal-sync/identity.js";const x="__roots__",L=5e3,Z=1024*1024*1024;function v(e){return/^[A-Za-z]:([\\/]|$)/.test(e)||/^\\\\[^\\]+\\[^\\]+/.test(e)}function m(e){return v(e)?h.win32:h.posix}function g(e){const t=l.statSync(e);return{name:m(e).basename(e),path:e,isDir:t.isDirectory(),size:t.isFile()?t.size:void 0,modifiedAt:t.mtimeMs}}function H(){if(R.platform()==="win32"){const e=[];for(let t=65;t<=90;t+=1){const r=`${String.fromCharCode(t)}:\\`;l.existsSync(r)&&e.push({name:r,path:r,isDir:!0})}return e}return[{name:"/",path:"/",isDir:!0}]}function U(e,t){const r=typeof e=="object"&&e&&"code"in e?String(e.code):"";return r==="ENOENT"?`Directory not found: ${t}`:r==="EACCES"||r==="EPERM"?`Permission denied: ${t}`:e instanceof Error?e.message:String(e)}function W(e){const t=e.trim();if(!t||t!==e||e==="."||e===".."||X(e)||/[\\/:]/.test(e)||/[<>|"?*]/.test(e)||/[. ]$/.test(e))return!1;const r=e.split(".")[0]?.toUpperCase();return!(r&&/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/.test(r))}function X(e){return Array.from(e).some(t=>{const r=t.charCodeAt(0);return r>=0&&r<=31})}async function se(e,t){const r=t.params.path||R.homedir(),s=t.params.rootPath||r;if(r===x){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:x,entries:H()}});return}const o=await e.resolveAuthorizedPathAsync(r,s);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}const a=o.path;try{const n=await l.promises.readdir(a,{withFileTypes:!0}),c=v(a)?h.win32.join:h.join,d=n.map(p=>({name:p.name,path:c(a,p.name),isDir:p.isDirectory()})).sort((p,i)=>p.isDir!==i.isDir?p.isDir?-1:1:p.name.localeCompare(i.name));e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a,entries:d}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:U(n,a)})}}async function ae(e,t){const r=t.params.path,s=t.params.rootPath||r,o=e.resolveAuthorizedPath(r,s);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}const a=o.path,n=t.params.encoding,c=typeof n=="string"?n:"utf8",d=t.params.offset,p=t.params.length;try{const i=l.statSync(a);if(!i.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}if(d!=null&&p!=null){const f=Math.min(p,Math.max(0,i.size-d));if(f<=0){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:"",offset:d,length:0,totalSize:i.size,path:a}});return}const y=l.openSync(a,"r");try{const A=Buffer.alloc(f),z=l.readSync(y,A,0,f,d);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:A.subarray(0,z).toString("base64"),offset:d,length:z,totalSize:i.size,path:a}})}finally{l.closeSync(y)}return}const S=t.params.maxSize||(c==="base64"?5*1024*1024:512*1024);if(i.size>S){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`File too large: ${i.size} bytes (max ${S})`,payload:{size:i.size,path:a}});return}if(c==="base64"){const f=l.readFileSync(a);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:f.toString("base64"),path:a,size:i.size}});return}if($(c)){const f=l.readFileSync(a),y=N(f);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:y.content,path:a,size:i.size,encoding:y.encoding,encodingFallback:y.fallback}});return}const w=l.readFileSync(a,"utf-8");e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:w,path:a,size:i.size}})}catch(i){e.client.sendRes({type:"res",id:t.id,ok:!1,error:i instanceof M?i.message:String(i)})}}async function oe(e,t){const r=t.params.path,s=t.params.content,o=t.params.rootPath||r;if(!r||typeof s!="string"){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and content are required"});return}const a=e.resolveAuthorizedPath(r,o);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}try{const n=l.existsSync(a.path)?l.statSync(a.path):null;if(n&&!n.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}l.writeFileSync(a.path,s,"utf-8");const c=l.statSync(a.path);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a.path,size:c.size,modifiedAt:c.mtimeMs}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(n)})}}async function ne(e,t){const r=t.params.path,s=t.params.newName;if(!r||!s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and newName are required"});return}if(!W(s)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid newName"});return}const o=m(r),a=t.params.rootPath||o.dirname(r),n=e.resolveAuthorizedPath(r,a);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}try{const c=m(n.path),d=c.join(c.dirname(n.path),s),p=e.resolveAuthorizedPath(d,a);if(!p.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:p.error});return}if(l.existsSync(p.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Target already exists"});return}l.renameSync(n.path,p.path),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{oldPath:n.path,newPath:p.path,entry:g(p.path)}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function ie(e,t){const r=t.params.path,s=t.params.rootPath||h.dirname(r||"."),o=typeof t.params.title=="string"?t.params.title:void 0;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const a=e.resolveAuthorizedPath(r,s);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}if(!/\.mdx?$/i.test(a.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Only Markdown files can be exported to PDF"});return}const n=E(a.path),c=e.resolveAuthorizedPath(n,s);if(!c.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:c.error});return}try{const d=await F(a.path,{outputPath:c.path,title:o});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:a.path,outputPath:d.outputPath,entry:g(d.outputPath)}})}catch(d){if(d instanceof C){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"This machine needs the PDF export component before Markdown files can be exported to PDF.",payload:d.setup});return}e.client.sendRes({type:"res",id:t.id,ok:!1,error:d instanceof Error?d.message:String(d)})}}async function de(e,t){try{await b();const r=await T();e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{ready:!0,browserPath:r,managedPath:I()}})}catch(r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:r instanceof Error?r.message:String(r)})}}function V(e){let t="";for(const s of e.trim())s.charCodeAt(0)<=31||'<>:"/\\|?*'.includes(s)?t.endsWith("-")||(t+="-"):t+=s;return t.replace(/[. ]+$/g,"")||"folder"}async function ce(e,t){const r=t.params.path,s=t.params.rootPath||r;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const o=e.resolveAuthorizedPath(r,s);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}try{if(!l.statSync(o.path).isDirectory()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a directory"});return}const n=m(o.path),c=V(n.basename(o.path)),d=h.join(R.tmpdir(),"shennian-archives"),p=h.join(d,`${c}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),i=D(o.path,p,{maxFiles:L,maxTotalSize:Z});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:o.path,outputPath:i.outputPath,entry:g(i.outputPath),fileCount:i.fileCount,totalSize:i.totalSize}})}catch(a){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a instanceof Error?a.message:String(a)})}}async function pe(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const o=B({...r,rootPath:s.path});o.reused||e.pendingTransfers.set(r.transferId,{transferId:r.transferId,rootPath:s.path,lastActivityAt:Date.now()}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),...o,...o.attachment?{attachment:{...u(P()),...o.attachment}}:{}}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function le(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const o=Buffer.from(r.data,"base64"),a=O({rootPath:s.path,transferId:r.transferId,relativePath:r.relativePath,offset:r.offset,data:o});e.pendingTransfers.set(r.transferId,{transferId:r.transferId,rootPath:s.path,lastActivityAt:Date.now()}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),transferId:r.transferId,...a}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function fe(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const o=j(s.path,r.transferId);e.pendingTransfers.delete(r.transferId),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),...o}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function he(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}_(s.path,r.transferId),e.pendingTransfers.delete(r.transferId),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),transferId:r.transferId,aborted:!0}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}function ye(e){e.pendingTransfers.clear()}export{ye as cleanupPendingTransfers,ce as handleFsArchiveZip,ie as handleFsExportMarkdownPdf,de as handleFsExportMarkdownPdfSetup,se as handleFsLs,ae as handleFsRead,ne as handleFsRename,he as handleFsTransferAbort,le as handleFsTransferChunk,fe as handleFsTransferFinish,pe as handleFsTransferStart,oe as handleFsWrite};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SESSION_SYNC_PROTOCOL_VERSION as p,SESSION_SYNC_DATA_EPOCH as f,isAvailableAgentType as k}from"@shennian/wire";import g from"node:crypto";import{getRegisteredAgents as S,unregisterAgent as A}from"../agents/adapter.js";import{loadConfig as T}from"../config/index.js";import{handleUpgradeStart as I,handleUpgradeStatus as C}from"../commands/upgrade.js";import{handleAgentsRefresh as P,handleModelsRefresh as E}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as x,handleAgentConfigGet as M,handleAgentConfigTest as O,handleAgentConfigUpsert as F}from"./handlers/agent-config.js";import{handleChatAbort as _,handleChatSend as v}from"./handlers/chat.js";import{handleSessionRefresh as N}from"./handlers/session-refresh.js";import{handleSessionToolDetail as Q}from"./handlers/tool-detail.js";import{handleSessionTitleSet as U}from"./handlers/title.js";import{handleSessionMessageStatus as W}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as z}from"./handlers/voice-window.js";import{handleRemoteAccessPause as V}from"./handlers/remote-access.js";import{cleanupPendingTransfers as B,handleFsLs as j,handleFsRead as $,handleFsRename as L,handleFsWrite as H,handleFsTransferAbort as Y,handleFsTransferChunk as G,handleFsTransferFinish as K,handleFsTransferStart as X,handleFsExportMarkdownPdf as Z,handleFsExportMarkdownPdfSetup as J,handleFsArchiveZip as q}from"./handlers/fs.js";import{handleRegionProbe as ee,handleRegionSwitch as se,handleUpgradeSetPolicy as te}from"./handlers/control.js";import{ManagerRuntimeService as ae,setManagerRuntimeService as b}from"../manager/runtime.js";import{ChatQueueManager as ie}from"./queue.js";import{createAuthorizedFsRoot as ne,resolveAuthorizedPath as oe,resolveSessionWorkDir as re}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/manager.js";import{registerCustomAgent as ce}from"../agents/custom.js";import{handleManagedRoomBind as le,handleManagedRoomRestart as he}from"./handlers/room-managed.js";import{getDaemonInstallationId as R,listSessionRecords as y,purgeCanonicalSession as de,purgeSessionRecord as ue}from"./store.js";import{PersonalHistorySubscriptions as me}from"./history-subscriptions.js";import{sweepAttachmentTransfers as pe}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as fe}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as ge}from"./tool-detail-store.js";import{readDeletionSyncRevision as ve,writeDeletionSyncRevision as w}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as be,clearDeletionSyncFailure as Re,DeletionSyncRetryWaitError as ye,DeletionSyncTerminalError as we,recordDeletionSyncFailure as m}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as ke}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as l}from"../personal-sync/epoch.js";const Se=50;function Ae(r,e){if(!r||!Number.isSafeInteger(r.baselineRevision)||r.baselineRevision<e||!Number.isSafeInteger(r.latestRevision)||r.latestRevision<r.baselineRevision||!Array.isArray(r.deletions))throw new Error("invalid deletion sync response");let s=r.baselineRevision;for(const t of r.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of t.sources)if(!a?.sourceSessionKey||!k(a.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==r.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as us}from"../fs/boundary.js";class ls{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;constructor(e,s=null,t,a=null,i=null,c=null,n=null,h=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=c,this.managedRoomContextRevoker=n,this.managedRoomExecutor=h,this.managerRuntime=new ae({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o)}),this.chatQueue=new ie({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o),dispatchQueuedReq:o=>this.dispatchQueuedChat(o)}),this.historySubscriptions=new me(this.client),this.nativeCleanupOutbox=new ke({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(o=>{this.handleExternalTerminal(o)}),this.nativeCleanupOutbox.start(),b(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(o=>{console.error("[session.activity] managed probe failed",o)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const o=new Set([...y().map(d=>d.workDir),...Array.from(this.pendingTransfers.values()).map(d=>d.rootPath)]);for(const d of o)try{pe(this.resolvePath(d))}catch(u){console.error("[attachment.cleanup] failed",u instanceof Error?u.message:String(u))}},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,s)=>this.resolveAuthorizedPath(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.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}}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}async enqueueManagedRoomBatch(e){const s=e.map(t=>{const a=y().find(i=>i.sessionId===t.params.sessionId);return{context:t.context,params:{...t.params,modelId:a?.modelId??t.params.modelId,sessionMode:a?.sessionMode??t.params.sessionMode,managerConfig:a?.managerConfig??t.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??t.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??t.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(s)}async dispatchQueuedChat(e){const s={value:null},t=new Proxy(this.client,{get:(c,n)=>{if(n==="sendRes")return o=>{s.value=o};const h=Reflect.get(c,n,c);return typeof h=="function"?h.bind(c):h}}),a=e.params,i=a.roomActivation===!0?{source:"room_activation",managedAgentPolicy:a.managedAgentPolicy,managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:void 0;if(await v({...this.getRuntime(),client:t},e,i),!s.value?.ok||s.value.payload?.deliveryState==="failed")throw new Error(s.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){be();try{await this.synchronizeSessionDeletionsOnce(),Re()}catch(e){if(e instanceof ye||e instanceof we)throw e;const s=e instanceof Error?e.message:String(e),t=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(s);throw m({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=R(),s=ve(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const n=t.rejection;throw n?(m({code:n.code,retryable:n.retryable&&n.requiredAction==="retry"}),new Error(n.code)):(m({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const a=t.payload;l(a),Ae(a,s);let i=a.baselineRevision;i>s&&w(i);for(const n of a.deletions)n.deletionRevision<=i||(await this.applySessionDeletion(n),i=n.deletionRevision,w(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const c=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,deletionRevision:i}});if(!c.ok)throw new Error(c.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new fe({daemonInstallationId:R()});s.recordSession(e);for(const a of e.sources)s.record({...e,...a});const t=this.sessions.get(e.sessionId);if(t){this.revokeManagedRoomToken(t);try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),ge(e.sessionId),de(e.sessionId),ue(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,s){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:s}})}async handleExternalTerminal(e){const s=this.sessions.get(e);s&&(this.sessions.delete(e),this.revokeManagedRoomToken(s),s.heartbeatTimer&&clearInterval(s.heartbeatTimer),s.adapter.removeAllListeners(),await s.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:a,canStop:t.canStop??!0});const i=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:i,runPhase:t.runPhase,canStop:t.canStop??!0},seq:i,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of S())s.startsWith("custom:")&&A(s);const e=T();for(const[s,t]of Object.entries(e.customAgents??{}))ce(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":l(e.params),await v(s,e);break;case"chat.enqueue":l(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":l(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":l(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":l(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":l(e.params),await _(s,e);break;case"session.refresh":l(e.params),await N(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":W(s,e);break;case"session.tool.detail":await Q(s,e);break;case"voice.session.body.window":z(s,e);break;case"session.title.set":await U(s,e);break;case"room.agent.bind-managed":await le(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await he(s,this.managedRoomBinder,e);break;case"fs.ls":await j(s,e);break;case"fs.read":await $(s,e);break;case"fs.write":await H(s,e);break;case"fs.export.markdown-pdf":await Z(s,e);break;case"fs.export.markdown-pdf.setup":await J(s,e);break;case"fs.archive.zip":await q(s,e);break;case"fs.rename":await L(s,e);break;case"fs.transfer.start":await X(s,e);break;case"fs.transfer.chunk":await G(s,e);break;case"fs.transfer.finish":await K(s,e);break;case"fs.transfer.abort":await Y(s,e);break;case"region.probe":await ee(s,e);break;case"region.switch":await se(s,e);break;case"upgrade.start":await I(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await C(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await te(s,e);break;case"machine.remote-access.pause":await V(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await P(s,e);break;case"models.refresh":await E(s,e);break;case"agent.config.get":await M(s,e);break;case"agent.config.upsert":await F(s,e);break;case"agent.config.clear":await x(s,e);break;case"agent.config.test":await O(s,e);break;case"agent.capabilities.list":await D(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(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<Se)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});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 re(e)}resolveAuthorizedPath(e,s){return oe(e,ne(s))}async cleanup(){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),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),B(this.getRuntime()),await this.managerRuntime.stop(),b(null)}revokeManagedRoomToken(e){const s=e.managedRoomContextToken;e.managedRoomContextToken=null,s&&this.managedRoomContextRevoker?.(s)}}export{ls as SessionManager,us as resolveSessionWorkDir};
|
|
1
|
+
import{SESSION_SYNC_PROTOCOL_VERSION as p,SESSION_SYNC_DATA_EPOCH as f,isAvailableAgentType as k}from"@shennian/wire";import g from"node:crypto";import{getRegisteredAgents as S,unregisterAgent as A}from"../agents/adapter.js";import{loadConfig as T}from"../config/index.js";import{handleUpgradeStart as I,handleUpgradeStatus as C}from"../commands/upgrade.js";import{handleAgentsRefresh as P,handleModelsRefresh as E}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as x,handleAgentConfigGet as M,handleAgentConfigTest as O,handleAgentConfigUpsert as F}from"./handlers/agent-config.js";import{handleChatAbort as _,handleChatSend as v}from"./handlers/chat.js";import{handleSessionRefresh as N}from"./handlers/session-refresh.js";import{handleSessionToolDetail as z}from"./handlers/tool-detail.js";import{handleSessionTitleSet as Q}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as W}from"./handlers/voice-window.js";import{handleRemoteAccessPause as V}from"./handlers/remote-access.js";import{cleanupPendingTransfers as B,handleFsLs as j,handleFsRead as $,handleFsRename as L,handleFsWrite as H,handleFsTransferAbort as Y,handleFsTransferChunk as G,handleFsTransferFinish as K,handleFsTransferStart as X,handleFsExportMarkdownPdf as Z,handleFsExportMarkdownPdfSetup as J,handleFsArchiveZip as q}from"./handlers/fs.js";import{handleRegionProbe as ee,handleRegionSwitch as se,handleUpgradeSetPolicy as te}from"./handlers/control.js";import{ManagerRuntimeService as ae,setManagerRuntimeService as b}from"../manager/runtime.js";import{ChatQueueManager as ie}from"./queue.js";import{createAuthorizedFsRoot as ne,createAuthorizedFsRootAsync as oe,resolveAuthorizedPath as re,resolveAuthorizedPathAsync as ce,resolveSessionWorkDir as le}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/manager.js";import{registerCustomAgent as he}from"../agents/custom.js";import{handleManagedRoomBind as de,handleManagedRoomRestart as ue}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as R,purgeCanonicalSession as me,purgeSessionRecord as pe}from"./store.js";import{PersonalHistorySubscriptions as fe}from"./history-subscriptions.js";import{sweepAttachmentTransfers as ge}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ve}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as be}from"./tool-detail-store.js";import{readDeletionSyncRevision as ye,writeDeletionSyncRevision as w}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as Re,clearDeletionSyncFailure as we,DeletionSyncRetryWaitError as ke,DeletionSyncTerminalError as Se,recordDeletionSyncFailure as m}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Ae}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as l}from"../personal-sync/epoch.js";const Te=50;function Ie(r,e){if(!r||!Number.isSafeInteger(r.baselineRevision)||r.baselineRevision<e||!Number.isSafeInteger(r.latestRevision)||r.latestRevision<r.baselineRevision||!Array.isArray(r.deletions))throw new Error("invalid deletion sync response");let s=r.baselineRevision;for(const t of r.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of t.sources)if(!a?.sourceSessionKey||!k(a.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==r.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as ps}from"../fs/boundary.js";class ds{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;constructor(e,s=null,t,a=null,i=null,c=null,n=null,h=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=c,this.managedRoomContextRevoker=n,this.managedRoomExecutor=h,this.managerRuntime=new ae({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o)}),this.chatQueue=new ie({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o),dispatchQueuedReq:o=>this.dispatchQueuedChat(o)}),this.historySubscriptions=new fe(this.client),this.nativeCleanupOutbox=new Ae({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(o=>{this.handleExternalTerminal(o)}),this.nativeCleanupOutbox.start(),b(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(o=>{console.error("[session.activity] managed probe failed",o)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const o=new Set([...R().map(d=>d.workDir),...Array.from(this.pendingTransfers.values()).map(d=>d.rootPath)]);for(const d of o)try{ge(this.resolvePath(d))}catch(u){console.error("[attachment.cleanup] failed",u instanceof Error?u.message:String(u))}},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,s)=>this.resolveAuthorizedPath(e,s),resolveAuthorizedPathAsync:(e,s)=>this.resolveAuthorizedPathAsync(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.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}}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}async enqueueManagedRoomBatch(e){const s=e.map(t=>{const a=R().find(i=>i.sessionId===t.params.sessionId);return{context:t.context,params:{...t.params,modelId:a?.modelId??t.params.modelId,sessionMode:a?.sessionMode??t.params.sessionMode,managerConfig:a?.managerConfig??t.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??t.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??t.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(s)}async dispatchQueuedChat(e){const s={value:null},t=new Proxy(this.client,{get:(c,n)=>{if(n==="sendRes")return o=>{s.value=o};const h=Reflect.get(c,n,c);return typeof h=="function"?h.bind(c):h}}),a=e.params,i=a.roomActivation===!0?{source:"room_activation",managedAgentPolicy:a.managedAgentPolicy,managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:void 0;if(await v({...this.getRuntime(),client:t},e,i),!s.value?.ok||s.value.payload?.deliveryState==="failed")throw new Error(s.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){Re();try{await this.synchronizeSessionDeletionsOnce(),we()}catch(e){if(e instanceof ke||e instanceof Se)throw e;const s=e instanceof Error?e.message:String(e),t=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(s);throw m({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),s=ye(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const n=t.rejection;throw n?(m({code:n.code,retryable:n.retryable&&n.requiredAction==="retry"}),new Error(n.code)):(m({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const a=t.payload;l(a),Ie(a,s);let i=a.baselineRevision;i>s&&w(i);for(const n of a.deletions)n.deletionRevision<=i||(await this.applySessionDeletion(n),i=n.deletionRevision,w(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const c=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,deletionRevision:i}});if(!c.ok)throw new Error(c.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new ve({daemonInstallationId:y()});s.recordSession(e);for(const a of e.sources)s.record({...e,...a});const t=this.sessions.get(e.sessionId);if(t){this.revokeManagedRoomToken(t);try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),be(e.sessionId),me(e.sessionId),pe(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,s){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:s}})}async handleExternalTerminal(e){const s=this.sessions.get(e);s&&(this.sessions.delete(e),this.revokeManagedRoomToken(s),s.heartbeatTimer&&clearInterval(s.heartbeatTimer),s.adapter.removeAllListeners(),await s.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:a,canStop:t.canStop??!0});const i=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:i,runPhase:t.runPhase,canStop:t.canStop??!0},seq:i,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of S())s.startsWith("custom:")&&A(s);const e=T();for(const[s,t]of Object.entries(e.customAgents??{}))he(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":l(e.params),await v(s,e);break;case"chat.enqueue":l(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":l(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":l(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":l(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":l(e.params),await _(s,e);break;case"session.refresh":l(e.params),await N(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":U(s,e);break;case"session.tool.detail":await z(s,e);break;case"voice.session.body.window":W(s,e);break;case"session.title.set":await Q(s,e);break;case"room.agent.bind-managed":await de(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await ue(s,this.managedRoomBinder,e);break;case"fs.ls":await j(s,e);break;case"fs.read":await $(s,e);break;case"fs.write":await H(s,e);break;case"fs.export.markdown-pdf":await Z(s,e);break;case"fs.export.markdown-pdf.setup":await J(s,e);break;case"fs.archive.zip":await q(s,e);break;case"fs.rename":await L(s,e);break;case"fs.transfer.start":await X(s,e);break;case"fs.transfer.chunk":await G(s,e);break;case"fs.transfer.finish":await K(s,e);break;case"fs.transfer.abort":await Y(s,e);break;case"region.probe":await ee(s,e);break;case"region.switch":await se(s,e);break;case"upgrade.start":await I(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await C(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await te(s,e);break;case"machine.remote-access.pause":await V(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await P(s,e);break;case"models.refresh":await E(s,e);break;case"agent.config.get":await M(s,e);break;case"agent.config.upsert":await F(s,e);break;case"agent.config.clear":await x(s,e);break;case"agent.config.test":await O(s,e);break;case"agent.capabilities.list":await D(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(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<Te)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});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 le(e)}resolveAuthorizedPath(e,s){return re(e,ne(s))}async resolveAuthorizedPathAsync(e,s){return ce(e,await oe(s))}async cleanup(){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),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),B(this.getRuntime()),await this.managerRuntime.stop(),b(null)}revokeManagedRoomToken(e){const s=e.managedRoomContextToken;e.managedRoomContextToken=null,s&&this.managedRoomContextRevoker?.(s)}}export{ds as SessionManager,ps as resolveSessionWorkDir};
|
|
@@ -66,6 +66,7 @@ export type SessionManagerRuntime = {
|
|
|
66
66
|
reloadCustomAgents: () => void;
|
|
67
67
|
resolvePath: (pathValue: string) => string;
|
|
68
68
|
resolveAuthorizedPath: (pathValue: string, rootPath: string) => ResolveAuthorizedPathResult;
|
|
69
|
+
resolveAuthorizedPathAsync: (pathValue: string, rootPath: string) => Promise<ResolveAuthorizedPathResult>;
|
|
69
70
|
runTextAcc: Map<string, string>;
|
|
70
71
|
sessions: Map<string, ActiveSession>;
|
|
71
72
|
evictIdleSessions: () => void;
|
|
@@ -4,6 +4,7 @@ const GIT_DIRECTIVE_LINE_RE = /^\s*::git-[a-z-]+\{[^\n]*\}\s*$/gm;
|
|
|
4
4
|
const TOOL_PAYLOAD_PREFIX_RE = /^\s*\{\s*"v"\s*:\s*1\s*,\s*"type"\s*:\s*"(tool|tool_use|tool_result)"/;
|
|
5
5
|
const ENVIRONMENT_CONTEXT_RE = /^\s*<environment_context>\s*[^]*?<\/environment_context>\s*$/i;
|
|
6
6
|
const ENVIRONMENT_CONTEXT_REQUIRED_TAG_RE = /<(cwd|shell|current_date|timezone)>[^<]*<\/\1>/gi;
|
|
7
|
+
const CODEX_RECOMMENDED_PLUGINS_PREFIX_RE = /^\s*<recommended_plugins>[^]*?<\/recommended_plugins>\s*/i;
|
|
7
8
|
const CODEX_TURN_ABORTED_RE = /^\s*<turn_aborted>[^]*?<\/turn_aborted>\s*$/i;
|
|
8
9
|
const CODEX_INTERNAL_CONTEXT_RE = /^\s*<codex_internal_context\b[^>]*>[^]*?<\/codex_internal_context>\s*$/i;
|
|
9
10
|
const CODEX_PROJECT_INSTRUCTIONS_RE = /^\s*# AGENTS\.md instructions for [^\n]+\n+\s*<INSTRUCTIONS>\s*[^]*?<\/INSTRUCTIONS>\s*(?:<environment_context>[^]*?<\/environment_context>)?\s*$/i;
|
|
@@ -202,6 +203,15 @@ export function isCodexProjectInstructionsPayload(payload) {
|
|
|
202
203
|
return CODEX_PROJECT_INSTRUCTIONS_RE.test(payload);
|
|
203
204
|
}
|
|
204
205
|
export function isSystemControlPayload(payload) {
|
|
206
|
+
// Codex can inject the catalog alone or before environment/project metadata.
|
|
207
|
+
// Consume only a complete prefix and require the entire remainder to be control
|
|
208
|
+
// context, so discussions, code samples and appended user requests stay visible.
|
|
209
|
+
const plugins = payload.match(CODEX_RECOMMENDED_PLUGINS_PREFIX_RE);
|
|
210
|
+
if (plugins) {
|
|
211
|
+
const remainder = payload.slice(plugins[0].length).trim();
|
|
212
|
+
return !remainder || isSystemEnvironmentContextPayload(remainder) ||
|
|
213
|
+
isCodexProjectInstructionsPayload(remainder);
|
|
214
|
+
}
|
|
205
215
|
return (isSystemEnvironmentContextPayload(payload) ||
|
|
206
216
|
isCodexTurnAbortedPayload(payload) ||
|
|
207
217
|
CODEX_INTERNAL_CONTEXT_RE.test(payload) ||
|