shennian 0.3.63 → 0.3.65

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 (70) hide show
  1. package/dist/publish-build-manifest.json +78 -63
  2. package/dist/src/agents/adapter.d.ts +5 -1
  3. package/dist/src/agents/claude.d.ts +3 -2
  4. package/dist/src/agents/claude.js +6 -6
  5. package/dist/src/agents/workbuddy.d.ts +3 -2
  6. package/dist/src/agents/workbuddy.js +2 -2
  7. package/dist/src/index.js +3 -3
  8. package/dist/src/manager/runtime.js +8 -8
  9. package/dist/src/native-fusion/codex-files.d.ts +2 -0
  10. package/dist/src/native-fusion/codex-files.js +1 -0
  11. package/dist/src/native-fusion/codex-parser.d.ts +1 -2
  12. package/dist/src/native-fusion/codex-parser.js +3 -5
  13. package/dist/src/native-fusion/codex-user-input.d.ts +11 -0
  14. package/dist/src/native-fusion/codex-user-input.js +5 -0
  15. package/dist/src/native-fusion/file-watcher.d.ts +13 -5
  16. package/dist/src/native-fusion/file-watcher.js +1 -1
  17. package/dist/src/native-fusion/local-repository.d.ts +2 -0
  18. package/dist/src/native-fusion/local-repository.js +2 -2
  19. package/dist/src/native-fusion/parser-common.d.ts +1 -0
  20. package/dist/src/native-fusion/parser-common.js +4 -4
  21. package/dist/src/native-fusion/parsers.d.ts +1 -0
  22. package/dist/src/native-fusion/parsers.js +2 -2
  23. package/dist/src/native-fusion/service.d.ts +3 -0
  24. package/dist/src/native-fusion/service.js +2 -2
  25. package/dist/src/native-fusion/state.js +2 -2
  26. package/dist/src/native-fusion/types.d.ts +4 -0
  27. package/dist/src/personal-sync/epoch.d.ts +17 -0
  28. package/dist/src/personal-sync/epoch.js +2 -0
  29. package/dist/src/personal-sync/identity.js +2 -2
  30. package/dist/src/personal-sync/machine-authority.d.ts +11 -0
  31. package/dist/src/personal-sync/machine-authority.js +1 -0
  32. package/dist/src/personal-sync/metrics.d.ts +7 -1
  33. package/dist/src/personal-sync/metrics.js +1 -1
  34. package/dist/src/personal-sync/paths.d.ts +2 -5
  35. package/dist/src/personal-sync/paths.js +1 -1
  36. package/dist/src/relay/client.d.ts +8 -2
  37. package/dist/src/relay/client.js +1 -1
  38. package/dist/src/session/attachment-transfer-store.d.ts +10 -3
  39. package/dist/src/session/canonical-store.d.ts +2 -0
  40. package/dist/src/session/canonical-store.js +5 -5
  41. package/dist/src/session/deletion-sync-state.d.ts +18 -0
  42. package/dist/src/session/deletion-sync-state.js +2 -2
  43. package/dist/src/session/handlers/chat.js +2 -2
  44. package/dist/src/session/handlers/fs.js +1 -1
  45. package/dist/src/session/handlers/message-status.js +1 -1
  46. package/dist/src/session/handlers/title.js +1 -1
  47. package/dist/src/session/handlers/tool-detail.js +1 -1
  48. package/dist/src/session/handlers/voice-window.js +1 -1
  49. package/dist/src/session/history-subscriptions.js +1 -1
  50. package/dist/src/session/index-publisher.d.ts +2 -2
  51. package/dist/src/session/index-publisher.js +1 -1
  52. package/dist/src/session/manager.d.ts +1 -0
  53. package/dist/src/session/manager.js +1 -1
  54. package/dist/src/session/native-session-lifecycle.js +4 -4
  55. package/dist/src/session/queue.d.ts +1 -0
  56. package/dist/src/session/queue.js +4 -4
  57. package/dist/src/session/store.d.ts +16 -0
  58. package/dist/src/session/store.js +2 -2
  59. package/node_modules/@shennian/wire/dist/frames.d.ts +3 -0
  60. package/node_modules/@shennian/wire/dist/fs.d.ts +16 -6
  61. package/node_modules/@shennian/wire/dist/machine.d.ts +2 -0
  62. package/node_modules/@shennian/wire/dist/message-payload.js +2 -0
  63. package/node_modules/@shennian/wire/dist/session-sync.d.ts +76 -33
  64. package/node_modules/@shennian/wire/dist/session-sync.js +50 -2
  65. package/node_modules/@shennian/wire/dist/session.d.ts +20 -10
  66. package/node_modules/@shennian/wire/dist/tool-detail.d.ts +3 -2
  67. package/node_modules/@shennian/wire/package.json +1 -1
  68. package/package.json +1 -1
  69. package/dist/src/personal-sync/account-namespace.d.ts +0 -6
  70. package/dist/src/personal-sync/account-namespace.js +0 -1
