shennian 0.3.42 → 0.3.44

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.
Files changed (73) hide show
  1. package/dist/publish-build-manifest.json +91 -41
  2. package/dist/src/agents/adapter.d.ts +1 -0
  3. package/dist/src/index.js +3 -3
  4. package/dist/src/native-fusion/codex-parser.d.ts +1 -0
  5. package/dist/src/native-fusion/codex-parser.js +4 -4
  6. package/dist/src/native-fusion/file-watcher.d.ts +18 -0
  7. package/dist/src/native-fusion/file-watcher.js +1 -0
  8. package/dist/src/native-fusion/local-repository.d.ts +53 -0
  9. package/dist/src/native-fusion/local-repository.js +2 -0
  10. package/dist/src/native-fusion/opencode-parser.js +2 -2
  11. package/dist/src/native-fusion/parser-common.d.ts +7 -1
  12. package/dist/src/native-fusion/parser-common.js +4 -4
  13. package/dist/src/native-fusion/parsers.d.ts +2 -1
  14. package/dist/src/native-fusion/parsers.js +2 -2
  15. package/dist/src/native-fusion/service.d.ts +10 -2
  16. package/dist/src/native-fusion/service.js +2 -2
  17. package/dist/src/native-fusion/session-header.d.ts +9 -0
  18. package/dist/src/native-fusion/session-header.js +2 -0
  19. package/dist/src/native-fusion/state.d.ts +1 -1
  20. package/dist/src/native-fusion/state.js +2 -1
  21. package/dist/src/native-fusion/types.d.ts +31 -5
  22. package/dist/src/native-fusion/types.js +1 -1
  23. package/dist/src/personal-sync/paths.d.ts +2 -0
  24. package/dist/src/personal-sync/paths.js +1 -0
  25. package/dist/src/relay/client.d.ts +4 -1
  26. package/dist/src/relay/client.js +1 -1
  27. package/dist/src/session/attachment-reference.d.ts +2 -0
  28. package/dist/src/session/attachment-reference.js +1 -0
  29. package/dist/src/session/attachment-transfer-store.d.ts +38 -0
  30. package/dist/src/session/attachment-transfer-store.js +2 -0
  31. package/dist/src/session/canonical-store.d.ts +101 -0
  32. package/dist/src/session/canonical-store.js +5 -0
  33. package/dist/src/session/handlers/chat.js +2 -2
  34. package/dist/src/session/handlers/fs.d.ts +0 -1
  35. package/dist/src/session/handlers/fs.js +1 -1
  36. package/dist/src/session/handlers/message-status.d.ts +3 -0
  37. package/dist/src/session/handlers/message-status.js +1 -0
  38. package/dist/src/session/handlers/title.js +1 -1
  39. package/dist/src/session/handlers/tool-detail.js +1 -1
  40. package/dist/src/session/handlers/voice-window.d.ts +3 -0
  41. package/dist/src/session/handlers/voice-window.js +1 -0
  42. package/dist/src/session/history-subscriptions.d.ts +17 -0
  43. package/dist/src/session/history-subscriptions.js +1 -0
  44. package/dist/src/session/index-publisher.d.ts +3 -0
  45. package/dist/src/session/index-publisher.js +1 -0
  46. package/dist/src/session/manager.d.ts +2 -0
  47. package/dist/src/session/manager.js +1 -1
  48. package/dist/src/session/queue.d.ts +1 -1
  49. package/dist/src/session/queue.js +4 -3
  50. package/dist/src/session/store.d.ts +33 -3
  51. package/dist/src/session/store.js +2 -3
  52. package/dist/src/session/tool-detail-store.d.ts +16 -0
  53. package/dist/src/session/tool-detail-store.js +2 -0
  54. package/dist/src/session/types.d.ts +3 -13
  55. package/node_modules/@shennian/wire/dist/events.d.ts +2 -0
  56. package/node_modules/@shennian/wire/dist/fs.d.ts +45 -0
  57. package/node_modules/@shennian/wire/dist/fs.js +7 -1
  58. package/node_modules/@shennian/wire/dist/index.d.ts +1 -0
  59. package/node_modules/@shennian/wire/dist/index.js +1 -0
  60. package/node_modules/@shennian/wire/dist/machine.d.ts +6 -0
  61. package/node_modules/@shennian/wire/dist/message-payload.d.ts +2 -0
  62. package/node_modules/@shennian/wire/dist/message-payload.js +15 -1
  63. package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
  64. package/node_modules/@shennian/wire/dist/session-sync.d.ts +197 -0
  65. package/node_modules/@shennian/wire/dist/session-sync.js +49 -0
  66. package/node_modules/@shennian/wire/dist/session.d.ts +24 -13
  67. package/node_modules/@shennian/wire/dist/tool-detail.d.ts +18 -11
  68. package/node_modules/@shennian/wire/dist/tool-detail.js +7 -1
  69. package/package.json +1 -1
  70. package/dist/src/session/remote-attachments.d.ts +0 -15
  71. package/dist/src/session/remote-attachments.js +0 -1
  72. package/dist/src/session/tool-summary.d.ts +0 -1
  73. package/dist/src/session/tool-summary.js +0 -1
