shennian 0.3.41 → 0.3.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/publish-build-manifest.json +93 -43
  2. package/dist/src/agents/adapter.d.ts +1 -0
  3. package/dist/src/commands/daemon.d.ts +6 -0
  4. package/dist/src/commands/daemon.js +9 -9
  5. package/dist/src/index.js +3 -3
  6. package/dist/src/native-fusion/codex-parser.d.ts +1 -0
  7. package/dist/src/native-fusion/codex-parser.js +4 -4
  8. package/dist/src/native-fusion/file-watcher.d.ts +18 -0
  9. package/dist/src/native-fusion/file-watcher.js +1 -0
  10. package/dist/src/native-fusion/local-repository.d.ts +52 -0
  11. package/dist/src/native-fusion/local-repository.js +2 -0
  12. package/dist/src/native-fusion/opencode-parser.js +2 -2
  13. package/dist/src/native-fusion/parser-common.d.ts +7 -1
  14. package/dist/src/native-fusion/parser-common.js +4 -4
  15. package/dist/src/native-fusion/parsers.d.ts +2 -1
  16. package/dist/src/native-fusion/parsers.js +2 -2
  17. package/dist/src/native-fusion/service.d.ts +10 -2
  18. package/dist/src/native-fusion/service.js +2 -2
  19. package/dist/src/native-fusion/session-header.d.ts +9 -0
  20. package/dist/src/native-fusion/session-header.js +2 -0
  21. package/dist/src/native-fusion/state.d.ts +1 -1
  22. package/dist/src/native-fusion/state.js +2 -1
  23. package/dist/src/native-fusion/types.d.ts +29 -5
  24. package/dist/src/native-fusion/types.js +1 -1
  25. package/dist/src/personal-sync/paths.d.ts +2 -0
  26. package/dist/src/personal-sync/paths.js +1 -0
  27. package/dist/src/relay/client.d.ts +4 -1
  28. package/dist/src/relay/client.js +1 -1
  29. package/dist/src/session/attachment-reference.d.ts +2 -0
  30. package/dist/src/session/attachment-reference.js +1 -0
  31. package/dist/src/session/attachment-transfer-store.d.ts +38 -0
  32. package/dist/src/session/attachment-transfer-store.js +2 -0
  33. package/dist/src/session/canonical-store.d.ts +101 -0
  34. package/dist/src/session/canonical-store.js +5 -0
  35. package/dist/src/session/handlers/chat.js +2 -2
  36. package/dist/src/session/handlers/fs.d.ts +0 -1
  37. package/dist/src/session/handlers/fs.js +1 -1
  38. package/dist/src/session/handlers/message-status.d.ts +3 -0
  39. package/dist/src/session/handlers/message-status.js +1 -0
  40. package/dist/src/session/handlers/title.js +1 -1
  41. package/dist/src/session/handlers/tool-detail.js +1 -1
  42. package/dist/src/session/handlers/voice-window.d.ts +3 -0
  43. package/dist/src/session/handlers/voice-window.js +1 -0
  44. package/dist/src/session/history-subscriptions.d.ts +17 -0
  45. package/dist/src/session/history-subscriptions.js +1 -0
  46. package/dist/src/session/index-publisher.d.ts +3 -0
  47. package/dist/src/session/index-publisher.js +1 -0
  48. package/dist/src/session/manager.d.ts +2 -0
  49. package/dist/src/session/manager.js +1 -1
  50. package/dist/src/session/queue.d.ts +1 -1
  51. package/dist/src/session/queue.js +4 -3
  52. package/dist/src/session/store.d.ts +33 -3
  53. package/dist/src/session/store.js +2 -3
  54. package/dist/src/session/tool-detail-store.d.ts +16 -0
  55. package/dist/src/session/tool-detail-store.js +2 -0
  56. package/dist/src/session/types.d.ts +3 -13
  57. package/node_modules/@shennian/wire/dist/events.d.ts +2 -0
  58. package/node_modules/@shennian/wire/dist/fs.d.ts +45 -0
  59. package/node_modules/@shennian/wire/dist/fs.js +7 -1
  60. package/node_modules/@shennian/wire/dist/index.d.ts +1 -0
  61. package/node_modules/@shennian/wire/dist/index.js +1 -0
  62. package/node_modules/@shennian/wire/dist/machine.d.ts +6 -0
  63. package/node_modules/@shennian/wire/dist/message-payload.d.ts +2 -0
  64. package/node_modules/@shennian/wire/dist/message-payload.js +15 -1
  65. package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
  66. package/node_modules/@shennian/wire/dist/session-sync.d.ts +197 -0
  67. package/node_modules/@shennian/wire/dist/session-sync.js +49 -0
  68. package/node_modules/@shennian/wire/dist/session.d.ts +24 -13
  69. package/node_modules/@shennian/wire/dist/tool-detail.d.ts +18 -11
  70. package/node_modules/@shennian/wire/dist/tool-detail.js +7 -1
  71. package/package.json +1 -1
  72. package/dist/src/session/remote-attachments.d.ts +0 -15
  73. package/dist/src/session/remote-attachments.js +0 -1
  74. package/dist/src/session/tool-summary.d.ts +0 -1
  75. package/dist/src/session/tool-summary.js +0 -1
