shennian 0.4.0 → 0.4.2

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 (61) hide show
  1. package/dist/native/macos-background-client/Shennian.app/Contents/CodeResources +0 -0
  2. package/dist/native/macos-background-client/Shennian.app/Contents/Info.plist +17 -0
  3. package/dist/native/macos-background-client/Shennian.app/Contents/MacOS/ShennianClientLauncher +0 -0
  4. package/dist/native/macos-background-client/Shennian.app/Contents/Resources/en.lproj/InfoPlist.strings +2 -0
  5. package/dist/native/macos-background-client/Shennian.app/Contents/Resources/zh-Hans.lproj/InfoPlist.strings +2 -0
  6. package/dist/native/macos-background-client/Shennian.app/Contents/_CodeSignature/CodeResources +153 -0
  7. package/dist/publish-build-manifest.json +78 -43
  8. package/dist/src/agent-env.d.ts +1 -0
  9. package/dist/src/agent-env.js +4 -4
  10. package/dist/src/agents/codex.d.ts +3 -2
  11. package/dist/src/agents/codex.js +8 -8
  12. package/dist/src/agents/command-spec.js +1 -1
  13. package/dist/src/agents/config-status.js +1 -1
  14. package/dist/src/agents/detect.d.ts +1 -0
  15. package/dist/src/agents/detect.js +1 -1
  16. package/dist/src/agents/manager.d.ts +3 -3
  17. package/dist/src/agents/manager.js +9 -9
  18. package/dist/src/agents/model-registry/cache.d.ts +1 -1
  19. package/dist/src/agents/model-registry/cache.js +1 -1
  20. package/dist/src/agents/model-registry/discovery.js +3 -2
  21. package/dist/src/agents/model-registry/parsers.js +4 -4
  22. package/dist/src/agents/model-registry/service.js +1 -1
  23. package/dist/src/agents/model-registry/types.d.ts +1 -0
  24. package/dist/src/agents/pi-coding-agent-managed-permissions.d.ts +7 -0
  25. package/dist/src/agents/pi-coding-agent-managed-permissions.js +1 -0
  26. package/dist/src/agents/pi-coding-agent.d.ts +55 -0
  27. package/dist/src/agents/pi-coding-agent.js +4 -0
  28. package/dist/src/agents/pi-managed-extension.d.ts +12 -0
  29. package/dist/src/agents/pi-managed-extension.js +89 -0
  30. package/dist/src/agents/pi-managed-model-broker.d.ts +27 -0
  31. package/dist/src/agents/pi-managed-model-broker.js +1 -0
  32. package/dist/src/agents/pi-rpc-client.d.ts +31 -0
  33. package/dist/src/agents/pi-rpc-client.js +3 -0
  34. package/dist/src/agents/pi-session-ownership.d.ts +2 -0
  35. package/dist/src/agents/pi-session-ownership.js +2 -0
  36. package/dist/src/commands/daemon.d.ts +4 -0
  37. package/dist/src/commands/daemon.js +21 -17
  38. package/dist/src/commands/pair-browser.js +1 -1
  39. package/dist/src/manager/prompt.d.ts +1 -1
  40. package/dist/src/manager/prompt.js +2 -2
  41. package/dist/src/manager/registry.d.ts +4 -4
  42. package/dist/src/manager/runtime.d.ts +3 -3
  43. package/dist/src/room/activation-service.js +4 -4
  44. package/dist/src/room/device-authorization-client.d.ts +2 -0
  45. package/dist/src/room/managed-prompt-composer.d.ts +1 -1
  46. package/dist/src/room/managed-prompt-composer.js +2 -1
  47. package/dist/src/security/managed-agent-policy.js +1 -1
  48. package/dist/src/session/handlers/agent-workspace-validation.d.ts +24 -0
  49. package/dist/src/session/handlers/agent-workspace-validation.js +1 -0
  50. package/dist/src/session/handlers/chat.js +1 -1
  51. package/dist/src/session/manager.d.ts +1 -0
  52. package/dist/src/session/manager.js +1 -1
  53. package/dist/src/session/native-session-lifecycle.js +6 -6
  54. package/dist/src/session/queue.d.ts +5 -1
  55. package/dist/src/session/queue.js +2 -2
  56. package/dist/src/upgrade/engine.d.ts +1 -0
  57. package/dist/src/upgrade/engine.js +2 -2
  58. package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
  59. package/node_modules/@shennian/wire/dist/session.d.ts +2 -2
  60. package/node_modules/@shennian/wire/dist/session.js +1 -0
  61. package/package.json +3 -3