@@ -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{randomUUID as I}from"node:crypto";import S from"node:fs";import{listClaudeTranscriptFiles as v,listCodexRolloutFiles as A,listOpenCodeSessionFiles as T,parseClaudeTranscriptChunk as w,parseCodexRolloutChunk as M,parseOpenCodeSessionFile as E}from"./parsers.js";import{loadNativeScannerState as C,saveNativeScannerState as _}from"./state.js";const g=6e4,m=g*2,b=30*6e4,y=20,P=2e3,O=3e4,d=240*6e4;function x(a){return a.replace(/\r\n/g,`
2
- `).trim()}function N(a){return a.split(/[\\/]+/).includes(".codex")}class L{client;timer=null;activeTimer=null;scanPromise=null;pendingClaims=new Map;suppressionWindows=new Map;externalObservations=new Map;constructor(e){this.client=e}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},g))}stop(){this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null}handleConnected(){this.start(),this.scanNow().catch(e=>{console.error("[native-fusion] initial scan failed",e)})}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:x(e.text),createdAt:Date.now(),expiresAt:Date.now()+(e.externalRunId?d:m),sourceSessionKey:e.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null}),e.externalRunId&&e.sourceSessionKey){const s=`${t}:${e.sourceSessionKey}`,i=this.externalObservations.get(s)??[];i.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,i),this.emitExternalActivity(i[i.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)})},P),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(){if(this.client.getState()==="connected")return this.scanPromise?this.scanPromise:(this.scanPromise=this.runScan().finally(()=>{this.scanPromise=null}),this.scanPromise)}async runScan(){this.pruneState(),this.refreshExternalObservations();const e=C(),t={...e.files},n=[],s=[...A(),...v(),...T()];for(const i of s){const o=S.statSync(i),r=e.files[i];if(!r){t[i]={offset:o.size,mtimeMs:o.mtimeMs};continue}const h=N(i),c=i.endsWith(".opencode-session.json"),l=c&&r?.mtimeMs!==o.mtimeMs?r.offset:r?.offset??0,f=c?E(i,l):h?M(i,l):w(i,l);t[i]={offset:f.nextOffset,mtimeMs:o.mtimeMs};for(const u of f.events){const p=this.tryClaimManagedEcho(u);if(p){n.push(p);continue}this.isSuppressed(u)||n.push(u)}}for(let i=0;i<n.length;i+=y){const o=n.slice(i,i+y);if(o.length===0)continue;const r=o.map(({repairHint:h,...c})=>c);await this.client.sendBufferedEvent({type:"event",event:"native.session.events",id:`native-fusion-${I()}`,payload:{events:r}},6e4)}for(const i of n)this.observeExternalLifecycle(i);e.files=t,_(e)}refreshExternalObservations(){const e=Date.now();for(const[t,n]of this.externalObservations.entries()){const s=n.filter(o=>e-o.createdAt<d);if(s.length===0){this.externalObservations.delete(t);continue}this.externalObservations.set(t,s);const i=s[0];e-i.lastPublishedAt<O||(i.lastPublishedAt=e,this.emitExternalActivity(i,i.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 i=s.externalRunId?d:m;if(!(Math.abs(e.ts-s.createdAt)>i)&&x(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)+b}),{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:s.canonicalMessageId,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}}export{L as NativeSessionFusionService};
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,3 +1,3 @@
1
- import type { NativeScannerState } from './types.js';
1
+ import { type NativeScannerState } from './types.js';
2
2
  export declare function loadNativeScannerState(): NativeScannerState;
3
3
  export declare function saveNativeScannerState(state: NativeScannerState): void;
@@ -1 +1,2 @@
1
- import r from"node:fs";import i from"node:path";import{resolveShennianPath as o}from"../config/index.js";const n=o("native-fusion-state.json");function f(){try{const t=r.readFileSync(n,"utf-8"),e=JSON.parse(t);if(e&&typeof e=="object"&&e.files&&typeof e.files=="object")return e}catch{}return{files:{}}}function S(t){r.mkdirSync(i.dirname(n),{recursive:!0}),r.writeFileSync(n,JSON.stringify(t,null,2))}export{f as loadNativeScannerState,S as saveNativeScannerState};
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,41 @@
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
- files: Record<string, {
5
- offset: number;
6
- mtimeMs: number;
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
+ canonicalMessageKey?: string;
28
+ canonicalMutation?: 'append' | 'upsert';
29
+ titleSource?: Extract<SessionTitleSource, 'agent_native' | 'first_user_fallback'>;
30
+ localToolDetail?: {
31
+ toolCallId: string;
32
+ detailRevision: number;
33
+ name: string;
34
+ args?: unknown;
35
+ result?: unknown;
36
+ status: 'running' | 'completed' | 'failed';
37
+ durationMs?: number;
38
+ };
13
39
  };
14
40
  export type PendingManagedClaim = {
15
41
  sessionId: string;
@@ -1 +1 @@
1
-
1
+ const E=1;export{E as NATIVE_SCANNER_STATE_SCHEMA_VERSION};
@@ -0,0 +1,2 @@
1
+ export declare const PERSONAL_SYNC_V1_ROOT_NAME = "personal-sync-v1";
2
+ export declare function resolvePersonalSyncV1Path(...parts: string[]): string;
@@ -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;
@@ -1 +1 @@
1
- import d from"ws";import{generateTraceId as h}from"../log-reporter.js";const u=9e4,g=3e4,p=1e3,f=.2,m=2e4,T=1e4,w=15e3,E=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.serverUrl}${e}token=${encodeURIComponent(this.options.machineToken)}${t}${s}${o}`,i=new d(n);this.ws=i,this.connectTimer=setTimeout(()=>{this.ws===i&&this.state==="connecting"&&(this.closeTrigger="connect-timeout",i.terminate())},w),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",r=>{if(this.ws!==i)return;let c;try{c=JSON.parse(r.toString())}catch{return}this.handleFrame(c)}),i.on("close",(r,c)=>{if(this.ws!==i)return;const l=this.state,a={code:r,reason:c.toString(),error:this.lastSocketError,phase:l,trigger:this.closeTrigger??"socket-close",reconnectAttempt:this.reconnectAttempts+1};this.cleanup(!1),this.options.onDisconnected?.(a),this.scheduleReconnect()}),i.on("error",r=>{this.lastSocketError=r.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 i=setTimeout(()=>{this.pendingRequests.delete(e.id),o(new Error("Relay request timed out"))},t);this.pendingRequests.set(e.id,{resolve:s,reject:o,timer:i}),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 i=this.pendingAcks.get(s);i&&(clearTimeout(i.timer),i.reject(new Error("Superseded by a newer buffered event")));const r=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:r}),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>E){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()},T);try{e.ping()}catch{this.clearPongTimer(),this.closeTrigger="socket-error",e.close()}}},m)}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()},u)}resetHeartbeat(){this.startHeartbeat()}clearHeartbeat(){this.heartbeatTimer!==null&&(clearTimeout(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.disposed)return;const e=Math.min(p*Math.pow(2,this.reconnectAttempts),g),t=e*f*(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};
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,2 @@
1
+ import type { PersonalAttachmentReference } from '@shennian/wire';
2
+ export declare function parsePersonalAttachmentReferences(value: unknown): PersonalAttachmentReference[] | undefined;
@@ -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 {};
@@ -0,0 +1,5 @@
1
+ import D from"node:crypto";import o from"node:fs";import c from"node:path";const b=1,V=64,v="mutations-000001.jsonl";class I extends Error{constructor(e,s){super(e,s),this.name="CanonicalStoreError"}}class q extends I{constructor(e,s){super(e,s),this.name="CanonicalStoreCorruptionError"}}class j extends I{constructor(e){super(e),this.name="CanonicalStoreConflictError"}}class K{rootDir;daemonInstallationId;mutationListeners=new Set;constructor(e,s){if(!s)throw new I("daemonInstallationId is required");this.rootDir=c.resolve(e),this.daemonInstallationId=s}commit(e){k(e);const s=this.sessionDir(e.sessionId);this.assertHealthy(s),o.mkdirSync(this.messageStateDir(s),{recursive:!0,mode:448});let t=this.loadManifestOrRebuild(e.sessionId);const n=this.readMessageState(s,e.messageId),a=z(e);if(n&&n.contentHash===a&&n.tombstoned===(e.mutation==="tombstone"))return{bodyRevision:t.bodyRevision,sequence:n.sequence,mutation:this.findMutationByRevision(e.sessionId,n.lastRevision),duplicate:!0};if(e.mutation==="append"&&n)throw new j(`message ${e.messageId} already exists in session ${e.sessionId}; use upsert`);const m=n?.sequence??t.nextSequence,g=t.bodyRevision+1,S=e.committedAt??new Date().toISOString(),y={schemaVersion:b,mutationId:T(e.sessionId,e.messageId,e.mutation,n?.contentHash??"none",a),sessionId:e.sessionId,revision:g,mutation:e.mutation,messageId:e.messageId,sequence:m,...e.message?{message:e.message}:{},...e.deliveryState?{deliveryState:e.deliveryState}:{},committedAt:S,contentHash:a},d=c.join(s,v),u=_(d,`${JSON.stringify(y)}
2
+ `),r={schemaVersion:b,sessionId:e.sessionId,messageId:e.messageId,sequence:m,lastRevision:g,contentHash:a,tombstoned:e.mutation==="tombstone",...e.message?{message:e.message}:{},...e.deliveryState?{deliveryState:e.deliveryState}:{}};w(this.messageStateFile(s,m,e.messageId),r);const f=this.readSparseIndex(s,e.sessionId);(g===1||(g-1)%V===0)&&(f.entries.push({revision:g,offset:u}),w(c.join(s,"sparse-index.json"),f)),t={...t,bodyRevision:g,nextSequence:n?t.nextSequence:m+1,firstAvailableSeq:t.firstAvailableSeq??m,lastAvailableSeq:Math.max(t.lastAvailableSeq??m,m),mutationCount:t.mutationCount+1,visibleMessageCount:t.visibleMessageCount+(!n&&e.mutation!=="tombstone"?1:0)-(n&&!n.tombstoned&&e.mutation==="tombstone"?1:0),lastVisibleMessageId:e.mutation==="tombstone"?t.lastVisibleMessageId===e.messageId?this.findLastVisibleMessageId(s,e.messageId):t.lastVisibleMessageId:e.messageId,lastBodyCommittedAt:S,logByteLength:o.statSync(d).size,updatedAt:S},w(c.join(s,"manifest.json"),t);for(const x of this.mutationListeners)try{x(y)}catch(p){console.error("[canonical-store] mutation listener failed",p)}return{bodyRevision:g,sequence:m,mutation:y,duplicate:!1}}subscribe(e){return this.mutationListeners.add(e),()=>this.mutationListeners.delete(e)}append(e,s,t){return this.commit({sessionId:e,mutation:"append",messageId:s.id,message:s,deliveryState:t})}upsert(e,s,t){return this.commit({sessionId:e,mutation:"upsert",messageId:s.id,message:s,deliveryState:t})}tombstone(e,s,t){return this.commit({sessionId:e,mutation:"tombstone",messageId:s,deliveryState:t})}readRecent(e,s){return this.readPage(e,{limit:s})}readPage(e,s){const t=O(s.limit),n=this.sessionDir(e);if(!o.existsSync(n))return H();const a=this.loadManifestOrRebuild(e),m=this.listMessageStateFiles(n).filter(d=>s.beforeSequence===void 0||d.sequence<s.beforeSequence).sort((d,u)=>u.sequence-d.sequence),g=[];let S=0;for(const d of m){S+=1;const u=M(c.join(this.messageStateDir(n),d.name));if(!u.tombstoned&&u.message&&g.push(u),g.length>=t)break}g.sort((d,u)=>d.sequence-u.sequence);const y=g.length>0&&m.slice(S).some(d=>{const u=M(c.join(this.messageStateDir(n),d.name));return!u.tombstoned&&!!u.message});return{bodyRevision:a.bodyRevision,firstAvailableSeq:a.firstAvailableSeq,lastAvailableSeq:a.lastAvailableSeq,hasOlder:y,messages:g.map(d=>d.message),entries:g.map(d=>({sequence:d.sequence,revision:d.lastRevision,message:d.message}))}}readPageAtRevision(e,s){const t=O(s.limit),n=this.sessionDir(e);if(!o.existsSync(n))return H();const a=this.loadManifestOrRebuild(e);if(!Number.isSafeInteger(s.revision)||s.revision<0||s.revision>a.bodyRevision)throw new I("snapshot revision is unavailable");const m=new Map,g=L(c.join(n,v),0);for(const r of g.split(`
3
+ `)){if(!r)continue;const f=A(r,e);if(f.revision>s.revision)break;m.set(f.messageId,{schemaVersion:b,sessionId:e,messageId:f.messageId,sequence:f.sequence,lastRevision:f.revision,contentHash:f.contentHash,tombstoned:f.mutation==="tombstone",...f.message?{message:f.message}:{},...f.deliveryState?{deliveryState:f.deliveryState}:{}})}const S=[...m.values()].filter(r=>!r.tombstoned&&r.message&&(s.beforeSequence===void 0||r.sequence<s.beforeSequence)).sort((r,f)=>f.sequence-r.sequence),y=S.slice(0,t).sort((r,f)=>r.sequence-f.sequence),u=[...m.values()].filter(r=>!r.tombstoned&&r.message).map(r=>r.sequence);return{bodyRevision:s.revision,firstAvailableSeq:u.length?Math.min(...u):null,lastAvailableSeq:u.length?Math.max(...u):null,hasOlder:S.length>y.length,messages:y.map(r=>r.message),entries:y.map(r=>({sequence:r.sequence,revision:r.lastRevision,message:r.message}))}}readDeltas(e,s,t){if(!Number.isSafeInteger(s)||s<0)throw new I("afterRevision must be a non-negative safe integer");const n=O(t),a=this.sessionDir(e);if(!o.existsSync(a))return[];const m=this.loadManifestOrRebuild(e);if(s>=m.bodyRevision)return[];const g=this.readSparseIndex(a,e),S=s+1,y=[...g.entries].reverse().find(r=>r.revision<=S),d=L(c.join(a,v),y?.offset??0),u=[];for(const r of d.split(`
4
+ `)){if(!r)continue;const f=A(r,e);if(f.revision>s&&u.push(f),u.length>=n)break}return u}getManifest(e){const s=this.sessionDir(e);return o.existsSync(s)?this.loadManifestOrRebuild(e):null}getMessageState(e,s){const t=this.sessionDir(e);if(!o.existsSync(t))return null;this.loadManifestOrRebuild(e);const n=this.readMessageState(t,s);return n?{sequence:n.sequence,lastRevision:n.lastRevision,tombstoned:n.tombstoned,message:n.message,deliveryState:n.deliveryState}:null}listSessionIds(){const e=c.join(this.rootDir,"sessions");if(!o.existsSync(e))return[];const s=[];for(const t of o.readdirSync(e,{withFileTypes:!0})){if(!t.isDirectory())continue;const n=c.join(e,t.name,"manifest.json");o.existsSync(n)&&s.push(M(n).sessionId)}return s.sort()}findLastVisibleMessageId(e,s){for(const t of this.listMessageStateFiles(e).sort((n,a)=>a.sequence-n.sequence)){const n=M(c.join(this.messageStateDir(e),t.name));if(n.messageId!==s&&!n.tombstoned&&n.message)return n.messageId}return null}rebuild(e){const s=this.sessionDir(e);this.assertHealthy(s),o.mkdirSync(s,{recursive:!0,mode:448});const t=c.join(s,"manifest.json");if(o.existsSync(t))try{const l=JSON.parse(o.readFileSync(t,"utf8"));if(l.daemonInstallationId&&l.daemonInstallationId!==this.daemonInstallationId)throw new j(`canonical session ${e} belongs to a different daemon installation`)}catch(l){if(l instanceof j)throw l}const n=c.join(s,v);if(!o.existsSync(n)){const l=P(e,this.daemonInstallationId);return w(c.join(s,"manifest.json"),l),w(c.join(s,"sparse-index.json"),C(e)),l}const a=o.readFileSync(n);let m=a.length;if(a.length>0&&a[a.length-1]!==10){const l=a.lastIndexOf(10);m=l<0?0:l+1,o.truncateSync(n,m)}const g=a.subarray(0,m).toString("utf8"),S=new Map,y=C(e);let d=1,u=0,r=new Date(0).toISOString();try{for(const l of g.match(/.*\n/g)??[]){const R=l.slice(0,-1);if(!R){u+=Buffer.byteLength(l);continue}const h=A(R,e);if(h.revision!==d)throw new q(`non-contiguous revision in ${e}: expected ${d}, got ${h.revision}`);(h.revision===1||(h.revision-1)%V===0)&&y.entries.push({revision:h.revision,offset:u});const N=S.get(h.messageId);if(N&&N.sequence!==h.sequence)throw new q(`sequence changed for message ${h.messageId}`);S.set(h.messageId,{schemaVersion:b,sessionId:e,messageId:h.messageId,sequence:h.sequence,lastRevision:h.revision,contentHash:h.contentHash,tombstoned:h.mutation==="tombstone",...h.message?{message:h.message}:{},...h.deliveryState?{deliveryState:h.deliveryState}:{}}),d+=1,u+=Buffer.byteLength(l),r=h.committedAt}}catch(l){this.quarantineCorruptSegment(e,n,l)}const f=this.messageStateDir(s);o.rmSync(f,{recursive:!0,force:!0}),o.mkdirSync(f,{recursive:!0,mode:448});for(const l of S.values())w(this.messageStateFile(s,l.sequence,l.messageId),l);w(c.join(s,"sparse-index.json"),y);const x=[...S.values()].map(l=>l.sequence),p=[...S.values()].filter(l=>!l.tombstoned&&!!l.message).sort((l,R)=>l.sequence-R.sequence),$=d-1,F={schemaVersion:b,daemonInstallationId:this.daemonInstallationId,sessionId:e,bodyRevision:$,nextSequence:x.length===0?1:Math.max(...x)+1,firstAvailableSeq:x.length===0?null:Math.min(...x),lastAvailableSeq:x.length===0?null:Math.max(...x),mutationCount:$,visibleMessageCount:p.length,lastVisibleMessageId:p.at(-1)?.messageId??null,lastBodyCommittedAt:$>0?r:null,segment:v,logByteLength:m,updatedAt:r};return w(c.join(s,"manifest.json"),F),F}loadManifestOrRebuild(e){const s=this.sessionDir(e);this.assertHealthy(s);const t=c.join(s,"manifest.json"),n=c.join(s,v);if(!o.existsSync(t))return this.rebuild(e);try{const a=M(t);J(a,e,this.daemonInstallationId);const m=o.existsSync(n)?o.statSync(n).size:0;return a.logByteLength===m?a:this.rebuild(e)}catch(a){if(a instanceof j)throw a;return this.rebuild(e)}}findMutationByRevision(e,s){const t=this.readDeltas(e,s-1,1)[0];if(!t||t.revision!==s)throw new q(`missing mutation revision ${s} in ${e}`);return t}readSparseIndex(e,s){const t=c.join(e,"sparse-index.json");if(!o.existsSync(t))return C(s);const n=M(t);if(n.schemaVersion!==b||n.sessionId!==s||n.segment!==v)throw new q(`invalid sparse index for session ${s}`);return n}readMessageState(e,s){const t=`.${E(s)}.json`,n=this.listMessageStateFiles(e).find(m=>m.name.endsWith(t));if(!n)return null;const a=M(c.join(this.messageStateDir(e),n.name));if(a.messageId!==s)throw new q(`message state hash collision for ${s}`);return a}listMessageStateFiles(e){const s=this.messageStateDir(e);return o.existsSync(s)?o.readdirSync(s).map(t=>({name:t,match:/^(\d{16})\.[a-f0-9]{32}\.json$/.exec(t)})).filter(t=>!!t.match).map(t=>({name:t.name,sequence:Number(t.match[1])})):[]}quarantineCorruptSegment(e,s,t){const n=c.join(this.rootDir,"quarantine");o.mkdirSync(n,{recursive:!0,mode:448});const a=c.join(n,`${B(e)}-${Date.now()}-${v}`);throw o.renameSync(s,a),w(c.join(this.sessionDir(e),"CORRUPT.json"),{schemaVersion:b,sessionId:e,quarantined:a,detectedAt:new Date().toISOString(),reason:t instanceof Error?t.message:String(t)}),new q(`canonical segment for ${e} was quarantined at ${a}`,{cause:t})}assertHealthy(e){const s=c.join(e,"CORRUPT.json");if(o.existsSync(s))throw new q(`canonical session is quarantined: ${s}`)}sessionDir(e){return c.join(this.rootDir,"sessions",B(e))}messageStateDir(e){return c.join(e,"messages")}messageStateFile(e,s,t){return c.join(this.messageStateDir(e),`${String(s).padStart(16,"0")}.${E(t)}.json`)}}function k(i){if(!i.sessionId||!i.messageId)throw new I("sessionId and messageId are required");if(i.message&&(i.message.id!==i.messageId||i.message.sessionId!==i.sessionId))throw new I("message identity does not match commit identity");if(i.mutation!=="tombstone"&&!i.message)throw new I(`${i.mutation} mutation requires a message`)}function J(i,e,s){if(i.daemonInstallationId!==s)throw new j(`canonical session ${e} belongs to a different daemon installation`);if(i.schemaVersion!==b||i.sessionId!==e||i.segment!==v||!Number.isSafeInteger(i.bodyRevision)||i.bodyRevision<0)throw new Error(`invalid canonical manifest for ${e}`)}function A(i,e){let s;try{s=JSON.parse(i)}catch(t){throw new q(`malformed canonical mutation in ${e}`,{cause:t})}if(s.schemaVersion!==b||s.sessionId!==e||!Number.isSafeInteger(s.revision)||s.revision<1||!Number.isSafeInteger(s.sequence)||s.sequence<1||!["append","upsert","tombstone"].includes(s.mutation))throw new q(`invalid canonical mutation in ${e}`);return s}function P(i,e){return{schemaVersion:b,daemonInstallationId:e,sessionId:i,bodyRevision:0,nextSequence:1,firstAvailableSeq:null,lastAvailableSeq:null,mutationCount:0,visibleMessageCount:0,lastVisibleMessageId:null,lastBodyCommittedAt:null,segment:v,logByteLength:0,updatedAt:new Date(0).toISOString()}}function C(i){return{schemaVersion:b,sessionId:i,segment:v,entries:[]}}function H(){return{bodyRevision:0,firstAvailableSeq:null,lastAvailableSeq:null,hasOlder:!1,messages:[],entries:[]}}function O(i){if(!Number.isSafeInteger(i)||i<1||i>500)throw new I("limit must be an integer between 1 and 500");return i}function B(i){return D.createHash("sha256").update(i).digest("hex")}function E(i){return D.createHash("sha256").update(i).digest("hex").slice(0,32)}function z(i){return D.createHash("sha256").update(JSON.stringify({mutation:i.mutation,message:i.message??null,deliveryState:i.deliveryState??null})).digest("hex")}function T(i,e,s,t,n){return D.createHash("sha256").update(`${i}\0${e}\0${s}\0${t}\0${n}`).digest("hex")}function _(i,e){o.mkdirSync(c.dirname(i),{recursive:!0,mode:448});const s=o.openSync(i,"a",384);try{const t=o.fstatSync(s).size;return o.writeFileSync(s,e,"utf8"),o.fsyncSync(s),t}finally{o.closeSync(s)}}function w(i,e){const s=c.dirname(i);o.mkdirSync(s,{recursive:!0,mode:448});const t=`${i}.tmp-${process.pid}-${D.randomBytes(6).toString("hex")}`;let n;try{n=o.openSync(t,"wx",384),o.writeFileSync(n,`${JSON.stringify(e,null,2)}
5
+ `,"utf8"),o.fsyncSync(n),o.closeSync(n),n=void 0,o.renameSync(t,i),U(s)}finally{n!==void 0&&o.closeSync(n);try{o.unlinkSync(t)}catch{}}}function U(i){if(process.platform==="win32")return;const e=o.openSync(i,"r");try{o.fsyncSync(e)}finally{o.closeSync(e)}}function M(i){try{return JSON.parse(o.readFileSync(i,"utf8"))}catch(e){throw new q(`failed to read canonical JSON file ${i}`,{cause:e})}}function L(i,e){const s=o.openSync(i,"r");try{const t=o.fstatSync(s).size;if(e>=t)return"";const n=Buffer.allocUnsafe(t-e);return o.readSync(s,n,0,n.length,e),n.toString("utf8")}finally{o.closeSync(s)}}export{K as CanonicalSessionStore,j as CanonicalStoreConflictError,q as CanonicalStoreCorruptionError,I as CanonicalStoreError};
@@ -1,2 +1,2 @@
1
- import O from"node:os";import{createAgent as X}from"../../agents/adapter.js";import{buildApprovalPendingPayload as Y,buildUserMessagePayload as Z}from"@shennian/wire";import{ManagerModeAdapter as ee}from"../../agents/manager.js";import{reportLog as E}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as te}from"../../native-fusion/parsers.js";import{appendMessage as x,recordSession as F}from"../store.js";import{mergeProjectedSessions as ne}from"../projection.js";import{buildManagedAgentEnv as ae}from"../../agents/config-status.js";import{materializeRemoteChatAttachments as re}from"../remote-attachments.js";import{buildToolArgsSummary as se}from"../tool-summary.js";function oe(e){if(!Array.isArray(e))return;const t=e.map(a=>{if(!a||typeof a!="object")return null;const r=a,o=typeof r.path=="string"?r.path:"",u=typeof r.name=="string"?r.name:"",g=typeof r.mimeType=="string"?r.mimeType:"";if(!o||!u||!g)return null;const f=typeof r.previewData=="string"&&r.previewData.trim()?r.previewData.trim():void 0;return{path:o,name:u,mimeType:g,kind:g.startsWith("image/")?"image":"file",...f?{previewData:f}:{}}}).filter(a=>a!=null);return t.length?t:void 0}function de(e){const t=e.indexOf(`
2
- `),a=t>0?Math.min(t,80):Math.min(e.length,80);return e.slice(0,a)}function ie(e,t,a={}){if(e.state==="tool-call"||e.state==="tool-result"){const r={runId:e.runId,sourceSeq:e.seq},o=se(e.args);return{state:e.state,runId:e.runId,seq:e.seq,sessionId:t,detailRef:r,...e.name?{name:e.name}:{},...o!==void 0?{argsSummary:o}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...a}}return{...e,sessionId:t,...a}}const le=3e4;function ue(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function ce(e,t){return`Agent send failed: ${t instanceof Error?t.message:String(t)}`}function L(e,t){return e!=="manager"?e:t==="claude"?"claude":"codex"}function H(e,t,a){e.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:t.sessionId,message:t,session:{id:t.sessionId,agentType:a.agentType,agentSessionId:a.agentSessionId??null,modelId:a.modelId??null,workDir:a.workDir,status:"active",externalChannel:null}}})}function ge(e,t){F({sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,workDir:t.workDir,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,workDir:t.workDir,status:"active",externalChannel:null}}})}function U(e){return ae(e)}function j(e,t,a){return{}}function B(e,t,a,r,o,u){e.configure?.({sessionId:t,externalChannel:r??null,env:{...U(a),...j(t,r,o),...u?{SHENNIAN_MANAGED_ROOM_CONTEXT:u}:{}}})}function fe(e,t,a){if(e!=="claude"||!a)return t;const r=t.trim();return!!r&&r.startsWith("-")&&!r.includes("/")?te(a)??t:t}function pe(e,t,a,r){let o=null;function u(n,s={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:ie(n,t,s),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function g(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function f(n){if(!n.currentRunId||!n.currentRunPhase)return;const s=n.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:t,runId:n.currentRunId,seq:s,runPhase:n.currentRunPhase},seq:s,id:`agent-heartbeat-${n.currentRunId}-${s}-${Date.now()}`})}function y(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{f(n)},le),n.heartbeatTimer.unref?.())}function i(n){const s=n?.pendingTextEvent;!n||!s||!s.text||(u({state:"delta",runId:s.runId,seq:s.seq,text:s.text,thinking:s.thinking||void 0}),n.pendingTextEvent=null)}r.on("agentEvent",n=>{const s=e.sessions.get(t),T=ue(n),I=n.state==="final"||n.state==="error"||n.state==="aborted";if(s&&(s.nextEventSeq=n.seq+1,I||(s.currentRunId=n.runId),!I&&T&&(s.currentRunPhase=T,y(s)),n.agentSessionId&&(s.agentSessionId=n.agentSessionId)),e.managerRuntime?.noteAgentEvent(t,n),n.state==="delta"&&n.thinking)return;n.state!=="delta"&&E({level:"info",sessionId:t,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}}),n.state==="delta"&&n.text&&!n.thinking?x(t,{id:`agent-${n.runId}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?x(t,{id:`agent-${n.runId}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?x(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:Y(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&x(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.message});const h=`${t}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&e.runTextAcc.set(h,(e.runTextAcc.get(h)??"")+n.text),n.agentSessionId&&n.agentSessionId!==o&&(o=n.agentSessionId,e.nativeFusion?.noteManagedSourceSession(t,L(a,n.source),n.agentSessionId),s&&F({sessionId:t,agentType:a,workDir:s.workDir,agentSessionId:n.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const S=n.text??"";if(!S)return;s?.pendingTextEvent&&s.pendingTextEvent.runId!==n.runId&&i(s),s&&!s.pendingTextEvent&&(s.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:!1}),s?.pendingTextEvent&&(s.pendingTextEvent.text+=S,s.pendingTextEvent.seq=n.seq);return}let w={};if(n.state==="final"){i(s);const S=e.runTextAcc.get(h)??"";S&&(w={messageSummary:de(S)}),e.runTextAcc.delete(h)}else n.state==="error"||n.state==="aborted"?(i(s),e.runTextAcc.delete(h)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&i(s);I&&s?.currentRunId===n.runId&&(s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,g(s),e.chatQueue?.noteTerminal(t)),u(n,w)}),r.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${t} agentType=${a}: ${n.message}`),e.sessions.delete(t),e.chatQueue?.noteTerminal(t),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:t,message:n.message,runId:"",seq:0}})})}function me(e,t){if(e.processedReqIds.add(t),e.processedReqIds.size>1e3){const a=e.processedReqIds.values().next().value;e.processedReqIds.delete(a)}}async function C(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function J(e,t,a,r,o,u,g){return g?e.send(t,a,r,o,u,g):u?.trim()?e.send(t,a,r,o,u):o?.length?e.send(t,a,r,o):r?e.send(t,a,r):e.send(t,a)}async function he(e,t,a,r,o,u,g,f){e.evictIdleSessions();const y=X(a);if(!y)throw new Error(`Unsupported agent: ${a}`);const i=r==="manager"&&a!=="manager"?new ee(y,a==="claude"?"claude":"codex"):y;B(i,t,a,g,f),await i.start(t,o,u);const n={adapter:i,workDir:o,agentType:a,sessionMode:r,agentSessionId:u??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:g??null,externalReplyTarget:f??null,externalChannelEnv:{...U(a),...j(t,g,f)}};return e.sessions.set(t,n),pe(e,t,a,i),n}function ye(e,t){const a=e.sessions.get(t),r=a?.currentRunId;if(!a||!r)return;const o=a.nextEventSeq;e.runTextAcc.delete(`${t}:${r}`),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:t,runId:r,seq:o},seq:o,id:`agent-evt-${r}-${o}`}),e.chatQueue?.noteTerminal(t)}async function qe(e,t,a){if(e.processedReqIds.has(t.id)){e.client.sendRes({type:"res",id:t.id,ok:!0});return}me(e,t.id);const{sessionId:r,text:o,agentType:u,sessionMode:g,managerConfig:f,workDir:y,agentSessionId:i,modelId:n,systemPrompt:s,managerDefaultWorkerAgentType:T,managerDefaultWorkerModelId:I,reasoningEffort:h,clientMessageId:w,sessionListProjection:S,waitForDispatch:Q,responseId:K,managedRoomContextToken:$,suppressExternalOwnerEcho:V}=t.params,k=K||t.id;ne(S);const b=null,P="";if(!r||!o){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:"sessionId and text are required"});return}const c=u,D=c==="manager"||g==="manager"?"manager":"standard";D==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(r,T??null,I??null);const q=(c==="claude"||c==="codex"||c==="workbuddy")&&typeof h=="string"&&h.trim()?h.trim():void 0,A=e.resolvePath(fe(c,y||O.homedir(),i)||O.homedir()),M=A,_=oe(t.params.attachments),p=_?.length?await re({text:o,attachments:_,workDir:A}):{text:o,attachments:_,localized:!1};let d=e.sessions.get(r);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==c||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==D||d.workDir!==A||JSON.stringify(d.externalChannel??null)!==JSON.stringify(b??null)){e.sessions.delete(r);try{await C(d)}catch{e.processedReqIds.delete(t.id)}d=void 0}else if(i&&d.agentSessionId!==i)try{await d.adapter.resume(i),d.agentSessionId=i}catch{e.sessions.delete(r);try{await C(d)}catch{e.processedReqIds.delete(t.id)}d=void 0}}if(!d)try{d=await he(e,r,c,D,A,i,b,P)}catch(l){const m=l instanceof Error&&l.message.startsWith("Unsupported agent:")?l.message:`Failed to start ${u}: ${l instanceof Error?l.message:String(l)}`;console.error(`[chat.send] start failed reqId=${t.id} sessionId=${r} agentType=${u} workDir=${A} agentSessionId=${i??""}: ${m}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:m,runId:"",seq:0}}),e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:m});return}$&&/^mrc_[A-Za-z0-9_-]{43}$/.test($)&&B(d.adapter,r,c,b,P,$);const W={id:w??`user-${t.id}`,sessionId:r,role:"user",ts:Date.now(),payload:Z(p.text,p.attachments)};E({level:"info",sessionId:r,wsEvent:"chat.send.start",metadata:{reqId:t.id,agentType:c,sessionMode:D,managerConfig:f??null,modelId:n,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:I??null,reasoningEffort:q}});const z=l=>{ge(e,{sessionId:r,agentType:c,sessionMode:D,managerConfig:f??null,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:I??null}),d.currentRunId||(d.nextEventSeq=0),e.nativeFusion?.registerManagedSend({sessionId:r,agentType:c,sourceAgentType:L(c,n),canonicalMessageId:w??null,sourceSessionKey:d.agentSessionId??i??null,text:p.text,managedEchoPolicy:l?.deliveryMode==="external_owner_queue"&&!V?"import_following":"suppress_following",externalRunId:l?.deliveryMode==="external_owner_queue"?`codex-external-${l.queuedSubmissionId??w??t.id}`:null}),x(r,W),H(e,W,{agentType:c,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n})},N=async(l,m)=>{const R=ce(c,l);console.error(`[chat.send] send failed reqId=${t.id} sessionId=${r} agentType=${u} workDir=${A} agentSessionId=${d.agentSessionId??i??""}: ${R}`),e.sessions.delete(r);try{await C(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:R,runId:"",seq:0}}),!m){const v={id:`agent-error-${t.id}-${Date.now()}`,sessionId:r,role:"agent",ts:Date.now(),payload:R};x(r,v),H(e,v,{agentType:c,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n})}m&&(e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:R}))};if(Q){let l;try{const m=p.attachments;l=await J(d.adapter,p.text,n,q,m,s,a),E({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}catch(m){await N(m,!0);return}z(l??void 0),e.client.sendRes({type:"res",id:k,ok:!0,...p.localized||l?.deliveryMode==="external_owner_queue"?{payload:{...p.localized?{localizedAttachments:!0}:{},...l?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:l.deliveryMode,queuedSubmissionId:l.queuedSubmissionId}:{}}}:{}}),E({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});return}z(),e.client.sendRes({type:"res",id:k,ok:!0,...p.localized?{payload:{localizedAttachments:!0}}:{}}),E({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});const G=p.attachments;J(d.adapter,p.text,n,q,G,s,a).then(()=>{E({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}).catch(l=>{N(l,!1)})}async function Me(e,t){const{sessionId:a}=t.params,r=e.sessions.get(a);if(r){try{await r.adapter.stop()}catch{}ye(e,a),e.activityPublisher?.publish(a,null)}e.client.sendRes({type:"res",id:t.id,ok:!0})}export{Me as handleChatAbort,qe as handleChatSend,ge as sendSessionUpdateEvent};
1
+ import V from"node:os";import{createAgent as re}from"../../agents/adapter.js";import{buildApprovalPendingPayload as oe,buildUserMessagePayload as de}from"@shennian/wire";import{ManagerModeAdapter as ie}from"../../agents/manager.js";import{reportLog as M}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as le}from"../../native-fusion/parsers.js";import{appendMessage as q,getMessageDeliveryStatus as ce,recordSession as z,upsertMessage as F}from"../store.js";import{mergeProjectedSessions as ue}from"../projection.js";import{buildManagedAgentEnv as ge}from"../../agents/config-status.js";import{parsePersonalAttachmentReferences as fe}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as pe,markAttachmentReferences as me}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as R}from"../index-publisher.js";import{writeToolDetail as Ie}from"../tool-detail-store.js";function he(e){const n=e.indexOf(`
2
+ `),s=n>0?Math.min(n,80):Math.min(e.length,80);return e.slice(0,s)}function ye(e,n,s={}){if(e.state==="tool-call"||e.state==="tool-result"){const a={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:n,detailRef:a,status:e.state==="tool-call"?"running":"completed",detailAvailable:!0,...e.durationMs!==void 0?{durationMs:e.durationMs}:{},...e.name?{name:e.name}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...s}}return{...e,sessionId:n,...s}}const Se=3e4;function Ae(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function j(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function K(e,n){return e!=="manager"?e:n==="claude"?"claude":"codex"}function Ee(e,n){z({sessionId:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:n.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,workDir:n.workDir,status:"active",externalChannel:null}}})}function G(e){return ge(e)}function X(e,n,s){return{}}function Y(e,n,s,a,i,c){e.configure?.({sessionId:n,externalChannel:a??null,env:{...G(s),...X(n,a,i),...c?{SHENNIAN_MANAGED_ROOM_CONTEXT:c}:{}}})}function xe(e,n,s){if(e!=="claude"||!s)return n;const a=n.trim();return!!a&&a.startsWith("-")&&!a.includes("/")?le(s)??n:n}function Re(e,n,s,a){let i=null;function c(t,r={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:ye(t,n,r),seq:t.seq,id:`agent-evt-${t.runId}-${t.seq}`})}function m(t){t?.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null)}function S(t){if(!t.currentRunId||!t.currentRunPhase)return;const r=t.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:n,runId:t.currentRunId,seq:r,runPhase:t.currentRunPhase},seq:r,id:`agent-heartbeat-${t.currentRunId}-${r}-${Date.now()}`})}function w(t){!t||t.heartbeatTimer||(t.heartbeatTimer=setInterval(()=>{S(t)},Se),t.heartbeatTimer.unref?.())}function l(t){const r=t?.pendingTextEvent;!t||!r||!r.text||(q(n,{id:`agent-${r.runId}-${r.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:r.text}),R(e.client),c({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),t.pendingTextEvent=null)}a.on("agentEvent",t=>{const r=e.sessions.get(n),b=Ae(t),T=t.state==="final"||t.state==="error"||t.state==="aborted";if(r&&(r.nextEventSeq=t.seq+1,T||(r.currentRunId=t.runId),!T&&b&&(r.currentRunPhase=b,w(r)),t.agentSessionId&&(r.agentSessionId=t.agentSessionId)),e.managerRuntime?.noteAgentEvent(n,t),t.state==="delta"&&t.thinking)return;t.state!=="delta"&&M({level:"info",sessionId:n,wsEvent:`agent.${t.state}`,wsDirection:"out",metadata:{runId:t.runId,seq:t.seq,agentType:s}});let A=!1;if(t.state==="tool-call"||t.state==="tool-result"){const p=`agent-${t.runId}-${t.seq}`;Ie({sessionId:n,toolCallId:p,detailRevision:t.seq,ts:Date.now(),name:t.name??"tool",...t.args!==void 0?{args:t.args}:{},...t.result!==void 0?{result:t.result}:{},status:t.state==="tool-call"?"running":"completed",...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq}}),q(n,{id:p,sessionId:n,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:t.state==="tool-call"?"tool_use":"tool_result",name:t.name,toolCallId:p,status:t.state==="tool-call"?"running":"completed",...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq},detailRevision:t.seq,detailAvailable:!0,resultOmitted:t.state==="tool-result"})}),A=!0}else t.state==="approval-pending"?(q(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:oe(t.approval)}),A=!0):(t.state==="error"||t.state==="aborted")&&t.message&&(q(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:t.message}),A=!0);A&&R(e.client);const I=`${n}:${t.runId}`;if(t.state==="delta"&&!t.thinking&&t.text&&e.runTextAcc.set(I,(e.runTextAcc.get(I)??"")+t.text),t.agentSessionId&&t.agentSessionId!==i&&(i=t.agentSessionId,e.nativeFusion?.noteManagedSourceSession(n,K(s,t.source),t.agentSessionId),r&&z({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:t.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n,agentType:s,agentSessionId:t.agentSessionId}}})),t.state==="delta"){const p=t.text??"";if(!p)return;r?.pendingTextEvent&&r.pendingTextEvent.runId!==t.runId&&l(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:t.runId,seq:t.seq,text:"",thinking:!1}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=p,r.pendingTextEvent.seq=t.seq);return}let $={};if(t.state==="final"){l(r);const p=e.runTextAcc.get(I)??"";p&&($={messageSummary:he(p)}),e.runTextAcc.delete(I)}else t.state==="error"||t.state==="aborted"?(l(r),e.runTextAcc.delete(I)):(t.state==="tool-call"||t.state==="tool-result"||t.state==="approval-pending")&&l(r);T&&r?.currentRunId===t.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,m(r),e.chatQueue?.noteTerminal(n),z({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:r.agentSessionId,status:t.state==="final"?"completed":"failed",lastActivityAt:new Date().toISOString()}),R(e.client)),c(t,$)}),a.on("error",t=>{console.error(`[chat.send] adapter error sessionId=${n} agentType=${s}: ${t.message}`),e.sessions.delete(n),e.chatQueue?.noteTerminal(n),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:n,message:t.message,runId:"",seq:0}})})}function we(e,n){if(e.processedReqIds.add(n),e.processedReqIds.size>1e3){const s=e.processedReqIds.values().next().value;e.processedReqIds.delete(s)}}async function v(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function Z(e,n,s,a,i,c,m){return m?e.send(n,s,a,i,c,m):c?.trim()?e.send(n,s,a,i,c):i?.length?e.send(n,s,a,i):a?e.send(n,s,a):e.send(n,s)}async function Te(e,n,s,a,i,c,m,S){e.evictIdleSessions();const w=re(s);if(!w)throw new Error(`Unsupported agent: ${s}`);const l=a==="manager"&&s!=="manager"?new ie(w,s==="claude"?"claude":"codex"):w;Y(l,n,s,m,S),await l.start(n,i,c);const t={adapter:l,workDir:i,agentType:s,sessionMode:a,agentSessionId:c??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:m??null,externalReplyTarget:S??null,externalChannelEnv:{...G(s),...X(n,m,S)}};return e.sessions.set(n,t),Re(e,n,s,l),t}function ke(e,n){const s=e.sessions.get(n),a=s?.currentRunId;if(!s||!a)return;const i=s.nextEventSeq;e.runTextAcc.delete(`${n}:${a}`),s.pendingTextEvent=null,s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:a,seq:i},seq:i,id:`agent-evt-${a}-${i}`}),e.chatQueue?.noteTerminal(n)}async function Le(e,n,s){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}we(e,n.id);const{sessionId:a,text:i,agentType:c,sessionMode:m,managerConfig:S,workDir:w,agentSessionId:l,modelId:t,systemPrompt:r,managerDefaultWorkerAgentType:b,managerDefaultWorkerModelId:T,reasoningEffort:A,clientMessageId:I,sessionListProjection:$,waitForDispatch:p,responseId:ee,managedRoomContextToken:P,suppressExternalOwnerEcho:te}=n.params,h=ee||n.id;ue($);const C=null,L="";if(!a||!i){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:"sessionId and text are required"});return}const g=c,D=g==="manager"||m==="manager"?"manager":"standard";D==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(a,b??null,T??null);const W=(g==="claude"||g==="codex"||g==="workbuddy")&&typeof A=="string"&&A.trim()?A.trim():void 0,k=e.resolvePath(xe(g,w||V.homedir(),l)||V.homedir()),ne=k,_=fe(n.params.attachments);_?.length&&pe(k,_);const y={text:i,attachments:_,localized:!1};let d=e.sessions.get(a);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==g||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==D||d.workDir!==k||JSON.stringify(d.externalChannel??null)!==JSON.stringify(C??null)){e.sessions.delete(a);try{await v(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}else if(l&&d.agentSessionId!==l)try{await d.adapter.resume(l),d.agentSessionId=l}catch{e.sessions.delete(a);try{await v(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}}if(!d)try{d=await Te(e,a,g,D,k,l,C,L)}catch(o){const f=o instanceof Error&&o.message.startsWith("Unsupported agent:")?o.message:`Failed to start ${c}: ${o instanceof Error?o.message:String(o)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${a} agentType=${c} workDir=${k} agentSessionId=${l??""}: ${f}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:f,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:f});return}P&&/^mrc_[A-Za-z0-9_-]{43}$/.test(P)&&Y(d.adapter,a,g,C,L,P);const E={id:I??`user-${n.id}`,sessionId:a,role:"user",ts:Date.now(),payload:de(y.text,y.attachments)},x=ce(a,E.id),H=x.state==="queued";if(x.state!=="not_found"&&!H){e.client.sendRes({type:"res",id:h,ok:!0,payload:x});return}M({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:g,sessionMode:D,managerConfig:S??null,modelId:t,managerDefaultWorkerAgentType:b??null,managerDefaultWorkerModelId:T??null,reasoningEffort:W}});const O=(o,f)=>({sessionId:a,clientMessageId:E.id,messageId:E.id,deliveryState:o,bodyRevision:f,committedAt:new Date(E.ts).toISOString()}),U=()=>{if(H)return{sessionId:a,clientMessageId:x.clientMessageId,messageId:x.messageId,deliveryState:x.deliveryState,bodyRevision:x.bodyRevision,committedAt:x.committedAt};Ee(e,{sessionId:a,agentType:g,sessionMode:D,managerConfig:S??null,workDir:ne,agentSessionId:d.agentSessionId??l??null,modelId:t,managerDefaultWorkerAgentType:b??null,managerDefaultWorkerModelId:T??null});const o=q(a,E,"committed");return me(k,_??[]),R(e.client),O("committed",o)},B=o=>{d.currentRunId||(d.nextEventSeq=0),e.nativeFusion?.registerManagedSend({sessionId:a,agentType:g,sourceAgentType:K(g,t),canonicalMessageId:I??null,sourceSessionKey:d.agentSessionId??l??null,text:y.text,managedEchoPolicy:o?.deliveryMode==="external_owner_queue"&&!te?"import_following":"suppress_following",externalRunId:o?.deliveryMode==="external_owner_queue"?`codex-external-${o.queuedSubmissionId??I??n.id}`:null});const f=o?.deliveryMode==="external_owner_queue"?"queued":"executing",u=F(a,E,f);return R(e.client),O(f,u)},Q=async(o,f)=>{const u=j(g,o);console.error(`[chat.send] send failed reqId=${n.id} sessionId=${a} agentType=${c} workDir=${k} agentSessionId=${d.agentSessionId??l??""}: ${u}`),e.sessions.delete(a);try{await v(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:u,runId:"",seq:0}}),!f){const N={id:`agent-error-${n.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:u};q(a,N),R(e.client)}f&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:u}))};if(p){try{U()}catch(u){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${u instanceof Error?u.message:String(u)}`});return}let o;try{const u=y.attachments;o=await Z(d.adapter,y.text,t,W,u,r,s),M({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(u){const N=F(a,E,"failed");R(e.client);const se=j(g,u);await Q(u,!1),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...O("failed",N),failureMessage:se}});return}const f=B(o??void 0);e.client.sendRes({type:"res",id:h,ok:!0,payload:{...f,...y.localized?{localizedAttachments:!0}:{},...o?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:o.deliveryMode,queuedSubmissionId:o.queuedSubmissionId}:{}}}),M({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let J;try{J=U()}catch(o){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${o instanceof Error?o.message:String(o)}`});return}B(),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...J,...y.localized?{localizedAttachments:!0}:{}}}),M({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const ae=y.attachments;Z(d.adapter,y.text,t,W,ae,r,s).then(()=>{M({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(o=>{F(a,E,"failed"),R(e.client),Q(o,!1)})}async function He(e,n){const{sessionId:s}=n.params,a=e.sessions.get(s);if(a){try{await a.adapter.stop()}catch{}ke(e,s),e.activityPublisher?.publish(s,null)}e.client.sendRes({type:"res",id:n.id,ok:!0})}export{He as handleChatAbort,Le as handleChatSend,Ee as sendSessionUpdateEvent};
@@ -7,7 +7,6 @@ export declare function handleFsRename(runtime: SessionManagerRuntime, req: ReqF
7
7
  export declare function handleFsExportMarkdownPdf(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
8
8
  export declare function handleFsExportMarkdownPdfSetup(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
9
9
  export declare function handleFsArchiveZip(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
10
- export declare function handleFsTransfer(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
11
10
  export declare function handleFsTransferStart(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
12
11
  export declare function handleFsTransferChunk(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
13
12
  export declare function handleFsTransferFinish(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
@@ -1 +1 @@
1
- import d from"node:fs";import g from"node:os";import p from"node:path";import{convertMarkdownToPdf as _,defaultPdfOutputPath as j,findChromiumExecutable as O,installManagedChromium as L,managedChromiumBrowsersPath as B,MarkdownPdfBrowserMissingError as U}from"../../tools/markdown-to-pdf.js";import{createZipArchive as X}from"../archive-zip.js";import{BinaryTextPreviewError as Z,decodeTextBufferAuto as H,isAutoTextEncoding as W}from"../../fs/text-decoder.js";const D="__roots__",V=2e3,Y=1024*1024*1024,G=5e3,J=1024*1024*1024;function E(e){return/^[A-Za-z]:([\\/]|$)/.test(e)||/^\\\\[^\\]+\\[^\\]+/.test(e)}function v(e){return E(e)?p.win32:p.posix}function x(e){const t=d.statSync(e);return{name:v(e).basename(e),path:e,isDir:t.isDirectory(),size:t.isFile()?t.size:void 0,modifiedAt:t.mtimeMs}}function K(){if(g.platform()==="win32"){const e=[];for(let t=65;t<=90;t+=1){const r=`${String.fromCharCode(t)}:\\`;d.existsSync(r)&&e.push({name:r,path:r,isDir:!0})}return e}return[{name:"/",path:"/",isDir:!0}]}function Q(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 q(e){if(!e||e==="."||e.trim()!==e||p.posix.isAbsolute(e)||p.win32.isAbsolute(e))return!1;const t=e.replace(/\\/g,"/");return t!==e?!1:t.split("/").every(r=>r&&r!=="."&&r!==".."&&!$(r))}function M(e){const t=e.trim();if(!t||t!==e||e==="."||e===".."||$(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 $(e){return Array.from(e).some(t=>{const r=t.charCodeAt(0);return r>=0&&r<=31})}function ee(e){return Array.isArray(e)?e.map(t=>({relativePath:String(t.relativePath||""),size:Number(t.size||0),mimeType:t.mimeType,modifiedAt:t.modifiedAt})):[]}async function de(e,t){const r=t.params.path||g.homedir(),a=t.params.rootPath||r;if(r===D){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:D,entries:K()}});return}const n=e.resolveAuthorizedPath(r,a);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}const s=n.path;try{const o=d.readdirSync(s,{withFileTypes:!0}),c=E(s)?p.win32.join:p.join,i=o.map(l=>({name:l.name,path:c(s,l.name),isDir:l.isDirectory()})).sort((l,f)=>l.isDir!==f.isDir?l.isDir?-1:1:l.name.localeCompare(f.name));e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:s,entries:i}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:Q(o,s)})}}async function ce(e,t){const r=t.params.path,a=t.params.rootPath||r,n=e.resolveAuthorizedPath(r,a);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}const s=n.path,o=t.params.encoding,c=typeof o=="string"?o:"utf8",i=t.params.offset,l=t.params.length;try{const f=d.statSync(s);if(!f.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}if(i!=null&&l!=null){const u=Math.min(l,Math.max(0,f.size-i));if(u<=0){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:"",offset:i,length:0,totalSize:f.size,path:s}});return}const P=d.openSync(s,"r");try{const k=Buffer.alloc(u),S=d.readSync(P,k,0,u,i);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:k.subarray(0,S).toString("base64"),offset:i,length:S,totalSize:f.size,path:s}})}finally{d.closeSync(P)}return}const y=t.params.maxSize||(c==="base64"?5*1024*1024:512*1024);if(f.size>y){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`File too large: ${f.size} bytes (max ${y})`,payload:{size:f.size,path:s}});return}if(c==="base64"){const u=d.readFileSync(s);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:u.toString("base64"),path:s,size:f.size}});return}if(W(c)){const u=d.readFileSync(s),P=H(u);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:P.content,path:s,size:f.size,encoding:P.encoding,encodingFallback:P.fallback}});return}const R=d.readFileSync(s,"utf-8");e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:R,path:s,size:f.size}})}catch(f){e.client.sendRes({type:"res",id:t.id,ok:!1,error:f instanceof Z?f.message:String(f)})}}async function le(e,t){const r=t.params.path,a=t.params.content,n=t.params.rootPath||r;if(!r||typeof a!="string"){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and content are required"});return}const s=e.resolveAuthorizedPath(r,n);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}try{const o=d.existsSync(s.path)?d.statSync(s.path):null;if(o&&!o.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}d.writeFileSync(s.path,a,"utf-8");const c=d.statSync(s.path);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:s.path,size:c.size,modifiedAt:c.mtimeMs}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(o)})}}async function fe(e,t){const r=t.params.path,a=t.params.newName;if(!r||!a){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and newName are required"});return}if(!M(a)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid newName"});return}const n=v(r),s=t.params.rootPath||n.dirname(r),o=e.resolveAuthorizedPath(r,s);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}try{const c=v(o.path),i=c.join(c.dirname(o.path),a),l=e.resolveAuthorizedPath(i,s);if(!l.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:l.error});return}if(d.existsSync(l.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Target already exists"});return}d.renameSync(o.path,l.path),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{oldPath:o.path,newPath:l.path,entry:x(l.path)}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function pe(e,t){const r=t.params.path,a=t.params.rootPath||p.dirname(r||"."),n=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 s=e.resolveAuthorizedPath(r,a);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}if(!/\.mdx?$/i.test(s.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Only Markdown files can be exported to PDF"});return}const o=j(s.path),c=e.resolveAuthorizedPath(o,a);if(!c.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:c.error});return}try{const i=await _(s.path,{outputPath:c.path,title:n});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:s.path,outputPath:i.outputPath,entry:x(i.outputPath)}})}catch(i){if(i instanceof U){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:i.setup});return}e.client.sendRes({type:"res",id:t.id,ok:!1,error:i instanceof Error?i.message:String(i)})}}async function he(e,t){try{await L();const r=await O();e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{ready:!0,browserPath:r,managedPath:B()}})}catch(r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:r instanceof Error?r.message:String(r)})}}function te(e){let t="";for(const a of e.trim())a.charCodeAt(0)<=31||'<>:"/\\|?*'.includes(a)?t.endsWith("-")||(t+="-"):t+=a;return t.replace(/[. ]+$/g,"")||"folder"}async function ye(e,t){const r=t.params.path,a=t.params.rootPath||r;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const n=e.resolveAuthorizedPath(r,a);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}try{if(!d.statSync(n.path).isDirectory()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a directory"});return}const o=v(n.path),c=te(o.basename(n.path)),i=p.join(g.tmpdir(),"shennian-archives"),l=p.join(i,`${c}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),f=X(n.path,l,{maxFiles:G,maxTotalSize:J});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:n.path,outputPath:f.outputPath,entry:x(f.outputPath),fileCount:f.fileCount,totalSize:f.totalSize}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function ue(e,t){const{name:r,targetPath:a,data:n,direct:s}=t.params;if(!r||!n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"name and data are required"});return}try{const o=t.params.rootPath||a||g.homedir(),c=e.resolveAuthorizedPath(a||o,o);if(!c.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:c.error});return}const i=c.path,l=s?i:p.join(i,".uploads");s||d.mkdirSync(l,{recursive:!0});const f=p.join(l,p.basename(r)),y=Buffer.from(n,"base64");d.writeFileSync(f,y),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:f}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(o)})}}async function Pe(e,t){const{name:r,targetPath:a,totalSize:n,direct:s,kind:o,baseName:c}=t.params,i=ee(t.params.manifest),l=o==="folder",f=l&&c||r;if(!f||!l&&!n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"name and totalSize are required"});return}try{const y=t.params.rootPath||a||g.homedir(),R=e.resolveAuthorizedPath(a||y,y);if(!R.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:R.error});return}const u=R.path,P=s?u:p.join(u,".uploads");s||d.mkdirSync(P,{recursive:!0});const k=`tf-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;if(l){if(!i.length){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"manifest is required for folder uploads"});return}if(i.length>V){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Too many files: ${i.length}`});return}const T=p.basename(f);if(!M(T)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid folder name"});return}const C=p.join(P,T),m=e.resolveAuthorizedPath(C,y);if(!m.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:m.error});return}const b=new Set;let z=0;const A=new Map;for(const h of i){if(!q(h.relativePath)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Invalid relativePath: ${h.relativePath}`});return}if(!Number.isFinite(h.size)||h.size<0){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Invalid size: ${h.relativePath}`});return}if(b.has(h.relativePath)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Duplicate relativePath: ${h.relativePath}`});return}if(b.add(h.relativePath),z+=h.size,z>Y){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Folder too large: ${z} bytes`});return}const I=p.join(m.path,...h.relativePath.split("/")),F=e.resolveAuthorizedPath(I,y);if(!F.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:F.error});return}const w=p.join(g.tmpdir(),`.shennian-upload-${k}-${A.size}`);d.writeFileSync(w,Buffer.alloc(0)),A.set(h.relativePath,{relativePath:h.relativePath,tempPath:w,targetPath:F.path,size:h.size})}e.pendingTransfers.set(k,{tempPath:"",targetPath:m.path,totalSize:z,kind:"folder",rootPath:y,targetDir:m.path,files:A}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{transferId:k,path:m.path}});return}const S=p.join(g.tmpdir(),`.shennian-upload-${k}`),N=p.join(P,p.basename(f));d.writeFileSync(S,Buffer.alloc(0)),e.pendingTransfers.set(k,{tempPath:S,targetPath:N,totalSize:n,kind:"file"}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{transferId:k}})}catch(y){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(y)})}}async function ke(e,t){const{transferId:r,offset:a,data:n,relativePath:s}=t.params,o=e.pendingTransfers.get(r);if(!o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid transferId"});return}try{const c=o.kind==="folder"?o.files?.get(String(s||"")):{tempPath:o.tempPath,size:o.totalSize};if(!c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid relativePath"});return}const i=Buffer.from(n,"base64");if(a<0||a+i.length>c.size){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Chunk exceeds declared size"});return}const l=d.openSync(c.tempPath,"r+");try{d.writeSync(l,i,0,i.length,a)}finally{d.closeSync(l)}e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{written:i.length}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function ge(e,t){const{transferId:r}=t.params,a=e.pendingTransfers.get(r);if(!a){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid transferId"});return}try{if(a.kind==="folder"){let n=0;for(const s of a.files?.values()??[])d.mkdirSync(p.dirname(s.targetPath),{recursive:!0}),d.renameSync(s.tempPath,s.targetPath),n+=1;e.pendingTransfers.delete(r),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a.targetDir??a.targetPath,count:n}});return}d.renameSync(a.tempPath,a.targetPath),e.pendingTransfers.delete(r),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a.targetPath}})}catch{try{d.copyFileSync(a.tempPath,a.targetPath),d.unlinkSync(a.tempPath),e.pendingTransfers.delete(r),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a.targetPath}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(n)})}}}async function me(e,t){const{transferId:r}=t.params,a=e.pendingTransfers.get(r);if(a){const n=a.kind==="folder"?Array.from(a.files?.values()??[]).map(s=>s.tempPath):[a.tempPath];for(const s of n)try{s&&d.unlinkSync(s)}catch{}e.pendingTransfers.delete(r)}e.client.sendRes({type:"res",id:t.id,ok:!0})}function Re(e){for(const[,t]of e.pendingTransfers){const r=t.kind==="folder"?Array.from(t.files?.values()??[]).map(a=>a.tempPath):[t.tempPath];for(const a of r)try{a&&d.unlinkSync(a)}catch{}}e.pendingTransfers.clear()}export{Re as cleanupPendingTransfers,ye as handleFsArchiveZip,pe as handleFsExportMarkdownPdf,he as handleFsExportMarkdownPdfSetup,de as handleFsLs,ce as handleFsRead,fe as handleFsRename,ue as handleFsTransfer,me as handleFsTransferAbort,ke as handleFsTransferChunk,ge as handleFsTransferFinish,Pe as handleFsTransferStart,le as handleFsWrite};
1
+ import l from"node:fs";import P from"node:os";import h from"node:path";import{convertMarkdownToPdf as x,defaultPdfOutputPath as v,findChromiumExecutable as F,installManagedChromium as w,managedChromiumBrowsersPath as E,MarkdownPdfBrowserMissingError as T}from"../../tools/markdown-to-pdf.js";import{createZipArchive as b}from"../archive-zip.js";import{BinaryTextPreviewError as C,decodeTextBufferAuto as M,isAutoTextEncoding as D}from"../../fs/text-decoder.js";import{abortAttachmentTransfer as I,appendAttachmentTransferChunk as N,finishAttachmentTransfer as $,startAttachmentTransfer as _}from"../attachment-transfer-store.js";const S="__roots__",O=5e3,j=1024*1024*1024;function A(e){return/^[A-Za-z]:([\\/]|$)/.test(e)||/^\\\\[^\\]+\\[^\\]+/.test(e)}function u(e){return A(e)?h.win32:h.posix}function k(e){const t=l.statSync(e);return{name:u(e).basename(e),path:e,isDir:t.isDirectory(),size:t.isFile()?t.size:void 0,modifiedAt:t.mtimeMs}}function B(){if(P.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 L(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 Z(e){const t=e.trim();if(!t||t!==e||e==="."||e===".."||H(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 H(e){return Array.from(e).some(t=>{const r=t.charCodeAt(0);return r>=0&&r<=31})}async function Q(e,t){const r=t.params.path||P.homedir(),s=t.params.rootPath||r;if(r===S){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:S,entries:B()}});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=A(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:L(n,o)})}}async function q(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 m=Buffer.alloc(f),g=l.readSync(y,m,0,f,d);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:m.subarray(0,g).toString("base64"),offset:d,length:g,totalSize:i.size,path:o}})}finally{l.closeSync(y)}return}const R=t.params.maxSize||(c==="base64"?5*1024*1024:512*1024);if(i.size>R){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`File too large: ${i.size} bytes (max ${R})`,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(D(c)){const f=l.readFileSync(o),y=M(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 z=l.readFileSync(o,"utf-8");e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:z,path:o,size:i.size}})}catch(i){e.client.sendRes({type:"res",id:t.id,ok:!1,error:i instanceof C?i.message:String(i)})}}async function ee(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 te(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(!Z(s)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid newName"});return}const a=u(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=u(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:k(p.path)}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function re(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=v(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 x(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:k(d.outputPath)}})}catch(d){if(d instanceof T){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 se(e,t){try{await w();const r=await F();e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{ready:!0,browserPath:r,managedPath:E()}})}catch(r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:r instanceof Error?r.message:String(r)})}}function U(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 oe(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=u(a.path),c=U(n.basename(a.path)),d=h.join(P.tmpdir(),"shennian-archives"),p=h.join(d,`${c}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),i=b(a.path,p,{maxFiles:O,maxTotalSize:j});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:a.path,outputPath:i.outputPath,entry:k(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 ae(e,t){const r=t.params;try{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=_({...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:a})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function ne(e,t){const r=t.params;try{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=N({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:o})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function ie(e,t){const r=t.params;try{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=$(s.path,r.transferId);e.pendingTransfers.delete(r.transferId),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:a})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function de(e,t){const r=t.params;try{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=I(s.path,r.transferId);e.pendingTransfers.delete(r.transferId),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{aborted:a}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}function ce(e){e.pendingTransfers.clear()}export{ce as cleanupPendingTransfers,oe as handleFsArchiveZip,re as handleFsExportMarkdownPdf,se as handleFsExportMarkdownPdfSetup,Q as handleFsLs,q as handleFsRead,te as handleFsRename,de as handleFsTransferAbort,ne as handleFsTransferChunk,ie as handleFsTransferFinish,ae as handleFsTransferStart,ee as handleFsWrite};
@@ -0,0 +1,3 @@
1
+ import type { ReqFrame } from '@shennian/wire';
2
+ import type { SessionManagerRuntime } from '../types.js';
3
+ export declare function handleSessionMessageStatus(runtime: SessionManagerRuntime, req: ReqFrame): void;
@@ -0,0 +1 @@
1
+ import{getMessageDeliveryStatus as t}from"../store.js";function n(a,s){const e=s.params;if(!e.sessionId||!e.clientMessageId)throw new Error("sessionId and clientMessageId are required");a.client.sendRes({type:"res",id:s.id,ok:!0,payload:t(e.sessionId,e.clientMessageId)})}export{n as handleSessionMessageStatus};