shennian 0.4.41 → 0.4.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 (34) hide show
  1. package/dist/publish-build-manifest.json +54 -24
  2. package/dist/src/index.js +2 -2
  3. package/dist/src/manager/registry.d.ts +4 -0
  4. package/dist/src/manager/registry.js +1 -1
  5. package/dist/src/manager/runtime.d.ts +1 -0
  6. package/dist/src/manager/runtime.js +7 -7
  7. package/dist/src/room/activation-service.js +5 -5
  8. package/dist/src/room/managed-room-binding.js +1 -1
  9. package/dist/src/room/mirror-sync-service.d.ts +2 -0
  10. package/dist/src/room/mirror-sync-service.js +1 -1
  11. package/dist/src/room/registration-authority.d.ts +4 -0
  12. package/dist/src/room/registration-authority.js +1 -0
  13. package/dist/src/room/room-action-client.d.ts +3 -0
  14. package/dist/src/room/room-action-client.js +1 -1
  15. package/dist/src/room/session-registry.d.ts +5 -0
  16. package/dist/src/room/session-registry.js +2 -2
  17. package/dist/src/session/deletion-sync-validation.d.ts +2 -0
  18. package/dist/src/session/deletion-sync-validation.js +1 -0
  19. package/dist/src/session/handlers/agent-control.d.ts +4 -0
  20. package/dist/src/session/handlers/agent-control.js +1 -0
  21. package/dist/src/session/handlers/room-managed.d.ts +1 -0
  22. package/dist/src/session/handlers/room-managed.js +1 -1
  23. package/dist/src/session/manager.d.ts +1 -5
  24. package/dist/src/session/manager.js +2 -2
  25. package/dist/src/session/queue-helpers.d.ts +10 -0
  26. package/dist/src/session/queue-helpers.js +11 -0
  27. package/dist/src/session/queue-retry.d.ts +11 -0
  28. package/dist/src/session/queue-retry.js +1 -0
  29. package/dist/src/session/queue.d.ts +11 -1
  30. package/dist/src/session/queue.js +2 -12
  31. package/dist/src/session/queued-chat-dispatch.d.ts +7 -0
  32. package/dist/src/session/queued-chat-dispatch.js +1 -0
  33. package/node_modules/@shennian/wire/dist/message-payload.js +1 -1
  34. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
