shennian 0.3.41 → 0.3.43
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 +93 -43
- package/dist/src/agents/adapter.d.ts +1 -0
- package/dist/src/commands/daemon.d.ts +6 -0
- package/dist/src/commands/daemon.js +9 -9
- package/dist/src/index.js +3 -3
- package/dist/src/native-fusion/codex-parser.d.ts +1 -0
- package/dist/src/native-fusion/codex-parser.js +4 -4
- package/dist/src/native-fusion/file-watcher.d.ts +18 -0
- package/dist/src/native-fusion/file-watcher.js +1 -0
- package/dist/src/native-fusion/local-repository.d.ts +52 -0
- package/dist/src/native-fusion/local-repository.js +2 -0
- package/dist/src/native-fusion/opencode-parser.js +2 -2
- package/dist/src/native-fusion/parser-common.d.ts +7 -1
- package/dist/src/native-fusion/parser-common.js +4 -4
- package/dist/src/native-fusion/parsers.d.ts +2 -1
- package/dist/src/native-fusion/parsers.js +2 -2
- package/dist/src/native-fusion/service.d.ts +10 -2
- package/dist/src/native-fusion/service.js +2 -2
- package/dist/src/native-fusion/session-header.d.ts +9 -0
- package/dist/src/native-fusion/session-header.js +2 -0
- package/dist/src/native-fusion/state.d.ts +1 -1
- package/dist/src/native-fusion/state.js +2 -1
- package/dist/src/native-fusion/types.d.ts +29 -5
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/personal-sync/paths.d.ts +2 -0
- package/dist/src/personal-sync/paths.js +1 -0
- package/dist/src/relay/client.d.ts +4 -1
- package/dist/src/relay/client.js +1 -1
- package/dist/src/session/attachment-reference.d.ts +2 -0
- package/dist/src/session/attachment-reference.js +1 -0
- package/dist/src/session/attachment-transfer-store.d.ts +38 -0
- package/dist/src/session/attachment-transfer-store.js +2 -0
- package/dist/src/session/canonical-store.d.ts +101 -0
- package/dist/src/session/canonical-store.js +5 -0
- package/dist/src/session/handlers/chat.js +2 -2
- package/dist/src/session/handlers/fs.d.ts +0 -1
- package/dist/src/session/handlers/fs.js +1 -1
- package/dist/src/session/handlers/message-status.d.ts +3 -0
- package/dist/src/session/handlers/message-status.js +1 -0
- package/dist/src/session/handlers/title.js +1 -1
- package/dist/src/session/handlers/tool-detail.js +1 -1
- package/dist/src/session/handlers/voice-window.d.ts +3 -0
- package/dist/src/session/handlers/voice-window.js +1 -0
- package/dist/src/session/history-subscriptions.d.ts +17 -0
- package/dist/src/session/history-subscriptions.js +1 -0
- package/dist/src/session/index-publisher.d.ts +3 -0
- package/dist/src/session/index-publisher.js +1 -0
- package/dist/src/session/manager.d.ts +2 -0
- package/dist/src/session/manager.js +1 -1
- package/dist/src/session/queue.d.ts +1 -1
- package/dist/src/session/queue.js +4 -3
- package/dist/src/session/store.d.ts +33 -3
- package/dist/src/session/store.js +2 -3
- package/dist/src/session/tool-detail-store.d.ts +16 -0
- package/dist/src/session/tool-detail-store.js +2 -0
- package/dist/src/session/types.d.ts +3 -13
- package/node_modules/@shennian/wire/dist/events.d.ts +2 -0
- package/node_modules/@shennian/wire/dist/fs.d.ts +45 -0
- package/node_modules/@shennian/wire/dist/fs.js +7 -1
- package/node_modules/@shennian/wire/dist/index.d.ts +1 -0
- package/node_modules/@shennian/wire/dist/index.js +1 -0
- package/node_modules/@shennian/wire/dist/machine.d.ts +6 -0
- package/node_modules/@shennian/wire/dist/message-payload.d.ts +2 -0
- package/node_modules/@shennian/wire/dist/message-payload.js +15 -1
- package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
- package/node_modules/@shennian/wire/dist/session-sync.d.ts +197 -0
- package/node_modules/@shennian/wire/dist/session-sync.js +49 -0
- package/node_modules/@shennian/wire/dist/session.d.ts +24 -13
- package/node_modules/@shennian/wire/dist/tool-detail.d.ts +18 -11
- package/node_modules/@shennian/wire/dist/tool-detail.js +7 -1
- package/package.json +1 -1
- package/dist/src/session/remote-attachments.d.ts +0 -15
- package/dist/src/session/remote-attachments.js +0 -1
- package/dist/src/session/tool-summary.d.ts +0 -1
- package/dist/src/session/tool-summary.js +0 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CanonicalSessionStore } from '../session/canonical-store.js';
|
|
2
|
+
import { listSessionRecords, recordSession } from '../session/store.js';
|
|
3
|
+
import type { ParsedNativeEvent } from './types.js';
|
|
4
|
+
import { type LocalToolDetail } from '../session/tool-detail-store.js';
|
|
5
|
+
declare const SCHEMA_VERSION: 1;
|
|
6
|
+
export declare class MissingUserBoundaryError extends Error {
|
|
7
|
+
readonly code = "missing_user_boundary";
|
|
8
|
+
constructor(sourceSessionKey: string);
|
|
9
|
+
}
|
|
10
|
+
type SourceBinding = {
|
|
11
|
+
schemaVersion: typeof SCHEMA_VERSION;
|
|
12
|
+
daemonInstallationId: string;
|
|
13
|
+
sessionId: string;
|
|
14
|
+
agentType: ParsedNativeEvent['agentType'];
|
|
15
|
+
sourceSessionKey: string;
|
|
16
|
+
sourceMode: ParsedNativeEvent['sourceMode'];
|
|
17
|
+
lastCursor: string | null;
|
|
18
|
+
bodyRevision: number;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
};
|
|
22
|
+
export type NativeLocalCommitResult = {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
messageId: string;
|
|
25
|
+
bodyRevision: number;
|
|
26
|
+
duplicate: boolean;
|
|
27
|
+
claimed: boolean;
|
|
28
|
+
};
|
|
29
|
+
type RecordSessionInput = Parameters<typeof recordSession>[0];
|
|
30
|
+
export declare class NativeFusionLocalRepository {
|
|
31
|
+
private options;
|
|
32
|
+
constructor(options?: {
|
|
33
|
+
rootDir?: string;
|
|
34
|
+
daemonInstallationId?: string;
|
|
35
|
+
canonicalStore?: CanonicalSessionStore;
|
|
36
|
+
recordLocalSession?: (input: RecordSessionInput) => unknown;
|
|
37
|
+
listLocalSessions?: typeof listSessionRecords;
|
|
38
|
+
writeLocalToolDetail?: (detail: Omit<LocalToolDetail, 'schemaVersion'>) => void;
|
|
39
|
+
});
|
|
40
|
+
commitEvents(events: readonly ParsedNativeEvent[]): NativeLocalCommitResult[];
|
|
41
|
+
commitEvent(event: ParsedNativeEvent): NativeLocalCommitResult;
|
|
42
|
+
listSourceBindings(): SourceBinding[];
|
|
43
|
+
observeNativeTitle(agentType: ParsedNativeEvent['agentType'], sourceSessionKey: string, titleValue: string): boolean;
|
|
44
|
+
private recordIndex;
|
|
45
|
+
private readSources;
|
|
46
|
+
private installationId;
|
|
47
|
+
private canonicalStore;
|
|
48
|
+
private rootDir;
|
|
49
|
+
private sourcesFile;
|
|
50
|
+
private ledgerFile;
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import d from"node:crypto";import a from"node:fs";import w from"node:path";import{resolvePersonalSyncV1Path as M}from"../personal-sync/paths.js";import{getCanonicalSessionStore as F,getDaemonInstallationId as _,listSessionRecords as k,recordSession as K}from"../session/store.js";import{writeToolDetail as B}from"../session/tool-detail-store.js";const S=1;class C 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{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((o,t)=>o.ts-t.ts).map(o=>this.commitEvent(o))}commitEvent(e){O(e);const o=this.installationId(),t=this.readSources(),i=x(e.agentType,e.sourceSessionKey),r=t.sources[i],l=A(e),u=this.ledgerFile(e.agentType,e.sourceEventKey),n=a.existsSync(u)?D(u):null;if(n&&n.eventFingerprint!==l)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!r&&!n&&e.role!=="user")throw new C(e.sourceSessionKey);const g=e.claimSessionId??null,c=r?.sessionId??n?.sessionId??g??H(o,e.agentType,e.sourceSessionKey);if(g&&r&&r.sessionId!==g)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const f=this.canonicalStore(),p=e.claimCanonicalMessageId??R(o,e.agentType,e.sourceEventKey);let y=f.getManifest(c)?.bodyRevision??0,E=!!n;const m=!!e.claimCanonicalMessageId;if(m){const h=f.getMessageState(c,p);if(!h||h.tombstoned)throw new Error(`managed echo claim target is not durable: ${p}`)}if(!n&&!m){const h={id:p,sessionId:c,role:e.role,ts:e.ts,payload:e.payload},$=f.append(c,h);y=$.bodyRevision,E=$.duplicate,e.localToolDetail&&(this.options.writeLocalToolDetail??B)({sessionId:c,...e.localToolDetail,ts:e.ts,detailRef:{toolIndex:0}})}n||this.recordIndex(e,c);const I=new Date().toISOString();return n||b(u,{schemaVersion:S,daemonInstallationId:o,sessionId:c,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:l,canonicalMessageId:p,canonicalRevision:y,cursor:e.cursor??null,mappedBy:m?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:I}),t.sources[i]={schemaVersion:S,daemonInstallationId:o,sessionId:c,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:n?r?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:n?r?.lastCursor??n.cursor:e.cursor??r?.lastCursor??null,bodyRevision:y,createdAt:r?.createdAt??I,updatedAt:I},b(this.sourcesFile(),t),{sessionId:c,messageId:p,bodyRevision:y,duplicate:E,claimed:m}}listSourceBindings(){return Object.values(this.readSources().sources)}observeNativeTitle(e,o,t){const i=t.trim().slice(0,120);if(!i)return!1;const r=this.readSources().sources[x(e,o)];if(!r)return!1;const l=(this.options.listLocalSessions??k)().find(g=>g.sessionId===r.sessionId);if(l?.titleLockedByUser||l?.titleSource==="agent_native"&&l.title===i)return!1;const u=d.createHash("sha256").update(`${e}\0${o}\0${i}`).digest("hex");return(this.options.recordLocalSession??K)({sessionId:r.sessionId,agentType:r.agentType,workDir:l?.workDir??"",agentSessionId:o,title:i,titleSource:"agent_native",titleOriginEventId:`agent-native:${u}`}),!0}recordIndex(e,o){(this.options.recordLocalSession??K)({sessionId:o,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?"completed":"active",lastActivityAt:new Date(e.ts).toISOString(),lastMessagePreview:e.role==="user"?T(e.payload):void 0,title:e.title?.trim()||(e.role==="user"?T(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(!a.existsSync(e))return{schemaVersion:S,daemonInstallationId:this.installationId(),sources:{}};const o=D(e);if(o.schemaVersion!==S||o.daemonInstallationId!==this.installationId()||!o.sources)throw new Error("invalid native source binding store");return o}installationId(){return this.options.daemonInstallationId??_()}canonicalStore(){return this.options.canonicalStore??F()}rootDir(){return this.options.rootDir??M("native-fusion")}sourcesFile(){return w.join(this.rootDir(),"sources.json")}ledgerFile(e,o){const t=d.createHash("sha256").update(`${e}\0${o}`).digest("hex");return w.join(this.rootDir(),"ledger",`${t}.json`)}}function O(s){if(!s.agentType||!s.sourceSessionKey||!s.sourceEventKey||!s.payload)throw new Error("invalid native event");if(!Number.isFinite(s.ts))throw new Error("invalid native event timestamp")}function x(s,e){return d.createHash("sha256").update(`${s}\0${e}`).digest("hex")}function H(s,e,o){return`native-session-${d.createHash("sha256").update(`${s}\0${e}\0${o}`).digest("hex").slice(0,32)}`}function R(s,e,o){return`native-${d.createHash("sha256").update(`${s}\0${e}\0${o}`).digest("hex").slice(0,32)}`}function A(s){return d.createHash("sha256").update(JSON.stringify({agentType:s.agentType,sourceSessionKey:s.sourceSessionKey,sourceEventKey:s.sourceEventKey,role:s.role,ts:s.ts,payload:s.payload,claimCanonicalMessageId:s.claimCanonicalMessageId??null,claimSessionId:s.claimSessionId??null})).digest("hex")}function T(s){const e=s.replace(/\s+/g," ").trim();return e?e.length>120?`${e.slice(0,120)}...`:e:null}function b(s,e){const o=w.dirname(s);a.mkdirSync(o,{recursive:!0,mode:448});const t=`${s}.tmp-${process.pid}-${d.randomBytes(6).toString("hex")}`;let i;try{i=a.openSync(t,"wx",384),a.writeFileSync(i,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8"),a.fsyncSync(i),a.closeSync(i),i=void 0,a.renameSync(t,s)}finally{i!==void 0&&a.closeSync(i);try{a.unlinkSync(t)}catch{}}}function D(s){return JSON.parse(a.readFileSync(s,"utf8"))}export{C as MissingUserBoundaryError,W as NativeFusionLocalRepository};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import d from"node:fs";import
|
|
1
|
+
import d from"node:fs";import _ from"node:os";import u from"node:path";import{resolveBuiltinCommand as S,spawnResolvedCommandSync as v}from"../agents/command-spec.js";import{normalizeNativeTimestamp as f}from"./session-header.js";function l(t){try{const e=JSON.parse(t);return typeof e=="object"&&e!==null?e:null}catch{return null}}function O(t){return t.filter(e=>e.type==="text"||e.type==="reasoning").map(e=>typeof e.text=="string"?e.text:"").filter(Boolean).join(`
|
|
2
2
|
|
|
3
|
-
`).trim()}function h(t,e,s,r,c,n,i,o){n
|
|
3
|
+
`).trim()}function h(t,e,s,r,c,n,i,o){if(!n)return;const a=f(r.time_created),p=f(s.time_created);!a||!p||t.push({agentType:"opencode",sourceMode:"opencode_session_import",sourceSessionKey:s.id,sourceEventKey:`opencode:${s.id}:${r.id}:${c}`,cursor:`${e}:${r.time_created}:${r.id}`,role:c,ts:a,payload:n,title:s.title,titleSource:"agent_native",modelId:i,workDir:s.directory,terminal:o,sourceSessionCreatedAtMs:p})}function x(t,e){const s=[],r=new Map(e.sessions.map(n=>[n.id,n])),c=new Map;for(const n of e.parts){const i=l(n.data);if(!i)continue;const o=c.get(n.message_id)??[];o.push(i),c.set(n.message_id,o)}for(const n of[...e.messages].sort((i,o)=>i.time_created-o.time_created)){const i=r.get(n.session_id);if(!i)continue;const o=l(n.data);if(!o)continue;const a=o.role==="user"?"user":o.role==="assistant"?"agent":null;if(!a)continue;const p=c.get(n.id)??[],y=O(p),g=typeof o.modelID=="string"?`${typeof o.providerID=="string"?`${o.providerID}/`:""}${o.modelID}`:null;h(s,t,i,n,a,y,g,a==="agent")}return s}function I(){const t=u.join(_.homedir(),".local","share","opencode"),e=u.join(t,"shennian-opencode-session-snapshot.opencode-session.json");return C(e),d.existsSync(e)?[e]:[]}function m(t){const e=S("opencode");if(!e)return[];const s=v(e,["db",t,"--format","json"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e4});if(s.status!==0)return[];try{const r=JSON.parse(String(s.stdout??""));return Array.isArray(r)?r:[]}catch{return[]}}function C(t){try{const e=m("select id, title, directory, time_created, time_updated from session where time_archived is null order by time_updated asc");if(e.length===0)return;const s=m("select id, session_id, time_created, data from message order by time_created asc, id asc"),r=m("select id, message_id, session_id, time_created, data from part order by message_id asc, id asc");d.mkdirSync(u.dirname(t),{recursive:!0}),d.writeFileSync(t,JSON.stringify({sessions:e,messages:s,parts:r}))}catch{}}function z(t,e){const s=d.statSync(t);if(e>=s.size)return{nextOffset:s.size,events:[]};if(e>0)return{nextOffset:s.size,events:[]};const r=JSON.parse(d.readFileSync(t,"utf8"));return{nextOffset:s.size,events:x(t,r)}}export{I as listOpenCodeSessionFiles,z as parseOpenCodeSessionFile,x as parseOpenCodeSessionSnapshot};
|
|
@@ -15,7 +15,13 @@ export declare function makeEventKey(filePath: string, offset: number, kind: str
|
|
|
15
15
|
export declare function safeStringify(value: unknown): string;
|
|
16
16
|
export declare function normalizeToolText(text: string): string;
|
|
17
17
|
export declare function parseStructuredString(value: unknown): unknown;
|
|
18
|
-
export declare function buildToolPayload(
|
|
18
|
+
export declare function buildToolPayload(input: {
|
|
19
|
+
name: string;
|
|
20
|
+
toolCallId: string;
|
|
21
|
+
detailRevision: number;
|
|
22
|
+
status: 'running' | 'completed' | 'failed';
|
|
23
|
+
durationMs?: number;
|
|
24
|
+
}): string;
|
|
19
25
|
export declare function escapeMarkdownLabel(label: string): string;
|
|
20
26
|
export declare function buildMarkdownImageRef(filePath: string, label?: string): string;
|
|
21
27
|
export declare function buildImagePayload(filePath: string): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createHash as
|
|
2
|
-
`).trim())}const
|
|
3
|
-
`).replace(/\n+$/g,""):""}function v(e){if(typeof e!="string")return null;const t=Date.parse(e);return Number.isFinite(t)?t:null}function
|
|
4
|
-
`).trim()}function
|
|
1
|
+
import{createHash as I}from"node:crypto";import m from"node:fs";import T from"node:os";import n from"node:path";const x=64*1024*1024,S=["/root/.claude-mem/observer-session"],N=S.map(z);function j(e){return _(e.replace(/\r\n/g,`
|
|
2
|
+
`).trim())}const E=/^\s*::git-[a-z-]+\{[^\n]*\}\s*$/gm;function _(e){if(!e)return"";if(!/(^|\n)\s*::git-[a-z-]+\{[^\n]*\}\s*(?=\n|$)/.test(e))return e;const r=e.replace(E,"");return r.trim()?r.replace(/[ \t]+\n/g,`
|
|
3
|
+
`).replace(/\n+$/g,""):""}function v(e){if(typeof e!="string")return null;const t=Date.parse(e);return Number.isFinite(t)?t:null}function B(e){try{const t=JSON.parse(e);return typeof t=="object"&&t!==null?t:null}catch{return null}}function J(e,t,r){const u=m.statSync(e).size;if(t>=u)return u;const h=m.openSync(e,"r");try{const y=Buffer.allocUnsafe(262144);let l=t,f=t,c=Buffer.alloc(0),p=!1;for(;l<u;){const d=m.readSync(h,y,0,Math.min(262144,u-l),l);if(d<=0)break;l+=d;let a=y.subarray(0,d);c.length>0&&(a=Buffer.concat([c,a]),c=Buffer.alloc(0));let s=0;for(;s<a.length;){const i=a.indexOf(10,s);if(i<0)break;const g=a.subarray(s,i),C=f;f+=i-s+1,s=i+1,!p&&g.length>0&&g.length<=x&&r(g.toString("utf8"),C),p=!1}if(s<a.length){const i=a.subarray(s);p||i.length>x?(p=!0,f+=i.length,c=Buffer.alloc(0)):c=Buffer.from(i)}}return f}finally{m.closeSync(h)}}function M(e){return typeof e.cwd=="string"&&e.cwd.trim()?e.cwd:null}function F(e){return n.basename(n.dirname(e))==="subagents"}function b(e){const t=n.resolve(e);return process.platform==="win32"?t.toLowerCase():t}function w(e,t){const r=b(e),o=b(t);return r===o||r.startsWith(o+n.sep)}function z(e){return n.resolve(e).replace(/\//g,"-")}function G(e){return S.some(t=>w(e,t))}function D(e){return N.includes(e)}function U(e){const t=n.join(T.homedir(),".claude","projects"),r=n.relative(t,e);if(!r||r.startsWith("..")||n.isAbsolute(r))return!1;const[o]=r.split(n.sep);return o?D(o):!1}function V(e,t){return`${e}:${t}`}function H(e,t,r){return`${I("sha1").update(e).digest("hex").slice(0,16)}:${t}:${r}`}function W(e){if(e==null)return"";if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}function K(e){return e.replace(/\r\n/g,`
|
|
4
|
+
`).trim()}function X(e){if(typeof e!="string")return e;const t=e.trim();if(!t)return"";try{return JSON.parse(t)}catch{return e}}function Y(e){const t={v:1,type:"tool",name:e.name,toolCallId:e.toolCallId,detailRevision:e.detailRevision,status:e.status,resultOmitted:!0,detailAvailable:!0,...e.durationMs!==void 0?{durationMs:e.durationMs}:{}};return JSON.stringify(t)}function O(e){return e.replace(/[[\]\\]/g,"\\$&")}function k(e,t="Generated image"){return``}function q(e){const t=n.basename(e)||"generated-image.png";return JSON.stringify({v:1,type:"image",content:k(e,t),attachments:[{path:e,name:t,mimeType:"image/png",kind:"image"}]})}function Q(e){const t=n.extname(e).toLowerCase();return t===".png"?"image/png":t===".jpg"||t===".jpeg"?"image/jpeg":t===".webp"?"image/webp":t===".gif"?"image/gif":"application/octet-stream"}export{q as buildImagePayload,k as buildMarkdownImageRef,Y as buildToolPayload,z as encodeClaudeProjectDir,O as escapeMarkdownLabel,Q as inferMimeType,F as isClaudeSubagentTranscript,w as isSameOrChildPath,V as makeCursor,H as makeEventKey,b as normalizePathForCompare,j as normalizeText,K as normalizeToolText,X as parseStructuredString,M as readClaudeEventCwd,J as readJsonlLines,v as readTimestamp,B as safeParse,W as safeStringify,D as shouldIgnoreClaudeProjectDir,U as shouldIgnoreClaudeTranscriptPath,G as shouldIgnoreNativeScanPath};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ParsedNativeEvent } from './types.js';
|
|
2
|
-
export { listCodexRolloutFiles, parseCodexRolloutChunk } from './codex-parser.js';
|
|
2
|
+
export { listCodexRolloutFiles, lookupCodexThreadName, parseCodexRolloutChunk, } from './codex-parser.js';
|
|
3
|
+
export { inspectNativeSessionFile, normalizeNativeTimestamp } from './session-header.js';
|
|
3
4
|
export { listOpenCodeSessionFiles, parseOpenCodeSessionFile, parseOpenCodeSessionSnapshot, type OpenCodeSnapshot, } from './opencode-parser.js';
|
|
4
5
|
export declare function listClaudeTranscriptFiles(): string[];
|
|
5
6
|
export declare function lookupClaudeTranscriptCwd(sourceSessionKey: string): string | null;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import g from"node:fs";import
|
|
1
|
+
import g from"node:fs";import x from"node:os";import l from"node:path";import{listCodexRolloutFiles as q,lookupCodexThreadName as G,parseCodexRolloutChunk as H}from"./codex-parser.js";import{inspectNativeSessionFile as U,normalizeNativeTimestamp as V}from"./session-header.js";import{listOpenCodeSessionFiles as Y,parseOpenCodeSessionFile as Z,parseOpenCodeSessionSnapshot as $}from"./opencode-parser.js";import{makeCursor as j,makeEventKey as h,normalizeText as k,readClaudeEventCwd as S,readJsonlLines as v,readTimestamp as w,safeParse as T,isClaudeSubagentTranscript as D,shouldIgnoreClaudeProjectDir as z,shouldIgnoreClaudeTranscriptPath as E,shouldIgnoreNativeScanPath as b}from"./parser-common.js";function I(e){const n=x.homedir();return e.startsWith(n)?e.slice(n.length).replace(/^\//,"")||"~":e.replace(/\//g,"-")}function N(){const e=l.join(x.homedir(),".claude","projects");if(!g.existsSync(e))return[];const n=[],o=t=>{for(const r of g.readdirSync(t,{withFileTypes:!0})){const i=l.join(t,r.name);if(r.isDirectory()){if(t===e&&z(r.name))continue;o(i)}else r.isFile()&&r.name.endsWith(".jsonl")&&n.push(i)}};return o(e),n.sort()}function R(e){if(!e)return null;const n=N().find(t=>l.basename(t,".jsonl")===e);if(!n)return null;let o=null;return v(n,0,t=>{if(o||!t.trim())return;const r=T(t);if(!r)return;const i=S(r);i&&(o=i)}),o}function B(e,n){const o=g.statSync(e).size;if(D(e)||E(e))return{nextOffset:o,events:[]};const t=[],r=l.basename(e,".jsonl"),i=I(l.dirname(e).replace(l.join(x.homedir(),".claude","projects")+l.sep,""));if(b(i))return{nextOffset:o,events:[]};let u="";return{nextOffset:v(e,n,(_,p)=>{const s=T(_);if(!s)return;const d=w(s.timestamp),y=typeof s.type=="string"?s.type:"";if(!d||!y)return;const m=S(s);if(!(m&&b(m))){if(y==="user"){const a=typeof s.message=="object"&&s.message!==null?s.message:null,c=typeof a?.content=="string"?k(a.content):"";if(!c)return;u||(u=c.slice(0,80)),t.push({agentType:"claude",sourceMode:"claude_transcript_import",sourceSessionKey:r,sourceEventKey:h(e,p,"user"),cursor:j(e,p),role:"user",ts:d,payload:c,title:u,titleSource:"first_user_fallback",workDir:m??i})}else if(y==="assistant"){const a=typeof s.message=="object"&&s.message!==null?s.message:null,c=Array.isArray(a?.content)?a.content:[],C=k(c.map(f=>typeof f=="object"&&f!==null&&typeof f.text=="string"?f.text:"").filter(Boolean).join(`
|
|
2
2
|
|
|
3
|
-
`));if(!
|
|
3
|
+
`));if(!C)return;const F=typeof a?.model=="string"?a.model:null;t.push({agentType:"claude",sourceMode:"claude_transcript_import",sourceSessionKey:r,sourceEventKey:h(e,p,"assistant"),cursor:j(e,p),role:"agent",ts:d,payload:C,title:u,titleSource:"first_user_fallback",modelId:F,workDir:m??i})}}}),events:t}}export{U as inspectNativeSessionFile,N as listClaudeTranscriptFiles,q as listCodexRolloutFiles,Y as listOpenCodeSessionFiles,R as lookupClaudeTranscriptCwd,G as lookupCodexThreadName,V as normalizeNativeTimestamp,B as parseClaudeTranscriptChunk,H as parseCodexRolloutChunk,Z as parseOpenCodeSessionFile,$ as parseOpenCodeSessionSnapshot};
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import type { CliRelayClient } from '../relay/client.js';
|
|
2
|
+
import { NativeFusionLocalRepository } from './local-repository.js';
|
|
2
3
|
export declare class NativeSessionFusionService {
|
|
3
4
|
private client;
|
|
5
|
+
private repository;
|
|
4
6
|
private timer;
|
|
5
7
|
private activeTimer;
|
|
6
8
|
private scanPromise;
|
|
9
|
+
private queuedTargetFiles;
|
|
10
|
+
private fullScanQueued;
|
|
11
|
+
private watchEnabled;
|
|
12
|
+
private lifecycleRevision;
|
|
13
|
+
private watcher;
|
|
7
14
|
private pendingClaims;
|
|
8
15
|
private suppressionWindows;
|
|
9
16
|
private externalObservations;
|
|
10
|
-
constructor(client: CliRelayClient);
|
|
17
|
+
constructor(client: CliRelayClient, repository?: NativeFusionLocalRepository);
|
|
11
18
|
start(): void;
|
|
12
19
|
stop(): void;
|
|
13
20
|
handleConnected(): void;
|
|
@@ -26,7 +33,8 @@ export declare class NativeSessionFusionService {
|
|
|
26
33
|
private emitExternalActivity;
|
|
27
34
|
private emitExternalTerminal;
|
|
28
35
|
private pruneState;
|
|
29
|
-
scanNow(): Promise<void>;
|
|
36
|
+
scanNow(targetFiles?: readonly string[]): Promise<void>;
|
|
37
|
+
private runScanLoop;
|
|
30
38
|
private runScan;
|
|
31
39
|
private refreshExternalObservations;
|
|
32
40
|
private tryClaimManagedEcho;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`).trim()}function
|
|
1
|
+
import T from"node:fs";import{publishPendingSessionIndexes as E}from"../session/index-publisher.js";import{NativeFusionFileWatcher as M}from"./file-watcher.js";import{NativeFusionLocalRepository as b}from"./local-repository.js";import{listClaudeTranscriptFiles as C,listCodexRolloutFiles as K,listOpenCodeSessionFiles as N,inspectNativeSessionFile as _,lookupCodexThreadName as P,parseClaudeTranscriptChunk as R,parseCodexRolloutChunk as O,parseOpenCodeSessionFile as k}from"./parsers.js";import{loadNativeScannerState as q,saveNativeScannerState as F}from"./state.js";const x=6e4,I=x*2,L=30*6e4,$=2e3,W=3e4,v=240*6e4;function A(l){return l.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function B(l){return l.split(/[\\/]+/).includes(".codex")}class H{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new M(e=>{this.scanNow(e).catch(t=>{console.error("[native-fusion] watched file scan failed",t)})});pendingClaims=new Map;suppressionWindows=new Map;externalObservations=new Map;constructor(e,t=new b){this.client=e,this.repository=t}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},x))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.watchEnabled=!1,this.watcher.stop()}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(t=>{console.error("[native-fusion] initial scan failed",t)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const t=e.sourceAgentType??e.agentType;if(t!=="codex"&&t!=="claude"&&t!=="opencode")return;const n=e.canonicalMessageId;if(this.pendingClaims.set(n,{sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:t,canonicalMessageId:e.canonicalMessageId,text:A(e.text),createdAt:Date.now(),expiresAt:Date.now()+(e.externalRunId?v:I),sourceSessionKey:e.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null}),e.externalRunId&&e.sourceSessionKey){const s=`${t}:${e.sourceSessionKey}`,a=this.externalObservations.get(s)??[];a.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(s,a),this.emitExternalActivity(a[a.length-1],"queued"),this.ensureActiveScanning()}}noteManagedSourceSession(e,t,n){if(n)for(const s of this.pendingClaims.values())s.sessionId===e&&s.sourceAgentType===t&&(s.sourceSessionKey=n)}ensureActiveScanning(){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanNow().catch(e=>{console.error("[native-fusion] active observation scan failed",e)})},$),this.scanNow().catch(()=>{}))}emitExternalActivity(e,t){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}-${e.lastPublishedAt}`,payload:{state:t==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:t==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:t}})}emitExternalTerminal(e){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-final`,payload:{state:"final",sessionId:e.sessionId,runId:e.runId,seq:2,owner:"external",ownerLabel:"Codex Desktop"}})}pruneState(){const e=Date.now();for(const[t,n]of this.pendingClaims.entries())n.expiresAt<=e&&this.pendingClaims.delete(t);for(const[t,n]of this.suppressionWindows.entries())n.endsAt<=e&&this.suppressionWindows.delete(t)}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const t=this.scanPromise;if(e)for(const n of e)this.queuedTargetFiles.add(n);else this.fullScanQueued=!0;if(await t,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const n=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(n)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let t=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(t),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),t=void 0;continue}if(this.queuedTargetFiles.size>0){t=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const t=q(),n={...t.files},s=[],a=new Map,f=e?[...e]:[...K(),...C(),...N()];!e&&this.watchEnabled&&this.watcher.refresh(f);for(const i of f){if(!T.existsSync(i))continue;const u=T.statSync(i),S=B(i),d=i.endsWith(".opencode-session.json");let o=t.files[i];const p=`${String(u.dev)}:${String(u.ino)}:${String(u.birthtimeMs)}`;let c=!o;if(o&&!d&&(u.size<o.offset||o.fileIdentity&&o.fileIdentity!==p)&&(o=void 0,c=!0),o){if(d&&o.mtimeMs===u.mtimeMs)continue}else{const r=_(i);if(r.status==="pending")continue;o={offset:0,mtimeMs:u.mtimeMs,classification:r.createdAtMs>=t.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:r.createdAtMs,sourceBoundaryEstablished:{}}}const h=d?0:o.offset,y=d?k(i,h):S?O(i,h):R(i,h);for(const r of y.events)r.title?.trim()&&r.titleSource==="agent_native"&&a.set(`${r.agentType}:${r.sourceSessionKey}`,r);let m,g;if(d){const r=U(y.events,o,t.fusionEpochMs,c,u.mtimeMs,y.nextOffset);m=r.events,g=r.cursor}else if(c&&o.classification==="historical")m=[],g={...o,offset:y.nextOffset,mtimeMs:u.mtimeMs,awaitingUserBoundary:!0};else{const r=D(y.events,o,y.nextOffset,u.mtimeMs);m=r.events,g=r.cursor}g.fileIdentity=p,n[i]=g;for(const r of m){const w=this.tryClaimManagedEcho(r);if(w){s.push(w);continue}this.isSuppressed(r)||s.push(r)}}this.repository.commitEvents(s);for(const i of a.values())this.repository.observeNativeTitle(i.agentType,i.sourceSessionKey,i.title??"");for(const i of this.repository.listSourceBindings()){if(i.agentType!=="codex")continue;const u=P(i.sourceSessionKey);u&&this.repository.observeNativeTitle("codex",i.sourceSessionKey,u)}for(const i of s)this.observeExternalLifecycle(i);t.files=n,F(t),await E(this.client).catch(i=>{console.error("[native-fusion] index projection sync failed; retained for retry",i)})}refreshExternalObservations(){const e=Date.now();for(const[t,n]of this.externalObservations.entries()){const s=n.filter(f=>e-f.createdAt<v);if(s.length===0){this.externalObservations.delete(t);continue}this.externalObservations.set(t,s);const a=s[0];e-a.lastPublishedAt<W||(a.lastPublishedAt=e,this.emitExternalActivity(a,a.state))}}tryClaimManagedEcho(e){const t=Date.now();for(const[n,s]of this.pendingClaims.entries()){if(s.sourceAgentType!==e.agentType||s.expiresAt<=t||e.role!=="user"||!s.sourceSessionKey||s.sourceSessionKey!==e.sourceSessionKey)continue;const a=s.externalRunId?v:I;if(!(Math.abs(e.ts-s.createdAt)>a)&&A(e.payload)===s.text)return this.pendingClaims.delete(n),s.managedEchoPolicy==="suppress_following"&&this.suppressionWindows.set(`${e.agentType}:${e.sourceSessionKey}`,{sessionId:s.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,startedAt:e.ts,endsAt:Math.max(t,e.ts)+L}),{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:s.canonicalMessageId,claimSessionId:s.sessionId,managedEchoPolicy:s.managedEchoPolicy}}return null}observeExternalLifecycle(e){const t=`${e.agentType}:${e.sourceSessionKey}`,n=this.externalObservations.get(t);if(!n?.length)return;const s=n[0];e.claimCanonicalMessageId===s.canonicalMessageId&&s.state==="queued"&&(s.state="running",s.lastPublishedAt=Date.now(),this.emitExternalActivity(s,"running")),!(!e.terminal||s.state!=="running")&&(this.emitExternalTerminal(s),n.shift(),n.length===0?this.externalObservations.delete(t):(this.externalObservations.set(t,n),n[0].lastPublishedAt=Date.now(),this.emitExternalActivity(n[0],"queued")))}isSuppressed(e){const t=`${e.agentType}:${e.sourceSessionKey}`,n=this.suppressionWindows.get(t);return!n||e.ts<n.startedAt?!1:e.ts>n.endsAt?(this.suppressionWindows.delete(t),!1):e.role==="user"?(this.suppressionWindows.delete(t),!1):!0}}function D(l,e,t,n){if(!e.awaitingUserBoundary)return{events:l,cursor:{...e,offset:t,mtimeMs:n}};if(l.length===0)return{events:[],cursor:{...e,offset:t,mtimeMs:n,awaitingUserBoundary:!0}};const s=l.findIndex(a=>a.role==="user");return s<0?{events:[],cursor:{...e,mtimeMs:n,awaitingUserBoundary:!0}}:{events:l.slice(s),cursor:{...e,offset:t,mtimeMs:n,awaitingUserBoundary:!1}}}function U(l,e,t,n,s,a){const f={...e.sourceBoundaryEstablished??{}},i=e.eventWatermarkTs??-1,u=new Set(e.eventKeysAtWatermark??[]),S=n?l:l.filter(c=>c.ts>i||c.ts===i&&!u.has(c.sourceEventKey)),d=[];for(const c of S){const h=c.sourceSessionKey;if(f[h]){d.push(c);continue}!(!n||(c.sourceSessionCreatedAtMs??0)>=t)||c.role!=="user"||(f[h]=!0,d.push(c))}const o=l.reduce((c,h)=>Math.max(c,h.ts),i),p=new Set(o===i?e.eventKeysAtWatermark??[]:[]);for(const c of l)c.ts===o&&p.add(c.sourceEventKey);return{events:d,cursor:{...e,offset:a,mtimeMs:s,eventWatermarkTs:o>=0?o:void 0,eventKeysAtWatermark:o>=0?[...p]:void 0,sourceBoundaryEstablished:f}}}export{H as NativeSessionFusionService};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type NativeSessionFileInspection = {
|
|
2
|
+
status: 'pending';
|
|
3
|
+
} | {
|
|
4
|
+
status: 'ready';
|
|
5
|
+
createdAtMs: number;
|
|
6
|
+
sourceCreatedAtByKey?: Record<string, number>;
|
|
7
|
+
};
|
|
8
|
+
export declare function inspectNativeSessionFile(filePath: string): NativeSessionFileInspection;
|
|
9
|
+
export declare function normalizeNativeTimestamp(value: unknown): number | null;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import o from"node:fs";import{readTimestamp as d,safeParse as y}from"./parser-common.js";const l=1024*1024,g=64;function h(t){if(t.endsWith(".opencode-session.json"))return M(t);const e=S(t);if(e.length===0)return{status:"pending"};const n=o.statSync(t);if(t.split(/[\\/]+/).includes(".codex")){const s=e.find(u=>u.type==="session_meta");if(!s)return{status:"pending"};const c=p(s.payload),a=d(s.timestamp)??d(c?.timestamp)??m(n.birthtimeMs);return a?{status:"ready",createdAtMs:a}:{status:"pending"}}const i=e.map(s=>d(s.timestamp)).find(s=>s!=null)??m(n.birthtimeMs);return i?{status:"ready",createdAtMs:i}:{status:"pending"}}function M(t){let e;try{e=JSON.parse(o.readFileSync(t,"utf8"))}catch{return{status:"pending"}}const n=p(e);if(!n||!Array.isArray(n.sessions)||n.sessions.length===0)return{status:"pending"};const r={};for(const s of n.sessions){const c=p(s),a=typeof c?.id=="string"?c.id:"",u=f(c?.time_created);a&&u&&(r[a]=u)}const i=Object.values(r);return i.length===0?{status:"pending"}:{status:"ready",createdAtMs:Math.min(...i),sourceCreatedAtByKey:r}}function S(t){const e=o.openSync(t,"r");try{const n=Math.min(o.fstatSync(e).size,l);if(n===0)return[];const r=Buffer.allocUnsafe(n),i=o.readSync(e,r,0,n,0);return r.subarray(0,i).toString("utf8").split(`
|
|
2
|
+
`).slice(0,g).map(s=>y(s)).filter(s=>s!=null)}finally{o.closeSync(e)}}function p(t){return typeof t=="object"&&t!==null?t:null}function m(t){return Number.isFinite(t)&&t>0?t:null}function _(t){return f(t)}function f(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?null:t<1e10?Math.round(t*1e3):Math.round(t)}export{h as inspectNativeSessionFile,_ as normalizeNativeTimestamp};
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import p from"node:crypto";import e from"node:fs";import m from"node:path";import{resolvePersonalSyncV1Path as u}from"../personal-sync/paths.js";import{getDaemonInstallationId as a}from"../session/store.js";import{NATIVE_SCANNER_STATE_SCHEMA_VERSION as f}from"./types.js";function l(){return u("native-fusion-state.json")}function w(){const n=l(),t=a();if(!e.existsSync(n)){const o={schemaVersion:f,fusionEpochMs:Date.now(),daemonInstallationId:t,files:{}};return y(n,o),o}let r;try{r=JSON.parse(e.readFileSync(n,"utf8"))}catch(o){throw new Error(`native fusion v1 state is unreadable: ${n}`,{cause:o})}return s(r,t),r}function N(n){const t=a();s(n,t);const r=l();if(e.existsSync(r)){const o=JSON.parse(e.readFileSync(r,"utf8"));if(s(o,t),o.fusionEpochMs!==n.fusionEpochMs)throw new Error("native fusion epoch is immutable")}y(r,n)}function s(n,t){if(!n||n.schemaVersion!==f||!Number.isSafeInteger(n.fusionEpochMs)||n.fusionEpochMs<=0||n.daemonInstallationId!==t||!n.files||typeof n.files!="object"||Array.isArray(n.files))throw new Error("invalid native fusion v1 state")}function y(n,t){const r=m.dirname(n);e.mkdirSync(r,{recursive:!0,mode:448});const o=`${n}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let i;try{if(i=e.openSync(o,"wx",384),e.writeFileSync(i,`${JSON.stringify(t,null,2)}
|
|
2
|
+
`,"utf8"),e.fsyncSync(i),e.closeSync(i),i=void 0,e.renameSync(o,n),process.platform!=="win32"){const c=e.openSync(r,"r");try{e.fsyncSync(c)}finally{e.closeSync(c)}}}finally{i!==void 0&&e.closeSync(i);try{e.unlinkSync(o)}catch{}}}export{w as loadNativeScannerState,N as saveNativeScannerState};
|
|
@@ -1,15 +1,39 @@
|
|
|
1
|
-
import type { NativeSessionEventPayload } from '@shennian/wire';
|
|
1
|
+
import type { NativeSessionEventPayload, SessionTitleSource } from '@shennian/wire';
|
|
2
2
|
export type NativeRepairHint = 'codex_app_context_wrapper';
|
|
3
|
+
export declare const NATIVE_SCANNER_STATE_SCHEMA_VERSION: 1;
|
|
4
|
+
export type NativeFileCursor = {
|
|
5
|
+
offset: number;
|
|
6
|
+
mtimeMs: number;
|
|
7
|
+
fileIdentity?: string;
|
|
8
|
+
classification: 'live' | 'historical';
|
|
9
|
+
awaitingUserBoundary?: boolean;
|
|
10
|
+
sessionCreatedAtMs?: number;
|
|
11
|
+
eventWatermarkTs?: number;
|
|
12
|
+
eventKeysAtWatermark?: string[];
|
|
13
|
+
sourceBoundaryEstablished?: Record<string, boolean>;
|
|
14
|
+
};
|
|
3
15
|
export type NativeScannerState = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
16
|
+
schemaVersion: typeof NATIVE_SCANNER_STATE_SCHEMA_VERSION;
|
|
17
|
+
fusionEpochMs: number;
|
|
18
|
+
daemonInstallationId: string;
|
|
19
|
+
files: Record<string, NativeFileCursor>;
|
|
8
20
|
codexWindowsPathParserFixed?: boolean;
|
|
9
21
|
codexAppContextWrapperFixed?: boolean;
|
|
10
22
|
};
|
|
11
23
|
export type ParsedNativeEvent = NativeSessionEventPayload & {
|
|
12
24
|
repairHint?: NativeRepairHint;
|
|
25
|
+
sourceSessionCreatedAtMs?: number;
|
|
26
|
+
claimSessionId?: string | null;
|
|
27
|
+
titleSource?: Extract<SessionTitleSource, 'agent_native' | 'first_user_fallback'>;
|
|
28
|
+
localToolDetail?: {
|
|
29
|
+
toolCallId: string;
|
|
30
|
+
detailRevision: number;
|
|
31
|
+
name: string;
|
|
32
|
+
args?: unknown;
|
|
33
|
+
result?: unknown;
|
|
34
|
+
status: 'running' | 'completed' | 'failed';
|
|
35
|
+
durationMs?: number;
|
|
36
|
+
};
|
|
13
37
|
};
|
|
14
38
|
export type PendingManagedClaim = {
|
|
15
39
|
sessionId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
const E=1;export{E as NATIVE_SCANNER_STATE_SCHEMA_VERSION};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import n from"node:path";import{resolveShennianPath as r}from"../config/index.js";const e="personal-sync-v1";function p(...o){return n.join(r(e),...o)}export{e as PERSONAL_SYNC_V1_ROOT_NAME,p as resolvePersonalSyncV1Path};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReqFrame, ResFrame, EventFrame } from '@shennian/wire';
|
|
1
|
+
import type { ReqFrame, ResFrame, EventFrame, SessionSyncCapability } from '@shennian/wire';
|
|
2
2
|
import type { RuntimeAutoUpgradePolicy } from '../config/index.js';
|
|
3
3
|
export type CliRelayOptions = {
|
|
4
4
|
serverUrl: string;
|
|
@@ -6,6 +6,9 @@ export type CliRelayOptions = {
|
|
|
6
6
|
cliVersion?: string;
|
|
7
7
|
agentList?: string[];
|
|
8
8
|
autoUpgradePolicy?: RuntimeAutoUpgradePolicy;
|
|
9
|
+
sessionSyncProtocolVersion?: number;
|
|
10
|
+
sessionSyncCapabilities?: readonly SessionSyncCapability[];
|
|
11
|
+
daemonInstallationId?: string;
|
|
9
12
|
onReq?: (req: ReqFrame) => void;
|
|
10
13
|
onConnected?: () => void;
|
|
11
14
|
onDisconnected?: (info: CliRelayDisconnectInfo) => void;
|
package/dist/src/relay/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import g from"ws";import{generateTraceId as h}from"../log-reporter.js";const m=9e4,f=3e4,T=1e3,w=.2,E=2e4,y=1e4,R=15e3,I=1e3;class k{options;ws=null;state="disconnected";reconnectAttempts=0;heartbeatTimer=null;reconnectTimer=null;connectTimer=null;pingTimer=null;pongTimer=null;disposed=!1;closeTrigger;lastSocketError;sendBuffer=new Map;pendingAcks=new Map;pendingRequests=new Map;constructor(e){this.options=e}connect(){if(this.disposed)return;this.ws&&this.cleanup(!1),this.state="connecting";const e=this.options.serverUrl.includes("?")?"&":"?",t=this.options.cliVersion?`&v=${encodeURIComponent(this.options.cliVersion)}`:"",s=this.options.agentList?.length?`&agents=${encodeURIComponent(this.options.agentList.join(","))}`:"",o=this.options.autoUpgradePolicy?`&upgradePolicy=${encodeURIComponent(this.options.autoUpgradePolicy)}`:"",n=this.options.sessionSyncProtocolVersion?`&syncProtocol=${this.options.sessionSyncProtocolVersion}`:"",r=this.options.sessionSyncCapabilities?.length?`&syncCapabilities=${encodeURIComponent(this.options.sessionSyncCapabilities.join(","))}`:"",l=this.options.daemonInstallationId?`&daemonInstallationId=${encodeURIComponent(this.options.daemonInstallationId)}`:"",d=`${this.options.serverUrl}${e}token=${encodeURIComponent(this.options.machineToken)}${t}${s}${o}${n}${r}${l}`,i=new g(d);this.ws=i,this.connectTimer=setTimeout(()=>{this.ws===i&&this.state==="connecting"&&(this.closeTrigger="connect-timeout",i.terminate())},R),i.on("open",()=>{this.ws===i&&(this.clearConnectTimer(),this.state="connected",this.reconnectAttempts=0,this.startHeartbeat(),this.startPing(i),this.flushSendBuffer(),this.options.onConnected?.())}),i.on("pong",()=>{this.ws===i&&(this.clearPongTimer(),this.resetHeartbeat())}),i.on("message",c=>{if(this.ws!==i)return;let a;try{a=JSON.parse(c.toString())}catch{return}this.handleFrame(a)}),i.on("close",(c,a)=>{if(this.ws!==i)return;const u=this.state,p={code:c,reason:a.toString(),error:this.lastSocketError,phase:u,trigger:this.closeTrigger??"socket-close",reconnectAttempt:this.reconnectAttempts+1};this.cleanup(!1),this.options.onDisconnected?.(p),this.scheduleReconnect()}),i.on("error",c=>{this.lastSocketError=c.message,this.closeTrigger="socket-error",i.close()})}disconnect(){this.disposed=!0,this.cleanup(!0)}sendRes(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=h()),this.ws.send(JSON.stringify(e)))}sendEvent(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=h()),this.ws.send(JSON.stringify(e)))}sendReq(e,t=6e4){return this.state!=="connected"||!this.ws?Promise.reject(new Error("Relay is not connected")):(e.traceId||(e.traceId=h()),new Promise((s,o)=>{const n=this.pendingRequests.get(e.id);n&&(clearTimeout(n.timer),n.reject(new Error("Superseded by a newer relay request")));const r=setTimeout(()=>{this.pendingRequests.delete(e.id),o(new Error("Relay request timed out"))},t);this.pendingRequests.set(e.id,{resolve:s,reject:o,timer:r}),this.ws?.send(JSON.stringify(e))}))}sendBufferedEvent(e,t=12e4){if(e.traceId||(e.traceId=h()),!e.id)return this.sendEvent(e),Promise.resolve();const s=e.id;return new Promise((o,n)=>{const r=this.pendingAcks.get(s);r&&(clearTimeout(r.timer),r.reject(new Error("Superseded by a newer buffered event")));const l=setTimeout(()=>{this.pendingAcks.delete(s),this.sendBuffer.delete(s),n(new Error("Relay event acknowledgement timed out"))},t);this.pendingAcks.set(s,{resolve:o,reject:n,timer:l}),this.bufferEvent(e)})}sendAgentEvent(e){this.sendBufferedEvent(e).catch(()=>{})}getState(){return this.state}handleFrame(e){if(e.type==="event"&&e.event==="tick"){this.resetHeartbeat();return}if(e.type==="res"&&e.id){this.sendBuffer.delete(e.id);const t=this.pendingAcks.get(e.id);t&&(this.pendingAcks.delete(e.id),clearTimeout(t.timer),e.ok?t.resolve():t.reject(new Error(e.error??"Relay event failed")));const s=this.pendingRequests.get(e.id);s&&(this.pendingRequests.delete(e.id),clearTimeout(s.timer),s.resolve(e));return}e.type==="req"&&this.options.onReq?.(e)}flushSendBuffer(){if(!(this.sendBuffer.size===0||!this.ws))for(const e of this.sendBuffer.values()){if(this.state!=="connected"||!this.ws)break;this.ws.send(JSON.stringify(e))}}bufferEvent(e){if(!e.id){this.sendEvent(e);return}if(this.sendBuffer.set(e.id,e),this.sendBuffer.size>I){const t=this.sendBuffer.keys().next().value;if(t){this.sendBuffer.delete(t);const s=this.pendingAcks.get(t);s&&(this.pendingAcks.delete(t),clearTimeout(s.timer),s.reject(new Error("Relay send buffer overflow")))}}this.state==="connected"&&this.ws&&this.ws.send(JSON.stringify(e))}clearConnectTimer(){this.connectTimer!==null&&(clearTimeout(this.connectTimer),this.connectTimer=null)}startPing(e){this.clearPing(),this.pingTimer=setInterval(()=>{if(!(this.ws!==e||this.state!=="connected")){this.pongTimer=setTimeout(()=>{this.closeTrigger="pong-timeout",e.close()},y);try{e.ping()}catch{this.clearPongTimer(),this.closeTrigger="socket-error",e.close()}}},E)}clearPing(){this.pingTimer!==null&&(clearInterval(this.pingTimer),this.pingTimer=null),this.clearPongTimer()}clearPongTimer(){this.pongTimer!==null&&(clearTimeout(this.pongTimer),this.pongTimer=null)}startHeartbeat(){this.clearHeartbeat(),this.heartbeatTimer=setTimeout(()=>{this.closeTrigger="heartbeat-timeout",this.ws?.close()},m)}resetHeartbeat(){this.startHeartbeat()}clearHeartbeat(){this.heartbeatTimer!==null&&(clearTimeout(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.disposed)return;const e=Math.min(T*Math.pow(2,this.reconnectAttempts),f),t=e*w*(Math.random()*2-1),s=Math.round(e+t);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},s)}cleanup(e){if(this.state="disconnected",this.closeTrigger=void 0,this.lastSocketError=void 0,this.clearConnectTimer(),this.clearHeartbeat(),this.clearPing(),this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){const t=this.ws;this.ws=null,t.removeAllListeners();try{t.close()}catch{}}for(const[t,s]of this.pendingRequests)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingRequests.delete(t);if(e)for(const[t,s]of this.pendingAcks)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingAcks.delete(t)}}export{k as CliRelayClient};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const c=/^[a-f0-9]{64}$/i;function d(t){if(t==null)return;if(!Array.isArray(t))throw new Error("invalid_attachments");if(t.length>32)throw new Error("too_many_attachments");const o=t.map(r=>{if(!r||typeof r!="object")throw new Error("invalid_attachment_reference");const e=r,i=typeof e.path=="string"?e.path.trim():"",s=typeof e.name=="string"?e.name.trim():"",f=typeof e.mimeType=="string"?e.mimeType.trim():"",n=e.kind,a=e.byteSize,m=typeof e.sha256=="string"?e.sha256.toLowerCase():"";if(!i||/^https?:\/\//i.test(i)||!s||!f||n!=="file"&&n!=="image"&&n!=="folder"||!Number.isSafeInteger(a)||Number(a)<0||!c.test(m))throw new Error("invalid_attachment_reference");if(e.previewData!==void 0||e.data!==void 0||e.blob!==void 0)throw new Error("attachment_bytes_forbidden");return{path:i,name:s,mimeType:f,kind:n,byteSize:Number(a),sha256:m}});return o.length?o:void 0}export{d as parsePersonalAttachmentReferences};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type FsTransferFinishResult, type FsTransferStartParams, type FsTransferStartResult } from '@shennian/wire';
|
|
2
|
+
export declare const ATTACHMENT_TRANSFER_IDLE_MS: number;
|
|
3
|
+
export declare const ATTACHMENT_ORPHAN_TTL_MS: number;
|
|
4
|
+
export declare const ATTACHMENT_MAX_CHUNK_BYTES: number;
|
|
5
|
+
export declare const ATTACHMENT_MAX_FILE_BYTES: number;
|
|
6
|
+
export declare const ATTACHMENT_MAX_FOLDER_FILES = 2000;
|
|
7
|
+
export declare const ATTACHMENT_MAX_FOLDER_BYTES: number;
|
|
8
|
+
export type AttachmentTransferCleanupResult = {
|
|
9
|
+
stagingRemoved: number;
|
|
10
|
+
orphanRemoved: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function startAttachmentTransfer(input: FsTransferStartParams, now?: number): FsTransferStartResult;
|
|
13
|
+
export declare function appendAttachmentTransferChunk(input: {
|
|
14
|
+
rootPath: string;
|
|
15
|
+
transferId: string;
|
|
16
|
+
relativePath?: string;
|
|
17
|
+
offset: number;
|
|
18
|
+
data: Buffer;
|
|
19
|
+
now?: number;
|
|
20
|
+
}): {
|
|
21
|
+
written: number;
|
|
22
|
+
nextOffset: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function finishAttachmentTransfer(rootPath: string, transferId: string, now?: number): FsTransferFinishResult;
|
|
25
|
+
export declare function abortAttachmentTransfer(rootPath: string, transferId: string): boolean;
|
|
26
|
+
export declare function markAttachmentReferences(rootPath: string, attachments: readonly {
|
|
27
|
+
path: string;
|
|
28
|
+
sha256: string;
|
|
29
|
+
}[], now?: number): void;
|
|
30
|
+
export declare function assertFinishedAttachmentReferences(rootPath: string, attachments: readonly {
|
|
31
|
+
path: string;
|
|
32
|
+
name: string;
|
|
33
|
+
mimeType: string;
|
|
34
|
+
kind: 'file' | 'image' | 'folder';
|
|
35
|
+
byteSize: number;
|
|
36
|
+
sha256: string;
|
|
37
|
+
}[]): void;
|
|
38
|
+
export declare function sweepAttachmentTransfers(rootPath: string, now?: number): AttachmentTransferCleanupResult;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import M from"node:crypto";import o from"node:fs";import s from"node:path";import{serializeFsTransferManifest as X}from"@shennian/wire";const Y=600*1e3,K=1440*60*1e3,U=1024*1024,D=1024*1024*1024,q=2e3,H=1024*1024*1024,S=1,$="__file__",C=/^[a-f0-9]{64}$/i,O=/^[A-Za-z0-9_-]{8,128}$/;function l(e){return s.join(e,".uploads")}function w(e){return s.join(l(e),".staging")}function E(e){return s.join(l(e),".metadata")}function m(e,r){return s.join(w(e),r)}function T(e,r){return s.join(m(e,r),"manifest.json")}function A(e,r){return s.join(E(e),`${r.toLowerCase()}.json`)}function d(e,r){const t=s.relative(s.resolve(e),s.resolve(r));if(!(t===""||!t.startsWith("..")&&!s.isAbsolute(t)))throw new Error("attachment_path_outside_uploads")}function z(e){if(!O.test(e))throw new Error("invalid_transfer_id")}function g(e){if(!C.test(e))throw new Error("invalid_attachment_sha256");return e.toLowerCase()}function V(e){if(!e||e.trim()!==e||s.basename(e)!==e||e==="."||e==="..")throw new Error("invalid_attachment_name");if(Array.from(e).some(r=>r.charCodeAt(0)<=31))throw new Error("invalid_attachment_name");return e}function p(e){if(!e||e.trim()!==e||e.includes("\\"))throw new Error("invalid_attachment_relative_path");if(s.posix.isAbsolute(e)||s.win32.isAbsolute(e))throw new Error("invalid_attachment_relative_path");if(e.split("/").some(t=>!t||t==="."||t===".."))throw new Error("invalid_attachment_relative_path");return e}function L(e,r,t){if(!Number.isSafeInteger(e)||e<0||e>r)throw new Error(`invalid_${t}`);return e}function k(e,r){o.mkdirSync(s.dirname(e),{recursive:!0});const t=`${e}.${process.pid}.tmp`;o.writeFileSync(t,`${JSON.stringify(r)}
|
|
2
|
+
`,{mode:384}),o.renameSync(t,e)}function u(e){try{return JSON.parse(o.readFileSync(e,"utf8"))}catch{return null}}function P(e){const r=M.createHash("sha256"),t=o.openSync(e,"r"),n=Buffer.allocUnsafe(256*1024);try{let i=0;do i=o.readSync(t,n,0,n.length,null),i>0&&r.update(n.subarray(0,i));while(i>0)}finally{o.closeSync(t)}return r.digest("hex")}function x(e){return M.createHash("sha256").update(X(e)).digest("hex")}function y(e,r,t){const n=s.join(m(e,r),"payload"),i=t?s.join(n,...t.split("/")):n;return d(m(e,r),i),i}function B(e,r){z(r);const t=u(T(e,r));if(!t||t.version!==S||t.transferId!==r||s.resolve(t.rootPath)!==s.resolve(e))throw new Error("invalid_transfer_id");return d(l(e),t.targetPath),t}function F(e){k(T(e.rootPath,e.transferId),e)}function Z(e,r){if(!o.existsSync(e))return e;const t=s.parse(e);for(let n=0;n<1e3;n+=1){const i=n===0?r.slice(0,10):`${r.slice(0,10)}-${n+1}`,a=s.join(t.dir,`${t.name}-${i}${t.ext}`);if(!o.existsSync(a))return a}throw new Error("attachment_name_conflict")}function N(e,r){const t=u(A(e,r));if(!t||t.version!==S||t.sha256!==r)return null;try{d(l(e),t.path);const n=o.statSync(t.path);if(t.kind==="file"&&(!n.isFile()||n.size!==t.byteSize||P(t.path)!==t.sha256))return null;if(t.kind==="folder"){if(!n.isDirectory()||!t.files?.length)return null;for(const i of t.files){const a=s.join(t.path,...p(i.relativePath).split("/"));if(d(t.path,a),!o.statSync(a).isFile()||o.statSync(a).size!==i.size||P(a)!==i.sha256)return null}if(x(t.files)!==t.sha256)return null}return t}catch{return null}}function R(e,r){return{transferId:e.transferId,path:e.path,name:e.name,mimeType:e.mimeType,kind:e.kind,byteSize:e.byteSize,sha256:e.sha256,reused:r}}function G(e){if(e.kind==="file")return[{relativePath:$,size:e.totalSize,sha256:e.sha256,receivedSize:0}];if(!e.manifest?.length||e.manifest.length>q)throw new Error("invalid_attachment_manifest");const r=new Set;let t=0;const n=e.manifest.map(i=>{const a=p(i.relativePath);if(r.has(a))throw new Error("duplicate_attachment_relative_path");r.add(a);const f=L(i.size,D,"attachment_size");if(t+=f,t>H)throw new Error("attachment_folder_too_large");return{...i,relativePath:a,size:f,sha256:g(i.sha256),receivedSize:0}});if(t!==e.totalSize)throw new Error("attachment_total_size_mismatch");if(x(n)!==e.sha256)throw new Error("attachment_manifest_hash_mismatch");return n}function ae(e,r=Date.now()){z(e.transferId);const t=V(e.name),n=g(e.sha256),i=e.kind==="folder"?H:D,a=L(e.totalSize,i,"attachment_total_size");if(!e.rootPath||!s.isAbsolute(e.rootPath))throw new Error("invalid_attachment_root");if(!e.mimeType||e.mimeType.length>255)throw new Error("invalid_attachment_mime_type");Q(e.rootPath,r);const f=N(e.rootPath,n);if(f&&f.kind===e.kind&&f.byteSize===a)return{transferId:e.transferId,path:f.path,reused:!0,offsets:{},attachment:{...R(f,!0),transferId:e.transferId}};const c=u(T(e.rootPath,e.transferId));if(c){if(c.sha256!==n||c.totalSize!==a||c.name!==t||c.kind!==e.kind||c.mimeType!==e.mimeType)throw new Error("transfer_identity_conflict");return c.lastActivityAt=r,F(c),{transferId:e.transferId,path:c.targetPath,reused:!1,offsets:Object.fromEntries(c.files.map(_=>[_.relativePath,_.receivedSize]))}}const h=G({...e,name:t,sha256:n,totalSize:a}),v=l(e.rootPath);o.mkdirSync(w(e.rootPath),{recursive:!0,mode:448}),o.mkdirSync(E(e.rootPath),{recursive:!0,mode:448});const W=m(e.rootPath,e.transferId);o.mkdirSync(W,{recursive:!1,mode:448});const b=s.join(v,t);d(v,b);const I=Z(b,n);if(e.kind==="folder"){o.mkdirSync(y(e.rootPath,e.transferId),{recursive:!0,mode:448});for(const _ of h){const j=y(e.rootPath,e.transferId,_.relativePath);o.mkdirSync(s.dirname(j),{recursive:!0,mode:448}),o.writeFileSync(j,Buffer.alloc(0),{mode:384})}}else o.writeFileSync(y(e.rootPath,e.transferId),Buffer.alloc(0),{mode:384});const J={version:S,transferId:e.transferId,rootPath:s.resolve(e.rootPath),uploadsRoot:v,kind:e.kind,name:t,mimeType:e.mimeType,totalSize:a,sha256:n,targetPath:I,createdAt:r,lastActivityAt:r,files:h};return F(J),{transferId:e.transferId,path:I,reused:!1,offsets:Object.fromEntries(h.map(_=>[_.relativePath,0]))}}function ie(e){const r=B(e.rootPath,e.transferId),t=r.kind==="file"?$:p(e.relativePath??""),n=r.files.find(a=>a.relativePath===t);if(!n)throw new Error("invalid_attachment_relative_path");if(!Number.isSafeInteger(e.offset)||e.offset!==n.receivedSize)throw new Error(e.offset<n.receivedSize?"duplicate_attachment_chunk":"out_of_order_attachment_chunk");if(e.data.length<=0||e.data.length>U)throw new Error("invalid_attachment_chunk_size");if(e.offset+e.data.length>n.size)throw new Error("attachment_chunk_exceeds_size");const i=y(e.rootPath,e.transferId,r.kind==="folder"?n.relativePath:void 0);return o.appendFileSync(i,e.data),n.receivedSize+=e.data.length,r.lastActivityAt=e.now??Date.now(),F(r),{written:e.data.length,nextOffset:n.receivedSize}}function oe(e,r,t=Date.now()){const n=B(e,r);for(const c of n.files){if(c.receivedSize!==c.size)throw new Error("attachment_transfer_incomplete");const h=y(e,r,n.kind==="folder"?c.relativePath:void 0);if(o.statSync(h).size!==c.size)throw new Error("attachment_size_mismatch");if(P(h)!==c.sha256)throw new Error("attachment_hash_mismatch")}if(n.kind==="folder"&&x(n.files)!==n.sha256)throw new Error("attachment_manifest_hash_mismatch");const i=N(e,n.sha256);if(i&&i.kind===n.kind&&i.byteSize===n.totalSize)return o.rmSync(m(e,r),{recursive:!0,force:!0}),{...R(i,!0),transferId:r};o.mkdirSync(s.dirname(n.targetPath),{recursive:!0,mode:448});const a=y(e,r);o.renameSync(a,n.targetPath);const f={version:S,transferId:r,path:n.targetPath,name:n.name,mimeType:n.mimeType,kind:n.kind,byteSize:n.totalSize,sha256:n.sha256,finishedAt:t,referencedAt:null,...n.kind==="folder"?{files:n.files.map(({receivedSize:c,...h})=>h)}:{}};return k(A(e,n.sha256),f),o.rmSync(m(e,r),{recursive:!0,force:!0}),R(f,!1)}function se(e,r){z(r);const t=m(e,r);d(w(e),t);const n=o.existsSync(t);return o.rmSync(t,{recursive:!0,force:!0}),n}function ce(e,r,t=Date.now()){for(const n of r){if(!C.test(n.sha256))continue;const i=A(e,n.sha256),a=u(i);!a||s.resolve(a.path)!==s.resolve(n.path)||(d(l(e),a.path),a.referencedAt=a.referencedAt??t,k(i,a))}}function fe(e,r){for(const t of r){const n=g(t.sha256),i=N(e,n);if(!i)throw new Error("attachment_not_finished");const a=i.kind==="folder"?"folder":i.mimeType.startsWith("image/")?"image":"file";if(s.resolve(i.path)!==s.resolve(t.path)||i.name!==t.name||i.mimeType!==t.mimeType||i.byteSize!==t.byteSize||a!==t.kind)throw new Error("attachment_reference_mismatch")}}function Q(e,r=Date.now()){const t={stagingRemoved:0,orphanRemoved:0},n=w(e);try{for(const a of o.readdirSync(n,{withFileTypes:!0})){if(!a.isDirectory()||!O.test(a.name))continue;const f=s.join(n,a.name),h=u(s.join(f,"manifest.json"))?.lastActivityAt??o.statSync(f).mtimeMs;r-h<Y||(d(n,f),o.rmSync(f,{recursive:!0,force:!0}),t.stagingRemoved+=1)}}catch(a){if(a.code!=="ENOENT")throw a}const i=E(e);try{for(const a of o.readdirSync(i,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const f=s.join(i,a.name),c=u(f);!c||c.referencedAt!==null||r-c.finishedAt<K||(d(l(e),c.path),o.rmSync(c.path,{recursive:c.kind==="folder",force:!0}),o.rmSync(f,{force:!0}),t.orphanRemoved+=1)}}catch(a){if(a.code!=="ENOENT")throw a}return t}export{U as ATTACHMENT_MAX_CHUNK_BYTES,D as ATTACHMENT_MAX_FILE_BYTES,H as ATTACHMENT_MAX_FOLDER_BYTES,q as ATTACHMENT_MAX_FOLDER_FILES,K as ATTACHMENT_ORPHAN_TTL_MS,Y as ATTACHMENT_TRANSFER_IDLE_MS,se as abortAttachmentTransfer,ie as appendAttachmentTransferChunk,fe as assertFinishedAttachmentReferences,oe as finishAttachmentTransfer,ce as markAttachmentReferences,ae as startAttachmentTransfer,Q as sweepAttachmentTransfers};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Envelope, SessionBodyMutation, SessionDeliveryState, SessionHistoryMessage } from '@shennian/wire';
|
|
2
|
+
declare const SCHEMA_VERSION: 1;
|
|
3
|
+
declare const SEGMENT_NAME = "mutations-000001.jsonl";
|
|
4
|
+
export type CanonicalMutationType = 'append' | 'upsert' | 'tombstone';
|
|
5
|
+
export type CanonicalCommitInput = {
|
|
6
|
+
sessionId: string;
|
|
7
|
+
mutation: CanonicalMutationType;
|
|
8
|
+
messageId: string;
|
|
9
|
+
message?: Envelope;
|
|
10
|
+
deliveryState?: SessionDeliveryState;
|
|
11
|
+
committedAt?: string;
|
|
12
|
+
};
|
|
13
|
+
export type CanonicalCommitResult = {
|
|
14
|
+
bodyRevision: number;
|
|
15
|
+
sequence: number;
|
|
16
|
+
mutation: CanonicalMutation;
|
|
17
|
+
duplicate: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type CanonicalHistoryPage = {
|
|
20
|
+
bodyRevision: number;
|
|
21
|
+
firstAvailableSeq: number | null;
|
|
22
|
+
lastAvailableSeq: number | null;
|
|
23
|
+
hasOlder: boolean;
|
|
24
|
+
messages: Envelope[];
|
|
25
|
+
entries: SessionHistoryMessage[];
|
|
26
|
+
};
|
|
27
|
+
export type CanonicalMutation = Omit<SessionBodyMutation, 'subscriptionId'> & {
|
|
28
|
+
schemaVersion: typeof SCHEMA_VERSION;
|
|
29
|
+
mutationId: string;
|
|
30
|
+
committedAt: string;
|
|
31
|
+
contentHash: string;
|
|
32
|
+
};
|
|
33
|
+
type CanonicalManifest = {
|
|
34
|
+
schemaVersion: typeof SCHEMA_VERSION;
|
|
35
|
+
daemonInstallationId: string;
|
|
36
|
+
sessionId: string;
|
|
37
|
+
bodyRevision: number;
|
|
38
|
+
nextSequence: number;
|
|
39
|
+
firstAvailableSeq: number | null;
|
|
40
|
+
lastAvailableSeq: number | null;
|
|
41
|
+
mutationCount: number;
|
|
42
|
+
visibleMessageCount: number;
|
|
43
|
+
lastVisibleMessageId: string | null;
|
|
44
|
+
lastBodyCommittedAt: string | null;
|
|
45
|
+
segment: typeof SEGMENT_NAME;
|
|
46
|
+
logByteLength: number;
|
|
47
|
+
updatedAt: string;
|
|
48
|
+
};
|
|
49
|
+
export declare class CanonicalStoreError extends Error {
|
|
50
|
+
constructor(message: string, options?: ErrorOptions);
|
|
51
|
+
}
|
|
52
|
+
export declare class CanonicalStoreCorruptionError extends CanonicalStoreError {
|
|
53
|
+
constructor(message: string, options?: ErrorOptions);
|
|
54
|
+
}
|
|
55
|
+
export declare class CanonicalStoreConflictError extends CanonicalStoreError {
|
|
56
|
+
constructor(message: string);
|
|
57
|
+
}
|
|
58
|
+
export declare class CanonicalSessionStore {
|
|
59
|
+
readonly rootDir: string;
|
|
60
|
+
readonly daemonInstallationId: string;
|
|
61
|
+
private mutationListeners;
|
|
62
|
+
constructor(rootDir: string, daemonInstallationId: string);
|
|
63
|
+
commit(input: CanonicalCommitInput): CanonicalCommitResult;
|
|
64
|
+
subscribe(listener: (mutation: CanonicalMutation) => void): () => void;
|
|
65
|
+
append(sessionId: string, message: Envelope, deliveryState?: SessionDeliveryState): CanonicalCommitResult;
|
|
66
|
+
upsert(sessionId: string, message: Envelope, deliveryState?: SessionDeliveryState): CanonicalCommitResult;
|
|
67
|
+
tombstone(sessionId: string, messageId: string, deliveryState?: SessionDeliveryState): CanonicalCommitResult;
|
|
68
|
+
readRecent(sessionId: string, limit: number): CanonicalHistoryPage;
|
|
69
|
+
readPage(sessionId: string, options: {
|
|
70
|
+
limit: number;
|
|
71
|
+
beforeSequence?: number;
|
|
72
|
+
}): CanonicalHistoryPage;
|
|
73
|
+
readPageAtRevision(sessionId: string, options: {
|
|
74
|
+
limit: number;
|
|
75
|
+
revision: number;
|
|
76
|
+
beforeSequence?: number;
|
|
77
|
+
}): CanonicalHistoryPage;
|
|
78
|
+
readDeltas(sessionId: string, afterRevision: number, limit: number): CanonicalMutation[];
|
|
79
|
+
getManifest(sessionId: string): Readonly<CanonicalManifest> | null;
|
|
80
|
+
getMessageState(sessionId: string, messageId: string): {
|
|
81
|
+
sequence: number;
|
|
82
|
+
lastRevision: number;
|
|
83
|
+
tombstoned: boolean;
|
|
84
|
+
message?: Envelope;
|
|
85
|
+
deliveryState?: SessionDeliveryState;
|
|
86
|
+
} | null;
|
|
87
|
+
listSessionIds(): string[];
|
|
88
|
+
private findLastVisibleMessageId;
|
|
89
|
+
rebuild(sessionId: string): Readonly<CanonicalManifest>;
|
|
90
|
+
private loadManifestOrRebuild;
|
|
91
|
+
private findMutationByRevision;
|
|
92
|
+
private readSparseIndex;
|
|
93
|
+
private readMessageState;
|
|
94
|
+
private listMessageStateFiles;
|
|
95
|
+
private quarantineCorruptSegment;
|
|
96
|
+
private assertHealthy;
|
|
97
|
+
private sessionDir;
|
|
98
|
+
private messageStateDir;
|
|
99
|
+
private messageStateFile;
|
|
100
|
+
}
|
|
101
|
+
export {};
|