@@ -1 +1 @@
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};
1
+ import l from"node:fs";import R from"node:os";import h from"node:path";import{convertMarkdownToPdf as w,defaultPdfOutputPath as E,findChromiumExecutable as T,installManagedChromium as b,managedChromiumBrowsersPath as I,MarkdownPdfBrowserMissingError as C}from"../../tools/markdown-to-pdf.js";import{createZipArchive as D}from"../archive-zip.js";import{BinaryTextPreviewError as M,decodeTextBufferAuto as N,isAutoTextEncoding as $}from"../../fs/text-decoder.js";import{abortAttachmentTransfer as _,appendAttachmentTransferChunk as O,finishAttachmentTransfer as j,startAttachmentTransfer as B}from"../attachment-transfer-store.js";import{assertPersonalSyncIdentity as k,getPersonalSyncIdentity as u}from"../../personal-sync/epoch.js";import{getDaemonInstallationId as P}from"../../personal-sync/identity.js";const x="__roots__",L=5e3,Z=1024*1024*1024;function v(e){return/^[A-Za-z]:([\\/]|$)/.test(e)||/^\\\\[^\\]+\\[^\\]+/.test(e)}function m(e){return v(e)?h.win32:h.posix}function g(e){const t=l.statSync(e);return{name:m(e).basename(e),path:e,isDir:t.isDirectory(),size:t.isFile()?t.size:void 0,modifiedAt:t.mtimeMs}}function H(){if(R.platform()==="win32"){const e=[];for(let t=65;t<=90;t+=1){const r=`${String.fromCharCode(t)}:\\`;l.existsSync(r)&&e.push({name:r,path:r,isDir:!0})}return e}return[{name:"/",path:"/",isDir:!0}]}function U(e,t){const r=typeof e=="object"&&e&&"code"in e?String(e.code):"";return r==="ENOENT"?`Directory not found: ${t}`:r==="EACCES"||r==="EPERM"?`Permission denied: ${t}`:e instanceof Error?e.message:String(e)}function W(e){const t=e.trim();if(!t||t!==e||e==="."||e===".."||X(e)||/[\\/:]/.test(e)||/[<>|"?*]/.test(e)||/[. ]$/.test(e))return!1;const r=e.split(".")[0]?.toUpperCase();return!(r&&/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/.test(r))}function X(e){return Array.from(e).some(t=>{const r=t.charCodeAt(0);return r>=0&&r<=31})}async function se(e,t){const r=t.params.path||R.homedir(),s=t.params.rootPath||r;if(r===x){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:x,entries:H()}});return}const a=e.resolveAuthorizedPath(r,s);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}const o=a.path;try{const n=l.readdirSync(o,{withFileTypes:!0}),c=v(o)?h.win32.join:h.join,d=n.map(p=>({name:p.name,path:c(o,p.name),isDir:p.isDirectory()})).sort((p,i)=>p.isDir!==i.isDir?p.isDir?-1:1:p.name.localeCompare(i.name));e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:o,entries:d}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:U(n,o)})}}async function oe(e,t){const r=t.params.path,s=t.params.rootPath||r,a=e.resolveAuthorizedPath(r,s);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}const o=a.path,n=t.params.encoding,c=typeof n=="string"?n:"utf8",d=t.params.offset,p=t.params.length;try{const i=l.statSync(o);if(!i.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}if(d!=null&&p!=null){const f=Math.min(p,Math.max(0,i.size-d));if(f<=0){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:"",offset:d,length:0,totalSize:i.size,path:o}});return}const y=l.openSync(o,"r");try{const A=Buffer.alloc(f),z=l.readSync(y,A,0,f,d);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:A.subarray(0,z).toString("base64"),offset:d,length:z,totalSize:i.size,path:o}})}finally{l.closeSync(y)}return}const S=t.params.maxSize||(c==="base64"?5*1024*1024:512*1024);if(i.size>S){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`File too large: ${i.size} bytes (max ${S})`,payload:{size:i.size,path:o}});return}if(c==="base64"){const f=l.readFileSync(o);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:f.toString("base64"),path:o,size:i.size}});return}if($(c)){const f=l.readFileSync(o),y=N(f);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:y.content,path:o,size:i.size,encoding:y.encoding,encodingFallback:y.fallback}});return}const F=l.readFileSync(o,"utf-8");e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:F,path:o,size:i.size}})}catch(i){e.client.sendRes({type:"res",id:t.id,ok:!1,error:i instanceof M?i.message:String(i)})}}async function ae(e,t){const r=t.params.path,s=t.params.content,a=t.params.rootPath||r;if(!r||typeof s!="string"){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and content are required"});return}const o=e.resolveAuthorizedPath(r,a);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}try{const n=l.existsSync(o.path)?l.statSync(o.path):null;if(n&&!n.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}l.writeFileSync(o.path,s,"utf-8");const c=l.statSync(o.path);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:o.path,size:c.size,modifiedAt:c.mtimeMs}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(n)})}}async function ne(e,t){const r=t.params.path,s=t.params.newName;if(!r||!s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and newName are required"});return}if(!W(s)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid newName"});return}const a=m(r),o=t.params.rootPath||a.dirname(r),n=e.resolveAuthorizedPath(r,o);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}try{const c=m(n.path),d=c.join(c.dirname(n.path),s),p=e.resolveAuthorizedPath(d,o);if(!p.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:p.error});return}if(l.existsSync(p.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Target already exists"});return}l.renameSync(n.path,p.path),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{oldPath:n.path,newPath:p.path,entry:g(p.path)}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function ie(e,t){const r=t.params.path,s=t.params.rootPath||h.dirname(r||"."),a=typeof t.params.title=="string"?t.params.title:void 0;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const o=e.resolveAuthorizedPath(r,s);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}if(!/\.mdx?$/i.test(o.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Only Markdown files can be exported to PDF"});return}const n=E(o.path),c=e.resolveAuthorizedPath(n,s);if(!c.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:c.error});return}try{const d=await w(o.path,{outputPath:c.path,title:a});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:o.path,outputPath:d.outputPath,entry:g(d.outputPath)}})}catch(d){if(d instanceof C){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"This machine needs the PDF export component before Markdown files can be exported to PDF.",payload:d.setup});return}e.client.sendRes({type:"res",id:t.id,ok:!1,error:d instanceof Error?d.message:String(d)})}}async function de(e,t){try{await b();const r=await T();e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{ready:!0,browserPath:r,managedPath:I()}})}catch(r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:r instanceof Error?r.message:String(r)})}}function V(e){let t="";for(const s of e.trim())s.charCodeAt(0)<=31||'<>:"/\\|?*'.includes(s)?t.endsWith("-")||(t+="-"):t+=s;return t.replace(/[. ]+$/g,"")||"folder"}async function ce(e,t){const r=t.params.path,s=t.params.rootPath||r;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const a=e.resolveAuthorizedPath(r,s);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}try{if(!l.statSync(a.path).isDirectory()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a directory"});return}const n=m(a.path),c=V(n.basename(a.path)),d=h.join(R.tmpdir(),"shennian-archives"),p=h.join(d,`${c}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),i=D(a.path,p,{maxFiles:L,maxTotalSize:Z});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:a.path,outputPath:i.outputPath,entry:g(i.outputPath),fileCount:i.fileCount,totalSize:i.totalSize}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o instanceof Error?o.message:String(o)})}}async function pe(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const a=B({...r,rootPath:s.path});a.reused||e.pendingTransfers.set(r.transferId,{transferId:r.transferId,rootPath:s.path,lastActivityAt:Date.now()}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),...a,...a.attachment?{attachment:{...u(P()),...a.attachment}}:{}}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function le(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const a=Buffer.from(r.data,"base64"),o=O({rootPath:s.path,transferId:r.transferId,relativePath:r.relativePath,offset:r.offset,data:a});e.pendingTransfers.set(r.transferId,{transferId:r.transferId,rootPath:s.path,lastActivityAt:Date.now()}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),transferId:r.transferId,...o}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function fe(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const a=j(s.path,r.transferId);e.pendingTransfers.delete(r.transferId),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),...a}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function he(e,t){const r=t.params;try{k(r);const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}_(s.path,r.transferId),e.pendingTransfers.delete(r.transferId),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{...u(P()),transferId:r.transferId,aborted:!0}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}function ye(e){e.pendingTransfers.clear()}export{ye as cleanupPendingTransfers,ce as handleFsArchiveZip,ie as handleFsExportMarkdownPdf,de as handleFsExportMarkdownPdfSetup,se as handleFsLs,oe as handleFsRead,ne as handleFsRename,he as handleFsTransferAbort,le as handleFsTransferChunk,fe as handleFsTransferFinish,pe as handleFsTransferStart,ae as handleFsWrite};
@@ -1 +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};
1
+ import{getMessageDeliveryStatus as a}from"../store.js";import{assertPersonalSyncIdentity as n}from"../../personal-sync/epoch.js";function o(t,s){const e=s.params;if(n(e),!e.sessionId||!e.clientMessageId)throw new Error("sessionId and clientMessageId are required");t.client.sendRes({type:"res",id:s.id,ok:!0,payload:a(e.sessionId,e.clientMessageId)})}export{o as handleSessionMessageStatus};
@@ -1 +1 @@
1
- import{createAgent as y}from"../../agents/adapter.js";import{buildManagedAgentEnv as g}from"../../agents/config-status.js";import{recordSession as f}from"../store.js";import{requestSessionIndexPublish as S}from"../index-publisher.js";function o(e){return typeof e=="string"?e.trim():""}async function w(e,i){const t=i.params,r=o(t.sessionId),d=o(t.title),n=o(t.agentSessionId),c=o(t.agentType),l=o(t.workDir)||process.cwd(),u=o(t.titleOriginEventId),p=t.titleRevision;if(!r||!d||!c||t.titleSource!=="shennian_manual"||!Number.isSafeInteger(p)||Number(p)<1||!u){e.client.sendRes({type:"res",id:i.id,ok:!1,error:"explicit shennian_manual title authority is required"});return}if(f({sessionId:r,agentType:c,workDir:l,agentSessionId:n||null,title:d,titleSource:"shennian_manual",titleRevision:Number(p),titleLockedByUser:!0,titleOriginEventId:u}),S(e.client),c!=="codex"||!n){e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{localApplied:!0,nativeWriteback:"unsupported"}});return}const s=e.sessions.get(r);if(s?.agentType==="codex"&&s.adapter.setTitle){await s.adapter.setTitle(n,d,s.workDir||l),s.agentSessionId=n,s.lastActiveAt=Date.now(),e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{synced:!0}});return}const a=y("codex");if(!a?.setTitle){e.client.sendRes({type:"res",id:i.id,ok:!1,error:"Codex adapter does not support title sync"});return}a.configure?.({sessionId:r,env:g("codex")});try{await a.start(r,l,n),await a.setTitle(n,d,l),e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{synced:!0}})}finally{await a.stop().catch(()=>{})}}export{w as handleSessionTitleSet};
1
+ import{createAgent as y}from"../../agents/adapter.js";import{buildManagedAgentEnv as f}from"../../agents/config-status.js";import{recordSession as g}from"../store.js";import{requestSessionIndexPublish as m}from"../index-publisher.js";import{assertPersonalSyncIdentity as S}from"../../personal-sync/epoch.js";function o(e){return typeof e=="string"?e.trim():""}async function R(e,i){const t=i.params;S(t);const r=o(t.sessionId),d=o(t.title),n=o(t.agentSessionId),c=o(t.agentType),l=o(t.workDir)||process.cwd(),u=o(t.titleOriginEventId),p=t.titleRevision;if(!r||!d||!c||t.titleSource!=="shennian_manual"||!Number.isSafeInteger(p)||Number(p)<1||!u){e.client.sendRes({type:"res",id:i.id,ok:!1,error:"explicit shennian_manual title authority is required"});return}if(g({sessionId:r,agentType:c,workDir:l,agentSessionId:n||null,title:d,titleSource:"shennian_manual",titleRevision:Number(p),titleLockedByUser:!0,titleOriginEventId:u}),m(e.client),c!=="codex"||!n){e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{localApplied:!0,nativeWriteback:"unsupported"}});return}const s=e.sessions.get(r);if(s?.agentType==="codex"&&s.adapter.setTitle){await s.adapter.setTitle(n,d,s.workDir||l),s.agentSessionId=n,s.lastActiveAt=Date.now(),e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{synced:!0}});return}const a=y("codex");if(!a?.setTitle){e.client.sendRes({type:"res",id:i.id,ok:!1,error:"Codex adapter does not support title sync"});return}a.configure?.({sessionId:r,env:f("codex")});try{await a.start(r,l,n),await a.setTitle(n,d,l),e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{synced:!0}})}finally{await a.stop().catch(()=>{})}}export{R as handleSessionTitleSet};
@@ -1 +1 @@
1
- import{isSystemControlPayload as I,SESSION_TOOL_DETAIL_LIMITS as l}from"@shennian/wire";import{readToolDetail as g}from"../tool-detail-store.js";const A=/(?:token|api[-_]?key|password|passwd|pwd|secret|authorization|cookie|credential|private[-_]?key)/i,_=/\b([A-Z0-9_]*(?:TOKEN|KEY|SECRET|PASSWORD|PASSWD|PWD|AUTH)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi,T=/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi,D=/\bsk-[A-Za-z0-9_-]{12,}\b/g;function O(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function M(e){const n=typeof e=="number"?e:Number(e);return!Number.isFinite(n)||n<=0?l.defaultMaxBytes:Math.min(Math.max(l.minMaxBytes,Math.floor(n)),l.maxMaxBytes)}function u(e,n){if(I(e))return"[SYSTEM CONTEXT REDACTED]";const t=e.replace(_,"$1=[REDACTED]").replace(T,"Bearer [REDACTED]").replace(D,"sk-[REDACTED]");return t.length>n?`${t.slice(0,n)}\u2026`:t}function f(e,n,t=0){if(e==null)return e;if(typeof e=="string")return u(e,n);if(typeof e=="number"||typeof e=="boolean")return e;if(t>=4)return"[Truncated]";if(Array.isArray(e))return e.slice(0,20).map(r=>f(r,n,t+1));if(typeof e=="object"){const r={};for(const[o,a]of Object.entries(e).slice(0,50))r[o]=A.test(o)?"[REDACTED]":f(a,n,t+1);return r}return String(e)}function z(e,n){const t=O(e)?.command;if(typeof t=="string")return u(t,n);if(Array.isArray(t))return u(t.map(String).join(" "),n)}function N(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}function p(e){for(let n=0;n<3;n++){const t=N(e);if(t===e.byteSize)break;e.byteSize=t}return e}function E(e,n){const t={sessionId:e.sessionId,toolCallId:e.toolCallId,detailRevision:e.detailRevision,detailStatus:n==="not_found"?"not_found":"unavailable",resultOmitted:!0,redacted:!0,byteSize:0,expiresAt:new Date(Date.now()+l.responseTtlMs).toISOString(),source:"unavailable",reason:n};return p(t)}async function C(e,n){const t=n.params,r=typeof t.sessionId=="string"?t.sessionId:"",o=typeof t.toolCallId=="string"?t.toolCallId:"",a=typeof t.detailRevision=="number"&&Number.isSafeInteger(t.detailRevision)?t.detailRevision:-1;if(!r||!o||a<0){e.client.sendRes({type:"res",id:n.id,ok:!1,error:"upgrade_required: sessionId, toolCallId and detailRevision are required"});return}const c=M(t.maxBytes),y={sessionId:r,toolCallId:o,detailRevision:a,maxBytes:c},i=g(r,o);if(!i){e.client.sendRes({type:"res",id:n.id,ok:!0,payload:E(y,"not_found")});return}if(i.detailRevision!==a){e.client.sendRes({type:"res",id:n.id,ok:!0,payload:E(y,"revision_mismatch")});return}const d=Math.max(512,Math.floor(c/4)),m=i.args==null?void 0:f(i.args,d),b=z(i.args,d),S=typeof i.result=="string"?u(i.result,d):i.result==null?void 0:u(JSON.stringify(f(i.result,d)),d),R=new Date(Date.now()+l.responseTtlMs).toISOString();let s={sessionId:r,toolCallId:o,detailRevision:a,detailStatus:"available",status:i.status,...b?{command:b}:{},...m!==void 0?{argsSummary:m}:{},...S!==void 0?{result:S}:{},resultOmitted:S===void 0,redacted:!0,byteSize:0,expiresAt:R,source:"local-tool-index"};p(s),s.byteSize>c&&(delete s.result,s.resultOmitted=!0,p(s)),s.byteSize>c&&(s=E(y,"response_too_large")),e.client.sendRes({type:"res",id:n.id,ok:!0,payload:s})}export{C as handleSessionToolDetail};
1
+ import{isSystemControlPayload as A,SESSION_TOOL_DETAIL_LIMITS as l}from"@shennian/wire";import{readToolDetail as _}from"../tool-detail-store.js";import{assertPersonalSyncIdentity as T,getPersonalSyncIdentity as p}from"../../personal-sync/epoch.js";import{getDaemonInstallationId as m}from"../store.js";const D=/(?:token|api[-_]?key|password|passwd|pwd|secret|authorization|cookie|credential|private[-_]?key)/i,O=/\b([A-Z0-9_]*(?:TOKEN|KEY|SECRET|PASSWORD|PASSWD|PWD|AUTH)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi,M=/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi,z=/\bsk-[A-Za-z0-9_-]{12,}\b/g;function N(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function k(e){const n=typeof e=="number"?e:Number(e);return!Number.isFinite(n)||n<=0?l.defaultMaxBytes:Math.min(Math.max(l.minMaxBytes,Math.floor(n)),l.maxMaxBytes)}function c(e,n){if(A(e))return"[SYSTEM CONTEXT REDACTED]";const t=e.replace(O,"$1=[REDACTED]").replace(M,"Bearer [REDACTED]").replace(z,"sk-[REDACTED]");return t.length>n?`${t.slice(0,n)}\u2026`:t}function f(e,n,t=0){if(e==null)return e;if(typeof e=="string")return c(e,n);if(typeof e=="number"||typeof e=="boolean")return e;if(t>=4)return"[Truncated]";if(Array.isArray(e))return e.slice(0,20).map(i=>f(i,n,t+1));if(typeof e=="object"){const i={};for(const[s,a]of Object.entries(e).slice(0,50))i[s]=D.test(s)?"[REDACTED]":f(a,n,t+1);return i}return String(e)}function x(e,n){const t=N(e)?.command;if(typeof t=="string")return c(t,n);if(Array.isArray(t))return c(t.map(String).join(" "),n)}function C(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}function E(e){for(let n=0;n<3;n++){const t=C(e);if(t===e.byteSize)break;e.byteSize=t}return e}function I(e,n){const t={...p(m()),sessionId:e.sessionId,toolCallId:e.toolCallId,detailRevision:e.detailRevision,detailStatus:n==="not_found"?"not_found":"unavailable",resultOmitted:!0,redacted:!0,byteSize:0,expiresAt:new Date(Date.now()+l.responseTtlMs).toISOString(),source:"unavailable",reason:n};return E(t)}async function j(e,n){const t=n.params;T(t);const i=typeof t.sessionId=="string"?t.sessionId:"",s=typeof t.toolCallId=="string"?t.toolCallId:"",a=typeof t.detailRevision=="number"&&Number.isSafeInteger(t.detailRevision)?t.detailRevision:-1;if(!i||!s||a<0){e.client.sendRes({type:"res",id:n.id,ok:!1,error:"upgrade_required: sessionId, toolCallId and detailRevision are required"});return}const u=k(t.maxBytes),y={...p(m()),sessionId:i,toolCallId:s,detailRevision:a,maxBytes:u},r=_(i,s);if(!r){e.client.sendRes({type:"res",id:n.id,ok:!0,payload:I(y,"not_found")});return}if(r.detailRevision!==a){e.client.sendRes({type:"res",id:n.id,ok:!0,payload:I(y,"revision_mismatch")});return}const d=Math.max(512,Math.floor(u/4)),b=r.args==null?void 0:f(r.args,d),g=x(r.args,d),S=typeof r.result=="string"?c(r.result,d):r.result==null?void 0:c(JSON.stringify(f(r.result,d)),d),R=new Date(Date.now()+l.responseTtlMs).toISOString();let o={...p(m()),sessionId:i,toolCallId:s,detailRevision:a,detailStatus:"available",status:r.status,...g?{command:g}:{},...b!==void 0?{argsSummary:b}:{},...S!==void 0?{result:S}:{},resultOmitted:S===void 0,redacted:!0,byteSize:0,expiresAt:R,source:"local-tool-index"};E(o),o.byteSize>u&&(delete o.result,o.resultOmitted=!0,E(o)),o.byteSize>u&&(o=I(y,"response_too_large")),e.client.sendRes({type:"res",id:n.id,ok:!0,payload:o})}export{j as handleSessionToolDetail};
@@ -1 +1 @@
1
- import{extractPayloadText as m,isAgentHiddenPayload as h,isSystemControlPayload as g,isToolPayload as p}from"@shennian/wire";import{getCanonicalSessionStore as I}from"../store.js";const S=8,y=32*1024;function M(e,i){const t=i.params;if(!t?.sessionId?.trim()||!t.callId?.trim())throw new Error("sessionId and callId are required");const d=u(t.limit,4,S),l=u(t.maxBytes,y,y),o=I().readRecent(t.sessionId,Math.min(d*6,48)),s=o.messages.filter(b),n=[];let c=0;for(let r=s.length-1;r>=0&&n.length<d;r-=1){const f=s[r],a=Buffer.byteLength(JSON.stringify(f),"utf8");if(n.length>0&&c+a>l)break;a>l||(n.push(f),c+=a)}n.reverse(),e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:t.sessionId,callId:t.callId,bodyRevision:o.bodyRevision,messages:n,truncated:o.hasOlder||n.length<s.length}})}function b(e){return e.role!=="user"&&e.role!=="agent"||p(e.payload)||g(e.payload)||h(e.payload)?!1:m(e.payload).trim().length>0}function u(e,i,t){if(e===void 0)return i;if(!Number.isSafeInteger(e)||e<1)throw new Error("invalid voice window limit");return Math.min(e,t)}export{M as handleVoiceSessionBodyWindow};
1
+ import{extractPayloadText as u,isAgentHiddenPayload as g,isSystemControlPayload as I,isToolPayload as h}from"@shennian/wire";import{getCanonicalSessionStore as p,getDaemonInstallationId as S}from"../store.js";import{assertPersonalSyncIdentity as b,getPersonalSyncIdentity as w}from"../../personal-sync/epoch.js";const x=8,y=32*1024;function R(e,i){const t=i.params;if(b(t),!t?.sessionId?.trim()||!t.callId?.trim())throw new Error("sessionId and callId are required");const l=m(t.limit,4,x),d=m(t.maxBytes,y,y),o=p().readRecent(t.sessionId,Math.min(l*6,48)),s=o.messages.filter(M),n=[];let c=0;for(let r=s.length-1;r>=0&&n.length<l;r-=1){const f=s[r],a=Buffer.byteLength(JSON.stringify(f),"utf8");if(n.length>0&&c+a>d)break;a>d||(n.push(f),c+=a)}n.reverse(),e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...w(S()),sessionId:t.sessionId,callId:t.callId,bodyRevision:o.bodyRevision,messages:n,truncated:o.hasOlder||n.length<s.length}})}function M(e){return e.role!=="user"&&e.role!=="agent"||h(e.payload)||I(e.payload)||g(e.payload)?!1:u(e.payload).trim().length>0}function m(e,i,t){if(e===void 0)return i;if(!Number.isSafeInteger(e)||e<1)throw new Error("invalid voice window limit");return Math.min(e,t)}export{R as handleVoiceSessionBodyWindow};
@@ -1 +1 @@
1
- import{SESSION_HISTORY_LIMITS as c}from"@shennian/wire";import{getCanonicalSessionStore as p,listSessionRecords as h}from"./store.js";import{recordPersonalSyncLocalMetric as n,setPersonalSyncLocalSubscriptions as a}from"../personal-sync/metrics.js";class I{client;store;hasLocalSession;subscriptions=new Map;unsubscribeStore;constructor(i,s=p(),o=e=>h().some(t=>t.sessionId===e)){this.client=i,this.store=s,this.hasLocalSession=o,this.unsubscribeStore=s.subscribe(e=>this.handleMutation(e))}handleOpen(i){const s=i.params;if(d(s.sessionId,s.subscriptionId),!this.hasLocalSession(s.sessionId)&&!this.store.getManifest(s.sessionId))throw new Error("history_unavailable");if(this.subscriptions.has(s.subscriptionId))throw new Error("subscription already exists");if([...this.subscriptions.values()].filter(t=>t.sessionId===s.sessionId).length>=c.maxSubscriptionsPerSession)throw new Error("subscription_limit_exceeded");const e={sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:0,lastSentRevision:0,active:!1,buffered:[]};this.subscriptions.set(s.subscriptionId,e),a(this.subscriptions.size);try{const t=this.store.readPage(s.sessionId,{limit:u(s.limit,c.defaultSnapshotMessages),...s.beforeSequence===void 0?{}:{beforeSequence:s.beforeSequence}});e.snapshotRevision=t.bodyRevision,e.lastSentRevision=t.bodyRevision,this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:s.sessionId,subscriptionId:s.subscriptionId,storageMode:"machine_local",snapshotRevision:t.bodyRevision,firstAvailableSeq:t.firstAvailableSeq,lastAvailableSeq:t.lastAvailableSeq,hasOlder:t.hasOlder,messages:t.entries}}),n("historySnapshots"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(t.entries),"utf8")),e.active=!0;for(const l of e.buffered)this.publishMutation(e,l);e.buffered=[]}catch(t){throw this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size),t}}handlePage(i){const s=i.params,o=this.requireSubscription(s.sessionId,s.subscriptionId);let e;try{e=this.store.readPageAtRevision(s.sessionId,{beforeSequence:s.beforeSequence,limit:u(s.limit,c.defaultPageMessages),revision:o.snapshotRevision})}catch{throw new Error("snapshot_compacted")}this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:o.snapshotRevision,firstAvailableSeq:e.firstAvailableSeq,hasOlder:e.hasOlder,messages:e.entries}}),n("historyPages"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(e.entries),"utf8"))}handleClose(i){const s=i.params;d(s.sessionId,s.subscriptionId),this.subscriptions.get(s.subscriptionId)?.sessionId===s.sessionId&&(this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size)),this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:s.sessionId,subscriptionId:s.subscriptionId,closed:!0}})}closeAll(){this.subscriptions.clear(),a(0),this.unsubscribeStore()}closeSession(i){for(const[s,o]of this.subscriptions.entries())o.sessionId===i&&this.subscriptions.delete(s);a(this.subscriptions.size)}handleMutation(i){for(const s of this.subscriptions.values())s.sessionId===i.sessionId&&(s.active?this.publishMutation(s,i):s.buffered.push(i))}publishMutation(i,s){s.revision<=i.lastSentRevision||(this.client.sendEvent({type:"event",event:"session.body.delta",payload:{sessionId:i.sessionId,subscriptionId:i.subscriptionId,fromRevision:i.lastSentRevision,toRevision:s.revision,mutations:[{...s,subscriptionId:i.subscriptionId}]}}),n("historyDeltas"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(s),"utf8")),i.lastSentRevision=s.revision)}requireSubscription(i,s){d(i,s);const o=this.subscriptions.get(s);if(!o||o.sessionId!==i)throw new Error("subscription_not_found");return o}}function d(r,i){if(typeof r!="string"||!r||typeof i!="string"||!i)throw new Error("invalid history subscription identity")}function u(r,i){const s=r??i;if(!Number.isSafeInteger(s)||s<1||s>c.maxPageMessages)throw new Error("invalid history limit");return s}export{I as PersonalHistorySubscriptions};
1
+ import{SESSION_HISTORY_LIMITS as d}from"@shennian/wire";import{getCanonicalSessionStore as b,listSessionRecords as f}from"./store.js";import{recordPersonalSyncLocalMetric as n,setPersonalSyncLocalSubscriptions as a}from"../personal-sync/metrics.js";import{assertPersonalSyncIdentity as l,getPersonalSyncIdentity as c}from"../personal-sync/epoch.js";class m{client;store;hasLocalSession;subscriptions=new Map;unsubscribeStore;constructor(i,s=b(),o=e=>f().some(t=>t.sessionId===e)){this.client=i,this.store=s,this.hasLocalSession=o,this.unsubscribeStore=s.subscribe(e=>this.handleMutation(e))}handleOpen(i){const s=i.params;if(l(s),u(s.sessionId,s.subscriptionId),!this.hasLocalSession(s.sessionId)&&!this.store.getManifest(s.sessionId))throw new Error("history_unavailable");if(this.subscriptions.has(s.subscriptionId))throw new Error("subscription already exists");if([...this.subscriptions.values()].filter(t=>t.sessionId===s.sessionId).length>=d.maxSubscriptionsPerSession)throw new Error("subscription_limit_exceeded");const e={sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:0,lastSentRevision:0,active:!1,buffered:[]};this.subscriptions.set(s.subscriptionId,e),a(this.subscriptions.size);try{const t=this.store.readPage(s.sessionId,{limit:h(s.limit,d.defaultSnapshotMessages),...s.beforeSequence===void 0?{}:{beforeSequence:s.beforeSequence}});e.snapshotRevision=t.bodyRevision,e.lastSentRevision=t.bodyRevision,this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,storageMode:"machine_local",snapshotRevision:t.bodyRevision,firstAvailableSeq:t.firstAvailableSeq,lastAvailableSeq:t.lastAvailableSeq,hasOlder:t.hasOlder,messages:t.entries}}),n("historySnapshots"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(t.entries),"utf8")),e.active=!0;for(const p of e.buffered)this.publishMutation(e,p);e.buffered=[]}catch(t){throw this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size),t}}handlePage(i){const s=i.params;l(s);const o=this.requireSubscription(s.sessionId,s.subscriptionId);let e;try{e=this.store.readPageAtRevision(s.sessionId,{beforeSequence:s.beforeSequence,limit:h(s.limit,d.defaultPageMessages),revision:o.snapshotRevision})}catch{throw new Error("snapshot_compacted")}this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:o.snapshotRevision,firstAvailableSeq:e.firstAvailableSeq,hasOlder:e.hasOlder,messages:e.entries}}),n("historyPages"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(e.entries),"utf8"))}handleClose(i){const s=i.params;l(s),u(s.sessionId,s.subscriptionId),this.subscriptions.get(s.subscriptionId)?.sessionId===s.sessionId&&(this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size)),this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,closed:!0}})}closeAll(){this.subscriptions.clear(),a(0),this.unsubscribeStore()}closeSession(i){for(const[s,o]of this.subscriptions.entries())o.sessionId===i&&this.subscriptions.delete(s);a(this.subscriptions.size)}handleMutation(i){for(const s of this.subscriptions.values())s.sessionId===i.sessionId&&(s.active?this.publishMutation(s,i):s.buffered.push(i))}publishMutation(i,s){s.revision<=i.lastSentRevision||(this.client.sendEvent({type:"event",event:"session.body.delta",payload:{...c(this.store.daemonInstallationId),sessionId:i.sessionId,subscriptionId:i.subscriptionId,fromRevision:i.lastSentRevision,toRevision:s.revision,mutations:[{...s,subscriptionId:i.subscriptionId}]}}),n("historyDeltas"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(s),"utf8")),i.lastSentRevision=s.revision)}requireSubscription(i,s){u(i,s);const o=this.subscriptions.get(s);if(!o||o.sessionId!==i)throw new Error("subscription_not_found");return o}}function u(r,i){if(typeof r!="string"||!r||typeof i!="string"||!i)throw new Error("invalid history subscription identity")}function h(r,i){const s=r??i;if(!Number.isSafeInteger(s)||s<1||s>d.maxPageMessages)throw new Error("invalid history limit");return s}export{m as PersonalHistorySubscriptions};
@@ -1,3 +1,3 @@
1
1
  import type { CliRelayClient } from '../relay/client.js';