@@ -0,0 +1,5 @@
1
+ import D from"node:crypto";import o from"node:fs";import c from"node:path";const b=1,V=64,v="mutations-000001.jsonl";class I extends Error{constructor(e,s){super(e,s),this.name="CanonicalStoreError"}}class q extends I{constructor(e,s){super(e,s),this.name="CanonicalStoreCorruptionError"}}class j extends I{constructor(e){super(e),this.name="CanonicalStoreConflictError"}}class K{rootDir;daemonInstallationId;mutationListeners=new Set;constructor(e,s){if(!s)throw new I("daemonInstallationId is required");this.rootDir=c.resolve(e),this.daemonInstallationId=s}commit(e){k(e);const s=this.sessionDir(e.sessionId);this.assertHealthy(s),o.mkdirSync(this.messageStateDir(s),{recursive:!0,mode:448});let t=this.loadManifestOrRebuild(e.sessionId);const n=this.readMessageState(s,e.messageId),a=z(e);if(n&&n.contentHash===a&&n.tombstoned===(e.mutation==="tombstone"))return{bodyRevision:t.bodyRevision,sequence:n.sequence,mutation:this.findMutationByRevision(e.sessionId,n.lastRevision),duplicate:!0};if(e.mutation==="append"&&n)throw new j(`message ${e.messageId} already exists in session ${e.sessionId}; use upsert`);const m=n?.sequence??t.nextSequence,g=t.bodyRevision+1,S=e.committedAt??new Date().toISOString(),y={schemaVersion:b,mutationId:T(e.sessionId,e.messageId,e.mutation,n?.contentHash??"none",a),sessionId:e.sessionId,revision:g,mutation:e.mutation,messageId:e.messageId,sequence:m,...e.message?{message:e.message}:{},...e.deliveryState?{deliveryState:e.deliveryState}:{},committedAt:S,contentHash:a},d=c.join(s,v),u=_(d,`${JSON.stringify(y)}
2
+ `),r={schemaVersion:b,sessionId:e.sessionId,messageId:e.messageId,sequence:m,lastRevision:g,contentHash:a,tombstoned:e.mutation==="tombstone",...e.message?{message:e.message}:{},...e.deliveryState?{deliveryState:e.deliveryState}:{}};w(this.messageStateFile(s,m,e.messageId),r);const f=this.readSparseIndex(s,e.sessionId);(g===1||(g-1)%V===0)&&(f.entries.push({revision:g,offset:u}),w(c.join(s,"sparse-index.json"),f)),t={...t,bodyRevision:g,nextSequence:n?t.nextSequence:m+1,firstAvailableSeq:t.firstAvailableSeq??m,lastAvailableSeq:Math.max(t.lastAvailableSeq??m,m),mutationCount:t.mutationCount+1,visibleMessageCount:t.visibleMessageCount+(!n&&e.mutation!=="tombstone"?1:0)-(n&&!n.tombstoned&&e.mutation==="tombstone"?1:0),lastVisibleMessageId:e.mutation==="tombstone"?t.lastVisibleMessageId===e.messageId?this.findLastVisibleMessageId(s,e.messageId):t.lastVisibleMessageId:e.messageId,lastBodyCommittedAt:S,logByteLength:o.statSync(d).size,updatedAt:S},w(c.join(s,"manifest.json"),t);for(const x of this.mutationListeners)try{x(y)}catch(p){console.error("[canonical-store] mutation listener failed",p)}return{bodyRevision:g,sequence:m,mutation:y,duplicate:!1}}subscribe(e){return this.mutationListeners.add(e),()=>this.mutationListeners.delete(e)}append(e,s,t){return this.commit({sessionId:e,mutation:"append",messageId:s.id,message:s,deliveryState:t})}upsert(e,s,t){return this.commit({sessionId:e,mutation:"upsert",messageId:s.id,message:s,deliveryState:t})}tombstone(e,s,t){return this.commit({sessionId:e,mutation:"tombstone",messageId:s,deliveryState:t})}readRecent(e,s){return this.readPage(e,{limit:s})}readPage(e,s){const t=O(s.limit),n=this.sessionDir(e);if(!o.existsSync(n))return H();const a=this.loadManifestOrRebuild(e),m=this.listMessageStateFiles(n).filter(d=>s.beforeSequence===void 0||d.sequence<s.beforeSequence).sort((d,u)=>u.sequence-d.sequence),g=[];let S=0;for(const d of m){S+=1;const u=M(c.join(this.messageStateDir(n),d.name));if(!u.tombstoned&&u.message&&g.push(u),g.length>=t)break}g.sort((d,u)=>d.sequence-u.sequence);const y=g.length>0&&m.slice(S).some(d=>{const u=M(c.join(this.messageStateDir(n),d.name));return!u.tombstoned&&!!u.message});return{bodyRevision:a.bodyRevision,firstAvailableSeq:a.firstAvailableSeq,lastAvailableSeq:a.lastAvailableSeq,hasOlder:y,messages:g.map(d=>d.message),entries:g.map(d=>({sequence:d.sequence,revision:d.lastRevision,message:d.message}))}}readPageAtRevision(e,s){const t=O(s.limit),n=this.sessionDir(e);if(!o.existsSync(n))return H();const a=this.loadManifestOrRebuild(e);if(!Number.isSafeInteger(s.revision)||s.revision<0||s.revision>a.bodyRevision)throw new I("snapshot revision is unavailable");const m=new Map,g=L(c.join(n,v),0);for(const r of g.split(`
3
+ `)){if(!r)continue;const f=A(r,e);if(f.revision>s.revision)break;m.set(f.messageId,{schemaVersion:b,sessionId:e,messageId:f.messageId,sequence:f.sequence,lastRevision:f.revision,contentHash:f.contentHash,tombstoned:f.mutation==="tombstone",...f.message?{message:f.message}:{},...f.deliveryState?{deliveryState:f.deliveryState}:{}})}const S=[...m.values()].filter(r=>!r.tombstoned&&r.message&&(s.beforeSequence===void 0||r.sequence<s.beforeSequence)).sort((r,f)=>f.sequence-r.sequence),y=S.slice(0,t).sort((r,f)=>r.sequence-f.sequence),u=[...m.values()].filter(r=>!r.tombstoned&&r.message).map(r=>r.sequence);return{bodyRevision:s.revision,firstAvailableSeq:u.length?Math.min(...u):null,lastAvailableSeq:u.length?Math.max(...u):null,hasOlder:S.length>y.length,messages:y.map(r=>r.message),entries:y.map(r=>({sequence:r.sequence,revision:r.lastRevision,message:r.message}))}}readDeltas(e,s,t){if(!Number.isSafeInteger(s)||s<0)throw new I("afterRevision must be a non-negative safe integer");const n=O(t),a=this.sessionDir(e);if(!o.existsSync(a))return[];const m=this.loadManifestOrRebuild(e);if(s>=m.bodyRevision)return[];const g=this.readSparseIndex(a,e),S=s+1,y=[...g.entries].reverse().find(r=>r.revision<=S),d=L(c.join(a,v),y?.offset??0),u=[];for(const r of d.split(`
4
+ `)){if(!r)continue;const f=A(r,e);if(f.revision>s&&u.push(f),u.length>=n)break}return u}getManifest(e){const s=this.sessionDir(e);return o.existsSync(s)?this.loadManifestOrRebuild(e):null}getMessageState(e,s){const t=this.sessionDir(e);if(!o.existsSync(t))return null;this.loadManifestOrRebuild(e);const n=this.readMessageState(t,s);return n?{sequence:n.sequence,lastRevision:n.lastRevision,tombstoned:n.tombstoned,message:n.message,deliveryState:n.deliveryState}:null}listSessionIds(){const e=c.join(this.rootDir,"sessions");if(!o.existsSync(e))return[];const s=[];for(const t of o.readdirSync(e,{withFileTypes:!0})){if(!t.isDirectory())continue;const n=c.join(e,t.name,"manifest.json");o.existsSync(n)&&s.push(M(n).sessionId)}return s.sort()}findLastVisibleMessageId(e,s){for(const t of this.listMessageStateFiles(e).sort((n,a)=>a.sequence-n.sequence)){const n=M(c.join(this.messageStateDir(e),t.name));if(n.messageId!==s&&!n.tombstoned&&n.message)return n.messageId}return null}rebuild(e){const s=this.sessionDir(e);this.assertHealthy(s),o.mkdirSync(s,{recursive:!0,mode:448});const t=c.join(s,"manifest.json");if(o.existsSync(t))try{const l=JSON.parse(o.readFileSync(t,"utf8"));if(l.daemonInstallationId&&l.daemonInstallationId!==this.daemonInstallationId)throw new j(`canonical session ${e} belongs to a different daemon installation`)}catch(l){if(l instanceof j)throw l}const n=c.join(s,v);if(!o.existsSync(n)){const l=P(e,this.daemonInstallationId);return w(c.join(s,"manifest.json"),l),w(c.join(s,"sparse-index.json"),C(e)),l}const a=o.readFileSync(n);let m=a.length;if(a.length>0&&a[a.length-1]!==10){const l=a.lastIndexOf(10);m=l<0?0:l+1,o.truncateSync(n,m)}const g=a.subarray(0,m).toString("utf8"),S=new Map,y=C(e);let d=1,u=0,r=new Date(0).toISOString();try{for(const l of g.match(/.*\n/g)??[]){const R=l.slice(0,-1);if(!R){u+=Buffer.byteLength(l);continue}const h=A(R,e);if(h.revision!==d)throw new q(`non-contiguous revision in ${e}: expected ${d}, got ${h.revision}`);(h.revision===1||(h.revision-1)%V===0)&&y.entries.push({revision:h.revision,offset:u});const N=S.get(h.messageId);if(N&&N.sequence!==h.sequence)throw new q(`sequence changed for message ${h.messageId}`);S.set(h.messageId,{schemaVersion:b,sessionId:e,messageId:h.messageId,sequence:h.sequence,lastRevision:h.revision,contentHash:h.contentHash,tombstoned:h.mutation==="tombstone",...h.message?{message:h.message}:{},...h.deliveryState?{deliveryState:h.deliveryState}:{}}),d+=1,u+=Buffer.byteLength(l),r=h.committedAt}}catch(l){this.quarantineCorruptSegment(e,n,l)}const f=this.messageStateDir(s);o.rmSync(f,{recursive:!0,force:!0}),o.mkdirSync(f,{recursive:!0,mode:448});for(const l of S.values())w(this.messageStateFile(s,l.sequence,l.messageId),l);w(c.join(s,"sparse-index.json"),y);const x=[...S.values()].map(l=>l.sequence),p=[...S.values()].filter(l=>!l.tombstoned&&!!l.message).sort((l,R)=>l.sequence-R.sequence),$=d-1,F={schemaVersion:b,daemonInstallationId:this.daemonInstallationId,sessionId:e,bodyRevision:$,nextSequence:x.length===0?1:Math.max(...x)+1,firstAvailableSeq:x.length===0?null:Math.min(...x),lastAvailableSeq:x.length===0?null:Math.max(...x),mutationCount:$,visibleMessageCount:p.length,lastVisibleMessageId:p.at(-1)?.messageId??null,lastBodyCommittedAt:$>0?r:null,segment:v,logByteLength:m,updatedAt:r};return w(c.join(s,"manifest.json"),F),F}loadManifestOrRebuild(e){const s=this.sessionDir(e);this.assertHealthy(s);const t=c.join(s,"manifest.json"),n=c.join(s,v);if(!o.existsSync(t))return this.rebuild(e);try{const a=M(t);J(a,e,this.daemonInstallationId);const m=o.existsSync(n)?o.statSync(n).size:0;return a.logByteLength===m?a:this.rebuild(e)}catch(a){if(a instanceof j)throw a;return this.rebuild(e)}}findMutationByRevision(e,s){const t=this.readDeltas(e,s-1,1)[0];if(!t||t.revision!==s)throw new q(`missing mutation revision ${s} in ${e}`);return t}readSparseIndex(e,s){const t=c.join(e,"sparse-index.json");if(!o.existsSync(t))return C(s);const n=M(t);if(n.schemaVersion!==b||n.sessionId!==s||n.segment!==v)throw new q(`invalid sparse index for session ${s}`);return n}readMessageState(e,s){const t=`.${E(s)}.json`,n=this.listMessageStateFiles(e).find(m=>m.name.endsWith(t));if(!n)return null;const a=M(c.join(this.messageStateDir(e),n.name));if(a.messageId!==s)throw new q(`message state hash collision for ${s}`);return a}listMessageStateFiles(e){const s=this.messageStateDir(e);return o.existsSync(s)?o.readdirSync(s).map(t=>({name:t,match:/^(\d{16})\.[a-f0-9]{32}\.json$/.exec(t)})).filter(t=>!!t.match).map(t=>({name:t.name,sequence:Number(t.match[1])})):[]}quarantineCorruptSegment(e,s,t){const n=c.join(this.rootDir,"quarantine");o.mkdirSync(n,{recursive:!0,mode:448});const a=c.join(n,`${B(e)}-${Date.now()}-${v}`);throw o.renameSync(s,a),w(c.join(this.sessionDir(e),"CORRUPT.json"),{schemaVersion:b,sessionId:e,quarantined:a,detectedAt:new Date().toISOString(),reason:t instanceof Error?t.message:String(t)}),new q(`canonical segment for ${e} was quarantined at ${a}`,{cause:t})}assertHealthy(e){const s=c.join(e,"CORRUPT.json");if(o.existsSync(s))throw new q(`canonical session is quarantined: ${s}`)}sessionDir(e){return c.join(this.rootDir,"sessions",B(e))}messageStateDir(e){return c.join(e,"messages")}messageStateFile(e,s,t){return c.join(this.messageStateDir(e),`${String(s).padStart(16,"0")}.${E(t)}.json`)}}function k(i){if(!i.sessionId||!i.messageId)throw new I("sessionId and messageId are required");if(i.message&&(i.message.id!==i.messageId||i.message.sessionId!==i.sessionId))throw new I("message identity does not match commit identity");if(i.mutation!=="tombstone"&&!i.message)throw new I(`${i.mutation} mutation requires a message`)}function J(i,e,s){if(i.daemonInstallationId!==s)throw new j(`canonical session ${e} belongs to a different daemon installation`);if(i.schemaVersion!==b||i.sessionId!==e||i.segment!==v||!Number.isSafeInteger(i.bodyRevision)||i.bodyRevision<0)throw new Error(`invalid canonical manifest for ${e}`)}function A(i,e){let s;try{s=JSON.parse(i)}catch(t){throw new q(`malformed canonical mutation in ${e}`,{cause:t})}if(s.schemaVersion!==b||s.sessionId!==e||!Number.isSafeInteger(s.revision)||s.revision<1||!Number.isSafeInteger(s.sequence)||s.sequence<1||!["append","upsert","tombstone"].includes(s.mutation))throw new q(`invalid canonical mutation in ${e}`);return s}function P(i,e){return{schemaVersion:b,daemonInstallationId:e,sessionId:i,bodyRevision:0,nextSequence:1,firstAvailableSeq:null,lastAvailableSeq:null,mutationCount:0,visibleMessageCount:0,lastVisibleMessageId:null,lastBodyCommittedAt:null,segment:v,logByteLength:0,updatedAt:new Date(0).toISOString()}}function C(i){return{schemaVersion:b,sessionId:i,segment:v,entries:[]}}function H(){return{bodyRevision:0,firstAvailableSeq:null,lastAvailableSeq:null,hasOlder:!1,messages:[],entries:[]}}function O(i){if(!Number.isSafeInteger(i)||i<1||i>500)throw new I("limit must be an integer between 1 and 500");return i}function B(i){return D.createHash("sha256").update(i).digest("hex")}function E(i){return D.createHash("sha256").update(i).digest("hex").slice(0,32)}function z(i){return D.createHash("sha256").update(JSON.stringify({mutation:i.mutation,message:i.message??null,deliveryState:i.deliveryState??null})).digest("hex")}function T(i,e,s,t,n){return D.createHash("sha256").update(`${i}\0${e}\0${s}\0${t}\0${n}`).digest("hex")}function _(i,e){o.mkdirSync(c.dirname(i),{recursive:!0,mode:448});const s=o.openSync(i,"a",384);try{const t=o.fstatSync(s).size;return o.writeFileSync(s,e,"utf8"),o.fsyncSync(s),t}finally{o.closeSync(s)}}function w(i,e){const s=c.dirname(i);o.mkdirSync(s,{recursive:!0,mode:448});const t=`${i}.tmp-${process.pid}-${D.randomBytes(6).toString("hex")}`;let n;try{n=o.openSync(t,"wx",384),o.writeFileSync(n,`${JSON.stringify(e,null,2)}
5
+ `,"utf8"),o.fsyncSync(n),o.closeSync(n),n=void 0,o.renameSync(t,i),U(s)}finally{n!==void 0&&o.closeSync(n);try{o.unlinkSync(t)}catch{}}}function U(i){if(process.platform==="win32")return;const e=o.openSync(i,"r");try{o.fsyncSync(e)}finally{o.closeSync(e)}}function M(i){try{return JSON.parse(o.readFileSync(i,"utf8"))}catch(e){throw new q(`failed to read canonical JSON file ${i}`,{cause:e})}}function L(i,e){const s=o.openSync(i,"r");try{const t=o.fstatSync(s).size;if(e>=t)return"";const n=Buffer.allocUnsafe(t-e);return o.readSync(s,n,0,n.length,e),n.toString("utf8")}finally{o.closeSync(s)}}export{K as CanonicalSessionStore,j as CanonicalStoreConflictError,q as CanonicalStoreCorruptionError,I as CanonicalStoreError};
@@ -1,2 +1,2 @@
1
- import O from"node:os";import{createAgent as X}from"../../agents/adapter.js";import{buildApprovalPendingPayload as Y,buildUserMessagePayload as Z}from"@shennian/wire";import{ManagerModeAdapter as ee}from"../../agents/manager.js";import{reportLog as E}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as te}from"../../native-fusion/parsers.js";import{appendMessage as x,recordSession as F}from"../store.js";import{mergeProjectedSessions as ne}from"../projection.js";import{buildManagedAgentEnv as ae}from"../../agents/config-status.js";import{materializeRemoteChatAttachments as re}from"../remote-attachments.js";import{buildToolArgsSummary as se}from"../tool-summary.js";function oe(e){if(!Array.isArray(e))return;const t=e.map(a=>{if(!a||typeof a!="object")return null;const r=a,o=typeof r.path=="string"?r.path:"",u=typeof r.name=="string"?r.name:"",g=typeof r.mimeType=="string"?r.mimeType:"";if(!o||!u||!g)return null;const f=typeof r.previewData=="string"&&r.previewData.trim()?r.previewData.trim():void 0;return{path:o,name:u,mimeType:g,kind:g.startsWith("image/")?"image":"file",...f?{previewData:f}:{}}}).filter(a=>a!=null);return t.length?t:void 0}function de(e){const t=e.indexOf(`
2
- `),a=t>0?Math.min(t,80):Math.min(e.length,80);return e.slice(0,a)}function ie(e,t,a={}){if(e.state==="tool-call"||e.state==="tool-result"){const r={runId:e.runId,sourceSeq:e.seq},o=se(e.args);return{state:e.state,runId:e.runId,seq:e.seq,sessionId:t,detailRef:r,...e.name?{name:e.name}:{},...o!==void 0?{argsSummary:o}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...a}}return{...e,sessionId:t,...a}}const le=3e4;function ue(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function ce(e,t){return`Agent send failed: ${t instanceof Error?t.message:String(t)}`}function L(e,t){return e!=="manager"?e:t==="claude"?"claude":"codex"}function H(e,t,a){e.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:t.sessionId,message:t,session:{id:t.sessionId,agentType:a.agentType,agentSessionId:a.agentSessionId??null,modelId:a.modelId??null,workDir:a.workDir,status:"active",externalChannel:null}}})}function ge(e,t){F({sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,workDir:t.workDir,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,workDir:t.workDir,status:"active",externalChannel:null}}})}function U(e){return ae(e)}function j(e,t,a){return{}}function B(e,t,a,r,o,u){e.configure?.({sessionId:t,externalChannel:r??null,env:{...U(a),...j(t,r,o),...u?{SHENNIAN_MANAGED_ROOM_CONTEXT:u}:{}}})}function fe(e,t,a){if(e!=="claude"||!a)return t;const r=t.trim();return!!r&&r.startsWith("-")&&!r.includes("/")?te(a)??t:t}function pe(e,t,a,r){let o=null;function u(n,s={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:ie(n,t,s),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function g(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function f(n){if(!n.currentRunId||!n.currentRunPhase)return;const s=n.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:t,runId:n.currentRunId,seq:s,runPhase:n.currentRunPhase},seq:s,id:`agent-heartbeat-${n.currentRunId}-${s}-${Date.now()}`})}function y(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{f(n)},le),n.heartbeatTimer.unref?.())}function i(n){const s=n?.pendingTextEvent;!n||!s||!s.text||(u({state:"delta",runId:s.runId,seq:s.seq,text:s.text,thinking:s.thinking||void 0}),n.pendingTextEvent=null)}r.on("agentEvent",n=>{const s=e.sessions.get(t),T=ue(n),I=n.state==="final"||n.state==="error"||n.state==="aborted";if(s&&(s.nextEventSeq=n.seq+1,I||(s.currentRunId=n.runId),!I&&T&&(s.currentRunPhase=T,y(s)),n.agentSessionId&&(s.agentSessionId=n.agentSessionId)),e.managerRuntime?.noteAgentEvent(t,n),n.state==="delta"&&n.thinking)return;n.state!=="delta"&&E({level:"info",sessionId:t,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}}),n.state==="delta"&&n.text&&!n.thinking?x(t,{id:`agent-${n.runId}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?x(t,{id:`agent-${n.runId}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?x(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:Y(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&x(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.message});const h=`${t}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&e.runTextAcc.set(h,(e.runTextAcc.get(h)??"")+n.text),n.agentSessionId&&n.agentSessionId!==o&&(o=n.agentSessionId,e.nativeFusion?.noteManagedSourceSession(t,L(a,n.source),n.agentSessionId),s&&F({sessionId:t,agentType:a,workDir:s.workDir,agentSessionId:n.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const S=n.text??"";if(!S)return;s?.pendingTextEvent&&s.pendingTextEvent.runId!==n.runId&&i(s),s&&!s.pendingTextEvent&&(s.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:!1}),s?.pendingTextEvent&&(s.pendingTextEvent.text+=S,s.pendingTextEvent.seq=n.seq);return}let w={};if(n.state==="final"){i(s);const S=e.runTextAcc.get(h)??"";S&&(w={messageSummary:de(S)}),e.runTextAcc.delete(h)}else n.state==="error"||n.state==="aborted"?(i(s),e.runTextAcc.delete(h)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&i(s);I&&s?.currentRunId===n.runId&&(s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,g(s),e.chatQueue?.noteTerminal(t)),u(n,w)}),r.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${t} agentType=${a}: ${n.message}`),e.sessions.delete(t),e.chatQueue?.noteTerminal(t),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:t,message:n.message,runId:"",seq:0}})})}function me(e,t){if(e.processedReqIds.add(t),e.processedReqIds.size>1e3){const a=e.processedReqIds.values().next().value;e.processedReqIds.delete(a)}}async function C(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function J(e,t,a,r,o,u,g){return g?e.send(t,a,r,o,u,g):u?.trim()?e.send(t,a,r,o,u):o?.length?e.send(t,a,r,o):r?e.send(t,a,r):e.send(t,a)}async function he(e,t,a,r,o,u,g,f){e.evictIdleSessions();const y=X(a);if(!y)throw new Error(`Unsupported agent: ${a}`);const i=r==="manager"&&a!=="manager"?new ee(y,a==="claude"?"claude":"codex"):y;B(i,t,a,g,f),await i.start(t,o,u);const n={adapter:i,workDir:o,agentType:a,sessionMode:r,agentSessionId:u??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:g??null,externalReplyTarget:f??null,externalChannelEnv:{...U(a),...j(t,g,f)}};return e.sessions.set(t,n),pe(e,t,a,i),n}function ye(e,t){const a=e.sessions.get(t),r=a?.currentRunId;if(!a||!r)return;const o=a.nextEventSeq;e.runTextAcc.delete(`${t}:${r}`),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:t,runId:r,seq:o},seq:o,id:`agent-evt-${r}-${o}`}),e.chatQueue?.noteTerminal(t)}async function qe(e,t,a){if(e.processedReqIds.has(t.id)){e.client.sendRes({type:"res",id:t.id,ok:!0});return}me(e,t.id);const{sessionId:r,text:o,agentType:u,sessionMode:g,managerConfig:f,workDir:y,agentSessionId:i,modelId:n,systemPrompt:s,managerDefaultWorkerAgentType:T,managerDefaultWorkerModelId:I,reasoningEffort:h,clientMessageId:w,sessionListProjection:S,waitForDispatch:Q,responseId:K,managedRoomContextToken:$,suppressExternalOwnerEcho:V}=t.params,k=K||t.id;ne(S);const b=null,P="";if(!r||!o){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:"sessionId and text are required"});return}const c=u,D=c==="manager"||g==="manager"?"manager":"standard";D==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(r,T??null,I??null);const q=(c==="claude"||c==="codex"||c==="workbuddy")&&typeof h=="string"&&h.trim()?h.trim():void 0,A=e.resolvePath(fe(c,y||O.homedir(),i)||O.homedir()),M=A,_=oe(t.params.attachments),p=_?.length?await re({text:o,attachments:_,workDir:A}):{text:o,attachments:_,localized:!1};let d=e.sessions.get(r);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==c||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==D||d.workDir!==A||JSON.stringify(d.externalChannel??null)!==JSON.stringify(b??null)){e.sessions.delete(r);try{await C(d)}catch{e.processedReqIds.delete(t.id)}d=void 0}else if(i&&d.agentSessionId!==i)try{await d.adapter.resume(i),d.agentSessionId=i}catch{e.sessions.delete(r);try{await C(d)}catch{e.processedReqIds.delete(t.id)}d=void 0}}if(!d)try{d=await he(e,r,c,D,A,i,b,P)}catch(l){const m=l instanceof Error&&l.message.startsWith("Unsupported agent:")?l.message:`Failed to start ${u}: ${l instanceof Error?l.message:String(l)}`;console.error(`[chat.send] start failed reqId=${t.id} sessionId=${r} agentType=${u} workDir=${A} agentSessionId=${i??""}: ${m}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:m,runId:"",seq:0}}),e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:m});return}$&&/^mrc_[A-Za-z0-9_-]{43}$/.test($)&&B(d.adapter,r,c,b,P,$);const W={id:w??`user-${t.id}`,sessionId:r,role:"user",ts:Date.now(),payload:Z(p.text,p.attachments)};E({level:"info",sessionId:r,wsEvent:"chat.send.start",metadata:{reqId:t.id,agentType:c,sessionMode:D,managerConfig:f??null,modelId:n,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:I??null,reasoningEffort:q}});const z=l=>{ge(e,{sessionId:r,agentType:c,sessionMode:D,managerConfig:f??null,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:I??null}),d.currentRunId||(d.nextEventSeq=0),e.nativeFusion?.registerManagedSend({sessionId:r,agentType:c,sourceAgentType:L(c,n),canonicalMessageId:w??null,sourceSessionKey:d.agentSessionId??i??null,text:p.text,managedEchoPolicy:l?.deliveryMode==="external_owner_queue"&&!V?"import_following":"suppress_following",externalRunId:l?.deliveryMode==="external_owner_queue"?`codex-external-${l.queuedSubmissionId??w??t.id}`:null}),x(r,W),H(e,W,{agentType:c,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n})},N=async(l,m)=>{const R=ce(c,l);console.error(`[chat.send] send failed reqId=${t.id} sessionId=${r} agentType=${u} workDir=${A} agentSessionId=${d.agentSessionId??i??""}: ${R}`),e.sessions.delete(r);try{await C(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:R,runId:"",seq:0}}),!m){const v={id:`agent-error-${t.id}-${Date.now()}`,sessionId:r,role:"agent",ts:Date.now(),payload:R};x(r,v),H(e,v,{agentType:c,workDir:M,agentSessionId:d.agentSessionId??i??null,modelId:n})}m&&(e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:k,ok:!1,error:R}))};if(Q){let l;try{const m=p.attachments;l=await J(d.adapter,p.text,n,q,m,s,a),E({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}catch(m){await N(m,!0);return}z(l??void 0),e.client.sendRes({type:"res",id:k,ok:!0,...p.localized||l?.deliveryMode==="external_owner_queue"?{payload:{...p.localized?{localizedAttachments:!0}:{},...l?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:l.deliveryMode,queuedSubmissionId:l.queuedSubmissionId}:{}}}:{}}),E({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});return}z(),e.client.sendRes({type:"res",id:k,ok:!0,...p.localized?{payload:{localizedAttachments:!0}}:{}}),E({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});const G=p.attachments;J(d.adapter,p.text,n,q,G,s,a).then(()=>{E({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}).catch(l=>{N(l,!1)})}async function Me(e,t){const{sessionId:a}=t.params,r=e.sessions.get(a);if(r){try{await r.adapter.stop()}catch{}ye(e,a),e.activityPublisher?.publish(a,null)}e.client.sendRes({type:"res",id:t.id,ok:!0})}export{Me as handleChatAbort,qe as handleChatSend,ge as sendSessionUpdateEvent};
1
+ import V from"node:os";import{createAgent as re}from"../../agents/adapter.js";import{buildApprovalPendingPayload as oe,buildUserMessagePayload as de}from"@shennian/wire";import{ManagerModeAdapter as ie}from"../../agents/manager.js";import{reportLog as M}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as le}from"../../native-fusion/parsers.js";import{appendMessage as q,getMessageDeliveryStatus as ce,recordSession as z,upsertMessage as F}from"../store.js";import{mergeProjectedSessions as ue}from"../projection.js";import{buildManagedAgentEnv as ge}from"../../agents/config-status.js";import{parsePersonalAttachmentReferences as fe}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as pe,markAttachmentReferences as me}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as R}from"../index-publisher.js";import{writeToolDetail as Ie}from"../tool-detail-store.js";function he(e){const n=e.indexOf(`
2
+ `),s=n>0?Math.min(n,80):Math.min(e.length,80);return e.slice(0,s)}function ye(e,n,s={}){if(e.state==="tool-call"||e.state==="tool-result"){const a={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:n,detailRef:a,status:e.state==="tool-call"?"running":"completed",detailAvailable:!0,...e.durationMs!==void 0?{durationMs:e.durationMs}:{},...e.name?{name:e.name}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...s}}return{...e,sessionId:n,...s}}const Se=3e4;function Ae(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function j(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function K(e,n){return e!=="manager"?e:n==="claude"?"claude":"codex"}function Ee(e,n){z({sessionId:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:n.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,workDir:n.workDir,status:"active",externalChannel:null}}})}function G(e){return ge(e)}function X(e,n,s){return{}}function Y(e,n,s,a,i,c){e.configure?.({sessionId:n,externalChannel:a??null,env:{...G(s),...X(n,a,i),...c?{SHENNIAN_MANAGED_ROOM_CONTEXT:c}:{}}})}function xe(e,n,s){if(e!=="claude"||!s)return n;const a=n.trim();return!!a&&a.startsWith("-")&&!a.includes("/")?le(s)??n:n}function Re(e,n,s,a){let i=null;function c(t,r={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:ye(t,n,r),seq:t.seq,id:`agent-evt-${t.runId}-${t.seq}`})}function m(t){t?.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null)}function S(t){if(!t.currentRunId||!t.currentRunPhase)return;const r=t.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:n,runId:t.currentRunId,seq:r,runPhase:t.currentRunPhase},seq:r,id:`agent-heartbeat-${t.currentRunId}-${r}-${Date.now()}`})}function w(t){!t||t.heartbeatTimer||(t.heartbeatTimer=setInterval(()=>{S(t)},Se),t.heartbeatTimer.unref?.())}function l(t){const r=t?.pendingTextEvent;!t||!r||!r.text||(q(n,{id:`agent-${r.runId}-${r.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:r.text}),R(e.client),c({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),t.pendingTextEvent=null)}a.on("agentEvent",t=>{const r=e.sessions.get(n),b=Ae(t),T=t.state==="final"||t.state==="error"||t.state==="aborted";if(r&&(r.nextEventSeq=t.seq+1,T||(r.currentRunId=t.runId),!T&&b&&(r.currentRunPhase=b,w(r)),t.agentSessionId&&(r.agentSessionId=t.agentSessionId)),e.managerRuntime?.noteAgentEvent(n,t),t.state==="delta"&&t.thinking)return;t.state!=="delta"&&M({level:"info",sessionId:n,wsEvent:`agent.${t.state}`,wsDirection:"out",metadata:{runId:t.runId,seq:t.seq,agentType:s}});let A=!1;if(t.state==="tool-call"||t.state==="tool-result"){const p=`agent-${t.runId}-${t.seq}`;Ie({sessionId:n,toolCallId:p,detailRevision:t.seq,ts:Date.now(),name:t.name??"tool",...t.args!==void 0?{args:t.args}:{},...t.result!==void 0?{result:t.result}:{},status:t.state==="tool-call"?"running":"completed",...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq}}),q(n,{id:p,sessionId:n,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:t.state==="tool-call"?"tool_use":"tool_result",name:t.name,toolCallId:p,status:t.state==="tool-call"?"running":"completed",...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq},detailRevision:t.seq,detailAvailable:!0,resultOmitted:t.state==="tool-result"})}),A=!0}else t.state==="approval-pending"?(q(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:oe(t.approval)}),A=!0):(t.state==="error"||t.state==="aborted")&&t.message&&(q(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:t.message}),A=!0);A&&R(e.client);const I=`${n}:${t.runId}`;if(t.state==="delta"&&!t.thinking&&t.text&&e.runTextAcc.set(I,(e.runTextAcc.get(I)??"")+t.text),t.agentSessionId&&t.agentSessionId!==i&&(i=t.agentSessionId,e.nativeFusion?.noteManagedSourceSession(n,K(s,t.source),t.agentSessionId),r&&z({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:t.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n,agentType:s,agentSessionId:t.agentSessionId}}})),t.state==="delta"){const p=t.text??"";if(!p)return;r?.pendingTextEvent&&r.pendingTextEvent.runId!==t.runId&&l(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:t.runId,seq:t.seq,text:"",thinking:!1}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=p,r.pendingTextEvent.seq=t.seq);return}let $={};if(t.state==="final"){l(r);const p=e.runTextAcc.get(I)??"";p&&($={messageSummary:he(p)}),e.runTextAcc.delete(I)}else t.state==="error"||t.state==="aborted"?(l(r),e.runTextAcc.delete(I)):(t.state==="tool-call"||t.state==="tool-result"||t.state==="approval-pending")&&l(r);T&&r?.currentRunId===t.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,m(r),e.chatQueue?.noteTerminal(n),z({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:r.agentSessionId,status:t.state==="final"?"completed":"failed",lastActivityAt:new Date().toISOString()}),R(e.client)),c(t,$)}),a.on("error",t=>{console.error(`[chat.send] adapter error sessionId=${n} agentType=${s}: ${t.message}`),e.sessions.delete(n),e.chatQueue?.noteTerminal(n),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:n,message:t.message,runId:"",seq:0}})})}function we(e,n){if(e.processedReqIds.add(n),e.processedReqIds.size>1e3){const s=e.processedReqIds.values().next().value;e.processedReqIds.delete(s)}}async function v(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function Z(e,n,s,a,i,c,m){return m?e.send(n,s,a,i,c,m):c?.trim()?e.send(n,s,a,i,c):i?.length?e.send(n,s,a,i):a?e.send(n,s,a):e.send(n,s)}async function Te(e,n,s,a,i,c,m,S){e.evictIdleSessions();const w=re(s);if(!w)throw new Error(`Unsupported agent: ${s}`);const l=a==="manager"&&s!=="manager"?new ie(w,s==="claude"?"claude":"codex"):w;Y(l,n,s,m,S),await l.start(n,i,c);const t={adapter:l,workDir:i,agentType:s,sessionMode:a,agentSessionId:c??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:m??null,externalReplyTarget:S??null,externalChannelEnv:{...G(s),...X(n,m,S)}};return e.sessions.set(n,t),Re(e,n,s,l),t}function ke(e,n){const s=e.sessions.get(n),a=s?.currentRunId;if(!s||!a)return;const i=s.nextEventSeq;e.runTextAcc.delete(`${n}:${a}`),s.pendingTextEvent=null,s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:a,seq:i},seq:i,id:`agent-evt-${a}-${i}`}),e.chatQueue?.noteTerminal(n)}async function Le(e,n,s){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}we(e,n.id);const{sessionId:a,text:i,agentType:c,sessionMode:m,managerConfig:S,workDir:w,agentSessionId:l,modelId:t,systemPrompt:r,managerDefaultWorkerAgentType:b,managerDefaultWorkerModelId:T,reasoningEffort:A,clientMessageId:I,sessionListProjection:$,waitForDispatch:p,responseId:ee,managedRoomContextToken:P,suppressExternalOwnerEcho:te}=n.params,h=ee||n.id;ue($);const C=null,L="";if(!a||!i){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:"sessionId and text are required"});return}const g=c,D=g==="manager"||m==="manager"?"manager":"standard";D==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(a,b??null,T??null);const W=(g==="claude"||g==="codex"||g==="workbuddy")&&typeof A=="string"&&A.trim()?A.trim():void 0,k=e.resolvePath(xe(g,w||V.homedir(),l)||V.homedir()),ne=k,_=fe(n.params.attachments);_?.length&&pe(k,_);const y={text:i,attachments:_,localized:!1};let d=e.sessions.get(a);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==g||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==D||d.workDir!==k||JSON.stringify(d.externalChannel??null)!==JSON.stringify(C??null)){e.sessions.delete(a);try{await v(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}else if(l&&d.agentSessionId!==l)try{await d.adapter.resume(l),d.agentSessionId=l}catch{e.sessions.delete(a);try{await v(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}}if(!d)try{d=await Te(e,a,g,D,k,l,C,L)}catch(o){const f=o instanceof Error&&o.message.startsWith("Unsupported agent:")?o.message:`Failed to start ${c}: ${o instanceof Error?o.message:String(o)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${a} agentType=${c} workDir=${k} agentSessionId=${l??""}: ${f}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:f,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:f});return}P&&/^mrc_[A-Za-z0-9_-]{43}$/.test(P)&&Y(d.adapter,a,g,C,L,P);const E={id:I??`user-${n.id}`,sessionId:a,role:"user",ts:Date.now(),payload:de(y.text,y.attachments)},x=ce(a,E.id),H=x.state==="queued";if(x.state!=="not_found"&&!H){e.client.sendRes({type:"res",id:h,ok:!0,payload:x});return}M({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:g,sessionMode:D,managerConfig:S??null,modelId:t,managerDefaultWorkerAgentType:b??null,managerDefaultWorkerModelId:T??null,reasoningEffort:W}});const O=(o,f)=>({sessionId:a,clientMessageId:E.id,messageId:E.id,deliveryState:o,bodyRevision:f,committedAt:new Date(E.ts).toISOString()}),U=()=>{if(H)return{sessionId:a,clientMessageId:x.clientMessageId,messageId:x.messageId,deliveryState:x.deliveryState,bodyRevision:x.bodyRevision,committedAt:x.committedAt};Ee(e,{sessionId:a,agentType:g,sessionMode:D,managerConfig:S??null,workDir:ne,agentSessionId:d.agentSessionId??l??null,modelId:t,managerDefaultWorkerAgentType:b??null,managerDefaultWorkerModelId:T??null});const o=q(a,E,"committed");return me(k,_??[]),R(e.client),O("committed",o)},B=o=>{d.currentRunId||(d.nextEventSeq=0),e.nativeFusion?.registerManagedSend({sessionId:a,agentType:g,sourceAgentType:K(g,t),canonicalMessageId:I??null,sourceSessionKey:d.agentSessionId??l??null,text:y.text,managedEchoPolicy:o?.deliveryMode==="external_owner_queue"&&!te?"import_following":"suppress_following",externalRunId:o?.deliveryMode==="external_owner_queue"?`codex-external-${o.queuedSubmissionId??I??n.id}`:null});const f=o?.deliveryMode==="external_owner_queue"?"queued":"executing",u=F(a,E,f);return R(e.client),O(f,u)},Q=async(o,f)=>{const u=j(g,o);console.error(`[chat.send] send failed reqId=${n.id} sessionId=${a} agentType=${c} workDir=${k} agentSessionId=${d.agentSessionId??l??""}: ${u}`),e.sessions.delete(a);try{await v(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:u,runId:"",seq:0}}),!f){const N={id:`agent-error-${n.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:u};q(a,N),R(e.client)}f&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:u}))};if(p){try{U()}catch(u){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${u instanceof Error?u.message:String(u)}`});return}let o;try{const u=y.attachments;o=await Z(d.adapter,y.text,t,W,u,r,s),M({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(u){const N=F(a,E,"failed");R(e.client);const se=j(g,u);await Q(u,!1),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...O("failed",N),failureMessage:se}});return}const f=B(o??void 0);e.client.sendRes({type:"res",id:h,ok:!0,payload:{...f,...y.localized?{localizedAttachments:!0}:{},...o?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:o.deliveryMode,queuedSubmissionId:o.queuedSubmissionId}:{}}}),M({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let J;try{J=U()}catch(o){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${o instanceof Error?o.message:String(o)}`});return}B(),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...J,...y.localized?{localizedAttachments:!0}:{}}}),M({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const ae=y.attachments;Z(d.adapter,y.text,t,W,ae,r,s).then(()=>{M({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(o=>{F(a,E,"failed"),R(e.client),Q(o,!1)})}async function He(e,n){const{sessionId:s}=n.params,a=e.sessions.get(s);if(a){try{await a.adapter.stop()}catch{}ke(e,s),e.activityPublisher?.publish(s,null)}e.client.sendRes({type:"res",id:n.id,ok:!0})}export{He as handleChatAbort,Le as handleChatSend,Ee as sendSessionUpdateEvent};
@@ -7,7 +7,6 @@ export declare function handleFsRename(runtime: SessionManagerRuntime, req: ReqF
7
7
  export declare function handleFsExportMarkdownPdf(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
8
8
  export declare function handleFsExportMarkdownPdfSetup(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
9
9
  export declare function handleFsArchiveZip(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
10
- export declare function handleFsTransfer(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
11
10
  export declare function handleFsTransferStart(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
12
11
  export declare function handleFsTransferChunk(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
13
12
  export declare function handleFsTransferFinish(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
@@ -1 +1 @@
1
- import d from"node:fs";import g from"node:os";import p from"node:path";import{convertMarkdownToPdf as _,defaultPdfOutputPath as j,findChromiumExecutable as O,installManagedChromium as L,managedChromiumBrowsersPath as B,MarkdownPdfBrowserMissingError as U}from"../../tools/markdown-to-pdf.js";import{createZipArchive as X}from"../archive-zip.js";import{BinaryTextPreviewError as Z,decodeTextBufferAuto as H,isAutoTextEncoding as W}from"../../fs/text-decoder.js";const D="__roots__",V=2e3,Y=1024*1024*1024,G=5e3,J=1024*1024*1024;function E(e){return/^[A-Za-z]:([\\/]|$)/.test(e)||/^\\\\[^\\]+\\[^\\]+/.test(e)}function v(e){return E(e)?p.win32:p.posix}function x(e){const t=d.statSync(e);return{name:v(e).basename(e),path:e,isDir:t.isDirectory(),size:t.isFile()?t.size:void 0,modifiedAt:t.mtimeMs}}function K(){if(g.platform()==="win32"){const e=[];for(let t=65;t<=90;t+=1){const r=`${String.fromCharCode(t)}:\\`;d.existsSync(r)&&e.push({name:r,path:r,isDir:!0})}return e}return[{name:"/",path:"/",isDir:!0}]}function Q(e,t){const r=typeof e=="object"&&e&&"code"in e?String(e.code):"";return r==="ENOENT"?`Directory not found: ${t}`:r==="EACCES"||r==="EPERM"?`Permission denied: ${t}`:e instanceof Error?e.message:String(e)}function q(e){if(!e||e==="."||e.trim()!==e||p.posix.isAbsolute(e)||p.win32.isAbsolute(e))return!1;const t=e.replace(/\\/g,"/");return t!==e?!1:t.split("/").every(r=>r&&r!=="."&&r!==".."&&!$(r))}function M(e){const t=e.trim();if(!t||t!==e||e==="."||e===".."||$(e)||/[\\/:]/.test(e)||/[<>|"?*]/.test(e)||/[. ]$/.test(e))return!1;const r=e.split(".")[0]?.toUpperCase();return!(r&&/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/.test(r))}function $(e){return Array.from(e).some(t=>{const r=t.charCodeAt(0);return r>=0&&r<=31})}function ee(e){return Array.isArray(e)?e.map(t=>({relativePath:String(t.relativePath||""),size:Number(t.size||0),mimeType:t.mimeType,modifiedAt:t.modifiedAt})):[]}async function de(e,t){const r=t.params.path||g.homedir(),a=t.params.rootPath||r;if(r===D){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:D,entries:K()}});return}const n=e.resolveAuthorizedPath(r,a);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}const s=n.path;try{const o=d.readdirSync(s,{withFileTypes:!0}),c=E(s)?p.win32.join:p.join,i=o.map(l=>({name:l.name,path:c(s,l.name),isDir:l.isDirectory()})).sort((l,f)=>l.isDir!==f.isDir?l.isDir?-1:1:l.name.localeCompare(f.name));e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:s,entries:i}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:Q(o,s)})}}async function ce(e,t){const r=t.params.path,a=t.params.rootPath||r,n=e.resolveAuthorizedPath(r,a);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}const s=n.path,o=t.params.encoding,c=typeof o=="string"?o:"utf8",i=t.params.offset,l=t.params.length;try{const f=d.statSync(s);if(!f.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}if(i!=null&&l!=null){const u=Math.min(l,Math.max(0,f.size-i));if(u<=0){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:"",offset:i,length:0,totalSize:f.size,path:s}});return}const P=d.openSync(s,"r");try{const k=Buffer.alloc(u),S=d.readSync(P,k,0,u,i);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:k.subarray(0,S).toString("base64"),offset:i,length:S,totalSize:f.size,path:s}})}finally{d.closeSync(P)}return}const y=t.params.maxSize||(c==="base64"?5*1024*1024:512*1024);if(f.size>y){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`File too large: ${f.size} bytes (max ${y})`,payload:{size:f.size,path:s}});return}if(c==="base64"){const u=d.readFileSync(s);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:u.toString("base64"),path:s,size:f.size}});return}if(W(c)){const u=d.readFileSync(s),P=H(u);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:P.content,path:s,size:f.size,encoding:P.encoding,encodingFallback:P.fallback}});return}const R=d.readFileSync(s,"utf-8");e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:R,path:s,size:f.size}})}catch(f){e.client.sendRes({type:"res",id:t.id,ok:!1,error:f instanceof Z?f.message:String(f)})}}async function le(e,t){const r=t.params.path,a=t.params.content,n=t.params.rootPath||r;if(!r||typeof a!="string"){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and content are required"});return}const s=e.resolveAuthorizedPath(r,n);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}try{const o=d.existsSync(s.path)?d.statSync(s.path):null;if(o&&!o.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}d.writeFileSync(s.path,a,"utf-8");const c=d.statSync(s.path);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:s.path,size:c.size,modifiedAt:c.mtimeMs}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(o)})}}async function fe(e,t){const r=t.params.path,a=t.params.newName;if(!r||!a){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and newName are required"});return}if(!M(a)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid newName"});return}const n=v(r),s=t.params.rootPath||n.dirname(r),o=e.resolveAuthorizedPath(r,s);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}try{const c=v(o.path),i=c.join(c.dirname(o.path),a),l=e.resolveAuthorizedPath(i,s);if(!l.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:l.error});return}if(d.existsSync(l.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Target already exists"});return}d.renameSync(o.path,l.path),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{oldPath:o.path,newPath:l.path,entry:x(l.path)}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function pe(e,t){const r=t.params.path,a=t.params.rootPath||p.dirname(r||"."),n=typeof t.params.title=="string"?t.params.title:void 0;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const s=e.resolveAuthorizedPath(r,a);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}if(!/\.mdx?$/i.test(s.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Only Markdown files can be exported to PDF"});return}const o=j(s.path),c=e.resolveAuthorizedPath(o,a);if(!c.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:c.error});return}try{const i=await _(s.path,{outputPath:c.path,title:n});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:s.path,outputPath:i.outputPath,entry:x(i.outputPath)}})}catch(i){if(i instanceof U){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"This machine needs the PDF export component before Markdown files can be exported to PDF.",payload:i.setup});return}e.client.sendRes({type:"res",id:t.id,ok:!1,error:i instanceof Error?i.message:String(i)})}}async function he(e,t){try{await L();const r=await O();e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{ready:!0,browserPath:r,managedPath:B()}})}catch(r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:r instanceof Error?r.message:String(r)})}}function te(e){let t="";for(const a of e.trim())a.charCodeAt(0)<=31||'<>:"/\\|?*'.includes(a)?t.endsWith("-")||(t+="-"):t+=a;return t.replace(/[. ]+$/g,"")||"folder"}async function ye(e,t){const r=t.params.path,a=t.params.rootPath||r;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const n=e.resolveAuthorizedPath(r,a);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}try{if(!d.statSync(n.path).isDirectory()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a directory"});return}const o=v(n.path),c=te(o.basename(n.path)),i=p.join(g.tmpdir(),"shennian-archives"),l=p.join(i,`${c}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),f=X(n.path,l,{maxFiles:G,maxTotalSize:J});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:n.path,outputPath:f.outputPath,entry:x(f.outputPath),fileCount:f.fileCount,totalSize:f.totalSize}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function ue(e,t){const{name:r,targetPath:a,data:n,direct:s}=t.params;if(!r||!n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"name and data are required"});return}try{const o=t.params.rootPath||a||g.homedir(),c=e.resolveAuthorizedPath(a||o,o);if(!c.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:c.error});return}const i=c.path,l=s?i:p.join(i,".uploads");s||d.mkdirSync(l,{recursive:!0});const f=p.join(l,p.basename(r)),y=Buffer.from(n,"base64");d.writeFileSync(f,y),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:f}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(o)})}}async function Pe(e,t){const{name:r,targetPath:a,totalSize:n,direct:s,kind:o,baseName:c}=t.params,i=ee(t.params.manifest),l=o==="folder",f=l&&c||r;if(!f||!l&&!n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"name and totalSize are required"});return}try{const y=t.params.rootPath||a||g.homedir(),R=e.resolveAuthorizedPath(a||y,y);if(!R.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:R.error});return}const u=R.path,P=s?u:p.join(u,".uploads");s||d.mkdirSync(P,{recursive:!0});const k=`tf-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;if(l){if(!i.length){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"manifest is required for folder uploads"});return}if(i.length>V){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Too many files: ${i.length}`});return}const T=p.basename(f);if(!M(T)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid folder name"});return}const C=p.join(P,T),m=e.resolveAuthorizedPath(C,y);if(!m.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:m.error});return}const b=new Set;let z=0;const A=new Map;for(const h of i){if(!q(h.relativePath)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Invalid relativePath: ${h.relativePath}`});return}if(!Number.isFinite(h.size)||h.size<0){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Invalid size: ${h.relativePath}`});return}if(b.has(h.relativePath)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Duplicate relativePath: ${h.relativePath}`});return}if(b.add(h.relativePath),z+=h.size,z>Y){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Folder too large: ${z} bytes`});return}const I=p.join(m.path,...h.relativePath.split("/")),F=e.resolveAuthorizedPath(I,y);if(!F.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:F.error});return}const w=p.join(g.tmpdir(),`.shennian-upload-${k}-${A.size}`);d.writeFileSync(w,Buffer.alloc(0)),A.set(h.relativePath,{relativePath:h.relativePath,tempPath:w,targetPath:F.path,size:h.size})}e.pendingTransfers.set(k,{tempPath:"",targetPath:m.path,totalSize:z,kind:"folder",rootPath:y,targetDir:m.path,files:A}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{transferId:k,path:m.path}});return}const S=p.join(g.tmpdir(),`.shennian-upload-${k}`),N=p.join(P,p.basename(f));d.writeFileSync(S,Buffer.alloc(0)),e.pendingTransfers.set(k,{tempPath:S,targetPath:N,totalSize:n,kind:"file"}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{transferId:k}})}catch(y){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(y)})}}async function ke(e,t){const{transferId:r,offset:a,data:n,relativePath:s}=t.params,o=e.pendingTransfers.get(r);if(!o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid transferId"});return}try{const c=o.kind==="folder"?o.files?.get(String(s||"")):{tempPath:o.tempPath,size:o.totalSize};if(!c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid relativePath"});return}const i=Buffer.from(n,"base64");if(a<0||a+i.length>c.size){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Chunk exceeds declared size"});return}const l=d.openSync(c.tempPath,"r+");try{d.writeSync(l,i,0,i.length,a)}finally{d.closeSync(l)}e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{written:i.length}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function ge(e,t){const{transferId:r}=t.params,a=e.pendingTransfers.get(r);if(!a){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid transferId"});return}try{if(a.kind==="folder"){let n=0;for(const s of a.files?.values()??[])d.mkdirSync(p.dirname(s.targetPath),{recursive:!0}),d.renameSync(s.tempPath,s.targetPath),n+=1;e.pendingTransfers.delete(r),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a.targetDir??a.targetPath,count:n}});return}d.renameSync(a.tempPath,a.targetPath),e.pendingTransfers.delete(r),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a.targetPath}})}catch{try{d.copyFileSync(a.tempPath,a.targetPath),d.unlinkSync(a.tempPath),e.pendingTransfers.delete(r),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a.targetPath}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(n)})}}}async function me(e,t){const{transferId:r}=t.params,a=e.pendingTransfers.get(r);if(a){const n=a.kind==="folder"?Array.from(a.files?.values()??[]).map(s=>s.tempPath):[a.tempPath];for(const s of n)try{s&&d.unlinkSync(s)}catch{}e.pendingTransfers.delete(r)}e.client.sendRes({type:"res",id:t.id,ok:!0})}function Re(e){for(const[,t]of e.pendingTransfers){const r=t.kind==="folder"?Array.from(t.files?.values()??[]).map(a=>a.tempPath):[t.tempPath];for(const a of r)try{a&&d.unlinkSync(a)}catch{}}e.pendingTransfers.clear()}export{Re as cleanupPendingTransfers,ye as handleFsArchiveZip,pe as handleFsExportMarkdownPdf,he as handleFsExportMarkdownPdfSetup,de as handleFsLs,ce as handleFsRead,fe as handleFsRename,ue as handleFsTransfer,me as handleFsTransferAbort,ke as handleFsTransferChunk,ge as handleFsTransferFinish,Pe as handleFsTransferStart,le as handleFsWrite};
1
+ import l from"node:fs";import P from"node:os";import h from"node:path";import{convertMarkdownToPdf as x,defaultPdfOutputPath as v,findChromiumExecutable as F,installManagedChromium as w,managedChromiumBrowsersPath as E,MarkdownPdfBrowserMissingError as T}from"../../tools/markdown-to-pdf.js";import{createZipArchive as b}from"../archive-zip.js";import{BinaryTextPreviewError as C,decodeTextBufferAuto as M,isAutoTextEncoding as D}from"../../fs/text-decoder.js";import{abortAttachmentTransfer as I,appendAttachmentTransferChunk as N,finishAttachmentTransfer as $,startAttachmentTransfer as _}from"../attachment-transfer-store.js";const S="__roots__",O=5e3,j=1024*1024*1024;function A(e){return/^[A-Za-z]:([\\/]|$)/.test(e)||/^\\\\[^\\]+\\[^\\]+/.test(e)}function u(e){return A(e)?h.win32:h.posix}function k(e){const t=l.statSync(e);return{name:u(e).basename(e),path:e,isDir:t.isDirectory(),size:t.isFile()?t.size:void 0,modifiedAt:t.mtimeMs}}function B(){if(P.platform()==="win32"){const e=[];for(let t=65;t<=90;t+=1){const r=`${String.fromCharCode(t)}:\\`;l.existsSync(r)&&e.push({name:r,path:r,isDir:!0})}return e}return[{name:"/",path:"/",isDir:!0}]}function L(e,t){const r=typeof e=="object"&&e&&"code"in e?String(e.code):"";return r==="ENOENT"?`Directory not found: ${t}`:r==="EACCES"||r==="EPERM"?`Permission denied: ${t}`:e instanceof Error?e.message:String(e)}function Z(e){const t=e.trim();if(!t||t!==e||e==="."||e===".."||H(e)||/[\\/:]/.test(e)||/[<>|"?*]/.test(e)||/[. ]$/.test(e))return!1;const r=e.split(".")[0]?.toUpperCase();return!(r&&/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/.test(r))}function H(e){return Array.from(e).some(t=>{const r=t.charCodeAt(0);return r>=0&&r<=31})}async function Q(e,t){const r=t.params.path||P.homedir(),s=t.params.rootPath||r;if(r===S){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:S,entries:B()}});return}const a=e.resolveAuthorizedPath(r,s);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}const o=a.path;try{const n=l.readdirSync(o,{withFileTypes:!0}),c=A(o)?h.win32.join:h.join,d=n.map(p=>({name:p.name,path:c(o,p.name),isDir:p.isDirectory()})).sort((p,i)=>p.isDir!==i.isDir?p.isDir?-1:1:p.name.localeCompare(i.name));e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:o,entries:d}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:L(n,o)})}}async function q(e,t){const r=t.params.path,s=t.params.rootPath||r,a=e.resolveAuthorizedPath(r,s);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}const o=a.path,n=t.params.encoding,c=typeof n=="string"?n:"utf8",d=t.params.offset,p=t.params.length;try{const i=l.statSync(o);if(!i.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}if(d!=null&&p!=null){const f=Math.min(p,Math.max(0,i.size-d));if(f<=0){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:"",offset:d,length:0,totalSize:i.size,path:o}});return}const y=l.openSync(o,"r");try{const m=Buffer.alloc(f),g=l.readSync(y,m,0,f,d);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:m.subarray(0,g).toString("base64"),offset:d,length:g,totalSize:i.size,path:o}})}finally{l.closeSync(y)}return}const R=t.params.maxSize||(c==="base64"?5*1024*1024:512*1024);if(i.size>R){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`File too large: ${i.size} bytes (max ${R})`,payload:{size:i.size,path:o}});return}if(c==="base64"){const f=l.readFileSync(o);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:f.toString("base64"),path:o,size:i.size}});return}if(D(c)){const f=l.readFileSync(o),y=M(f);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:y.content,path:o,size:i.size,encoding:y.encoding,encodingFallback:y.fallback}});return}const z=l.readFileSync(o,"utf-8");e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:z,path:o,size:i.size}})}catch(i){e.client.sendRes({type:"res",id:t.id,ok:!1,error:i instanceof C?i.message:String(i)})}}async function ee(e,t){const r=t.params.path,s=t.params.content,a=t.params.rootPath||r;if(!r||typeof s!="string"){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and content are required"});return}const o=e.resolveAuthorizedPath(r,a);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}try{const n=l.existsSync(o.path)?l.statSync(o.path):null;if(n&&!n.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}l.writeFileSync(o.path,s,"utf-8");const c=l.statSync(o.path);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:o.path,size:c.size,modifiedAt:c.mtimeMs}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(n)})}}async function te(e,t){const r=t.params.path,s=t.params.newName;if(!r||!s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and newName are required"});return}if(!Z(s)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid newName"});return}const a=u(r),o=t.params.rootPath||a.dirname(r),n=e.resolveAuthorizedPath(r,o);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}try{const c=u(n.path),d=c.join(c.dirname(n.path),s),p=e.resolveAuthorizedPath(d,o);if(!p.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:p.error});return}if(l.existsSync(p.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Target already exists"});return}l.renameSync(n.path,p.path),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{oldPath:n.path,newPath:p.path,entry:k(p.path)}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function re(e,t){const r=t.params.path,s=t.params.rootPath||h.dirname(r||"."),a=typeof t.params.title=="string"?t.params.title:void 0;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const o=e.resolveAuthorizedPath(r,s);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}if(!/\.mdx?$/i.test(o.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Only Markdown files can be exported to PDF"});return}const n=v(o.path),c=e.resolveAuthorizedPath(n,s);if(!c.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:c.error});return}try{const d=await x(o.path,{outputPath:c.path,title:a});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:o.path,outputPath:d.outputPath,entry:k(d.outputPath)}})}catch(d){if(d instanceof T){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"This machine needs the PDF export component before Markdown files can be exported to PDF.",payload:d.setup});return}e.client.sendRes({type:"res",id:t.id,ok:!1,error:d instanceof Error?d.message:String(d)})}}async function se(e,t){try{await w();const r=await F();e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{ready:!0,browserPath:r,managedPath:E()}})}catch(r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:r instanceof Error?r.message:String(r)})}}function U(e){let t="";for(const s of e.trim())s.charCodeAt(0)<=31||'<>:"/\\|?*'.includes(s)?t.endsWith("-")||(t+="-"):t+=s;return t.replace(/[. ]+$/g,"")||"folder"}async function oe(e,t){const r=t.params.path,s=t.params.rootPath||r;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const a=e.resolveAuthorizedPath(r,s);if(!a.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:a.error});return}try{if(!l.statSync(a.path).isDirectory()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a directory"});return}const n=u(a.path),c=U(n.basename(a.path)),d=h.join(P.tmpdir(),"shennian-archives"),p=h.join(d,`${c}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),i=b(a.path,p,{maxFiles:O,maxTotalSize:j});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:a.path,outputPath:i.outputPath,entry:k(i.outputPath),fileCount:i.fileCount,totalSize:i.totalSize}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o instanceof Error?o.message:String(o)})}}async function ae(e,t){const r=t.params;try{const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const a=_({...r,rootPath:s.path});a.reused||e.pendingTransfers.set(r.transferId,{transferId:r.transferId,rootPath:s.path,lastActivityAt:Date.now()}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:a})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function ne(e,t){const r=t.params;try{const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const a=Buffer.from(r.data,"base64"),o=N({rootPath:s.path,transferId:r.transferId,relativePath:r.relativePath,offset:r.offset,data:a});e.pendingTransfers.set(r.transferId,{transferId:r.transferId,rootPath:s.path,lastActivityAt:Date.now()}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:o})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function ie(e,t){const r=t.params;try{const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const a=$(s.path,r.transferId);e.pendingTransfers.delete(r.transferId),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:a})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function de(e,t){const r=t.params;try{const s=e.resolveAuthorizedPath(r.rootPath,r.rootPath);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}const a=I(s.path,r.transferId);e.pendingTransfers.delete(r.transferId),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{aborted:a}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}function ce(e){e.pendingTransfers.clear()}export{ce as cleanupPendingTransfers,oe as handleFsArchiveZip,re as handleFsExportMarkdownPdf,se as handleFsExportMarkdownPdfSetup,Q as handleFsLs,q as handleFsRead,te as handleFsRename,de as handleFsTransferAbort,ne as handleFsTransferChunk,ie as handleFsTransferFinish,ae as handleFsTransferStart,ee as handleFsWrite};
@@ -0,0 +1,3 @@
1
+ import type { ReqFrame } from '@shennian/wire';
2
+ import type { SessionManagerRuntime } from '../types.js';
3
+ export declare function handleSessionMessageStatus(runtime: SessionManagerRuntime, req: ReqFrame): void;
@@ -0,0 +1 @@
1
+ import{getMessageDeliveryStatus as t}from"../store.js";function n(a,s){const e=s.params;if(!e.sessionId||!e.clientMessageId)throw new Error("sessionId and clientMessageId are required");a.client.sendRes({type:"res",id:s.id,ok:!0,payload:t(e.sessionId,e.clientMessageId)})}export{n as handleSessionMessageStatus};
@@ -1 +1 @@
1
- import{createAgent as l}from"../../agents/adapter.js";import{buildManagedAgentEnv as y}from"../../agents/config-status.js";function a(e){return typeof e=="string"?e.trim():""}async function u(e,t){const n=t.params,r=a(n.sessionId),d=a(n.title),i=a(n.agentSessionId),p=a(n.agentType),c=a(n.workDir)||process.cwd();if(!r||!d){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"sessionId and title are required"});return}if(p!=="codex"||!i){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{skipped:!0}});return}const s=e.sessions.get(r);if(s?.agentType==="codex"&&s.adapter.setTitle){await s.adapter.setTitle(i,d,s.workDir||c),s.agentSessionId=i,s.lastActiveAt=Date.now(),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{synced:!0}});return}const o=l("codex");if(!o?.setTitle){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Codex adapter does not support title sync"});return}o.configure?.({sessionId:r,env:y("codex")});try{await o.start(r,c,i),await o.setTitle(i,d,c),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{synced:!0}})}finally{await o.stop().catch(()=>{})}}export{u as handleSessionTitleSet};
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 +1 @@
1
- import{isSystemControlPayload as h}from"@shennian/wire";import{readMessages as x}from"../store.js";const q=2e3,D=8e3,C=/(?:token|api[-_]?key|password|passwd|pwd|secret|authorization|cookie|credential|private[-_]?key)/i,M=/\b([A-Z0-9_]*(?:TOKEN|KEY|SECRET|PASSWORD|PASSWD|PWD|AUTH)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi,N=/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi,k=/\bsk-[A-Za-z0-9_-]{12,}\b/g;function R(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function l(e){if(!e)return null;if(typeof e=="string")try{return l(JSON.parse(e))}catch{return null}const t=R(e);if(!t)return null;const n=typeof t.runId=="string"?t.runId:void 0,r=typeof t.sourceSeq=="number"?t.sourceSeq:typeof t.seq=="number"?t.seq:void 0,s=typeof t.toolIndex=="number"?t.toolIndex:void 0;return{...n?{runId:n}:{},...r!=null?{sourceSeq:r}:{},...s!=null?{toolIndex:s}:{}}}function f(e){return/^agent-(.+)-\d+$/.exec(e)?.[1]??null}function m(e){const t=/^agent-.+-(\d+)$/.exec(e);return t?Number(t[1]):null}function E(e){const t=typeof e=="number"?e:Number(e);return!Number.isFinite(t)||t<=0?q:Math.min(Math.max(200,Math.floor(t)),D)}function d(e,t){if(h(e))return"[SYSTEM CONTEXT REDACTED]";const n=e.replace(M,"$1=[REDACTED]").replace(N,"Bearer [REDACTED]").replace(k,"sk-[REDACTED]");return n.length>t?`${n.slice(0,t)}\u2026`:n}function p(e,t,n=0){if(e==null)return e;if(typeof e=="string")return d(e,t);if(typeof e=="number"||typeof e=="boolean")return e;if(n>=4)return"[Truncated]";if(Array.isArray(e))return e.slice(0,20).map(r=>p(r,t,n+1));if(typeof e=="object"){const r={};for(const[s,o]of Object.entries(e).slice(0,50)){if(C.test(s)){r[s]="[REDACTED]";continue}r[s]=p(o,t,n+1)}return r}return String(e)}function O(e,t){const r=R(e)?.command;if(typeof r=="string")return d(r,t);if(Array.isArray(r)){const s=r.map(o=>String(o)).join(" ");return d(s,t)}}function A(e){const t=x(e),n=[];for(const r of t)if(!(r.role!=="agent"||!r.payload.trim().startsWith("{")))try{const s=JSON.parse(r.payload),o=s.type;if(o!=="tool"&&o!=="tool_use"&&o!=="tool_result")continue;const i=typeof s.name=="string"&&s.name.trim()?s.name.trim():"tool";n.push({id:r.id,ts:r.ts,name:i,args:s.args,result:s.result,status:o==="tool_result"||Object.prototype.hasOwnProperty.call(s,"result")?"completed":s.status==="completed"||s.status==="failed"||s.status==="running"?s.status:"running",detailRef:l(s.detailRef)})}catch{}return n}function w(e,t,n){let r=0;e.id===t.messageId&&(r+=100),t.name&&e.name===t.name&&(r+=20);const s=n?.runId??t.runId??f(t.messageId),o=n?.sourceSeq??t.sourceSeq??m(t.messageId)??void 0;return s&&(e.detailRef?.runId??f(e.id))===s&&(r+=20),o!=null&&(e.detailRef?.sourceSeq??m(e.id))===o&&(r+=80),e.args!=null&&(r+=5),r}function P(e){const t=l(e.detailRef)??l({runId:e.runId,sourceSeq:e.sourceSeq,toolIndex:e.toolIndex}),n=A(e.sessionId);return n.length===0?null:n.map(s=>({candidate:s,score:w(s,e,t)})).filter(s=>s.score>0).sort((s,o)=>o.score-s.score||o.candidate.ts-s.candidate.ts)[0]?.candidate??null}function b(e){return e.detailRef?.runId??f(e.id)}function I(e){return e.detailRef?.sourceSeq??m(e.id)}function F(e,t,n){if(n.result!=null)return n;const r=b(n)??t.runId??f(t.messageId),s=I(n)??t.sourceSeq??m(t.messageId);return A(e).filter(o=>{if(o.id===n.id||o.name!==n.name||o.result==null||r&&b(o)!==r)return!1;const i=I(o);return s!=null&&i!=null?i>=s:o.ts>=n.ts}).sort((o,i)=>{const u=I(o),a=I(i);return s!=null&&u!=null&&a!=null?Math.abs(u-s)-Math.abs(a-s):o.ts-i.ts})[0]??null}async function K(e,t){const n=t.params,r=typeof n.sessionId=="string"?n.sessionId:"",s=typeof n.messageId=="string"?n.messageId:"";if(!r||!s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"sessionId and messageId are required"});return}const o={sessionId:r,messageId:s,...typeof n.name=="string"?{name:n.name}:{},...typeof n.toolIndex=="number"?{toolIndex:n.toolIndex}:{},...typeof n.runId=="string"?{runId:n.runId}:{},...typeof n.sourceSeq=="number"?{sourceSeq:n.sourceSeq}:{},...n.detailRef!==void 0?{detailRef:n.detailRef}:{},maxChars:E(n.maxChars)},i=E(o.maxChars),u=P(o);if(!u||u.args==null&&u.result==null){const T={detailStatus:"not_found",messageId:s,toolIndex:o.toolIndex??0,name:o.name,resultOmitted:!0,source:"unavailable",reason:"tool detail is not available on this machine"};e.client.sendRes({type:"res",id:t.id,ok:!0,payload:T});return}const a=u.args==null?void 0:p(u.args,i),S=O(u.args,i),g=F(r,o,u),c=g?.result??u.result,y=typeof c=="string"?d(c,i):c==null?void 0:d(JSON.stringify(p(c,i)),i),_={detailStatus:"available",messageId:s,toolIndex:o.toolIndex??0,name:u.name,status:g?.status??u.status,...a!==void 0?{argsSummary:a}:{},...S?{command:S}:{},...y!==void 0?{result:y}:{},resultOmitted:y===void 0,source:"local-session-store"};e.client.sendRes({type:"res",id:t.id,ok:!0,payload:_})}export{K as handleSessionToolDetail};
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};
@@ -0,0 +1,3 @@
1
+ import { type ReqFrame } from '@shennian/wire';
2
+ import type { SessionManagerRuntime } from '../types.js';
3
+ export declare function handleVoiceSessionBodyWindow(runtime: SessionManagerRuntime, req: ReqFrame): void;
@@ -0,0 +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};
@@ -0,0 +1,17 @@
1
+ import { type ReqFrame } from '@shennian/wire';
2
+ import type { CliRelayClient } from '../relay/client.js';
3
+ import type { CanonicalSessionStore } from './canonical-store.js';
4
+ export declare class PersonalHistorySubscriptions {
5
+ private client;
6
+ private store;
7
+ private subscriptions;
8
+ private unsubscribeStore;
9
+ constructor(client: Pick<CliRelayClient, 'sendRes' | 'sendEvent'>, store?: CanonicalSessionStore);
10
+ handleOpen(req: ReqFrame): void;
11
+ handlePage(req: ReqFrame): void;
12
+ handleClose(req: ReqFrame): void;
13
+ closeAll(): void;
14
+ private handleMutation;
15
+ private publishMutation;
16
+ private requireSubscription;
17
+ }
@@ -0,0 +1 @@
1
+ import{SESSION_HISTORY_LIMITS as r}from"@shennian/wire";import{getCanonicalSessionStore as u}from"./store.js";class b{client;store;subscriptions=new Map;unsubscribeStore;constructor(i,s=u()){this.client=i,this.store=s,this.unsubscribeStore=s.subscribe(o=>this.handleMutation(o))}handleOpen(i){const s=i.params;if(a(s.sessionId,s.subscriptionId),this.subscriptions.has(s.subscriptionId))throw new Error("subscription already exists");if([...this.subscriptions.values()].filter(t=>t.sessionId===s.sessionId).length>=r.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);try{const t=this.store.readPage(s.sessionId,{limit:d(s.limit,r.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}}),e.active=!0;for(const c of e.buffered)this.publishMutation(e,c);e.buffered=[]}catch(t){throw this.subscriptions.delete(s.subscriptionId),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:d(s.limit,r.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}})}handleClose(i){const s=i.params;a(s.sessionId,s.subscriptionId),this.subscriptions.get(s.subscriptionId)?.sessionId===s.sessionId&&this.subscriptions.delete(s.subscriptionId),this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:s.sessionId,subscriptionId:s.subscriptionId,closed:!0}})}closeAll(){this.subscriptions.clear(),this.unsubscribeStore()}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}]}}),i.lastSentRevision=s.revision)}requireSubscription(i,s){a(i,s);const o=this.subscriptions.get(s);if(!o||o.sessionId!==i)throw new Error("subscription_not_found");return o}}function a(n,i){if(typeof n!="string"||!n||typeof i!="string"||!i)throw new Error("invalid history subscription identity")}function d(n,i){const s=n??i;if(!Number.isSafeInteger(s)||s<1||s>r.maxPageMessages)throw new Error("invalid history limit");return s}export{b as PersonalHistorySubscriptions};
@@ -0,0 +1,3 @@
1
+ import type { CliRelayClient } from '../relay/client.js';
2
+ export declare function publishPendingSessionIndexes(client: Pick<CliRelayClient, 'sendBufferedEvent'>): Promise<void>;
3
+ export declare function requestSessionIndexPublish(client: Pick<CliRelayClient, 'sendBufferedEvent'>): void;
@@ -0,0 +1 @@
1
+ import{acknowledgeSessionIndexUpdate as d,listPendingSessionIndexUpdates as r}from"./store.js";const i=new WeakMap,t=new WeakSet;function a(s){const e=s,n=i.get(e);if(n)return t.add(e),n;const o=c(s).finally(()=>{i.get(e)===o&&i.delete(e),t.delete(e)&&f(s)});return i.set(e,o),o}async function c(s){const e=r();for(const n of e)await s.sendBufferedEvent({type:"event",event:"session.index.updated",id:`session-index-${n.daemonInstallationId}-${n.session.id}-${n.session.indexRevision}`,payload:n},6e4),d(n.session.id,n.session.indexRevision)}function f(s){a(s).catch(e=>{console.error("[session-index] sync failed; dirty revisions retained for retry",e)})}export{a as publishPendingSessionIndexes,f as requestSessionIndexPublish};
@@ -27,7 +27,9 @@ export declare class SessionManager {
27
27
  private pendingTransfers;
28
28
  private managerRuntime;
29
29
  private chatQueue;
30
+ private historySubscriptions;
30
31
  private activityProbeTimer;
32
+ private attachmentCleanupTimer;
31
33
  private activeRequests;
32
34
  constructor(client: CliRelayClient, nativeFusion?: NativeSessionFusionService | null, cliVersion?: string | undefined, upgradePolicyController?: UpgradePolicyController | null, managedRoomBinder?: ManagedRoomBinder | null);
33
35
  private getRuntime;
@@ -1 +1 @@
1
- import u from"node:crypto";import{getRegisteredAgents as m,unregisterAgent as p}from"../agents/adapter.js";import{loadConfig as f}from"../config/index.js";import{handleUpgradeStart as g,handleUpgradeStatus as b}from"../commands/upgrade.js";import{handleAgentsRefresh as w,handleModelsRefresh as v}from"./handlers/agents.js";import{handleAgentCapabilitiesList as k}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as R,handleAgentConfigGet as y,handleAgentConfigTest as S,handleAgentConfigUpsert as A}from"./handlers/agent-config.js";import{handleChatAbort as c,handleChatSend as l}from"./handlers/chat.js";import{handleSessionRefresh as I}from"./handlers/session-refresh.js";import{handleSessionToolDetail as T}from"./handlers/tool-detail.js";import{handleSessionTitleSet as P}from"./handlers/title.js";import{cleanupPendingTransfers as C,handleFsLs as M,handleFsRead as F,handleFsRename as x,handleFsWrite as D,handleFsTransfer as E,handleFsTransferAbort as Q,handleFsTransferChunk as z,handleFsTransferFinish as U,handleFsTransferStart as W,handleFsExportMarkdownPdf as _,handleFsExportMarkdownPdfSetup as O,handleFsArchiveZip as V}from"./handlers/fs.js";import{handleRegionProbe as B,handleRegionSwitch as $,handleUpgradeSetPolicy as j}from"./handlers/control.js";import{ManagerRuntimeService as L,setManagerRuntimeService as h}from"../manager/runtime.js";import{ChatQueueManager as G}from"./queue.js";import{createAuthorizedFsRoot as N,resolveAuthorizedPath as X,resolveSessionWorkDir as Z}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 H}from"../agents/custom.js";import{handleManagedRoomBind as J,handleManagedRoomRestart as K}from"./handlers/room-managed.js";import{listSessionRecords as Y}from"./store.js";const q=50;import{resolveSessionWorkDir as Fe}from"../fs/boundary.js";class Pe{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;activityProbeTimer=null;activeRequests=0;constructor(e,t=null,a,i=null,n=null){this.client=e,this.nativeFusion=t,this.cliVersion=a,this.upgradePolicyController=i,this.managedRoomBinder=n,this.managerRuntime=new L({getRuntime:()=>this.getRuntime(),dispatchReq:s=>this.handleReq(s)}),this.chatQueue=new G({getRuntime:()=>this.getRuntime(),dispatchReq:s=>this.handleReq(s)}),h(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(s=>{console.error("[session.activity] managed probe failed",s)})},15e3),this.activityProbeTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async activateRoomSession(e){const t=Y().find(s=>s.sessionId===e.sessionId),a=t?.agentType===e.agentType?t.modelId??void 0:void 0,i={value:null},n=new Proxy(this.client,{get:(s,o)=>{if(o==="sendRes")return d=>{i.value=d};const r=Reflect.get(s,o,s);return typeof r=="function"?r.bind(s):r}});return await l({...this.getRuntime(),client:n},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:a,sessionMode:t?.sessionMode??(t?.agentType==="manager"?"manager":"standard"),managerConfig:t?.managerConfig??null,managerDefaultWorkerAgentType:t?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t?.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 t=new Proxy(this.client,{get:(a,i)=>{if(i==="sendRes")return()=>{};const n=Reflect.get(a,i,a);return typeof n=="function"?n.bind(a):n}});await c({...this.getRuntime(),client:t},{type:"req",id:`room-activation-abort-${u.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const a=await t.adapter.getStatus().catch(()=>null);if(!a?.active||!a.runPhase)continue;const i=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:a.runId||t.currentRunId,runPhase:a.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:i,canStop:a.canStop??!0});const n=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:a.runId||t.currentRunId,seq:n,runPhase:a.runPhase,canStop:a.canStop??!0},seq:n,id:`agent-status-${a.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of m())t.startsWith("custom:")&&p(t);const e=f();for(const[t,a]of Object.entries(e.customAgents??{}))H(t,a)}async handleReq(e){const t=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":await l(t,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 c(t,e);break;case"session.refresh":await I(t,e);break;case"session.tool.detail":await T(t,e);break;case"session.title.set":await P(t,e);break;case"room.agent.bind-managed":await J(t,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await K(t,this.managedRoomBinder,e);break;case"fs.ls":await M(t,e);break;case"fs.read":await F(t,e);break;case"fs.write":await D(t,e);break;case"fs.export.markdown-pdf":await _(t,e);break;case"fs.export.markdown-pdf.setup":await O(t,e);break;case"fs.archive.zip":await V(t,e);break;case"fs.rename":await x(t,e);break;case"fs.transfer":await E(t,e);break;case"fs.transfer.start":await W(t,e);break;case"fs.transfer.chunk":await z(t,e);break;case"fs.transfer.finish":await U(t,e);break;case"fs.transfer.abort":await Q(t,e);break;case"region.probe":await B(t,e);break;case"region.switch":await $(t,e);break;case"upgrade.start":await g(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await b(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await j(t,e);break;case"agents.refresh":await w(t,e);break;case"models.refresh":await v(t,e);break;case"agent.config.get":await y(t,e);break;case"agent.config.upsert":await A(t,e);break;case"agent.config.clear":await R(t,e);break;case"agent.config.test":await S(t,e);break;case"agent.capabilities.list":await k(t,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(a){this.client.sendRes({type:"res",id:e.id,ok:!1,error:a instanceof Error?a.message:String(a)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<q)return;let e=null;for(const[t,a]of this.sessions)(!e||a.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:a});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 Z(e)}resolveAuthorizedPath(e,t){return X(e,N(t))}async cleanup(){this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=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(),C(this.getRuntime()),await this.managerRuntime.stop(),h(null)}}export{Pe as SessionManager,Fe as resolveSessionWorkDir};
1
+ import m from"node:crypto";import{getRegisteredAgents as p,unregisterAgent as f}from"../agents/adapter.js";import{loadConfig as g}from"../config/index.js";import{handleUpgradeStart as b,handleUpgradeStatus as w}from"../commands/upgrade.js";import{handleAgentsRefresh as v,handleModelsRefresh as k}from"./handlers/agents.js";import{handleAgentCapabilitiesList as y}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as R,handleAgentConfigGet as S,handleAgentConfigTest as A,handleAgentConfigUpsert as T}from"./handlers/agent-config.js";import{handleChatAbort as c,handleChatSend as l}from"./handlers/chat.js";import{handleSessionRefresh as I}from"./handlers/session-refresh.js";import{handleSessionToolDetail as P}from"./handlers/tool-detail.js";import{handleSessionTitleSet as C}from"./handlers/title.js";import{handleSessionMessageStatus as M}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as F}from"./handlers/voice-window.js";import{cleanupPendingTransfers as D,handleFsLs as x,handleFsRead as E,handleFsRename as Q,handleFsWrite as W,handleFsTransferAbort as _,handleFsTransferChunk as z,handleFsTransferFinish as U,handleFsTransferStart as O,handleFsExportMarkdownPdf as V,handleFsExportMarkdownPdfSetup as B,handleFsArchiveZip as $}from"./handlers/fs.js";import{handleRegionProbe as j,handleRegionSwitch as L,handleUpgradeSetPolicy as G}from"./handlers/control.js";import{ManagerRuntimeService as H,setManagerRuntimeService as h}from"../manager/runtime.js";import{ChatQueueManager as N}from"./queue.js";import{createAuthorizedFsRoot as X,resolveAuthorizedPath as Z,resolveSessionWorkDir as J}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 K}from"../agents/custom.js";import{handleManagedRoomBind as Y,handleManagedRoomRestart as q}from"./handlers/room-managed.js";import{listSessionRecords as d}from"./store.js";import{PersonalHistorySubscriptions as ee}from"./history-subscriptions.js";import{sweepAttachmentTransfers as te}from"./attachment-transfer-store.js";const se=50;import{resolveSessionWorkDir as ze}from"../fs/boundary.js";class Qe{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,t=null,s,i=null,n=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=i,this.managedRoomBinder=n,this.managerRuntime=new H({getRuntime:()=>this.getRuntime(),dispatchReq:a=>this.handleReq(a)}),this.chatQueue=new N({getRuntime:()=>this.getRuntime(),dispatchReq:a=>this.handleReq(a)}),this.historySubscriptions=new ee(this.client),h(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(a=>{console.error("[session.activity] managed probe failed",a)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const a=new Set([...d().map(r=>r.workDir),...Array.from(this.pendingTransfers.values()).map(r=>r.rootPath)]);for(const r of a)try{te(this.resolvePath(r))}catch(o){console.error("[attachment.cleanup] failed",o instanceof Error?o.message:String(o))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async activateRoomSession(e){const t=d().find(a=>a.sessionId===e.sessionId),s=t?.agentType===e.agentType?t.modelId??void 0:void 0,i={value:null},n=new Proxy(this.client,{get:(a,r)=>{if(r==="sendRes")return u=>{i.value=u};const o=Reflect.get(a,r,a);return typeof o=="function"?o.bind(a):o}});return await l({...this.getRuntime(),client:n},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:s,sessionMode:t?.sessionMode??(t?.agentType==="manager"?"manager":"standard"),managerConfig:t?.managerConfig??null,managerDefaultWorkerAgentType:t?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t?.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 t=new Proxy(this.client,{get:(s,i)=>{if(i==="sendRes")return()=>{};const n=Reflect.get(s,i,s);return typeof n=="function"?n.bind(s):n}});await c({...this.getRuntime(),client:t},{type:"req",id:`room-activation-abort-${m.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const i=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:i,canStop:s.canStop??!0});const n=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:n,runPhase:s.runPhase,canStop:s.canStop??!0},seq:n,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of p())t.startsWith("custom:")&&f(t);const e=g();for(const[t,s]of Object.entries(e.customAgents??{}))K(t,s)}async handleReq(e){const t=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":await l(t,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 c(t,e);break;case"session.refresh":await I(t,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":M(t,e);break;case"session.tool.detail":await P(t,e);break;case"voice.session.body.window":F(t,e);break;case"session.title.set":await C(t,e);break;case"room.agent.bind-managed":await Y(t,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await q(t,this.managedRoomBinder,e);break;case"fs.ls":await x(t,e);break;case"fs.read":await E(t,e);break;case"fs.write":await W(t,e);break;case"fs.export.markdown-pdf":await V(t,e);break;case"fs.export.markdown-pdf.setup":await B(t,e);break;case"fs.archive.zip":await $(t,e);break;case"fs.rename":await Q(t,e);break;case"fs.transfer.start":await O(t,e);break;case"fs.transfer.chunk":await z(t,e);break;case"fs.transfer.finish":await U(t,e);break;case"fs.transfer.abort":await _(t,e);break;case"region.probe":await j(t,e);break;case"region.switch":await L(t,e);break;case"upgrade.start":await b(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await w(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await G(t,e);break;case"agents.refresh":await v(t,e);break;case"models.refresh":await k(t,e);break;case"agent.config.get":await S(t,e);break;case"agent.config.upsert":await T(t,e);break;case"agent.config.clear":await R(t,e);break;case"agent.config.test":await A(t,e);break;case"agent.capabilities.list":await y(t,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(s){this.client.sendRes({type:"res",id:e.id,ok:!1,error:s instanceof Error?s.message:String(s)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<se)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});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 J(e)}resolveAuthorizedPath(e,t){return Z(e,X(t))}async cleanup(){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(),D(this.getRuntime()),await this.managerRuntime.stop(),h(null)}}export{Qe as SessionManager,ze as resolveSessionWorkDir};
@@ -1,4 +1,4 @@
1
- import type { ChatQueueSnapshot, ReqFrame } from '@shennian/wire';
1
+ import { type ChatQueueSnapshot, type ReqFrame } from '@shennian/wire';
2
2
  import type { LocalReqDispatcher } from '../manager/runtime.js';
3
3
  import type { SessionManagerRuntime } from './types.js';
4
4
  export declare class ChatQueueManager {
@@ -1,4 +1,5 @@
1
- import h from"node:fs";import{randomUUID as y}from"node:crypto";import{resolveShennianPath as I}from"../config/index.js";import{mergeProjectedSessions as M}from"./projection.js";import{materializeRemoteChatAttachments as k}from"./remote-attachments.js";const x=I("chat-queue.json");function w(){return{sessions:{}}}function f(){return new Date().toISOString()}function S(s){return`enqueue-send-${s}-${y()}`}function d(){try{const s=JSON.parse(h.readFileSync(x,"utf-8"));return{sessions:s.sessions&&typeof s.sessions=="object"?s.sessions:{}}}catch{return w()}}function u(s){h.mkdirSync(I(""),{recursive:!0}),h.writeFileSync(x,JSON.stringify(s,null,2))}function p(s){if(!Array.isArray(s))return;const e=s.map(n=>{if(!n||typeof n!="object")return null;const t=n,i={path:typeof t.path=="string"?t.path:"",name:typeof t.name=="string"?t.name:"",mimeType:typeof t.mimeType=="string"?t.mimeType:"",...typeof t.previewData=="string"&&t.previewData.trim()?{previewData:t.previewData.trim()}:{}};return i.path&&i.name&&i.mimeType?i:null}).filter(n=>n!=null);return e.length?e:void 0}function D(s){const e=f();return{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:p(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}class C{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:d().sessions[e]??[]}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if(M(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=p(t.attachments),r=i?.length?await k({text:t.text,attachments:i,workDir:t.workDir}):{text:t.text,attachments:i,localized:!1},a=await this.isSessionBusy(t.sessionId),o=d(),c=o.sessions[t.sessionId]??[];if(!a&&!c.length){await this.opts.dispatchReq({...e,id:S(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 l=D({...t,text:r.text,attachments:r.attachments}),m=t.origin==="external"?q(c,l):null;m||(o.sessions[t.sessionId]=[...c,l],u(o)),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:m?.id??l.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=d(),r=i.sessions[t.sessionId]??[],a=r.findIndex(o=>o.id===t.queueMessageId);if(a<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}r[a]={...r[a],text:t.text,attachments:p(t.attachments),updatedAt:f()},i.sessions[t.sessionId]=r,u(i),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=d(),r=i.sessions[t.sessionId]??[],a=r.filter(o=>o.id!==t.queueMessageId);if(a.length===r.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],u(i),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)}drainIdleQueues(){const e=d();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 n=d(),t=n.sessions[e]??[],i=t.shift();if(!i){this.broadcast(e);return}const r=i.origin==="external"?this.mergeExternalMessages(i,t):i;t.length?n.sessions[e]=t:delete n.sessions[e],u(n),this.broadcast(e),this.draining.add(e);try{await this.dispatchQueuedMessage(r)}finally{this.draining.delete(e)}}mergeExternalMessages(e,n){const t=[e];for(;n[0]?.origin==="external";)t.push(n.shift());const i=R(t);return i.length===1?i[0]:{...e,id:`external-batch-${e.id}`,text:i.map(r=>r.text).filter(Boolean).join(`
1
+ import c from"node:fs";import{randomUUID as p}from"node:crypto";import{buildUserMessagePayload as R}from"@shennian/wire";import{mergeProjectedSessions as T}from"./projection.js";import{parsePersonalAttachmentReferences as m}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as k,markAttachmentReferences as q}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as w}from"../personal-sync/paths.js";import{appendMessage as b,getMessageDeliveryStatus as E,recordSession as v,tombstoneMessage as P,upsertMessage as D}from"./store.js";import{requestSessionIndexPublish as y}from"./index-publisher.js";const I=w("outbound-queue.json");function A(){return{sessions:{}}}function M(){return new Date().toISOString()}function Q(s){return`enqueue-send-${s}-${p()}`}function l(){try{const s=JSON.parse(c.readFileSync(I,"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 A();throw s}}function g(s){const e=w();c.mkdirSync(e,{recursive:!0,mode:448});const n=`${I}.tmp-${process.pid}-${p()}`;try{c.writeFileSync(n,`${JSON.stringify(s,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"});const t=c.openSync(n,"r");if(typeof t=="number")try{c.fsyncSync(t)}finally{c.closeSync(t)}c.renameSync(n,I)}finally{try{c.unlinkSync(n)}catch{}}}function $(s){const e=M();return{id:s.queueMessageId||s.clientMessageId||`queue-${p()}`,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:m(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}function x(s){return{id:s.clientMessageId??s.id,sessionId:s.sessionId,role:"user",ts:Date.parse(s.createdAt),payload:R(s.text,s.attachments)}}class _{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:l().sessions[e]??[]}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if(T(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=m(t.attachments);o?.length&&k(i,o);const r={text:t.text,attachments:o,localized:!1},d=await this.isSessionBusy(t.sessionId),u=l(),h=u.sessions[t.sessionId]??[];if(!d&&!h.length){await this.opts.dispatchReq({...e,id:Q(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 a=$({...t,text:r.text,attachments:r.attachments}),f=t.origin==="external"?C(h,a):null;f||(u.sessions[t.sessionId]=[...h,a],g(u),v({sessionId:a.sessionId,agentType:a.agentType,sessionMode:a.sessionMode,managerConfig:a.managerConfig,workDir:a.workDir,agentSessionId:a.agentSessionId,modelId:a.modelId}),b(a.sessionId,x(a),"queued"),q(i,a.attachments??[]),y(n.client)),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:f?.id??a.id,...E(t.sessionId,f?.clientMessageId??f?.id??a.clientMessageId??a.id),queue:this.getSnapshot(t.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),d||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=l(),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 d=m(t.attachments),u=n.resolvePath(o[r].workDir);d?.length&&k(u,d),o[r]={...o[r],text:t.text,attachments:d,updatedAt:M()},i.sessions[t.sessionId]=o,g(i),D(t.sessionId,x(o[r]),"queued"),o[r].attachments?.length&&q(u,o[r].attachments??[]),y(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=l(),o=i.sessions[t.sessionId]??[],r=o.find(u=>u.id===t.queueMessageId),d=o.filter(u=>u.id!==t.queueMessageId);if(d.length===o.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}d.length?i.sessions[t.sessionId]=d:delete i.sessions[t.sessionId],g(i),P(t.sessionId,r?.clientMessageId??r?.id??t.queueMessageId),y(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)}drainIdleQueues(){const e=l();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=(l().sessions[e]??[])[0];if(!i){this.broadcast(e);return}const o=i;this.draining.add(e);try{D(e,x(o),"queued"),await this.dispatchQueuedMessage(o);const r=l(),d=(r.sessions[e]??[]).filter(u=>u.id!==i.id);d.length?r.sessions[e]=d:delete r.sessions[e],g(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=W(t);return i.length===1?i[0]:{...e,id:`external-batch-${e.id}`,text:i.map(o=>o.text).filter(Boolean).join(`
2
3
 
3
- `),attachments:b(i.flatMap(r=>r.attachments??[])),updatedAt:f()}}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 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 R(s){const e=new Set,n=[];for(const t of s){const i=g(t);e.has(i)||(e.add(i),n.push(t))}return n}function q(s,e){const n=g(e);return s.find(t=>g(t)===n)??null}function g(s){const e=T(s);return e?`id:${e}`:["body",A(s.text),...(s.attachments??[]).map(n=>n.path||`${n.name}:${n.mimeType}`)].join(`
4
- `)}function T(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 b(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 A(s){return String(s||"").replace(/\s+/g," ").trim()}export{C as ChatQueueManager};
4
+ `),attachments:B(i.flatMap(o=>o.attachments??[])),updatedAt:M()}}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 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 W(s){const e=new Set,n=[];for(const t of s){const i=S(t);e.has(i)||(e.add(i),n.push(t))}return n}function C(s,e){const n=S(e);return s.find(t=>S(t)===n)??null}function S(s){const e=N(s);return e?`id:${e}`:["body",j(s.text),...(s.attachments??[]).map(n=>n.path||`${n.name}:${n.mimeType}`)].join(`
5
+ `)}function N(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 B(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 j(s){return String(s||"").replace(/\s+/g," ").trim()}export{_ as ChatQueueManager};
@@ -1,4 +1,7 @@
1
- import type { AgentType, Envelope, ManagerConfig, SessionMode } from '@shennian/wire';
1
+ import { type AgentType, type Envelope, type ManagerConfig, type SessionIndexUpdate, type SessionDeliveryState, type SessionMessageStatusResponse, type SessionMode, type SessionTitleSource } from '@shennian/wire';
2
+ import { PERSONAL_SYNC_V1_ROOT_NAME } from '../personal-sync/paths.js';
3
+ import { CanonicalSessionStore } from './canonical-store.js';
4
+ export { PERSONAL_SYNC_V1_ROOT_NAME };
2
5
  export type LocalSessionRecord = {
3
6
  sessionId: string;
4
7
  agentType: AgentType;
@@ -14,7 +17,22 @@ export type LocalSessionRecord = {
14
17
  updatedAt: string;
15
18
  lastActivityAt: string;
16
19
  lastMessagePreview?: string | null;
20
+ bodyRevision: number;
21
+ indexRevision: number;
22
+ title: string;
23
+ titleSource: SessionTitleSource;
24
+ titleRevision: number;
25
+ titleLockedByUser: boolean;
26
+ titleOriginEventId: string | null;
27
+ firstAvailableSeq: number | null;
28
+ visibleMessageCount: number;
29
+ lastVisibleMessageId: string | null;
30
+ lastBodyCommittedAt: string | null;
31
+ deletionRevision: number;
32
+ deletedAt: string | null;
33
+ syncedIndexRevision: number;
17
34
  };
35
+ export declare function getDaemonInstallationId(): string;
18
36
  export declare function recordSession(input: {
19
37
  sessionId: string;
20
38
  agentType: AgentType;
@@ -28,11 +46,23 @@ export declare function recordSession(input: {
28
46
  status?: 'active' | 'completed' | 'failed';
29
47
  lastActivityAt?: string;
30
48
  lastMessagePreview?: string | null;
31
- }): void;
49
+ title?: string;
50
+ titleSource?: SessionTitleSource;
51
+ titleRevision?: number;
52
+ titleLockedByUser?: boolean;
53
+ titleOriginEventId?: string | null;
54
+ deletedAt?: string | null;
55
+ }): LocalSessionRecord;
32
56
  export declare function listSessionRecords(): LocalSessionRecord[];
33
- export declare function appendMessage(sessionId: string, envelope: Envelope): void;
57
+ export declare function listPendingSessionIndexUpdates(): SessionIndexUpdate[];
58
+ export declare function acknowledgeSessionIndexUpdate(sessionId: string, indexRevision: number): void;
59
+ export declare function appendMessage(sessionId: string, envelope: Envelope, deliveryState?: SessionDeliveryState): number;
60
+ export declare function upsertMessage(sessionId: string, envelope: Envelope, deliveryState?: SessionDeliveryState): number;
61
+ export declare function getMessageDeliveryStatus(sessionId: string, clientMessageId: string): SessionMessageStatusResponse;
62
+ export declare function tombstoneMessage(sessionId: string, messageId: string): number;
34
63
  export declare function readMessages(sessionId: string, opts?: {
35
64
  limit?: number;
36
65
  before?: number;
37
66
  }): Envelope[];
38
67
  export declare function listSessions(): string[];
68
+ export declare function getCanonicalSessionStore(): CanonicalSessionStore;
@@ -1,3 +1,2 @@
1
- import i from"node:fs";import f from"node:path";import{resolveShennianPath as g}from"../config/index.js";const l=g("sessions"),c=g("sessions-index.json");function u(){i.mkdirSync(l,{recursive:!0})}function S(e){return f.join(l,`${e}.jsonl`)}function d(){try{const e=JSON.parse(i.readFileSync(c,"utf-8"));return e&&typeof e=="object"?e:{}}catch{return{}}}function m(e){i.mkdirSync(f.dirname(c),{recursive:!0}),i.writeFileSync(c,JSON.stringify(e,null,2))}function I(e){const t=e.payload.replace(/\s+/g," ").trim();return!t||t.startsWith('{"v":1,"type":"tool')?null:t.length>120?`${t.slice(0,120)}...`:t}function A(e){try{const t=d(),n=t[e.sessionId],r=new Date().toISOString();t[e.sessionId]={sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode??n?.sessionMode,managerConfig:e.managerConfig??n?.managerConfig??null,workDir:e.workDir,agentSessionId:e.agentSessionId??n?.agentSessionId??null,modelId:e.modelId??n?.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType??n?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId??n?.managerDefaultWorkerModelId??null,status:e.status??n?.status??"active",createdAt:n?.createdAt??r,updatedAt:r,lastActivityAt:e.lastActivityAt??n?.lastActivityAt??r,lastMessagePreview:e.lastMessagePreview??n?.lastMessagePreview??null},m(t)}catch{}}function x(){return Object.values(d())}function D(e,t){try{u();const n=JSON.stringify(t)+`
2
- `;i.appendFileSync(S(e),n,"utf-8");const r=d(),a=r[e];if(a){const s=I(t);r[e]={...a,updatedAt:new Date().toISOString(),lastActivityAt:new Date(t.ts).toISOString(),lastMessagePreview:s??a.lastMessagePreview??null},m(r)}}catch{}}function M(e,t){const n=S(e);if(!i.existsSync(n))return[];const a=i.readFileSync(n,"utf-8").split(`
3
- `).filter(o=>o.trim().length>0);let s=[];for(const o of a)try{s.push(JSON.parse(o))}catch{}return s.sort((o,y)=>y.ts-o.ts),t?.before!==void 0&&(s=s.filter(o=>o.ts<t.before)),t?.limit!==void 0&&t.limit>0&&(s=s.slice(0,t.limit)),s}function h(){return u(),i.readdirSync(l).filter(e=>e.endsWith(".jsonl")).map(e=>e.replace(/\.jsonl$/,""))}export{D as appendMessage,x as listSessionRecords,h as listSessions,M as readMessages,A as recordSession};
1
+ import v from"node:crypto";import l from"node:fs";import f from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as M,SESSION_TITLE_SOURCE_PRIORITY as y}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as w,resolvePersonalSyncV1Path as p}from"../personal-sync/paths.js";import{CanonicalSessionStore as k}from"./canonical-store.js";let S=null,I=null;function m(){return p()}function r(){const t=f.join(m(),"canonical");return(!S||I!==t)&&(S=new k(t,c()),I=t),S}function R(){return f.join(m(),"session-index.json")}function u(){const t=R();if(!l.existsSync(t))return{};const e=JSON.parse(l.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);return e}function g(t){b(R(),t)}function O(t){const e=t.payload.replace(/\s+/g," ").trim();return!e||e.startsWith('{"v":1,"type":"tool')?null:e.length>120?`${e.slice(0,120)}...`:e}function c(){const t=f.join(m(),"installation.json");if(l.existsSync(t)){const i=JSON.parse(l.readFileSync(t,"utf8"));if(i.schemaVersion!==1||!i.daemonInstallationId)throw new Error(`invalid daemon installation identity: ${t}`);return i.daemonInstallationId}const e={schemaVersion:1,daemonInstallationId:v.randomUUID(),createdAt:new Date().toISOString()};return b(t,e),e.daemonInstallationId}function U(t){c();const e=u(),i=e[t.sessionId],n=new Date().toISOString(),s=r().getManifest(t.sessionId)?.bodyRevision??i?.bodyRevision??0,o=r().getManifest(t.sessionId),a=D(i,t),d={sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??i?.sessionMode,managerConfig:t.managerConfig??i?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??i?.agentSessionId??null,modelId:t.modelId??i?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??i?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??i?.managerDefaultWorkerModelId??null,status:t.status??i?.status??"active",createdAt:i?.createdAt??n,updatedAt:n,lastActivityAt:t.lastActivityAt??i?.lastActivityAt??n,lastMessagePreview:t.lastMessagePreview??i?.lastMessagePreview??null,bodyRevision:s,indexRevision:(i?.indexRevision??0)+1,...a,firstAvailableSeq:o?.firstAvailableSeq??i?.firstAvailableSeq??null,visibleMessageCount:o?.visibleMessageCount??i?.visibleMessageCount??0,lastVisibleMessageId:o?.lastVisibleMessageId??i?.lastVisibleMessageId??null,lastBodyCommittedAt:o?.lastBodyCommittedAt??i?.lastBodyCommittedAt??null,deletionRevision:i?.deletionRevision??0,deletedAt:t.deletedAt??i?.deletedAt??null,syncedIndexRevision:i?.syncedIndexRevision??0};return e[t.sessionId]=d,g(e),d}function C(){return Object.values(u())}function V(){const t=c();return C().filter(e=>e.indexRevision>e.syncedIndexRevision).sort((e,i)=>e.indexRevision-i.indexRevision).map(e=>({protocolVersion:M,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:e.lastMessagePreview??null,bodyStorageMode:"machine_local",bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}))}function L(t,e){const i=u(),n=i[t];if(!(!n||e<=n.syncedIndexRevision)){if(e>n.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);i[t]={...n,syncedIndexRevision:e},g(i)}}function W(t,e,i){c();const n=r().append(t,e,i);return A(t,e,n.bodyRevision),n.bodyRevision}function N(t,e,i){c();const n=r().upsert(t,e,i);return A(t,e,n.bodyRevision),n.bodyRevision}function q(t,e){const i=r().getMessageState(t,e);return!i||i.tombstoned||!i.message||!i.deliveryState?{state:"not_found",sessionId:t,clientMessageId:e}:{state:i.deliveryState,sessionId:t,clientMessageId:e,messageId:i.message.id,deliveryState:i.deliveryState,bodyRevision:i.lastRevision,committedAt:new Date(i.message.ts).toISOString()}}function $(t,e){c();const i=r().tombstone(t,e),n=u(),s=n[t];return s&&(n[t]={...s,bodyRevision:i.bodyRevision,indexRevision:s.indexRevision+1,updatedAt:new Date().toISOString()},g(n)),i.bodyRevision}function A(t,e,i){const n=u(),s=n[t];if(!s)return;const o=O(e),a=r().getManifest(t),d=e.role==="user"&&o&&!s.title?{title:o,titleSource:"first_user_fallback",titleRevision:s.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null;n[t]={...s,...d??{},bodyRevision:i,indexRevision:s.indexRevision+1,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),lastMessagePreview:o??s.lastMessagePreview??null,firstAvailableSeq:a?.firstAvailableSeq??s.firstAvailableSeq,visibleMessageCount:a?.visibleMessageCount??s.visibleMessageCount,lastVisibleMessageId:a?.lastVisibleMessageId??s.lastVisibleMessageId,lastBodyCommittedAt:a?.lastBodyCommittedAt??s.lastBodyCommittedAt},g(n)}function D(t,e){const i={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},n=e.title?.trim()??"";if(!n)return i;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=n===i.title&&e.titleSource===i.titleSource,a=e.titleRevision??i.titleRevision+(o?0:1);if(!Number.isSafeInteger(a)||a<0)throw new Error("invalid titleRevision");const d=y[e.titleSource],x=y[i.titleSource];return d<x||a<i.titleRevision||a===i.titleRevision&&!o?i:{title:n,titleSource:e.titleSource,titleRevision:a,titleLockedByUser:s,titleOriginEventId:e.titleOriginEventId??i.titleOriginEventId}}function j(t,e){const i=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let s=r().readRecent(t,i).messages;return e?.before!==void 0&&(s=s.filter(o=>o.ts<e.before)),[...s].sort((o,a)=>a.ts-o.ts)}function F(){return r().listSessionIds()}function J(){return r()}function b(t,e){const i=f.dirname(t);l.mkdirSync(i,{recursive:!0,mode:448});const n=`${t}.tmp-${process.pid}-${v.randomBytes(6).toString("hex")}`;let s;try{if(s=l.openSync(n,"wx",384),l.writeFileSync(s,`${JSON.stringify(e,null,2)}
2
+ `,"utf8"),l.fsyncSync(s),l.closeSync(s),s=void 0,l.renameSync(n,t),process.platform!=="win32"){const o=l.openSync(i,"r");try{l.fsyncSync(o)}finally{l.closeSync(o)}}}finally{s!==void 0&&l.closeSync(s);try{l.unlinkSync(n)}catch{}}}export{w as PERSONAL_SYNC_V1_ROOT_NAME,L as acknowledgeSessionIndexUpdate,W as appendMessage,J as getCanonicalSessionStore,c as getDaemonInstallationId,q as getMessageDeliveryStatus,V as listPendingSessionIndexUpdates,C as listSessionRecords,F as listSessions,j as readMessages,U as recordSession,$ as tombstoneMessage,N as upsertMessage};
@@ -0,0 +1,16 @@
1
+ import type { ToolDetailRef } from '@shennian/wire';
2
+ export type LocalToolDetail = {
3
+ schemaVersion: 1;
4
+ sessionId: string;
5
+ toolCallId: string;
6
+ detailRevision: number;
7
+ ts: number;
8
+ name: string;
9
+ args?: unknown;
10
+ result?: unknown;
11
+ status: 'running' | 'completed' | 'failed';
12
+ durationMs?: number;
13
+ detailRef: ToolDetailRef;
14
+ };
15
+ export declare function writeToolDetail(detail: Omit<LocalToolDetail, 'schemaVersion'>): void;
16
+ export declare function readToolDetail(sessionId: string, toolCallId: string): LocalToolDetail | null;
@@ -0,0 +1,2 @@
1
+ import m from"node:crypto";import n from"node:fs";import r from"node:path";import{resolvePersonalSyncV1Path as a}from"../personal-sync/paths.js";const f=200,u=25*1024*1024;function I(e){const o=y(e.sessionId);n.mkdirSync(o,{recursive:!0,mode:448}),S(r.join(o,`${c(e.toolCallId)}.json`),{schemaVersion:1,...e}),d(o)}function j(e,o){const s=r.join(y(e),`${c(o)}.json`);if(!n.existsSync(s))return null;try{const t=JSON.parse(n.readFileSync(s,"utf8"));return t.schemaVersion===1&&t.sessionId===e&&t.toolCallId===o?t:null}catch{return null}}function y(e){return a("tool-details",c(e))}function c(e){return m.createHash("sha256").update(e).digest("hex")}function S(e,o){const s=`${e}.tmp-${process.pid}-${m.randomBytes(6).toString("hex")}`;let t;try{t=n.openSync(s,"wx",384),n.writeFileSync(t,`${JSON.stringify(o)}
2
+ `,"utf8"),n.fsyncSync(t),n.closeSync(t),t=void 0,n.renameSync(s,e)}finally{t!==void 0&&n.closeSync(t);try{n.unlinkSync(s)}catch{}}}function d(e){const o=n.readdirSync(e).filter(t=>t.endsWith(".json")).map(t=>{const i=r.join(e,t),l=n.statSync(i);return{file:i,bytes:l.size,mtimeMs:l.mtimeMs}}).sort((t,i)=>t.mtimeMs-i.mtimeMs);let s=o.reduce((t,i)=>t+i.bytes,0);for(;o.length>f||s>u;){const t=o.shift();if(!t)return;n.unlinkSync(t.file),s-=t.bytes}}export{j as readToolDetail,I as writeToolDetail};
@@ -27,19 +27,9 @@ export type ActiveSession = {
27
27
  externalReplyTarget?: string | null;
28
28
  };
29
29
  export type PendingTransfer = {
30
- tempPath: string;
31
- targetPath: string;
32
- totalSize: number;
33
- kind?: 'file' | 'folder';
34
- rootPath?: string;
35
- targetDir?: string;
36
- files?: Map<string, PendingTransferFile>;
37
- };
38
- export type PendingTransferFile = {
39
- relativePath: string;
40
- tempPath: string;
41
- targetPath: string;
42
- size: number;
30
+ transferId: string;
31
+ rootPath: string;
32
+ lastActivityAt: number;
43
33
  };
44
34
  export type ChatQueueService = {
45
35
  handleEnqueue(req: import('@shennian/wire').ReqFrame): Promise<void>;
@@ -68,6 +68,7 @@ export type AgentEventPayload = {
68
68
  name: string;
69
69
  args?: Record<string, unknown>;
70
70
  detailRef?: ToolDetailRef;
71
+ durationMs?: number;
71
72
  } | {
72
73
  state: 'tool-result';
73
74
  sessionId?: string;
@@ -76,6 +77,7 @@ export type AgentEventPayload = {
76
77
  name: string;
77
78
  result?: string;
78
79
  detailRef?: ToolDetailRef;
80
+ durationMs?: number;
79
81
  } | {
80
82
  state: 'notify';
81
83
  source: string;