- import{isAvailableBuiltinAgentType as A,managedAgentExecutionMode as k}from"@shennian/wire";import{getBuiltinAgentVersion as P,resolveBuiltinCommand as S}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as x,resolveCustomRuntimeExecutableFingerprint as B}from"../agents/custom-managed-runtime.js";import{loadConfig as K}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as $}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as T}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as q}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as W}from"./activation-policy.js";import{composeManagedRoomPrompt as D}from"./managed-prompt-composer.js";const E=4,R=6e4,F=6,N=5*6e4;class re{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,i,r,o={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],c=[],u=new Map,m=new Set,g=[...i].sort((t,a)=>H(t.sequence,a.sequence));for(const t of r){const a=this.deps.registry.getByRegistryKey(t.registryKey);!a?.binding||a.binding.roomId!==e||a.binding.status!=="active"&&a.binding.status!=="paused"&&a.binding.activationQueue.length}for(const t of r){const a=this.deps.registry.getByRegistryKey(t.registryKey);if(!a?.binding||a.binding.roomId!==e||a.binding.activationQueue.length===0&&!a.binding.activeActivation||(m.add(a.registryKey),a.binding.status!=="active"||a.binding.activationMode==="paused")||a.binding.activationQueue.length===0)continue;const l=this.deps.observations.getBySource(a);if(!l?.workspacePath||l.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(a.registryKey,o);for(const s of a.binding.activationQueue)d.push({registration:a,input:{params:{sessionId:a.nianSessionId,text:s.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:y(a.runtimeAdapter),sessionMode:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:l.workspacePath,agentSessionId:a.sourceSessionKey,queueMessageId:`room-activation-${a.binding.bindingId}-${s.causeMessageId}`,clientMessageId:`room-activation-${a.binding.bindingId}-${s.causeMessageId}`},context:{registryKey:a.registryKey,causeMessageId:s.causeMessageId,rootCauseMessageId:s.rootCauseMessageId,sequence:s.sequence,hopCount:s.hopCount}}})}for(const t of g){const a=new Set(W(t,r).map(l=>l.registration.registryKey));for(const l of r){const s=this.deps.registry.getByRegistryKey(l.registryKey);if(!s?.binding||s.binding.roomId!==e||s.binding.status!=="active"&&s.binding.status!=="paused"||o.agentsByIdentityId?.[s.binding.agentIdentityId]?.configurationPending||BigInt(t.sequence)<=BigInt(s.binding.lastActivatedSequence))continue;const f=u.get(s.registryKey)??[...s.binding.mentionContext??[]];if(u.set(s.registryKey,f),a.has(s.registryKey)){const p=(t.activationCause?.hopCount??0)+(t.author.principalType==="agent"?1:0),I=t.activationCause?.rootCauseMessageId??t.id;if(p>E)this.audit(s,t.id,I,p,"suppressed","max_hops");else{const h=this.deps.registry.claimActivation({registryKey:s.registryKey,windowMs:R,maxActivations:F,pauseMs:N,now:this.deps.now?.()});if(!h.allowed){h.newlyPaused&&this.audit(s,t.id,I,p,"suppressed","rate_limited"),c.push({registryKey:s.registryKey,sequence:t.sequence});continue}const b=this.deps.observations.getBySource(s);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const v=M([t]),C=s.binding.activationMode==="mention_only"?z([...f.map(_=>_.deliveryText),v]):v;this.latestContexts.set(s.registryKey,o),d.push({registration:s,input:{params:{sessionId:s.nianSessionId,text:C,agentType:y(s.runtimeAdapter),sessionMode:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:s.sourceSessionKey,queueMessageId:`room-activation-${s.binding.bindingId}-${t.id}`,clientMessageId:`room-activation-${s.binding.bindingId}-${t.id}`},context:{registryKey:s.registryKey,causeMessageId:t.id,rootCauseMessageId:I,sequence:t.sequence,hopCount:p}}}),s.binding.activationMode==="mention_only"&&u.set(s.registryKey,[])}}else s.binding.activationMode==="mention_only"&&t.kind!=="system"&&t.author.membershipId!==s.binding.agentMembershipId&&(f.some(p=>p.sequence===t.sequence)||f.push({sequence:t.sequence,deliveryText:M([t])}));c.push({registryKey:s.registryKey,sequence:t.sequence})}}await this.deps.enqueueConversationBatch(d.map(t=>t.input));for(const t of m)this.deps.registry.retireLegacyActivationState(t,this.deps.now?.()??new Date);for(const[t,a]of u)this.deps.registry.replaceMentionContext(t,a,this.deps.now?.()??new Date);for(const t of d)this.audit(t.registration,t.input.context.causeMessageId,t.input.context.rootCauseMessageId,t.input.context.hopCount,"queued","conversation_queue");for(const t of c)this.deps.registry.advanceCursor({registryKey:t.registryKey,cursor:"activated",sequence:t.sequence})}async prepareConversationTurn(e){const i=e.at(-1);if(!i)throw new Error("managed_room_queue_context_missing");const r=this.deps.registry.getByRegistryKey(i.registryKey);if(!r)throw new Error("managed_room_binding_unavailable");const o=await this.prepareManagedTurn(r,i.causeMessageId);return this.audit(r,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"activated","conversation_queue_dispatch"),o}async prepareDirectConversationTurn(e){const i=this.deps.registry.getByNianSessionId(e);return i?this.prepareManagedTurn(i):null}async prepareManagedTurn(e,i){if(!e.binding||e.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const r=this.deps.loadContext?await this.deps.loadContext(e):this.latestContexts.get(e.registryKey);if(!r)throw new Error("managed_room_prompt_context_unavailable");const o=r.agentsByIdentityId?.[e.binding.agentIdentityId];if(o?.configurationPending)throw new Error("managed_agent_configuration_pending");if(!o?.permissionPolicy)throw new Error("managed_agent_policy_missing");const d=o.sessionMode==="manager"?"manager":"standard",c=d==="manager"?{...o.managerConfig,defaultWorkerAgentType:o.managerConfig?.defaultWorkerAgentType??y(e.runtimeAdapter),defaultWorkerModelId:o.managerConfig?.defaultWorkerModelId??null}:null,u=this.deps.observations.getBySource(e);if(!u?.workspacePath||u.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const m=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:e,permissionPolicy:o.permissionPolicy,permissionConfirmation:o.permissionConfirmation,workDir:u.workspacePath}):await X({registration:e,permissionPolicy:o.permissionPolicy,permissionConfirmation:o.permissionConfirmation,workDir:u.workspacePath}),g=j(r,e,m),t=this.deps.issueManagedRoomContext(e.registryKey,i);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(t))throw new Error("managed_room_context_unavailable");return{agentType:y(e.runtimeAdapter),workDir:m.adapterPolicy.workDir,agentSessionId:e.sourceSessionKey,systemPrompt:g.systemPrompt,sessionMode:d,managerConfig:c,managerDefaultWorkerAgentType:c?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:c?.defaultWorkerModelId??null,roomContextRegistryKey:e.registryKey,managedRoomContextToken:t,managedAgentPolicy:m,managedRoomPromptSnapshot:g.snapshot}}audit(e,i,r,o,d,c){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:i,rootCauseMessageId:r,hopCount:o,outcome:d,reason:c,at:this.deps.now?.()})}}function M(n){const e=n.map(i=>{const r=i.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",o=i.attachments.length>0?`
2
- ${i.attachments.map(V).join(`
3
- `)}`:"";return{speaker:i.author.displayName,body:`${r}${o}`}});return w(e)}function V(n){return`\u9644\u4EF6\uFF1A${`${O(n.name)} \xB7 ${L(n.byteSize)}`}\uFF5C${n.mimeType}\uFF5CattachmentId=${n.attachmentId}`}function O(n){return String(n||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function L(n){const e=Number(n);if(!Number.isFinite(e)||e<0)return`${n} B`;if(e<1024)return`${e} B`;const i=["KB","MB","GB","TB"];let r=e,o=-1;do r/=1024,o+=1;while(r>=1024&&o<i.length-1);const d=r>=100?0:r>=10?1:2;return`${r.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${i[o]}`}function z(n){return w(n.map(Q))}function Q(n){const e=n.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const i=n.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(i)return{speaker:i[1],body:i[2]};const r=n.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(r)return{speaker:r[1],body:r[2]};const o=n.match(/^([^\n]+)\n([\s\S]*)$/);return o?{speaker:o[1],body:o[2]}:{speaker:"\u7FA4\u6210\u5458",body:n.trim()}}function w(n){if(n.length===1){const[e]=n;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
1
+ import{isAvailableBuiltinAgentType as k,managedAgentExecutionMode as S}from"@shennian/wire";import{getBuiltinAgentVersion as P,resolveBuiltinCommand as K}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as B,resolveCustomRuntimeExecutableFingerprint as x}from"../agents/custom-managed-runtime.js";import{loadConfig as $}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as T}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as q}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as W}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as D}from"./activation-policy.js";import{composeManagedRoomPrompt as E}from"./managed-prompt-composer.js";import{isCurrentRegistration as f}from"./registration-authority.js";const R=4,F=6e4,N=6,V=5*6e4;class ae{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,i,r,o={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],c=[],g=new Map,m=new Set,u=[...i].sort((n,a)=>U(n.sequence,a.sequence));for(const n of r){const a=this.deps.registry.getByRegistryKey(n.registryKey);!a?.binding||!f(this.deps.registry,n)||a.binding.roomId!==e||a.binding.status!=="active"&&a.binding.status!=="paused"&&a.binding.activationQueue.length}for(const n of r){const a=this.deps.registry.getByRegistryKey(n.registryKey);if(!a?.binding||!f(this.deps.registry,n)||a.binding.roomId!==e||a.binding.activationQueue.length===0&&!a.binding.activeActivation||(m.add(a.registryKey),a.binding.status!=="active"||a.binding.activationMode==="paused")||a.binding.activationQueue.length===0)continue;const l=this.deps.observations.getBySource(a);if(!l?.workspacePath||l.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(a.registryKey,o);for(const s of a.binding.activationQueue)d.push({registration:a,input:{params:{sessionId:a.nianSessionId,text:s.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:p(a.runtimeAdapter),sessionMode:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:l.workspacePath,agentSessionId:a.sourceSessionKey,queueMessageId:`room-activation-${a.binding.bindingId}-${s.causeMessageId}`,clientMessageId:`room-activation-${a.binding.bindingId}-${s.causeMessageId}`},context:{registryKey:a.registryKey,causeMessageId:s.causeMessageId,rootCauseMessageId:s.rootCauseMessageId,sequence:s.sequence,hopCount:s.hopCount}}})}for(const n of u){const a=new Set(D(n,r).map(l=>l.registration.registryKey));for(const l of r){const s=this.deps.registry.getByRegistryKey(l.registryKey);if(!s?.binding||!f(this.deps.registry,l)||s.binding.roomId!==e||s.binding.status!=="active"&&s.binding.status!=="paused"||o.agentsByIdentityId?.[s.binding.agentIdentityId]?.configurationPending||BigInt(n.sequence)<=BigInt(s.binding.lastActivatedSequence))continue;const I=g.get(s.registryKey)??[...s.binding.mentionContext??[]];if(g.set(s.registryKey,I),a.has(s.registryKey)){const y=(n.activationCause?.hopCount??0)+(n.author.principalType==="agent"?1:0),h=n.activationCause?.rootCauseMessageId??n.id;if(y>R)this.audit(s,n.id,h,y,"suppressed","max_hops");else{const v=this.deps.registry.claimActivation({registryKey:s.registryKey,causeMessageId:n.id,windowMs:F,maxActivations:N,pauseMs:V,now:this.deps.now?.()});if(!v.allowed){v.newlyPaused&&this.audit(s,n.id,h,y,"suppressed","rate_limited"),c.push({registryKey:s.registryKey,sequence:n.sequence});continue}const b=this.deps.observations.getBySource(s);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const M=w([n]),_=s.binding.activationMode==="mention_only"?Q([...I.map(A=>A.deliveryText),M]):M;this.latestContexts.set(s.registryKey,o),d.push({registration:s,input:{params:{sessionId:s.nianSessionId,text:_,agentType:p(s.runtimeAdapter),sessionMode:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:s.sourceSessionKey,queueMessageId:`room-activation-${s.binding.bindingId}-${n.id}`,clientMessageId:`room-activation-${s.binding.bindingId}-${n.id}`},context:{registryKey:s.registryKey,causeMessageId:n.id,rootCauseMessageId:h,sequence:n.sequence,hopCount:y}}}),s.binding.activationMode==="mention_only"&&g.set(s.registryKey,[])}}else s.binding.activationMode==="mention_only"&&n.kind!=="system"&&n.author.membershipId!==s.binding.agentMembershipId&&(I.some(y=>y.sequence===n.sequence)||I.push({sequence:n.sequence,deliveryText:w([n])}));c.push({registryKey:s.registryKey,sequence:n.sequence})}}await this.deps.enqueueConversationBatch(d.map(n=>n.input));for(const n of m)this.deps.registry.retireLegacyActivationState(n,this.deps.now?.()??new Date);for(const[n,a]of g)this.deps.registry.getByRegistryKey(n)?.lifecycleState!=="closed"&&this.deps.registry.replaceMentionContext(n,a,this.deps.now?.()??new Date);for(const n of d)this.audit(n.registration,n.input.context.causeMessageId,n.input.context.rootCauseMessageId,n.input.context.hopCount,"queued","conversation_queue");for(const n of c)this.deps.registry.getByRegistryKey(n.registryKey)?.lifecycleState!=="closed"&&this.deps.registry.advanceCursor({registryKey:n.registryKey,cursor:"activated",sequence:n.sequence})}async prepareConversationTurn(e){const i=e.at(-1);if(!i)throw new Error("managed_room_queue_context_missing");const r=this.deps.registry.getByRegistryKey(i.registryKey);if(!r)throw new Error("managed_room_binding_unavailable");const o=await this.prepareManagedTurn(r,i.causeMessageId);return this.audit(r,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"activated","conversation_queue_dispatch"),o}async prepareDirectConversationTurn(e){const i=this.deps.registry.getByNianSessionId(e);return i?this.prepareManagedTurn(i):null}async prepareManagedTurn(e,i){if(!f(this.deps.registry,e)||!e.binding||e.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const r=this.deps.loadContext?await this.deps.loadContext(e):this.latestContexts.get(e.registryKey);if(!r)throw new Error("managed_room_prompt_context_unavailable");const o=r.agentsByIdentityId?.[e.binding.agentIdentityId];if(o?.configurationPending)throw new Error("managed_agent_configuration_pending");if(!o?.permissionPolicy)throw new Error("managed_agent_policy_missing");const d=o.sessionMode==="manager"?"manager":"standard",c=d==="manager"?{...o.managerConfig,defaultWorkerAgentType:o.managerConfig?.defaultWorkerAgentType??p(e.runtimeAdapter),defaultWorkerModelId:o.managerConfig?.defaultWorkerModelId??null}:null,g=this.deps.observations.getBySource(e);if(!g?.workspacePath||g.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const m=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:e,permissionPolicy:o.permissionPolicy,permissionConfirmation:o.permissionConfirmation,workDir:g.workspacePath}):await G({registration:e,permissionPolicy:o.permissionPolicy,permissionConfirmation:o.permissionConfirmation,workDir:g.workspacePath});if(!f(this.deps.registry,e))throw new Error("managed_room_binding_unavailable");const u=X(r,e,m),n=this.deps.issueManagedRoomContext(e.registryKey,i);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(n))throw new Error("managed_room_context_unavailable");return{agentType:p(e.runtimeAdapter),workDir:m.adapterPolicy.workDir,agentSessionId:e.sourceSessionKey,systemPrompt:u.systemPrompt,sessionMode:d,managerConfig:c,managerDefaultWorkerAgentType:c?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:c?.defaultWorkerModelId??null,roomContextRegistryKey:e.registryKey,managedRoomContextToken:n,managedAgentPolicy:m,managedRoomPromptSnapshot:u.snapshot}}audit(e,i,r,o,d,c){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:i,rootCauseMessageId:r,hopCount:o,outcome:d,reason:c,at:this.deps.now?.()})}}function w(t){const e=t.map(i=>{const r=i.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",o=i.attachments.length>0?`
2
+ ${i.attachments.map(O).join(`
3
+ `)}`:"";return{speaker:i.author.displayName,body:`${r}${o}`}});return C(e)}function O(t){return`\u9644\u4EF6\uFF1A${`${L(t.name)} \xB7 ${z(t.byteSize)}`}\uFF5C${t.mimeType}\uFF5CattachmentId=${t.attachmentId}`}function L(t){return String(t||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function z(t){const e=Number(t);if(!Number.isFinite(e)||e<0)return`${t} B`;if(e<1024)return`${e} B`;const i=["KB","MB","GB","TB"];let r=e,o=-1;do r/=1024,o+=1;while(r>=1024&&o<i.length-1);const d=r>=100?0:r>=10?1:2;return`${r.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${i[o]}`}function Q(t){return C(t.map(j))}function j(t){const e=t.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const i=t.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(i)return{speaker:i[1],body:i[2]};const r=t.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(r)return{speaker:r[1],body:r[2]};const o=t.match(/^([^\n]+)\n([\s\S]*)$/);return o?{speaker:o[1],body:o[2]}:{speaker:"\u7FA4\u6210\u5458",body:t.trim()}}function C(t){if(t.length===1){const[e]=t;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
4
4
  ${e.body}`}return`[\u7FA4\u6D88\u606F]
5
- ${n.map(e=>`<${e.speaker}>
5
+ ${t.map(e=>`<${e.speaker}>
6
6
  ${e.body}`).join(`
7
7
 
8
- `)}`}function j(n,e,i){const r=e.binding?n.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!r)throw new Error("managed_room_prompt_incomplete");return D({runtimeAdapter:y(e.runtimeAdapter),agent:{displayName:r.displayName,description:r.description,systemPrompt:n.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId],configurationVersion:r.configurationVersion},room:{name:n.roomName,activationMode:e.binding.activationMode,members:n.members},runtimePermissions:{requestedPolicy:i.requestedPolicy,enforcement:i.enforcement,resolvedWorkDir:i.adapterPolicy.workDir}})}async function X(n){const e=y(n.registration.runtimeAdapter),i=A(e)?S(e):null,r=e.startsWith("custom:")?K().customAgents?.[e.slice(7)]:void 0;r&&x(e,r);const o=n.runtime?.version??(i?P(e,i):r?.caps.version),d=n.runtime?{platform:n.runtime.runtimePlatform??process.platform,isWsl:n.runtime.runtimeIsWsl??!1,executableFingerprint:n.runtime.executableFingerprint}:i?$(i):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:r?B(r):void 0},c={agentType:e,runtimeVersion:o,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},u=q(c),m=G({confirmation:n.permissionConfirmation,policy:n.permissionPolicy,runtimeAdapter:e,protection:u}),g=await T({...c,policy:n.permissionPolicy,workDir:n.workDir,hostFullAccessConfirmed:m&&n.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:m&&n.permissionConfirmation?.limitedProtection});if(!g.ok){const t=g.unsupportedCapabilities?.length?`:${g.unsupportedCapabilities.join(",")}`:"";throw new Error(`${g.errorCode}${t}`)}return g.value}function G(n){const{confirmation:e,policy:i,runtimeAdapter:r,protection:o}=n,d=k(i);return!!(e&&e.policyVersion===i.version&&e.runtimeAdapter===r&&e.runtimeVersion===o.runtimeVersion&&e.platform===o.platform&&e.executableFingerprint===o.executableFingerprint&&(e.executionMode??"restricted")===d&&e.hostFullAccess===(d==="native-full"||i.fileAccess==="host-full")&&e.limitedProtection===(d==="restricted"&&o.level!=="hard"))}function y(n){return n==="claude_code"?"claude":n}function H(n,e){const i=BigInt(n),r=BigInt(e);return i<r?-1:i>r?1:0}export{re as RoomActivationService,G as managedAgentPermissionConfirmationMatches,X as resolveManagedAgentPolicy};
8
+ `)}`}function X(t,e,i){const r=e.binding?t.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!r)throw new Error("managed_room_prompt_incomplete");return E({runtimeAdapter:p(e.runtimeAdapter),agent:{displayName:r.displayName,description:r.description,systemPrompt:t.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId],configurationVersion:r.configurationVersion},room:{name:t.roomName,activationMode:e.binding.activationMode,members:t.members},runtimePermissions:{requestedPolicy:i.requestedPolicy,enforcement:i.enforcement,resolvedWorkDir:i.adapterPolicy.workDir}})}async function G(t){const e=p(t.registration.runtimeAdapter),i=k(e)?K(e):null,r=e.startsWith("custom:")?$().customAgents?.[e.slice(7)]:void 0;r&&B(e,r);const o=t.runtime?.version??(i?P(e,i):r?.caps.version),d=t.runtime?{platform:t.runtime.runtimePlatform??process.platform,isWsl:t.runtime.runtimeIsWsl??!1,executableFingerprint:t.runtime.executableFingerprint}:i?T(i):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:r?x(r):void 0},c={agentType:e,runtimeVersion:o,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},g=W(c),m=H({confirmation:t.permissionConfirmation,policy:t.permissionPolicy,runtimeAdapter:e,protection:g}),u=await q({...c,policy:t.permissionPolicy,workDir:t.workDir,hostFullAccessConfirmed:m&&t.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:m&&t.permissionConfirmation?.limitedProtection});if(!u.ok){const n=u.unsupportedCapabilities?.length?`:${u.unsupportedCapabilities.join(",")}`:"";throw new Error(`${u.errorCode}${n}`)}return u.value}function H(t){const{confirmation:e,policy:i,runtimeAdapter:r,protection:o}=t,d=S(i);return!!(e&&e.policyVersion===i.version&&e.runtimeAdapter===r&&e.runtimeVersion===o.runtimeVersion&&e.platform===o.platform&&e.executableFingerprint===o.executableFingerprint&&(e.executionMode??"restricted")===d&&e.hostFullAccess===(d==="native-full"||i.fileAccess==="host-full")&&e.limitedProtection===(d==="restricted"&&o.level!=="hard"))}function p(t){return t==="claude_code"?"claude":t}function U(t,e){const i=BigInt(t),r=BigInt(e);return i<r?-1:i>r?1:0}export{ae as RoomActivationService,H as managedAgentPermissionConfirmationMatches,G as resolveManagedAgentPolicy};
@@ -1 +1 @@
1
- import{roomRuntimeAdapterForAgent as y}from"@shennian/wire";import h from"node:crypto";class u{deps;constructor(e){this.deps=e}async bind(e){const{machineId:s,machineToken:i}=this.deps.getMachineCredentials();if(!s||!i)throw new Error("Machine is not paired with Shennian.");const t=I(e.agentType),o=this.deps.hostProcessIdentity(),r=this.deps.registry.register({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.create({machineToken:i,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:t,sessionRegistryKey:r.registryKey,activationCapability:"managed",activationMode:e.activationMode??"auto_follow"});this.deps.registry.setBinding({registryKey:r.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode});const a=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,hostContextHash:g(o),workspaceHash:g(a),workspacePath:a,lifecycleState:"active",eventName:"ManagedBind"}),n}async restart(e){const{machineId:s,machineToken:i}=this.deps.getMachineCredentials();if(!s||!i)throw new Error("Machine is not paired with Shennian.");const t=I(e.agentType),o=this.deps.hostProcessIdentity(),r=this.deps.registry.register({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.restart({machineToken:i,bindingId:e.bindingId,previousNianSessionId:e.previousNianSessionId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:t,sessionRegistryKey:r.registryKey});this.deps.registry.setBinding({registryKey:r.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode}),this.deps.registry.advanceCursor({registryKey:r.registryKey,cursor:"delivered",sequence:n.resumeSequence}),this.deps.registry.advanceCursor({registryKey:r.registryKey,cursor:"activated",sequence:n.resumeSequence});for(const c of this.deps.registry.list())c.registryKey!==r.registryKey&&c.nianSessionId===e.previousNianSessionId&&c.binding?.bindingId===e.bindingId&&this.deps.registry.detachBinding(c.registryKey);const a=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,hostContextHash:g(o),workspaceHash:g(a),workspacePath:a,lifecycleState:"active",eventName:"ManagedRestart"}),n}async resolveServerOwnedWorkspace(e){const s=await this.deps.bindingClient.status(e),i=s.managedWorkspacePath?.trim(),t=s.agent?.sessionMode;if(s.agentIdentityId!==e.agentIdentityId||t!=="standard"&&t!=="manager"||!i)throw new Error("managed_room_workspace_unavailable");return i}}function g(d){return h.createHash("sha256").update(d,"utf8").digest("hex")}function I(d){const e=y(d);if(e)return e;throw new Error(`Agent ${d} does not support managed Room binding.`)}export{u as TrustedManagedRoomBinder};
1
+ import{roomRuntimeAdapterForAgent as I}from"@shennian/wire";import y from"node:crypto";class b{deps;constructor(e){this.deps=e}async bind(e){const{machineId:t,machineToken:i}=this.deps.getMachineCredentials();if(!t||!i)throw new Error("Machine is not paired with Shennian.");const s=g(e.agentType),o=this.deps.hostProcessIdentity(),d=this.deps.registry.register({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.create({machineToken:i,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:s,sessionRegistryKey:d.registryKey,activationCapability:"managed",activationMode:e.activationMode??"auto_follow"});this.deps.registry.setBinding({registryKey:d.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode,replaceOwner:!0});const r=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,hostContextHash:c(o),workspaceHash:c(r),workspacePath:r,lifecycleState:"active",eventName:"ManagedBind"}),n}async restart(e){const{machineId:t,machineToken:i}=this.deps.getMachineCredentials();if(!t||!i)throw new Error("Machine is not paired with Shennian.");const s=g(e.agentType),o=this.deps.hostProcessIdentity(),d=this.deps.registry.register({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.restart({machineToken:i,bindingId:e.bindingId,previousNianSessionId:e.previousNianSessionId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:s,sessionRegistryKey:d.registryKey});this.deps.registry.setBinding({registryKey:d.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode,replaceOwner:!0,initialSequence:n.resumeSequence});const r=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,hostContextHash:c(o),workspaceHash:c(r),workspacePath:r,lifecycleState:"active",eventName:"ManagedRestart"}),n}async resolveServerOwnedWorkspace(e){const t=await this.deps.bindingClient.status(e),i=t.managedWorkspacePath?.trim(),s=t.agent?.sessionMode;if(t.agentIdentityId!==e.agentIdentityId||s!=="standard"&&s!=="manager"||!i)throw new Error("managed_room_workspace_unavailable");return i}}function c(a){return y.createHash("sha256").update(a,"utf8").digest("hex")}function g(a){const e=I(a);if(e)return e;throw new Error(`Agent ${a} does not support managed Room binding.`)}export{b as TrustedManagedRoomBinder};
@@ -24,6 +24,7 @@ export declare class RoomMirrorSyncService {
24
24
  setTimeout?: (handler: () => void, delayMs: number) => Timer;
25
25
  clearTimeout?: (timer: Timer) => void;
26
26
  onMessages?: (roomId: string, messages: RoomMessageView[], registrations: RoomSessionRegistration[], context: RoomActivationContext) => Promise<void>;
27
+ isSessionDeleted?: (sessionId: string) => boolean;
27
28
  onBindingPaused?: (registration: RoomSessionRegistration) => void;
28
29
  });
29
30
  start(): void;
@@ -31,6 +32,7 @@ export declare class RoomMirrorSyncService {
31
32
  runOnce(): Promise<void>;
32
33
  private syncRoom;
33
34
  private pullPages;
35
+ private retire;
34
36
  private arm;
35
37
  }
36
38
  export {};
@@ -1 +1 @@
1
- import{RoomActionHttpError as m}from"./room-action-client.js";import p from"node:crypto";class v{deps;timer=null;running=!1;started=!1;constructor(i){this.deps=i}start(){this.started||(this.started=!0,this.arm(this.deps.initialDelayMs??0))}stop(){this.started=!1,this.timer&&(this.deps.clearTimeout??clearTimeout)(this.timer),this.timer=null}async runOnce(){if(this.running)return;const i=this.deps.getMachineCredentials().machineToken?.trim();if(i){this.running=!0;try{const s=l(this.deps.registry.list());for(const[r,u]of s)await this.syncRoom(r,u,i)}finally{this.running=!1}}}async syncRoom(i,s,r){const u=s.filter(e=>e.binding&&(e.binding.status==="active"||e.binding.status==="paused"));if(u.length===0)return;let g=0,o=null,n=null;for(const e of u){const d=e.binding,c={machineToken:r,roomId:i,bindingId:d.bindingId};try{const t=await this.deps.actionClient.status(c);t.activationCapability==="managed"&&t.managedWorkspacePath&&!this.deps.observations?.getBySource(e)&&this.deps.observations?.observe({machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,hostContextHash:h(e.hostProcessIdentity),workspaceHash:h(t.managedWorkspacePath),workspacePath:t.managedWorkspacePath,lifecycleState:"active",eventName:"ManagedSync"});const y=this.deps.registry.setBinding({registryKey:e.registryKey,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:t.status,activationMode:t.activationMode});if((t.status==="paused"||t.activationMode==="paused")&&this.deps.onBindingPaused?.(y),t.status==="detached"){g+=1;continue}if(t.status==="pending_approval")continue;o??=c,n??={roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:{},agentSystemPromptsByIdentityId:{}},t.agent&&(n.agentsByIdentityId[t.agentIdentityId]=t.agent,n.agentSystemPromptsByIdentityId[t.agentIdentityId]=t.agent.systemPrompt??"")}catch(t){if(f(t)){this.deps.registry.detachBinding(e.registryKey),g+=1;continue}return}}if(o){const e=s.map(d=>this.deps.registry.getByRegistryKey(d.registryKey)??d);await this.pullPages(o,i,e,n);return}}async pullPages(i,s,r,u){const g=this.deps.maxPagesPerRun??100;let o=r.map(n=>n.binding?.lastDeliveredSequence??"0").reduce((n,e)=>BigInt(n)<BigInt(e)?n:e,"0");for(let n=0;n<g;n+=1){const e=await this.deps.actionClient.read({...i,afterSequence:o,limit:100});this.deps.onMessages&&await this.deps.onMessages(s,e.messages,r,u??{roomName:e.roomName});for(const d of r){const c=this.deps.registry.getByRegistryKey(d.registryKey);c?.binding&&(c.binding.status==="active"||c.binding.status==="paused")&&BigInt(e.nextSequence)>BigInt(c.binding.lastDeliveredSequence)&&this.deps.registry.advanceCursor({registryKey:d.registryKey,cursor:"delivered",sequence:e.nextSequence})}if(!e.hasMore)return;if(e.nextSequence===o)throw new m("room_sync_no_progress","Room mirror synchronization made no progress.");o=e.nextSequence}}arm(i){if(!this.started||this.timer)return;const s=this.deps.setTimeout??setTimeout;this.timer=s(()=>{this.timer=null,this.runOnce().catch(()=>{}).finally(()=>{this.started&&this.arm(this.deps.intervalMs??5e3)})},i),this.timer.unref?.()}}function h(a){return p.createHash("sha256").update(a,"utf8").digest("hex")}function l(a){const i=new Map;for(const s of a){if(!s.binding||s.lifecycleState==="closed")continue;const r=i.get(s.binding.roomId)??[];r.push(s),i.set(s.binding.roomId,r)}return i}function f(a){return a instanceof m&&(a.code==="room_binding_unavailable"||a.code==="room_action_http_403")}export{v as RoomMirrorSyncService};
1
+ import{RoomActionHttpError as h}from"./room-action-client.js";import p from"node:crypto";import{isCurrentRegistration as m,matchesRoomOwner as f}from"./registration-authority.js";class P{deps;timer=null;running=!1;started=!1;constructor(i){this.deps=i}start(){this.started||(this.started=!0,this.arm(this.deps.initialDelayMs??0))}stop(){this.started=!1,this.timer&&(this.deps.clearTimeout??clearTimeout)(this.timer),this.timer=null}async runOnce(){if(this.running)return;const i=this.deps.getMachineCredentials().machineToken?.trim();if(i){this.running=!0;try{for(const n of this.deps.registry.list())n.lifecycleState!=="closed"&&this.deps.isSessionDeleted?.(n.nianSessionId)&&this.retire(n);const s=I(this.deps.registry.list());for(const[n,g]of s)await this.syncRoom(n,g,i)}finally{this.running=!1}}}async syncRoom(i,s,n){const g=s.filter(e=>e.binding&&(e.binding.status==="active"||e.binding.status==="paused"));if(g.length===0)return;let u=0,d=null,r=null;for(const e of g){if(!m(this.deps.registry,e))continue;const o=e.binding,c={machineToken:n,roomId:i,bindingId:o.bindingId};try{const t=await this.deps.actionClient.status(c);if(!m(this.deps.registry,e))continue;if(!f(e,t)||this.deps.isSessionDeleted?.(e.nianSessionId)){this.retire(e);continue}t.activationCapability==="managed"&&t.managedWorkspacePath&&!this.deps.observations?.getBySource(e)&&this.deps.observations?.observe({machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,hostContextHash:y(e.hostProcessIdentity),workspaceHash:y(t.managedWorkspacePath),workspacePath:t.managedWorkspacePath,lifecycleState:"active",eventName:"ManagedSync"});const l=this.deps.registry.setBinding({registryKey:e.registryKey,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:t.status,activationMode:t.activationMode});if((t.status==="paused"||t.activationMode==="paused")&&this.deps.onBindingPaused?.(l),t.status==="detached"){u+=1;continue}if(t.status==="pending_approval")continue;d??=c,r??={roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:{},agentSystemPromptsByIdentityId:{}},t.agent&&(r.agentsByIdentityId[t.agentIdentityId]=t.agent,r.agentSystemPromptsByIdentityId[t.agentIdentityId]=t.agent.systemPrompt??"")}catch(t){if(b(t)){this.deps.registry.detachBinding(e.registryKey),u+=1;continue}return}}if(d){const e=s.filter(o=>m(this.deps.registry,o)).map(o=>this.deps.registry.getByRegistryKey(o.registryKey)??o);await this.pullPages(d,i,e,r);return}}async pullPages(i,s,n,g){if(n.length===0)return;const u=this.deps.maxPagesPerRun??100;let d=n.map(r=>r.binding?.lastDeliveredSequence??"0").reduce((r,e)=>BigInt(r)<BigInt(e)?r:e);for(let r=0;r<u;r+=1){const e=await this.deps.actionClient.read({...i,afterSequence:d,limit:100}),o=n.filter(c=>m(this.deps.registry,c));if(o.length===0)return;this.deps.onMessages&&await this.deps.onMessages(s,e.messages,o,g??{roomName:e.roomName});for(const c of o){const t=this.deps.registry.getByRegistryKey(c.registryKey);t?.binding&&(t.binding.status==="active"||t.binding.status==="paused")&&BigInt(e.nextSequence)>BigInt(t.binding.lastDeliveredSequence)&&this.deps.registry.advanceCursor({registryKey:c.registryKey,cursor:"delivered",sequence:e.nextSequence})}if(!e.hasMore)return;if(e.nextSequence===d)throw new h("room_sync_no_progress","Room mirror synchronization made no progress.");d=e.nextSequence}}retire(i){const s=this.deps.registry.updateLifecycle(i.registryKey,"closed");this.deps.onBindingPaused?.(s)}arm(i){if(!this.started||this.timer)return;const s=this.deps.setTimeout??setTimeout;this.timer=s(()=>{this.timer=null,this.runOnce().catch(()=>{console.warn("[room.sync] delivery failed; retrying on next poll")}).finally(()=>{this.started&&this.arm(this.deps.intervalMs??5e3)})},i),this.timer.unref?.()}}function y(a){return p.createHash("sha256").update(a,"utf8").digest("hex")}function I(a){const i=new Map;for(const s of a){if(!s.binding||s.lifecycleState==="closed")continue;const n=i.get(s.binding.roomId)??[];n.push(s),i.set(s.binding.roomId,n)}return i}function b(a){return a instanceof h&&(a.code==="room_binding_unavailable"||a.code==="room_action_http_403")}export{P as RoomMirrorSyncService};
@@ -0,0 +1,4 @@
1
+ import type { RoomSessionRegistration, RoomSessionRegistry } from './session-registry.js';
2
+ import type { RemoteRoomAgentStatus } from './room-action-client.js';
3
+ export declare function isCurrentRegistration(registry: RoomSessionRegistry, snapshot: RoomSessionRegistration): boolean;
4
+ export declare function matchesRoomOwner(registration: RoomSessionRegistration, remote: Pick<RemoteRoomAgentStatus, 'nianSessionId' | 'sessionRegistryKey'>): boolean;
@@ -0,0 +1 @@
1
+ function s(e,n){const i=e.getByRegistryKey(n.registryKey);return!!(i&&i.lifecycleState!=="closed"&&i.registrationEpoch===n.registrationEpoch&&i.binding?.bindingId===n.binding?.bindingId&&(i.binding?.status==="active"||i.binding?.status==="paused"))}function t(e,n){return(n.nianSessionId===void 0||n.nianSessionId===e.nianSessionId)&&(n.sessionRegistryKey===void 0||n.sessionRegistryKey===e.registryKey)}export{s as isCurrentRegistration,t as matchesRoomOwner};
@@ -7,6 +7,9 @@ type BindingTarget = {
7
7
  signal?: AbortSignal;
8
8
  };
9
9
  export type RemoteRoomAgentStatus = {
10
+ /** Additive authority fields; absent on older servers. */
11
+ nianSessionId?: string | null;
12
+ sessionRegistryKey?: string;
10
13
  bindingId: string;
11
14
  roomId: string;
12
15
  agentIdentityId: string;
@@ -1 +1 @@
1
- import{isAvailableAgentType as j,parseManagedAgentPermissionPolicy as _}from"@shennian/wire";import M from"node:path";class c extends Error{code;constructor(e,n){super(n),this.code=e,this.name="RoomActionHttpError"}}class O{fetchImpl;baseUrl;constructor(e,n=fetch){this.fetchImpl=n,this.baseUrl=e.replace(/\/+$/,"")}async createOperatorRoom(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/operator/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({name:e.name}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(n);if(!n.ok)throw f(n.status,t);return N(t,e,this.baseUrl)}async resolveNativeSession(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/resolve-session`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(n);if(!n.ok)throw f(n.status,t);(!t||typeof t!="object")&&r();const o=t;return(typeof o.nianSessionId!="string"||!/^[A-Za-z0-9_.:@/-]{1,512}$/.test(o.nianSessionId)||typeof o.created!="boolean")&&r(),o}async createRoom(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,name:e.name,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(n);if(!n.ok)throw f(n.status,t);return R(t,e,this.baseUrl)}async joinRoom(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/join`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,inviteToken:e.inviteToken,message:e.message,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(n);if(!n.ok)throw f(n.status,t);return $(t,e,this.baseUrl)}async status(e){const n=await this.request(e,"status");return q(n,e)}async getAttachmentCapability(e){const n=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/download-capability`);(!n||typeof n!="object")&&r();const t=n;return(t.attachmentId!==e.attachmentId||t.roomId!==e.roomId||typeof t.safeName!="string"||!I(t.safeName)||typeof t.mimeType!="string"||t.mimeType.length<1||t.mimeType.length>191||typeof t.byteSize!="string"||!m(t.byteSize)||typeof t.sha256!="string"||!/^[a-f0-9]{64}$/.test(t.sha256)||!S(t.downloadUrl)||typeof t.expiresAt!="string"||!Number.isFinite(Date.parse(t.expiresAt)))&&r(),t}async initializeAttachment(e){const n=await this.request(e,"attachments/init",{method:"POST",body:JSON.stringify({clientUploadId:e.clientUploadId,name:e.name,mediaKind:e.mediaKind,mimeType:e.mimeType,byteSize:e.byteSize,sha256:e.sha256})});(!n||typeof n!="object")&&r();const t=n,o=t.attachment,s=t.upload;return(!o||typeof o.id!="string"||o.roomId!==e.roomId||typeof o.name!="string"||!I(o.name)||o.mimeType!==e.mimeType||o.byteSize!==String(e.byteSize)||o.sha256!==e.sha256||o.status!=="pending"&&o.status!=="uploaded"||!s||s.method!=="PUT"||!S(s.url)||!s.headers||typeof s.headers!="object"||s.headers["content-type"]!==e.mimeType||s.headers["x-oss-meta-sha256"]!==e.sha256||typeof s.expiresAt!="string"||!Number.isFinite(Date.parse(s.expiresAt))||Date.parse(s.expiresAt)<=Date.now()||typeof t.idempotent!="boolean")&&r(),t}async completeAttachment(e){const n=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/complete`,{method:"POST"});(!n||typeof n!="object")&&r();const t=n;typeof t.idempotent!="boolean"&&r();const o=t.attachment;return(!o||o.attachmentId!==e.attachmentId||o.mediaKind!=="image"&&o.mediaKind!=="audio"&&o.mediaKind!=="file"||typeof o.name!="string"||typeof o.mimeType!="string"||typeof o.byteSize!="string"||!m(o.byteSize)||typeof o.sha256!="string"||!/^[a-f0-9]{64}$/.test(o.sha256)||o.status!=="ready")&&r(),{attachment:o,idempotent:t.idempotent}}async read(e){if(e.beforeSequence&&e.afterSequence)throw new c("ambiguous_sequence_cursor","beforeSequence and afterSequence are mutually exclusive.");const n=new URLSearchParams({limit:String(e.limit)});e.beforeSequence&&n.set("before",e.beforeSequence),e.afterSequence&&n.set("after",e.afterSequence);const t=await this.request(e,`messages?${n}`);(!t||typeof t!="object")&&r();const o=t;return(!Array.isArray(o.messages)||typeof o.nextSequence!="string"||!m(o.nextSequence)||typeof o.hasMore!="boolean"||typeof o.historyFromSequence!="string"||!m(o.historyFromSequence)||typeof o.roomName!="string")&&r(),{messages:o.messages.map(a=>h(a,e.roomId)),nextSequence:o.nextSequence,hasMore:o.hasMore,historyFromSequence:o.historyFromSequence,roomName:o.roomName}}async send(e){const n=await this.request(e,"messages",{method:"POST",body:JSON.stringify({clientMessageId:e.clientMessageId,text:e.text,mentionMembershipIds:e.mentionMembershipIds,replyToMessageId:e.replyToMessageId,activationCauseMessageId:e.activationCauseMessageId,attachmentIds:e.attachmentIds})});(!n||typeof n!="object")&&r();const t=n;return typeof t.idempotent!="boolean"&&r(),{message:h(t.message,e.roomId),idempotent:t.idempotent}}async mode(e){const n=await this.request(e,"mode",{method:"PATCH",body:JSON.stringify({activationMode:e.activationMode})});(!n||typeof n!="object")&&r();const t=n;return(t.bindingId!==e.bindingId||t.roomId!==e.roomId||t.status!=="active"&&t.status!=="paused"||!v(t.activationMode)||t.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&r(),t}async pause(e){const n=await this.request(e,"pause",{method:"POST",body:JSON.stringify({reason:e.reason})});(!n||typeof n!="object")&&r();const t=n;return(t.bindingId!==e.bindingId||t.roomId!==e.roomId||t.status!=="paused"||t.activationMode!=="paused"||typeof t.idempotent!="boolean")&&r(),{...t,message:h(t.message,e.roomId)}}async request(e,n,t={}){let o;const s=C(e.signal,1e4);try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/${n}`,{...t,headers:{authorization:`Bearer ${e.machineToken}`,...t.body?{"content-type":"application/json"}:{}},signal:s.signal})}catch{throw e.signal?.aborted?new c("managed_room_context_revoked","Managed Room context was revoked."):new c("room_action_server_unavailable","Shennian Room service is unavailable.")}finally{s.dispose()}if(!o.ok){let a=`room_action_http_${o.status}`;try{const d=await o.json();typeof d.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(d.error)&&(a=d.error)}catch{}throw new c(a,"Shennian Room action failed.")}try{return await o.json()}catch{r()}}}function C(i,e){const n=new AbortController,t=()=>n.abort();i?.aborted?n.abort():i?.addEventListener("abort",t,{once:!0});const o=setTimeout(()=>n.abort(),e);return o.unref?.(),{signal:n.signal,dispose:()=>{clearTimeout(o),i?.removeEventListener("abort",t)}}}function q(i,e){(!i||typeof i!="object")&&r();const n=i,t=n.room,o=n.agent;(n.bindingId!==e.bindingId||n.roomId!==e.roomId||typeof n.agentIdentityId!="string"||n.agentMembershipId!==null&&typeof n.agentMembershipId!="string"||n.status!=="pending_approval"&&n.status!=="active"&&n.status!=="paused"&&n.status!=="detached"||!v(n.activationMode)||n.activationCapability!==void 0&&n.activationCapability!=="managed"&&n.activationCapability!=="idle_resume"&&n.activationCapability!=="notify_only"||n.managedWorkspacePath!==void 0&&n.managedWorkspacePath!==null&&(typeof n.managedWorkspacePath!="string"||n.managedWorkspacePath.length>4096||!M.isAbsolute(n.managedWorkspacePath))||typeof n.lastDeliveredSequence!="string"||!m(n.lastDeliveredSequence)||typeof n.lastActivatedSequence!="string"||!m(n.lastActivatedSequence)||n.appliedConfigurationVersion!==void 0&&(!Number.isSafeInteger(n.appliedConfigurationVersion)||Number(n.appliedConfigurationVersion)<1)||n.pendingConfigurationVersion!==void 0&&n.pendingConfigurationVersion!==null&&(!Number.isSafeInteger(n.pendingConfigurationVersion)||Number(n.pendingConfigurationVersion)<1)||!t||typeof t!="object"||o!==void 0&&(!o||typeof o!="object"))&&r(),(n.status==="pending_approval"&&n.agentMembershipId!==null||(n.status==="active"||n.status==="paused")&&typeof n.agentMembershipId!="string")&&r();const s=t,a=o,d=a?_(a.permissionPolicy):null,g=a?T(a.permissionConfirmation):null,w=a?Object.prototype.hasOwnProperty.call(a,"permissionConfirmation"):!1,l=s.members;return(a!==void 0&&(typeof a.displayName!="string"||!a.displayName.trim()||typeof a.description!="string"||a.sessionMode!=="standard"&&a.sessionMode!=="manager"||a.configurationVersion!==void 0&&(!Number.isSafeInteger(a.configurationVersion)||Number(a.configurationVersion)<1)||a.configurationPending!==void 0&&typeof a.configurationPending!="boolean"||!d||!w||a.permissionConfirmation!==null&&!g)||typeof s.name!="string"||typeof s.status!="string"||s.memberCount!==void 0&&(typeof s.memberCount!="number"||!Number.isInteger(s.memberCount)||s.memberCount<0)||typeof s.lastSequence!="string"||!m(s.lastSequence)||l!==void 0&&(!Array.isArray(l)||l.some(u=>{if(!u||typeof u!="object")return!0;const y=u;return typeof y.displayName!="string"||y.principalType!=="human"&&y.principalType!=="agent"})))&&r(),{...n,...a&&d?{agent:{...a,permissionPolicy:d,permissionConfirmation:g}}:{}}}function T(i){if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i;return!Number.isSafeInteger(e.policyVersion)||Number(e.policyVersion)<1||typeof e.runtimeAdapter!="string"||!j(e.runtimeAdapter)||typeof e.runtimeVersion!="string"||!e.runtimeVersion.trim()||typeof e.platform!="string"||!e.platform.trim()||typeof e.executableFingerprint!="string"||!/^sha256:[a-f0-9]{64}$/.test(e.executableFingerprint)||typeof e.hostFullAccess!="boolean"||typeof e.limitedProtection!="boolean"||typeof e.confirmedAt!="string"||!Number.isFinite(Date.parse(e.confirmedAt))?null:{policyVersion:Number(e.policyVersion),runtimeAdapter:e.runtimeAdapter,runtimeVersion:e.runtimeVersion,platform:e.platform,executableFingerprint:e.executableFingerprint,executionMode:e.executionMode==="native-full"?"native-full":"restricted",hostFullAccess:e.hostFullAccess,limitedProtection:e.limitedProtection,confirmedAt:e.confirmedAt}}function h(i,e){(!i||typeof i!="object")&&r();const n=i;if((typeof n.id!="string"||n.roomId!==e||typeof n.sequence!="string"||!m(n.sequence)||!n.author||typeof n.author.membershipId!="string"||!Array.isArray(n.attachments)||typeof n.createdAt!="string")&&r(),n.activationCause!=null){const t=n.activationCause;(typeof t.causeMessageId!="string"||typeof t.rootCauseMessageId!="string"||!Number.isInteger(t.hopCount)||t.hopCount<1||t.hopCount>32)&&r()}return n}function R(i,e,n){(!i||typeof i!="object")&&r();const t=i,o=t.room,s=t.agentIdentity,a=t.binding;return(t.outcome!=="created"||!o||typeof o.id!="string"||o.name!==e.name||o.visibility!=="private"||o.joinPolicy!=="open"||!b(o.canonicalUrl,n,`/spaces/${o.id}`)||!P(o.inviteUrl,n,o.id)||!s||typeof s.id!="string"||s.displayName!==e.agentAlias||!a||typeof a.id!="string"||a.roomId!==o.id||a.agentIdentityId!==s.id||typeof a.agentMembershipId!="string"||a.status!=="active"&&a.status!=="paused"||a.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&r(),t}function N(i,e,n){(!i||typeof i!="object")&&r();const t=i,o=t.room;return(t.outcome!=="created"||!o||typeof o.id!="string"||o.name!==e.name||o.visibility!=="private"||o.joinPolicy!=="open"||!b(o.canonicalUrl,n,`/spaces/${o.id}`))&&r(),t}function $(i,e,n){(!i||typeof i!="object")&&r();const t=i,o=t.room,s=t.agentIdentity,a=t.binding;return(t.outcome!=="joined"&&t.outcome!=="pending_approval"||!o||typeof o.id!="string"||typeof o.name!="string"||!b(o.canonicalUrl,n,`/spaces/${o.id}`)||!s||typeof s.id!="string"||s.displayName!==e.agentAlias||!a||typeof a.id!="string"||a.roomId!==o.id||a.agentIdentityId!==s.id||a.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&r(),(t.outcome==="pending_approval"&&(a.status!=="pending_approval"||a.agentMembershipId!==null||typeof t.joinRequestId!="string")||t.outcome==="joined"&&(a.status!=="active"&&a.status!=="paused"||typeof a.agentMembershipId!="string"||t.joinRequestId!==void 0))&&r(),t}function m(i){return/^(0|[1-9]\d{0,19})$/.test(i)}function I(i){return i.length<=255&&i===i.trim()&&i!=="."&&i!==".."&&!/[\\/\0-\x1f\x7f]/.test(i)}function S(i){if(typeof i!="string")return!1;try{const e=new URL(i);return e.protocol==="https:"&&!e.username&&!e.password&&!e.hash}catch{return!1}}function v(i){return i==="auto_follow"||i==="mention_only"||i==="paused"}function r(){throw new c("invalid_room_action_response","Invalid Room action response.")}async function p(i){try{return await i.json()}catch{r()}}function f(i,e){let n=`room_action_http_${i}`;if(e&&typeof e=="object"){const t=e.error;typeof t=="string"&&/^[a-z0-9_-]{1,80}$/.test(t)&&(n=t)}return new c(n,"Shennian Room action failed.")}function b(i,e,n){return A(i,e,t=>t.pathname===n)}function P(i,e,n){if(typeof i!="string")return!1;try{const t=new URL(i);return A(`${t.origin}${t.pathname}`,e,o=>o.pathname===`/spaces/${n}`)?t.searchParams.size===1&&/^[A-Za-z0-9_-]{32,256}$/.test(t.searchParams.get("invite")??"")&&!t.hash&&!t.username&&!t.password:!1}catch{return!1}}function A(i,e,n){if(typeof i!="string")return!1;try{const t=new URL(i),o=new URL(e),s=t.origin===o.origin,a=o.protocol==="http:"&&t.protocol==="http:"&&t.hostname===o.hostname,d=t.protocol==="https:"&&t.port===""&&t.hostname==="app.shennian.net";return(s||a||d)&&!t.username&&!t.password&&!t.search&&!t.hash&&n(t)}catch{return!1}}export{O as HttpRoomActionClient,c as RoomActionHttpError};
1
+ import{isAvailableAgentType as j,parseManagedAgentPermissionPolicy as _}from"@shennian/wire";import M from"node:path";class c extends Error{code;constructor(e,t){super(t),this.code=e,this.name="RoomActionHttpError"}}class O{fetchImpl;baseUrl;constructor(e,t=fetch){this.fetchImpl=t,this.baseUrl=e.replace(/\/+$/,"")}async createOperatorRoom(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/operator/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({name:e.name}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const n=await p(t);if(!t.ok)throw f(t.status,n);return N(n,e,this.baseUrl)}async resolveNativeSession(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/resolve-session`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const n=await p(t);if(!t.ok)throw f(t.status,n);(!n||typeof n!="object")&&r();const o=n;return(typeof o.nianSessionId!="string"||!/^[A-Za-z0-9_.:@/-]{1,512}$/.test(o.nianSessionId)||typeof o.created!="boolean")&&r(),o}async createRoom(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,name:e.name,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const n=await p(t);if(!t.ok)throw f(t.status,n);return T(n,e,this.baseUrl)}async joinRoom(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/join`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,inviteToken:e.inviteToken,message:e.message,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const n=await p(t);if(!t.ok)throw f(t.status,n);return $(n,e,this.baseUrl)}async status(e){const t=await this.request(e,"status");return q(t,e)}async getAttachmentCapability(e){const t=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/download-capability`);(!t||typeof t!="object")&&r();const n=t;return(n.attachmentId!==e.attachmentId||n.roomId!==e.roomId||typeof n.safeName!="string"||!I(n.safeName)||typeof n.mimeType!="string"||n.mimeType.length<1||n.mimeType.length>191||typeof n.byteSize!="string"||!m(n.byteSize)||typeof n.sha256!="string"||!/^[a-f0-9]{64}$/.test(n.sha256)||!S(n.downloadUrl)||typeof n.expiresAt!="string"||!Number.isFinite(Date.parse(n.expiresAt)))&&r(),n}async initializeAttachment(e){const t=await this.request(e,"attachments/init",{method:"POST",body:JSON.stringify({clientUploadId:e.clientUploadId,name:e.name,mediaKind:e.mediaKind,mimeType:e.mimeType,byteSize:e.byteSize,sha256:e.sha256})});(!t||typeof t!="object")&&r();const n=t,o=n.attachment,a=n.upload;return(!o||typeof o.id!="string"||o.roomId!==e.roomId||typeof o.name!="string"||!I(o.name)||o.mimeType!==e.mimeType||o.byteSize!==String(e.byteSize)||o.sha256!==e.sha256||o.status!=="pending"&&o.status!=="uploaded"||!a||a.method!=="PUT"||!S(a.url)||!a.headers||typeof a.headers!="object"||a.headers["content-type"]!==e.mimeType||a.headers["x-oss-meta-sha256"]!==e.sha256||typeof a.expiresAt!="string"||!Number.isFinite(Date.parse(a.expiresAt))||Date.parse(a.expiresAt)<=Date.now()||typeof n.idempotent!="boolean")&&r(),n}async completeAttachment(e){const t=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/complete`,{method:"POST"});(!t||typeof t!="object")&&r();const n=t;typeof n.idempotent!="boolean"&&r();const o=n.attachment;return(!o||o.attachmentId!==e.attachmentId||o.mediaKind!=="image"&&o.mediaKind!=="audio"&&o.mediaKind!=="file"||typeof o.name!="string"||typeof o.mimeType!="string"||typeof o.byteSize!="string"||!m(o.byteSize)||typeof o.sha256!="string"||!/^[a-f0-9]{64}$/.test(o.sha256)||o.status!=="ready")&&r(),{attachment:o,idempotent:n.idempotent}}async read(e){if(e.beforeSequence&&e.afterSequence)throw new c("ambiguous_sequence_cursor","beforeSequence and afterSequence are mutually exclusive.");const t=new URLSearchParams({limit:String(e.limit)});e.beforeSequence&&t.set("before",e.beforeSequence),e.afterSequence&&t.set("after",e.afterSequence);const n=await this.request(e,`messages?${t}`);(!n||typeof n!="object")&&r();const o=n;return(!Array.isArray(o.messages)||typeof o.nextSequence!="string"||!m(o.nextSequence)||typeof o.hasMore!="boolean"||typeof o.historyFromSequence!="string"||!m(o.historyFromSequence)||typeof o.roomName!="string")&&r(),{messages:o.messages.map(s=>h(s,e.roomId)),nextSequence:o.nextSequence,hasMore:o.hasMore,historyFromSequence:o.historyFromSequence,roomName:o.roomName}}async send(e){const t=await this.request(e,"messages",{method:"POST",body:JSON.stringify({clientMessageId:e.clientMessageId,text:e.text,mentionMembershipIds:e.mentionMembershipIds,replyToMessageId:e.replyToMessageId,activationCauseMessageId:e.activationCauseMessageId,attachmentIds:e.attachmentIds})});(!t||typeof t!="object")&&r();const n=t;return typeof n.idempotent!="boolean"&&r(),{message:h(n.message,e.roomId),idempotent:n.idempotent}}async mode(e){const t=await this.request(e,"mode",{method:"PATCH",body:JSON.stringify({activationMode:e.activationMode})});(!t||typeof t!="object")&&r();const n=t;return(n.bindingId!==e.bindingId||n.roomId!==e.roomId||n.status!=="active"&&n.status!=="paused"||!v(n.activationMode)||n.activationMode!==e.activationMode||typeof n.idempotent!="boolean")&&r(),n}async pause(e){const t=await this.request(e,"pause",{method:"POST",body:JSON.stringify({reason:e.reason})});(!t||typeof t!="object")&&r();const n=t;return(n.bindingId!==e.bindingId||n.roomId!==e.roomId||n.status!=="paused"||n.activationMode!=="paused"||typeof n.idempotent!="boolean")&&r(),{...n,message:h(n.message,e.roomId)}}async request(e,t,n={}){let o;const a=C(e.signal,1e4);try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/${t}`,{...n,headers:{authorization:`Bearer ${e.machineToken}`,...n.body?{"content-type":"application/json"}:{}},signal:a.signal})}catch{throw e.signal?.aborted?new c("managed_room_context_revoked","Managed Room context was revoked."):new c("room_action_server_unavailable","Shennian Room service is unavailable.")}finally{a.dispose()}if(!o.ok){let s=`room_action_http_${o.status}`;try{const d=await o.json();typeof d.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(d.error)&&(s=d.error)}catch{}throw new c(s,"Shennian Room action failed.")}try{return await o.json()}catch{r()}}}function C(i,e){const t=new AbortController,n=()=>t.abort();i?.aborted?t.abort():i?.addEventListener("abort",n,{once:!0});const o=setTimeout(()=>t.abort(),e);return o.unref?.(),{signal:t.signal,dispose:()=>{clearTimeout(o),i?.removeEventListener("abort",n)}}}function q(i,e){(!i||typeof i!="object")&&r();const t=i,n=t.room,o=t.agent;(t.nianSessionId!==void 0&&t.nianSessionId!==null&&(typeof t.nianSessionId!="string"||!t.nianSessionId)||t.sessionRegistryKey!==void 0&&(typeof t.sessionRegistryKey!="string"||!/^[a-f0-9]{64}$/.test(t.sessionRegistryKey))||t.bindingId!==e.bindingId||t.roomId!==e.roomId||typeof t.agentIdentityId!="string"||t.agentMembershipId!==null&&typeof t.agentMembershipId!="string"||t.status!=="pending_approval"&&t.status!=="active"&&t.status!=="paused"&&t.status!=="detached"||!v(t.activationMode)||t.activationCapability!==void 0&&t.activationCapability!=="managed"&&t.activationCapability!=="idle_resume"&&t.activationCapability!=="notify_only"||t.managedWorkspacePath!==void 0&&t.managedWorkspacePath!==null&&(typeof t.managedWorkspacePath!="string"||t.managedWorkspacePath.length>4096||!M.isAbsolute(t.managedWorkspacePath))||typeof t.lastDeliveredSequence!="string"||!m(t.lastDeliveredSequence)||typeof t.lastActivatedSequence!="string"||!m(t.lastActivatedSequence)||t.appliedConfigurationVersion!==void 0&&(!Number.isSafeInteger(t.appliedConfigurationVersion)||Number(t.appliedConfigurationVersion)<1)||t.pendingConfigurationVersion!==void 0&&t.pendingConfigurationVersion!==null&&(!Number.isSafeInteger(t.pendingConfigurationVersion)||Number(t.pendingConfigurationVersion)<1)||!n||typeof n!="object"||o!==void 0&&(!o||typeof o!="object"))&&r(),(t.status==="pending_approval"&&t.agentMembershipId!==null||(t.status==="active"||t.status==="paused")&&typeof t.agentMembershipId!="string")&&r();const a=n,s=o,d=s?_(s.permissionPolicy):null,g=s?R(s.permissionConfirmation):null,w=s?Object.prototype.hasOwnProperty.call(s,"permissionConfirmation"):!1,l=a.members;return(s!==void 0&&(typeof s.displayName!="string"||!s.displayName.trim()||typeof s.description!="string"||s.sessionMode!=="standard"&&s.sessionMode!=="manager"||s.configurationVersion!==void 0&&(!Number.isSafeInteger(s.configurationVersion)||Number(s.configurationVersion)<1)||s.configurationPending!==void 0&&typeof s.configurationPending!="boolean"||!d||!w||s.permissionConfirmation!==null&&!g)||typeof a.name!="string"||typeof a.status!="string"||a.memberCount!==void 0&&(typeof a.memberCount!="number"||!Number.isInteger(a.memberCount)||a.memberCount<0)||typeof a.lastSequence!="string"||!m(a.lastSequence)||l!==void 0&&(!Array.isArray(l)||l.some(u=>{if(!u||typeof u!="object")return!0;const y=u;return typeof y.displayName!="string"||y.principalType!=="human"&&y.principalType!=="agent"})))&&r(),{...t,...s&&d?{agent:{...s,permissionPolicy:d,permissionConfirmation:g}}:{}}}function R(i){if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i;return!Number.isSafeInteger(e.policyVersion)||Number(e.policyVersion)<1||typeof e.runtimeAdapter!="string"||!j(e.runtimeAdapter)||typeof e.runtimeVersion!="string"||!e.runtimeVersion.trim()||typeof e.platform!="string"||!e.platform.trim()||typeof e.executableFingerprint!="string"||!/^sha256:[a-f0-9]{64}$/.test(e.executableFingerprint)||typeof e.hostFullAccess!="boolean"||typeof e.limitedProtection!="boolean"||typeof e.confirmedAt!="string"||!Number.isFinite(Date.parse(e.confirmedAt))?null:{policyVersion:Number(e.policyVersion),runtimeAdapter:e.runtimeAdapter,runtimeVersion:e.runtimeVersion,platform:e.platform,executableFingerprint:e.executableFingerprint,executionMode:e.executionMode==="native-full"?"native-full":"restricted",hostFullAccess:e.hostFullAccess,limitedProtection:e.limitedProtection,confirmedAt:e.confirmedAt}}function h(i,e){(!i||typeof i!="object")&&r();const t=i;if((typeof t.id!="string"||t.roomId!==e||typeof t.sequence!="string"||!m(t.sequence)||!t.author||typeof t.author.membershipId!="string"||!Array.isArray(t.attachments)||typeof t.createdAt!="string")&&r(),t.activationCause!=null){const n=t.activationCause;(typeof n.causeMessageId!="string"||typeof n.rootCauseMessageId!="string"||!Number.isInteger(n.hopCount)||n.hopCount<1||n.hopCount>32)&&r()}return t}function T(i,e,t){(!i||typeof i!="object")&&r();const n=i,o=n.room,a=n.agentIdentity,s=n.binding;return(n.outcome!=="created"||!o||typeof o.id!="string"||o.name!==e.name||o.visibility!=="private"||o.joinPolicy!=="open"||!b(o.canonicalUrl,t,`/spaces/${o.id}`)||!P(o.inviteUrl,t,o.id)||!a||typeof a.id!="string"||a.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==o.id||s.agentIdentityId!==a.id||typeof s.agentMembershipId!="string"||s.status!=="active"&&s.status!=="paused"||s.activationMode!==e.activationMode||typeof n.idempotent!="boolean")&&r(),n}function N(i,e,t){(!i||typeof i!="object")&&r();const n=i,o=n.room;return(n.outcome!=="created"||!o||typeof o.id!="string"||o.name!==e.name||o.visibility!=="private"||o.joinPolicy!=="open"||!b(o.canonicalUrl,t,`/spaces/${o.id}`))&&r(),n}function $(i,e,t){(!i||typeof i!="object")&&r();const n=i,o=n.room,a=n.agentIdentity,s=n.binding;return(n.outcome!=="joined"&&n.outcome!=="pending_approval"||!o||typeof o.id!="string"||typeof o.name!="string"||!b(o.canonicalUrl,t,`/spaces/${o.id}`)||!a||typeof a.id!="string"||a.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==o.id||s.agentIdentityId!==a.id||s.activationMode!==e.activationMode||typeof n.idempotent!="boolean")&&r(),(n.outcome==="pending_approval"&&(s.status!=="pending_approval"||s.agentMembershipId!==null||typeof n.joinRequestId!="string")||n.outcome==="joined"&&(s.status!=="active"&&s.status!=="paused"||typeof s.agentMembershipId!="string"||n.joinRequestId!==void 0))&&r(),n}function m(i){return/^(0|[1-9]\d{0,19})$/.test(i)}function I(i){return i.length<=255&&i===i.trim()&&i!=="."&&i!==".."&&!/[\\/\0-\x1f\x7f]/.test(i)}function S(i){if(typeof i!="string")return!1;try{const e=new URL(i);return e.protocol==="https:"&&!e.username&&!e.password&&!e.hash}catch{return!1}}function v(i){return i==="auto_follow"||i==="mention_only"||i==="paused"}function r(){throw new c("invalid_room_action_response","Invalid Room action response.")}async function p(i){try{return await i.json()}catch{r()}}function f(i,e){let t=`room_action_http_${i}`;if(e&&typeof e=="object"){const n=e.error;typeof n=="string"&&/^[a-z0-9_-]{1,80}$/.test(n)&&(t=n)}return new c(t,"Shennian Room action failed.")}function b(i,e,t){return A(i,e,n=>n.pathname===t)}function P(i,e,t){if(typeof i!="string")return!1;try{const n=new URL(i);return A(`${n.origin}${n.pathname}`,e,o=>o.pathname===`/spaces/${t}`)?n.searchParams.size===1&&/^[A-Za-z0-9_-]{32,256}$/.test(n.searchParams.get("invite")??"")&&!n.hash&&!n.username&&!n.password:!1}catch{return!1}}function A(i,e,t){if(typeof i!="string")return!1;try{const n=new URL(i),o=new URL(e),a=n.origin===o.origin,s=o.protocol==="http:"&&n.protocol==="http:"&&n.hostname===o.hostname,d=n.protocol==="https:"&&n.port===""&&n.hostname==="app.shennian.net";return(a||s||d)&&!n.username&&!n.password&&!n.search&&!n.hash&&t(n)}catch{return!1}}export{O as HttpRoomActionClient,c as RoomActionHttpError};
@@ -21,6 +21,7 @@ export type RoomSessionBindingState = {
21
21
  activeActivation: RoomActivationQueueItem | null;
22
22
  mentionContext?: RoomMentionContextItem[];
23
23
  loopBreaker: {
24
+ claimedMessageIds?: string[];
24
25
  windowStartedAt: string | null;
25
26
  activationCount: number;
26
27
  pausedUntil: string | null;
@@ -98,6 +99,9 @@ export declare class RoomSessionRegistry {
98
99
  agentMembershipId: string | null;
99
100
  status: RoomBindingStatus;
100
101
  activationMode?: RoomActivationMode;
102
+ /** Only a successful authoritative bind/restart may replace the owner. */
103
+ replaceOwner?: boolean;
104
+ initialSequence?: string;
101
105
  now?: Date;
102
106
  }): RoomSessionRegistration;
103
107
  detachBinding(registryKey: string, now?: Date): RoomSessionRegistration;
@@ -126,6 +130,7 @@ export declare class RoomSessionRegistry {
126
130
  private requeueActiveActivation;
127
131
  private markPersistedActivationsInterrupted;
128
132
  claimActivation(input: {
133
+ causeMessageId?: string;
129
134
  registryKey: string;
130
135
  windowMs: number;
131
136
  maxActivations: number;
@@ -1,2 +1,2 @@
1
- import A from"node:crypto";import u from"node:fs";import q from"node:path";import{isRoomRuntimeAdapter as C}from"@shennian/wire";import{resolveShennianPath as M}from"../config/index.js";const I=500,p=20,T=/^[A-Za-z0-9_.:@/-]{1,512}$/,b=new Set(["pending_approval","active","paused","detached"]),S=new Set(["auto_follow","mention_only","paused"]);class o extends Error{code;constructor(e,i){super(i),this.code=e,this.name="RoomSessionRegistryError"}}class j{filePath;state;mutexTails=new Map;constructor(e=M("runtime","room-session-registry.json"),i=new Date){this.filePath=e,this.state=this.load(),this.markPersistedActivationsInterrupted(i)&&this.persist()}register(e){c(e.machineId,"machine_id"),_(e.runtimeAdapter),y(e.sourceSessionKey),c(e.nianSessionId,"nian_session_id"),c(e.hostProcessIdentity,"host_process_identity"),P(e.capabilities);const i=m(e),n=(e.now??new Date).toISOString(),s=this.state.records[i];if(Object.values(this.state.records).find(h=>h.registryKey!==i&&h.nianSessionId===e.nianSessionId&&h.lifecycleState!=="closed"))throw new o("nian_session_already_registered","This Shennian session is already registered to another native Agent session.");const d=!!(s&&s.hostProcessIdentity!==e.hostProcessIdentity),l={schemaVersion:1,registryKey:i,machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,panelContextKey:e.nianSessionId,hostProcessIdentity:e.hostProcessIdentity,registrationEpoch:s?s.registrationEpoch+(d?1:0):1,lifecycleState:"active",capabilities:{...e.capabilities},binding:s?.binding??null,registeredAt:s?.registeredAt??n,lastSeenAt:n,updatedAt:n};return this.state.records[i]=l,this.persist(),r(l)}getBySource(e){const i=this.state.records[m(e)];return i?r(i):null}getByNianSessionId(e){c(e,"nian_session_id");const i=Object.values(this.state.records).find(n=>n.nianSessionId===e&&n.lifecycleState!=="closed");return i?r(i):null}getByRegistryKey(e){if(!/^[a-f0-9]{64}$/.test(e))throw new o("invalid_registry_key","Invalid Room session registry key.");const i=this.state.records[e];return i?r(i):null}list(){return Object.values(this.state.records).sort((e,i)=>e.registeredAt.localeCompare(i.registeredAt)).map(r)}setBinding(e){const i=this.requireRecord(e.registryKey);for(const[a,d]of Object.entries({binding_id:e.bindingId,room_id:e.roomId,agent_identity_id:e.agentIdentityId}))c(d,a);if(!b.has(e.status))throw new o("invalid_binding_status","Invalid binding status.");if(e.status==="pending_approval"&&e.agentMembershipId!==null||(e.status==="active"||e.status==="paused")&&e.agentMembershipId===null)throw new o("invalid_agent_membership_id","Pending Room bindings cannot claim an Agent membership.");e.agentMembershipId!==null&&c(e.agentMembershipId,"agent_membership_id");const n=e.activationMode??"auto_follow";if(!S.has(n))throw new o("invalid_activation_mode","Invalid activation mode.");if(i.binding&&i.binding.status!=="detached"&&(i.binding.bindingId!==e.bindingId||i.binding.roomId!==e.roomId))throw new o("session_already_bound","An Agent session can have only one active Room binding.");const s=i.binding?.activationMode;return i.binding={bindingId:e.bindingId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,agentMembershipId:e.agentMembershipId,status:e.status,activationMode:n,lastDeliveredSequence:i.binding?.lastDeliveredSequence??"0",lastActivatedSequence:i.binding?.lastActivatedSequence??"0",activationQueue:i.binding?.activationQueue??[],activeActivation:i.binding?.activeActivation??null,mentionContext:s==="mention_only"&&n==="mention_only"?i.binding?.mentionContext??[]:[],loopBreaker:i.binding?.loopBreaker??{windowStartedAt:null,activationCount:0,pausedUntil:null}},this.touchAndPersist(i,e.now),r(i)}detachBinding(e,i=new Date){const n=this.requireRecord(e);return n.binding&&(n.binding.status="detached",n.binding.activationMode="paused",n.binding.activationQueue=[],n.binding.mentionContext=[]),this.touchAndPersist(n,i),r(n)}advanceCursor(e){const i=this.requireBoundRecord(e.registryKey);v(e.sequence);const n=e.cursor==="delivered"?"lastDeliveredSequence":"lastActivatedSequence";if(BigInt(e.sequence)<BigInt(i.binding[n]))throw new o("cursor_regression","Room cursor cannot move backwards.");return i.binding[n]=e.sequence,this.touchAndPersist(i,e.now),r(i)}enqueueActivation(e,i,n=new Date,s=!1){const a=this.requireBoundRecord(e);w(i);const d=a.binding.activationQueue;if(!d.some(l=>l.causeMessageId===i.causeMessageId)){if(d.length>=I)throw new o("activation_queue_full","Agent activation queue is full.");d.push(r(i))}return s&&(a.binding.mentionContext=[]),this.touchAndPersist(a,n),r(a)}appendMentionContext(e,i,n=new Date){const s=this.requireBoundRecord(e);f(i);const a=s.binding.mentionContext??[];return a.some(d=>d.sequence===i.sequence)||(a.push(r(i)),s.binding.mentionContext=a.slice(-p)),this.touchAndPersist(s,n),r(s)}replaceMentionContext(e,i,n=new Date){const s=this.requireBoundRecord(e);for(const a of i)f(a);return s.binding.mentionContext=i.slice(-p).map(r),this.touchAndPersist(s,n),r(s)}retireLegacyActivationState(e,i=new Date){const n=this.requireBoundRecord(e);return n.binding.activationQueue=[],n.binding.activeActivation=null,this.touchAndPersist(n,i),r(n)}dequeueActivation(e,i,n=new Date){const s=this.requireBoundRecord(e);return s.binding.activationQueue=s.binding.activationQueue.filter(a=>a.causeMessageId!==i),this.touchAndPersist(s,n),r(s)}markActivationDispatching(e,i,n=new Date){const s=this.requireBoundRecord(e),a=s.binding.activationQueue.find(d=>d.causeMessageId===i);if(!a)throw new o("activation_not_found","Agent activation is unavailable.");return s.binding.activeActivation={...r(a),dispatchState:"dispatching",dispatchedAt:n.toISOString(),interruptedAt:void 0},s.binding.activationQueue=s.binding.activationQueue.filter(d=>d.causeMessageId!==i),this.touchAndPersist(s,n),r(s)}clearActiveActivation(e,i=new Date){const n=this.requireBoundRecord(e);return n.binding.activeActivation=null,this.touchAndPersist(n,i),r(n)}retryActiveActivation(e){const i=this.requireBoundRecord(e.registryKey),n=i.binding.activeActivation;if(!n)return{queued:!1,attempts:0};if(x(n))throw new o("activation_interrupted_requires_explicit_retry","Interrupted Agent activation requires an explicit retry.");return this.requeueActiveActivation(i,e)}requeueActiveActivation(e,i){const n=e.binding.activeActivation,s=i.now??new Date,{dispatchState:a,dispatchedAt:d,interruptedAt:l,...h}=n,g={...h,attempts:n.attempts+1};return e.binding.activeActivation=null,g.attempts<i.maxAttempts?(e.binding.activationQueue.some(R=>R.causeMessageId===g.causeMessageId)||e.binding.activationQueue.unshift(g),this.touchAndPersist(e,s),{queued:!0,attempts:g.attempts}):(e.binding.loopBreaker.pausedUntil=new Date(s.getTime()+i.pauseMs).toISOString(),this.touchAndPersist(e,s),{queued:!1,attempts:g.attempts})}markPersistedActivationsInterrupted(e){const i=e.toISOString();let n=!1;for(const s of Object.values(this.state.records)){const a=s.binding?.activeActivation;!a||x(a)||(a.dispatchState="interrupted",a.interruptedAt=i,s.updatedAt=i,n=!0)}return n}claimActivation(e){const i=this.requireBoundRecord(e.registryKey),n=e.now??new Date,s=i.binding.loopBreaker;return s.pausedUntil&&Date.parse(s.pausedUntil)>n.getTime()?{allowed:!1,pausedUntil:s.pausedUntil,newlyPaused:!1}:((!s.windowStartedAt||n.getTime()-Date.parse(s.windowStartedAt)>=e.windowMs)&&(s.windowStartedAt=n.toISOString(),s.activationCount=0,s.pausedUntil=null),s.activationCount>=e.maxActivations?(s.pausedUntil=new Date(n.getTime()+e.pauseMs).toISOString(),this.touchAndPersist(i,n),{allowed:!1,pausedUntil:s.pausedUntil,newlyPaused:!0}):(s.activationCount+=1,this.touchAndPersist(i,n),{allowed:!0,pausedUntil:null}))}updateLifecycle(e,i,n=new Date){const s=this.requireRecord(e);return s.lifecycleState=i,i==="closed"&&s.binding&&(s.binding.status="detached",s.binding.activationMode="paused",s.binding.activationQueue=[],s.binding.mentionContext=[]),this.touchAndPersist(s,n),r(s)}async runExclusive(e,i){this.requireRecord(e);const n=this.mutexTails.get(e)??Promise.resolve();let s;const a=new Promise(l=>{s=l}),d=n.then(()=>a);this.mutexTails.set(e,d),await n;try{return await i()}finally{s(),this.mutexTails.get(e)===d&&this.mutexTails.delete(e)}}requireRecord(e){const i=this.state.records[e];if(!i)throw new o("session_registration_not_found","Agent session is not registered.");return i}requireBoundRecord(e){const i=this.requireRecord(e);if(!i.binding||i.binding.status!=="active"&&i.binding.status!=="paused")throw new o("session_not_bound","Agent session has no active Room binding.");return i}touchAndPersist(e,i=new Date){e.updatedAt=i.toISOString(),this.persist()}load(){try{const e=u.lstatSync(this.filePath);if(!e.isFile()||e.isSymbolicLink())throw new o("unsafe_session_registry","Room session registry is not a regular file.");process.platform!=="win32"&&(e.mode&63)!==0&&u.chmodSync(this.filePath,384);const i=JSON.parse(u.readFileSync(this.filePath,"utf8"));return D(i)}catch(e){if(k(e))return{schemaVersion:1,records:{}};throw e instanceof o?e:new o("corrupt_session_registry","Room Agent session registry is corrupt.")}}persist(){const e=q.dirname(this.filePath);u.mkdirSync(e,{recursive:!0,mode:448}),process.platform!=="win32"&&u.chmodSync(e,448);const i=`${this.filePath}.tmp-${process.pid}-${A.randomBytes(6).toString("hex")}`;try{if(u.writeFileSync(i,`${JSON.stringify(this.state,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&u.chmodSync(i,384),process.platform==="win32"&&u.existsSync(this.filePath)){const n=u.lstatSync(this.filePath);if(!n.isFile()||n.isSymbolicLink())throw new o("unsafe_session_registry","Refusing to replace an unsafe Room session registry.");u.unlinkSync(this.filePath)}u.renameSync(i,this.filePath),process.platform!=="win32"&&u.chmodSync(this.filePath,384)}finally{try{u.unlinkSync(i)}catch{}}}}function m(t){return c(t.machineId,"machine_id"),_(t.runtimeAdapter),y(t.sourceSessionKey),A.createHash("sha256").update(`${t.machineId}\0${t.runtimeAdapter}\0${t.sourceSessionKey}`,"utf8").digest("hex")}function D(t){if(!t||typeof t!="object"||t.schemaVersion!==1)throw new o("corrupt_session_registry","Unsupported Room session registry.");const e=t.records;if(!e||typeof e!="object"||Array.isArray(e))throw new o("corrupt_session_registry","Room session records are invalid.");const i={};for(const[n,s]of Object.entries(e)){if(B(s),n!==s.registryKey||n!==m(s))throw new o("corrupt_session_registry","Room session registry key mismatch.");i[n]={...s,binding:s.binding?{...s.binding,activeActivation:s.binding.activeActivation??null,mentionContext:s.binding.mentionContext??[]}:null}}return{schemaVersion:1,records:i}}function B(t){if(!t||t.schemaVersion!==1)throw new o("corrupt_session_registry","Invalid registration.");if(c(t.machineId,"machine_id"),_(t.runtimeAdapter),y(t.sourceSessionKey),c(t.nianSessionId,"nian_session_id"),c(t.hostProcessIdentity,"host_process_identity"),t.panelContextKey!==t.nianSessionId)throw new o("corrupt_session_registry","Invalid panel context.");if(!Number.isInteger(t.registrationEpoch)||t.registrationEpoch<1)throw new o("corrupt_session_registry","Invalid registration epoch.");P(t.capabilities),t.binding&&O(t.binding)}function O(t){if(c(t.bindingId,"binding_id"),c(t.roomId,"room_id"),c(t.agentIdentityId,"agent_identity_id"),!b.has(t.status))throw new o("corrupt_session_registry","Invalid binding status.");if(t.status==="pending_approval"&&t.agentMembershipId!==null||(t.status==="active"||t.status==="paused")&&t.agentMembershipId===null)throw new o("corrupt_session_registry","Invalid Agent membership.");if(t.agentMembershipId!==null&&c(t.agentMembershipId,"agent_membership_id"),!S.has(t.activationMode))throw new o("corrupt_session_registry","Invalid activation mode.");if(v(t.lastDeliveredSequence),v(t.lastActivatedSequence),!Array.isArray(t.activationQueue)||t.activationQueue.length>I)throw new o("corrupt_session_registry","Invalid activation queue.");if(t.activationQueue.forEach(w),t.activeActivation!=null&&w(t.activeActivation),t.mentionContext!==void 0&&(!Array.isArray(t.mentionContext)||t.mentionContext.length>p))throw new o("corrupt_session_registry","Invalid mention context.");t.mentionContext?.forEach(f)}function f(t){if(v(t.sequence),typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3)throw new o("invalid_mention_context","Invalid mention context.")}function w(t){if(c(t.causeMessageId,"cause_message_id"),c(t.rootCauseMessageId,"root_cause_message_id"),v(t.sequence),!Number.isInteger(t.hopCount)||t.hopCount<0||t.hopCount>32)throw new o("invalid_hop_count","Invalid Agent activation hop count.");if(!Number.isInteger(t.attempts)||t.attempts<0||t.attempts>100)throw new o("invalid_activation_attempts","Invalid Agent activation attempts.");if(!Number.isFinite(new Date(t.enqueuedAt).getTime()))throw new o("invalid_activation_time","Invalid Agent activation time.");if(t.deliveryText!==void 0&&(typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3))throw new o("invalid_activation_text","Invalid Agent activation text.");if(t.systemPrompt!==void 0&&(typeof t.systemPrompt!="string"||!t.systemPrompt.trim()||t.systemPrompt.length>24e3))throw new o("invalid_activation_prompt","Invalid Agent activation prompt.");if(t.promptSnapshot!==void 0&&!E(t.promptSnapshot))throw new o("invalid_activation_prompt_snapshot","Invalid Agent activation prompt snapshot.");if(t.dispatchState!==void 0&&t.dispatchState!=="dispatching"&&t.dispatchState!=="interrupted")throw new o("invalid_activation_state","Invalid Agent activation state.");if(t.dispatchedAt!==void 0&&!Number.isFinite(new Date(t.dispatchedAt).getTime()))throw new o("invalid_activation_dispatch_time","Invalid Agent activation dispatch time.");if(t.dispatchState===void 0&&t.dispatchedAt!==void 0)throw new o("invalid_activation_dispatch_time","Queued Agent activation cannot have a dispatch time.");if(t.dispatchState==="interrupted"&&(t.interruptedAt===void 0||!Number.isFinite(new Date(t.interruptedAt).getTime())))throw new o("invalid_activation_interrupted_time","Invalid Agent activation interruption time.");if(t.dispatchState!=="interrupted"&&t.interruptedAt!==void 0)throw new o("invalid_activation_interrupted_time","Unexpected Agent activation interruption time.")}function E(t){if(!t||typeof t!="object"||Array.isArray(t))return!1;const e=t,i=n=>n===null||typeof n=="string"&&/^[a-f0-9]{64}$/.test(n);return typeof e.managedRoomPolicyVersion=="string"&&e.managedRoomPolicyVersion.length<=64&&i(e.sessionModeInstructionsHash)&&i(e.agentSystemPromptHash)&&typeof e.trustedRoomContextHash=="string"&&/^[a-f0-9]{64}$/.test(e.trustedRoomContextHash)}function x(t){return t.dispatchState==="interrupted"}function P(t){if(!t||["canIdentifySession","canBindAutomatically","canOpenEmbeddedRoomPage","canResumeIdleSession","canActivateWhileHostOpen"].some(i=>typeof t[i]!="boolean"))throw new o("invalid_session_capabilities","Invalid Agent session capabilities.")}function _(t){if(!C(t))throw new o("invalid_runtime_adapter","Unsupported Room runtime adapter.")}function y(t){if(!t||t.length>1024||/[\0\r\n]/.test(t))throw new o("invalid_source_session_key","Invalid native Agent session key.")}function c(t,e){if(!T.test(t))throw new o(`invalid_${e}`,`Invalid ${e}.`)}function v(t){if(!/^(0|[1-9]\d{0,19})$/.test(t))throw new o("invalid_room_sequence","Invalid Room sequence.")}function r(t){return structuredClone(t)}function k(t){return typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT"}export{j as RoomSessionRegistry,o as RoomSessionRegistryError,m as buildRoomSessionRegistryKey,x as isInterruptedRoomActivation};
1
+ import I from"node:crypto";import u from"node:fs";import P from"node:path";import{isRoomRuntimeAdapter as M}from"@shennian/wire";import{resolveShennianPath as C}from"../config/index.js";const f=500,m=20,D=/^[A-Za-z0-9_.:@/-]{1,512}$/,b=new Set(["pending_approval","active","paused","detached"]),S=new Set(["auto_follow","mention_only","paused"]);class o extends Error{code;constructor(e,i){super(i),this.code=e,this.name="RoomSessionRegistryError"}}class j{filePath;state;mutexTails=new Map;constructor(e=C("runtime","room-session-registry.json"),i=new Date){this.filePath=e,this.state=this.load(),this.markPersistedActivationsInterrupted(i)&&this.persist()}register(e){c(e.machineId,"machine_id"),_(e.runtimeAdapter),A(e.sourceSessionKey),c(e.nianSessionId,"nian_session_id"),c(e.hostProcessIdentity,"host_process_identity"),q(e.capabilities);const i=p(e),s=(e.now??new Date).toISOString(),n=this.state.records[i];if(n?.lifecycleState==="closed")throw new o("session_registration_closed","A retired session cannot be registered again.");if(Object.values(this.state.records).find(g=>g.registryKey!==i&&g.nianSessionId===e.nianSessionId&&g.lifecycleState!=="closed"))throw new o("nian_session_already_registered","This Shennian session is already registered to another native Agent session.");const a=!!(n&&n.hostProcessIdentity!==e.hostProcessIdentity),l={schemaVersion:1,registryKey:i,machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,panelContextKey:e.nianSessionId,hostProcessIdentity:e.hostProcessIdentity,registrationEpoch:n?n.registrationEpoch+(a?1:0):1,lifecycleState:"active",capabilities:{...e.capabilities},binding:n?.binding??null,registeredAt:n?.registeredAt??s,lastSeenAt:s,updatedAt:s};return this.state.records[i]=l,this.persist(),d(l)}getBySource(e){const i=this.state.records[p(e)];return i?d(i):null}getByNianSessionId(e){c(e,"nian_session_id");const i=Object.values(this.state.records).find(s=>s.nianSessionId===e&&s.lifecycleState!=="closed");return i?d(i):null}getByRegistryKey(e){if(!/^[a-f0-9]{64}$/.test(e))throw new o("invalid_registry_key","Invalid Room session registry key.");const i=this.state.records[e];return i?d(i):null}list(){return Object.values(this.state.records).sort((e,i)=>e.registeredAt.localeCompare(i.registeredAt)).map(d)}setBinding(e){const i=this.requireRecord(e.registryKey);for(const[a,l]of Object.entries({binding_id:e.bindingId,room_id:e.roomId,agent_identity_id:e.agentIdentityId}))c(l,a);if(!b.has(e.status))throw new o("invalid_binding_status","Invalid binding status.");if(e.status==="pending_approval"&&e.agentMembershipId!==null||(e.status==="active"||e.status==="paused")&&e.agentMembershipId===null)throw new o("invalid_agent_membership_id","Pending Room bindings cannot claim an Agent membership.");e.agentMembershipId!==null&&c(e.agentMembershipId,"agent_membership_id");const s=e.activationMode??"auto_follow";if(!S.has(s))throw new o("invalid_activation_mode","Invalid activation mode.");if(i.binding&&i.binding.status!=="detached"&&(i.binding.bindingId!==e.bindingId||i.binding.roomId!==e.roomId))throw new o("session_already_bound","An Agent session can have only one active Room binding.");if(i.lifecycleState==="closed")throw new o("session_registration_closed","A retired session cannot be rebound.");e.initialSequence!==void 0&&h(e.initialSequence);const n=Object.values(this.state.records).filter(a=>a.registryKey!==i.registryKey&&a.lifecycleState!=="closed"&&a.binding?.bindingId===e.bindingId&&a.binding.status!=="detached");if(e.status!=="detached"&&n.length&&!e.replaceOwner)throw new o("binding_already_owned","A Room binding can have only one live session.");if(e.replaceOwner)for(const a of Object.values(this.state.records))a.registryKey===i.registryKey||a.binding?.bindingId!==e.bindingId||(a.lifecycleState="closed",a.registrationEpoch+=1,a.binding.status="detached",a.binding.activationMode="paused",a.binding.activationQueue=[],a.binding.activeActivation=null,a.binding.mentionContext=[],a.updatedAt=(e.now??new Date).toISOString());const r=i.binding?.activationMode;return i.binding={bindingId:e.bindingId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,agentMembershipId:e.agentMembershipId,status:e.status,activationMode:s,lastDeliveredSequence:i.binding?.lastDeliveredSequence??e.initialSequence??"0",lastActivatedSequence:i.binding?.lastActivatedSequence??e.initialSequence??"0",activationQueue:i.binding?.activationQueue??[],activeActivation:i.binding?.activeActivation??null,mentionContext:r==="mention_only"&&s==="mention_only"?i.binding?.mentionContext??[]:[],loopBreaker:i.binding?.loopBreaker??{windowStartedAt:null,activationCount:0,pausedUntil:null}},this.touchAndPersist(i,e.now),d(i)}detachBinding(e,i=new Date){const s=this.requireRecord(e);return s.binding&&(s.binding.status="detached",s.binding.activationMode="paused",s.binding.activationQueue=[],s.binding.activeActivation=null,s.binding.mentionContext=[]),this.touchAndPersist(s,i),d(s)}advanceCursor(e){const i=this.requireBoundRecord(e.registryKey);h(e.sequence);const s=e.cursor==="delivered"?"lastDeliveredSequence":"lastActivatedSequence";if(BigInt(e.sequence)<BigInt(i.binding[s]))throw new o("cursor_regression","Room cursor cannot move backwards.");return i.binding[s]=e.sequence,this.touchAndPersist(i,e.now),d(i)}enqueueActivation(e,i,s=new Date,n=!1){const r=this.requireBoundRecord(e);y(i);const a=r.binding.activationQueue;if(!a.some(l=>l.causeMessageId===i.causeMessageId)){if(a.length>=f)throw new o("activation_queue_full","Agent activation queue is full.");a.push(d(i))}return n&&(r.binding.mentionContext=[]),this.touchAndPersist(r,s),d(r)}appendMentionContext(e,i,s=new Date){const n=this.requireBoundRecord(e);w(i);const r=n.binding.mentionContext??[];return r.some(a=>a.sequence===i.sequence)||(r.push(d(i)),n.binding.mentionContext=r.slice(-m)),this.touchAndPersist(n,s),d(n)}replaceMentionContext(e,i,s=new Date){const n=this.requireBoundRecord(e);for(const r of i)w(r);return n.binding.mentionContext=i.slice(-m).map(d),this.touchAndPersist(n,s),d(n)}retireLegacyActivationState(e,i=new Date){const s=this.requireBoundRecord(e);return s.binding.activationQueue=[],s.binding.activeActivation=null,this.touchAndPersist(s,i),d(s)}dequeueActivation(e,i,s=new Date){const n=this.requireBoundRecord(e);return n.binding.activationQueue=n.binding.activationQueue.filter(r=>r.causeMessageId!==i),this.touchAndPersist(n,s),d(n)}markActivationDispatching(e,i,s=new Date){const n=this.requireBoundRecord(e),r=n.binding.activationQueue.find(a=>a.causeMessageId===i);if(!r)throw new o("activation_not_found","Agent activation is unavailable.");return n.binding.activeActivation={...d(r),dispatchState:"dispatching",dispatchedAt:s.toISOString(),interruptedAt:void 0},n.binding.activationQueue=n.binding.activationQueue.filter(a=>a.causeMessageId!==i),this.touchAndPersist(n,s),d(n)}clearActiveActivation(e,i=new Date){const s=this.requireBoundRecord(e);return s.binding.activeActivation=null,this.touchAndPersist(s,i),d(s)}retryActiveActivation(e){const i=this.requireBoundRecord(e.registryKey),s=i.binding.activeActivation;if(!s)return{queued:!1,attempts:0};if(x(s))throw new o("activation_interrupted_requires_explicit_retry","Interrupted Agent activation requires an explicit retry.");return this.requeueActiveActivation(i,e)}requeueActiveActivation(e,i){const s=e.binding.activeActivation,n=i.now??new Date,{dispatchState:r,dispatchedAt:a,interruptedAt:l,...g}=s,v={...g,attempts:s.attempts+1};return e.binding.activeActivation=null,v.attempts<i.maxAttempts?(e.binding.activationQueue.some(R=>R.causeMessageId===v.causeMessageId)||e.binding.activationQueue.unshift(v),this.touchAndPersist(e,n),{queued:!0,attempts:v.attempts}):(e.binding.loopBreaker.pausedUntil=new Date(n.getTime()+i.pauseMs).toISOString(),this.touchAndPersist(e,n),{queued:!1,attempts:v.attempts})}markPersistedActivationsInterrupted(e){const i=e.toISOString();let s=!1;for(const n of Object.values(this.state.records)){const r=n.binding?.activeActivation;!r||x(r)||(r.dispatchState="interrupted",r.interruptedAt=i,n.updatedAt=i,s=!0)}return s}claimActivation(e){const i=this.requireBoundRecord(e.registryKey),s=e.now??new Date,n=i.binding.loopBreaker;return e.causeMessageId&&n.claimedMessageIds?.includes(e.causeMessageId)?{allowed:!0,pausedUntil:null}:n.pausedUntil&&Date.parse(n.pausedUntil)>s.getTime()?{allowed:!1,pausedUntil:n.pausedUntil,newlyPaused:!1}:((!n.windowStartedAt||s.getTime()-Date.parse(n.windowStartedAt)>=e.windowMs)&&(n.windowStartedAt=s.toISOString(),n.activationCount=0,n.pausedUntil=null),n.activationCount>=e.maxActivations?(n.pausedUntil=new Date(s.getTime()+e.pauseMs).toISOString(),this.touchAndPersist(i,s),{allowed:!1,pausedUntil:n.pausedUntil,newlyPaused:!0}):(n.activationCount+=1,e.causeMessageId&&(n.claimedMessageIds=[...n.claimedMessageIds??[],e.causeMessageId].slice(-f)),this.touchAndPersist(i,s),{allowed:!0,pausedUntil:null}))}updateLifecycle(e,i,s=new Date){const n=this.requireRecord(e);if(n.lifecycleState==="closed"&&i!=="closed")throw new o("session_registration_closed","A retired session cannot be reopened.");return n.lifecycleState!==i&&(n.registrationEpoch+=1),n.lifecycleState=i,i==="closed"&&n.binding&&(n.binding.status="detached",n.binding.activationMode="paused",n.binding.activationQueue=[],n.binding.activeActivation=null,n.binding.mentionContext=[]),this.touchAndPersist(n,s),d(n)}async runExclusive(e,i){this.requireRecord(e);const s=this.mutexTails.get(e)??Promise.resolve();let n;const r=new Promise(l=>{n=l}),a=s.then(()=>r);this.mutexTails.set(e,a),await s;try{return await i()}finally{n(),this.mutexTails.get(e)===a&&this.mutexTails.delete(e)}}requireRecord(e){const i=this.state.records[e];if(!i)throw new o("session_registration_not_found","Agent session is not registered.");return i}requireBoundRecord(e){const i=this.requireRecord(e);if(!i.binding||i.binding.status!=="active"&&i.binding.status!=="paused")throw new o("session_not_bound","Agent session has no active Room binding.");return i}touchAndPersist(e,i=new Date){e.updatedAt=i.toISOString(),this.persist()}load(){try{const e=u.lstatSync(this.filePath);if(!e.isFile()||e.isSymbolicLink())throw new o("unsafe_session_registry","Room session registry is not a regular file.");process.platform!=="win32"&&(e.mode&63)!==0&&u.chmodSync(this.filePath,384);const i=JSON.parse(u.readFileSync(this.filePath,"utf8"));return T(i)}catch(e){if(k(e))return{schemaVersion:1,records:{}};throw e instanceof o?e:new o("corrupt_session_registry","Room Agent session registry is corrupt.")}}persist(){const e=P.dirname(this.filePath);u.mkdirSync(e,{recursive:!0,mode:448}),process.platform!=="win32"&&u.chmodSync(e,448);const i=`${this.filePath}.tmp-${process.pid}-${I.randomBytes(6).toString("hex")}`;try{if(u.writeFileSync(i,`${JSON.stringify(this.state,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&u.chmodSync(i,384),process.platform==="win32"&&u.existsSync(this.filePath)){const s=u.lstatSync(this.filePath);if(!s.isFile()||s.isSymbolicLink())throw new o("unsafe_session_registry","Refusing to replace an unsafe Room session registry.");u.unlinkSync(this.filePath)}u.renameSync(i,this.filePath),process.platform!=="win32"&&u.chmodSync(this.filePath,384)}finally{try{u.unlinkSync(i)}catch{}}}}function p(t){return c(t.machineId,"machine_id"),_(t.runtimeAdapter),A(t.sourceSessionKey),I.createHash("sha256").update(`${t.machineId}\0${t.runtimeAdapter}\0${t.sourceSessionKey}`,"utf8").digest("hex")}function T(t){if(!t||typeof t!="object"||t.schemaVersion!==1)throw new o("corrupt_session_registry","Unsupported Room session registry.");const e=t.records;if(!e||typeof e!="object"||Array.isArray(e))throw new o("corrupt_session_registry","Room session records are invalid.");const i={};for(const[s,n]of Object.entries(e)){if(O(n),s!==n.registryKey||s!==p(n))throw new o("corrupt_session_registry","Room session registry key mismatch.");i[s]={...n,binding:n.binding?{...n.binding,activeActivation:n.binding.activeActivation??null,mentionContext:n.binding.mentionContext??[]}:null}}return{schemaVersion:1,records:i}}function O(t){if(!t||t.schemaVersion!==1)throw new o("corrupt_session_registry","Invalid registration.");if(c(t.machineId,"machine_id"),_(t.runtimeAdapter),A(t.sourceSessionKey),c(t.nianSessionId,"nian_session_id"),c(t.hostProcessIdentity,"host_process_identity"),t.panelContextKey!==t.nianSessionId)throw new o("corrupt_session_registry","Invalid panel context.");if(!Number.isInteger(t.registrationEpoch)||t.registrationEpoch<1)throw new o("corrupt_session_registry","Invalid registration epoch.");q(t.capabilities),t.binding&&B(t.binding)}function B(t){if(c(t.bindingId,"binding_id"),c(t.roomId,"room_id"),c(t.agentIdentityId,"agent_identity_id"),!b.has(t.status))throw new o("corrupt_session_registry","Invalid binding status.");if(t.status==="pending_approval"&&t.agentMembershipId!==null||(t.status==="active"||t.status==="paused")&&t.agentMembershipId===null)throw new o("corrupt_session_registry","Invalid Agent membership.");if(t.agentMembershipId!==null&&c(t.agentMembershipId,"agent_membership_id"),!S.has(t.activationMode))throw new o("corrupt_session_registry","Invalid activation mode.");if(h(t.lastDeliveredSequence),h(t.lastActivatedSequence),!Array.isArray(t.activationQueue)||t.activationQueue.length>f)throw new o("corrupt_session_registry","Invalid activation queue.");if(t.activationQueue.forEach(y),t.activeActivation!=null&&y(t.activeActivation),t.mentionContext!==void 0&&(!Array.isArray(t.mentionContext)||t.mentionContext.length>m))throw new o("corrupt_session_registry","Invalid mention context.");t.mentionContext?.forEach(w)}function w(t){if(h(t.sequence),typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3)throw new o("invalid_mention_context","Invalid mention context.")}function y(t){if(c(t.causeMessageId,"cause_message_id"),c(t.rootCauseMessageId,"root_cause_message_id"),h(t.sequence),!Number.isInteger(t.hopCount)||t.hopCount<0||t.hopCount>32)throw new o("invalid_hop_count","Invalid Agent activation hop count.");if(!Number.isInteger(t.attempts)||t.attempts<0||t.attempts>100)throw new o("invalid_activation_attempts","Invalid Agent activation attempts.");if(!Number.isFinite(new Date(t.enqueuedAt).getTime()))throw new o("invalid_activation_time","Invalid Agent activation time.");if(t.deliveryText!==void 0&&(typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3))throw new o("invalid_activation_text","Invalid Agent activation text.");if(t.systemPrompt!==void 0&&(typeof t.systemPrompt!="string"||!t.systemPrompt.trim()||t.systemPrompt.length>24e3))throw new o("invalid_activation_prompt","Invalid Agent activation prompt.");if(t.promptSnapshot!==void 0&&!E(t.promptSnapshot))throw new o("invalid_activation_prompt_snapshot","Invalid Agent activation prompt snapshot.");if(t.dispatchState!==void 0&&t.dispatchState!=="dispatching"&&t.dispatchState!=="interrupted")throw new o("invalid_activation_state","Invalid Agent activation state.");if(t.dispatchedAt!==void 0&&!Number.isFinite(new Date(t.dispatchedAt).getTime()))throw new o("invalid_activation_dispatch_time","Invalid Agent activation dispatch time.");if(t.dispatchState===void 0&&t.dispatchedAt!==void 0)throw new o("invalid_activation_dispatch_time","Queued Agent activation cannot have a dispatch time.");if(t.dispatchState==="interrupted"&&(t.interruptedAt===void 0||!Number.isFinite(new Date(t.interruptedAt).getTime())))throw new o("invalid_activation_interrupted_time","Invalid Agent activation interruption time.");if(t.dispatchState!=="interrupted"&&t.interruptedAt!==void 0)throw new o("invalid_activation_interrupted_time","Unexpected Agent activation interruption time.")}function E(t){if(!t||typeof t!="object"||Array.isArray(t))return!1;const e=t,i=s=>s===null||typeof s=="string"&&/^[a-f0-9]{64}$/.test(s);return typeof e.managedRoomPolicyVersion=="string"&&e.managedRoomPolicyVersion.length<=64&&i(e.sessionModeInstructionsHash)&&i(e.agentSystemPromptHash)&&typeof e.trustedRoomContextHash=="string"&&/^[a-f0-9]{64}$/.test(e.trustedRoomContextHash)}function x(t){return t.dispatchState==="interrupted"}function q(t){if(!t||["canIdentifySession","canBindAutomatically","canOpenEmbeddedRoomPage","canResumeIdleSession","canActivateWhileHostOpen"].some(i=>typeof t[i]!="boolean"))throw new o("invalid_session_capabilities","Invalid Agent session capabilities.")}function _(t){if(!M(t))throw new o("invalid_runtime_adapter","Unsupported Room runtime adapter.")}function A(t){if(!t||t.length>1024||/[\0\r\n]/.test(t))throw new o("invalid_source_session_key","Invalid native Agent session key.")}function c(t,e){if(!D.test(t))throw new o(`invalid_${e}`,`Invalid ${e}.`)}function h(t){if(!/^(0|[1-9]\d{0,19})$/.test(t))throw new o("invalid_room_sequence","Invalid Room sequence.")}function d(t){return structuredClone(t)}function k(t){return typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT"}export{j as RoomSessionRegistry,o as RoomSessionRegistryError,p as buildRoomSessionRegistryKey,x as isInterruptedRoomActivation};
@@ -0,0 +1,2 @@
1
+ import { type SessionDeletionsSyncResponse } from '@shennian/wire';
2
+ export declare function validateDeletionSyncResponse(payload: SessionDeletionsSyncResponse, afterRevision: number): void;
@@ -0,0 +1 @@
1
+ import{isAvailableAgentType as r}from"@shennian/wire";function l(e,o){if(!e||!Number.isSafeInteger(e.baselineRevision)||e.baselineRevision<o||!Number.isSafeInteger(e.latestRevision)||e.latestRevision<e.baselineRevision||!Array.isArray(e.deletions))throw new Error("invalid deletion sync response");let n=e.baselineRevision;for(const i of e.deletions){if(!i?.sessionId||!Number.isSafeInteger(i.deletionRevision)||i.deletionRevision!==n+1||!Array.isArray(i.sources)||Number.isNaN(Date.parse(i.deletedAt))||i.nativeCleanupPolicy!==void 0&&i.nativeCleanupPolicy!=="none"&&i.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const s of i.sources)if(!s?.sourceSessionKey||!r(s.agentType))throw new Error("invalid native source deletion command");n=i.deletionRevision}if(n!==e.latestRevision)throw new Error("invalid deletion sync revision range")}export{l as validateDeletionSyncResponse};
@@ -0,0 +1,4 @@
1
+ import { type ReqFrame } from '@shennian/wire';
2
+ import type { SessionManagerRuntime } from '../types.js';
3
+ export declare function handleAgentPermissionResponse(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
4
+ export declare function handleAcpAgentRequest(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
@@ -0,0 +1 @@
1
+ import g from"node:crypto";import{isAvailableAgentType as y}from"@shennian/wire";import{createAgent as h}from"../../agents/adapter.js";import{upsertMessage as w,recordSession as I}from"../store.js";import{requestSessionIndexPublish as S}from"../index-publisher.js";async function P(t,s){const o=typeof s.params.sessionId=="string"?s.params.sessionId:"",d=typeof s.params.permissionId=="string"?s.params.permissionId:"",e=typeof s.params.optionId=="string"?s.params.optionId:"",i=t.sessions.get(o);if(!o||!d||!e||!i?.adapter.respondToPermission){t.client.sendRes({type:"res",id:s.id,ok:!1,error:"permission_request_unavailable"});return}try{await i.adapter.respondToPermission(d,e),t.client.sendRes({type:"res",id:s.id,ok:!0,payload:{accepted:!0}})}catch(p){t.client.sendRes({type:"res",id:s.id,ok:!1,error:p instanceof Error?p.message:"permission_response_failed"})}}async function R(t,s){const o=typeof s.params.agentType=="string"?s.params.agentType:"";if(!o.startsWith("acp:")||!y(o)){t.client.sendRes({type:"res",id:s.id,ok:!1,error:"invalid_acp_agent"});return}const d=s.method==="agent.auth.start"?[...t.sessions.values()].find(a=>a.agentType===o&&a.adapter.hasPendingAuthenticationRetry?.())?.adapter:void 0;if(String(s.method)==="agent.session-options.get"){const a=typeof s.params.sessionId=="string"?s.params.sessionId:"",n=t.sessions.get(a);if(!n||n.agentType!==o||!n.adapter.getSessionOptions){t.client.sendRes({type:"res",id:s.id,ok:!1,error:"acp_session_not_active"});return}t.client.sendRes({type:"res",id:s.id,ok:!0,payload:n.adapter.getSessionOptions()});return}const e=d??h(o);if(!e){t.client.sendRes({type:"res",id:s.id,ok:!1,error:"acp_agent_not_installed"});return}const i=typeof s.params.cwd=="string"&&s.params.cwd?s.params.cwd:process.cwd(),p=!d;try{p&&await e.start(`acp-control-${g.randomUUID()}`,i);let a={ok:!0};if(s.method==="agent.external-sessions.list")a={sessions:await e.listExternalSessions?.({cwd:typeof s.params.cwd=="string"?s.params.cwd:void 0})??[]};else if(s.method==="agent.external-sessions.import"){if(!e.loadExternalSession)throw new Error("ACP session history import is unsupported");const n=String(s.params.agentSessionId??""),r=String(s.params.sessionId??"");if(!n||!r)throw new Error("ACP and Shennian session ids are required");const l=await e.loadExternalSession(n),f=Date.now();for(const[m,c]of l.entries()){const u=`acp-import-${g.createHash("sha256").update(`${o}\0${n}\0${c.sourceId}`).digest("hex")}`;w(r,{id:u,sessionId:r,role:c.role,ts:f+m,payload:c.payload})}I({sessionId:r,agentType:o,workDir:i,agentSessionId:n,title:typeof s.params.title=="string"?s.params.title.slice(0,120):void 0,titleSource:typeof s.params.title=="string"?"agent_native":void 0,status:"active"}),S(t.client),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:r,agentType:o,agentSessionId:n,workDir:i,status:"active"}}}),a={sessionId:r,agentSessionId:n,importedMessageCount:l.length}}else if(s.method==="agent.auth.start"){if(!e.authenticate)throw new Error("ACP authentication is unsupported");await e.authenticate(String(s.params.methodId??""))}else if(s.method==="agent.auth.logout"){if(!e.logout)throw new Error("ACP logout is unsupported");await e.logout()}else{const n=String(s.params.agentSessionId??"");if(!n)throw new Error("ACP session id is required");if(s.method==="agent.external-sessions.close"){if(!e.closeExternalSession)throw new Error("ACP session close is unsupported");await e.closeExternalSession(n)}else{if(!e.deleteExternalSession)throw new Error("ACP session delete is unsupported");await e.deleteExternalSession(n)}}t.client.sendRes({type:"res",id:s.id,ok:!0,payload:a})}catch(a){t.client.sendRes({type:"res",id:s.id,ok:!1,error:a instanceof Error?a.message:"acp_request_failed"})}finally{p&&await e.stop().catch(()=>{})}}export{R as handleAcpAgentRequest,P as handleAgentPermissionResponse};
@@ -3,3 +3,4 @@ import type { ManagedRoomBinder } from '../../room/managed-room-binding.js';
3
3
  import type { SessionManagerRuntime } from '../types.js';
4
4
  export declare function handleManagedRoomBind(runtime: SessionManagerRuntime, binder: ManagedRoomBinder | null, req: ReqFrame): Promise<void>;
5
5
  export declare function handleManagedRoomRestart(runtime: SessionManagerRuntime, binder: ManagedRoomBinder | null, req: ReqFrame): Promise<void>;
6
+ export declare function discardPreBindingSession(runtime: SessionManagerRuntime, nianSessionId: string): Promise<void>;
@@ -1 +1 @@
1
- import{isAvailableAgentType as w}from"@shennian/wire";const f=/^[A-Za-z0-9_.:@/-]{1,191}$/,S=new Set(["roomId","agentIdentityId","nianSessionId","assignmentIntentId","agentType","activationMode"]),l=new Set(["roomId","bindingId","agentIdentityId","previousNianSessionId","nianSessionId","assignmentIntentId","agentType"]);async function M(n,t,e){if(!t)throw new Error("Managed Room binding is unavailable.");if(Object.keys(e.params).some(g=>!S.has(g)))throw new Error("Managed Room binding contains unsupported parameters.");const d=a(e.params.roomId,"roomId"),r=a(e.params.agentIdentityId,"agentIdentityId"),o=a(e.params.nianSessionId,"nianSessionId"),s=a(e.params.assignmentIntentId,"assignmentIntentId"),i=c(e.params.agentType),I=R(e.params.activationMode);await p(n,o);const m=await t.bind({roomId:d,agentIdentityId:r,nianSessionId:o,assignmentIntentId:s,sourceSessionKey:y(o),agentType:i,activationMode:I});n.client.sendRes({type:"res",id:e.id,ok:!0,payload:m})}async function h(n,t,e){if(!t)throw new Error("Managed Room binding is unavailable.");if(Object.keys(e.params).some(u=>!l.has(u)))throw new Error("Managed Room restart contains unsupported parameters.");const d=a(e.params.roomId,"roomId"),r=a(e.params.bindingId,"bindingId"),o=a(e.params.agentIdentityId,"agentIdentityId"),s=a(e.params.previousNianSessionId,"previousNianSessionId"),i=a(e.params.nianSessionId,"nianSessionId"),I=a(e.params.assignmentIntentId,"assignmentIntentId"),m=c(e.params.agentType);await p(n,s),n.chatQueue?.purgeSession(s),await p(n,i);const g=await t.restart({roomId:d,bindingId:r,agentIdentityId:o,previousNianSessionId:s,nianSessionId:i,assignmentIntentId:I,sourceSessionKey:y(i),agentType:m});n.client.sendRes({type:"res",id:e.id,ok:!0,payload:g})}function a(n,t){if(typeof n!="string"||!f.test(n))throw new Error(`Invalid ${t}.`);return n}function c(n){if(typeof n!="string"||!w(n)||n==="manager"||n==="pi"||n==="openclaw")throw new Error("managed_agent_runtime_unsupported");return n}function R(n){if(n===void 0)return"auto_follow";if(n==="auto_follow"||n==="mention_only"||n==="paused")return n;throw new Error("Invalid activationMode.")}function y(n){return`managed:${n}`}async function p(n,t){const e=n.sessions.get(t);e&&(n.sessions.delete(t),e.managedRoomContextToken&&(n.revokeManagedRoomContextToken?.(e.managedRoomContextToken),e.managedRoomContextToken=null),e.heartbeatTimer&&clearInterval(e.heartbeatTimer),e.adapter.removeAllListeners(),await e.adapter.stop())}export{M as handleManagedRoomBind,h as handleManagedRoomRestart};
1
+ import{isAvailableAgentType as w}from"@shennian/wire";const f=/^[A-Za-z0-9_.:@/-]{1,191}$/,S=new Set(["roomId","agentIdentityId","nianSessionId","assignmentIntentId","agentType","activationMode"]),l=new Set(["roomId","bindingId","agentIdentityId","previousNianSessionId","nianSessionId","assignmentIntentId","agentType"]);async function M(n,t,e){if(!t)throw new Error("Managed Room binding is unavailable.");if(Object.keys(e.params).some(m=>!S.has(m)))throw new Error("Managed Room binding contains unsupported parameters.");const r=a(e.params.roomId,"roomId"),d=a(e.params.agentIdentityId,"agentIdentityId"),o=a(e.params.nianSessionId,"nianSessionId"),s=a(e.params.assignmentIntentId,"assignmentIntentId"),i=c(e.params.agentType),I=R(e.params.activationMode);await p(n,o);const g=await t.bind({roomId:r,agentIdentityId:d,nianSessionId:o,assignmentIntentId:s,sourceSessionKey:y(o),agentType:i,activationMode:I});n.client.sendRes({type:"res",id:e.id,ok:!0,payload:g})}async function b(n,t,e){if(!t)throw new Error("Managed Room binding is unavailable.");if(Object.keys(e.params).some(u=>!l.has(u)))throw new Error("Managed Room restart contains unsupported parameters.");const r=a(e.params.roomId,"roomId"),d=a(e.params.bindingId,"bindingId"),o=a(e.params.agentIdentityId,"agentIdentityId"),s=a(e.params.previousNianSessionId,"previousNianSessionId"),i=a(e.params.nianSessionId,"nianSessionId"),I=a(e.params.assignmentIntentId,"assignmentIntentId"),g=c(e.params.agentType);await p(n,s),n.chatQueue?.purgeSession(s),await p(n,i);const m=await t.restart({roomId:r,bindingId:d,agentIdentityId:o,previousNianSessionId:s,nianSessionId:i,assignmentIntentId:I,sourceSessionKey:y(i),agentType:g});n.client.sendRes({type:"res",id:e.id,ok:!0,payload:m})}function a(n,t){if(typeof n!="string"||!f.test(n))throw new Error(`Invalid ${t}.`);return n}function c(n){if(typeof n!="string"||!w(n)||n==="manager"||n==="pi"||n==="openclaw")throw new Error("managed_agent_runtime_unsupported");return n}function R(n){if(n===void 0)return"auto_follow";if(n==="auto_follow"||n==="mention_only"||n==="paused")return n;throw new Error("Invalid activationMode.")}function y(n){return`managed:${n}`}async function p(n,t){n.runJournal?.markSessionAborted(t),n.recoveryBarrier?.end(t);const e=n.sessions.get(t);e&&(n.sessions.delete(t),e.managedRoomContextToken&&(n.revokeManagedRoomContextToken?.(e.managedRoomContextToken),e.managedRoomContextToken=null),e.heartbeatTimer&&clearInterval(e.heartbeatTimer),e.adapter.removeAllListeners(),await e.adapter.stop())}export{p as discardPreBindingSession,M as handleManagedRoomBind,b as handleManagedRoomRestart};
@@ -68,10 +68,8 @@ export declare class SessionManager {
68
68
  params: ManagedRoomEnqueueParams;
69
69
  context: ManagedRoomQueueContext;
70
70
  }>): Promise<void>;
71
+ retireManagedRoomSession(sessionId: string): Promise<void>;
71
72
  discardPendingManagedRoomMessages(sessionId: string): number;
72
- private managedChatContext;
73
- private dispatchManagedChat;
74
- private dispatchQueuedChat;
75
73
  synchronizeSessionDeletions(): Promise<void>;
76
74
  private synchronizeSessionDeletionsOnce;
77
75
  private applySessionDeletion;
@@ -79,8 +77,6 @@ export declare class SessionManager {
79
77
  private handleExternalTerminal;
80
78
  private publishManagedActivitySnapshots;
81
79
  private reloadCustomAgents;
82
- private handleAgentPermissionResponse;
83
- private handleAcpAgentRequest;
84
80
  handleReq(req: ReqFrame, options?: {
85
81
  propagateErrors?: boolean;
86
82
  }): Promise<void>;
@@ -1,3 +1,3 @@
1
- import{SESSION_SYNC_PROTOCOL_VERSION as b,SESSION_SYNC_DATA_EPOCH as S,isAvailableAgentType as k}from"@shennian/wire";import m from"node:crypto";import{createAgent as E,getRegisteredAgents as x,unregisterAgent as M}from"../agents/adapter.js";import{resetCommandSpecCache as D}from"../agents/command-spec.js";import{loadConfig as _}from"../config/index.js";import{handleUpgradeStart as F,handleUpgradeStatus as O}from"../commands/upgrade.js";import{handleAgentsRefresh as j,handleModelsRefresh as $,refreshMachineAgents as U}from"./handlers/agents.js";import{AcpDiscoveryWatcher as Q}from"../agents/acp/watcher.js";import{handleAgentCapabilitiesList as z}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as B,handleAgentConfigGet as N,handleAgentConfigTest as W,handleAgentConfigUpsert as H}from"./handlers/agent-config.js";import{handleChatAbort as J,handleChatSend as v,recoverJournaledRun as V}from"./handlers/chat.js";import{handleSessionRefresh as L}from"./handlers/session-refresh.js";import{handleSessionToolDetail as G}from"./handlers/tool-detail.js";import{handleSessionTitleSet as Y}from"./handlers/title.js";import{handleSessionMessageStatus as K}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as X}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as Z}from"./handlers/voice-window.js";import{handleRemoteAccessPause as q}from"./handlers/remote-access.js";import{cleanupPendingTransfers as ee,handleFsLs as se,handleFsRead as te,handleFsRename as ae,handleFsWrite as ne,handleFsTransferAbort as ie,handleFsTransferChunk as re,handleFsTransferFinish as oe,handleFsTransferStart as ce,handleFsExportMarkdownPdf as de,handleFsExportMarkdownPdfSetup as le,handleFsArchiveZip as he}from"./handlers/fs.js";import{handleRegionProbe as ue,handleRegionSwitch as pe,handleUpgradeSetPolicy as me}from"./handlers/control.js";import{ManagerRuntimeService as ge,setManagerRuntimeService as I}from"../manager/runtime.js";import{ChatQueueManager as fe,TerminalQueuedChatDispatchError as ve}from"./queue.js";import{createAuthorizedFsRoot as ye,createAuthorizedFsRootAsync as Re,resolveAuthorizedPath as we,resolveAuthorizedPathAsync as be,resolveSessionWorkDir as Se}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as ke}from"../agents/custom.js";import{handleManagedRoomBind as Ie,handleManagedRoomRestart as Ae}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as R,purgeCanonicalSession as Te,purgeSessionRecord as Pe,recordSession as A,upsertMessage as T}from"./store.js";import{requestSessionIndexPublish as P}from"./index-publisher.js";import{reportUpgradeAggregateMetric as Ce}from"../log-reporter.js";import{PersonalHistorySubscriptions as Ee}from"./history-subscriptions.js";import{sweepAttachmentTransfers as xe}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as Me}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as De}from"./tool-detail-store.js";import{readDeletionSyncRevision as _e,writeDeletionSyncRevision as C}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as Fe,clearDeletionSyncFailure as Oe,DeletionSyncRetryWaitError as je,DeletionSyncTerminalError as $e,recordDeletionSyncFailure as w}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Ue}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as p}from"../personal-sync/epoch.js";import{ActiveRunJournal as Qe}from"./run-recovery/journal.js";import{RunRecoveryBarrier as ze}from"./run-recovery/barrier.js";import{RunRecoveryCoordinator as Be}from"./run-recovery/coordinator.js";import{ProjectFsHandleStore as Ne}from"./project-fs-store.js";import{handleProjectFsRequest as We}from"./handlers/project-fs.js";const He=50;function Je(l,e){if(!l||!Number.isSafeInteger(l.baselineRevision)||l.baselineRevision<e||!Number.isSafeInteger(l.latestRevision)||l.latestRevision<l.baselineRevision||!Array.isArray(l.deletions))throw new Error("invalid deletion sync response");let t=l.baselineRevision;for(const s of l.deletions){if(!s?.sessionId||!Number.isSafeInteger(s.deletionRevision)||s.deletionRevision!==t+1||!Array.isArray(s.sources)||Number.isNaN(Date.parse(s.deletedAt))||s.nativeCleanupPolicy!==void 0&&s.nativeCleanupPolicy!=="none"&&s.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of s.sources)if(!a?.sourceSessionKey||!k(a.agentType))throw new Error("invalid native source deletion command");t=s.deletionRevision}if(t!==l.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as Hs}from"../fs/boundary.js";class Bs{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;trustedProjectFsRequestResolver;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;acpDiscoveryWatcher;runJournal;recoveryBarrier=new ze;recoveryCoordinator;recoveryPass=null;prepareManagedRecovery;projectFsHandleStore=new Ne;constructor(e,t=null,s,a=null,i=null,d=null,r=null,o=null,u=null,g=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=d,this.managedRoomContextRevoker=r,this.managedRoomExecutor=o,this.trustedProjectFsRequestResolver=g,this.runJournal=u??new Qe(y());for(const n of this.runJournal.loadPendingReconciliation().records)this.recoveryBarrier.begin(n.sessionId);this.recoveryCoordinator=new Be({journal:this.runJournal,barrier:this.recoveryBarrier,recover:(n,c)=>V(this.getRuntime(),n,c),commitTerminal:(n,c)=>this.commitRecoveryTerminal(n,c),isCanonicalTerminal:n=>{const c=R().find(f=>f.sessionId===n.sessionId),h=`terminal:${m.createHash("sha256").update(`${n.runId}\0`).digest("hex")}`;return c?.lastAttentionId===h},onOutcome:n=>Ce({kind:"recovery",adapterType:n.adapterType,result:n.result,stage:n.result==="continued"?"continue":n.result==="reattached"?"reattach":n.result==="resumed"?"resume":n.result==="terminal_reconciled"?"inspect":"terminal",durationMs:n.durationMs,retries:Math.max(0,n.attempts-1),...n.reason?{errorCode:n.reason}:{}})}),this.managerRuntime=new ge({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n,{propagateErrors:!0})}),this.chatQueue=new fe({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n),dispatchManagedReq:(n,c)=>this.dispatchManagedChat(n,c),dispatchQueuedReq:(n,c)=>this.dispatchQueuedChat(n,c)}),this.historySubscriptions=new Ee(this.client),this.nativeCleanupOutbox=new Ue({logger:console.info}),this.acpDiscoveryWatcher=new Q(async()=>{D(),await U(this.getRuntime())}),this.nativeFusion?.setExternalTerminalHandler?.(n=>{this.handleExternalTerminal(n)}),this.nativeCleanupOutbox.start(),I(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents();try{this.acpDiscoveryWatcher.start()}catch(n){console.warn(`[acp.discovery] watcher unavailable: ${n instanceof Error?n.message:String(n)}`)}this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...R().map(c=>c.workDir),...Array.from(this.pendingTransfers.values()).map(c=>c.rootPath)]);for(const c of n)try{xe(this.resolvePath(c))}catch(h){console.error("[attachment.cleanup] failed",h instanceof Error?h.message:String(h))}},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),resolveAuthorizedPathAsync:(e,t)=>this.resolveAuthorizedPathAsync(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,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor,runJournal:this.runJournal,recoveryBarrier:this.recoveryBarrier,prepareManagedRecovery:this.prepareManagedRecovery}}async resumePendingAcpAuthentication(e){const t=[...this.sessions.values()].find(s=>s.agentType===e&&s.adapter.hasPendingAuthenticationRetry?.());return t?.adapter.resumeAfterExternalAuthentication?(await t.adapter.resumeAfterExternalAuthentication(),!0):!1}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}}prepareForUpgrade(e){this.recoveryBarrier.freezeDispatch();const t=this.runJournal.markPlannedUpgrade(e);return this.runJournal.sync(),{activeRunCount:t}}cancelUpgradePreparation(){this.recoveryBarrier.unfreezeDispatch()}async recoverInterruptedRuns(){this.recoveryPass??=this.recoveryCoordinator.recoverAll(),await this.recoveryPass}getPendingRecoveryCount(){return this.runJournal.loadPendingReconciliation().records.length}revokeProjectFsHandles(){this.projectFsHandleStore.revokeAll()}async commitRecoveryTerminal(e,t){const s=this.sessions.get(e.sessionId);s&&(s.currentRunId=null,s.currentRunPhase=null,s.journalRunId=null,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null)),this.runTextAcc.delete(`${e.sessionId}:${e.providerRunId??e.runId}`),this.publishSessionActivity(e.sessionId,null);const a=t.result==="interrupted_by_upgrade"?`\u4EFB\u52A1\u56E0\u795E\u5FF5\u5347\u7EA7\u800C\u4E2D\u65AD\uFF08${t.reason}\uFF09\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002
1
+ import{SESSION_SYNC_PROTOCOL_VERSION as g,SESSION_SYNC_DATA_EPOCH as f}from"@shennian/wire";import h from"node:crypto";import{getRegisteredAgents as w,unregisterAgent as I}from"../agents/adapter.js";import{resetCommandSpecCache as A}from"../agents/command-spec.js";import{loadConfig as T}from"../config/index.js";import{handleUpgradeStart as P,handleUpgradeStatus as C}from"../commands/upgrade.js";import{handleAgentsRefresh as M,handleModelsRefresh as D,refreshMachineAgents as x}from"./handlers/agents.js";import{AcpDiscoveryWatcher as E}from"../agents/acp/watcher.js";import{handleAgentCapabilitiesList as F}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as O,handleAgentConfigGet as _,handleAgentConfigTest as j,handleAgentConfigUpsert as B}from"./handlers/agent-config.js";import{handleChatAbort as Q,handleChatSend as U,recoverJournaledRun as z}from"./handlers/chat.js";import{handleSessionRefresh as $}from"./handlers/session-refresh.js";import{handleSessionToolDetail as W}from"./handlers/tool-detail.js";import{handleSessionTitleSet as J}from"./handlers/title.js";import{handleSessionMessageStatus as N}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as H}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as V}from"./handlers/voice-window.js";import{handleRemoteAccessPause as L}from"./handlers/remote-access.js";import{cleanupPendingTransfers as G,handleFsLs as Y,handleFsRead as K,handleFsRename as X,handleFsWrite as Z,handleFsTransferAbort as q,handleFsTransferChunk as ee,handleFsTransferFinish as se,handleFsTransferStart as te,handleFsExportMarkdownPdf as ae,handleFsExportMarkdownPdfSetup as ne,handleFsArchiveZip as re}from"./handlers/fs.js";import{handleRegionProbe as ie,handleRegionSwitch as oe,handleUpgradeSetPolicy as ce}from"./handlers/control.js";import{ManagerRuntimeService as le,setManagerRuntimeService as v}from"../manager/runtime.js";import{ChatQueueManager as de}from"./queue.js";import{createAuthorizedFsRoot as he,createAuthorizedFsRootAsync as ue,resolveAuthorizedPath as pe,resolveAuthorizedPathAsync as me,resolveSessionWorkDir as ge}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/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as fe}from"../agents/custom.js";import{handleManagedRoomBind as ve,handleManagedRoomRestart as ye,discardPreBindingSession as Re}from"./handlers/room-managed.js";import{getDaemonInstallationId as u,listSessionRecords as p,purgeCanonicalSession as be,purgeSessionRecord as Se,recordSession as ke,upsertMessage as we}from"./store.js";import{requestSessionIndexPublish as Ie}from"./index-publisher.js";import{reportUpgradeAggregateMetric as Ae}from"../log-reporter.js";import{PersonalHistorySubscriptions as Te}from"./history-subscriptions.js";import{sweepAttachmentTransfers as Pe}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as Ce}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as Me}from"./tool-detail-store.js";import{readDeletionSyncRevision as De,writeDeletionSyncRevision as y}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as xe,clearDeletionSyncFailure as Ee,DeletionSyncRetryWaitError as Fe,DeletionSyncTerminalError as Oe,recordDeletionSyncFailure as m}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as _e}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as c}from"../personal-sync/epoch.js";import{ActiveRunJournal as je}from"./run-recovery/journal.js";import{RunRecoveryBarrier as Be}from"./run-recovery/barrier.js";import{RunRecoveryCoordinator as Qe}from"./run-recovery/coordinator.js";import{ProjectFsHandleStore as Ue}from"./project-fs-store.js";import{handleProjectFsRequest as ze}from"./handlers/project-fs.js";import{handleAgentPermissionResponse as $e,handleAcpAgentRequest as We}from"./handlers/agent-control.js";import{validateDeletionSyncResponse as Je}from"./deletion-sync-validation.js";import{dispatchManagedChat as Ne,dispatchQueuedChat as He}from"./queued-chat-dispatch.js";const Ve=50;import{resolveSessionWorkDir as Ys}from"../fs/boundary.js";class Vs{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;trustedProjectFsRequestResolver;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;acpDiscoveryWatcher;runJournal;recoveryBarrier=new Be;recoveryCoordinator;recoveryPass=null;prepareManagedRecovery;projectFsHandleStore=new Ue;constructor(e,t=null,s,n=null,r=null,d=null,o=null,R=null,b=null,S=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=n,this.managedRoomBinder=r,this.remoteAccessPauseController=d,this.managedRoomContextRevoker=o,this.managedRoomExecutor=R,this.trustedProjectFsRequestResolver=S,this.runJournal=b??new je(u());for(const a of this.runJournal.loadPendingReconciliation().records)this.recoveryBarrier.begin(a.sessionId);this.recoveryCoordinator=new Qe({journal:this.runJournal,barrier:this.recoveryBarrier,recover:(a,i)=>z(this.getRuntime(),a,i),commitTerminal:(a,i)=>this.commitRecoveryTerminal(a,i),isCanonicalTerminal:a=>{const i=p().find(k=>k.sessionId===a.sessionId),l=`terminal:${h.createHash("sha256").update(`${a.runId}\0`).digest("hex")}`;return i?.lastAttentionId===l},onOutcome:a=>Ae({kind:"recovery",adapterType:a.adapterType,result:a.result,stage:a.result==="continued"?"continue":a.result==="reattached"?"reattach":a.result==="resumed"?"resume":a.result==="terminal_reconciled"?"inspect":"terminal",durationMs:a.durationMs,retries:Math.max(0,a.attempts-1),...a.reason?{errorCode:a.reason}:{}})}),this.managerRuntime=new le({getRuntime:()=>this.getRuntime(),dispatchReq:a=>this.handleReq(a,{propagateErrors:!0})}),this.chatQueue=new de({getRuntime:()=>this.getRuntime(),dispatchReq:a=>this.handleReq(a),dispatchManagedReq:(a,i)=>Ne(this.getRuntime(),a,i),dispatchQueuedReq:(a,i)=>He(this.getRuntime(),a,i)}),this.historySubscriptions=new Te(this.client),this.nativeCleanupOutbox=new _e({logger:console.info}),this.acpDiscoveryWatcher=new E(async()=>{A(),await x(this.getRuntime())}),this.nativeFusion?.setExternalTerminalHandler?.(a=>{this.handleExternalTerminal(a)}),this.nativeCleanupOutbox.start(),v(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents();try{this.acpDiscoveryWatcher.start()}catch(a){console.warn(`[acp.discovery] watcher unavailable: ${a instanceof Error?a.message:String(a)}`)}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([...p().map(i=>i.workDir),...Array.from(this.pendingTransfers.values()).map(i=>i.rootPath)]);for(const i of a)try{Pe(this.resolvePath(i))}catch(l){console.error("[attachment.cleanup] failed",l instanceof Error?l.message:String(l))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),resolveAuthorizedPathAsync:(e,t)=>this.resolveAuthorizedPathAsync(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,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor,runJournal:this.runJournal,recoveryBarrier:this.recoveryBarrier,prepareManagedRecovery:this.prepareManagedRecovery}}async resumePendingAcpAuthentication(e){const t=[...this.sessions.values()].find(s=>s.agentType===e&&s.adapter.hasPendingAuthenticationRetry?.());return t?.adapter.resumeAfterExternalAuthentication?(await t.adapter.resumeAfterExternalAuthentication(),!0):!1}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}}prepareForUpgrade(e){this.recoveryBarrier.freezeDispatch();const t=this.runJournal.markPlannedUpgrade(e);return this.runJournal.sync(),{activeRunCount:t}}cancelUpgradePreparation(){this.recoveryBarrier.unfreezeDispatch()}async recoverInterruptedRuns(){this.recoveryPass??=this.recoveryCoordinator.recoverAll(),await this.recoveryPass}getPendingRecoveryCount(){return this.runJournal.loadPendingReconciliation().records.length}revokeProjectFsHandles(){this.projectFsHandleStore.revokeAll()}async commitRecoveryTerminal(e,t){const s=this.sessions.get(e.sessionId);s&&(s.currentRunId=null,s.currentRunPhase=null,s.journalRunId=null,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null)),this.runTextAcc.delete(`${e.sessionId}:${e.providerRunId??e.runId}`),this.publishSessionActivity(e.sessionId,null);const n=t.result==="interrupted_by_upgrade"?`\u4EFB\u52A1\u56E0\u795E\u5FF5\u5347\u7EA7\u800C\u4E2D\u65AD\uFF08${t.reason}\uFF09\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002
2
2
  Task interrupted by the Shennian upgrade (${t.reason}). The session is preserved and ready for a new message.`:`\u795E\u5FF5\u5347\u7EA7\u540E\u672A\u80FD\u6062\u590D\u4EFB\u52A1\uFF08${t.reason}\uFF09\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u91CD\u8BD5\u6216\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002
3
- The task could not be recovered after the Shennian upgrade (${t.reason}). The session is preserved; retry or send a new message.`;T(e.sessionId,{id:`upgrade-recovery-terminal-${e.runId}`,sessionId:e.sessionId,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:"error",content:a,error:!0,recoveryState:t.result,recoveryReason:t.reason})}),A({sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,status:"failed",attentionId:`upgrade-recovery:${e.runId}`,lastActivityAt:new Date().toISOString()}),P(this.client);const i=e.lastObservedEventSeq+1;this.client.sendAgentEvent({type:"event",event:"agent",payload:t.result==="interrupted_by_upgrade"?{state:"interrupted_by_upgrade",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:i,reason:t.reason}:{state:"recovery_failed",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:i,reason:t.reason,...t.message?{message:t.message}:{}},seq:i,id:`agent-recovery-terminal-${e.runId}`}),this.chatQueue.noteTerminal(e.sessionId)}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}setManagedAgentDirectTurnPreparer(e){this.chatQueue.setManagedAgentDirectTurnPreparer(e)}setManagedRecoveryPreparer(e){this.prepareManagedRecovery=e}async enqueueManagedRoomBatch(e){const t=e.map(s=>{const a=R().find(i=>i.sessionId===s.params.sessionId);return{context:s.context,params:{...s.params,modelId:a?.modelId??s.params.modelId,sessionMode:a?.sessionMode??s.params.sessionMode,managerConfig:a?.managerConfig??s.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??s.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??s.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(t)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}managedChatContext(e){return{source:"room_activation",managedAgentPolicy:e.managedAgentPolicy,managedRoomPromptSnapshot:e.managedRoomPromptSnapshot}}async dispatchManagedChat(e,t){await v(this.getRuntime(),e,this.managedChatContext(t))}async dispatchQueuedChat(e,t){const s={value:null},a=new Proxy(this.client,{get:(i,d)=>{if(d==="sendRes")return o=>{s.value=o};const r=Reflect.get(i,d,i);return typeof r=="function"?r.bind(i):r}});if(await v({...this.getRuntime(),client:a},e,t?this.managedChatContext(t):void 0),s.value?.payload?.deliveryState==="failed")throw new ve(s.value.payload.failureMessage??s.value.error??"queued_chat_dispatch_failed");if(!s.value?.ok)throw new Error(s.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){Fe();try{await this.synchronizeSessionDeletionsOnce(),Oe()}catch(e){if(e instanceof je||e instanceof $e)throw e;const t=e instanceof Error?e.message:String(e),s=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(t)?"transport_unavailable":/invalid (?:deletion sync|session deletion)|deletion revision gap/i.test(t)?"invalid_deletion_sync_response":"deletion_sync_failed";throw console.warn("[session.deletions] retrying failed synchronization",{code:s}),w({code:s,retryable:!0}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),t=_e(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${m.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:b,dataEpoch:S,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const r=s.rejection;throw r?(w({code:r.code,retryable:r.retryable&&r.requiredAction==="retry"}),new Error(r.code)):(w({code:"unstructured_rejection",retryable:!0}),new Error(s.error??"unstructured_rejection"))}const a=s.payload;p(a),Je(a,t);let i=a.baselineRevision;i>t&&C(i);for(const r of a.deletions)r.deletionRevision<=i||(await this.applySessionDeletion(r),i=r.deletionRevision,C(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const d=await this.client.sendReq({type:"req",id:`session-deletions-ack-${m.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:b,dataEpoch:S,daemonInstallationId:e,deletionRevision:i}});if(!d.ok)throw new Error(d.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new Me({daemonInstallationId:y()});t.recordSession(e);for(const a of e.sources){const i=t.find(a.agentType,a.sourceSessionKey);i&&i.sessionId!==e.sessionId||t.record({...e,...a})}this.runJournal.markSessionAborted(e.sessionId),this.recoveryBarrier.end(e.sessionId);const s=this.sessions.get(e.sessionId);if(s){this.revokeManagedRoomToken(s);try{await s.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),De(e.sessionId),Te(e.sessionId),Pe(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async handleExternalTerminal(e){const t=this.sessions.get(e);t&&(t.journalRunId&&(this.recoveryCoordinator.completeFromProvider(e,t.journalRunId),t.journalRunId=null),this.sessions.delete(e),this.revokeManagedRoomToken(t),t.heartbeatTimer&&clearInterval(t.heartbeatTimer),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}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 a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:a,canStop:s.canStop??!0});const i=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:i,runPhase:s.runPhase,canStop:s.canStop??!0},seq:i,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of x())t.startsWith("custom:")&&M(t);const e=_();for(const[t,s]of Object.entries(e.customAgents??{}))ke(t,s)}async handleAgentPermissionResponse(e){const t=typeof e.params.sessionId=="string"?e.params.sessionId:"",s=typeof e.params.permissionId=="string"?e.params.permissionId:"",a=typeof e.params.optionId=="string"?e.params.optionId:"",i=this.sessions.get(t);if(!t||!s||!a||!i?.adapter.respondToPermission){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"permission_request_unavailable"});return}try{await i.adapter.respondToPermission(s,a),this.client.sendRes({type:"res",id:e.id,ok:!0,payload:{accepted:!0}})}catch(d){this.client.sendRes({type:"res",id:e.id,ok:!1,error:d instanceof Error?d.message:"permission_response_failed"})}}async handleAcpAgentRequest(e){const t=typeof e.params.agentType=="string"?e.params.agentType:"";if(!t.startsWith("acp:")||!k(t)){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"invalid_acp_agent"});return}const s=e.method==="agent.auth.start"?[...this.sessions.values()].find(r=>r.agentType===t&&r.adapter.hasPendingAuthenticationRetry?.())?.adapter:void 0;if(String(e.method)==="agent.session-options.get"){const r=typeof e.params.sessionId=="string"?e.params.sessionId:"",o=this.sessions.get(r);if(!o||o.agentType!==t||!o.adapter.getSessionOptions){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"acp_session_not_active"});return}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:o.adapter.getSessionOptions()});return}const a=s??E(t);if(!a){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"acp_agent_not_installed"});return}const i=typeof e.params.cwd=="string"&&e.params.cwd?e.params.cwd:process.cwd(),d=!s;try{d&&await a.start(`acp-control-${m.randomUUID()}`,i);let r={ok:!0};if(e.method==="agent.external-sessions.list")r={sessions:await a.listExternalSessions?.({cwd:typeof e.params.cwd=="string"?e.params.cwd:void 0})??[]};else if(e.method==="agent.external-sessions.import"){if(!a.loadExternalSession)throw new Error("ACP session history import is unsupported");const o=String(e.params.agentSessionId??""),u=String(e.params.sessionId??"");if(!o||!u)throw new Error("ACP and Shennian session ids are required");const g=await a.loadExternalSession(o),n=Date.now();for(const[c,h]of g.entries()){const f=`acp-import-${m.createHash("sha256").update(`${t}\0${o}\0${h.sourceId}`).digest("hex")}`;T(u,{id:f,sessionId:u,role:h.role,ts:n+c,payload:h.payload})}A({sessionId:u,agentType:t,workDir:i,agentSessionId:o,title:typeof e.params.title=="string"?e.params.title.slice(0,120):void 0,titleSource:typeof e.params.title=="string"?"agent_native":void 0,status:"active"}),P(this.client),this.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:u,agentType:t,agentSessionId:o,workDir:i,status:"active"}}}),r={sessionId:u,agentSessionId:o,importedMessageCount:g.length}}else if(e.method==="agent.auth.start"){if(!a.authenticate)throw new Error("ACP authentication is unsupported");await a.authenticate(String(e.params.methodId??""))}else if(e.method==="agent.auth.logout"){if(!a.logout)throw new Error("ACP logout is unsupported");await a.logout()}else{const o=String(e.params.agentSessionId??"");if(!o)throw new Error("ACP session id is required");if(e.method==="agent.external-sessions.close"){if(!a.closeExternalSession)throw new Error("ACP session close is unsupported");await a.closeExternalSession(o)}else{if(!a.deleteExternalSession)throw new Error("ACP session delete is unsupported");await a.deleteExternalSession(o)}}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:r})}catch(r){this.client.sendRes({type:"res",id:e.id,ok:!1,error:r instanceof Error?r.message:"acp_request_failed"})}finally{d&&await a.stop().catch(()=>{})}}async handleReq(e,t={}){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":p(e.params),this.recoveryBarrier.isDispatchFrozen()||this.recoveryBarrier.isRecovering(String(e.params.sessionId??""))?await this.chatQueue.handleEnqueue({...e,method:"chat.enqueue"}):await v(s,e);break;case"chat.enqueue":p(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":p(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":p(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":p(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":p(e.params),await J(s,e);break;case"session.refresh":p(e.params),await L(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":K(s,e);break;case"session.tool.detail":await G(s,e);break;case"voice.session.body.window":Z(s,e);break;case"session.title.set":await Y(s,e);break;case"room.agent.bind-managed":await Ie(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await Ae(s,this.managedRoomBinder,e);break;case"fs.ls":await se(s,e);break;case"project.fs.open":case"project.fs.list":case"project.fs.read":case"project.fs.close":await We(this.client,this.projectFsHandleStore,e,this.trustedProjectFsRequestResolver);break;case"fs.read":await te(s,e);break;case"fs.write":await ne(s,e);break;case"fs.export.markdown-pdf":await de(s,e);break;case"fs.export.markdown-pdf.setup":await le(s,e);break;case"fs.archive.zip":await he(s,e);break;case"fs.rename":await ae(s,e);break;case"fs.transfer.start":await ce(s,e);break;case"fs.transfer.chunk":await re(s,e);break;case"fs.transfer.finish":await oe(s,e);break;case"fs.transfer.abort":await ie(s,e);break;case"region.probe":await ue(s,e);break;case"region.switch":await pe(s,e);break;case"upgrade.start":await F(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0,prepareForUpgrade:a=>this.prepareForUpgrade(a),cancelUpgradePreparation:()=>this.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()}});break;case"upgrade.status":await O(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await me(s,e);break;case"machine.remote-access.pause":await q(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await j(s,e);break;case"agent.permission.respond":await this.handleAgentPermissionResponse(e);break;case"agent.auth.start":case"agent.auth.logout":case"agent.session-options.get":case"agent.external-sessions.list":case"agent.external-sessions.import":case"agent.external-sessions.close":case"agent.external-sessions.delete":await this.handleAcpAgentRequest(e);break;case"models.refresh":await $(s,e);break;case"agent.config.get":await N(s,e);break;case"agent.config.upsert":await H(s,e);break;case"agent.config.clear":await B(s,e);break;case"agent.config.test":await W(s,e);break;case"agent.workspace.validate":await X(s,e);break;case"agent.capabilities.list":await z(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(a){if(t.propagateErrors)throw 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<He)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(this.revokeManagedRoomToken(e.session),e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return Se(e)}resolveAuthorizedPath(e,t){return we(e,ye(t))}async resolveAuthorizedPathAsync(e,t){return be(e,await Re(t))}async cleanup(){this.projectFsHandleStore.revokeAll(),this.acpDiscoveryWatcher.stop(),this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),ee(this.getRuntime()),await this.managerRuntime.stop(),I(null)}revokeManagedRoomToken(e){const t=e.managedRoomContextToken;e.managedRoomContextToken=null,t&&this.managedRoomContextRevoker?.(t)}}export{Bs as SessionManager,Hs as resolveSessionWorkDir};
3
+ The task could not be recovered after the Shennian upgrade (${t.reason}). The session is preserved; retry or send a new message.`;we(e.sessionId,{id:`upgrade-recovery-terminal-${e.runId}`,sessionId:e.sessionId,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:"error",content:n,error:!0,recoveryState:t.result,recoveryReason:t.reason})}),ke({sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,status:"failed",attentionId:`upgrade-recovery:${e.runId}`,lastActivityAt:new Date().toISOString()}),Ie(this.client);const r=e.lastObservedEventSeq+1;this.client.sendAgentEvent({type:"event",event:"agent",payload:t.result==="interrupted_by_upgrade"?{state:"interrupted_by_upgrade",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:r,reason:t.reason}:{state:"recovery_failed",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:r,reason:t.reason,...t.message?{message:t.message}:{}},seq:r,id:`agent-recovery-terminal-${e.runId}`}),this.chatQueue.noteTerminal(e.sessionId)}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}setManagedAgentDirectTurnPreparer(e){this.chatQueue.setManagedAgentDirectTurnPreparer(e)}setManagedRecoveryPreparer(e){this.prepareManagedRecovery=e}async enqueueManagedRoomBatch(e){const t=e.map(s=>{const n=p().find(r=>r.sessionId===s.params.sessionId);return{context:s.context,params:{...s.params,modelId:n?.modelId??s.params.modelId,sessionMode:n?.sessionMode??s.params.sessionMode,managerConfig:n?.managerConfig??s.params.managerConfig,managerDefaultWorkerAgentType:n?.managerDefaultWorkerAgentType??s.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:n?.managerDefaultWorkerModelId??s.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(t)}async retireManagedRoomSession(e){this.chatQueue.purgeSession(e),await Re(this.getRuntime(),e)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}async synchronizeSessionDeletions(){xe();try{await this.synchronizeSessionDeletionsOnce(),Ee()}catch(e){if(e instanceof Fe||e instanceof Oe)throw e;const t=e instanceof Error?e.message:String(e),s=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(t)?"transport_unavailable":/invalid (?:deletion sync|session deletion)|deletion revision gap/i.test(t)?"invalid_deletion_sync_response":"deletion_sync_failed";throw console.warn("[session.deletions] retrying failed synchronization",{code:s}),m({code:s,retryable:!0}),e}}async synchronizeSessionDeletionsOnce(){const e=u(),t=De(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${h.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:g,dataEpoch:f,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const o=s.rejection;throw o?(m({code:o.code,retryable:o.retryable&&o.requiredAction==="retry"}),new Error(o.code)):(m({code:"unstructured_rejection",retryable:!0}),new Error(s.error??"unstructured_rejection"))}const n=s.payload;c(n),Je(n,t);let r=n.baselineRevision;r>t&&y(r);for(const o of n.deletions)o.deletionRevision<=r||(await this.applySessionDeletion(o),r=o.deletionRevision,y(r));if(r!==n.latestRevision)throw new Error(`deletion revision gap: applied ${r}, latest ${n.latestRevision}`);if(r===0)return;const d=await this.client.sendReq({type:"req",id:`session-deletions-ack-${h.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:g,dataEpoch:f,daemonInstallationId:e,deletionRevision:r}});if(!d.ok)throw new Error(d.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new Ce({daemonInstallationId:u()});t.recordSession(e);for(const n of e.sources){const r=t.find(n.agentType,n.sourceSessionKey);r&&r.sessionId!==e.sessionId||t.record({...e,...n})}this.runJournal.markSessionAborted(e.sessionId),this.recoveryBarrier.end(e.sessionId);const s=this.sessions.get(e.sessionId);if(s){this.revokeManagedRoomToken(s);try{await s.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),Me(e.sessionId),be(e.sessionId),Se(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async handleExternalTerminal(e){const t=this.sessions.get(e);t&&(t.journalRunId&&(this.recoveryCoordinator.completeFromProvider(e,t.journalRunId),t.journalRunId=null),this.sessions.delete(e),this.revokeManagedRoomToken(t),t.heartbeatTimer&&clearInterval(t.heartbeatTimer),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}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 n=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:n,canStop:s.canStop??!0});const r=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:r,runPhase:s.runPhase,canStop:s.canStop??!0},seq:r,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of w())t.startsWith("custom:")&&I(t);const e=T();for(const[t,s]of Object.entries(e.customAgents??{}))fe(t,s)}async handleReq(e,t={}){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":c(e.params),this.recoveryBarrier.isDispatchFrozen()||this.recoveryBarrier.isRecovering(String(e.params.sessionId??""))?await this.chatQueue.handleEnqueue({...e,method:"chat.enqueue"}):await U(s,e);break;case"chat.enqueue":c(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":c(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":c(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":c(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":c(e.params),await Q(s,e);break;case"session.refresh":c(e.params),await $(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":N(s,e);break;case"session.tool.detail":await W(s,e);break;case"voice.session.body.window":V(s,e);break;case"session.title.set":await J(s,e);break;case"room.agent.bind-managed":await ve(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await ye(s,this.managedRoomBinder,e);break;case"fs.ls":await Y(s,e);break;case"project.fs.open":case"project.fs.list":case"project.fs.read":case"project.fs.close":await ze(this.client,this.projectFsHandleStore,e,this.trustedProjectFsRequestResolver);break;case"fs.read":await K(s,e);break;case"fs.write":await Z(s,e);break;case"fs.export.markdown-pdf":await ae(s,e);break;case"fs.export.markdown-pdf.setup":await ne(s,e);break;case"fs.archive.zip":await re(s,e);break;case"fs.rename":await X(s,e);break;case"fs.transfer.start":await te(s,e);break;case"fs.transfer.chunk":await ee(s,e);break;case"fs.transfer.finish":await se(s,e);break;case"fs.transfer.abort":await q(s,e);break;case"region.probe":await ie(s,e);break;case"region.switch":await oe(s,e);break;case"upgrade.start":await P(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0,prepareForUpgrade:n=>this.prepareForUpgrade(n),cancelUpgradePreparation:()=>this.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()}});break;case"upgrade.status":await C(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await ce(s,e);break;case"machine.remote-access.pause":await L(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await M(s,e);break;case"agent.permission.respond":await $e(s,e);break;case"agent.auth.start":case"agent.auth.logout":case"agent.session-options.get":case"agent.external-sessions.list":case"agent.external-sessions.import":case"agent.external-sessions.close":case"agent.external-sessions.delete":await We(s,e);break;case"models.refresh":await D(s,e);break;case"agent.config.get":await _(s,e);break;case"agent.config.upsert":await B(s,e);break;case"agent.config.clear":await O(s,e);break;case"agent.config.test":await j(s,e);break;case"agent.workspace.validate":await H(s,e);break;case"agent.capabilities.list":await F(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(n){if(t.propagateErrors)throw n;this.client.sendRes({type:"res",id:e.id,ok:!1,error:n instanceof Error?n.message:String(n)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<Ve)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(this.revokeManagedRoomToken(e.session),e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return ge(e)}resolveAuthorizedPath(e,t){return pe(e,he(t))}async resolveAuthorizedPathAsync(e,t){return me(e,await ue(t))}async cleanup(){this.chatQueue.stop(),this.projectFsHandleStore.revokeAll(),this.acpDiscoveryWatcher.stop(),this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),G(this.getRuntime()),await this.managerRuntime.stop(),v(null)}revokeManagedRoomToken(e){const t=e.managedRoomContextToken;e.managedRoomContextToken=null,t&&this.managedRoomContextRevoker?.(t)}}export{Vs as SessionManager,Ys as resolveSessionWorkDir};
@@ -0,0 +1,10 @@
1
+ import type { QueuedChatMessage } from '@shennian/wire';
2
+ import type { LocalQueuedChatMessage } from './queue.js';
3
+ export declare function findDuplicateQueuedMessage<T extends QueuedChatMessage & {
4
+ origin?: 'app' | 'external' | 'room';
5
+ }>(pending: T[], message: T): T | null;
6
+ export declare function stripLocalQueueFields(message: LocalQueuedChatMessage): QueuedChatMessage;
7
+ export declare function takeCompatibleBatch(pending: LocalQueuedChatMessage[]): LocalQueuedChatMessage[];
8
+ export declare function formatQueuedBatch(batch: LocalQueuedChatMessage[]): string;
9
+ export declare function dedupeQueuedAttachments(attachments: NonNullable<QueuedChatMessage['attachments']>): NonNullable<QueuedChatMessage['attachments']> | undefined;
10
+ export declare function classifyQueueError(message: string, fallback: string): string;