2
- export declare function publishPendingSessionIndexes(client: Pick<CliRelayClient, 'sendBufferedEvent' | 'sendReq'>): Promise<void>;
3
- export declare function requestSessionIndexPublish(client: Pick<CliRelayClient, 'sendBufferedEvent' | 'sendReq'>): void;
2
+ export declare function publishPendingSessionIndexes(client: Pick<CliRelayClient, 'sendBufferedEvent' | 'sendReq'> & Partial<Pick<CliRelayClient, 'disconnect'>>): Promise<void>;
3
+ export declare function requestSessionIndexPublish(client: Pick<CliRelayClient, 'sendBufferedEvent' | 'sendReq'> & Partial<Pick<CliRelayClient, 'disconnect'>>): void;
@@ -1 +1 @@
1
- import c from"node:crypto";import{acknowledgeSessionIndexUpdate as l,applyGeneratedSessionTitle as f,claimSessionTitleGenerationCandidates as p,listPendingSessionIndexUpdates as u}from"./store.js";import{recordPersonalSyncLocalMetric as r}from"../personal-sync/metrics.js";const t=new WeakMap,d=new WeakSet;function g(s){const n=s,o=t.get(n);if(o)return d.add(n),o;const e=y(s).finally(()=>{t.get(n)===e&&t.delete(n),d.delete(n)&&x(s)});return t.set(n,e),e}async function y(s){const n=u(),o=[];for(const e of n)try{await s.sendBufferedEvent({type:"event",event:"session.index.updated",id:`session-index-${e.daemonInstallationId}-${e.session.id}-${e.session.indexRevision}`,payload:e},6e4),l(e.session.id,e.session.indexRevision),r("indexAcknowledged")}catch(i){o.push(i),r("indexFailed"),r("indexDirtyRetained")}if(o.length>0)throw new AggregateError(o,"one or more session indexes failed");for(const e of p())try{const i=await s.sendReq({type:"req",id:`session-title-generate-${c.randomUUID()}`,method:"session.title.generate",params:e},15e3);if(!i.ok)continue;const a=i.payload;a?.generated&&f(e.sessionId,a)&&d.add(s)}catch(i){console.warn(`[session-title] generation attempt ended sessionId=${e.sessionId} requestId=${e.requestId}: ${i instanceof Error?i.message:String(i)}`)}}function x(s){g(s).catch(n=>{console.error("[session-index] sync failed; dirty revisions retained for retry",n)})}export{g as publishPendingSessionIndexes,x as requestSessionIndexPublish};
1
+ import x from"node:crypto";import{acknowledgeSessionIndexUpdate as v,applyGeneratedSessionTitle as h,claimSessionTitleGenerationCandidates as R,listPendingSessionIndexUpdates as q,listSessionRecords as w,purgeCanonicalSession as _,purgeSessionRecord as A,recordSessionIndexRejection as p}from"./store.js";import{recordPersonalSyncLocalMetric as d}from"../personal-sync/metrics.js";import{SessionSyncRejectedError as E}from"../relay/client.js";import{NativeSourceDeletionStore as k}from"../native-fusion/native-source-deletions.js";import{getDaemonInstallationId as D}from"../personal-sync/identity.js";import{purgeToolDetails as $}from"./tool-detail-store.js";import{purgeQueuedSessionData as b}from"./queue.js";import{getPersonalSyncIdentity as T}from"../personal-sync/epoch.js";const c=new WeakMap,u=new WeakSet;function N(i){const n=i,t=c.get(n);if(t)return u.add(n),t;const e=P(i).finally(()=>{c.get(n)===e&&c.delete(n),u.delete(n)&&F(i)});return c.set(n,e),e}async function P(i){const n=q(),t=[];for(const e of n)try{const o=await i.sendBufferedEvent({type:"event",event:"session.index.updated",id:`session-index-${e.daemonInstallationId}-${e.session.id}-${e.session.indexRevision}`,payload:e},6e4);o&&U(e,o),v(e.session.id,e.session.indexRevision),d("indexAcknowledged")}catch(o){const r=j(e,o,i);r==="retry"&&t.push(o),d("indexFailed"),r==="retry"&&d("indexDirtyRetained")}if(t.length>0)throw new AggregateError(t,"one or more session indexes failed");for(const e of R())try{const o=await i.sendReq({type:"req",id:`session-title-generate-${x.randomUUID()}`,method:"session.title.generate",params:{...T(),...e}},15e3);if(!o.ok)continue;const r=o.payload;r?.generated&&h(e.sessionId,r)&&u.add(i)}catch(o){console.warn(`[session-title] generation attempt ended sessionId=${e.sessionId} requestId=${e.requestId}: ${o instanceof Error?o.message:String(o)}`)}}function U(i,n){if(!n)return;const t=n.payload;if(!t||t.daemonInstallationId!==i.daemonInstallationId||t.dataEpoch!==i.dataEpoch||t.sessionId!==i.session.id||t.indexRevision!==i.session.indexRevision)throw new Error("invalid session index acknowledgement identity")}function j(i,n,t){const e=i.session.id,o=i.session.indexRevision;if(n instanceof E){const{rejection:s}=n;if(s.requiredAction==="purge_session"&&s.code==="session_deleted"){const a=w().find(S=>S.sessionId===e),m=Math.max(1,a?.deletionRevision??0,s.authoritativeRevision??0),g=new k({daemonInstallationId:D()});return g.recordSession({sessionId:e,deletionRevision:m}),a?.agentSessionId&&g.record({sessionId:e,deletionRevision:m,agentType:a.agentType,sourceSessionKey:a.agentSessionId}),b(e),$(e),_(e),A(e),"purged"}if(s.retryable&&s.requiredAction==="retry")return d("transientRetries"),p({sessionId:e,indexRevision:o,code:s.code,disposition:"retry"});const I=s.requiredAction==="quarantine_record"||s.code==="room_authority_conflict"||s.code==="invalid_payload"||s.code==="invalid_identity",f=p({sessionId:e,indexRevision:o,code:s.code,disposition:I?"quarantined":"terminal"});return d(f==="quarantined"?"quarantinedRecords":"permanentStops"),(s.requiredAction==="refresh_machine_ownership"||s.requiredAction==="upgrade_client"||s.requiredAction==="reset_epoch"||s.requiredAction==="stop")&&t.disconnect?.(),f}const r=n instanceof Error?n.message:String(n),l=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND/i.test(r),y=p({sessionId:e,indexRevision:o,code:l?"transport_unavailable":"unstructured_rejection",disposition:l?"retry":"terminal"});return d(l?"transientRetries":"permanentStops"),y}function F(i){N(i).catch(n=>{console.error("[session-index] sync failed; dirty revisions retained for retry",n)})}export{N as publishPendingSessionIndexes,F as requestSessionIndexPublish};
@@ -36,6 +36,7 @@ export declare class SessionManager {
36
36
  private getRuntime;
37
37
  getUpgradeIdleState(): UpgradeIdleState;
38
38
  synchronizeSessionDeletions(): Promise<void>;
39
+ private synchronizeSessionDeletionsOnce;
39
40
  private applySessionDeletion;
40
41
  activateRoomSession(input: {
41
42
  sessionId: string;
@@ -1 +1 @@
1
- import{SESSION_SYNC_PROTOCOL_VERSION as h,isAvailableAgentType as b}from"@shennian/wire";import l from"node:crypto";import{getRegisteredAgents as w,unregisterAgent as y}from"../agents/adapter.js";import{loadConfig as R}from"../config/index.js";import{handleUpgradeStart as S,handleUpgradeStatus as k}from"../commands/upgrade.js";import{handleAgentsRefresh as I,handleModelsRefresh as A}from"./handlers/agents.js";import{handleAgentCapabilitiesList as T}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as C,handleAgentConfigGet as P,handleAgentConfigTest as D,handleAgentConfigUpsert as M}from"./handlers/agent-config.js";import{handleChatAbort as d,handleChatSend as u}from"./handlers/chat.js";import{handleSessionRefresh as x}from"./handlers/session-refresh.js";import{handleSessionToolDetail as E}from"./handlers/tool-detail.js";import{handleSessionTitleSet as F}from"./handlers/title.js";import{handleSessionMessageStatus as O}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as N}from"./handlers/voice-window.js";import{cleanupPendingTransfers as U,handleFsLs as _,handleFsRead as Q,handleFsRename as V,handleFsWrite as z,handleFsTransferAbort as W,handleFsTransferChunk as $,handleFsTransferFinish as B,handleFsTransferStart as L,handleFsExportMarkdownPdf as j,handleFsExportMarkdownPdfSetup as G,handleFsArchiveZip as H}from"./handlers/fs.js";import{handleRegionProbe as K,handleRegionSwitch as X,handleUpgradeSetPolicy as Y}from"./handlers/control.js";import{ManagerRuntimeService as Z,setManagerRuntimeService as m}from"../manager/runtime.js";import{ChatQueueManager as J}from"./queue.js";import{createAuthorizedFsRoot as q,resolveAuthorizedPath as ee,resolveSessionWorkDir as se}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as te}from"../agents/custom.js";import{handleManagedRoomBind as ie,handleManagedRoomRestart as ae}from"./handlers/room-managed.js";import{getDaemonInstallationId as p,listSessionRecords as f,purgeCanonicalSession as ne,purgeSessionRecord as oe}from"./store.js";import{PersonalHistorySubscriptions as re}from"./history-subscriptions.js";import{sweepAttachmentTransfers as ce}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as le}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as he}from"./tool-detail-store.js";import{readDeletionSyncRevision as de,writeDeletionSyncRevision as g}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as ue}from"./native-cleanup-outbox.js";const me=50;function pe(r,e){if(!r||!Number.isSafeInteger(r.baselineRevision)||r.baselineRevision<e||!Number.isSafeInteger(r.latestRevision)||r.latestRevision<r.baselineRevision||!Array.isArray(r.deletions))throw new Error("invalid deletion sync response");let s=r.baselineRevision;for(const t of r.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const i of t.sources)if(!i?.sourceSessionKey||!b(i.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==r.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as qe}from"../fs/boundary.js";class Ye{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,s=null,t,i=null,a=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=i,this.managedRoomBinder=a,this.managerRuntime=new Z({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.chatQueue=new J({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.historySubscriptions=new re(this.client),this.nativeCleanupOutbox=new ue({logger:console.info}),this.nativeCleanupOutbox.start(),m(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...f().map(o=>o.workDir),...Array.from(this.pendingTransfers.values()).map(o=>o.rootPath)]);for(const o of n)try{ce(this.resolvePath(o))}catch(c){console.error("[attachment.cleanup] failed",c instanceof Error?c.message:String(c))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,s)=>this.resolveAuthorizedPath(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async synchronizeSessionDeletions(){const e=p(),s=de(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${l.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:h,daemonInstallationId:e,afterRevision:s}});if(!t.ok)throw new Error(t.error??"session deletion sync failed");const i=t.payload;pe(i,s);let a=i.baselineRevision;a>s&&g(a);for(const o of i.deletions)o.deletionRevision<=a||(await this.applySessionDeletion(o),a=o.deletionRevision,g(a));if(a!==i.latestRevision)throw new Error(`deletion revision gap: applied ${a}, latest ${i.latestRevision}`);if(a===0)return;const n=await this.client.sendReq({type:"req",id:`session-deletions-ack-${l.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:h,daemonInstallationId:e,deletionRevision:a}});if(!n.ok)throw new Error(n.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new le({daemonInstallationId:p()});s.recordSession(e);for(const i of e.sources)s.record({...e,...i});const t=this.sessions.get(e.sessionId);if(t){try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),he(e.sessionId),ne(e.sessionId),oe(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}async activateRoomSession(e){const s=f().find(n=>n.sessionId===e.sessionId),t=s?.agentType===e.agentType?s.modelId??void 0:void 0,i={value:null},a=new Proxy(this.client,{get:(n,o)=>{if(o==="sendRes")return v=>{i.value=v};const c=Reflect.get(n,o,n);return typeof c=="function"?c.bind(n):c}});return await u({...this.getRuntime(),client:a},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:t,sessionMode:s?.sessionMode??(s?.agentType==="manager"?"manager":"standard"),managerConfig:s?.managerConfig??null,managerDefaultWorkerAgentType:s?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s?.managerDefaultWorkerModelId??null,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:i.value?.ok===!0&&i.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const s=new Proxy(this.client,{get:(t,i)=>{if(i==="sendRes")return()=>{};const a=Reflect.get(t,i,t);return typeof a=="function"?a.bind(t):a}});await d({...this.getRuntime(),client:s},{type:"req",id:`room-activation-abort-${l.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,s){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:s}})}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const i=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:i,canStop:t.canStop??!0});const a=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:a,runPhase:t.runPhase,canStop:t.canStop??!0},seq:a,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of w())s.startsWith("custom:")&&y(s);const e=R();for(const[s,t]of Object.entries(e.customAgents??{}))te(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":await u(s,e);break;case"chat.enqueue":await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":await this.chatQueue.handleGet(e);break;case"chat.queue.edit":await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":await this.chatQueue.handleDelete(e);break;case"chat.abort":await d(s,e);break;case"session.refresh":await x(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":O(s,e);break;case"session.tool.detail":await E(s,e);break;case"voice.session.body.window":N(s,e);break;case"session.title.set":await F(s,e);break;case"room.agent.bind-managed":await ie(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await ae(s,this.managedRoomBinder,e);break;case"fs.ls":await _(s,e);break;case"fs.read":await Q(s,e);break;case"fs.write":await z(s,e);break;case"fs.export.markdown-pdf":await j(s,e);break;case"fs.export.markdown-pdf.setup":await G(s,e);break;case"fs.archive.zip":await H(s,e);break;case"fs.rename":await V(s,e);break;case"fs.transfer.start":await L(s,e);break;case"fs.transfer.chunk":await $(s,e);break;case"fs.transfer.finish":await B(s,e);break;case"fs.transfer.abort":await W(s,e);break;case"region.probe":await K(s,e);break;case"region.switch":await X(s,e);break;case"upgrade.start":await S(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await k(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await Y(s,e);break;case"agents.refresh":await I(s,e);break;case"models.refresh":await A(s,e);break;case"agent.config.get":await P(s,e);break;case"agent.config.upsert":await M(s,e);break;case"agent.config.clear":await C(s,e);break;case"agent.config.test":await D(s,e);break;case"agent.capabilities.list":await T(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<me)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return se(e)}resolveAuthorizedPath(e,s){return ee(e,q(s))}async cleanup(){this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),U(this.getRuntime()),await this.managerRuntime.stop(),m(null)}}export{Ye as SessionManager,qe as resolveSessionWorkDir};
1
+ import{SESSION_SYNC_PROTOCOL_VERSION as u,SESSION_SYNC_DATA_EPOCH as m,isAvailableAgentType as S}from"@shennian/wire";import h from"node:crypto";import{getRegisteredAgents as R,unregisterAgent as k}from"../agents/adapter.js";import{loadConfig as I}from"../config/index.js";import{handleUpgradeStart as A,handleUpgradeStatus as T}from"../commands/upgrade.js";import{handleAgentsRefresh as C,handleModelsRefresh as P}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as E,handleAgentConfigGet as O,handleAgentConfigTest as M,handleAgentConfigUpsert as x}from"./handlers/agent-config.js";import{handleChatAbort as p,handleChatSend as f}from"./handlers/chat.js";import{handleSessionRefresh as F}from"./handlers/session-refresh.js";import{handleSessionToolDetail as _}from"./handlers/tool-detail.js";import{handleSessionTitleSet as N}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as z}from"./handlers/voice-window.js";import{cleanupPendingTransfers as Q,handleFsLs as V,handleFsRead as W,handleFsRename as $,handleFsWrite as j,handleFsTransferAbort as B,handleFsTransferChunk as L,handleFsTransferFinish as Y,handleFsTransferStart as G,handleFsExportMarkdownPdf as H,handleFsExportMarkdownPdfSetup as K,handleFsArchiveZip as X}from"./handlers/fs.js";import{handleRegionProbe as Z,handleRegionSwitch as J,handleUpgradeSetPolicy as q}from"./handlers/control.js";import{ManagerRuntimeService as ee,setManagerRuntimeService as g}from"../manager/runtime.js";import{ChatQueueManager as se}from"./queue.js";import{createAuthorizedFsRoot as te,resolveAuthorizedPath as ie,resolveSessionWorkDir as ne}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as ae}from"../agents/custom.js";import{handleManagedRoomBind as re,handleManagedRoomRestart as oe}from"./handlers/room-managed.js";import{getDaemonInstallationId as v,listSessionRecords as b,purgeCanonicalSession as ce,purgeSessionRecord as le}from"./store.js";import{PersonalHistorySubscriptions as he}from"./history-subscriptions.js";import{sweepAttachmentTransfers as de}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ue}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as me}from"./tool-detail-store.js";import{readDeletionSyncRevision as pe,writeDeletionSyncRevision as y}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as fe,clearDeletionSyncFailure as ge,DeletionSyncRetryWaitError as ve,DeletionSyncTerminalError as be,recordDeletionSyncFailure as d}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as ye}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as c}from"../personal-sync/epoch.js";const we=50;function Se(o,e){if(!o||!Number.isSafeInteger(o.baselineRevision)||o.baselineRevision<e||!Number.isSafeInteger(o.latestRevision)||o.latestRevision<o.baselineRevision||!Array.isArray(o.deletions))throw new Error("invalid deletion sync response");let s=o.baselineRevision;for(const t of o.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const i of t.sources)if(!i?.sourceSessionKey||!S(i.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==o.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as cs}from"../fs/boundary.js";class as{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,s=null,t,i=null,n=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=i,this.managedRoomBinder=n,this.managerRuntime=new ee({getRuntime:()=>this.getRuntime(),dispatchReq:r=>this.handleReq(r)}),this.chatQueue=new se({getRuntime:()=>this.getRuntime(),dispatchReq:r=>this.handleReq(r)}),this.historySubscriptions=new he(this.client),this.nativeCleanupOutbox=new ye({logger:console.info}),this.nativeCleanupOutbox.start(),g(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(r=>{console.error("[session.activity] managed probe failed",r)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const r=new Set([...b().map(a=>a.workDir),...Array.from(this.pendingTransfers.values()).map(a=>a.rootPath)]);for(const a of r)try{de(this.resolvePath(a))}catch(l){console.error("[attachment.cleanup] failed",l instanceof Error?l.message:String(l))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,s)=>this.resolveAuthorizedPath(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async synchronizeSessionDeletions(){fe();try{await this.synchronizeSessionDeletionsOnce(),ge()}catch(e){if(e instanceof ve||e instanceof be)throw e;const s=e instanceof Error?e.message:String(e),t=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(s);throw d({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=v(),s=pe(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${h.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const a=t.rejection;throw a?(d({code:a.code,retryable:a.retryable&&a.requiredAction==="retry"}),new Error(a.code)):(d({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const i=t.payload;c(i),Se(i,s);let n=i.baselineRevision;n>s&&y(n);for(const a of i.deletions)a.deletionRevision<=n||(await this.applySessionDeletion(a),n=a.deletionRevision,y(n));if(n!==i.latestRevision)throw new Error(`deletion revision gap: applied ${n}, latest ${i.latestRevision}`);if(n===0)return;const r=await this.client.sendReq({type:"req",id:`session-deletions-ack-${h.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,deletionRevision:n}});if(!r.ok)throw new Error(r.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new ue({daemonInstallationId:v()});s.recordSession(e);for(const i of e.sources)s.record({...e,...i});const t=this.sessions.get(e.sessionId);if(t){try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),me(e.sessionId),ce(e.sessionId),le(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}async activateRoomSession(e){const s=b().find(r=>r.sessionId===e.sessionId),t=s?.agentType===e.agentType?s.modelId??void 0:void 0,i={value:null},n=new Proxy(this.client,{get:(r,a)=>{if(a==="sendRes")return w=>{i.value=w};const l=Reflect.get(r,a,r);return typeof l=="function"?l.bind(r):l}});return await f({...this.getRuntime(),client:n},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:t,sessionMode:s?.sessionMode??(s?.agentType==="manager"?"manager":"standard"),managerConfig:s?.managerConfig??null,managerDefaultWorkerAgentType:s?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s?.managerDefaultWorkerModelId??null,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:i.value?.ok===!0&&i.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const s=new Proxy(this.client,{get:(t,i)=>{if(i==="sendRes")return()=>{};const n=Reflect.get(t,i,t);return typeof n=="function"?n.bind(t):n}});await p({...this.getRuntime(),client:s},{type:"req",id:`room-activation-abort-${h.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,s){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:s}})}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const i=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:i,canStop:t.canStop??!0});const n=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:n,runPhase:t.runPhase,canStop:t.canStop??!0},seq:n,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of R())s.startsWith("custom:")&&k(s);const e=I();for(const[s,t]of Object.entries(e.customAgents??{}))ae(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":c(e.params),await f(s,e);break;case"chat.enqueue":c(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":c(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":c(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":c(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":c(e.params),await p(s,e);break;case"session.refresh":c(e.params),await F(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":U(s,e);break;case"session.tool.detail":await _(s,e);break;case"voice.session.body.window":z(s,e);break;case"session.title.set":await N(s,e);break;case"room.agent.bind-managed":await re(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await oe(s,this.managedRoomBinder,e);break;case"fs.ls":await V(s,e);break;case"fs.read":await W(s,e);break;case"fs.write":await j(s,e);break;case"fs.export.markdown-pdf":await H(s,e);break;case"fs.export.markdown-pdf.setup":await K(s,e);break;case"fs.archive.zip":await X(s,e);break;case"fs.rename":await $(s,e);break;case"fs.transfer.start":await G(s,e);break;case"fs.transfer.chunk":await L(s,e);break;case"fs.transfer.finish":await Y(s,e);break;case"fs.transfer.abort":await B(s,e);break;case"region.probe":await Z(s,e);break;case"region.switch":await J(s,e);break;case"upgrade.start":await A(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await T(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await q(s,e);break;case"agents.refresh":await C(s,e);break;case"models.refresh":await P(s,e);break;case"agent.config.get":await O(s,e);break;case"agent.config.upsert":await x(s,e);break;case"agent.config.clear":await E(s,e);break;case"agent.config.test":await M(s,e);break;case"agent.capabilities.list":await D(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<we)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return ne(e)}resolveAuthorizedPath(e,s){return ie(e,te(s))}async cleanup(){this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),Q(this.getRuntime()),await this.managerRuntime.stop(),g(null)}}export{as as SessionManager,cs as resolveSessionWorkDir};
@@ -1,6 +1,6 @@
1
- import{createInterface as E}from"node:readline";import D from"node:fs";import{isAvailableBuiltinAgentType as g}from"@shennian/wire";import{buildAgentProcessEnv as w}from"../agent-env.js";import{resolveBuiltinCommand as b,spawnAgentCommand as y}from"../agents/command-spec.js";const C=64*1024,h=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,k=/^ses_[A-Za-z0-9_-]{6,191}$/;function A(n,r){return n.length>=C?n:`${n}${String(r)}`.slice(0,C)}const x=async(n,r,i=1e4,e)=>{if(!g(n))return{code:null,stdout:"",stderr:"",timedOut:!1,spawnErrorCode:"command_unavailable"};const t=b(n);return t?await new Promise(d=>{let s="",l="",u=!1,p=!1,m;const c=y(t,r,{stdio:["ignore","pipe","pipe"],env:w({NO_COLOR:"1"}),...e?{cwd:e}:{}}),a=o=>{u||(u=!0,m&&clearTimeout(m),d({...o,stdout:s,stderr:l,timedOut:p}))};c.stdout?.on("data",o=>{s=A(s,o)}),c.stderr?.on("data",o=>{l=A(l,o)}),c.on("error",o=>{a({code:null,spawnErrorCode:o.code??"spawn_failed"})}),c.on("close",o=>a({code:o})),m=setTimeout(()=>{p=!0,c.kill("SIGTERM"),setTimeout(()=>c.kill("SIGKILL"),1e3).unref?.()},i),m.unref?.()}):{code:null,stdout:"",stderr:"",timedOut:!1,spawnErrorCode:"command_unavailable"}};function $(n){return`${n.stdout}
1
+ import{createInterface as E}from"node:readline";import D from"node:fs";import{isAvailableBuiltinAgentType as g}from"@shennian/wire";import{buildAgentProcessEnv as w}from"../agent-env.js";import{resolveBuiltinCommand as b,spawnAgentCommand as y}from"../agents/command-spec.js";const C=64*1024,h=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,k=/^ses_[A-Za-z0-9_-]{6,191}$/;function A(n,r){return n.length>=C?n:`${n}${String(r)}`.slice(0,C)}const x=async(n,r,i=1e4,e)=>{if(!g(n))return{code:null,stdout:"",stderr:"",timedOut:!1,spawnErrorCode:"command_unavailable"};const t=b(n);return t?await new Promise(c=>{let s="",l="",d=!1,m=!1;const p=y(t,r,{stdio:["ignore","pipe","pipe"],env:w({NO_COLOR:"1"}),...e?{cwd:e}:{}}),u=o=>{d||(d=!0,a&&clearTimeout(a),c({...o,stdout:s,stderr:l,timedOut:m}))};p.stdout?.on("data",o=>{s=A(s,o)}),p.stderr?.on("data",o=>{l=A(l,o)}),p.on("error",o=>{u({code:null,spawnErrorCode:o.code??"spawn_failed"})}),p.on("close",o=>u({code:o}));const a=setTimeout(()=>{m=!0,p.kill("SIGTERM"),setTimeout(()=>p.kill("SIGKILL"),1e3).unref?.()},i);a.unref?.()}):{code:null,stdout:"",stderr:"",timedOut:!1,spawnErrorCode:"command_unavailable"}};function $(n){return`${n.stdout}
2
2
  ${n.stderr}`.toLowerCase()}function f(n,r){if(n.spawnErrorCode==="command_unavailable"||n.spawnErrorCode==="ENOENT")return{outcome:"unsupported"};if(n.timedOut)return{outcome:"transient",errorCode:"command_timeout"};const i=$(n);return/not found|does not exist|no session|unknown session|already (?:deleted|archived)/.test(i)?{outcome:"already_absent"}:/busy|locked|writer|in use|current active session|active turn|temporar|try again|timed out|timeout/.test(i)?{outcome:"transient",errorCode:"native_busy"}:/invalid.*(?:uuid|session|id)|malformed/.test(i)?{outcome:"invalid_identity"}:/permission denied|unauthenticated|authentication|forbidden/.test(i)?{outcome:"permanent",errorCode:"native_auth_required"}:n.code===0?{outcome:r}:{outcome:"transient",errorCode:"native_command_failed"}}async function _(n,r,i,e){const t=await n(r,i);return!t.timedOut&&t.code===0&&e.test(`${t.stdout}
3
- ${t.stderr}`)}async function I(n,r){return await new Promise(i=>{let e=null,t=!1,d=1,s=null;const l=new Map,u=c=>{if(!t){t=!0,s&&clearTimeout(s);for(const a of l.values())a.reject(new Error("app_server_stopped"));l.clear(),e?.kill("SIGTERM"),i(c)}},p=(c,a)=>{if(!e?.stdin)return Promise.reject(new Error("app_server_unavailable"));const o=d++;return new Promise((v,O)=>{l.set(o,{resolve:v,reject:O}),e.stdin.write(`${JSON.stringify({id:o,method:c,params:a})}
4
- `)})};try{e=y(n,["app-server","--listen","stdio://"],{stdio:["pipe","pipe","ignore"],env:w({NO_COLOR:"1"})})}catch{u({outcome:"transient",errorCode:"app_server_start_failed"});return}E({input:e.stdout}).on("line",c=>{if(!c.trim())return;let a;try{a=JSON.parse(c)}catch{return}if(a.id==null)return;const o=l.get(a.id);if(o)if(l.delete(a.id),a.error){const v=new Error(a.error.message??"app_server_error");v.rpcCode=a.error.code,o.reject(v)}else o.resolve(a.result)}),e.on("error",()=>u({outcome:"transient",errorCode:"app_server_start_failed"})),e.on("close",()=>{t||u({outcome:"transient",errorCode:"app_server_stopped"})}),s=setTimeout(()=>u({outcome:"transient",errorCode:"app_server_timeout"}),12e3),s.unref?.(),(async()=>{try{await p("initialize",{clientInfo:{name:"shennian-native-cleanup",title:"Shennian",version:"1"},capabilities:{experimentalApi:!0}}),await p("thread/archive",{threadId:r}),u({outcome:"archived"})}catch(c){const a=c,o=a.message.toLowerCase();/not found|does not exist|already archived|no rollout/.test(o)?u({outcome:"already_absent"}):/busy|locked|writer|active turn|in use/.test(o)?u({outcome:"transient",errorCode:"native_busy"}):a.rpcCode===-32601||/method not found|unsupported/.test(o)?u({outcome:"unsupported"}):u({outcome:"transient",errorCode:"app_server_failed"})}})()})}class S{options;capabilities=new Map;constructor(r={}){this.options=r}async execute(r,i,e={}){const t=this.adapter(r),d=await this.probe(r,t);if(d.canArchive&&t.archive)return await t.archive(i);if(d.canDelete&&t.delete){if(r==="gemini"&&!e.workDir)return{outcome:"invalid_identity"};if((r==="gemini"||r==="opencode")&&e.workDir)try{if(!D.statSync(e.workDir).isDirectory())return{outcome:"permanent",errorCode:"workdir_unavailable"}}catch{return{outcome:"permanent",errorCode:"workdir_unavailable"}}return await this.deleteWithContext(t,i,e)}return{outcome:"unsupported"}}async deleteWithContext(r,i,e){return r.delete?await r.delete(i,e):{outcome:"unsupported"}}clearCapabilityCache(){this.capabilities.clear()}probe(r,i){const e=this.capabilities.get(r);if(e)return e;const t=i.probe().catch(()=>({canArchive:!1,canDelete:!1}));return this.capabilities.set(r,t),t}adapter(r){const i=this.options.runner??x;return r==="codex"?{probe:async()=>({canArchive:await _(i,"codex",["--help"],/\barchive\b/),canDelete:!1}),archive:async e=>{if(!h.test(e))return{outcome:"invalid_identity"};const t=this.options.codexArchive,d=b("codex");let s={outcome:"unsupported"};return t?s=await t(e):d&&(s=await I(d,e)),s.outcome==="archived"||s.outcome==="already_absent"||s.outcome==="invalid_identity"||s.outcome==="transient"&&s.errorCode==="native_busy"?s:f(await i("codex",["archive",e],15e3),"archived")}}:r==="gemini"?{probe:async()=>({canArchive:!1,canDelete:await _(i,"gemini",["--help"],/--delete-session\b/)}),delete:async(e,t)=>{if(!h.test(e))return{outcome:"invalid_identity"};const d=await i("gemini",["--delete-session",e],15e3,t?.workDir);return/invalid session identifier/i.test(`${d.stdout}
5
- ${d.stderr}`)?{outcome:"already_absent"}:f(d,"deleted")}}:r==="opencode"?{probe:async()=>({canArchive:!1,canDelete:await _(i,"opencode",["session","delete","--help"],/session delete\s+<sessionID>/i)}),delete:async(e,t)=>k.test(e)?f(await i("opencode",["session","delete",e],15e3,t?.workDir),"deleted"):{outcome:"invalid_identity"}}:r==="cursor"?{probe:async()=>{const e=await i("cursor",["--help"]),t=`${e.stdout}
3
+ ${t.stderr}`)}async function I(n,r){return await new Promise(i=>{let e=null,t=!1,c=1,s=null;const l=new Map,d=u=>{if(!t){t=!0,s&&clearTimeout(s);for(const a of l.values())a.reject(new Error("app_server_stopped"));l.clear(),e?.kill("SIGTERM"),i(u)}},m=(u,a)=>{if(!e?.stdin)return Promise.reject(new Error("app_server_unavailable"));const o=c++;return new Promise((v,O)=>{l.set(o,{resolve:v,reject:O}),e.stdin.write(`${JSON.stringify({id:o,method:u,params:a})}
4
+ `)})};try{e=y(n,["app-server","--listen","stdio://"],{stdio:["pipe","pipe","ignore"],env:w({NO_COLOR:"1"})})}catch{d({outcome:"transient",errorCode:"app_server_start_failed"});return}E({input:e.stdout}).on("line",u=>{if(!u.trim())return;let a;try{a=JSON.parse(u)}catch{return}if(a.id==null)return;const o=l.get(a.id);if(o)if(l.delete(a.id),a.error){const v=new Error(a.error.message??"app_server_error");v.rpcCode=a.error.code,o.reject(v)}else o.resolve(a.result)}),e.on("error",()=>d({outcome:"transient",errorCode:"app_server_start_failed"})),e.on("close",()=>{t||d({outcome:"transient",errorCode:"app_server_stopped"})}),s=setTimeout(()=>d({outcome:"transient",errorCode:"app_server_timeout"}),12e3),s.unref?.(),(async()=>{try{await m("initialize",{clientInfo:{name:"shennian-native-cleanup",title:"Shennian",version:"1"},capabilities:{experimentalApi:!0}}),await m("thread/archive",{threadId:r}),d({outcome:"archived"})}catch(u){const a=u,o=a.message.toLowerCase();/not found|does not exist|already archived|no rollout/.test(o)?d({outcome:"already_absent"}):/busy|locked|writer|active turn|in use/.test(o)?d({outcome:"transient",errorCode:"native_busy"}):a.rpcCode===-32601||/method not found|unsupported/.test(o)?d({outcome:"unsupported"}):d({outcome:"transient",errorCode:"app_server_failed"})}})()})}class S{options;capabilities=new Map;constructor(r={}){this.options=r}async execute(r,i,e={}){const t=this.adapter(r),c=await this.probe(r,t);if(c.canArchive&&t.archive)return await t.archive(i);if(c.canDelete&&t.delete){if(r==="gemini"&&!e.workDir)return{outcome:"invalid_identity"};if((r==="gemini"||r==="opencode")&&e.workDir)try{if(!D.statSync(e.workDir).isDirectory())return{outcome:"permanent",errorCode:"workdir_unavailable"}}catch{return{outcome:"permanent",errorCode:"workdir_unavailable"}}return await this.deleteWithContext(t,i,e)}return{outcome:"unsupported"}}async deleteWithContext(r,i,e){return r.delete?await r.delete(i,e):{outcome:"unsupported"}}clearCapabilityCache(){this.capabilities.clear()}probe(r,i){const e=this.capabilities.get(r);if(e)return e;const t=i.probe().catch(()=>({canArchive:!1,canDelete:!1}));return this.capabilities.set(r,t),t}adapter(r){const i=this.options.runner??x;return r==="codex"?{probe:async()=>({canArchive:await _(i,"codex",["--help"],/\barchive\b/),canDelete:!1}),archive:async e=>{if(!h.test(e))return{outcome:"invalid_identity"};const t=this.options.codexArchive,c=b("codex");let s={outcome:"unsupported"};return t?s=await t(e):c&&(s=await I(c,e)),s.outcome==="archived"||s.outcome==="already_absent"||s.outcome==="invalid_identity"||s.outcome==="transient"&&s.errorCode==="native_busy"?s:f(await i("codex",["archive",e],15e3),"archived")}}:r==="gemini"?{probe:async()=>({canArchive:!1,canDelete:await _(i,"gemini",["--help"],/--delete-session\b/)}),delete:async(e,t)=>{if(!h.test(e))return{outcome:"invalid_identity"};const c=await i("gemini",["--delete-session",e],15e3,t?.workDir);return/invalid session identifier/i.test(`${c.stdout}
5
+ ${c.stderr}`)?{outcome:"already_absent"}:f(c,"deleted")}}:r==="opencode"?{probe:async()=>({canArchive:!1,canDelete:await _(i,"opencode",["session","delete","--help"],/session delete\s+<sessionID>/i)}),delete:async(e,t)=>k.test(e)?f(await i("opencode",["session","delete",e],15e3,t?.workDir),"deleted"):{outcome:"invalid_identity"}}:r==="cursor"?{probe:async()=>{const e=await i("cursor",["--help"]),t=`${e.stdout}
6
6
  ${e.stderr}`;return{canArchive:e.code===0&&/\barchive-chat\b/.test(t),canDelete:e.code===0&&/\bdelete-chat\b/.test(t)}},archive:async e=>h.test(e)?f(await i("cursor",["archive-chat",e],15e3),"archived"):{outcome:"invalid_identity"},delete:async e=>h.test(e)?f(await i("cursor",["delete-chat",e],15e3),"deleted"):{outcome:"invalid_identity"}}:{probe:async()=>({canArchive:!1,canDelete:!1})}}}export{S as NativeSessionLifecycleRegistry,x as runNativeLifecycleCommand};
@@ -22,3 +22,4 @@ export declare class ChatQueueManager {
22
22
  private isSessionBusy;
23
23
  private broadcast;
24
24
  }
25
+ export declare function purgeQueuedSessionData(sessionId: string): void;
@@ -1,5 +1,5 @@
1
- import l from"node:fs";import{randomUUID as y}from"node:crypto";import{buildUserMessagePayload as A}from"@shennian/wire";import{mergeProjectedSessions as E}from"./projection.js";import{parsePersonalAttachmentReferences as I}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as D,markAttachmentReferences as w}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as R}from"../personal-sync/paths.js";import{appendMessage as $,getMessageDeliveryStatus as T,recordSession as Q,tombstoneMessage as W,upsertMessage as b}from"./store.js";import{requestSessionIndexPublish as M}from"./index-publisher.js";function C(){return{sessions:{}}}function x(){return new Date().toISOString()}function N(n){return`enqueue-send-${n}-${y()}`}function f(){try{const n=JSON.parse(l.readFileSync(v(),"utf-8"));return{sessions:n.sessions&&typeof n.sessions=="object"?n.sessions:{}}}catch(n){if(n.code==="ENOENT"||n instanceof Error&&n.message.startsWith("ENOENT:"))return C();throw n}}function p(n){const e=R();l.mkdirSync(e,{recursive:!0,mode:448});const s=v(),t=`${s}.tmp-${process.pid}-${y()}`;try{l.writeFileSync(t,`${JSON.stringify(n,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"});const i=l.openSync(t,"r");if(typeof i=="number")try{l.fsyncSync(i)}finally{l.closeSync(i)}l.renameSync(t,s)}finally{try{l.unlinkSync(t)}catch{}}}function v(){return R("outbound-queue.json")}function B(n){const e=x();return{id:n.queueMessageId||n.clientMessageId||`queue-${y()}`,sessionId:n.sessionId,text:n.text,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,systemPrompt:n.systemPrompt??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:n.managerDefaultWorkerModelId??null,reasoningEffort:n.reasoningEffort??null,clientMessageId:n.clientMessageId??null,attachments:I(n.attachments),externalChannel:n.externalChannel??null,replyTarget:n.replyTarget??null,origin:n.origin,createdAt:e,updatedAt:e}}function S(n){return{id:n.clientMessageId??n.id,sessionId:n.sessionId,role:"user",ts:Date.parse(n.createdAt),payload:A(n.text,n.attachments)}}class Y{opts;draining=new Set;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:f().sessions[e]??[]}}async handleEnqueue(e){const s=this.opts.getRuntime(),t=e.params;if(E(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=s.resolvePath(t.workDir),o=I(t.attachments);o?.length&&D(i,o);const r={text:t.text,attachments:o,localized:!1},a=await this.isSessionBusy(t.sessionId),d=f(),h=d.sessions[t.sessionId]??[];if(!a&&!h.length){await this.opts.dispatchReq({...e,id:N(e.id),method:"chat.send",params:{...t,text:r.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:r.attachments}});return}const c=B({...t,text:r.text,attachments:r.attachments}),P=c.clientMessageId??c.id,m=T(t.sessionId,P);if(m.state!=="not_found"){s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:m.state==="queued",queueMessageId:c.id,...m,queue:this.getSnapshot(t.sessionId)}});return}const g=z(h,c);g||(d.sessions[t.sessionId]=[...h,c],p(d));const u=g??c;Q({sessionId:u.sessionId,agentType:u.agentType,sessionMode:u.sessionMode,managerConfig:u.managerConfig,workDir:u.workDir,agentSessionId:u.agentSessionId,modelId:u.modelId}),$(u.sessionId,S(u),"queued"),w(s.resolvePath(u.workDir),u.attachments??[]),M(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:g?.id??c.id,...T(t.sessionId,g?.clientMessageId??g?.id??c.clientMessageId??c.id),queue:this.getSnapshot(t.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),a||this.drainNext(t.sessionId)}async handleGet(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(t.sessionId)}async handleEdit(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const i=f(),o=i.sessions[t.sessionId]??[],r=o.findIndex(h=>h.id===t.queueMessageId);if(r<0){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}const a=I(t.attachments),d=s.resolvePath(o[r].workDir);a?.length&&D(d,a),o[r]={...o[r],text:t.text,attachments:a,updatedAt:x()},i.sessions[t.sessionId]=o,p(i),b(t.sessionId,S(o[r]),"queued"),o[r].attachments?.length&&w(d,o[r].attachments??[]),M(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const i=f(),o=i.sessions[t.sessionId]??[],r=o.find(d=>d.id===t.queueMessageId),a=o.filter(d=>d.id!==t.queueMessageId);if(a.length===o.length){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?i.sessions[t.sessionId]=a:delete i.sessions[t.sessionId],p(i),W(t.sessionId,r?.clientMessageId??r?.id??t.queueMessageId),M(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}purgeSession(e){const s=f();s.sessions[e]&&(delete s.sessions[e],p(s),this.draining.delete(e))}drainIdleQueues(){const e=f();for(const s of Object.keys(e.sessions))this.drainNext(s)}async drainNext(e){if(this.draining.has(e)||await this.isSessionBusy(e))return;const i=(f().sessions[e]??[])[0];if(!i){this.broadcast(e);return}const o=i;this.draining.add(e);try{b(e,S(o),"queued"),await this.dispatchQueuedMessage(o);const r=f(),a=(r.sessions[e]??[]).filter(d=>d.id!==i.id);a.length?r.sessions[e]=a:delete r.sessions[e],p(r),this.broadcast(e)}finally{this.draining.delete(e)}}mergeExternalMessages(e,s){const t=[e];for(;s[0]?.origin==="external";)t.push(s.shift());const i=j(t);return i.length===1?i[0]:{...e,id:`external-batch-${e.id}`,text:i.map(o=>o.text).filter(Boolean).join(`
1
+ import l from"node:fs";import{randomUUID as y}from"node:crypto";import{buildUserMessagePayload as Q}from"@shennian/wire";import{mergeProjectedSessions as $}from"./projection.js";import{parsePersonalAttachmentReferences as I}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as R,markAttachmentReferences as T}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as b}from"../personal-sync/paths.js";import{getPersonalSyncIdentity as M}from"../personal-sync/epoch.js";import{getDaemonInstallationId as x}from"../personal-sync/identity.js";import{appendMessage as W,getMessageDeliveryStatus as v,recordSession as C,tombstoneMessage as N,upsertMessage as P}from"./store.js";import{requestSessionIndexPublish as S}from"./index-publisher.js";function B(){return{sessions:{}}}function k(){return new Date().toISOString()}function j(s){return`enqueue-send-${s}-${y()}`}function f(){try{const s=JSON.parse(l.readFileSync(A(),"utf-8"));return{sessions:s.sessions&&typeof s.sessions=="object"?s.sessions:{}}}catch(s){if(s.code==="ENOENT"||s instanceof Error&&s.message.startsWith("ENOENT:"))return B();throw s}}function p(s){const e=b();l.mkdirSync(e,{recursive:!0,mode:448});const n=A(),t=`${n}.tmp-${process.pid}-${y()}`;try{l.writeFileSync(t,`${JSON.stringify(s,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"});const i=l.openSync(t,"r");if(typeof i=="number")try{l.fsyncSync(i)}finally{l.closeSync(i)}l.renameSync(t,n)}finally{try{l.unlinkSync(t)}catch{}}}function A(){return b("outbound-queue.json")}function z(s){const e=k();return{...M(x()),id:s.queueMessageId||s.clientMessageId||`queue-${y()}`,sessionId:s.sessionId,text:s.text,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,reasoningEffort:s.reasoningEffort??null,clientMessageId:s.clientMessageId??null,attachments:I(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}function q(s){return{id:s.clientMessageId??s.id,sessionId:s.sessionId,role:"user",ts:Date.parse(s.createdAt),payload:Q(s.text,s.attachments)}}class ne{opts;draining=new Set;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{...M(x()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:f().sessions[e]??[]}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if($(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=n.resolvePath(t.workDir),o=I(t.attachments);o?.length&&R(i,o);const r={text:t.text,attachments:o,localized:!1},a=await this.isSessionBusy(t.sessionId),d=f(),h=d.sessions[t.sessionId]??[];if(!a&&!h.length){await this.opts.dispatchReq({...e,id:j(e.id),method:"chat.send",params:{...t,text:r.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:r.attachments}});return}const c=z({...t,text:r.text,attachments:r.attachments}),E=c.clientMessageId??c.id,m=v(t.sessionId,E);if(m.state!=="not_found"){n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:m.state==="queued",queueMessageId:c.id,...m,queue:this.getSnapshot(t.sessionId)}});return}const g=U(h,c);g||(d.sessions[t.sessionId]=[...h,c],p(d));const u=g??c;C({sessionId:u.sessionId,agentType:u.agentType,sessionMode:u.sessionMode,managerConfig:u.managerConfig,workDir:u.workDir,agentSessionId:u.agentSessionId,modelId:u.modelId}),W(u.sessionId,q(u),"queued"),T(n.resolvePath(u.workDir),u.attachments??[]),S(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:g?.id??c.id,...v(t.sessionId,g?.clientMessageId??g?.id??c.clientMessageId??c.id),queue:this.getSnapshot(t.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),a||this.drainNext(t.sessionId)}async handleGet(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(t.sessionId)}async handleEdit(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const i=f(),o=i.sessions[t.sessionId]??[],r=o.findIndex(h=>h.id===t.queueMessageId);if(r<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}const a=I(t.attachments),d=n.resolvePath(o[r].workDir);a?.length&&R(d,a),o[r]={...o[r],text:t.text,attachments:a,updatedAt:k()},i.sessions[t.sessionId]=o,p(i),P(t.sessionId,q(o[r]),"queued"),o[r].attachments?.length&&T(d,o[r].attachments??[]),S(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const i=f(),o=i.sessions[t.sessionId]??[],r=o.find(d=>d.id===t.queueMessageId),a=o.filter(d=>d.id!==t.queueMessageId);if(a.length===o.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?i.sessions[t.sessionId]=a:delete i.sessions[t.sessionId],p(i),N(t.sessionId,r?.clientMessageId??r?.id??t.queueMessageId),S(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}purgeSession(e){F(e),this.draining.delete(e)}drainIdleQueues(){const e=f();for(const n of Object.keys(e.sessions))this.drainNext(n)}async drainNext(e){if(this.draining.has(e)||await this.isSessionBusy(e))return;const i=(f().sessions[e]??[])[0];if(!i){this.broadcast(e);return}const o=i;this.draining.add(e);try{P(e,q(o),"queued"),await this.dispatchQueuedMessage(o);const r=f(),a=(r.sessions[e]??[]).filter(d=>d.id!==i.id);a.length?r.sessions[e]=a:delete r.sessions[e],p(r),this.broadcast(e)}finally{this.draining.delete(e)}}mergeExternalMessages(e,n){const t=[e];for(;n[0]?.origin==="external";)t.push(n.shift());const i=O(t);return i.length===1?i[0]:{...e,id:`external-batch-${e.id}`,text:i.map(o=>o.text).filter(Boolean).join(`
3
3
 
4
- `),attachments:F(i.flatMap(o=>o.attachments??[])),updatedAt:x()}}async dispatchQueuedMessage(e){await this.opts.dispatchReq({type:"req",id:`queue-send-${e.id}-${Date.now()}`,method:"chat.send",params:{sessionId:e.sessionId,text:e.text,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??void 0,systemPrompt:e.systemPrompt??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId,reasoningEffort:e.reasoningEffort??void 0,clientMessageId:e.clientMessageId??e.id,attachments:e.attachments,externalChannel:e.externalChannel,replyTarget:e.replyTarget,waitForDispatch:!0}})}async isSessionBusy(e){const s=this.opts.getRuntime().sessions.get(e);if(!s)return!1;if(s.currentRunId)return!0;if(!s.adapter.getStatus)return!1;const t=await s.adapter.getStatus().catch(()=>null);return t?.active?(t.runId&&(s.currentRunId=t.runId,s.currentRunPhase=t.runPhase??s.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function j(n){const e=new Set,s=[];for(const t of n){const i=k(t);e.has(i)||(e.add(i),s.push(t))}return s}function z(n,e){const s=q(e);if(s)return n.find(i=>q(i)===s)??null;if(e.origin!=="external")return null;const t=k(e);return n.find(i=>k(i)===t)??null}function k(n){const e=q(n);return e?`id:${e}`:["body",O(n.text),...(n.attachments??[]).map(s=>s.path||`${s.name}:${s.mimeType}`)].join(`
5
- `)}function q(n){const e=typeof n.clientMessageId=="string"?n.clientMessageId.trim():"";if(e)return e;const s=typeof n.id=="string"?n.id.trim():"";return s&&!s.startsWith("queue-")?s:""}function F(n){const e=new Set,s=[];for(const t of n){const i=t.path||`${t.name}:${t.mimeType}`;e.has(i)||(e.add(i),s.push(t))}return s.length?s:void 0}function O(n){return String(n||"").replace(/\s+/g," ").trim()}export{Y as ChatQueueManager};
4
+ `),attachments:J(i.flatMap(o=>o.attachments??[])),updatedAt:k()}}async dispatchQueuedMessage(e){await this.opts.dispatchReq({type:"req",id:`queue-send-${e.id}-${Date.now()}`,method:"chat.send",params:{...M(x()),sessionId:e.sessionId,text:e.text,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??void 0,systemPrompt:e.systemPrompt??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId,reasoningEffort:e.reasoningEffort??void 0,clientMessageId:e.clientMessageId??e.id,attachments:e.attachments,externalChannel:e.externalChannel,replyTarget:e.replyTarget,waitForDispatch:!0}})}async isSessionBusy(e){const n=this.opts.getRuntime().sessions.get(e);if(!n)return!1;if(n.currentRunId)return!0;if(!n.adapter.getStatus)return!1;const t=await n.adapter.getStatus().catch(()=>null);return t?.active?(t.runId&&(n.currentRunId=t.runId,n.currentRunPhase=t.runPhase??n.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function F(s){const e=f();e.sessions[s]&&(delete e.sessions[s],p(e))}function O(s){const e=new Set,n=[];for(const t of s){const i=D(t);e.has(i)||(e.add(i),n.push(t))}return n}function U(s,e){const n=w(e);if(n)return s.find(i=>w(i)===n)??null;if(e.origin!=="external")return null;const t=D(e);return s.find(i=>D(i)===t)??null}function D(s){const e=w(s);return e?`id:${e}`:["body",G(s.text),...(s.attachments??[]).map(n=>n.path||`${n.name}:${n.mimeType}`)].join(`
5
+ `)}function w(s){const e=typeof s.clientMessageId=="string"?s.clientMessageId.trim():"";if(e)return e;const n=typeof s.id=="string"?s.id.trim():"";return n&&!n.startsWith("queue-")?n:""}function J(s){const e=new Set,n=[];for(const t of s){const i=t.path||`${t.name}:${t.mimeType}`;e.has(i)||(e.add(i),n.push(t))}return n.length?n:void 0}function G(s){return String(s||"").replace(/\s+/g," ").trim()}export{ne as ChatQueueManager,F as purgeQueuedSessionData};
@@ -36,10 +36,18 @@ export type LocalSessionRecord = {
36
36
  lastVisibleMessageId: string | null;
37
37
  attentionCount: number;
38
38
  lastAttentionId: string | null;
39
+ /** Local-only replay ledger. This field is never included in SessionIndexUpdate. */
40
+ seenAttentionIds?: string[];
39
41
  lastBodyCommittedAt: string | null;
40
42
  deletionRevision: number;
41
43
  deletedAt: string | null;
42
44
  syncedIndexRevision: number;
45
+ indexSyncState?: 'pending' | 'applied' | 'quarantined' | 'terminal';
46
+ indexSyncStateRevision?: number;
47
+ indexSyncAttempts?: number;
48
+ indexSyncNextAttemptAt?: string | null;
49
+ indexSyncErrorCode?: string | null;
50
+ indexQuarantineCode?: string | null;
43
51
  /** Local attempt marker only. The ephemeral titleSeed is never stored. */
44
52
  titleGenerationAttemptedAt: string | null;
45
53
  };
@@ -76,6 +84,14 @@ export declare function purgeSessionRecord(sessionId: string): void;
76
84
  export declare function purgeCanonicalSession(sessionId: string): void;
77
85
  export declare function listPendingSessionIndexUpdates(): SessionIndexUpdate[];
78
86
  export declare function acknowledgeSessionIndexUpdate(sessionId: string, indexRevision: number): void;
87
+ export declare function recordSessionIndexRejection(input: {
88
+ sessionId: string;
89
+ indexRevision: number;
90
+ code: string;
91
+ disposition: 'retry' | 'quarantined' | 'terminal';
92
+ now?: number;
93
+ maxAttempts?: number;
94
+ }): 'retry' | 'quarantined' | 'terminal';
79
95
  export type SessionTitleGenerationCandidate = {
80
96
  sessionId: string;
81
97
  requestId: string;
@@ -1,2 +1,2 @@
1
- import A from"node:crypto";import r from"node:fs";import I from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as C,SESSION_TITLE_SOURCE_PRIORITY as y,sessionPreviewFromPayload as R}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as k,resolvePersonalSyncV1Path as h}from"../personal-sync/paths.js";import{CanonicalSessionStore as B}from"./canonical-store.js";import{getDaemonInstallationId as u}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as D}from"../native-fusion/native-source-deletions.js";let w=null,p=null;function M(){return h()}function g(){const t=I.join(M(),"canonical");return(!w||p!==t)&&(w=new B(t,u()),p=t),w}function b(){return I.join(M(),"session-index.json")}function c(){const t=b();if(!r.existsSync(t))return{};const e=JSON.parse(r.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);const s=u();for(const i of Object.values(e))if(i.daemonInstallationId!==s)throw new Error(`personal session index belongs to another daemon installation: ${t}`);return e}function v(t){q(b(),t)}function x(t){return R(t.payload,120)}function S(t){if(new D({rootDir:M(),daemonInstallationId:u()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function U(t){S(t.sessionId),u();const e=c(),s=e[t.sessionId],i=new Date().toISOString(),n=g().getManifest(t.sessionId)?.bodyRevision??s?.bodyRevision??0,o=g().getManifest(t.sessionId),a=L(s,t),l=t.attentionId?.trim()||null,d=!!(l&&l!==(s?.lastAttentionId??null)),f=t.lastMessagePreview===null?{preview:null,messageId:null,at:null}:t.lastMessagePreview!==void 0&&t.lastMessagePreviewMessageId&&t.lastMessagePreviewAt?{preview:t.lastMessagePreview,messageId:t.lastMessagePreviewMessageId,at:t.lastMessagePreviewAt}:{preview:s?.lastMessagePreview??null,messageId:s?.lastMessagePreviewMessageId??null,at:s?.lastMessagePreviewAt??null},m={daemonInstallationId:u(),sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??s?.sessionMode,managerConfig:t.managerConfig??s?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??s?.agentSessionId??null,modelId:t.modelId??s?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??s?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??s?.managerDefaultWorkerModelId??null,status:t.status??s?.status??"active",createdAt:s?.createdAt??i,updatedAt:i,lastActivityAt:t.lastActivityAt??s?.lastActivityAt??i,lastMessagePreview:f.preview,lastMessagePreviewMessageId:f.messageId,lastMessagePreviewAt:f.at,localReady:s?.localReady===!0||t.localReady===!0,firstUserMessageId:s?.firstUserMessageId??t.firstUserMessageId??null,firstUserBodyRevision:s?.firstUserBodyRevision??t.firstUserBodyRevision??null,bodyRevision:n,indexRevision:(s?.indexRevision??0)+1,...a,firstAvailableSeq:o?.firstAvailableSeq??s?.firstAvailableSeq??null,visibleMessageCount:o?.visibleMessageCount??s?.visibleMessageCount??0,lastVisibleMessageId:o?.lastVisibleMessageId??s?.lastVisibleMessageId??null,attentionCount:(s?.attentionCount??0)+(d?1:0),lastAttentionId:d?l:s?.lastAttentionId??null,lastBodyCommittedAt:o?.lastBodyCommittedAt??s?.lastBodyCommittedAt??null,deletionRevision:s?.deletionRevision??0,deletedAt:t.deletedAt??s?.deletedAt??null,syncedIndexRevision:s?.syncedIndexRevision??0,titleGenerationAttemptedAt:s?.titleGenerationAttemptedAt??null};return e[t.sessionId]=m,v(e),m}function T(){return Object.values(c())}function Y(t){if(!t)throw new Error("sessionId is required");const e=c();e[t]&&(delete e[t],v(e))}function z(t){g().purgeSession(t)}function H(){const t=u();return _(),T().filter(e=>e.indexRevision>e.syncedIndexRevision&&e.localReady&&!!e.firstUserMessageId&&e.firstUserBodyRevision!=null).sort((e,s)=>e.indexRevision-s.indexRevision).map(e=>{const s=!!(e.lastMessagePreview&&e.lastMessagePreviewMessageId&&e.lastMessagePreviewAt);return{protocolVersion:C,daemonInstallationId:t,session:{id:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId??null,title:e.title,titleSource:e.titleSource,titleRevision:e.titleRevision,titleLockedByUser:e.titleLockedByUser,titleOriginEventId:e.titleOriginEventId,status:e.status??"active",workDir:e.workDir,lastMessagePreview:s?e.lastMessagePreview:null,lastMessagePreviewMessageId:s?e.lastMessagePreviewMessageId:null,lastMessagePreviewAt:s?e.lastMessagePreviewAt:null,bodyStorageMode:"machine_local",localReady:!0,firstUserBoundary:{role:"user",messageId:e.firstUserMessageId,bodyRevision:e.firstUserBodyRevision},bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,attentionCount:e.attentionCount??0,lastAttentionId:e.lastAttentionId??null,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}})}function K(t,e){const s=c(),i=s[t];if(!(!i||e<=i.syncedIndexRevision)){if(e>i.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);s[t]={...i,syncedIndexRevision:e},v(s)}}const E=2*6e4;function Q(t=Date.now(),e=E){const s=c(),i=[],n=new Date(t).toISOString();for(const o of Object.values(s)){if(o.titleSource!=="first_user_fallback"||o.titleLockedByUser||o.titleGenerationAttemptedAt||!o.title.trim())continue;const a=o.status==="completed"||o.status==="failed",l=o.visibleMessageCount>=2;let d=null;if(o.agentType==="claude"&&l?d="agent_native_unavailable":a?d="terminal":l&&t-Date.parse(o.createdAt)>=e&&(d="grace_elapsed"),!d)continue;const f=R(o.title,240);if(!f)continue;const m=A.randomUUID();s[o.sessionId]={...o,titleGenerationAttemptedAt:n},i.push({sessionId:o.sessionId,requestId:m,titleSeed:f,baseTitleRevision:o.titleRevision,eligibility:d})}return i.length>0&&v(s),i}function X(t,e){const i=c()[t];return!i||i.titleLockedByUser||y[i.titleSource]>=y.shennian_generated||e.titleRevision<=i.titleRevision?!1:(U({sessionId:t,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,status:i.status,title:e.title,titleSource:"shennian_generated",titleRevision:e.titleRevision,titleLockedByUser:!1,titleOriginEventId:e.titleOriginEventId}),!0)}function Z(t,e,s){S(t),u();const i=g().append(t,e,s);return P(t,e,i.bodyRevision),i.bodyRevision}function ee(t,e,s){S(t),u();const i=g().upsert(t,e,s);return P(t,e,i.bodyRevision),i.bodyRevision}function te(t,e){const s=g().getMessageState(t,e);return!s||s.tombstoned||!s.message||!s.deliveryState?{state:"not_found",sessionId:t,clientMessageId:e}:{state:s.deliveryState,sessionId:t,clientMessageId:e,messageId:s.message.id,deliveryState:s.deliveryState,bodyRevision:s.lastRevision,committedAt:new Date(s.message.ts).toISOString()}}function se(t,e){S(t),u();const s=g().tombstone(t,e),i=c(),n=i[t];if(n){const o=e===n.lastMessagePreviewMessageId?O(t):{preview:n.lastMessagePreview??null,messageId:n.lastMessagePreviewMessageId??null,at:n.lastMessagePreviewAt??null};i[t]={...n,bodyRevision:s.bodyRevision,indexRevision:n.indexRevision+1,updatedAt:new Date().toISOString(),lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at},v(i)}return s.bodyRevision}function P(t,e,s){const i=c(),n=i[t];if(!n)return;const o=x(e),a=o?{lastMessagePreview:o,lastMessagePreviewMessageId:e.id,lastMessagePreviewAt:new Date(e.ts).toISOString()}:null,l=g().getManifest(t),d=e.role==="user"&&o&&!n.title?{title:o,titleSource:"first_user_fallback",titleRevision:n.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null,f=e.role==="user"&&!n.localReady?{localReady:!0,firstUserMessageId:e.id,firstUserBodyRevision:s}:null;i[t]={...n,...d??{},...f??{},bodyRevision:s,indexRevision:n.indexRevision+1,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),...a??{},firstAvailableSeq:l?.firstAvailableSeq??n.firstAvailableSeq,visibleMessageCount:l?.visibleMessageCount??n.visibleMessageCount,lastVisibleMessageId:l?.lastVisibleMessageId??n.lastVisibleMessageId,attentionCount:n.attentionCount??0,lastAttentionId:n.lastAttentionId??null,lastBodyCommittedAt:l?.lastBodyCommittedAt??n.lastBodyCommittedAt},v(i)}function O(t){let e;do{const s=g().readPage(t,{limit:500,beforeSequence:e});for(let i=s.entries.length-1;i>=0;i-=1){const n=s.entries[i],o=x(n.message);if(o)return{preview:o,messageId:n.message.id,at:new Date(n.message.ts).toISOString()}}if(!s.hasOlder||s.entries.length===0)break;e=s.entries[0].sequence}while(e!==void 0);return{preview:null,messageId:null,at:null}}function _(){const t=c();let e=!1;const s=new Date().toISOString();for(const[i,n]of Object.entries(t)){if(Object.prototype.hasOwnProperty.call(n,"lastMessagePreviewMessageId")&&Object.prototype.hasOwnProperty.call(n,"lastMessagePreviewAt"))continue;const o=O(i);t[i]={...n,lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at,indexRevision:n.indexRevision+1,updatedAt:s},e=!0}e&&v(t)}function L(t,e){const s={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},i=e.title?.trim()??"";if(!i)return s;if(!e.titleSource)throw new Error("titleSource is required with title");const n=e.titleSource==="shennian_manual";if(e.titleLockedByUser!==void 0&&e.titleLockedByUser!==n)throw new Error("titleLockedByUser conflicts with titleSource");const o=i===s.title&&e.titleSource===s.titleSource,a=e.titleRevision??s.titleRevision+(o?0:1);if(!Number.isSafeInteger(a)||a<0)throw new Error("invalid titleRevision");const l=y[e.titleSource],d=y[s.titleSource];return l<d||a<s.titleRevision||a===s.titleRevision&&!o?s:{title:i,titleSource:e.titleSource,titleRevision:a,titleLockedByUser:n,titleOriginEventId:e.titleOriginEventId??s.titleOriginEventId}}function ie(t,e){const s=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let n=g().readRecent(t,s).messages;return e?.before!==void 0&&(n=n.filter(o=>o.ts<e.before)),[...n].sort((o,a)=>a.ts-o.ts)}function ne(){return g().listSessionIds()}function oe(){return g()}function q(t,e){const s=I.dirname(t);r.mkdirSync(s,{recursive:!0,mode:448});const i=`${t}.tmp-${process.pid}-${A.randomBytes(6).toString("hex")}`;let n;try{if(n=r.openSync(i,"wx",384),r.writeFileSync(n,`${JSON.stringify(e,null,2)}
2
- `,"utf8"),r.fsyncSync(n),r.closeSync(n),n=void 0,r.renameSync(i,t),process.platform!=="win32"){const o=r.openSync(s,"r");try{r.fsyncSync(o)}finally{r.closeSync(o)}}}finally{n!==void 0&&r.closeSync(n);try{r.unlinkSync(i)}catch{}}}export{k as PERSONAL_SYNC_V1_ROOT_NAME,K as acknowledgeSessionIndexUpdate,Z as appendMessage,X as applyGeneratedSessionTitle,Q as claimSessionTitleGenerationCandidates,oe as getCanonicalSessionStore,u as getDaemonInstallationId,te as getMessageDeliveryStatus,H as listPendingSessionIndexUpdates,T as listSessionRecords,ne as listSessions,z as purgeCanonicalSession,Y as purgeSessionRecord,ie as readMessages,U as recordSession,se as tombstoneMessage,ee as upsertMessage};
1
+ import p from"node:crypto";import d from"node:fs";import I from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as x,SESSION_SYNC_DATA_EPOCH as A,SESSION_TITLE_SOURCE_PRIORITY as y,sessionPreviewFromPayload as b}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as k,resolvePersonalSyncV1Path as D}from"../personal-sync/paths.js";import{CanonicalSessionStore as _}from"./canonical-store.js";import{getDaemonInstallationId as c}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as B}from"../native-fusion/native-source-deletions.js";let w=null,C=null;function M(){return D()}function u(){const t=I.join(M(),"canonical");return(!w||C!==t)&&(w=new _(t,c()),C=t),w}function P(){return I.join(M(),"session-index.json")}function g(){const t=P();if(!d.existsSync(t))return{};const e=JSON.parse(d.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);const n=c();for(const i of Object.values(e))if(i.daemonInstallationId!==n)throw new Error(`personal session index belongs to another daemon installation: ${t}`);return e}function v(t){V(P(),t)}function O(t){return b(t.payload,120)}function m(t){if(new B({rootDir:M(),daemonInstallationId:c()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function T(t){m(t.sessionId),c();const e=g(),n=e[t.sessionId],i=new Date().toISOString(),s=u().getManifest(t.sessionId)?.bodyRevision??n?.bodyRevision??0,o=u().getManifest(t.sessionId),l=q(n,t),a=t.attentionId?.trim()||null,r=Array.from(new Set([...n?.seenAttentionIds??[],...n?.lastAttentionId?[n.lastAttentionId]:[]])),S=!!(a&&!r.includes(a)),f=t.lastMessagePreview===null?{preview:null,messageId:null,at:null}:t.lastMessagePreview!==void 0&&t.lastMessagePreviewMessageId&&t.lastMessagePreviewAt?{preview:t.lastMessagePreview,messageId:t.lastMessagePreviewMessageId,at:t.lastMessagePreviewAt}:{preview:n?.lastMessagePreview??null,messageId:n?.lastMessagePreviewMessageId??null,at:n?.lastMessagePreviewAt??null},R={daemonInstallationId:c(),sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??n?.sessionMode,managerConfig:t.managerConfig??n?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??n?.agentSessionId??null,modelId:t.modelId??n?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??n?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??n?.managerDefaultWorkerModelId??null,status:t.status??n?.status??"active",createdAt:n?.createdAt??i,updatedAt:i,lastActivityAt:t.lastActivityAt??n?.lastActivityAt??i,lastMessagePreview:f.preview,lastMessagePreviewMessageId:f.messageId,lastMessagePreviewAt:f.at,localReady:n?.localReady===!0||t.localReady===!0,firstUserMessageId:n?.firstUserMessageId??t.firstUserMessageId??null,firstUserBodyRevision:n?.firstUserBodyRevision??t.firstUserBodyRevision??null,bodyRevision:s,indexRevision:(n?.indexRevision??0)+1,...l,firstAvailableSeq:o?.firstAvailableSeq??n?.firstAvailableSeq??null,visibleMessageCount:o?.visibleMessageCount??n?.visibleMessageCount??0,lastVisibleMessageId:o?.lastVisibleMessageId??n?.lastVisibleMessageId??null,attentionCount:(n?.attentionCount??0)+(S?1:0),lastAttentionId:S?a:n?.lastAttentionId??null,seenAttentionIds:S&&a?[...r,a]:r,lastBodyCommittedAt:o?.lastBodyCommittedAt??n?.lastBodyCommittedAt??null,deletionRevision:n?.deletionRevision??0,deletedAt:t.deletedAt??n?.deletedAt??null,syncedIndexRevision:n?.syncedIndexRevision??0,indexSyncState:"pending",indexSyncStateRevision:(n?.indexRevision??0)+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,indexQuarantineCode:n?.indexQuarantineCode??null,titleGenerationAttemptedAt:n?.titleGenerationAttemptedAt??null};return e[t.sessionId]=R,v(e),R}function U(){return Object.values(g())}function H(t){if(!t)throw new Error("sessionId is required");const e=g();e[t]&&(delete e[t],v(e))}function z(t){u().purgeSession(t)}function K(){const t=c();return L(),U().filter(e=>{const n=e.indexSyncStateRevision===e.indexRevision;return e.indexQuarantineCode||n&&(e.indexSyncState==="quarantined"||e.indexSyncState==="terminal")||n&&e.indexSyncNextAttemptAt&&Date.parse(e.indexSyncNextAttemptAt)>Date.now()?!1:e.indexRevision>e.syncedIndexRevision&&e.localReady&&!!e.firstUserMessageId&&e.firstUserBodyRevision!=null}).sort((e,n)=>e.indexRevision-n.indexRevision).map(e=>{const n=!!(e.lastMessagePreview&&e.lastMessagePreviewMessageId&&e.lastMessagePreviewAt);return{protocolVersion:x,dataEpoch:A,daemonInstallationId:t,session:{id:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId??null,title:e.title,titleSource:e.titleSource,titleRevision:e.titleRevision,titleLockedByUser:e.titleLockedByUser,titleOriginEventId:e.titleOriginEventId,status:e.status??"active",workDir:e.workDir,lastMessagePreview:n?e.lastMessagePreview:null,lastMessagePreviewMessageId:n?e.lastMessagePreviewMessageId:null,lastMessagePreviewAt:n?e.lastMessagePreviewAt:null,bodyStorageMode:"machine_local",localReady:!0,firstUserBoundary:{role:"user",messageId:e.firstUserMessageId,bodyRevision:e.firstUserBodyRevision},bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,attentionCount:e.attentionCount??0,lastAttentionId:e.lastAttentionId??null,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}})}function X(t,e){const n=g(),i=n[t];if(!(!i||e<=i.syncedIndexRevision)){if(e>i.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);n[t]={...i,syncedIndexRevision:e,indexSyncState:"applied",indexSyncStateRevision:e,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null},v(n)}}function Z(t){const e=g(),n=e[t.sessionId];if(!n||n.indexRevision!==t.indexRevision)return"terminal";const s=(n.indexSyncStateRevision===t.indexRevision?n.indexSyncAttempts??0:0)+1;let o=t.disposition;o==="retry"&&s>=(t.maxAttempts??8)&&(o="terminal");const l=t.now??Date.now(),a=Math.min(5e3*2**Math.max(0,s-1),360*60*1e3),r=Math.round(a*.2*(Math.random()*2-1));return e[t.sessionId]={...n,indexSyncState:o==="retry"?"pending":o,indexSyncStateRevision:t.indexRevision,indexSyncAttempts:s,indexSyncNextAttemptAt:o==="retry"?new Date(l+a+r).toISOString():null,indexSyncErrorCode:t.code,indexQuarantineCode:o==="quarantined"?t.code:n.indexQuarantineCode??null},v(e),o}const N=2*6e4;function ee(t=Date.now(),e=N){const n=g(),i=[],s=new Date(t).toISOString();for(const o of Object.values(n)){if(o.titleSource!=="first_user_fallback"||o.titleLockedByUser||o.titleGenerationAttemptedAt||!o.title.trim())continue;const l=o.status==="completed"||o.status==="failed",a=o.visibleMessageCount>=2;let r=null;if(o.agentType==="claude"&&a?r="agent_native_unavailable":l?r="terminal":a&&t-Date.parse(o.createdAt)>=e&&(r="grace_elapsed"),!r)continue;const S=b(o.title,240);if(!S)continue;const f=p.randomUUID();n[o.sessionId]={...o,titleGenerationAttemptedAt:s},i.push({sessionId:o.sessionId,requestId:f,titleSeed:S,baseTitleRevision:o.titleRevision,eligibility:r})}return i.length>0&&v(n),i}function te(t,e){const i=g()[t];return!i||i.titleLockedByUser||y[i.titleSource]>=y.shennian_generated||e.titleRevision<=i.titleRevision?!1:(T({sessionId:t,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,status:i.status,title:e.title,titleSource:"shennian_generated",titleRevision:e.titleRevision,titleLockedByUser:!1,titleOriginEventId:e.titleOriginEventId}),!0)}function ne(t,e,n){m(t),c();const i=u().append(t,e,n);return E(t,e,i.bodyRevision),i.bodyRevision}function ie(t,e,n){m(t),c();const i=u().upsert(t,e,n);return E(t,e,i.bodyRevision),i.bodyRevision}function se(t,e){const n=u().getMessageState(t,e);return!n||n.tombstoned||!n.message||!n.deliveryState?{protocolVersion:x,dataEpoch:A,daemonInstallationId:c(),state:"not_found",sessionId:t,clientMessageId:e}:{protocolVersion:x,dataEpoch:A,daemonInstallationId:c(),state:n.deliveryState,sessionId:t,clientMessageId:e,messageId:n.message.id,deliveryState:n.deliveryState,bodyRevision:n.lastRevision,committedAt:new Date(n.message.ts).toISOString()}}function oe(t,e){m(t),c();const n=u().tombstone(t,e),i=g(),s=i[t];if(s){const o=e===s.lastMessagePreviewMessageId?h(t):{preview:s.lastMessagePreview??null,messageId:s.lastMessagePreviewMessageId??null,at:s.lastMessagePreviewAt??null};i[t]={...s,bodyRevision:n.bodyRevision,indexRevision:s.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:s.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at},v(i)}return n.bodyRevision}function E(t,e,n){const i=g(),s=i[t];if(!s)return;const o=O(e),l=o?{lastMessagePreview:o,lastMessagePreviewMessageId:e.id,lastMessagePreviewAt:new Date(e.ts).toISOString()}:null,a=u().getManifest(t),r=e.role==="user"&&o&&!s.title?{title:o,titleSource:"first_user_fallback",titleRevision:s.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null,S=e.role==="user"&&!s.localReady?{localReady:!0,firstUserMessageId:e.id,firstUserBodyRevision:n}:null;i[t]={...s,...r??{},...S??{},bodyRevision:n,indexRevision:s.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:s.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),...l??{},firstAvailableSeq:a?.firstAvailableSeq??s.firstAvailableSeq,visibleMessageCount:a?.visibleMessageCount??s.visibleMessageCount,lastVisibleMessageId:a?.lastVisibleMessageId??s.lastVisibleMessageId,attentionCount:s.attentionCount??0,lastAttentionId:s.lastAttentionId??null,lastBodyCommittedAt:a?.lastBodyCommittedAt??s.lastBodyCommittedAt},v(i)}function h(t){let e;do{const n=u().readPage(t,{limit:500,beforeSequence:e});for(let i=n.entries.length-1;i>=0;i-=1){const s=n.entries[i],o=O(s.message);if(o)return{preview:o,messageId:s.message.id,at:new Date(s.message.ts).toISOString()}}if(!n.hasOlder||n.entries.length===0)break;e=n.entries[0].sequence}while(e!==void 0);return{preview:null,messageId:null,at:null}}function L(){const t=g();let e=!1;const n=new Date().toISOString();for(const[i,s]of Object.entries(t)){if(Object.prototype.hasOwnProperty.call(s,"lastMessagePreviewMessageId")&&Object.prototype.hasOwnProperty.call(s,"lastMessagePreviewAt"))continue;const o=h(i);t[i]={...s,lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at,indexRevision:s.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:s.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e=!0}e&&v(t)}function q(t,e){const n={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},i=e.title?.trim()??"";if(!i)return n;if(!e.titleSource)throw new Error("titleSource is required with title");const s=e.titleSource==="shennian_manual";if(e.titleLockedByUser!==void 0&&e.titleLockedByUser!==s)throw new Error("titleLockedByUser conflicts with titleSource");const o=i===n.title&&e.titleSource===n.titleSource,l=e.titleRevision??n.titleRevision+(o?0:1);if(!Number.isSafeInteger(l)||l<0)throw new Error("invalid titleRevision");const a=y[e.titleSource],r=y[n.titleSource];return a<r||l<n.titleRevision||o&&l===n.titleRevision||l===n.titleRevision&&!o?n:{title:i,titleSource:e.titleSource,titleRevision:l,titleLockedByUser:s,titleOriginEventId:e.titleOriginEventId??n.titleOriginEventId}}function ae(t,e){const n=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let s=u().readRecent(t,n).messages;return e?.before!==void 0&&(s=s.filter(o=>o.ts<e.before)),[...s].sort((o,l)=>l.ts-o.ts)}function le(){return u().listSessionIds()}function re(){return u()}function V(t,e){const n=I.dirname(t);d.mkdirSync(n,{recursive:!0,mode:448});const i=`${t}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let s;try{if(s=d.openSync(i,"wx",384),d.writeFileSync(s,`${JSON.stringify(e,null,2)}
2
+ `,"utf8"),d.fsyncSync(s),d.closeSync(s),s=void 0,d.renameSync(i,t),process.platform!=="win32"){const o=d.openSync(n,"r");try{d.fsyncSync(o)}finally{d.closeSync(o)}}}finally{s!==void 0&&d.closeSync(s);try{d.unlinkSync(i)}catch{}}}export{k as PERSONAL_SYNC_V1_ROOT_NAME,X as acknowledgeSessionIndexUpdate,ne as appendMessage,te as applyGeneratedSessionTitle,ee as claimSessionTitleGenerationCandidates,re as getCanonicalSessionStore,c as getDaemonInstallationId,se as getMessageDeliveryStatus,K as listPendingSessionIndexUpdates,U as listSessionRecords,le as listSessions,z as purgeCanonicalSession,H as purgeSessionRecord,ae as readMessages,T as recordSession,Z as recordSessionIndexRejection,oe as tombstoneMessage,ie as upsertMessage};
@@ -1,4 +1,5 @@
1
1
  import type { ReqMethod } from './methods.ts';
2
+ import type { SessionSyncRejection } from './session-sync.ts';
2
3
  export type ReqFrame = {
3
4
  type: 'req';
4
5
  id: string;
@@ -12,6 +13,8 @@ export type ResFrame = {
12
13
  ok: boolean;
13
14
  payload?: unknown;
14
15
  error?: string;
16
+ /** Structured, machine-actionable rejection. Clients must not parse `error`. */
17
+ rejection?: SessionSyncRejection;
15
18
  traceId?: string;
16
19
  };
17
20
  export type EventFrame = {
@@ -1,3 +1,4 @@
1
+ import type { PersonalSyncIdentity } from './session-sync.ts';
1
2
  export type FsEntry = {
2
3
  name: string;
3
4
  path: string;
@@ -29,7 +30,7 @@ export type FsTransferManifestEntry = {
29
30
  mimeType?: string;
30
31
  modifiedAt?: number;
31
32
  };
32
- export type FsTransferStartParams = {
33
+ export type FsTransferStartParams = PersonalSyncIdentity & {
33
34
  transferId: string;
34
35
  rootPath: string;
35
36
  name: string;
@@ -39,25 +40,25 @@ export type FsTransferStartParams = {
39
40
  kind: 'file' | 'folder';
40
41
  manifest?: FsTransferManifestEntry[];
41
42
  };
42
- export type FsTransferStartResult = {
43
+ export type FsTransferStartResult = PersonalSyncIdentity & {
43
44
  transferId: string;
44
45
  path: string;
45
46
  reused: boolean;
46
47
  offsets: Record<string, number>;
47
48
  attachment?: FsTransferFinishResult;
48
49
  };
49
- export type FsTransferChunkParams = {
50
+ export type FsTransferChunkParams = PersonalSyncIdentity & {
50
51
  transferId: string;
51
52
  rootPath: string;
52
53
  offset: number;
53
54
  data: string;
54
55
  relativePath?: string;
55
56
  };
56
- export type FsTransferFinishParams = {
57
+ export type FsTransferFinishParams = PersonalSyncIdentity & {
57
58
  transferId: string;
58
59
  rootPath: string;
59
60
  };
60
- export type FsTransferFinishResult = {
61
+ export type FsTransferFinishResult = PersonalSyncIdentity & {
61
62
  transferId: string;
62
63
  path: string;
63
64
  name: string;
@@ -67,10 +68,19 @@ export type FsTransferFinishResult = {
67
68
  sha256: string;
68
69
  reused: boolean;
69
70
  };
70
- export type FsTransferAbortParams = {
71
+ export type FsTransferAbortParams = PersonalSyncIdentity & {
71
72
  transferId: string;
72
73
  rootPath: string;
73
74
  };
75
+ export type FsTransferChunkResult = PersonalSyncIdentity & {
76
+ transferId: string;
77
+ written: number;
78
+ nextOffset: number;
79
+ };
80
+ export type FsTransferAbortResult = PersonalSyncIdentity & {
81
+ transferId: string;
82
+ aborted: true;
83
+ };
74
84
  /** Stable input for the aggregate hash of a folder transfer. */
75
85
  export declare function serializeFsTransferManifest(entries: readonly FsTransferManifestEntry[]): string;
76
86
  export type FsRenameResult = {
@@ -18,6 +18,8 @@ export type Machine = {
18
18
  daemonInstallationId?: string;
19
19
  /** Personal Session sync protocol understood by this daemon. */
20
20
  sessionSyncProtocolVersion?: import('./session-sync.ts').SessionSyncProtocolVersion;
21
+ /** Active personal Session data authority generation. */
22
+ sessionSyncDataEpoch?: import('./session-sync.ts').SessionSyncDataEpoch;
21
23
  /** Full capability set required by the hard-cutover personal Session protocol. */
22
24
  sessionSyncCapabilities?: import('./session-sync.ts').SessionSyncCapability[];
23
25
  /** Auto-upgrade policy configured on this machine */
@@ -5,6 +5,7 @@ const TOOL_PAYLOAD_PREFIX_RE = /^\s*\{\s*"v"\s*:\s*1\s*,\s*"type"\s*:\s*"(tool|t
5
5
  const ENVIRONMENT_CONTEXT_RE = /^\s*<environment_context>\s*[^]*?<\/environment_context>\s*$/i;
6
6
  const ENVIRONMENT_CONTEXT_REQUIRED_TAG_RE = /<(cwd|shell|current_date|timezone)>[^<]*<\/\1>/gi;
7
7
  const CODEX_TURN_ABORTED_RE = /^\s*<turn_aborted>[^]*?<\/turn_aborted>\s*$/i;
8
+ const CODEX_INTERNAL_CONTEXT_RE = /^\s*<codex_internal_context\b[^>]*>[^]*?<\/codex_internal_context>\s*$/i;
8
9
  const CODEX_PROJECT_INSTRUCTIONS_RE = /^\s*# AGENTS\.md instructions for [^\n]+\n+\s*<INSTRUCTIONS>\s*[^]*?<\/INSTRUCTIONS>\s*(?:<environment_context>[^]*?<\/environment_context>)?\s*$/i;
9
10
  const APPROVAL_PENDING_PAYLOAD_PREFIX_RE = /^\s*\{\s*"v"\s*:\s*1\s*,\s*"type"\s*:\s*"approval_pending"/;
10
11
  const PREVIEW_PRIVATE_CONTEXT_RE = /(?:<\/?(?:environment_context|instructions|system|developer|codex_internal_context|recommended_plugins)\b|#\s*AGENTS\.md instructions\b)/i;
@@ -203,6 +204,7 @@ export function isCodexProjectInstructionsPayload(payload) {
203
204
  export function isSystemControlPayload(payload) {
204
205
  return (isSystemEnvironmentContextPayload(payload) ||
205
206
  isCodexTurnAbortedPayload(payload) ||
207
+ CODEX_INTERNAL_CONTEXT_RE.test(payload) ||
206
208
  isCodexProjectInstructionsPayload(payload));
207
209
  }
208
210
  export function stripGitDirectiveArtifacts(text) {