@@ -1,8 +1,8 @@
1
- import{isAvailableBuiltinAgentType as A}from"@shennian/wire";import{getBuiltinAgentVersion as k,resolveBuiltinCommand as P}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as S,resolveCustomRuntimeExecutableFingerprint as K}from"../agents/custom-managed-runtime.js";import{loadConfig as B}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as $}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as x}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as q}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as T}from"./activation-policy.js";import{composeManagedRoomPrompt as D}from"./managed-prompt-composer.js";const R=4,E=6e4,W=6,N=5*6e4;class oe{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,s,t,a={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],c=[],g=new Map,m=new Set,u=[...s].sort((i,r)=>G(i.sequence,r.sequence));for(const i of t){const r=this.deps.registry.getByRegistryKey(i.registryKey);!r?.binding||r.binding.roomId!==e||r.binding.status!=="active"&&r.binding.status!=="paused"&&r.binding.activationQueue.length}for(const i of t){const r=this.deps.registry.getByRegistryKey(i.registryKey);if(!r?.binding||r.binding.roomId!==e||r.binding.activationQueue.length===0&&!r.binding.activeActivation||(m.add(r.registryKey),r.binding.status!=="active"||r.binding.activationMode==="paused")||r.binding.activationQueue.length===0)continue;const p=this.deps.observations.getBySource(r);if(!p?.workspacePath||p.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(r.registryKey,a);for(const o of r.binding.activationQueue)d.push({registration:r,input:{params:{sessionId:r.nianSessionId,text:o.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:f(r.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:p.workspacePath,agentSessionId:r.sourceSessionKey,queueMessageId:`room-activation-${r.binding.bindingId}-${o.causeMessageId}`,clientMessageId:`room-activation-${r.binding.bindingId}-${o.causeMessageId}`},context:{registryKey:r.registryKey,causeMessageId:o.causeMessageId,rootCauseMessageId:o.rootCauseMessageId,sequence:o.sequence,hopCount:o.hopCount}}})}for(const i of u){const r=new Set(T(i,t).map(p=>p.registration.registryKey));for(const p of t){const o=this.deps.registry.getByRegistryKey(p.registryKey);if(!o?.binding||o.binding.roomId!==e||o.binding.status!=="active"&&o.binding.status!=="paused"||BigInt(i.sequence)<=BigInt(o.binding.lastActivatedSequence))continue;const l=g.get(o.registryKey)??[...o.binding.mentionContext??[]];if(g.set(o.registryKey,l),r.has(o.registryKey)){const y=(i.activationCause?.hopCount??0)+(i.author.principalType==="agent"?1:0),I=i.activationCause?.rootCauseMessageId??i.id;if(y>R)this.audit(o,i.id,I,y,"suppressed","max_hops");else{const h=this.deps.registry.claimActivation({registryKey:o.registryKey,windowMs:E,maxActivations:W,pauseMs:N,now:this.deps.now?.()});if(!h.allowed){h.newlyPaused&&this.audit(o,i.id,I,y,"suppressed","rate_limited"),c.push({registryKey:o.registryKey,sequence:i.sequence});continue}const b=this.deps.observations.getBySource(o);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const v=w([i]),M=o.binding.activationMode==="mention_only"?L([...l.map(_=>_.deliveryText),v]):v;this.latestContexts.set(o.registryKey,a),d.push({registration:o,input:{params:{sessionId:o.nianSessionId,text:M,agentType:f(o.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:o.sourceSessionKey,queueMessageId:`room-activation-${o.binding.bindingId}-${i.id}`,clientMessageId:`room-activation-${o.binding.bindingId}-${i.id}`},context:{registryKey:o.registryKey,causeMessageId:i.id,rootCauseMessageId:I,sequence:i.sequence,hopCount:y}}}),o.binding.activationMode==="mention_only"&&g.set(o.registryKey,[])}}else o.binding.activationMode==="mention_only"&&i.kind!=="system"&&i.author.membershipId!==o.binding.agentMembershipId&&(l.some(y=>y.sequence===i.sequence)||l.push({sequence:i.sequence,deliveryText:w([i])}));c.push({registryKey:o.registryKey,sequence:i.sequence})}}await this.deps.enqueueConversationBatch(d.map(i=>i.input));for(const i of m)this.deps.registry.retireLegacyActivationState(i,this.deps.now?.()??new Date);for(const[i,r]of g)this.deps.registry.replaceMentionContext(i,r,this.deps.now?.()??new Date);for(const i of d)this.audit(i.registration,i.input.context.causeMessageId,i.input.context.rootCauseMessageId,i.input.context.hopCount,"queued","conversation_queue");for(const i of c)this.deps.registry.advanceCursor({registryKey:i.registryKey,cursor:"activated",sequence:i.sequence})}async prepareConversationTurn(e){const s=e.at(-1);if(!s)throw new Error("managed_room_queue_context_missing");const t=this.deps.registry.getByRegistryKey(s.registryKey);if(!t?.binding||t.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const a=this.deps.loadContext?await this.deps.loadContext(t):this.latestContexts.get(s.registryKey);if(!a)throw new Error("managed_room_prompt_context_unavailable");const d=a.agentsByIdentityId?.[t.binding.agentIdentityId];if(!d?.permissionPolicy)throw new Error("managed_agent_policy_missing");const c=this.deps.observations.getBySource(t);if(!c?.workspacePath||c.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const g=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:t,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:c.workspacePath}):await j({registration:t,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:c.workspacePath}),m=Q(a,t,g),u=this.deps.issueManagedRoomContext(t.registryKey,s.causeMessageId);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(u))throw new Error("managed_room_context_unavailable");return this.audit(t,s.causeMessageId,s.rootCauseMessageId,s.hopCount,"activated","conversation_queue_dispatch"),{systemPrompt:m.systemPrompt,roomContextRegistryKey:t.registryKey,managedRoomContextToken:u,managedAgentPolicy:g,managedRoomPromptSnapshot:m.snapshot}}audit(e,s,t,a,d,c){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:s,rootCauseMessageId:t,hopCount:a,outcome:d,reason:c,at:this.deps.now?.()})}}function w(n){const e=n.map(s=>{const t=s.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",a=s.attachments.length>0?`
1
+ import{isAvailableBuiltinAgentType as A}from"@shennian/wire";import{getBuiltinAgentVersion as k,resolveBuiltinCommand as P}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as S,resolveCustomRuntimeExecutableFingerprint as K}from"../agents/custom-managed-runtime.js";import{loadConfig as B}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as $}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as x}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as q}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as T}from"./activation-policy.js";import{composeManagedRoomPrompt as W}from"./managed-prompt-composer.js";const D=4,R=6e4,E=6,N=5*6e4;class oe{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,s,t,a={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],c=[],g=new Map,u=new Set,m=[...s].sort((n,r)=>G(n.sequence,r.sequence));for(const n of t){const r=this.deps.registry.getByRegistryKey(n.registryKey);!r?.binding||r.binding.roomId!==e||r.binding.status!=="active"&&r.binding.status!=="paused"&&r.binding.activationQueue.length}for(const n of t){const r=this.deps.registry.getByRegistryKey(n.registryKey);if(!r?.binding||r.binding.roomId!==e||r.binding.activationQueue.length===0&&!r.binding.activeActivation||(u.add(r.registryKey),r.binding.status!=="active"||r.binding.activationMode==="paused")||r.binding.activationQueue.length===0)continue;const l=this.deps.observations.getBySource(r);if(!l?.workspacePath||l.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(r.registryKey,a);for(const o of r.binding.activationQueue)d.push({registration:r,input:{params:{sessionId:r.nianSessionId,text:o.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(r.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:l.workspacePath,agentSessionId:r.sourceSessionKey,queueMessageId:`room-activation-${r.binding.bindingId}-${o.causeMessageId}`,clientMessageId:`room-activation-${r.binding.bindingId}-${o.causeMessageId}`},context:{registryKey:r.registryKey,causeMessageId:o.causeMessageId,rootCauseMessageId:o.rootCauseMessageId,sequence:o.sequence,hopCount:o.hopCount}}})}for(const n of m){const r=new Set(T(n,t).map(l=>l.registration.registryKey));for(const l of t){const o=this.deps.registry.getByRegistryKey(l.registryKey);if(!o?.binding||o.binding.roomId!==e||o.binding.status!=="active"&&o.binding.status!=="paused"||BigInt(n.sequence)<=BigInt(o.binding.lastActivatedSequence))continue;const f=g.get(o.registryKey)??[...o.binding.mentionContext??[]];if(g.set(o.registryKey,f),r.has(o.registryKey)){const p=(n.activationCause?.hopCount??0)+(n.author.principalType==="agent"?1:0),I=n.activationCause?.rootCauseMessageId??n.id;if(p>D)this.audit(o,n.id,I,p,"suppressed","max_hops");else{const h=this.deps.registry.claimActivation({registryKey:o.registryKey,windowMs:R,maxActivations:E,pauseMs:N,now:this.deps.now?.()});if(!h.allowed){h.newlyPaused&&this.audit(o,n.id,I,p,"suppressed","rate_limited"),c.push({registryKey:o.registryKey,sequence:n.sequence});continue}const b=this.deps.observations.getBySource(o);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const v=C([n]),w=o.binding.activationMode==="mention_only"?L([...f.map(_=>_.deliveryText),v]):v;this.latestContexts.set(o.registryKey,a),d.push({registration:o,input:{params:{sessionId:o.nianSessionId,text:w,agentType:y(o.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:o.sourceSessionKey,queueMessageId:`room-activation-${o.binding.bindingId}-${n.id}`,clientMessageId:`room-activation-${o.binding.bindingId}-${n.id}`},context:{registryKey:o.registryKey,causeMessageId:n.id,rootCauseMessageId:I,sequence:n.sequence,hopCount:p}}}),o.binding.activationMode==="mention_only"&&g.set(o.registryKey,[])}}else o.binding.activationMode==="mention_only"&&n.kind!=="system"&&n.author.membershipId!==o.binding.agentMembershipId&&(f.some(p=>p.sequence===n.sequence)||f.push({sequence:n.sequence,deliveryText:C([n])}));c.push({registryKey:o.registryKey,sequence:n.sequence})}}await this.deps.enqueueConversationBatch(d.map(n=>n.input));for(const n of u)this.deps.registry.retireLegacyActivationState(n,this.deps.now?.()??new Date);for(const[n,r]of g)this.deps.registry.replaceMentionContext(n,r,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.advanceCursor({registryKey:n.registryKey,cursor:"activated",sequence:n.sequence})}async prepareConversationTurn(e){const s=e.at(-1);if(!s)throw new Error("managed_room_queue_context_missing");const t=this.deps.registry.getByRegistryKey(s.registryKey);if(!t?.binding||t.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const a=this.deps.loadContext?await this.deps.loadContext(t):this.latestContexts.get(s.registryKey);if(!a)throw new Error("managed_room_prompt_context_unavailable");const d=a.agentsByIdentityId?.[t.binding.agentIdentityId];if(!d?.permissionPolicy)throw new Error("managed_agent_policy_missing");const c=d.sessionMode==="manager"?"manager":"standard",g=c==="manager"?{...d.managerConfig,defaultWorkerAgentType:d.managerConfig?.defaultWorkerAgentType??y(t.runtimeAdapter),defaultWorkerModelId:d.managerConfig?.defaultWorkerModelId??null}:null,u=this.deps.observations.getBySource(t);if(!u?.workspacePath||u.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const m=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:t,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:u.workspacePath}):await j({registration:t,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:u.workspacePath}),n=Q(a,t,m),r=this.deps.issueManagedRoomContext(t.registryKey,s.causeMessageId);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(r))throw new Error("managed_room_context_unavailable");return this.audit(t,s.causeMessageId,s.rootCauseMessageId,s.hopCount,"activated","conversation_queue_dispatch"),{systemPrompt:n.systemPrompt,sessionMode:c,managerConfig:g,managerDefaultWorkerAgentType:g?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:g?.defaultWorkerModelId??null,roomContextRegistryKey:t.registryKey,managedRoomContextToken:r,managedAgentPolicy:m,managedRoomPromptSnapshot:n.snapshot}}audit(e,s,t,a,d,c){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:s,rootCauseMessageId:t,hopCount:a,outcome:d,reason:c,at:this.deps.now?.()})}}function C(i){const e=i.map(s=>{const t=s.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",a=s.attachments.length>0?`
2
2
  ${s.attachments.map(F).join(`
3
- `)}`:"";return{speaker:s.author.displayName,body:`${t}${a}`}});return C(e)}function F(n){return`\u9644\u4EF6\uFF1A${`${O(n.name)} \xB7 ${V(n.byteSize)}`}\uFF5C${n.mimeType}\uFF5CattachmentId=${n.attachmentId}`}function O(n){return String(n||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function V(n){const e=Number(n);if(!Number.isFinite(e)||e<0)return`${n} B`;if(e<1024)return`${e} B`;const s=["KB","MB","GB","TB"];let t=e,a=-1;do t/=1024,a+=1;while(t>=1024&&a<s.length-1);const d=t>=100?0:t>=10?1:2;return`${t.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${s[a]}`}function L(n){return C(n.map(z))}function z(n){const e=n.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const s=n.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(s)return{speaker:s[1],body:s[2]};const t=n.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const a=n.match(/^([^\n]+)\n([\s\S]*)$/);return a?{speaker:a[1],body:a[2]}:{speaker:"\u7FA4\u6210\u5458",body:n.trim()}}function C(n){if(n.length===1){const[e]=n;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
3
+ `)}`:"";return{speaker:s.author.displayName,body:`${t}${a}`}});return M(e)}function F(i){return`\u9644\u4EF6\uFF1A${`${O(i.name)} \xB7 ${V(i.byteSize)}`}\uFF5C${i.mimeType}\uFF5CattachmentId=${i.attachmentId}`}function O(i){return String(i||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function V(i){const e=Number(i);if(!Number.isFinite(e)||e<0)return`${i} B`;if(e<1024)return`${e} B`;const s=["KB","MB","GB","TB"];let t=e,a=-1;do t/=1024,a+=1;while(t>=1024&&a<s.length-1);const d=t>=100?0:t>=10?1:2;return`${t.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${s[a]}`}function L(i){return M(i.map(z))}function z(i){const e=i.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const s=i.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(s)return{speaker:s[1],body:s[2]};const t=i.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const a=i.match(/^([^\n]+)\n([\s\S]*)$/);return a?{speaker:a[1],body:a[2]}:{speaker:"\u7FA4\u6210\u5458",body:i.trim()}}function M(i){if(i.length===1){const[e]=i;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
4
4
  ${e.body}`}return`[\u7FA4\u6D88\u606F]
5
- ${n.map(e=>`<${e.speaker}>
5
+ ${i.map(e=>`<${e.speaker}>
6
6
  ${e.body}`).join(`
7
7
 
8
- `)}`}function Q(n,e,s){const t=e.binding?n.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!t)throw new Error("managed_room_prompt_incomplete");return D({runtimeAdapter:f(e.runtimeAdapter),agent:{displayName:t.displayName,description:t.description,systemPrompt:n.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId]},room:{name:n.roomName,activationMode:e.binding.activationMode,members:n.members},runtimePermissions:{requestedPolicy:s.requestedPolicy,enforcement:s.enforcement,resolvedWorkDir:s.adapterPolicy.workDir}})}async function j(n){const e=f(n.registration.runtimeAdapter),s=A(e)?P(e):null,t=e.startsWith("custom:")?B().customAgents?.[e.slice(7)]:void 0;t&&S(e,t);const a=s?k(e,s):t?.caps.version,d=s?$(s):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:t?K(t):void 0},c={agentType:e,runtimeVersion:a,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},g=q(c),m=X({confirmation:n.permissionConfirmation,policy:n.permissionPolicy,runtimeAdapter:e,protection:g}),u=await x({...c,policy:n.permissionPolicy,workDir:n.workDir,hostFullAccessConfirmed:m&&n.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:m&&n.permissionConfirmation?.limitedProtection});if(!u.ok){const i=u.unsupportedCapabilities?.length?`:${u.unsupportedCapabilities.join(",")}`:"";throw new Error(`${u.errorCode}${i}`)}return u.value}function X(n){const{confirmation:e,policy:s,runtimeAdapter:t,protection:a}=n;return!!(e&&e.policyVersion===s.version&&e.runtimeAdapter===t&&e.runtimeVersion===a.runtimeVersion&&e.platform===a.platform&&e.executableFingerprint===a.executableFingerprint&&e.hostFullAccess===(s.fileAccess==="host-full")&&e.limitedProtection===(a.level!=="hard"))}function f(n){return n==="claude_code"?"claude":n}function G(n,e){const s=BigInt(n),t=BigInt(e);return s<t?-1:s>t?1:0}export{oe as RoomActivationService,X as managedAgentPermissionConfirmationMatches,j as resolveManagedAgentPolicy};
8
+ `)}`}function Q(i,e,s){const t=e.binding?i.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!t)throw new Error("managed_room_prompt_incomplete");return W({runtimeAdapter:y(e.runtimeAdapter),agent:{displayName:t.displayName,description:t.description,systemPrompt:i.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId]},room:{name:i.roomName,activationMode:e.binding.activationMode,members:i.members},runtimePermissions:{requestedPolicy:s.requestedPolicy,enforcement:s.enforcement,resolvedWorkDir:s.adapterPolicy.workDir}})}async function j(i){const e=y(i.registration.runtimeAdapter),s=A(e)?P(e):null,t=e.startsWith("custom:")?B().customAgents?.[e.slice(7)]:void 0;t&&S(e,t);const a=s?k(e,s):t?.caps.version,d=s?$(s):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:t?K(t):void 0},c={agentType:e,runtimeVersion:a,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},g=q(c),u=X({confirmation:i.permissionConfirmation,policy:i.permissionPolicy,runtimeAdapter:e,protection:g}),m=await x({...c,policy:i.permissionPolicy,workDir:i.workDir,hostFullAccessConfirmed:u&&i.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:u&&i.permissionConfirmation?.limitedProtection});if(!m.ok){const n=m.unsupportedCapabilities?.length?`:${m.unsupportedCapabilities.join(",")}`:"";throw new Error(`${m.errorCode}${n}`)}return m.value}function X(i){const{confirmation:e,policy:s,runtimeAdapter:t,protection:a}=i;return!!(e&&e.policyVersion===s.version&&e.runtimeAdapter===t&&e.runtimeVersion===a.runtimeVersion&&e.platform===a.platform&&e.executableFingerprint===a.executableFingerprint&&e.hostFullAccess===(s.fileAccess==="host-full")&&e.limitedProtection===(a.level!=="hard"))}function y(i){return i==="claude_code"?"claude":i}function G(i,e){const s=BigInt(i),t=BigInt(e);return s<t?-1:s>t?1:0}export{oe as RoomActivationService,X as managedAgentPermissionConfirmationMatches,j as resolveManagedAgentPolicy};
@@ -2,6 +2,7 @@ export type DeviceAuthorizationStatus = 'waiting_user' | 'approved' | 'denied' |
2
2
  export interface DeviceAuthorizationClient {
3
3
  create(input: {
4
4
  deviceSecretHash: string;
5
+ daemonInstallationId: string;
5
6
  machineName: string;
6
7
  machineOs: string;
7
8
  agentList: string[];
@@ -49,6 +50,7 @@ export declare class HttpDeviceAuthorizationClient implements DeviceAuthorizatio
49
50
  constructor(serverUrl: string, fetchImpl?: FetchLike);
50
51
  create(input: {
51
52
  deviceSecretHash: string;
53
+ daemonInstallationId: string;
52
54
  machineName: string;
53
55
  machineOs: string;
54
56
  agentList: string[];
@@ -1,5 +1,5 @@
1
1
  import type { ManagedAgentEnforcement, ManagedAgentPermissionPolicy, AgentType, RoomActivationMode } from '@shennian/wire';
2
- export declare const MANAGED_ROOM_POLICY_VERSION = "managed-room-surface-v5";
2
+ export declare const MANAGED_ROOM_POLICY_VERSION = "managed-room-surface-v6";
3
3
  export type ManagedRoomPromptInput = {
4
4
  agent: {
5
5
  displayName: string;
@@ -1,4 +1,4 @@
1
- import $ from"node:crypto";import h from"node:path";const v="managed-room-surface-v5",O=["shennian room status","shennian room read [--before <sequence> | --after <sequence>] [--limit <1-50>]","shennian room send [--text <\u5185\u5BB9>] [--file <\u672C\u5730\u6587\u4EF6\u8DEF\u5F84> ...]","shennian room attachment get <attachment-id>","shennian room pause --reason <\u7B80\u77ED\u539F\u56E0>"],x=["shennian_room_status","shennian_room_read","shennian_room_send","shennian_room_attachment_get","shennian_room_pause"];function I(e){const o=m(e.agent.displayName,191,"\u5F53\u524D\u667A\u80FD\u4F53"),n=m(e.agent.description??"",1e3,"\u672A\u8BBE\u7F6E")||"\u672A\u8BBE\u7F6E",t=m(e.room.name,191,"\u5F53\u524D\u7FA4\u804A"),r=(e.room.members??[]).slice(0,100).map(s=>{const g=m(s.displayName,191,"\u7FA4\u6210\u5458"),P=s.principalType==="agent"?"\u667A\u80FD\u4F53":"\u771F\u4EBA",y=s.principalType==="agent"&&s.description?`\uFF5C${m(s.description,1e3,"")}`:"",w=s.principalType==="agent"&&s.ownerDisplayName?`\uFF5C\u6240\u5C5E\u6210\u5458\uFF1A${m(s.ownerDisplayName,191,"")}`:"";return`- ${g}\uFF5C${P}${y}${w}`}),c=[`[\u667A\u80FD\u4F53\u8EAB\u4EFD]
1
+ import $ from"node:crypto";import h from"node:path";const v="managed-room-surface-v6",O=["shennian room status","shennian room read [--before <sequence> | --after <sequence>] [--limit <1-50>]","shennian room send [--text <\u5185\u5BB9>] [--file <\u672C\u5730\u6587\u4EF6\u8DEF\u5F84> ...]","shennian room attachment get <attachment-id>","shennian room pause --reason <\u7B80\u77ED\u539F\u56E0>"],x=["shennian_room_status","shennian_room_read","shennian_room_send","shennian_room_attachment_get","shennian_room_pause"];function I(e){const o=m(e.agent.displayName,191,"\u5F53\u524D\u667A\u80FD\u4F53"),n=m(e.agent.description??"",1e3,"\u672A\u8BBE\u7F6E")||"\u672A\u8BBE\u7F6E",t=m(e.room.name,191,"\u5F53\u524D\u7FA4\u804A"),r=(e.room.members??[]).slice(0,100).map(s=>{const g=m(s.displayName,191,"\u7FA4\u6210\u5458"),P=s.principalType==="agent"?"\u667A\u80FD\u4F53":"\u771F\u4EBA",y=s.principalType==="agent"&&s.description?`\uFF5C${m(s.description,1e3,"")}`:"",w=s.principalType==="agent"&&s.ownerDisplayName?`\uFF5C\u6240\u5C5E\u6210\u5458\uFF1A${m(s.ownerDisplayName,191,"")}`:"";return`- ${g}\uFF5C${P}${y}${w}`}),c=[`[\u667A\u80FD\u4F53\u8EAB\u4EFD]
2
2
  \u540D\u79F0\uFF1A${o}
3
3
  \u516C\u5F00\u804C\u8D23\uFF1A${n}`,`[\u5F53\u524D\u7FA4\u804A]
4
4
  \u7FA4\u804A\uFF1A${t}
@@ -18,6 +18,7 @@ ${r.length?r.join(`
18
18
  ${p?`\u53EA\u5728\u5185\u90E8\u5BF9\u8BDD\u4E2D\u751F\u6210\u6587\u5B57\u4E0D\u7B49\u4E8E\u5DF2\u7ECF\u56DE\u590D\u7FA4\u804A\uFF0C\u5B8C\u6210\u7FA4\u804A\u53D1\u9001\u5FC5\u987B\u8C03\u7528\u5F53\u524D\u7FA4\u804A\u5DE5\u5177\u3002
19
19
  \u672C\u5730\u9644\u4EF6\u4E0A\u4F20\u5F53\u524D\u4E0D\u53EF\u7528\uFF1B\u9700\u8981\u53D1\u9001\u957F\u6587\u6863\u65F6\uFF0C\u5148\u53D1\u9001\u7B80\u77ED\u6458\u8981\u5E76\u8BF4\u660E\u6B64\u9650\u5236\u3002`:`\u53EA\u5728\u5185\u90E8\u5BF9\u8BDD\u4E2D\u751F\u6210\u6587\u5B57\u4E0D\u7B49\u4E8E\u5DF2\u7ECF\u56DE\u590D\u7FA4\u804A\uFF0C\u5B8C\u6210\u7FA4\u804A\u53D1\u9001\u5FC5\u987B\u6267\u884C Shennian CLI\u3002
20
20
  \u957F\u6587\u6863\u4F18\u5148\u4F5C\u4E3A\u9644\u4EF6\u53D1\u9001\uFF0C\u5E76\u9644\u7B80\u77ED\u7EAF\u6587\u672C\u8BF4\u660E\u3002`}
21
+ \u6536\u5230\u9700\u8981\u5904\u7406\u7684\u4EFB\u52A1\u540E\uFF0C\u5148\u5728\u7FA4\u91CC\u7B80\u77ED\u8BF4\u660E\u5DF2\u6536\u5230\u548C\u51C6\u5907\u600E\u4E48\u505A\uFF0C\u518D\u5F00\u59CB\u6267\u884C\uFF1B\u8017\u65F6\u8F83\u957F\u65F6\u9002\u5F53\u540C\u6B65\u8FDB\u5C55\uFF0C\u5B8C\u6210\u540E\u56DE\u590D\u7ED3\u679C\u3002\u7B80\u5355\u95EE\u9898\u53EF\u76F4\u63A5\u56DE\u7B54\u3002
21
22
  Room \u6B63\u6587\u5F53\u524D\u6309\u7EAF\u6587\u672C\u663E\u793A\uFF1B\u53D1\u9001\u65F6\u4F7F\u7528\u77ED\u6BB5\u843D\u548C\u81EA\u7136\u6362\u884C\uFF0C\u4E0D\u8981\u4F7F\u7528 Markdown \u6807\u8BB0\u3002`,l,a?`[\u4F1A\u8BDD\u6A21\u5F0F]
22
23
  ${a}`:"",i?`[\u667A\u80FD\u4F53\u79C1\u6709\u7CFB\u7EDF\u63D0\u793A\u8BCD]
23
24
  ${i}`:"",c,`[\u7FA4\u6D88\u606F\u683C\u5F0F]
@@ -1 +1 @@
1
- import c from"node:fs/promises";import i from"node:path";import{parseManagedAgentPermissionPolicy as v,resolveManagedAgentEffectivePermissionPolicy as y}from"@shennian/wire";import{getManagedAgentBaseEnvKeys as _,getManagedAgentProviderEnvKeys as f,MANAGED_AGENT_SESSION_ENV_KEYS as u}from"../agent-env.js";import{collectPolicyEnforcementGaps as k,resolveManagedAgentRuntimeProtection as b,resolveManagedAgentSecurityProfile as w}from"./managed-agent-runtime.js";import{resolveProtectedShennianDirectories as P}from"./managed-agent-credential-paths.js";function W(e){return{...e.enforcement,effectivePolicy:{version:e.adapterPolicy.version,fileAccess:e.adapterPolicy.fileAccess,externalCapabilities:{...e.adapterPolicy.externalCapabilities}}}}async function R(e){const r=v(e.policy);if(!r)return{ok:!1,errorCode:"managed_agent_policy_invalid"};if(r.fileAccess==="host-full"&&!e.hostFullAccessConfirmed)return{ok:!1,errorCode:"managed_agent_host_full_confirmation_required"};const t=await C(e.workDir,e.resolveWorkDir);if(!t.ok)return t;if(await E(t.workDir))return{ok:!1,errorCode:"managed_agent_workdir_invalid"};const n=A(e),o=b(n),a=w(n),s=k(r,a),l=g([...o.level==="hard"?[]:["runtime.hardIsolation"],...o.unsupportedCapabilities,...s.unavailableCapabilities,...s.overgrantedCapabilities]),p=y(r,o),m={level:o.level,effectivePolicy:p,unsupportedCapabilities:l,runtimeVersion:o.runtimeVersion,platform:o.platform,...o.executableFingerprint?{executableFingerprint:o.executableFingerprint}:{}};return{ok:!0,value:{requestedPolicy:x(r),enforcement:m,adapterPolicy:{version:r.version,workDir:t.workDir,fileAccess:r.fileAccess,externalCapabilities:{...r.externalCapabilities},denySensitiveFiles:!0,failClosed:!0},environmentPolicy:{inheritDaemonEnv:!1,loadLoginShell:!1,providerEnvKeys:[...f(e.agentType)],sessionEnvKeys:[...u],shellSubprocess:{inherit:"none",allowedEnvKeys:g([..._(e.platform),...u]),deniedEnvKeys:[...f(e.agentType)]}}}}}function A(e){return{agentType:e.agentType,runtimeVersion:e.runtimeVersion,platform:e.platform,isWsl:e.isWsl,executableFingerprint:e.executableFingerprint,requireExecutableFingerprint:e.requireExecutableFingerprint,securityDeclaration:e.securityDeclaration}}async function C(e,r){const t=e.trim();if(!t||!i.isAbsolute(t))return{ok:!1,errorCode:"managed_agent_workdir_invalid"};try{const n=await(r??h)(t);return!n||!i.isAbsolute(n)?{ok:!1,errorCode:"managed_agent_workdir_invalid"}:{ok:!0,workDir:i.normalize(n)}}catch{return{ok:!1,errorCode:"managed_agent_workdir_unavailable"}}}async function h(e){const r=await c.realpath(e);if(!(await c.stat(r)).isDirectory())throw new Error("managed_agent_workdir_not_directory");return r}async function E(e){return P().some(t=>D(e,t,process.platform))}function D(e,r,t){const n=s=>{const l=i.resolve(s);return t==="win32"?l.toLowerCase():l},o=n(e),a=n(r);return d(o,a)||d(a,o)}function d(e,r){const t=i.relative(e,r);return t===""||!t.startsWith("..")&&!i.isAbsolute(t)}function x(e){return{...e,externalCapabilities:{...e.externalCapabilities}}}function g(e){return[...new Set(e)]}export{R as compileManagedAgentPolicy,W as resolveManagedAgentAdapterEnforcement};
1
+ import c from"node:fs/promises";import i from"node:path";import{parseManagedAgentPermissionPolicy as m,resolveManagedAgentEffectivePermissionPolicy as y}from"@shennian/wire";import{getManagedAgentBaseEnvKeys as k,getManagedAgentProviderEnvKeys as f,getManagedAgentSessionEnvKeys as u}from"../agent-env.js";import{collectPolicyEnforcementGaps as _,resolveManagedAgentRuntimeProtection as b,resolveManagedAgentSecurityProfile as w}from"./managed-agent-runtime.js";import{resolveProtectedShennianDirectories as P}from"./managed-agent-credential-paths.js";function W(e){return{...e.enforcement,effectivePolicy:{version:e.adapterPolicy.version,fileAccess:e.adapterPolicy.fileAccess,externalCapabilities:{...e.adapterPolicy.externalCapabilities}}}}async function R(e){const r=m(e.policy);if(!r)return{ok:!1,errorCode:"managed_agent_policy_invalid"};if(r.fileAccess==="host-full"&&!e.hostFullAccessConfirmed)return{ok:!1,errorCode:"managed_agent_host_full_confirmation_required"};const t=await A(e.workDir,e.resolveWorkDir);if(!t.ok)return t;if(await x(t.workDir))return{ok:!1,errorCode:"managed_agent_workdir_invalid"};const o=C(e),n=b(o),a=w(o),s=_(r,a),l=g([...n.level==="hard"?[]:["runtime.hardIsolation"],...n.unsupportedCapabilities,...s.unavailableCapabilities,...s.overgrantedCapabilities]),p=y(r,n),v={level:n.level,effectivePolicy:p,unsupportedCapabilities:l,runtimeVersion:n.runtimeVersion,platform:n.platform,...n.executableFingerprint?{executableFingerprint:n.executableFingerprint}:{}};return{ok:!0,value:{requestedPolicy:E(r),enforcement:v,adapterPolicy:{version:r.version,workDir:t.workDir,fileAccess:r.fileAccess,externalCapabilities:{...r.externalCapabilities},denySensitiveFiles:!0,failClosed:!0},environmentPolicy:{inheritDaemonEnv:!1,loadLoginShell:!1,providerEnvKeys:[...f(e.agentType)],sessionEnvKeys:[...u(e.agentType)],shellSubprocess:{inherit:"none",allowedEnvKeys:g([...k(e.platform),...u(e.agentType)]),deniedEnvKeys:[...f(e.agentType)]}}}}}function C(e){return{agentType:e.agentType,runtimeVersion:e.runtimeVersion,platform:e.platform,isWsl:e.isWsl,executableFingerprint:e.executableFingerprint,requireExecutableFingerprint:e.requireExecutableFingerprint,securityDeclaration:e.securityDeclaration}}async function A(e,r){const t=e.trim();if(!t||!i.isAbsolute(t))return{ok:!1,errorCode:"managed_agent_workdir_invalid"};try{const o=await(r??h)(t);return!o||!i.isAbsolute(o)?{ok:!1,errorCode:"managed_agent_workdir_invalid"}:{ok:!0,workDir:i.normalize(o)}}catch{return{ok:!1,errorCode:"managed_agent_workdir_unavailable"}}}async function h(e){const r=await c.realpath(e);if(!(await c.stat(r)).isDirectory())throw new Error("managed_agent_workdir_not_directory");return r}async function x(e){return P().some(t=>D(e,t,process.platform))}function D(e,r,t){const o=s=>{const l=i.resolve(s);return t==="win32"?l.toLowerCase():l},n=o(e),a=o(r);return d(n,a)||d(a,n)}function d(e,r){const t=i.relative(e,r);return t===""||!t.startsWith("..")&&!i.isAbsolute(t)}function E(e){return{...e,externalCapabilities:{...e.externalCapabilities}}}function g(e){return[...new Set(e)]}export{R as compileManagedAgentPolicy,W as resolveManagedAgentAdapterEnforcement};
@@ -0,0 +1,24 @@
1
+ import { type AgentType, type ReqFrame, type SessionMode } from '@shennian/wire';
2
+ import { type AgentAdapter } from '../../agents/adapter.js';
3
+ import type { SessionManagerRuntime } from '../types.js';
4
+ export type AgentWorkspaceValidationInput = {
5
+ runtimeAdapter: AgentType;
6
+ workDir: string;
7
+ modelId?: string | null;
8
+ sessionMode: SessionMode;
9
+ };
10
+ export type AgentWorkspaceValidationResult = {
11
+ ok: true;
12
+ } | {
13
+ ok: false;
14
+ errorCode: string;
15
+ };
16
+ type ValidationDependencies = {
17
+ createAdapter?: (type: AgentType) => AgentAdapter | null;
18
+ resolveWorkDir?: (workDir: string) => Promise<string>;
19
+ timeoutMs?: number;
20
+ cleanupNativeSession?: (agentType: AgentType, sessionId: string, workDir: string) => void;
21
+ };
22
+ export declare function validateAgentWorkspaceConfiguration(input: AgentWorkspaceValidationInput, dependencies?: ValidationDependencies): Promise<AgentWorkspaceValidationResult>;
23
+ export declare function handleAgentWorkspaceValidation(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
24
+ export {};
@@ -0,0 +1 @@
1
+ import k from"node:fs/promises";import{randomUUID as A}from"node:crypto";import{isAvailableAgentType as p}from"@shennian/wire";import{createAgent as y}from"../../agents/adapter.js";import{buildManagedAgentEnv as E}from"../../agents/config-status.js";import{ManagerModeAdapter as I}from"../../agents/manager.js";import{NativeSessionLifecycleRegistry as h}from"../native-session-lifecycle.js";const b="\u8BF7\u56DE\u590D hello\uFF0C\u7528\u4E8E\u786E\u8BA4\u4F60\u5DF2\u51C6\u5907\u597D\u3002",C=6e4,D=new h;async function M(e,r={}){if(!p(e.runtimeAdapter)||e.runtimeAdapter==="manager"||e.runtimeAdapter==="pi"||e.runtimeAdapter==="openclaw")return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};if(e.sessionMode==="manager"&&!["codex","claude","pi-coding-agent"].includes(e.runtimeAdapter))return{ok:!1,errorCode:"agent_validation_manager_unavailable"};let o;try{o=await(r.resolveWorkDir??T)(e.workDir)}catch(t){return{ok:!1,errorCode:S(t)}}const n=(r.createAdapter??y)(e.runtimeAdapter);if(!n)return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};const i=e.sessionMode==="manager"?new I(n,e.runtimeAdapter):n,m=`agent-validation-${A()}`;let l="",u="",c=null,d=null;const w=new Promise(t=>{d=t}),g=t=>{t.agentSessionId&&(l=t.agentSessionId),t.state==="delta"&&!t.thinking&&t.text&&(u+=t.text),t.state==="final"?(t.text&&(u+=t.text),d?.({kind:"final"})):(t.state==="error"||t.state==="aborted")&&d?.({kind:"error",error:t.message??t.state})},_=t=>d?.({kind:"error",error:t});i.on("agentEvent",g),i.on("error",_),i.configure?.({sessionId:m,env:E(e.runtimeAdapter)});try{try{await i.start(m,o,null,{persistNativeSession:!1})}catch(a){return{ok:!1,errorCode:v(a,"start")}}if(e.runtimeAdapter==="pi-coding-agent")return{ok:!0};const t=new Promise(a=>{c=setTimeout(()=>a({kind:"timeout"}),r.timeoutMs??C)}),f=i.send(b,e.modelId??void 0).then(()=>w).catch(a=>({kind:"error",error:a})),s=await Promise.race([f,t]);return s.kind==="timeout"?{ok:!1,errorCode:"agent_validation_response_timeout"}:s.kind==="error"?{ok:!1,errorCode:v(s.error,"response")}:u.toLowerCase().includes("hello")?{ok:!0}:{ok:!1,errorCode:"agent_validation_response_mismatch"}}finally{c&&clearTimeout(c),i.removeListener("agentEvent",g),i.removeListener("error",_),await i.stop().catch(()=>{}),l&&(r.cleanupNativeSession??((f,s,a)=>{D.execute(f,s,{workDir:a}).catch(()=>{})}))(e.runtimeAdapter,l,o)}}async function U(e,r){const o=N(r.params);if(!o){e.client.sendRes({type:"res",id:r.id,ok:!1,error:"agent_validation_invalid_configuration"});return}const n=await M(o);e.client.sendRes(n.ok?{type:"res",id:r.id,ok:!0,payload:{ready:!0}}:{type:"res",id:r.id,ok:!1,error:n.errorCode})}async function T(e){const r=e.trim();if(!r)throw Object.assign(new Error("workdir_required"),{code:"EINVAL"});const o=await k.realpath(r);if(!(await k.stat(o)).isDirectory())throw Object.assign(new Error("workdir_not_directory"),{code:"ENOTDIR"});return o}function S(e){const r=x(e);return r==="ENOENT"?"agent_validation_workdir_not_found":r==="ENOTDIR"?"agent_validation_workdir_not_directory":r==="EACCES"||r==="EPERM"?"agent_validation_workdir_unavailable":"agent_validation_workdir_invalid"}function v(e,r){const o=e instanceof Error?e.message:String(e);return/model.*(?:not found|unavailable|unsupported|invalid)|unknown model/i.test(o)?"agent_validation_model_unavailable":/not found|enoent|command .* missing|is .* installed/i.test(o)?"agent_validation_runtime_unavailable":/unauth|authentication|not logged|login required|api key|token.*missing|forbidden|401|403/i.test(o)?"agent_validation_runtime_auth_required":r==="start"?"agent_validation_start_failed":"agent_validation_response_failed"}function x(e){return typeof e=="object"&&e&&"code"in e?String(e.code):""}function N(e){const r=typeof e.runtimeAdapter=="string"?e.runtimeAdapter:"",o=typeof e.workDir=="string"?e.workDir:"",n=e.sessionMode==="manager"?"manager":"standard";return!p(r)||!o.trim()?null:{runtimeAdapter:r,workDir:o,sessionMode:n,modelId:typeof e.modelId=="string"?e.modelId:null}}export{U as handleAgentWorkspaceValidation,M as validateAgentWorkspaceConfiguration};
@@ -1,2 +1,2 @@
1
1
  import de from"node:os";import{createHash as K}from"node:crypto";import{createAgent as Ee}from"../../agents/adapter.js";import{buildApprovalPendingPayload as xe,buildUserMessagePayload as ie}from"@shennian/wire";import{ManagerModeAdapter as Me}from"../../agents/manager.js";import{reportLog as D}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as Te}from"../../native-fusion/parsers.js";import{appendMessage as b,getDaemonInstallationId as le,getMessageDeliveryStatus as _e,recordSession as H,upsertMessage as z}from"../store.js";import{mergeProjectedSessions as De}from"../projection.js";import{buildManagedAgentEnv as be}from"../../agents/config-status.js";import{parsePersonalAttachmentReferences as qe}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as Pe,markAttachmentReferences as $e}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as M}from"../index-publisher.js";import{writeToolDetail as ve}from"../tool-detail-store.js";import{getPersonalSyncIdentity as ce}from"../../personal-sync/epoch.js";import{buildManagedAgentPermissionPrompt as Ce}from"../../room/managed-prompt-composer.js";import{getManagedRoomIpcRoot as We,MANAGED_ROOM_IPC_ROOT_ENV as Oe}from"../../daemon-ipc/paths.js";function Ne(e,t,a){if(!e.managedAgentPolicy)throw new Error("managed_agent_policy_missing");const s=e.managedRoomPromptSnapshot;if(!s)throw new Error("managed_room_prompt_snapshot_missing");if(!t?.trim())throw new Error("managed_room_prompt_incomplete");if(!a||!/^mrc_[A-Za-z0-9_-]{43}$/.test(a))throw new Error("managed_room_context_unavailable");const r=K("sha256").update(JSON.stringify({requestedPolicy:e.managedAgentPolicy.requestedPolicy,enforcement:e.managedAgentPolicy.enforcement})).digest("hex"),l=K("sha256").update(e.managedAgentPolicy.adapterPolicy.workDir).digest("hex");if(s.runtimePermissionPolicyHash!==r||s.resolvedWorkDirHash!==l||s.enforcementLevel!==e.managedAgentPolicy.enforcement.level)throw new Error("managed_room_prompt_snapshot_mismatch")}function Fe(e){const t=e.indexOf(`
2
- `),a=t>0?Math.min(t,80):Math.min(e.length,80);return e.slice(0,a)}function ue(e,t,a){return`${e}:${K("sha256").update(`${t}\0${a??""}`).digest("hex")}`}function He(e,t,a={}){if(e.state==="tool-call"||e.state==="tool-result"){const s={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:t,detailRef:s,status:e.state==="tool-call"?"running":"completed",detailAvailable:!0,...e.durationMs!==void 0?{durationMs:e.durationMs}:{},...e.name?{name:e.name}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...a}}return{...e,sessionId:t,...a}}const ze=3e4;function Be(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function ge(e,t){return`Agent send failed: ${t instanceof Error?t.message:String(t)}`}function fe(e,t){return e!=="manager"?e:t==="claude"?"claude":"codex"}function Le(e,t){H({sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,workDir:t.workDir,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,workDir:t.workDir,status:"active",externalChannel:null}}})}function me(e){return be(e)}function pe(e,t,a){return{}}function X(e,t,a,s,r,l,c,R){const S=a==="codex";e.configure?.({sessionId:t,externalChannel:s??null,env:{...me(a),...pe(t,s,r),...l&&!S?{SHENNIAN_MANAGED_ROOM_CONTEXT:l,[Oe]:We()}:{}},...c?{managedAgentPolicy:c}:{},...S?{managedRoomContextToken:l??null,managedRoomExecutor:R??null}:{}})}function he(e,t,a){const s=t.managedRoomContextToken;s&&s!==a&&e.revokeManagedRoomContextToken?.(s),t.managedRoomContextToken=a}function v(e,t){const a=t?.managedRoomContextToken;!t||!a||(t.managedRoomContextToken=null,e.revokeManagedRoomContextToken?.(a))}function Ue(e,t,a){if(e!=="claude"||!a)return t;const s=t.trim();return!!s&&s.startsWith("-")&&!s.includes("/")?Te(a)??t:t}function Je(e,t,a,s){let r=null;function l(n,o={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:He(n,t,o),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function c(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function R(n){if(!n.currentRunId||!n.currentRunPhase)return;const o=n.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:t,runId:n.currentRunId,seq:o,runPhase:n.currentRunPhase},seq:o,id:`agent-heartbeat-${n.currentRunId}-${o}-${Date.now()}`})}function S(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{R(n)},ze),n.heartbeatTimer.unref?.())}function h(n){const o=n?.pendingTextEvent;!n||!o||!o.text||(b(t,{id:`agent-${o.runId}-${o.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:o.text}),M(e.client),l({state:"delta",runId:o.runId,seq:o.seq,text:o.text,thinking:o.thinking||void 0}),n.pendingTextEvent=null)}s.on("agentEvent",n=>{const o=e.sessions.get(t),q=Be(n),T=n.state==="final"||n.state==="error"||n.state==="aborted";if(o&&(o.nextEventSeq=n.seq+1,T||(o.currentRunId=n.runId),!T&&q&&(o.currentRunPhase=q,S(o)),n.agentSessionId&&(o.agentSessionId=n.agentSessionId)),e.managerRuntime?.noteAgentEvent(t,n),n.state==="delta"&&n.thinking)return;n.state!=="delta"&&D({level:"info",sessionId:t,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}});let k=!1;if(n.state==="tool-call"||n.state==="tool-result"){const m=`agent-${n.runId}-${n.seq}`;ve({sessionId:t,toolCallId:m,detailRevision:n.seq,ts:Date.now(),name:n.name??"tool",...n.args!==void 0?{args:n.args}:{},...n.result!==void 0?{result:n.result}:{},status:n.state==="tool-call"?"running":"completed",...n.durationMs!==void 0?{durationMs:n.durationMs}:{},detailRef:{runId:n.runId,sourceSeq:n.seq}}),b(t,{id:m,sessionId:t,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,toolCallId:m,status:n.state==="tool-call"?"running":"completed",...n.durationMs!==void 0?{durationMs:n.durationMs}:{},detailRef:{runId:n.runId,sourceSeq:n.seq},detailRevision:n.seq,detailAvailable:!0,resultOmitted:n.state==="tool-result"})}),k=!0}else n.state==="approval-pending"?(b(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:xe(n.approval)}),k=!0,o&&H({sessionId:t,agentType:a,workDir:o.workDir,agentSessionId:o.agentSessionId,attentionId:ue("approval",n.runId,n.seq)})):(n.state==="error"||n.state==="aborted")&&n.message&&(b(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.message}),k=!0);k&&M(e.client);const A=`${t}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&e.runTextAcc.set(A,(e.runTextAcc.get(A)??"")+n.text),n.agentSessionId&&n.agentSessionId!==r&&(r=n.agentSessionId,e.nativeFusion?.noteManagedSourceSession(t,fe(a,n.source),n.agentSessionId),o&&H({sessionId:t,agentType:a,workDir:o.workDir,agentSessionId:n.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const m=n.text??"";if(!m)return;o?.pendingTextEvent&&o.pendingTextEvent.runId!==n.runId&&h(o),o&&!o.pendingTextEvent&&(o.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:!1}),o?.pendingTextEvent&&(o.pendingTextEvent.text+=m,o.pendingTextEvent.seq=n.seq);return}let C={};if(n.state==="final"){h(o);const m=e.runTextAcc.get(A)??"";m&&(C={messageSummary:Fe(m)}),e.runTextAcc.delete(A)}else n.state==="error"||n.state==="aborted"?(h(o),e.runTextAcc.delete(A)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&h(o);T&&o?.currentRunId===n.runId&&(v(e,o),o.currentRunId=null,o.currentRunPhase=null,o.nextEventSeq=0,c(o),e.chatQueue?.noteTerminal(t),H({sessionId:t,agentType:a,workDir:o.workDir,agentSessionId:o.agentSessionId,status:n.state==="final"?"completed":"failed",attentionId:ue("terminal",n.runId),lastActivityAt:new Date().toISOString()}),M(e.client)),l(n,C)}),s.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${t} agentType=${a}: ${n.message}`),v(e,e.sessions.get(t)),e.sessions.delete(t),e.chatQueue?.noteTerminal(t),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:t,message:n.message,runId:"",seq:0}})})}function Qe(e,t){if(e.processedReqIds.add(t),e.processedReqIds.size>1e3){const a=e.processedReqIds.values().next().value;e.processedReqIds.delete(a)}}async function B(e,t){v(e,t),t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})}function ye(e,t,a,s,r,l,c){return c?e.send(t,a,s,r,l,c):l?.trim()?e.send(t,a,s,r,l):r?.length?e.send(t,a,s,r):s?e.send(t,a,s):e.send(t,a)}function Ve(e,t,a,s,r,l,c){return e.prepareSend?e.prepareSend(t,a,s,r,l,c):Promise.resolve()}async function je(e,t,a,s,r,l,c,R,S){e.evictIdleSessions();const h=Ee(a);if(!h)throw new Error(`Unsupported agent: ${a}`);const n=s==="manager"&&a!=="manager"?new Me(h,a==="claude"?"claude":"codex"):h;X(n,t,a,c,R,null,S,e.managedRoomExecutor),await n.start(t,r,l);const o={adapter:n,workDir:r,agentType:a,sessionMode:s,agentSessionId:l??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:c??null,externalReplyTarget:R??null,externalChannelEnv:{...me(a),...pe(t,c,R)},managedRoomContextToken:null};return e.sessions.set(t,o),Je(e,t,a,n),o}function Ge(e,t){const a=e.sessions.get(t),s=a?.currentRunId;if(!a||!s)return;const r=a.nextEventSeq;e.runTextAcc.delete(`${t}:${s}`),v(e,a),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:t,runId:s,seq:r},seq:r,id:`agent-evt-${s}-${r}`}),e.chatQueue?.noteTerminal(t)}async function pt(e,t,a){const s=a?t.params.managedRoomContextToken??null:null;let r=!1;try{await Ke(e,t,a,()=>{r=!0})}finally{s&&!r&&e.revokeManagedRoomContextToken?.(s)}}async function Ke(e,t,a,s){if(e.processedReqIds.has(t.id)){e.client.sendRes({type:"res",id:t.id,ok:!0});return}const{sessionId:r,text:l,agentType:c,sessionMode:R,managerConfig:S,workDir:h,agentSessionId:n,modelId:o,systemPrompt:q,managerDefaultWorkerAgentType:T,managerDefaultWorkerModelId:k,reasoningEffort:A,clientMessageId:C,sessionListProjection:m,waitForDispatch:Y,responseId:Ie,managedRoomContextToken:P,suppressExternalOwnerEcho:Re,queuedBatchItems:L,managedWorkerParentSessionId:W}=t.params,y=Ie||t.id;De(m);const O=null,U="";if(a?.source==="room_activation"&&(Ne(a,q,P),c==="manager"))throw new Error("managed_agent_manager_mode_unsupported");if(Qe(e,t.id),!r||!l){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:"sessionId and text are required"});return}const g=c,$=g==="manager"||R==="manager"?"manager":"standard";$==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(r,T??null,k??null);const N=(g==="claude"||g==="codex"||g==="workbuddy")&&typeof A=="string"&&A.trim()?A.trim():void 0,w=e.resolvePath(Ue(g,h||de.homedir(),n)||de.homedir()),_=a?.managedAgentPolicy??(W?e.managerRuntime?.getManagedAgentPolicyForWorker(W,w)??null:null);if(W&&!_)throw new Error("managed_worker_permission_context_unavailable");const J=W&&_?Ce({requestedPolicy:_.requestedPolicy,enforcement:_.enforcement,resolvedWorkDir:_.adapterPolicy.workDir}):q,Se=w,F=qe(t.params.attachments);F?.length&&Pe(w,F);const p={text:l,attachments:F,localized:!1};let i=e.sessions.get(r);if(i){if(i.lastActiveAt=Date.now(),i.agentType!==g||(i.sessionMode??(i.agentType==="manager"?"manager":"standard"))!==$||i.workDir!==w||JSON.stringify(i.externalChannel??null)!==JSON.stringify(O??null)){e.sessions.delete(r);try{await B(e,i)}catch{e.processedReqIds.delete(t.id)}i=void 0}else if(n&&i.agentSessionId!==n)try{await i.adapter.resume(n),i.agentSessionId=n}catch{e.sessions.delete(r);try{await B(e,i)}catch{e.processedReqIds.delete(t.id)}i=void 0}}if(!i)try{i=await je(e,r,g,$,w,n,O,U,_)}catch(d){const f=d instanceof Error&&d.message.startsWith("Unsupported agent:")?d.message:`Failed to start ${c}: ${d instanceof Error?d.message:String(d)}`;console.error(`[chat.send] start failed reqId=${t.id} sessionId=${r} agentType=${c} workDir=${w} agentSessionId=${n??""}: ${f}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:f,runId:"",seq:0}}),e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:f});return}const I={id:C??`user-${t.id}`,sessionId:r,role:"user",ts:Date.now(),payload:ie(p.text,p.attachments)},Z=Array.isArray(L)&&L.length>0?L.map(d=>({id:d.clientMessageId??d.id,sessionId:r,role:"user",ts:Date.parse(d.createdAt),payload:ie(d.text,d.attachments)})):null,Q=Z??[I],E=_e(r,I.id),ee=E.state==="queued";if(E.state!=="not_found"&&!ee){e.client.sendRes({type:"res",id:y,ok:!0,payload:E});return}if(Y){a?.source==="room_activation"&&X(i.adapter,r,g,O,U,P,a.managedAgentPolicy,e.managedRoomExecutor);try{await Ve(i.adapter,p.text,o,N,p.attachments,J,a?{source:a.source}:void 0)}catch(d){e.processedReqIds.delete(t.id),e.sessions.delete(r),await B(e,i),e.client.sendRes({type:"res",id:y,ok:!1,error:d instanceof Error?d.message:String(d)});return}}D({level:"info",sessionId:r,wsEvent:"chat.send.start",metadata:{reqId:t.id,agentType:g,sessionMode:$,managerConfig:S??null,modelId:o,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:k??null,reasoningEffort:N,...a?.source==="room_activation"?{managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:{}}});const V=(d,f)=>({...ce(le()),sessionId:r,clientMessageId:I.id,messageId:I.id,deliveryState:d,bodyRevision:f,committedAt:new Date(I.ts).toISOString()}),te=d=>{e.nativeFusion?.registerManagedSend({sessionId:r,agentType:g,sourceAgentType:fe(g,o),canonicalMessageId:I.id,sourceSessionKey:i.agentSessionId??n??null,text:p.text,managedEchoPolicy:d?.deliveryMode==="external_owner_queue"&&!Re?"import_following":"suppress_following",externalRunId:d?.deliveryMode==="external_owner_queue"?`codex-external-${d.queuedSubmissionId??I.id}`:null})},ne=()=>{i.currentRunId||(i.currentRunId=`dispatch:${t.id}`,i.currentRunPhase="thinking",i.nextEventSeq=0)},ae=()=>{if(te(),ee)return{...ce(le()),sessionId:r,clientMessageId:E.clientMessageId,messageId:E.messageId,deliveryState:E.deliveryState,bodyRevision:E.bodyRevision,committedAt:E.committedAt};try{Le(e,{sessionId:r,agentType:g,sessionMode:$,managerConfig:S??null,workDir:Se,agentSessionId:i.agentSessionId??n??null,modelId:o,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:k??null});let d=0;for(const[f,u]of Q.entries()){const x=Z?z(r,u,"committed"):b(r,u,"committed");f===0&&(d=x)}return $e(w,F??[]),M(e.client),V("committed",d)}catch(d){throw e.nativeFusion?.cancelManagedSend?.(I.id),d}},re=d=>{i.currentRunId||(i.nextEventSeq=0),te(d);const f=d?.deliveryMode==="external_owner_queue"?"queued":"executing";let u=0;for(const[x,j]of Q.entries()){const G=z(r,j,f);x===0&&(u=G)}return M(e.client),V(f,u)},oe=async(d,f)=>{e.nativeFusion?.cancelManagedSend?.(I.id);const u=ge(g,d);console.error(`[chat.send] send failed reqId=${t.id} sessionId=${r} agentType=${c} workDir=${w} agentSessionId=${i.agentSessionId??n??""}: ${u}`),e.sessions.delete(r);try{await B(e,i)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:u,runId:"",seq:0}}),!f){const x={id:`agent-error-${t.id}-${Date.now()}`,sessionId:r,role:"agent",ts:Date.now(),payload:u};b(r,x),M(e.client)}f&&(e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:u}))};if(Y){try{ae()}catch(u){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:`Durable message commit failed: ${u instanceof Error?u.message:String(u)}`});return}ne();let d;try{a?.source==="room_activation"&&(he(e,i,P),s());const u=p.attachments;d=await ye(i.adapter,p.text,o,N,u,J,a?{source:a.source}:void 0),D({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}catch(u){let x=0;for(const[G,ke]of Q.entries()){const we=z(r,ke,"failed");G===0&&(x=we)}M(e.client);const j=ge(g,u);await oe(u,!1),e.client.sendRes({type:"res",id:y,ok:!0,payload:{...V("failed",x),failureMessage:j}});return}const f=re(d??void 0);e.client.sendRes({type:"res",id:y,ok:!0,payload:{...f,...p.localized?{localizedAttachments:!0}:{},...d?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:d.deliveryMode,queuedSubmissionId:d.queuedSubmissionId}:{}}}),D({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});return}let se;try{se=ae()}catch(d){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:`Durable message commit failed: ${d instanceof Error?d.message:String(d)}`});return}ne(),a?.source==="room_activation"&&(X(i.adapter,r,g,O,U,P,a.managedAgentPolicy,e.managedRoomExecutor),he(e,i,P),s()),re(),e.client.sendRes({type:"res",id:y,ok:!0,payload:{...se,...p.localized?{localizedAttachments:!0}:{}}}),D({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});const Ae=p.attachments;ye(i.adapter,p.text,o,N,Ae,J,a?{source:a.source}:void 0).then(()=>{D({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}).catch(d=>{z(r,I,"failed"),M(e.client),oe(d,!1)})}async function ht(e,t){const{sessionId:a}=t.params,s=e.sessions.get(a);if(s){v(e,s);try{await s.adapter.stop()}catch{e.client.sendRes({type:"res",id:t.id,ok:!1,error:"agent_stop_failed"});return}Ge(e,a),e.activityPublisher?.publish(a,null)}e.client.sendRes({type:"res",id:t.id,ok:!0})}export{ht as handleChatAbort,pt as handleChatSend,Le as sendSessionUpdateEvent};
2
+ `),a=t>0?Math.min(t,80):Math.min(e.length,80);return e.slice(0,a)}function ue(e,t,a){return`${e}:${K("sha256").update(`${t}\0${a??""}`).digest("hex")}`}function He(e,t,a={}){if(e.state==="tool-call"||e.state==="tool-result"){const s={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:t,detailRef:s,status:e.state==="tool-call"?"running":"completed",detailAvailable:!0,...e.durationMs!==void 0?{durationMs:e.durationMs}:{},...e.name?{name:e.name}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...a}}return{...e,sessionId:t,...a}}const ze=3e4;function Be(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function ge(e,t){return`Agent send failed: ${t instanceof Error?t.message:String(t)}`}function fe(e,t){return e!=="manager"?e:t==="claude"?"claude":"codex"}function Le(e,t){H({sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,workDir:t.workDir,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,workDir:t.workDir,status:"active",externalChannel:null}}})}function me(e){return be(e)}function pe(e,t,a){return{}}function X(e,t,a,s,r,l,c,R){const S=a==="codex";e.configure?.({sessionId:t,externalChannel:s??null,env:{...me(a),...pe(t,s,r),...l&&!S?{SHENNIAN_MANAGED_ROOM_CONTEXT:l,[Oe]:We()}:{}},...c?{managedAgentPolicy:c}:{},...S?{managedRoomContextToken:l??null,managedRoomExecutor:R??null}:{}})}function he(e,t,a){const s=t.managedRoomContextToken;s&&s!==a&&e.revokeManagedRoomContextToken?.(s),t.managedRoomContextToken=a}function v(e,t){const a=t?.managedRoomContextToken;!t||!a||(t.managedRoomContextToken=null,e.revokeManagedRoomContextToken?.(a))}function Ue(e,t,a){if(e!=="claude"||!a)return t;const s=t.trim();return!!s&&s.startsWith("-")&&!s.includes("/")?Te(a)??t:t}function Je(e,t,a,s){let r=null;function l(n,o={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:He(n,t,o),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function c(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function R(n){if(!n.currentRunId||!n.currentRunPhase)return;const o=n.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:t,runId:n.currentRunId,seq:o,runPhase:n.currentRunPhase},seq:o,id:`agent-heartbeat-${n.currentRunId}-${o}-${Date.now()}`})}function S(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{R(n)},ze),n.heartbeatTimer.unref?.())}function h(n){const o=n?.pendingTextEvent;!n||!o||!o.text||(b(t,{id:`agent-${o.runId}-${o.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:o.text}),M(e.client),l({state:"delta",runId:o.runId,seq:o.seq,text:o.text,thinking:o.thinking||void 0}),n.pendingTextEvent=null)}s.on("agentEvent",n=>{const o=e.sessions.get(t),q=Be(n),T=n.state==="final"||n.state==="error"||n.state==="aborted";if(o&&(o.nextEventSeq=n.seq+1,T||(o.currentRunId=n.runId),!T&&q&&(o.currentRunPhase=q,S(o)),n.agentSessionId&&(o.agentSessionId=n.agentSessionId)),e.managerRuntime?.noteAgentEvent(t,n),n.state==="delta"&&n.thinking)return;n.state!=="delta"&&D({level:"info",sessionId:t,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}});let k=!1;if(n.state==="tool-call"||n.state==="tool-result"){const m=`agent-${n.runId}-${n.seq}`;ve({sessionId:t,toolCallId:m,detailRevision:n.seq,ts:Date.now(),name:n.name??"tool",...n.args!==void 0?{args:n.args}:{},...n.result!==void 0?{result:n.result}:{},status:n.state==="tool-call"?"running":"completed",...n.durationMs!==void 0?{durationMs:n.durationMs}:{},detailRef:{runId:n.runId,sourceSeq:n.seq}}),b(t,{id:m,sessionId:t,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,toolCallId:m,status:n.state==="tool-call"?"running":"completed",...n.durationMs!==void 0?{durationMs:n.durationMs}:{},detailRef:{runId:n.runId,sourceSeq:n.seq},detailRevision:n.seq,detailAvailable:!0,resultOmitted:n.state==="tool-result"})}),k=!0}else n.state==="approval-pending"?(b(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:xe(n.approval)}),k=!0,o&&H({sessionId:t,agentType:a,workDir:o.workDir,agentSessionId:o.agentSessionId,attentionId:ue("approval",n.runId,n.seq)})):(n.state==="error"||n.state==="aborted")&&n.message&&(b(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.message}),k=!0);k&&M(e.client);const A=`${t}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&e.runTextAcc.set(A,(e.runTextAcc.get(A)??"")+n.text),n.agentSessionId&&n.agentSessionId!==r&&(r=n.agentSessionId,e.nativeFusion?.noteManagedSourceSession(t,fe(a,n.source),n.agentSessionId),o&&H({sessionId:t,agentType:a,workDir:o.workDir,agentSessionId:n.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const m=n.text??"";if(!m)return;o?.pendingTextEvent&&o.pendingTextEvent.runId!==n.runId&&h(o),o&&!o.pendingTextEvent&&(o.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:!1}),o?.pendingTextEvent&&(o.pendingTextEvent.text+=m,o.pendingTextEvent.seq=n.seq);return}let C={};if(n.state==="final"){h(o);const m=e.runTextAcc.get(A)??"";m&&(C={messageSummary:Fe(m)}),e.runTextAcc.delete(A)}else n.state==="error"||n.state==="aborted"?(h(o),e.runTextAcc.delete(A)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&h(o);T&&o?.currentRunId===n.runId&&(v(e,o),o.currentRunId=null,o.currentRunPhase=null,o.nextEventSeq=0,c(o),e.chatQueue?.noteTerminal(t),H({sessionId:t,agentType:a,workDir:o.workDir,agentSessionId:o.agentSessionId,status:n.state==="final"?"completed":"failed",attentionId:ue("terminal",n.runId),lastActivityAt:new Date().toISOString()}),M(e.client)),l(n,C)}),s.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${t} agentType=${a}: ${n.message}`),v(e,e.sessions.get(t)),e.sessions.delete(t),e.chatQueue?.noteTerminal(t),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:t,message:n.message,runId:"",seq:0}})})}function Qe(e,t){if(e.processedReqIds.add(t),e.processedReqIds.size>1e3){const a=e.processedReqIds.values().next().value;e.processedReqIds.delete(a)}}async function B(e,t){v(e,t),t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})}function ye(e,t,a,s,r,l,c){return c?e.send(t,a,s,r,l,c):l?.trim()?e.send(t,a,s,r,l):r?.length?e.send(t,a,s,r):s?e.send(t,a,s):e.send(t,a)}function Ve(e,t,a,s,r,l,c){return e.prepareSend?e.prepareSend(t,a,s,r,l,c):Promise.resolve()}async function je(e,t,a,s,r,l,c,R,S){e.evictIdleSessions();const h=Ee(a);if(!h)throw new Error(`Unsupported agent: ${a}`);const n=s==="manager"&&a!=="manager"?new Me(h,a):h;X(n,t,a,c,R,null,S,e.managedRoomExecutor),await n.start(t,r,l);const o={adapter:n,workDir:r,agentType:a,sessionMode:s,agentSessionId:l??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:c??null,externalReplyTarget:R??null,externalChannelEnv:{...me(a),...pe(t,c,R)},managedRoomContextToken:null};return e.sessions.set(t,o),Je(e,t,a,n),o}function Ge(e,t){const a=e.sessions.get(t),s=a?.currentRunId;if(!a||!s)return;const r=a.nextEventSeq;e.runTextAcc.delete(`${t}:${s}`),v(e,a),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:t,runId:s,seq:r},seq:r,id:`agent-evt-${s}-${r}`}),e.chatQueue?.noteTerminal(t)}async function pt(e,t,a){const s=a?t.params.managedRoomContextToken??null:null;let r=!1;try{await Ke(e,t,a,()=>{r=!0})}finally{s&&!r&&e.revokeManagedRoomContextToken?.(s)}}async function Ke(e,t,a,s){if(e.processedReqIds.has(t.id)){e.client.sendRes({type:"res",id:t.id,ok:!0});return}const{sessionId:r,text:l,agentType:c,sessionMode:R,managerConfig:S,workDir:h,agentSessionId:n,modelId:o,systemPrompt:q,managerDefaultWorkerAgentType:T,managerDefaultWorkerModelId:k,reasoningEffort:A,clientMessageId:C,sessionListProjection:m,waitForDispatch:Y,responseId:Ie,managedRoomContextToken:P,suppressExternalOwnerEcho:Re,queuedBatchItems:L,managedWorkerParentSessionId:W}=t.params,y=Ie||t.id;De(m);const O=null,U="";if(a?.source==="room_activation"&&(Ne(a,q,P),c==="manager"))throw new Error("managed_agent_manager_mode_unsupported");if(Qe(e,t.id),!r||!l){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:"sessionId and text are required"});return}const g=c,$=g==="manager"||R==="manager"?"manager":"standard";$==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(r,T??null,k??null);const N=(g==="claude"||g==="codex"||g==="workbuddy"||g==="pi-coding-agent")&&typeof A=="string"&&A.trim()?A.trim():void 0,w=e.resolvePath(Ue(g,h||de.homedir(),n)||de.homedir()),_=a?.managedAgentPolicy??(W?e.managerRuntime?.getManagedAgentPolicyForWorker(W,w)??null:null);if(W&&!_)throw new Error("managed_worker_permission_context_unavailable");const J=W&&_?Ce({requestedPolicy:_.requestedPolicy,enforcement:_.enforcement,resolvedWorkDir:_.adapterPolicy.workDir}):q,Se=w,F=qe(t.params.attachments);F?.length&&Pe(w,F);const p={text:l,attachments:F,localized:!1};let i=e.sessions.get(r);if(i){if(i.lastActiveAt=Date.now(),i.agentType!==g||(i.sessionMode??(i.agentType==="manager"?"manager":"standard"))!==$||i.workDir!==w||JSON.stringify(i.externalChannel??null)!==JSON.stringify(O??null)){e.sessions.delete(r);try{await B(e,i)}catch{e.processedReqIds.delete(t.id)}i=void 0}else if(n&&i.agentSessionId!==n)try{await i.adapter.resume(n),i.agentSessionId=n}catch{e.sessions.delete(r);try{await B(e,i)}catch{e.processedReqIds.delete(t.id)}i=void 0}}if(!i)try{i=await je(e,r,g,$,w,n,O,U,_)}catch(d){const f=d instanceof Error&&d.message.startsWith("Unsupported agent:")?d.message:`Failed to start ${c}: ${d instanceof Error?d.message:String(d)}`;console.error(`[chat.send] start failed reqId=${t.id} sessionId=${r} agentType=${c} workDir=${w} agentSessionId=${n??""}: ${f}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:f,runId:"",seq:0}}),e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:f});return}const I={id:C??`user-${t.id}`,sessionId:r,role:"user",ts:Date.now(),payload:ie(p.text,p.attachments)},Z=Array.isArray(L)&&L.length>0?L.map(d=>({id:d.clientMessageId??d.id,sessionId:r,role:"user",ts:Date.parse(d.createdAt),payload:ie(d.text,d.attachments)})):null,Q=Z??[I],E=_e(r,I.id),ee=E.state==="queued";if(E.state!=="not_found"&&!ee){e.client.sendRes({type:"res",id:y,ok:!0,payload:E});return}if(Y){a?.source==="room_activation"&&X(i.adapter,r,g,O,U,P,a.managedAgentPolicy,e.managedRoomExecutor);try{await Ve(i.adapter,p.text,o,N,p.attachments,J,a?{source:a.source}:void 0)}catch(d){e.processedReqIds.delete(t.id),e.sessions.delete(r),await B(e,i),e.client.sendRes({type:"res",id:y,ok:!1,error:d instanceof Error?d.message:String(d)});return}}D({level:"info",sessionId:r,wsEvent:"chat.send.start",metadata:{reqId:t.id,agentType:g,sessionMode:$,managerConfig:S??null,modelId:o,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:k??null,reasoningEffort:N,...a?.source==="room_activation"?{managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:{}}});const V=(d,f)=>({...ce(le()),sessionId:r,clientMessageId:I.id,messageId:I.id,deliveryState:d,bodyRevision:f,committedAt:new Date(I.ts).toISOString()}),te=d=>{e.nativeFusion?.registerManagedSend({sessionId:r,agentType:g,sourceAgentType:fe(g,o),canonicalMessageId:I.id,sourceSessionKey:i.agentSessionId??n??null,text:p.text,managedEchoPolicy:d?.deliveryMode==="external_owner_queue"&&!Re?"import_following":"suppress_following",externalRunId:d?.deliveryMode==="external_owner_queue"?`codex-external-${d.queuedSubmissionId??I.id}`:null})},ne=()=>{i.currentRunId||(i.currentRunId=`dispatch:${t.id}`,i.currentRunPhase="thinking",i.nextEventSeq=0)},ae=()=>{if(te(),ee)return{...ce(le()),sessionId:r,clientMessageId:E.clientMessageId,messageId:E.messageId,deliveryState:E.deliveryState,bodyRevision:E.bodyRevision,committedAt:E.committedAt};try{Le(e,{sessionId:r,agentType:g,sessionMode:$,managerConfig:S??null,workDir:Se,agentSessionId:i.agentSessionId??n??null,modelId:o,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:k??null});let d=0;for(const[f,u]of Q.entries()){const x=Z?z(r,u,"committed"):b(r,u,"committed");f===0&&(d=x)}return $e(w,F??[]),M(e.client),V("committed",d)}catch(d){throw e.nativeFusion?.cancelManagedSend?.(I.id),d}},re=d=>{i.currentRunId||(i.nextEventSeq=0),te(d);const f=d?.deliveryMode==="external_owner_queue"?"queued":"executing";let u=0;for(const[x,j]of Q.entries()){const G=z(r,j,f);x===0&&(u=G)}return M(e.client),V(f,u)},oe=async(d,f)=>{e.nativeFusion?.cancelManagedSend?.(I.id);const u=ge(g,d);console.error(`[chat.send] send failed reqId=${t.id} sessionId=${r} agentType=${c} workDir=${w} agentSessionId=${i.agentSessionId??n??""}: ${u}`),e.sessions.delete(r);try{await B(e,i)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:u,runId:"",seq:0}}),!f){const x={id:`agent-error-${t.id}-${Date.now()}`,sessionId:r,role:"agent",ts:Date.now(),payload:u};b(r,x),M(e.client)}f&&(e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:u}))};if(Y){try{ae()}catch(u){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:`Durable message commit failed: ${u instanceof Error?u.message:String(u)}`});return}ne();let d;try{a?.source==="room_activation"&&(he(e,i,P),s());const u=p.attachments;d=await ye(i.adapter,p.text,o,N,u,J,a?{source:a.source}:void 0),D({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}catch(u){let x=0;for(const[G,ke]of Q.entries()){const we=z(r,ke,"failed");G===0&&(x=we)}M(e.client);const j=ge(g,u);await oe(u,!1),e.client.sendRes({type:"res",id:y,ok:!0,payload:{...V("failed",x),failureMessage:j}});return}const f=re(d??void 0);e.client.sendRes({type:"res",id:y,ok:!0,payload:{...f,...p.localized?{localizedAttachments:!0}:{},...d?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:d.deliveryMode,queuedSubmissionId:d.queuedSubmissionId}:{}}}),D({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});return}let se;try{se=ae()}catch(d){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:`Durable message commit failed: ${d instanceof Error?d.message:String(d)}`});return}ne(),a?.source==="room_activation"&&(X(i.adapter,r,g,O,U,P,a.managedAgentPolicy,e.managedRoomExecutor),he(e,i,P),s()),re(),e.client.sendRes({type:"res",id:y,ok:!0,payload:{...se,...p.localized?{localizedAttachments:!0}:{}}}),D({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});const Ae=p.attachments;ye(i.adapter,p.text,o,N,Ae,J,a?{source:a.source}:void 0).then(()=>{D({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}).catch(d=>{z(r,I,"failed"),M(e.client),oe(d,!1)})}async function ht(e,t){const{sessionId:a}=t.params,s=e.sessions.get(a);if(s){v(e,s);try{await s.adapter.stop()}catch{e.client.sendRes({type:"res",id:t.id,ok:!1,error:"agent_stop_failed"});return}Ge(e,a),e.activityPublisher?.publish(a,null)}e.client.sendRes({type:"res",id:t.id,ok:!0})}export{ht as handleChatAbort,pt as handleChatSend,Le as sendSessionUpdateEvent};
@@ -10,6 +10,7 @@ import '../agents/workbuddy.js';
10
10
  import '../agents/gemini.js';
11
11
  import '../agents/cursor.js';
12
12
  import '../agents/opencode.js';
13
+ import '../agents/pi-coding-agent.js';
13
14
  import '../agents/manager.js';
14
15
  import type { ManagedRoomBinder } from '../room/managed-room-binding.js';
15
16
  import type { ManagedRoomExecutor } from '../room/managed-room-ipc.js';
@@ -1 +1 @@
1
- import{SESSION_SYNC_PROTOCOL_VERSION as p,SESSION_SYNC_DATA_EPOCH as f,isAvailableAgentType as k}from"@shennian/wire";import g from"node:crypto";import{getRegisteredAgents as S,unregisterAgent as A}from"../agents/adapter.js";import{loadConfig as T}from"../config/index.js";import{handleUpgradeStart as I,handleUpgradeStatus as C}from"../commands/upgrade.js";import{handleAgentsRefresh as P,handleModelsRefresh as E}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as M,handleAgentConfigGet as x,handleAgentConfigTest as O,handleAgentConfigUpsert as _}from"./handlers/agent-config.js";import{handleChatAbort as F,handleChatSend as v}from"./handlers/chat.js";import{handleSessionRefresh as N}from"./handlers/session-refresh.js";import{handleSessionToolDetail as Q}from"./handlers/tool-detail.js";import{handleSessionTitleSet as z}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as W}from"./handlers/voice-window.js";import{handleRemoteAccessPause as V}from"./handlers/remote-access.js";import{cleanupPendingTransfers as B,handleFsLs as j,handleFsRead as $,handleFsRename as L,handleFsWrite as H,handleFsTransferAbort as Y,handleFsTransferChunk as G,handleFsTransferFinish as K,handleFsTransferStart as X,handleFsExportMarkdownPdf as Z,handleFsExportMarkdownPdfSetup as J,handleFsArchiveZip as q}from"./handlers/fs.js";import{handleRegionProbe as ee,handleRegionSwitch as se,handleUpgradeSetPolicy as te}from"./handlers/control.js";import{ManagerRuntimeService as ae,setManagerRuntimeService as b}from"../manager/runtime.js";import{ChatQueueManager as ie,TerminalQueuedChatDispatchError as ne}from"./queue.js";import{createAuthorizedFsRoot as oe,createAuthorizedFsRootAsync as re,resolveAuthorizedPath as ce,resolveAuthorizedPathAsync as le,resolveSessionWorkDir as he}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as de}from"../agents/custom.js";import{handleManagedRoomBind as ue,handleManagedRoomRestart as me}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as R,purgeCanonicalSession as pe,purgeSessionRecord as fe}from"./store.js";import{PersonalHistorySubscriptions as ge}from"./history-subscriptions.js";import{sweepAttachmentTransfers as ve}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as be}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as ye}from"./tool-detail-store.js";import{readDeletionSyncRevision as Re,writeDeletionSyncRevision as w}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as we,clearDeletionSyncFailure as ke,DeletionSyncRetryWaitError as Se,DeletionSyncTerminalError as Ae,recordDeletionSyncFailure as m}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Te}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as l}from"../personal-sync/epoch.js";const Ie=50;function Ce(r,e){if(!r||!Number.isSafeInteger(r.baselineRevision)||r.baselineRevision<e||!Number.isSafeInteger(r.latestRevision)||r.latestRevision<r.baselineRevision||!Array.isArray(r.deletions))throw new Error("invalid deletion sync response");let s=r.baselineRevision;for(const t of r.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of t.sources)if(!a?.sourceSessionKey||!k(a.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==r.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as fs}from"../fs/boundary.js";class us{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,s=null,t,a=null,i=null,c=null,n=null,h=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=c,this.managedRoomContextRevoker=n,this.managedRoomExecutor=h,this.managerRuntime=new ae({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o)}),this.chatQueue=new ie({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o),dispatchQueuedReq:o=>this.dispatchQueuedChat(o)}),this.historySubscriptions=new ge(this.client),this.nativeCleanupOutbox=new Te({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(o=>{this.handleExternalTerminal(o)}),this.nativeCleanupOutbox.start(),b(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(o=>{console.error("[session.activity] managed probe failed",o)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const o=new Set([...R().map(d=>d.workDir),...Array.from(this.pendingTransfers.values()).map(d=>d.rootPath)]);for(const d of o)try{ve(this.resolvePath(d))}catch(u){console.error("[attachment.cleanup] failed",u instanceof Error?u.message:String(u))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,s)=>this.resolveAuthorizedPath(e,s),resolveAuthorizedPathAsync:(e,s)=>this.resolveAuthorizedPathAsync(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}async enqueueManagedRoomBatch(e){const s=e.map(t=>{const a=R().find(i=>i.sessionId===t.params.sessionId);return{context:t.context,params:{...t.params,modelId:a?.modelId??t.params.modelId,sessionMode:a?.sessionMode??t.params.sessionMode,managerConfig:a?.managerConfig??t.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??t.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??t.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(s)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}async dispatchQueuedChat(e){const s={value:null},t=new Proxy(this.client,{get:(c,n)=>{if(n==="sendRes")return o=>{s.value=o};const h=Reflect.get(c,n,c);return typeof h=="function"?h.bind(c):h}}),a=e.params,i=a.roomActivation===!0?{source:"room_activation",managedAgentPolicy:a.managedAgentPolicy,managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:void 0;if(await v({...this.getRuntime(),client:t},e,i),s.value?.payload?.deliveryState==="failed")throw new ne(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(){we();try{await this.synchronizeSessionDeletionsOnce(),ke()}catch(e){if(e instanceof Se||e instanceof Ae)throw e;const s=e instanceof Error?e.message:String(e),t=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(s);throw m({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),s=Re(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const n=t.rejection;throw n?(m({code:n.code,retryable:n.retryable&&n.requiredAction==="retry"}),new Error(n.code)):(m({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const a=t.payload;l(a),Ce(a,s);let i=a.baselineRevision;i>s&&w(i);for(const n of a.deletions)n.deletionRevision<=i||(await this.applySessionDeletion(n),i=n.deletionRevision,w(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const c=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,deletionRevision:i}});if(!c.ok)throw new Error(c.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new be({daemonInstallationId:y()});s.recordSession(e);for(const a of e.sources)s.record({...e,...a});const t=this.sessions.get(e.sessionId);if(t){this.revokeManagedRoomToken(t);try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),ye(e.sessionId),pe(e.sessionId),fe(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,s){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:s}})}async handleExternalTerminal(e){const s=this.sessions.get(e);s&&(this.sessions.delete(e),this.revokeManagedRoomToken(s),s.heartbeatTimer&&clearInterval(s.heartbeatTimer),s.adapter.removeAllListeners(),await s.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:a,canStop:t.canStop??!0});const i=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:i,runPhase:t.runPhase,canStop:t.canStop??!0},seq:i,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of S())s.startsWith("custom:")&&A(s);const e=T();for(const[s,t]of Object.entries(e.customAgents??{}))de(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":l(e.params),await v(s,e);break;case"chat.enqueue":l(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":l(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":l(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":l(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":l(e.params),await F(s,e);break;case"session.refresh":l(e.params),await N(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":U(s,e);break;case"session.tool.detail":await Q(s,e);break;case"voice.session.body.window":W(s,e);break;case"session.title.set":await z(s,e);break;case"room.agent.bind-managed":await ue(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await me(s,this.managedRoomBinder,e);break;case"fs.ls":await j(s,e);break;case"fs.read":await $(s,e);break;case"fs.write":await H(s,e);break;case"fs.export.markdown-pdf":await Z(s,e);break;case"fs.export.markdown-pdf.setup":await J(s,e);break;case"fs.archive.zip":await q(s,e);break;case"fs.rename":await L(s,e);break;case"fs.transfer.start":await X(s,e);break;case"fs.transfer.chunk":await G(s,e);break;case"fs.transfer.finish":await K(s,e);break;case"fs.transfer.abort":await Y(s,e);break;case"region.probe":await ee(s,e);break;case"region.switch":await se(s,e);break;case"upgrade.start":await I(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await C(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await te(s,e);break;case"machine.remote-access.pause":await V(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await P(s,e);break;case"models.refresh":await E(s,e);break;case"agent.config.get":await x(s,e);break;case"agent.config.upsert":await _(s,e);break;case"agent.config.clear":await M(s,e);break;case"agent.config.test":await O(s,e);break;case"agent.capabilities.list":await D(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<Ie)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});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 he(e)}resolveAuthorizedPath(e,s){return ce(e,oe(s))}async resolveAuthorizedPathAsync(e,s){return le(e,await re(s))}async cleanup(){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),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),B(this.getRuntime()),await this.managerRuntime.stop(),b(null)}revokeManagedRoomToken(e){const s=e.managedRoomContextToken;e.managedRoomContextToken=null,s&&this.managedRoomContextRevoker?.(s)}}export{us as SessionManager,fs as resolveSessionWorkDir};
1
+ import{SESSION_SYNC_PROTOCOL_VERSION as p,SESSION_SYNC_DATA_EPOCH as f,isAvailableAgentType as k}from"@shennian/wire";import g from"node:crypto";import{getRegisteredAgents as S,unregisterAgent as A}from"../agents/adapter.js";import{loadConfig as T}from"../config/index.js";import{handleUpgradeStart as I,handleUpgradeStatus as C}from"../commands/upgrade.js";import{handleAgentsRefresh as P,handleModelsRefresh as E}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as M,handleAgentConfigGet as x,handleAgentConfigTest as O,handleAgentConfigUpsert as _}from"./handlers/agent-config.js";import{handleChatAbort as F,handleChatSend as v}from"./handlers/chat.js";import{handleSessionRefresh as N}from"./handlers/session-refresh.js";import{handleSessionToolDetail as Q}from"./handlers/tool-detail.js";import{handleSessionTitleSet as z}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as W}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as V}from"./handlers/voice-window.js";import{handleRemoteAccessPause as B}from"./handlers/remote-access.js";import{cleanupPendingTransfers as j,handleFsLs as $,handleFsRead as L,handleFsRename as H,handleFsWrite as Y,handleFsTransferAbort as G,handleFsTransferChunk as K,handleFsTransferFinish as X,handleFsTransferStart as Z,handleFsExportMarkdownPdf as J,handleFsExportMarkdownPdfSetup as q,handleFsArchiveZip as ee}from"./handlers/fs.js";import{handleRegionProbe as se,handleRegionSwitch as te,handleUpgradeSetPolicy as ae}from"./handlers/control.js";import{ManagerRuntimeService as ie,setManagerRuntimeService as b}from"../manager/runtime.js";import{ChatQueueManager as ne,TerminalQueuedChatDispatchError as oe}from"./queue.js";import{createAuthorizedFsRoot as re,createAuthorizedFsRootAsync as ce,resolveAuthorizedPath as le,resolveAuthorizedPathAsync as he,resolveSessionWorkDir as de}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 ue}from"../agents/custom.js";import{handleManagedRoomBind as me,handleManagedRoomRestart as pe}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as R,purgeCanonicalSession as fe,purgeSessionRecord as ge}from"./store.js";import{PersonalHistorySubscriptions as ve}from"./history-subscriptions.js";import{sweepAttachmentTransfers as be}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ye}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as Re}from"./tool-detail-store.js";import{readDeletionSyncRevision as we,writeDeletionSyncRevision as w}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as ke,clearDeletionSyncFailure as Se,DeletionSyncRetryWaitError as Ae,DeletionSyncTerminalError as Te,recordDeletionSyncFailure as m}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Ie}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as l}from"../personal-sync/epoch.js";const Ce=50;function Pe(r,e){if(!r||!Number.isSafeInteger(r.baselineRevision)||r.baselineRevision<e||!Number.isSafeInteger(r.latestRevision)||r.latestRevision<r.baselineRevision||!Array.isArray(r.deletions))throw new Error("invalid deletion sync response");let s=r.baselineRevision;for(const t of r.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of t.sources)if(!a?.sourceSessionKey||!k(a.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==r.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as bs}from"../fs/boundary.js";class fs{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,s=null,t,a=null,i=null,c=null,n=null,h=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=c,this.managedRoomContextRevoker=n,this.managedRoomExecutor=h,this.managerRuntime=new ie({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o)}),this.chatQueue=new ne({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o),dispatchQueuedReq:o=>this.dispatchQueuedChat(o)}),this.historySubscriptions=new ve(this.client),this.nativeCleanupOutbox=new Ie({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(o=>{this.handleExternalTerminal(o)}),this.nativeCleanupOutbox.start(),b(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(o=>{console.error("[session.activity] managed probe failed",o)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const o=new Set([...R().map(d=>d.workDir),...Array.from(this.pendingTransfers.values()).map(d=>d.rootPath)]);for(const d of o)try{be(this.resolvePath(d))}catch(u){console.error("[attachment.cleanup] failed",u instanceof Error?u.message:String(u))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,s)=>this.resolveAuthorizedPath(e,s),resolveAuthorizedPathAsync:(e,s)=>this.resolveAuthorizedPathAsync(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}async enqueueManagedRoomBatch(e){const s=e.map(t=>{const a=R().find(i=>i.sessionId===t.params.sessionId);return{context:t.context,params:{...t.params,modelId:a?.modelId??t.params.modelId,sessionMode:a?.sessionMode??t.params.sessionMode,managerConfig:a?.managerConfig??t.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??t.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??t.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(s)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}async dispatchQueuedChat(e){const s={value:null},t=new Proxy(this.client,{get:(c,n)=>{if(n==="sendRes")return o=>{s.value=o};const h=Reflect.get(c,n,c);return typeof h=="function"?h.bind(c):h}}),a=e.params,i=a.roomActivation===!0?{source:"room_activation",managedAgentPolicy:a.managedAgentPolicy,managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:void 0;if(await v({...this.getRuntime(),client:t},e,i),s.value?.payload?.deliveryState==="failed")throw new oe(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(){ke();try{await this.synchronizeSessionDeletionsOnce(),Se()}catch(e){if(e instanceof Ae||e instanceof Te)throw e;const s=e instanceof Error?e.message:String(e),t=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(s);throw m({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),s=we(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const n=t.rejection;throw n?(m({code:n.code,retryable:n.retryable&&n.requiredAction==="retry"}),new Error(n.code)):(m({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const a=t.payload;l(a),Pe(a,s);let i=a.baselineRevision;i>s&&w(i);for(const n of a.deletions)n.deletionRevision<=i||(await this.applySessionDeletion(n),i=n.deletionRevision,w(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const c=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,deletionRevision:i}});if(!c.ok)throw new Error(c.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new ye({daemonInstallationId:y()});s.recordSession(e);for(const a of e.sources)s.record({...e,...a});const t=this.sessions.get(e.sessionId);if(t){this.revokeManagedRoomToken(t);try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),Re(e.sessionId),fe(e.sessionId),ge(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,s){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:s}})}async handleExternalTerminal(e){const s=this.sessions.get(e);s&&(this.sessions.delete(e),this.revokeManagedRoomToken(s),s.heartbeatTimer&&clearInterval(s.heartbeatTimer),s.adapter.removeAllListeners(),await s.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:a,canStop:t.canStop??!0});const i=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:i,runPhase:t.runPhase,canStop:t.canStop??!0},seq:i,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of S())s.startsWith("custom:")&&A(s);const e=T();for(const[s,t]of Object.entries(e.customAgents??{}))ue(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":l(e.params),await v(s,e);break;case"chat.enqueue":l(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":l(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":l(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":l(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":l(e.params),await F(s,e);break;case"session.refresh":l(e.params),await N(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":U(s,e);break;case"session.tool.detail":await Q(s,e);break;case"voice.session.body.window":V(s,e);break;case"session.title.set":await z(s,e);break;case"room.agent.bind-managed":await me(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await pe(s,this.managedRoomBinder,e);break;case"fs.ls":await $(s,e);break;case"fs.read":await L(s,e);break;case"fs.write":await Y(s,e);break;case"fs.export.markdown-pdf":await J(s,e);break;case"fs.export.markdown-pdf.setup":await q(s,e);break;case"fs.archive.zip":await ee(s,e);break;case"fs.rename":await H(s,e);break;case"fs.transfer.start":await Z(s,e);break;case"fs.transfer.chunk":await K(s,e);break;case"fs.transfer.finish":await X(s,e);break;case"fs.transfer.abort":await G(s,e);break;case"region.probe":await se(s,e);break;case"region.switch":await te(s,e);break;case"upgrade.start":await I(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await C(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await ae(s,e);break;case"machine.remote-access.pause":await B(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await P(s,e);break;case"models.refresh":await E(s,e);break;case"agent.config.get":await x(s,e);break;case"agent.config.upsert":await _(s,e);break;case"agent.config.clear":await M(s,e);break;case"agent.config.test":await O(s,e);break;case"agent.workspace.validate":await W(s,e);break;case"agent.capabilities.list":await D(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<Ce)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});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 de(e)}resolveAuthorizedPath(e,s){return le(e,re(s))}async resolveAuthorizedPathAsync(e,s){return he(e,await ce(s))}async cleanup(){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),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),j(this.getRuntime()),await this.managerRuntime.stop(),b(null)}revokeManagedRoomToken(e){const s=e.managedRoomContextToken;e.managedRoomContextToken=null,s&&this.managedRoomContextRevoker?.(s)}}export{fs as SessionManager,bs as resolveSessionWorkDir};
@@ -1,6 +1,6 @@
1
- import{createInterface as E}from"node:readline";import D from"node:fs";import{isAvailableBuiltinAgentType as g}from"@shennian/wire";import{buildAgentProcessEnv as w}from"../agent-env.js";import{resolveBuiltinCommand as b,spawnAgentCommand as y}from"../agents/command-spec.js";const C=64*1024,h=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,k=/^ses_[A-Za-z0-9_-]{6,191}$/;function A(n,r){return n.length>=C?n:`${n}${String(r)}`.slice(0,C)}const x=async(n,r,i=1e4,e)=>{if(!g(n))return{code:null,stdout:"",stderr:"",timedOut:!1,spawnErrorCode:"command_unavailable"};const t=b(n);return t?await new Promise(c=>{let s="",l="",d=!1,m=!1;const p=y(t,r,{stdio:["ignore","pipe","pipe"],env:w({NO_COLOR:"1"}),...e?{cwd:e}:{}}),u=o=>{d||(d=!0,a&&clearTimeout(a),c({...o,stdout:s,stderr:l,timedOut:m}))};p.stdout?.on("data",o=>{s=A(s,o)}),p.stderr?.on("data",o=>{l=A(l,o)}),p.on("error",o=>{u({code:null,spawnErrorCode:o.code??"spawn_failed"})}),p.on("close",o=>u({code:o}));const a=setTimeout(()=>{m=!0,p.kill("SIGTERM"),setTimeout(()=>p.kill("SIGKILL"),1e3).unref?.()},i);a.unref?.()}):{code:null,stdout:"",stderr:"",timedOut:!1,spawnErrorCode:"command_unavailable"}};function $(n){return`${n.stdout}
2
- ${n.stderr}`.toLowerCase()}function f(n,r){if(n.spawnErrorCode==="command_unavailable"||n.spawnErrorCode==="ENOENT")return{outcome:"unsupported"};if(n.timedOut)return{outcome:"transient",errorCode:"command_timeout"};const i=$(n);return/not found|does not exist|no session|unknown session|already (?:deleted|archived)/.test(i)?{outcome:"already_absent"}:/busy|locked|writer|in use|current active session|active turn|temporar|try again|timed out|timeout/.test(i)?{outcome:"transient",errorCode:"native_busy"}:/invalid.*(?:uuid|session|id)|malformed/.test(i)?{outcome:"invalid_identity"}:/permission denied|unauthenticated|authentication|forbidden/.test(i)?{outcome:"permanent",errorCode:"native_auth_required"}:n.code===0?{outcome:r}:{outcome:"transient",errorCode:"native_command_failed"}}async function _(n,r,i,e){const t=await n(r,i);return!t.timedOut&&t.code===0&&e.test(`${t.stdout}
3
- ${t.stderr}`)}async function I(n,r){return await new Promise(i=>{let e=null,t=!1,c=1,s=null;const l=new Map,d=u=>{if(!t){t=!0,s&&clearTimeout(s);for(const a of l.values())a.reject(new Error("app_server_stopped"));l.clear(),e?.kill("SIGTERM"),i(u)}},m=(u,a)=>{if(!e?.stdin)return Promise.reject(new Error("app_server_unavailable"));const o=c++;return new Promise((v,O)=>{l.set(o,{resolve:v,reject:O}),e.stdin.write(`${JSON.stringify({id:o,method:u,params:a})}
4
- `)})};try{e=y(n,["app-server","--listen","stdio://"],{stdio:["pipe","pipe","ignore"],env:w({NO_COLOR:"1"})})}catch{d({outcome:"transient",errorCode:"app_server_start_failed"});return}E({input:e.stdout}).on("line",u=>{if(!u.trim())return;let a;try{a=JSON.parse(u)}catch{return}if(a.id==null)return;const o=l.get(a.id);if(o)if(l.delete(a.id),a.error){const v=new Error(a.error.message??"app_server_error");v.rpcCode=a.error.code,o.reject(v)}else o.resolve(a.result)}),e.on("error",()=>d({outcome:"transient",errorCode:"app_server_start_failed"})),e.on("close",()=>{t||d({outcome:"transient",errorCode:"app_server_stopped"})}),s=setTimeout(()=>d({outcome:"transient",errorCode:"app_server_timeout"}),12e3),s.unref?.(),(async()=>{try{await m("initialize",{clientInfo:{name:"shennian-native-cleanup",title:"Shennian",version:"1"},capabilities:{experimentalApi:!0}}),await m("thread/archive",{threadId:r}),d({outcome:"archived"})}catch(u){const a=u,o=a.message.toLowerCase();/not found|does not exist|already archived|no rollout/.test(o)?d({outcome:"already_absent"}):/busy|locked|writer|active turn|in use/.test(o)?d({outcome:"transient",errorCode:"native_busy"}):a.rpcCode===-32601||/method not found|unsupported/.test(o)?d({outcome:"unsupported"}):d({outcome:"transient",errorCode:"app_server_failed"})}})()})}class S{options;capabilities=new Map;constructor(r={}){this.options=r}async execute(r,i,e={}){const t=this.adapter(r),c=await this.probe(r,t);if(c.canArchive&&t.archive)return await t.archive(i);if(c.canDelete&&t.delete){if(r==="gemini"&&!e.workDir)return{outcome:"invalid_identity"};if((r==="gemini"||r==="opencode")&&e.workDir)try{if(!D.statSync(e.workDir).isDirectory())return{outcome:"permanent",errorCode:"workdir_unavailable"}}catch{return{outcome:"permanent",errorCode:"workdir_unavailable"}}return await this.deleteWithContext(t,i,e)}return{outcome:"unsupported"}}async deleteWithContext(r,i,e){return r.delete?await r.delete(i,e):{outcome:"unsupported"}}clearCapabilityCache(){this.capabilities.clear()}probe(r,i){const e=this.capabilities.get(r);if(e)return e;const t=i.probe().catch(()=>({canArchive:!1,canDelete:!1}));return this.capabilities.set(r,t),t}adapter(r){const i=this.options.runner??x;return r==="codex"?{probe:async()=>({canArchive:await _(i,"codex",["--help"],/\barchive\b/),canDelete:!1}),archive:async e=>{if(!h.test(e))return{outcome:"invalid_identity"};const t=this.options.codexArchive,c=b("codex");let s={outcome:"unsupported"};return t?s=await t(e):c&&(s=await I(c,e)),s.outcome==="archived"||s.outcome==="already_absent"||s.outcome==="invalid_identity"||s.outcome==="transient"&&s.errorCode==="native_busy"?s:f(await i("codex",["archive",e],15e3),"archived")}}:r==="gemini"?{probe:async()=>({canArchive:!1,canDelete:await _(i,"gemini",["--help"],/--delete-session\b/)}),delete:async(e,t)=>{if(!h.test(e))return{outcome:"invalid_identity"};const c=await i("gemini",["--delete-session",e],15e3,t?.workDir);return/invalid session identifier/i.test(`${c.stdout}
5
- ${c.stderr}`)?{outcome:"already_absent"}:f(c,"deleted")}}:r==="opencode"?{probe:async()=>({canArchive:!1,canDelete:await _(i,"opencode",["session","delete","--help"],/session delete\s+<sessionID>/i)}),delete:async(e,t)=>k.test(e)?f(await i("opencode",["session","delete",e],15e3,t?.workDir),"deleted"):{outcome:"invalid_identity"}}:r==="cursor"?{probe:async()=>{const e=await i("cursor",["--help"]),t=`${e.stdout}
6
- ${e.stderr}`;return{canArchive:e.code===0&&/\barchive-chat\b/.test(t),canDelete:e.code===0&&/\bdelete-chat\b/.test(t)}},archive:async e=>h.test(e)?f(await i("cursor",["archive-chat",e],15e3),"archived"):{outcome:"invalid_identity"},delete:async e=>h.test(e)?f(await i("cursor",["delete-chat",e],15e3),"deleted"):{outcome:"invalid_identity"}}:{probe:async()=>({canArchive:!1,canDelete:!1})}}}export{S as NativeSessionLifecycleRegistry,x as runNativeLifecycleCommand};
1
+ import{createInterface as E}from"node:readline";import D from"node:fs";import{isAvailableBuiltinAgentType as g}from"@shennian/wire";import{buildAgentProcessEnv as w}from"../agent-env.js";import{resolveBuiltinCommand as b,spawnAgentCommand as y}from"../agents/command-spec.js";import{deleteOwnedPiSession as k}from"../agents/pi-session-ownership.js";const C=64*1024,_=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,x=/^ses_[A-Za-z0-9_-]{6,191}$/;function A(n,r){return n.length>=C?n:`${n}${String(r)}`.slice(0,C)}const $=async(n,r,i=1e4,e)=>{if(!g(n))return{code:null,stdout:"",stderr:"",timedOut:!1,spawnErrorCode:"command_unavailable"};const t=b(n);return t?await new Promise(c=>{let s="",l="",d=!1,p=!1;const m=y(t,r,{stdio:["ignore","pipe","pipe"],env:w({NO_COLOR:"1"}),...e?{cwd:e}:{}}),u=o=>{d||(d=!0,a&&clearTimeout(a),c({...o,stdout:s,stderr:l,timedOut:p}))};m.stdout?.on("data",o=>{s=A(s,o)}),m.stderr?.on("data",o=>{l=A(l,o)}),m.on("error",o=>{u({code:null,spawnErrorCode:o.code??"spawn_failed"})}),m.on("close",o=>u({code:o}));const a=setTimeout(()=>{p=!0,m.kill("SIGTERM"),setTimeout(()=>m.kill("SIGKILL"),1e3).unref?.()},i);a.unref?.()}):{code:null,stdout:"",stderr:"",timedOut:!1,spawnErrorCode:"command_unavailable"}};function I(n){return`${n.stdout}
2
+ ${n.stderr}`.toLowerCase()}function f(n,r){if(n.spawnErrorCode==="command_unavailable"||n.spawnErrorCode==="ENOENT")return{outcome:"unsupported"};if(n.timedOut)return{outcome:"transient",errorCode:"command_timeout"};const i=I(n);return/not found|does not exist|no session|unknown session|already (?:deleted|archived)/.test(i)?{outcome:"already_absent"}:/busy|locked|writer|in use|current active session|active turn|temporar|try again|timed out|timeout/.test(i)?{outcome:"transient",errorCode:"native_busy"}:/invalid.*(?:uuid|session|id)|malformed/.test(i)?{outcome:"invalid_identity"}:/permission denied|unauthenticated|authentication|forbidden/.test(i)?{outcome:"permanent",errorCode:"native_auth_required"}:n.code===0?{outcome:r}:{outcome:"transient",errorCode:"native_command_failed"}}async function h(n,r,i,e){const t=await n(r,i);return!t.timedOut&&t.code===0&&e.test(`${t.stdout}
3
+ ${t.stderr}`)}async function N(n,r){return await new Promise(i=>{let e=null,t=!1,c=1,s=null;const l=new Map,d=u=>{if(!t){t=!0,s&&clearTimeout(s);for(const a of l.values())a.reject(new Error("app_server_stopped"));l.clear(),e?.kill("SIGTERM"),i(u)}},p=(u,a)=>{if(!e?.stdin)return Promise.reject(new Error("app_server_unavailable"));const o=c++;return new Promise((v,O)=>{l.set(o,{resolve:v,reject:O}),e.stdin.write(`${JSON.stringify({id:o,method:u,params:a})}
4
+ `)})};try{e=y(n,["app-server","--listen","stdio://"],{stdio:["pipe","pipe","ignore"],env:w({NO_COLOR:"1"})})}catch{d({outcome:"transient",errorCode:"app_server_start_failed"});return}E({input:e.stdout}).on("line",u=>{if(!u.trim())return;let a;try{a=JSON.parse(u)}catch{return}if(a.id==null)return;const o=l.get(a.id);if(o)if(l.delete(a.id),a.error){const v=new Error(a.error.message??"app_server_error");v.rpcCode=a.error.code,o.reject(v)}else o.resolve(a.result)}),e.on("error",()=>d({outcome:"transient",errorCode:"app_server_start_failed"})),e.on("close",()=>{t||d({outcome:"transient",errorCode:"app_server_stopped"})}),s=setTimeout(()=>d({outcome:"transient",errorCode:"app_server_timeout"}),12e3),s.unref?.(),(async()=>{try{await p("initialize",{clientInfo:{name:"shennian-native-cleanup",title:"Shennian",version:"1"},capabilities:{experimentalApi:!0}}),await p("thread/archive",{threadId:r}),d({outcome:"archived"})}catch(u){const a=u,o=a.message.toLowerCase();/not found|does not exist|already archived|no rollout/.test(o)?d({outcome:"already_absent"}):/busy|locked|writer|active turn|in use/.test(o)?d({outcome:"transient",errorCode:"native_busy"}):a.rpcCode===-32601||/method not found|unsupported/.test(o)?d({outcome:"unsupported"}):d({outcome:"transient",errorCode:"app_server_failed"})}})()})}class j{options;capabilities=new Map;constructor(r={}){this.options=r}async execute(r,i,e={}){const t=this.adapter(r),c=await this.probe(r,t);if(c.canArchive&&t.archive)return await t.archive(i);if(c.canDelete&&t.delete){if(r==="gemini"&&!e.workDir)return{outcome:"invalid_identity"};if((r==="gemini"||r==="opencode")&&e.workDir)try{if(!D.statSync(e.workDir).isDirectory())return{outcome:"permanent",errorCode:"workdir_unavailable"}}catch{return{outcome:"permanent",errorCode:"workdir_unavailable"}}return await this.deleteWithContext(t,i,e)}return{outcome:"unsupported"}}async deleteWithContext(r,i,e){return r.delete?await r.delete(i,e):{outcome:"unsupported"}}clearCapabilityCache(){this.capabilities.clear()}probe(r,i){const e=this.capabilities.get(r);if(e)return e;const t=i.probe().catch(()=>({canArchive:!1,canDelete:!1}));return this.capabilities.set(r,t),t}adapter(r){const i=this.options.runner??$;return r==="codex"?{probe:async()=>({canArchive:await h(i,"codex",["--help"],/\barchive\b/),canDelete:!1}),archive:async e=>{if(!_.test(e))return{outcome:"invalid_identity"};const t=this.options.codexArchive,c=b("codex");let s={outcome:"unsupported"};return t?s=await t(e):c&&(s=await N(c,e)),s.outcome==="archived"||s.outcome==="already_absent"||s.outcome==="invalid_identity"||s.outcome==="transient"&&s.errorCode==="native_busy"?s:f(await i("codex",["archive",e],15e3),"archived")}}:r==="gemini"?{probe:async()=>({canArchive:!1,canDelete:await h(i,"gemini",["--help"],/--delete-session\b/)}),delete:async(e,t)=>{if(!_.test(e))return{outcome:"invalid_identity"};const c=await i("gemini",["--delete-session",e],15e3,t?.workDir);return/invalid session identifier/i.test(`${c.stdout}
5
+ ${c.stderr}`)?{outcome:"already_absent"}:f(c,"deleted")}}:r==="opencode"?{probe:async()=>({canArchive:!1,canDelete:await h(i,"opencode",["session","delete","--help"],/session delete\s+<sessionID>/i)}),delete:async(e,t)=>x.test(e)?f(await i("opencode",["session","delete",e],15e3,t?.workDir),"deleted"):{outcome:"invalid_identity"}}:r==="cursor"?{probe:async()=>{const e=await i("cursor",["--help"]),t=`${e.stdout}
6
+ ${e.stderr}`;return{canArchive:e.code===0&&/\barchive-chat\b/.test(t),canDelete:e.code===0&&/\bdelete-chat\b/.test(t)}},archive:async e=>_.test(e)?f(await i("cursor",["archive-chat",e],15e3),"archived"):{outcome:"invalid_identity"},delete:async e=>_.test(e)?f(await i("cursor",["delete-chat",e],15e3),"deleted"):{outcome:"invalid_identity"}}:r==="pi-coding-agent"?{probe:async()=>({canArchive:!1,canDelete:!0}),delete:async e=>{const t=k(e);return t==="deleted"?{outcome:"deleted"}:t==="already_absent"?{outcome:"already_absent"}:{outcome:"invalid_identity"}}}:{probe:async()=>({canArchive:!1,canDelete:!1})}}}export{j as NativeSessionLifecycleRegistry,$ as runNativeLifecycleCommand};
@@ -1,4 +1,4 @@
1
- import { type ChatEnqueueParams, type ChatQueueSnapshot, type ReqFrame } from '@shennian/wire';
1
+ import { type AgentType, type ChatEnqueueParams, type ChatQueueSnapshot, type ManagerConfig, type ReqFrame, type SessionMode } from '@shennian/wire';
2
2
  import type { LocalReqDispatcher } from '../manager/runtime.js';
3
3
  import type { SessionManagerRuntime } from './types.js';
4
4
  import type { CompiledManagedAgentPolicy } from '../security/managed-agent-policy.js';
@@ -12,6 +12,10 @@ export type ManagedRoomQueueContext = {
12
12
  };
13
13
  export type PreparedManagedRoomTurn = {
14
14
  systemPrompt: string;
15
+ sessionMode: SessionMode;
16
+ managerConfig: ManagerConfig | null;
17
+ managerDefaultWorkerAgentType: AgentType | null;
18
+ managerDefaultWorkerModelId: string | null;
15
19
  roomContextRegistryKey: string;
16
20
  managedRoomContextToken: string;
17
21
  managedAgentPolicy: CompiledManagedAgentPolicy;
@@ -1,5 +1,5 @@
1
- import h from"node:fs";import{randomUUID as q}from"node:crypto";import{CODEX_DESKTOP_SEND_UNAVAILABLE as z,buildUserMessagePayload as j}from"@shennian/wire";import{isCodexThreadTailInterrupted as K}from"../native-fusion/codex-files.js";import{mergeProjectedSessions as J}from"./projection.js";import{parsePersonalAttachmentReferences as D}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as w,markAttachmentReferences as T}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as C}from"../personal-sync/paths.js";import{getPersonalSyncIdentity as S}from"../personal-sync/epoch.js";import{getDaemonInstallationId as R}from"../personal-sync/identity.js";import{appendMessage as v,getMessageDeliveryStatus as k,listSessionRecords as U,recordSession as b,tombstoneMessage as E,upsertMessage as P}from"./store.js";import{requestSessionIndexPublish as M}from"./index-publisher.js";class V extends Error{constructor(e){super(e),this.name="TerminalQueuedChatDispatchError"}}function G(){return{sessions:{}}}function $(){return new Date().toISOString()}function H(s){return`enqueue-send-${s}-${q()}`}function g(){try{const s=JSON.parse(h.readFileSync(A(),"utf-8"));return{sessions:s.sessions&&typeof s.sessions=="object"?s.sessions:{}}}catch(s){if(s.code==="ENOENT"||s instanceof Error&&s.message.startsWith("ENOENT:"))return G();throw s}}function p(s){const e=C();h.mkdirSync(e,{recursive:!0,mode:448});const n=A(),t=`${n}.tmp-${process.pid}-${q()}`;try{h.writeFileSync(t,`${JSON.stringify(s,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"});const i=h.openSync(t,"r");if(typeof i=="number")try{h.fsyncSync(i)}finally{h.closeSync(i)}h.renameSync(t,n)}finally{try{h.unlinkSync(t)}catch{}}}function A(){return C("outbound-queue.json")}function Q(s){const e=$();return{...S(R()),id:s.queueMessageId||s.clientMessageId||`queue-${q()}`,sessionId:s.sessionId,text:s.text,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,reasoningEffort:s.reasoningEffort??null,clientMessageId:s.clientMessageId??null,attachments:D(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}function x(s){return{id:s.clientMessageId??s.id,sessionId:s.sessionId,role:"user",ts:Date.parse(s.createdAt),payload:j(s.text,s.attachments)}}class he{opts;draining=new Set;inFlightMessageIds=new Map;prepareManagedRoomTurn=null;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{...S(R()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:(g().sessions[e]??[]).map(B)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const n=this.opts.getRuntime(),t=g(),i=new Set,r=new Map;let a=!1;for(const o of e){const d=o.params;if(!d.sessionId||!d.text||!d.agentType||!d.workDir)throw new Error("sessionId, text, agentType and workDir are required");const u=Q({...S(R()),...d});u.origin="room",u.managedRoom=o.context;const f=t.sessions[d.sessionId]??[],l=N(f,u),I=u.clientMessageId??u.id,y=k(d.sessionId,I);if(!l&&y.state==="not_found")f.push(u),t.sessions[d.sessionId]=f,r.set(`${u.sessionId}\0${I}`,u),i.add(d.sessionId),a=!0;else if(l&&y.state==="not_found")r.set(`${l.sessionId}\0${I}`,l),i.add(d.sessionId);else if(l&&y.state==="queued")i.add(d.sessionId);else if(l){const m=f.filter(c=>c.id!==l.id);m.length?t.sessions[d.sessionId]=m:delete t.sessions[d.sessionId],a=!0}}a&&p(t);for(const o of r.values())v(o.sessionId,x(o),"queued"),b({sessionId:o.sessionId,agentType:o.agentType,sessionMode:o.sessionMode,managerConfig:o.managerConfig,workDir:o.workDir,agentSessionId:W(n,o),modelId:o.modelId});if(i.size!==0){M(n.client);for(const o of i)this.logLifecycle("enqueue.persisted",o,{origin:"room",pendingCount:g().sessions[o]?.length??0}),this.broadcast(o),this.drainNext(o)}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if(J(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=n.resolvePath(t.workDir),r=D(t.attachments);r?.length&&w(i,r);const a={text:t.text,attachments:r,localized:!1},o=n.sessions.get(t.sessionId);if(t.agentType==="codex"&&t.agentSessionId&&o?.currentRunId?.startsWith("dispatch:")&&K(t.agentSessionId)){n.client.sendRes({type:"res",id:e.id,ok:!1,error:z});return}const d=await this.isSessionBusy(t.sessionId),u=g(),f=u.sessions[t.sessionId]??[];if(!d&&!f.length){await this.opts.dispatchReq({...e,id:H(e.id),method:"chat.send",params:{...t,text:a.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:a.attachments}});return}const l=Q({...t,text:a.text,attachments:a.attachments}),I=l.clientMessageId??l.id,y=k(t.sessionId,I);if(y.state!=="not_found"){n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:y.state==="queued",queueMessageId:l.id,...y,queue:this.getSnapshot(t.sessionId)}});return}const m=N(f,l);m||(u.sessions[t.sessionId]=[...f,l],p(u));const c=m??l;b({sessionId:c.sessionId,agentType:c.agentType,sessionMode:c.sessionMode,managerConfig:c.managerConfig,workDir:c.workDir,agentSessionId:c.agentSessionId,modelId:c.modelId}),v(c.sessionId,x(c),"queued"),T(n.resolvePath(c.workDir),c.attachments??[]),M(n.client),this.logLifecycle("enqueue.persisted",t.sessionId,{origin:t.origin??"app",queueMessageId:c.id,pendingCount:(g().sessions[t.sessionId]??[]).length}),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:m?.id??l.id,...k(t.sessionId,m?.clientMessageId??m?.id??l.clientMessageId??l.id),queue:this.getSnapshot(t.sessionId),...a.localized?{localizedAttachments:!0}:{}}}),d||this.drainNext(t.sessionId)}async handleGet(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(t.sessionId)}async handleEdit(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const i=g(),r=i.sessions[t.sessionId]??[],a=r.findIndex(u=>u.id===t.queueMessageId);if(a<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}if(r[a].managedRoom){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const o=D(t.attachments),d=n.resolvePath(r[a].workDir);o?.length&&w(d,o),r[a]={...r[a],text:t.text,attachments:o,updatedAt:$()},i.sessions[t.sessionId]=r,p(i),P(t.sessionId,x(r[a]),"queued"),r[a].attachments?.length&&T(d,r[a].attachments??[]),M(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const i=g(),r=i.sessions[t.sessionId]??[],a=r.find(d=>d.id===t.queueMessageId);if(a?.managedRoom){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const o=r.filter(d=>d.id!==t.queueMessageId);if(o.length===r.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}o.length?i.sessions[t.sessionId]=o:delete i.sessions[t.sessionId],p(i),E(t.sessionId,a?.clientMessageId??a?.id??t.queueMessageId),M(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}purgeSession(e){X(e),this.draining.delete(e)}discardPendingManagedRoomMessages(e){const n=g(),t=n.sessions[e]??[],i=this.inFlightMessageIds.get(e)??new Set,r=t.filter(o=>o.managedRoom&&!i.has(o.id));if(r.length===0)return 0;const a=t.filter(o=>!r.includes(o));a.length?n.sessions[e]=a:delete n.sessions[e],p(n);for(const o of r)E(e,o.clientMessageId??o.id);return M(this.opts.getRuntime().client),this.logLifecycle("managed-room.discarded-paused",e,{discardedCount:r.length,pendingCount:a.length}),this.broadcast(e),r.length}drainIdleQueues(){const e=g();for(const n of Object.keys(e.sessions))this.drainNext(n)}async drainNext(e){if(this.draining.has(e))return;this.draining.add(e);let n=!1,t=[];try{if(await this.isSessionBusy(e)){this.logLifecycle("drain.deferred",e,{pendingCount:g().sessions[e]?.length??0});return}const i=g(),r=i.sessions[e]??[],a=r.filter(u=>{const f=u.clientMessageId??u.id,l=k(e,f).state;return l==="queued"||l==="not_found"});a.length!==r.length&&(a.length?i.sessions[e]=a:delete i.sessions[e],p(i),this.logLifecycle("drain.pruned-terminal",e,{prunedCount:r.length-a.length,pendingCount:a.length}));const o=Y(a);if(o.length===0){this.broadcast(e);return}t=o,this.inFlightMessageIds.set(e,new Set(o.map(u=>u.id))),this.logLifecycle("dispatch.start",e,{batchCount:o.length,queueMessageId:o[0].id,origin:o[0].origin??"app"});for(const u of o)P(e,x(u),"queued");await this.dispatchQueuedMessages(o);const d=this.removeBatch(e,o);this.logLifecycle("dispatch.accepted",e,{batchCount:o.length,queueMessageId:o[0].id,pendingCount:d}),n=d>0,this.broadcast(e)}catch(i){if(i instanceof V){const r=this.removeBatch(e,t);this.logLifecycle("dispatch.terminal-failure",e,{batchCount:t.length,queueMessageId:t[0]?.id,pendingCount:r,errorCode:O(i.message,"terminal_dispatch_failed")},"error"),n=r>0}else this.logLifecycle("dispatch.transient-failure",e,{errorCode:O(i instanceof Error?i.message:String(i),"transient_dispatch_failed")},"error");this.broadcast(e)}finally{this.inFlightMessageIds.delete(e),this.draining.delete(e),n&&queueMicrotask(()=>{this.drainNext(e)})}}removeBatch(e,n){if(n.length===0)return g().sessions[e]?.length??0;const t=g(),i=new Set(n.map(a=>a.id)),r=(t.sessions[e]??[]).filter(a=>!i.has(a.id));return r.length?t.sessions[e]=r:delete t.sessions[e],p(t),r.length}logLifecycle(e,n,t,i="info"){const r=`[chat.queue] ${JSON.stringify({event:e,sessionId:n,...t})}`;i==="error"?console.error(r):console.info(r)}async dispatchQueuedMessages(e){const n=e[0],t=e.flatMap(d=>d.managedRoom?[d.managedRoom]:[]),i=t.length>0?await this.prepareManagedRoomTurn?.(t):void 0;if(t.length>0&&!i)throw new Error("managed_room_turn_preparer_unavailable");const r=e.length===1?n.text:Z(e),a=te(e.flatMap(d=>d.attachments??[])),o=t.length>0?W(this.opts.getRuntime(),n):n.agentSessionId??null;await this.opts.dispatchQueuedReq({type:"req",id:`queue-send-${n.id}-${Date.now()}`,method:"chat.send",params:{...S(R()),sessionId:n.sessionId,text:r,agentType:n.agentType,sessionMode:t.length>0?"standard":n.sessionMode,managerConfig:t.length>0?null:n.managerConfig,workDir:n.workDir,agentSessionId:o,modelId:n.modelId??void 0,systemPrompt:i?.systemPrompt??n.systemPrompt??null,managerDefaultWorkerAgentType:t.length>0?null:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:t.length>0?null:n.managerDefaultWorkerModelId,reasoningEffort:n.reasoningEffort??void 0,clientMessageId:n.clientMessageId??n.id,attachments:a,externalChannel:n.externalChannel,replyTarget:n.replyTarget,...i??{},roomActivation:t.length>0||void 0,suppressExternalOwnerEcho:t.length>0||void 0,queuedBatchItems:e.map(B),waitForDispatch:!0}})}async isSessionBusy(e){const n=this.opts.getRuntime().sessions.get(e);if(!n)return!1;if(n.currentRunId)return!0;if(!n.adapter.getStatus)return!1;const t=await n.adapter.getStatus().catch(()=>null);return t?.active?(t.runId&&(n.currentRunId=t.runId,n.currentRunPhase=t.runPhase??n.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function W(s,e){if(s.sessions.has(e.sessionId))return null;const t=U().find(i=>i.sessionId===e.sessionId)?.agentSessionId?.trim();return!t||t.startsWith("managed:")?null:t}function X(s){const e=g();e.sessions[s]&&(delete e.sessions[s],p(e))}function N(s,e){const n=_(e);if(n)return s.find(i=>_(i)===n)??null;if(e.origin!=="external")return null;const t=F(e);return s.find(i=>F(i)===t)??null}function B(s){const{origin:e,managedRoom:n,...t}=s;return t}function Y(s){const e=s[0];if(!e)return[];const n=L(e),t=[];for(const i of s){if(L(i)!==n)break;t.push(i)}return t}function L(s){return JSON.stringify({agentType:s.agentType,sessionMode:s.sessionMode??null,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,reasoningEffort:s.reasoningEffort??null,managerConfig:s.managerConfig??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,roomRegistryKey:s.managedRoom?.registryKey??null})}function Z(s){return s.every(e=>e.managedRoom)?`[\u7FA4\u6D88\u606F]
1
+ import h from"node:fs";import{randomUUID as q}from"node:crypto";import{CODEX_DESKTOP_SEND_UNAVAILABLE as z,buildUserMessagePayload as j}from"@shennian/wire";import{isCodexThreadTailInterrupted as K}from"../native-fusion/codex-files.js";import{mergeProjectedSessions as J}from"./projection.js";import{parsePersonalAttachmentReferences as D}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as w,markAttachmentReferences as C}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as T}from"../personal-sync/paths.js";import{getPersonalSyncIdentity as S}from"../personal-sync/epoch.js";import{getDaemonInstallationId as R}from"../personal-sync/identity.js";import{appendMessage as v,getMessageDeliveryStatus as k,listSessionRecords as U,recordSession as b,tombstoneMessage as E,upsertMessage as P}from"./store.js";import{requestSessionIndexPublish as M}from"./index-publisher.js";class V extends Error{constructor(e){super(e),this.name="TerminalQueuedChatDispatchError"}}function G(){return{sessions:{}}}function $(){return new Date().toISOString()}function H(s){return`enqueue-send-${s}-${q()}`}function f(){try{const s=JSON.parse(h.readFileSync(A(),"utf-8"));return{sessions:s.sessions&&typeof s.sessions=="object"?s.sessions:{}}}catch(s){if(s.code==="ENOENT"||s instanceof Error&&s.message.startsWith("ENOENT:"))return G();throw s}}function p(s){const e=T();h.mkdirSync(e,{recursive:!0,mode:448});const n=A(),t=`${n}.tmp-${process.pid}-${q()}`;try{h.writeFileSync(t,`${JSON.stringify(s,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"});const i=h.openSync(t,"r");if(typeof i=="number")try{h.fsyncSync(i)}finally{h.closeSync(i)}h.renameSync(t,n)}finally{try{h.unlinkSync(t)}catch{}}}function A(){return T("outbound-queue.json")}function Q(s){const e=$();return{...S(R()),id:s.queueMessageId||s.clientMessageId||`queue-${q()}`,sessionId:s.sessionId,text:s.text,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,reasoningEffort:s.reasoningEffort??null,clientMessageId:s.clientMessageId??null,attachments:D(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}function x(s){return{id:s.clientMessageId??s.id,sessionId:s.sessionId,role:"user",ts:Date.parse(s.createdAt),payload:j(s.text,s.attachments)}}class he{opts;draining=new Set;inFlightMessageIds=new Map;prepareManagedRoomTurn=null;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{...S(R()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:(f().sessions[e]??[]).map(B)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const n=this.opts.getRuntime(),t=f(),i=new Set,o=new Map;let a=!1;for(const r of e){const d=r.params;if(!d.sessionId||!d.text||!d.agentType||!d.workDir)throw new Error("sessionId, text, agentType and workDir are required");const u=Q({...S(R()),...d});u.origin="room",u.managedRoom=r.context;const c=t.sessions[d.sessionId]??[],l=N(c,u),I=u.clientMessageId??u.id,y=k(d.sessionId,I);if(!l&&y.state==="not_found")c.push(u),t.sessions[d.sessionId]=c,o.set(`${u.sessionId}\0${I}`,u),i.add(d.sessionId),a=!0;else if(l&&y.state==="not_found")o.set(`${l.sessionId}\0${I}`,l),i.add(d.sessionId);else if(l&&y.state==="queued")i.add(d.sessionId);else if(l){const m=c.filter(g=>g.id!==l.id);m.length?t.sessions[d.sessionId]=m:delete t.sessions[d.sessionId],a=!0}}a&&p(t);for(const r of o.values())v(r.sessionId,x(r),"queued"),b({sessionId:r.sessionId,agentType:r.agentType,sessionMode:r.sessionMode,managerConfig:r.managerConfig,workDir:r.workDir,agentSessionId:W(n,r),modelId:r.modelId});if(i.size!==0){M(n.client);for(const r of i)this.logLifecycle("enqueue.persisted",r,{origin:"room",pendingCount:f().sessions[r]?.length??0}),this.broadcast(r),this.drainNext(r)}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if(J(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=n.resolvePath(t.workDir),o=D(t.attachments);o?.length&&w(i,o);const a={text:t.text,attachments:o,localized:!1},r=n.sessions.get(t.sessionId);if(t.agentType==="codex"&&t.agentSessionId&&r?.currentRunId?.startsWith("dispatch:")&&K(t.agentSessionId)){n.client.sendRes({type:"res",id:e.id,ok:!1,error:z});return}const d=await this.isSessionBusy(t.sessionId),u=f(),c=u.sessions[t.sessionId]??[];if(!d&&!c.length){await this.opts.dispatchReq({...e,id:H(e.id),method:"chat.send",params:{...t,text:a.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:a.attachments}});return}const l=Q({...t,text:a.text,attachments:a.attachments}),I=l.clientMessageId??l.id,y=k(t.sessionId,I);if(y.state!=="not_found"){n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:y.state==="queued",queueMessageId:l.id,...y,queue:this.getSnapshot(t.sessionId)}});return}const m=N(c,l);m||(u.sessions[t.sessionId]=[...c,l],p(u));const g=m??l;b({sessionId:g.sessionId,agentType:g.agentType,sessionMode:g.sessionMode,managerConfig:g.managerConfig,workDir:g.workDir,agentSessionId:g.agentSessionId,modelId:g.modelId}),v(g.sessionId,x(g),"queued"),C(n.resolvePath(g.workDir),g.attachments??[]),M(n.client),this.logLifecycle("enqueue.persisted",t.sessionId,{origin:t.origin??"app",queueMessageId:g.id,pendingCount:(f().sessions[t.sessionId]??[]).length}),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:m?.id??l.id,...k(t.sessionId,m?.clientMessageId??m?.id??l.clientMessageId??l.id),queue:this.getSnapshot(t.sessionId),...a.localized?{localizedAttachments:!0}:{}}}),d||this.drainNext(t.sessionId)}async handleGet(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(t.sessionId)}async handleEdit(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const i=f(),o=i.sessions[t.sessionId]??[],a=o.findIndex(u=>u.id===t.queueMessageId);if(a<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}if(o[a].managedRoom){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const r=D(t.attachments),d=n.resolvePath(o[a].workDir);r?.length&&w(d,r),o[a]={...o[a],text:t.text,attachments:r,updatedAt:$()},i.sessions[t.sessionId]=o,p(i),P(t.sessionId,x(o[a]),"queued"),o[a].attachments?.length&&C(d,o[a].attachments??[]),M(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const i=f(),o=i.sessions[t.sessionId]??[],a=o.find(d=>d.id===t.queueMessageId);if(a?.managedRoom){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const r=o.filter(d=>d.id!==t.queueMessageId);if(r.length===o.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}r.length?i.sessions[t.sessionId]=r:delete i.sessions[t.sessionId],p(i),E(t.sessionId,a?.clientMessageId??a?.id??t.queueMessageId),M(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}purgeSession(e){X(e),this.draining.delete(e)}discardPendingManagedRoomMessages(e){const n=f(),t=n.sessions[e]??[],i=this.inFlightMessageIds.get(e)??new Set,o=t.filter(r=>r.managedRoom&&!i.has(r.id));if(o.length===0)return 0;const a=t.filter(r=>!o.includes(r));a.length?n.sessions[e]=a:delete n.sessions[e],p(n);for(const r of o)E(e,r.clientMessageId??r.id);return M(this.opts.getRuntime().client),this.logLifecycle("managed-room.discarded-paused",e,{discardedCount:o.length,pendingCount:a.length}),this.broadcast(e),o.length}drainIdleQueues(){const e=f();for(const n of Object.keys(e.sessions))this.drainNext(n)}async drainNext(e){if(this.draining.has(e))return;this.draining.add(e);let n=!1,t=[];try{if(await this.isSessionBusy(e)){this.logLifecycle("drain.deferred",e,{pendingCount:f().sessions[e]?.length??0});return}const i=f(),o=i.sessions[e]??[],a=o.filter(u=>{const c=u.clientMessageId??u.id,l=k(e,c).state;return l==="queued"||l==="not_found"});a.length!==o.length&&(a.length?i.sessions[e]=a:delete i.sessions[e],p(i),this.logLifecycle("drain.pruned-terminal",e,{prunedCount:o.length-a.length,pendingCount:a.length}));const r=Y(a);if(r.length===0){this.broadcast(e);return}t=r,this.inFlightMessageIds.set(e,new Set(r.map(u=>u.id))),this.logLifecycle("dispatch.start",e,{batchCount:r.length,queueMessageId:r[0].id,origin:r[0].origin??"app"});for(const u of r)P(e,x(u),"queued");await this.dispatchQueuedMessages(r);const d=this.removeBatch(e,r);this.logLifecycle("dispatch.accepted",e,{batchCount:r.length,queueMessageId:r[0].id,pendingCount:d}),n=d>0,this.broadcast(e)}catch(i){if(i instanceof V){const o=this.removeBatch(e,t);this.logLifecycle("dispatch.terminal-failure",e,{batchCount:t.length,queueMessageId:t[0]?.id,pendingCount:o,errorCode:O(i.message,"terminal_dispatch_failed")},"error"),n=o>0}else this.logLifecycle("dispatch.transient-failure",e,{errorCode:O(i instanceof Error?i.message:String(i),"transient_dispatch_failed")},"error");this.broadcast(e)}finally{this.inFlightMessageIds.delete(e),this.draining.delete(e),n&&queueMicrotask(()=>{this.drainNext(e)})}}removeBatch(e,n){if(n.length===0)return f().sessions[e]?.length??0;const t=f(),i=new Set(n.map(a=>a.id)),o=(t.sessions[e]??[]).filter(a=>!i.has(a.id));return o.length?t.sessions[e]=o:delete t.sessions[e],p(t),o.length}logLifecycle(e,n,t,i="info"){const o=`[chat.queue] ${JSON.stringify({event:e,sessionId:n,...t})}`;i==="error"?console.error(o):console.info(o)}async dispatchQueuedMessages(e){const n=e[0],t=e.flatMap(c=>c.managedRoom?[c.managedRoom]:[]),i=t.length>0?await this.prepareManagedRoomTurn?.(t):void 0;if(t.length>0&&!i)throw new Error("managed_room_turn_preparer_unavailable");const o=t.length>0?i:null,a=o?.sessionMode==="manager"?o.managerConfig:null,r=e.length===1?n.text:Z(e),d=te(e.flatMap(c=>c.attachments??[])),u=t.length>0?W(this.opts.getRuntime(),n):n.agentSessionId??null;await this.opts.dispatchQueuedReq({type:"req",id:`queue-send-${n.id}-${Date.now()}`,method:"chat.send",params:{...S(R()),sessionId:n.sessionId,text:r,agentType:n.agentType,sessionMode:o?.sessionMode??n.sessionMode,managerConfig:o?a:n.managerConfig,workDir:n.workDir,agentSessionId:u,modelId:n.modelId??void 0,systemPrompt:i?.systemPrompt??n.systemPrompt??null,managerDefaultWorkerAgentType:o?o.managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:o?o.managerDefaultWorkerModelId:n.managerDefaultWorkerModelId,reasoningEffort:n.reasoningEffort??void 0,clientMessageId:n.clientMessageId??n.id,attachments:d,externalChannel:n.externalChannel,replyTarget:n.replyTarget,...i??{},roomActivation:t.length>0||void 0,suppressExternalOwnerEcho:t.length>0||void 0,queuedBatchItems:e.map(B),waitForDispatch:!0}})}async isSessionBusy(e){const n=this.opts.getRuntime().sessions.get(e);if(!n)return!1;if(n.currentRunId)return!0;if(!n.adapter.getStatus)return!1;const t=await n.adapter.getStatus().catch(()=>null);return t?.active?(t.runId&&(n.currentRunId=t.runId,n.currentRunPhase=t.runPhase??n.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function W(s,e){if(s.sessions.has(e.sessionId))return null;const t=U().find(i=>i.sessionId===e.sessionId)?.agentSessionId?.trim();return!t||t.startsWith("managed:")?null:t}function X(s){const e=f();e.sessions[s]&&(delete e.sessions[s],p(e))}function N(s,e){const n=_(e);if(n)return s.find(i=>_(i)===n)??null;if(e.origin!=="external")return null;const t=F(e);return s.find(i=>F(i)===t)??null}function B(s){const{origin:e,managedRoom:n,...t}=s;return t}function Y(s){const e=s[0];if(!e)return[];const n=L(e),t=[];for(const i of s){if(L(i)!==n)break;t.push(i)}return t}function L(s){return JSON.stringify({agentType:s.agentType,sessionMode:s.sessionMode??null,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,reasoningEffort:s.reasoningEffort??null,managerConfig:s.managerConfig??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,roomRegistryKey:s.managedRoom?.registryKey??null})}function Z(s){return s.every(e=>e.managedRoom)?`[\u7FA4\u6D88\u606F]
3
3
  ${s.map(e=>ee(e.text)).filter(Boolean).join(`
4
4
 
5
5
  `)}`:`[\u8FDE\u7EED\u7528\u6237\u6D88\u606F]
@@ -54,6 +54,7 @@ export declare function fetchLatestVersion(): Promise<string>;
54
54
  * 'major' — major differs
55
55
  */
56
56
  export declare function compareVersions(current: string, latest: string): 'none' | 'patch' | 'minor' | 'major';
57
+ export declare function resolveNpmGlobalPrefixFromPackageJson(packageJsonPath: string, platform?: NodeJS.Platform): string | null;
57
58
  export declare function readUpgradeAttempt(): UpgradeAttempt | null;
58
59
  export declare function writeUpgradeAttempt(attempt: UpgradeAttempt): void;
59
60
  export declare function clearUpgradeAttempt(): void;
@@ -1,3 +1,3 @@
1
- import i from"node:fs";import p from"node:path";import{execSync as F,execFile as T}from"node:child_process";import{promisify as D}from"node:util";import{fileURLToPath as L}from"node:url";import{getShennianDir as $,resolveShennianPath as d}from"../config/index.js";const f=D(T),m=d("upgrade-attempt.json"),J=3,I="https://registry.npmjs.org",H=10*6e4,U=[5*6e4,30*6e4,120*6e4,360*6e4];function y(){try{const r=P();if(r)return JSON.parse(i.readFileSync(r,"utf-8")).version}catch{}return"0.0.0"}function P(r=L(import.meta.url)){let e=p.dirname(r);for(let n=0;n<6;n++){const t=p.join(e,"package.json");if(i.existsSync(t)&&JSON.parse(i.readFileSync(t,"utf-8")).name==="shennian")return t;e=p.dirname(e)}return null}async function B(){const r=await fetch("https://registry.npmjs.org/shennian/latest",{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)});if(!r.ok)throw new Error(`npm registry returned ${r.status}`);return(await r.json()).version}function G(r,e){const n=u=>u.replace(/^v/,"").split(".").map(Number),[t,s,o]=n(r),[a,l,c]=n(e);return a>t?"major":a===t&&l>s?"minor":a===t&&l===s&&c>o?"patch":"none"}function Y(){return F("npm root -g",{encoding:"utf-8",stdio:"pipe",windowsHide:!0}).trim()}function z(){const r=Y();return p.join(r,"shennian","dist","bin","shennian.js")}function K(){try{return JSON.parse(i.readFileSync(m,"utf-8"))}catch{return null}}function g(r){i.mkdirSync($(),{recursive:!0}),i.writeFileSync(m,JSON.stringify(r,null,2))}function h(){try{i.unlinkSync(m)}catch{}}function _(){return d("upgrade-failures.json")}function X(){return d("upgrade.lock")}function N(r=X(),e={}){const n=e.now??Date.now(),t=e.pid??process.pid,s=e.isProcessAlive??(c=>{try{return process.kill(c,0),!0}catch{return!1}});i.mkdirSync(p.dirname(r),{recursive:!0});const o=()=>i.openSync(r,"wx",384);let a;try{a=o()}catch(c){if(c.code!=="EEXIST")throw c;let u=!1;try{const v=JSON.parse(i.readFileSync(r,"utf-8")),E=Number(v.pid),b=Number(v.createdAt);u=!Number.isInteger(E)||!Number.isFinite(b)||n-b>H||!s(E)}catch{u=!0}if(!u)throw new Error("Another Shennian upgrade is already running");i.rmSync(r,{force:!0}),a=o()}i.writeFileSync(a,JSON.stringify({pid:t,createdAt:n})),i.closeSync(a);let l=!1;return{release(){if(!l){l=!0;try{i.unlinkSync(r)}catch{}}}}}function k(r=_()){try{const e=JSON.parse(i.readFileSync(r,"utf-8"));return!e||typeof e!="object"||!e.versions?{versions:{}}:{versions:e.versions}}catch{return{versions:{}}}}function R(r){i.mkdirSync($(),{recursive:!0}),i.writeFileSync(_(),JSON.stringify(r,null,2))}function Z(r){return U[Math.min(Math.max(r-1,0),U.length-1)]}function or(r,e=Date.now(),n=k()){const t=n.versions[r];return!!(t&&t.nextRetryAt>e)}function ar(r,e,n=Date.now()){const t=k(),s=t.versions[r],o=(s?.failureCount??0)+1,a={version:r,failureCount:o,firstFailedAt:s?.firstFailedAt??n,lastFailedAt:n,nextRetryAt:n+Z(o),error:e};return t.versions[r]=a,R(t),a}function w(r){const e=k();e.versions[r]&&(delete e.versions[r],R(e))}async function ir(){const r=K();if(!r)return!1;if(Date.now()-r.attemptAt>600*1e3)return h(),!1;if(r.attemptCount++,r.attemptCount>=J){console.error(`[upgrade] New version crashed ${r.attemptCount} times, rolling back to ${r.from}...`);let n;try{n=N()}catch(t){return console.error(`[upgrade] Rollback deferred: ${t instanceof Error?t.message:String(t)}`),!1}try{return await M(r.from),h(),console.error(`[upgrade] Rolled back to ${r.from}. Restarting...`),!0}catch(t){return console.error(`[upgrade] Rollback failed: ${t instanceof Error?t.message:String(t)}`),h(),!1}finally{n.release()}}return g(r),!1}async function sr(r,e,n={}){let t;try{t=N()}catch(s){return{ok:!1,error:s instanceof Error?s.message:String(s)}}try{return await q(r,e,n)}finally{t.release()}}async function q(r,e,n={}){const t=n.currentVersion??y(),s=y();if(t===r)return w(r),{ok:!1,error:`Already on version ${r}`};e({step:"checking"});try{F("npm --version",{stdio:"pipe",windowsHide:!0})}catch{return{ok:!1,error:"npm is not available in PATH"}}if(s===r){e({step:"verifying",version:r});try{await A(r)}catch(o){return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}`}}return g({from:t,to:r,attemptCount:0,attemptAt:Date.now()}),w(r),e({step:"restarting",from:t,to:r}),{ok:!0,from:t,to:r}}e({step:"backing-up"}),e({step:"installing",version:r});try{await j(r)}catch(o){const a=await O(t);return{ok:!1,error:`npm install failed: ${o instanceof Error?o.message:String(o)}${a.ok?"":`; rollback failed: ${a.error}`}`,rolledBack:a.ok}}e({step:"verifying",version:r});try{await A(r)}catch(o){const a=await O(t);return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}${a.ok?"":`; rollback failed: ${a.error}`}`,rolledBack:a.ok}}return g({from:t,to:r,attemptCount:0,attemptAt:Date.now()}),w(r),e({step:"restarting",from:t,to:r}),{ok:!0,from:t,to:r}}function C(r){if(!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(r))throw new Error(`Invalid npm version: ${r}`)}function S(){return process.platform==="win32"?"npm.cmd":"npm"}function Q(r,e){return{timeout:e,windowsHide:!0,shell:r==="win32"}}function x(r){return Q(process.platform,r)}async function j(r){C(r);try{await f(S(),["install","-g",`shennian@${r}`],x(12e4))}catch(e){const n=e instanceof Error?e.message:String(e);try{await f(S(),["install","-g",`shennian@${r}`,"--registry",I],{...x(12e4)})}catch(t){const s=t instanceof Error?t.message:String(t);throw new Error(`${n}
1
+ import s from"node:fs";import c from"node:path";import{execSync as U,execFile as T}from"node:child_process";import{promisify as P}from"node:util";import{fileURLToPath as L}from"node:url";import{getShennianDir as _,resolveShennianPath as d}from"../config/index.js";const f=P(T),m=d("upgrade-attempt.json"),G=3,I="https://registry.npmjs.org",H=10*6e4,$=[5*6e4,30*6e4,120*6e4,360*6e4];function y(){try{const e=g();if(e)return JSON.parse(s.readFileSync(e,"utf-8")).version}catch{}return"0.0.0"}function g(e=L(import.meta.url)){let r=c.dirname(e);for(let n=0;n<6;n++){const t=c.join(r,"package.json");if(s.existsSync(t)&&JSON.parse(s.readFileSync(t,"utf-8")).name==="shennian")return t;r=c.dirname(r)}return null}async function B(){const e=await fetch("https://registry.npmjs.org/shennian/latest",{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)});if(!e.ok)throw new Error(`npm registry returned ${e.status}`);return(await e.json()).version}function Y(e,r){const n=p=>p.replace(/^v/,"").split(".").map(Number),[t,i,o]=n(e),[a,u,l]=n(r);return a>t?"major":a===t&&u>i?"minor":a===t&&u===i&&l>o?"patch":"none"}function z(){return U("npm root -g",{encoding:"utf-8",stdio:"pipe",windowsHide:!0}).trim()}function K(e,r=process.platform){const n=r==="win32"?c.win32:c.posix,t=n.dirname(e),i=n.dirname(t);if(n.basename(i)!=="node_modules")return null;const o=n.dirname(i);return n.basename(o)==="lib"?n.dirname(o):o}function N(){const e=g();return e?K(e):null}function X(){const e=g();return e?c.join(c.dirname(e),"dist","bin","shennian.js"):c.join(z(),"shennian","dist","bin","shennian.js")}function Z(){try{return JSON.parse(s.readFileSync(m,"utf-8"))}catch{return null}}function h(e){s.mkdirSync(_(),{recursive:!0}),s.writeFileSync(m,JSON.stringify(e,null,2))}function k(){try{s.unlinkSync(m)}catch{}}function R(){return d("upgrade-failures.json")}function q(){return d("upgrade.lock")}function j(e=q(),r={}){const n=r.now??Date.now(),t=r.pid??process.pid,i=r.isProcessAlive??(l=>{try{return process.kill(l,0),!0}catch{return!1}});s.mkdirSync(c.dirname(e),{recursive:!0});const o=()=>s.openSync(e,"wx",384);let a;try{a=o()}catch(l){if(l.code!=="EEXIST")throw l;let p=!1;try{const v=JSON.parse(s.readFileSync(e,"utf-8")),E=Number(v.pid),F=Number(v.createdAt);p=!Number.isInteger(E)||!Number.isFinite(F)||n-F>H||!i(E)}catch{p=!0}if(!p)throw new Error("Another Shennian upgrade is already running");s.rmSync(e,{force:!0}),a=o()}s.writeFileSync(a,JSON.stringify({pid:t,createdAt:n})),s.closeSync(a);let u=!1;return{release(){if(!u){u=!0;try{s.unlinkSync(e)}catch{}}}}}function w(e=R()){try{const r=JSON.parse(s.readFileSync(e,"utf-8"));return!r||typeof r!="object"||!r.versions?{versions:{}}:{versions:r.versions}}catch{return{versions:{}}}}function C(e){s.mkdirSync(_(),{recursive:!0}),s.writeFileSync(R(),JSON.stringify(e,null,2))}function Q(e){return $[Math.min(Math.max(e-1,0),$.length-1)]}function ie(e,r=Date.now(),n=w()){const t=n.versions[e];return!!(t&&t.nextRetryAt>r)}function se(e,r,n=Date.now()){const t=w(),i=t.versions[e],o=(i?.failureCount??0)+1,a={version:e,failureCount:o,firstFailedAt:i?.firstFailedAt??n,lastFailedAt:n,nextRetryAt:n+Q(o),error:r};return t.versions[e]=a,C(t),a}function S(e){const r=w();r.versions[e]&&(delete r.versions[e],C(r))}async function ce(){const e=Z();if(!e)return!1;if(Date.now()-e.attemptAt>600*1e3)return k(),!1;if(e.attemptCount++,e.attemptCount>=G){console.error(`[upgrade] New version crashed ${e.attemptCount} times, rolling back to ${e.from}...`);let n;try{n=j()}catch(t){return console.error(`[upgrade] Rollback deferred: ${t instanceof Error?t.message:String(t)}`),!1}try{return await J(e.from),k(),console.error(`[upgrade] Rolled back to ${e.from}. Restarting...`),!0}catch(t){return console.error(`[upgrade] Rollback failed: ${t instanceof Error?t.message:String(t)}`),k(),!1}finally{n.release()}}return h(e),!1}async function le(e,r,n={}){let t;try{t=j()}catch(i){return{ok:!1,error:i instanceof Error?i.message:String(i)}}try{return await W(e,r,n)}finally{t.release()}}async function W(e,r,n={}){const t=n.currentVersion??y(),i=y();if(t===e)return S(e),{ok:!1,error:`Already on version ${e}`};r({step:"checking"});try{U("npm --version",{stdio:"pipe",windowsHide:!0})}catch{return{ok:!1,error:"npm is not available in PATH"}}if(i===e){r({step:"verifying",version:e});try{await b(e)}catch(o){return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}`}}return h({from:t,to:e,attemptCount:0,attemptAt:Date.now()}),S(e),r({step:"restarting",from:t,to:e}),{ok:!0,from:t,to:e}}r({step:"backing-up"}),r({step:"installing",version:e});try{await D(e)}catch(o){const a=await O(t);return{ok:!1,error:`npm install failed: ${o instanceof Error?o.message:String(o)}${a.ok?"":`; rollback failed: ${a.error}`}`,rolledBack:a.ok}}r({step:"verifying",version:e});try{await b(e)}catch(o){const a=await O(t);return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}${a.ok?"":`; rollback failed: ${a.error}`}`,rolledBack:a.ok}}return h({from:t,to:e,attemptCount:0,attemptAt:Date.now()}),S(e),r({step:"restarting",from:t,to:e}),{ok:!0,from:t,to:e}}function M(e){if(!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(e))throw new Error(`Invalid npm version: ${e}`)}function x(){return process.platform==="win32"?"npm.cmd":"npm"}function V(e,r){return{timeout:r,windowsHide:!0,shell:e==="win32"}}function A(e){return V(process.platform,e)}async function D(e){M(e);const r=N(),n=["install","-g",`shennian@${e}`,...r?["--prefix",r]:[]];try{await f(x(),n,A(12e4))}catch(t){const i=t instanceof Error?t.message:String(t);try{await f(x(),[...n,"--registry",I],{...A(12e4)})}catch(o){const a=o instanceof Error?o.message:String(o);throw new Error(`${i}
2
2
  --- npmjs fallback ---
3
- ${s}`)}}}async function A(r){C(r);const e=z(),{stdout:n}=await f(process.execPath,[e,"--version"],{timeout:1e4,windowsHide:!0});if(n.trim()!==r)throw new Error(`Version verification failed: expected ${r}, got ${n.trim()||"empty output"}`);await f(S(),["ls","-g",`shennian@${r}`,"--omit=dev","--depth=0"],{...x(3e4)})}async function M(r,e={}){await(e.install??j)(r),await(e.verify??A)(r)}async function O(r){try{return await M(r),{ok:!0}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function cr(r){const e=r??y(),n=await B(),t=G(e,n);return t==="none"?{hasUpdate:!1}:{hasUpdate:!0,current:e,latest:n,changeType:t}}export{N as acquireUpgradeLock,cr as checkForUpdate,h as clearUpgradeAttempt,w as clearUpgradeFailure,G as compareVersions,B as fetchLatestVersion,P as findPackageJson,y as getCurrentVersion,Z as getUpgradeRetryDelayMs,ir as handleStartupCrashCheck,j as installShennianVersion,or as isUpgradeVersionInCooldown,Q as npmExecOptionsForPlatform,sr as performUpgrade,K as readUpgradeAttempt,k as readUpgradeFailures,ar as recordUpgradeFailure,M as rollbackToExactVersion,A as verifyInstalledVersion,g as writeUpgradeAttempt};
3
+ ${a}`)}}}async function b(e){M(e);const r=X(),{stdout:n}=await f(process.execPath,[r,"--version"],{timeout:1e4,windowsHide:!0});if(n.trim()!==e)throw new Error(`Version verification failed: expected ${e}, got ${n.trim()||"empty output"}`);const t=N();await f(x(),["ls","-g",`shennian@${e}`,"--omit=dev","--depth=0",...t?["--prefix",t]:[]],{...A(3e4)})}async function J(e,r={}){await(r.install??D)(e),await(r.verify??b)(e)}async function O(e){try{return await J(e),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}async function ue(e){const r=e??y(),n=await B(),t=Y(r,n);return t==="none"?{hasUpdate:!1}:{hasUpdate:!0,current:r,latest:n,changeType:t}}export{j as acquireUpgradeLock,ue as checkForUpdate,k as clearUpgradeAttempt,S as clearUpgradeFailure,Y as compareVersions,B as fetchLatestVersion,g as findPackageJson,y as getCurrentVersion,Q as getUpgradeRetryDelayMs,ce as handleStartupCrashCheck,D as installShennianVersion,ie as isUpgradeVersionInCooldown,V as npmExecOptionsForPlatform,le as performUpgrade,Z as readUpgradeAttempt,w as readUpgradeFailures,se as recordUpgradeFailure,K as resolveNpmGlobalPrefixFromPackageJson,J as rollbackToExactVersion,b as verifyInstalledVersion,h as writeUpgradeAttempt};
@@ -1 +1 @@
1
- export type ReqMethod = 'chat.send' | 'chat.enqueue' | 'chat.queue.get' | 'chat.queue.edit' | 'chat.queue.delete' | 'chat.abort' | 'session.resume' | 'session.history' | 'session.history.open' | 'session.history.sync' | 'session.history.page' | 'session.history.close' | 'session.body.replica.subscribe' | 'session.body.replica.unsubscribe' | 'session.index.update' | 'session.deletions.sync' | 'session.deletions.ack' | 'session.message.status' | 'voice.session.body.window' | 'session.refresh' | 'session.tool.detail' | 'session.title.set' | 'session.title.generate' | 'fs.ls' | 'fs.read' | 'fs.write' | 'fs.export.markdown-pdf' | 'fs.export.markdown-pdf.setup' | 'fs.archive.zip' | 'fs.transfer.start' | 'fs.transfer.chunk' | 'fs.transfer.finish' | 'fs.transfer.abort' | 'fs.rename' | 'skill.list' | 'skill.install' | 'skill.doctor' | 'skill.setup' | 'skill.use' | 'region.probe' | 'region.switch' | 'upgrade.start' | 'upgrade.status' | 'upgrade.set-policy' | 'machine.remote-access.pause' | 'agents.refresh' | 'models.refresh' | 'agent.config.get' | 'agent.config.upsert' | 'agent.config.clear' | 'agent.config.test' | 'agent.capabilities.list' | 'manager.channel.get' | 'manager.channel.upsert' | 'session.wechat-rpa.channel.get' | 'session.wechat-rpa.channel.upsert' | 'session.wechat-rpa.channel.sync' | 'session.wechat-rpa.outbound.cancel' | 'manager.wechat-rpa.channel.get' | 'manager.wechat-rpa.channel.upsert' | 'manager.wechat-rpa.channel.sync' | 'manager.wechat-rpa.outbound.cancel' | 'wechat-rpa.helper.runtime' | 'wecom.send' | 'external.send' | 'room.sync' | 'room.send' | 'room.bind' | 'room.agent.bind-managed' | 'room.agent.restart-managed' | 'room.unbind';
1
+ export type ReqMethod = 'chat.send' | 'chat.enqueue' | 'chat.queue.get' | 'chat.queue.edit' | 'chat.queue.delete' | 'chat.abort' | 'session.resume' | 'session.history' | 'session.history.open' | 'session.history.sync' | 'session.history.page' | 'session.history.close' | 'session.body.replica.subscribe' | 'session.body.replica.unsubscribe' | 'session.index.update' | 'session.deletions.sync' | 'session.deletions.ack' | 'session.message.status' | 'voice.session.body.window' | 'session.refresh' | 'session.tool.detail' | 'session.title.set' | 'session.title.generate' | 'fs.ls' | 'fs.read' | 'fs.write' | 'fs.export.markdown-pdf' | 'fs.export.markdown-pdf.setup' | 'fs.archive.zip' | 'fs.transfer.start' | 'fs.transfer.chunk' | 'fs.transfer.finish' | 'fs.transfer.abort' | 'fs.rename' | 'skill.list' | 'skill.install' | 'skill.doctor' | 'skill.setup' | 'skill.use' | 'region.probe' | 'region.switch' | 'upgrade.start' | 'upgrade.status' | 'upgrade.set-policy' | 'machine.remote-access.pause' | 'agents.refresh' | 'models.refresh' | 'agent.config.get' | 'agent.config.upsert' | 'agent.config.clear' | 'agent.config.test' | 'agent.workspace.validate' | 'agent.capabilities.list' | 'manager.channel.get' | 'manager.channel.upsert' | 'session.wechat-rpa.channel.get' | 'session.wechat-rpa.channel.upsert' | 'session.wechat-rpa.channel.sync' | 'session.wechat-rpa.outbound.cancel' | 'manager.wechat-rpa.channel.get' | 'manager.wechat-rpa.channel.upsert' | 'manager.wechat-rpa.channel.sync' | 'manager.wechat-rpa.outbound.cancel' | 'wechat-rpa.helper.runtime' | 'wecom.send' | 'external.send' | 'room.sync' | 'room.send' | 'room.bind' | 'room.agent.bind-managed' | 'room.agent.restart-managed' | 'room.unbind';