shennian 0.3.73 → 0.3.74

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 (89) hide show
  1. package/dist/publish-build-manifest.json +122 -57
  2. package/dist/src/agent-env.d.ts +30 -0
  3. package/dist/src/agent-env.js +4 -4
  4. package/dist/src/agents/adapter.d.ts +5 -0
  5. package/dist/src/agents/claude-managed-permissions.d.ts +33 -0
  6. package/dist/src/agents/claude-managed-permissions.js +1 -0
  7. package/dist/src/agents/claude.d.ts +11 -2
  8. package/dist/src/agents/claude.js +5 -5
  9. package/dist/src/agents/codex-managed-permissions.d.ts +32 -0
  10. package/dist/src/agents/codex-managed-permissions.js +1 -0
  11. package/dist/src/agents/codex-managed-room-tools.d.ts +40 -0
  12. package/dist/src/agents/codex-managed-room-tools.js +1 -0
  13. package/dist/src/agents/codex.d.ts +20 -2
  14. package/dist/src/agents/codex.js +11 -10
  15. package/dist/src/agents/command-spec.d.ts +8 -0
  16. package/dist/src/agents/command-spec.js +3 -2
  17. package/dist/src/agents/cursor-managed-permissions.d.ts +28 -0
  18. package/dist/src/agents/cursor-managed-permissions.js +2 -0
  19. package/dist/src/agents/cursor.d.ts +8 -2
  20. package/dist/src/agents/cursor.js +1 -1
  21. package/dist/src/agents/custom-managed-runtime.d.ts +37 -0
  22. package/dist/src/agents/custom-managed-runtime.js +2 -0
  23. package/dist/src/agents/custom.d.ts +12 -2
  24. package/dist/src/agents/custom.js +4 -4
  25. package/dist/src/agents/detect.d.ts +3 -0
  26. package/dist/src/agents/detect.js +1 -1
  27. package/dist/src/agents/model-registry/service.js +1 -1
  28. package/dist/src/agents/opencode-managed-permissions.d.ts +25 -0
  29. package/dist/src/agents/opencode-managed-permissions.js +2 -0
  30. package/dist/src/agents/opencode.d.ts +8 -2
  31. package/dist/src/agents/opencode.js +1 -1
  32. package/dist/src/agents/workbuddy-managed-permissions.d.ts +37 -0
  33. package/dist/src/agents/workbuddy-managed-permissions.js +2 -0
  34. package/dist/src/agents/workbuddy.d.ts +8 -0
  35. package/dist/src/agents/workbuddy.js +2 -2
  36. package/dist/src/commands/daemon.d.ts +29 -0
  37. package/dist/src/commands/daemon.js +14 -14
  38. package/dist/src/commands/room.js +3 -3
  39. package/dist/src/commands/runtime.js +5 -5
  40. package/dist/src/config/index.d.ts +22 -0
  41. package/dist/src/daemon-ipc/paths.d.ts +8 -0
  42. package/dist/src/daemon-ipc/paths.js +1 -1
  43. package/dist/src/index.js +3 -3
  44. package/dist/src/log-redaction.d.ts +4 -0
  45. package/dist/src/log-redaction.js +1 -0
  46. package/dist/src/relay/client.d.ts +1 -0
  47. package/dist/src/relay/client.js +1 -1
  48. package/dist/src/room/activation-service.d.ts +23 -1
  49. package/dist/src/room/activation-service.js +7 -7
  50. package/dist/src/room/attachment-cache.d.ts +3 -0
  51. package/dist/src/room/attachment-cache.js +2 -2
  52. package/dist/src/room/attachment-upload.js +1 -1
  53. package/dist/src/room/managed-prompt-composer.d.ts +16 -3
  54. package/dist/src/room/managed-prompt-composer.js +26 -13
  55. package/dist/src/room/managed-room-binding.d.ts +3 -3
  56. package/dist/src/room/managed-room-binding.js +1 -1
  57. package/dist/src/room/managed-room-ipc.d.ts +32 -0
  58. package/dist/src/room/managed-room-ipc.js +3 -1
  59. package/dist/src/room/room-action-client.d.ts +5 -1
  60. package/dist/src/room/room-action-client.js +1 -1
  61. package/dist/src/security/managed-agent-credential-paths.d.ts +6 -0
  62. package/dist/src/security/managed-agent-credential-paths.js +1 -0
  63. package/dist/src/security/managed-agent-executable.d.ts +19 -0
  64. package/dist/src/security/managed-agent-executable.js +1 -0
  65. package/dist/src/security/managed-agent-policy.d.ts +55 -0
  66. package/dist/src/security/managed-agent-policy.js +1 -0
  67. package/dist/src/security/managed-agent-runtime.d.ts +44 -0
  68. package/dist/src/security/managed-agent-runtime.js +1 -0
  69. package/dist/src/session/handlers/chat.d.ts +8 -1
  70. package/dist/src/session/handlers/chat.js +2 -2
  71. package/dist/src/session/handlers/remote-access.d.ts +4 -0
  72. package/dist/src/session/handlers/remote-access.js +1 -0
  73. package/dist/src/session/handlers/room-managed.d.ts +1 -1
  74. package/dist/src/session/handlers/room-managed.js +1 -1
  75. package/dist/src/session/history-subscriptions.d.ts +6 -0
  76. package/dist/src/session/history-subscriptions.js +1 -1
  77. package/dist/src/session/manager.d.ts +7 -2
  78. package/dist/src/session/manager.js +1 -1
  79. package/dist/src/session/queue.d.ts +4 -0
  80. package/dist/src/session/queue.js +4 -4
  81. package/dist/src/session/types.d.ts +14 -0
  82. package/node_modules/@shennian/wire/dist/agent-workspace.d.ts +70 -0
  83. package/node_modules/@shennian/wire/dist/agent-workspace.js +111 -1
  84. package/node_modules/@shennian/wire/dist/machine.d.ts +2 -0
  85. package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
  86. package/node_modules/@shennian/wire/dist/session-sync.d.ts +53 -3
  87. package/node_modules/@shennian/wire/dist/session-sync.js +5 -1
  88. package/node_modules/@shennian/wire/dist/session.d.ts +2 -0
  89. package/package.json +3 -1
@@ -1,3 +1,3 @@
1
- import y from"node:crypto";import I from"node:fs";import x from"node:path";import{PublicAttachmentCache as j}from"../room/public-attachment-cache.js";import{PublicRoomClient as z,PublicRoomClientError as A}from"../room/public-room-client.js";import{callDaemonIpc as k}from"../daemon-ipc/client.js";import{DaemonIpcError as D}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_CONTEXT_ENV as M}from"../room/managed-context.js";import{createRoomInviteQr as $}from"../room/invite-qr.js";const T="https://shennian.net/install.md",O={write:e=>{process.stdout.write(e)},fail:()=>{process.exitCode=1}};function X(e,t={}){const n=t.client??new z,s=t.cache??new j({client:n}),r=t.io??O,p=t.createInviteQr??$,d=e.command("room").description("Use one explicitly referenced Shennian Room");d.command("create").description("Create a Room as the paired user").requiredOption("--name <name>","Room name").action(async m=>f("room.create",r,async()=>{w("create");const a=m.name.trim();(!a||a.length>80)&&o("invalid_room_name","Room name must contain 1 to 80 characters.");const i=await n.create(a),c=h(i.room,"invalid_room_response");typeof c.inviteUrl!="string"&&o("invalid_room_response","Shennian returned an invalid Room invitation.");const u=await p(c.inviteUrl).catch(()=>({imagePath:null,mimeType:"image/png",payload:c.inviteUrl}));return{...i,sharing:{inviteUrl:c.inviteUrl,inviteQr:u,installGuideUrl:T}}})),d.command("join <invite-url>").description("Join or request access through an official Room invitation").option("--message <message>","Introduction and reason required when owner approval is enabled").action(async(m,a)=>f("room.join",r,async()=>{w("join");const i=U(m,"invite"),c=a.message?.trim();return c&&c.length>500&&o("join_message_too_long","Join request message must be at most 500 characters."),n.join(i.roomId,i.inviteToken,c)})),d.command("open <room-url>").description("Open one Room where the paired user is already a member").action(async m=>f("room.open",r,async()=>{w("open");const a=U(m,"stable");return n.open(a.roomId)})),d.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async m=>f("room.status",r,()=>{const a=g();return a?(m&&o("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),b(a,"status",{})):n.status(R(m))})),d.command("read [room-ref]").description("Read recent, older, or newer messages from one Room").option("--before <sequence>","Read messages before this sequence").option("--after <sequence>","Read messages after this sequence").option("--limit <count>","Maximum messages, 1 to 50","50").action(async(m,a)=>{await f("room.read",r,async()=>{a.before&&a.after&&o("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),a.before&&!q(a.before)&&o("invalid_sequence","--before sequence is invalid."),a.after&&!q(a.after)&&o("invalid_sequence","--after sequence is invalid.");const i=Number(a.limit);(!Number.isInteger(i)||i<1||i>50)&&o("invalid_limit","--limit must be between 1 and 50.");const c=g();if(c)return m&&o("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),b(c,"read",{...a.before?{before:a.before}:{},...a.after?{after:a.after}:{},limit:i});const u=R(m),_=await n.read(u,{before:a.before,after:a.after,limit:i});return P(_,u,s)})}),d.command("send [room-ref]").description("Send text and up to 10 files as the paired user").option("--text <text>","Message text").option("--file <path>","Local file path; repeat for multiple files",C,[]).action(async(m,a)=>{await f("room.send",r,async()=>{const i=a.text?.trim()??"";!i&&a.file.length===0&&o("room_message_empty","Provide --text or at least one --file."),a.file.length>10&&o("room_attachments_too_many","A message can contain at most 10 files.");const c=g();if(c)return m&&o("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),b(c,"send",{clientMessageId:`managed:${y.randomUUID()}`,text:i,files:a.file});const u=R(m),_=[];for(const L of a.file)_.push(await N(n,u,L));return n.send(u,{clientMessageId:`cli:${y.randomUUID()}`,...i?{text:i}:{},..._.length>0?{attachmentIds:_}:{}})})}),d.command("attachment").description("Download Room attachments through Shennian").command("get [room-ref] [attachment-id]").description("Download and verify one Room attachment").action(async(m,a)=>{await f("room.attachment.get",r,async()=>{const i=g(),c=i?m:a;return(!c||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(c))&&o("invalid_attachment_ref","attachment-id is invalid."),i?b(i,"attachment.get",{attachmentId:c}):s.materialize(R(m),c)})}),d.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async m=>f("room.leave",r,()=>(w("leave"),n.leave(S(m))))),d.command("list",{hidden:!0}).action(()=>v(r,"command_removed","Room listing is not available to Agents.",null)),d.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>v(r,"command_removed","Room search is not available to Agents.",null)),d.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>v(r,"command_removed","Managed Agents are created in the Shennian mobile App.",null))}async function f(e,t,n){try{const s=await n();t.write(`${JSON.stringify({ok:!0,command:e,result:s})}
2
- `)}catch(s){if(s instanceof A){v(t,s.code,s.message,s.nextAction);return}if(s instanceof D){v(t,s.code,s.message,null);return}v(t,"room_command_failed","Shennian Room command failed.",null)}}function g(){return process.env[M]?.trim()||null}async function b(e,t,n){return k({method:"managed-room.execute",params:{context:e,action:t,...n},timeoutMs:t==="send"||t==="attachment.get"?18e4:15e3})}function w(e){g()&&o("managed_room_action_forbidden",`${e} is not available in managed Room context.`)}function R(e){return e||o("room_ref_required","Use an official Room URL returned by Shennian."),S(e)}function S(e){return U(e,"stable").roomId}function U(e,t){let n;const s=t==="invite"?"invalid_invite_url":"invalid_room_ref",r=t==="invite"?"Use an official Shennian Room invitation URL.":"Use an official Room URL returned by Shennian.";try{n=new URL(e)}catch{o(s,r)}const p=n.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/),d=n.searchParams.get("invite");return(n.protocol!=="https:"||n.hostname!=="app.shennian.net"||n.port||n.username||n.password||n.hash||!p||n.searchParams.has("access")||[...n.searchParams.keys()].some(l=>l!=="invite")||t==="stable"&&d!=null||t==="invite"&&(d==null||!/^[A-Za-z0-9_-]{32,256}$/.test(d)))&&o(s,r),{roomId:p[1],...d?{inviteToken:d}:{}}}function q(e){return/^(0|[1-9]\d{0,19})$/.test(e)}function C(e,t){return[...t,e]}async function N(e,t,n){const s=x.resolve(n);let r;try{r=await I.promises.lstat(s)}catch{o("file_not_found",`File does not exist: ${n}`)}(!r.isFile()||r.isSymbolicLink())&&o("invalid_file",`Not a regular file: ${n}`),(r.size<1||r.size>100*1024*1024)&&o("invalid_file_size","Each file must be between 1 byte and 100 MB.");const p=await I.promises.readFile(s),d=y.createHash("sha256").update(p).digest("hex"),l=E(s),m=await e.initializeAttachment(t,{clientUploadId:`cli:${y.randomUUID()}`,name:x.basename(s),mediaKind:l.startsWith("image/")?"image":l.startsWith("audio/")?"audio":"file",mimeType:l,byteSize:r.size,sha256:d}),a=h(m.attachment,"invalid_attachment_response"),i=h(m.upload,"invalid_attachment_response");(typeof a.id!="string"||typeof i.url!="string"||i.method!=="PUT")&&o("invalid_attachment_response","Shennian returned an invalid attachment response.");const c=h(i.headers,"invalid_attachment_response");return Object.values(c).some(u=>typeof u!="string")&&o("invalid_attachment_response","Shennian returned invalid upload headers."),F(i.url),await e.upload(i.url,c,p),await e.completeAttachment(t,a.id),a.id}function E(e){const t=x.extname(e).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif",".heic":"image/heic",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".mp4":"video/mp4",".mov":"video/quicktime",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".csv":"text/csv",".zip":"application/zip",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}[t]??"application/octet-stream"}function P(e,t,n){return Array.isArray(e.messages)||o("invalid_server_response","Shennian returned invalid messages."),{messages:e.messages.map(s=>{const r=h(s,"invalid_server_response"),p=Array.isArray(r.attachments)?r.attachments:[];return{...r,attachments:p.map(d=>{const l=h(d,"invalid_server_response");return(typeof l.attachmentId!="string"||typeof l.name!="string"||typeof l.mimeType!="string"||typeof l.byteSize!="string"||typeof l.sha256!="string")&&o("invalid_server_response","Shennian returned invalid attachment metadata."),{...l,...n.lookup(t,l)}})}})}}function h(e,t){return(!e||typeof e!="object"||Array.isArray(e))&&o(t,"Invalid structured response."),e}function F(e){let t;try{t=new URL(e)}catch{o("invalid_attachment_response","Invalid upload URL.")}const n=t.protocol==="http:"&&(t.hostname==="localhost"||t.hostname==="127.0.0.1");(t.protocol!=="https:"&&!n||t.username||t.password||t.hash)&&o("invalid_attachment_response","Invalid upload URL.")}function o(e,t){throw new A(e,t)}function v(e,t,n,s){e.write(`${JSON.stringify({ok:!1,error:t,message:n,nextAction:s})}
3
- `),e.fail()}export{X as registerRoomCommand};
1
+ import y from"node:crypto";import U from"node:fs";import b from"node:path";import{PublicAttachmentCache as j}from"../room/public-attachment-cache.js";import{PublicRoomClient as z,PublicRoomClientError as A}from"../room/public-room-client.js";import{callDaemonIpc as O}from"../daemon-ipc/client.js";import{DaemonIpcError as S}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_IPC_ROOT_ENV as k,resolveManagedRoomIpcPaths as D}from"../daemon-ipc/paths.js";import{MANAGED_ROOM_CONTEXT_ENV as T}from"../room/managed-context.js";import{createRoomInviteQr as $}from"../room/invite-qr.js";const C="https://shennian.net/install.md",N={write:e=>{process.stdout.write(e)},fail:()=>{process.exitCode=1}};function te(e,t={}){const n=t.client??new z,a=t.cache??new j({client:n}),s=t.io??N,p=t.createInviteQr??$,d=e.command("room").description("Use one explicitly referenced Shennian Room");d.command("create").description("Create a Room as the paired user").requiredOption("--name <name>","Room name").action(async m=>f("room.create",s,async()=>{R("create");const o=m.name.trim();(!o||o.length>80)&&i("invalid_room_name","Room name must contain 1 to 80 characters.");const r=await n.create(o),c=h(r.room,"invalid_room_response");typeof c.inviteUrl!="string"&&i("invalid_room_response","Shennian returned an invalid Room invitation.");const u=await p(c.inviteUrl).catch(()=>({imagePath:null,mimeType:"image/png",payload:c.inviteUrl}));return{...r,sharing:{inviteUrl:c.inviteUrl,inviteQr:u,installGuideUrl:C}}})),d.command("join <invite-url>").description("Join or request access through an official Room invitation").option("--message <message>","Introduction and reason required when owner approval is enabled").action(async(m,o)=>f("room.join",s,async()=>{R("join");const r=I(m,"invite"),c=o.message?.trim();return c&&c.length>500&&i("join_message_too_long","Join request message must be at most 500 characters."),n.join(r.roomId,r.inviteToken,c)})),d.command("open <room-url>").description("Open one Room where the paired user is already a member").action(async m=>f("room.open",s,async()=>{R("open");const o=I(m,"stable");return n.open(o.roomId)})),d.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async m=>f("room.status",s,()=>{const o=g();return o?(m&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),w(o,"status",{})):n.status(x(m))})),d.command("read [room-ref]").description("Read recent, older, or newer messages from one Room").option("--before <sequence>","Read messages before this sequence").option("--after <sequence>","Read messages after this sequence").option("--limit <count>","Maximum messages, 1 to 50","50").action(async(m,o)=>{await f("room.read",s,async()=>{o.before&&o.after&&i("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),o.before&&!q(o.before)&&i("invalid_sequence","--before sequence is invalid."),o.after&&!q(o.after)&&i("invalid_sequence","--after sequence is invalid.");const r=Number(o.limit);(!Number.isInteger(r)||r<1||r>50)&&i("invalid_limit","--limit must be between 1 and 50.");const c=g();if(c)return m&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),w(c,"read",{...o.before?{before:o.before}:{},...o.after?{after:o.after}:{},limit:r});const u=x(m),_=await n.read(u,{before:o.before,after:o.after,limit:r});return G(_,u,a)})}),d.command("send [room-ref]").description("Send text and up to 10 files as the paired user").option("--text <text>","Message text").option("--file <path>","Local file path; repeat for multiple files",P,[]).action(async(m,o)=>{await f("room.send",s,async()=>{const r=o.text?.trim()??"";!r&&o.file.length===0&&i("room_message_empty","Provide --text or at least one --file."),o.file.length>10&&i("room_attachments_too_many","A message can contain at most 10 files.");const c=g();if(c)return m&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),w(c,"send",{clientMessageId:`managed:${y.randomUUID()}`,text:r,files:o.file});const u=x(m),_=[];for(const L of o.file)_.push(await E(n,u,L));return n.send(u,{clientMessageId:`cli:${y.randomUUID()}`,...r?{text:r}:{},..._.length>0?{attachmentIds:_}:{}})})}),d.command("attachment").description("Download Room attachments through Shennian").command("get [room-ref] [attachment-id]").description("Download and verify one Room attachment").action(async(m,o)=>{await f("room.attachment.get",s,async()=>{const r=g(),c=r?m:o;return(!c||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(c))&&i("invalid_attachment_ref","attachment-id is invalid."),r?w(r,"attachment.get",{attachmentId:c}):a.materialize(x(m),c)})}),d.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async m=>f("room.leave",s,()=>(R("leave"),n.leave(M(m))))),d.command("list",{hidden:!0}).action(()=>v(s,"command_removed","Room listing is not available to Agents.",null)),d.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>v(s,"command_removed","Room search is not available to Agents.",null)),d.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>v(s,"command_removed","Managed Agents are created in the Shennian mobile App.",null))}async function f(e,t,n){try{const a=await n();t.write(`${JSON.stringify({ok:!0,command:e,result:a})}
2
+ `)}catch(a){if(a instanceof A){v(t,a.code,a.message,a.nextAction);return}if(a instanceof S){v(t,a.code,a.message,null);return}v(t,"room_command_failed","Shennian Room command failed.",null)}}function g(){return process.env[T]?.trim()||null}async function w(e,t,n){const a=process.env[k]?.trim();if(!a||!b.isAbsolute(a))throw new S("managed_room_context_unavailable","Managed Room IPC context is unavailable.");return O({method:"managed-room.execute",params:{context:e,action:t,...n},paths:D(a),timeoutMs:t==="send"||t==="attachment.get"?18e4:15e3})}function R(e){g()&&i("managed_room_action_forbidden",`${e} is not available in managed Room context.`)}function x(e){return e||i("room_ref_required","Use an official Room URL returned by Shennian."),M(e)}function M(e){return I(e,"stable").roomId}function I(e,t){let n;const a=t==="invite"?"invalid_invite_url":"invalid_room_ref",s=t==="invite"?"Use an official Shennian Room invitation URL.":"Use an official Room URL returned by Shennian.";try{n=new URL(e)}catch{i(a,s)}const p=n.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/),d=n.searchParams.get("invite");return(n.protocol!=="https:"||n.hostname!=="app.shennian.net"||n.port||n.username||n.password||n.hash||!p||n.searchParams.has("access")||[...n.searchParams.keys()].some(l=>l!=="invite")||t==="stable"&&d!=null||t==="invite"&&(d==null||!/^[A-Za-z0-9_-]{32,256}$/.test(d)))&&i(a,s),{roomId:p[1],...d?{inviteToken:d}:{}}}function q(e){return/^(0|[1-9]\d{0,19})$/.test(e)}function P(e,t){return[...t,e]}async function E(e,t,n){const a=b.resolve(n);let s;try{s=await U.promises.lstat(a)}catch{i("file_not_found",`File does not exist: ${n}`)}(!s.isFile()||s.isSymbolicLink())&&i("invalid_file",`Not a regular file: ${n}`),(s.size<1||s.size>100*1024*1024)&&i("invalid_file_size","Each file must be between 1 byte and 100 MB.");const p=await U.promises.readFile(a),d=y.createHash("sha256").update(p).digest("hex"),l=F(a),m=await e.initializeAttachment(t,{clientUploadId:`cli:${y.randomUUID()}`,name:b.basename(a),mediaKind:l.startsWith("image/")?"image":l.startsWith("audio/")?"audio":"file",mimeType:l,byteSize:s.size,sha256:d}),o=h(m.attachment,"invalid_attachment_response"),r=h(m.upload,"invalid_attachment_response");(typeof o.id!="string"||typeof r.url!="string"||r.method!=="PUT")&&i("invalid_attachment_response","Shennian returned an invalid attachment response.");const c=h(r.headers,"invalid_attachment_response");return Object.values(c).some(u=>typeof u!="string")&&i("invalid_attachment_response","Shennian returned invalid upload headers."),J(r.url),await e.upload(r.url,c,p),await e.completeAttachment(t,o.id),o.id}function F(e){const t=b.extname(e).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif",".heic":"image/heic",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".mp4":"video/mp4",".mov":"video/quicktime",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".csv":"text/csv",".zip":"application/zip",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}[t]??"application/octet-stream"}function G(e,t,n){return Array.isArray(e.messages)||i("invalid_server_response","Shennian returned invalid messages."),{messages:e.messages.map(a=>{const s=h(a,"invalid_server_response"),p=Array.isArray(s.attachments)?s.attachments:[];return{...s,attachments:p.map(d=>{const l=h(d,"invalid_server_response");return(typeof l.attachmentId!="string"||typeof l.name!="string"||typeof l.mimeType!="string"||typeof l.byteSize!="string"||typeof l.sha256!="string")&&i("invalid_server_response","Shennian returned invalid attachment metadata."),{...l,...n.lookup(t,l)}})}})}}function h(e,t){return(!e||typeof e!="object"||Array.isArray(e))&&i(t,"Invalid structured response."),e}function J(e){let t;try{t=new URL(e)}catch{i("invalid_attachment_response","Invalid upload URL.")}const n=t.protocol==="http:"&&(t.hostname==="localhost"||t.hostname==="127.0.0.1");(t.protocol!=="https:"&&!n||t.username||t.password||t.hash)&&i("invalid_attachment_response","Invalid upload URL.")}function i(e,t){throw new A(e,t)}function v(e,t,n,a){e.write(`${JSON.stringify({ok:!1,error:t,message:n,nextAction:a})}
3
+ `),e.fail()}export{te as registerRoomCommand};
@@ -1,6 +1,6 @@
1
- import{loadConfig as c,saveConfig as f}from"../config/index.js";import{spawnCommandString as w}from"../agents/command-spec.js";function u(o){return o.models?.length?o.models:o.model?[o.model]:[]}async function h(o){return new Promise((i,t)=>{const n=w(o,["/caps"],{stdio:["ignore","pipe","pipe"]});let e="",m="",a=!1;const d=s=>{a||(a=!0,clearTimeout(g),s())};n.stdout?.on("data",s=>{e+=s.toString()}),n.stderr?.on("data",s=>{m+=s.toString()}),n.on("error",s=>d(()=>t(new Error(`Failed to run command: ${s.message}`)))),n.on("close",()=>d(()=>{const s=e.trim().split(`
2
- `).find(r=>r.trim().startsWith("{"));if(!s){t(new Error(`Command did not output valid caps JSON.
1
+ import{loadConfig as l,saveConfig as g}from"../config/index.js";import{spawnCommandString as v}from"../agents/command-spec.js";import{assessCustomRuntimeSecurity as w}from"../agents/custom-managed-runtime.js";function f(r){return r.models?.length?r.models:r.model?[r.model]:[]}async function y(r){return new Promise((s,t)=>{const n=v(r,["/caps"],{stdio:["ignore","pipe","pipe"]});let e="",c="",a=!1;const m=i=>{a||(a=!0,clearTimeout(u),i())};n.stdout?.on("data",i=>{e+=i.toString()}),n.stderr?.on("data",i=>{c+=i.toString()}),n.on("error",i=>m(()=>t(new Error(`Failed to run command: ${i.message}`)))),n.on("close",()=>m(()=>{const i=e.trim().split(`
2
+ `).find(o=>o.trim().startsWith("{"));if(!i){t(new Error(`Command did not output valid caps JSON.
3
3
  stdout: ${e.trim()}
4
- stderr: ${m.trim()}`));return}try{const r=JSON.parse(s);if(!r.name||!r.mode)throw new Error('Caps JSON must contain "name" and "mode" fields');if(u(r).length===0)throw new Error('Caps JSON must contain either "model" or a non-empty "models" array');if(r.mode!=="spawn"&&r.mode!=="stdio")throw new Error(`Caps "mode" must be "spawn" or "stdio", got: ${r.mode}`);i(r)}catch(r){t(r instanceof Error?r:new Error(`Failed to parse caps JSON: ${s}`))}}));const g=setTimeout(()=>{n.kill("SIGTERM"),d(()=>t(new Error("Timed out waiting for caps response (5s)")))},5e3)})}function C(o){const i=o.command("runtime").description("Manage custom Agent runtimes");i.command("add <name>").description("Register a custom Agent runtime").requiredOption("--command <cmd>","Command to run the runtime").action(async(t,n)=>{if(!/^[a-zA-Z0-9_-]+$/.test(t)){p("invalid_runtime_name","Runtime name may contain only letters, numbers, hyphens, and underscores.");return}let e;try{e=await h(n.command)}catch(a){p("runtime_probe_failed",a instanceof Error?a.message:String(a));return}const m=c();m.customAgents=m.customAgents??{},m.customAgents[t]={command:n.command,caps:e},f(m),l("runtime.add",{runtimeRef:`custom:${t}`,name:e.name,models:u(e),mode:e.mode,version:e.version??null})}),i.command("list").description("List registered custom Agent runtimes").action(()=>{const t=Object.entries(c().customAgents??{}).map(([n,e])=>({runtimeRef:`custom:${n}`,name:e.caps.name,models:u(e.caps),mode:e.caps.mode,command:e.command,version:e.caps.version??null}));l("runtime.list",{runtimes:t})}),i.command("remove <name>").description("Unregister a custom Agent runtime").action(t=>{const n=c();if(!n.customAgents?.[t]){p("runtime_not_found",`No custom runtime named "${t}" exists.`);return}delete n.customAgents[t],f(n),l("runtime.remove",{runtimeRef:`custom:${t}`})})}function l(o,i){process.stdout.write(`${JSON.stringify({ok:!0,command:o,result:i})}
5
- `)}function p(o,i){process.stdout.write(`${JSON.stringify({ok:!1,error:o,message:i,nextAction:null})}
6
- `),process.exitCode=1}export{C as registerRuntimeCommand};
4
+ stderr: ${c.trim()}`));return}try{const o=JSON.parse(i);if(!o.name||!o.mode)throw new Error('Caps JSON must contain "name" and "mode" fields');if(f(o).length===0)throw new Error('Caps JSON must contain either "model" or a non-empty "models" array');if(o.mode!=="spawn"&&o.mode!=="stdio")throw new Error(`Caps "mode" must be "spawn" or "stdio", got: ${o.mode}`);s(o)}catch(o){t(o instanceof Error?o:new Error(`Failed to parse caps JSON: ${i}`))}}));const u=setTimeout(()=>{n.kill("SIGTERM"),m(()=>t(new Error("Timed out waiting for caps response (5s)")))},5e3)})}function $(r){const s=r.command("runtime").description("Manage custom Agent runtimes");s.command("add <name>").description("Register a custom Agent runtime").requiredOption("--command <cmd>","Command to run the runtime").action(async(t,n)=>{if(!/^[a-zA-Z0-9_-]+$/.test(t)){d("invalid_runtime_name","Runtime name may contain only letters, numbers, hyphens, and underscores.");return}let e;try{e=await y(n.command)}catch(u){d("runtime_probe_failed",u instanceof Error?u.message:String(u));return}const c={command:n.command,caps:e},a=w(c);if(e.security&&a.state==="unverified"){d("runtime_security_declaration_invalid",`Custom runtime security declaration could not be verified: ${a.reason}`);return}const m=l();m.customAgents=m.customAgents??{},m.customAgents[t]=c,g(m),p("runtime.add",{runtimeRef:`custom:${t}`,name:e.name,models:f(e),mode:e.mode,version:e.version??null,managedProtection:"unverified",securityDeclaration:a.state})}),s.command("list").description("List registered custom Agent runtimes").action(()=>{const t=Object.entries(l().customAgents??{}).map(([n,e])=>({runtimeRef:`custom:${n}`,name:e.caps.name,models:f(e.caps),mode:e.caps.mode,command:e.command,version:e.caps.version??null,managedProtection:"unverified",securityDeclaration:w(e).state}));p("runtime.list",{runtimes:t})}),s.command("remove <name>").description("Unregister a custom Agent runtime").action(t=>{const n=l();if(!n.customAgents?.[t]){d("runtime_not_found",`No custom runtime named "${t}" exists.`);return}delete n.customAgents[t],g(n),p("runtime.remove",{runtimeRef:`custom:${t}`})})}function p(r,s){process.stdout.write(`${JSON.stringify({ok:!0,command:r,result:s})}
5
+ `)}function d(r,s){process.stdout.write(`${JSON.stringify({ok:!1,error:r,message:s,nextAction:null})}
6
+ `),process.exitCode=1}export{$ as registerRuntimeCommand};
@@ -1,3 +1,4 @@
1
+ import type { ManagedAgentRuntimePlatform, ManagedAgentRuntimeSecurityDeclaration } from '../security/managed-agent-runtime.js';
1
2
  export declare function getShennianDir(): string;
2
3
  export declare function resolveShennianPath(...parts: string[]): string;
3
4
  export declare function getConfigPath(): string;
@@ -13,8 +14,29 @@ export type CustomAgentEntry = {
13
14
  proactive?: boolean;
14
15
  resume?: boolean;
15
16
  version?: string;
17
+ security?: CustomAgentSecurityDeclaration;
16
18
  };
17
19
  };
20
+ /**
21
+ * Optional managed-runtime attestation returned by a Custom Agent's /caps
22
+ * probe. It is bound to one runtime version, executable digest and platform.
23
+ * Missing or stale attestations leave the runtime unverified.
24
+ */
25
+ export type CustomAgentSecurityDeclaration = {
26
+ schemaVersion: 1;
27
+ declarationVersion: number;
28
+ policyProtocol: 'shennian-managed-policy-file-v1';
29
+ runtimeVersion: string;
30
+ executablePath: string;
31
+ executableSha256: string;
32
+ conformance: {
33
+ suiteVersion: string;
34
+ status: 'passed' | 'failed';
35
+ platform: ManagedAgentRuntimePlatform;
36
+ testedAt: string;
37
+ };
38
+ runtimeSecurity: ManagedAgentRuntimeSecurityDeclaration;
39
+ };
18
40
  export type ShennianConfig = {
19
41
  defaultProvider?: string;
20
42
  defaultModel?: string;
@@ -1,3 +1,4 @@
1
+ export declare const MANAGED_ROOM_IPC_ROOT_ENV = "SHENNIAN_MANAGED_ROOM_IPC_ROOT";
1
2
  export type DaemonIpcPaths = {
2
3
  runtimeDirectory: string;
3
4
  secretFile: string;
@@ -5,3 +6,10 @@ export type DaemonIpcPaths = {
5
6
  transport: 'unix_socket' | 'windows_named_pipe';
6
7
  };
7
8
  export declare function resolveDaemonIpcPaths(rootDir?: string, platform?: NodeJS.Platform): DaemonIpcPaths;
9
+ /**
10
+ * Managed Agents must never read the persistent ~/.shennian tree. Their only
11
+ * local bridge is a separate, daemon-owned IPC endpoint whose handler still
12
+ * requires the per-turn managed Room capability.
13
+ */
14
+ export declare function getManagedRoomIpcRoot(shennianDir?: string): string;
15
+ export declare function resolveManagedRoomIpcPaths(rootDir?: string, platform?: NodeJS.Platform): DaemonIpcPaths;
@@ -1 +1 @@
1
- import s from"node:crypto";import n from"node:path";import{getShennianDir as p}from"../config/index.js";function d(o=p(),r=process.platform){const e=n.join(o,"runtime"),t=n.join(e,"daemon-ipc.secret");if(r==="win32"){const i=s.createHash("sha256").update(n.resolve(o).toLowerCase()).digest("hex").slice(0,20);return{runtimeDirectory:e,secretFile:t,endpoint:`\\\\.\\pipe\\shennian-daemon-${i}`,transport:"windows_named_pipe"}}return{runtimeDirectory:e,secretFile:t,endpoint:n.join(e,"daemon.sock"),transport:"unix_socket"}}export{d as resolveDaemonIpcPaths};
1
+ import s from"node:crypto";import a from"node:os";import o from"node:path";import{getShennianDir as i}from"../config/index.js";const f="SHENNIAN_MANAGED_ROOM_IPC_ROOT";function c(e=i(),t=process.platform){const n=o.join(e,"runtime"),r=o.join(n,"daemon-ipc.secret");if(t==="win32"){const p=s.createHash("sha256").update(o.resolve(e).toLowerCase()).digest("hex").slice(0,20);return{runtimeDirectory:n,secretFile:r,endpoint:`\\\\.\\pipe\\shennian-daemon-${p}`,transport:"windows_named_pipe"}}return{runtimeDirectory:n,secretFile:r,endpoint:o.join(n,"daemon.sock"),transport:"unix_socket"}}function m(e=i()){const t=s.createHash("sha256").update(o.resolve(e)).digest("hex").slice(0,20);return o.join(a.tmpdir(),`shennian-managed-room-${t}`)}function l(e=m(),t=process.platform){return c(e,t)}export{f as MANAGED_ROOM_IPC_ROOT_ENV,m as getManagedRoomIpcRoot,c as resolveDaemonIpcPaths,l as resolveManagedRoomIpcPaths};
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{Command as te}from"commander";import I from"chalk";import re from"node:os";import P from"node:fs";import{loadConfig as p,saveConfig as ie,getConfigPath as se,getShennianDir as ae,resolveShennianPath as J,normalizeAutoUpgradePolicy as ce}from"./config/index.js";import{CliRelayClient as me}from"./relay/client.js";import{registerPairCommand as le,runSmartStart as de}from"./commands/pair.js";import{clearDaemonPidIfOwner as z,createDaemonInstanceId as pe,clearDaemonLauncher as j,findRunningDaemonProcessIds as ge,isRemoteAccessDisabled as ue,isIsolatedDaemonE2eInstance as he,registerDaemonCommand as fe,writeDaemonPid as K,writeDaemonLauncher as W}from"./commands/daemon.js";import{registerRuntimeCommand as Se}from"./commands/runtime.js";import{registerManagerCommand as ve}from"./commands/manager.js";import{registerUpgradeCommand as Ie}from"./commands/upgrade.js";import{registerToolsCommand as ye}from"./commands/tools.js";import{registerIntegrationCommand as we}from"./commands/integration.js";import{registerRoomCommand as Ce}from"./commands/room.js";import{SessionManager as Re}from"./session/manager.js";import{SERVERS as _,regionToUrl as ke,urlToRegion as De}from"./region.js";import{getCurrentVersion as Y,handleStartupCrashCheck as Ee}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Te}from"./upgrade/scheduler.js";import{detectAgents as Ae}from"./agents/detect.js";import{augmentProcessPath as q}from"./env-path.js";q();const Q=Y();import{getCachedAgentInfos as be,resolveAgentInfos as Oe}from"./agents/model-registry.js";import{initCliLogReporter as Ue,reportLog as y}from"./log-reporter.js";import{isNativeFusionEnabled as Ne}from"./native-fusion/config.js";import{NativeSessionFusionService as $e}from"./native-fusion/service.js";import{startDaemonLogRetention as Pe}from"./daemon-log.js";import{startDaemonIpcServer as _e}from"./daemon-ipc/server.js";import{RoomSessionRegistry as Me}from"./room/session-registry.js";import{HttpRoomBindingClient as xe}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Le}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Ve}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Be}from"./room/mirror-sync-service.js";import{RoomActivationService as He}from"./room/activation-service.js";import{RoomActivationAudit as Ge}from"./room/activation-audit.js";import{RoomAttachmentCache as Fe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Je}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as ze}from"./room/managed-context.js";import{createManagedRoomIpcHandlers as je}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as Ke,SESSION_SYNC_ADVERTISED_CAPABILITIES as We}from"@shennian/wire";import{getDaemonInstallationId as Ye}from"./session/store.js";import{synchronizeMachinePersonalAuthority as qe}from"./personal-sync/machine-authority.js";import{getPersonalSyncLocalMetrics as Qe}from"./personal-sync/metrics.js";import{CLI_SESSION_SYNC_DATA_EPOCH as Xe,ensurePersonalDataEpoch as Ze}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as eo,DeletionSyncTerminalError as oo}from"./session/deletion-sync-state.js";const X=ae(),no=J("daemon.pid");function to(){try{const o=P.readFileSync(no,"utf-8").trim();if(o.startsWith("{")){const a=JSON.parse(o),m=Number(a.pid);return Number.isInteger(m)&&m>0?m:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function ro(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function M(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
- `),process.exitCode=1}function io(o){const n=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&n.push(`reason=${o.reason}`),o.error&&n.push(`error=${o.error}`),n.join(" ")}async function Z(o,n=5e3){const a=Date.now();for(;Date.now()-a<n;){try{process.kill(o,0)}catch{return!0}await new Promise(m=>setTimeout(m,100))}try{return process.kill(o,0),!1}catch{return!0}}const r=new te;r.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(Q),r.enablePositionalOptions(),we(r),Ce(r),r.option("--api <url>","Server URL override").option("--name <name>","Machine name",re.hostname()).action(async o=>{const n=p(),a=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await de(a,o.name)}),r.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const n=J("env.json");try{const e=JSON.parse(P.readFileSync(n,"utf-8"));for(const[i,t]of Object.entries(e))process.env[i]||(process.env[i]=t);q()}catch{}const a=Pe();ue()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(a),process.exit(0));const m=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),x=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await Z(e)||(process.kill(e,"SIGKILL"),await Z(e,2e3))};try{const e=to();if(e&&e!==process.pid)try{process.kill(e,0),m?await x(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const b=he(process.env,X)?[]:ge(process.pid);if(b.length>0)if(m)for(const e of b)try{await x(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${b[0]}), skipping duplicate start`),process.exit(0);const d=pe();K(process.pid,d,{version:Q}),W(process.pid,void 0,d),process.on("exit",()=>{z(process.pid,d),j(d)}),await Ee()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));try{Ze()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const c=p(),u=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??c.serverUrl??_.cn.url,g=Y(),O=Ae(),S=O.map(e=>e.type),U=be(O),w=new Me,C=new Ve,L=new ze(w),h=new xe(u),oe=new Fe({actionClient:h}),ne=new Je({actionClient:h});let N=null;const R=await _e({handlers:{"personal.sync.metrics":()=>Qe(),...je({authority:L,actionClient:h,attachmentCache:oe,attachmentUploader:ne,observations:C,getMachineCredentials:()=>({machineToken:p().machineToken})})}});let V=!1;const k=()=>{V||(V=!0,R.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",k),process.once("SIGTERM",k),!c.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await R.close(),process.exitCode=1;return}process.removeListener("SIGINT",k),process.removeListener("SIGTERM",k);const B=`${ro(u)}/relay/machine`,$=c.machineToken;if(!$)return;try{await qe({serverUrl:u,machineToken:$,machineId:c.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await R.close(),process.exitCode=1;return}c.machineId&&Ue(u,c.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${B}... (v${g}) agents: ${S.join(",")}`),y({level:"info",wsEvent:"daemon.start",metadata:{version:g,agents:S}});let D=null,E=null,T=0;const H=ce(c.autoUpgrade),l=new me({serverUrl:B,machineToken:$,cliVersion:g,agentList:S,autoUpgradePolicy:H,sessionSyncProtocolVersion:Ke,sessionSyncCapabilities:We,daemonInstallationId:Ye(),dataEpoch:Xe,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),y({level:"info",wsEvent:"daemon.connected"}),U.some(t=>t.models.length>0)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:S,agents:U}}),Oe(O,{serverUrl:c.serverUrl??u,authToken:c.machineToken??c.accessToken}).then(t=>{JSON.stringify(t)!==JSON.stringify(U)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:S,agents:t}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:t,clearUpgradeAttempt:s})=>{const v=t();s(),v&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${v.from} \u2192 ${v.to}`),l.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:v.from,to:v.to}}))}).catch(()=>{}),A.start(),E?.start();const e=++T,i=async()=>{try{await f.synchronizeSessionDeletions(),e===T&&l.getState()==="connected"&&D?.handleConnected()}catch(t){if(console.error("[session.deletions] startup catch-up failed",t),e===T&&l.getState()==="connected"){if(t instanceof oo)return;const s=t instanceof eo?Math.max(1e3,t.retryAfterMs):5e3;setTimeout(()=>{i()},s).unref?.()}}};i()},onDisconnected:e=>{T+=1,E?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${io(e)}`),y({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),y({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),f.handleReq(e)}});D=Ne()?new $e(l):null,E=new Be({registry:w,observations:C,actionClient:h,getMachineCredentials:()=>({machineToken:p().machineToken}),onMessages:async(e,i,t,s)=>{await N?.processMessages(e,i,t,s)}});const A=new Te({currentVersion:g,machineId:c.machineId??"unknown-machine",serverUrl:c.serverUrl??u,getIdleState:()=>f.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(l,"auto-upgrade",e,{currentVersion:g})}});A.setPolicy(H);const f=new Re(l,D,g,A,new Le({registry:w,observations:C,bindingClient:h,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${d}`}));N=new He({registry:w,observations:C,enqueueConversationBatch:e=>f.enqueueManagedRoomBatch(e),loadContext:async e=>{const i=p().machineToken?.trim(),t=e.binding;if(!i||!t)throw new Error("managed_room_context_unavailable");const s=await h.status({machineToken:i,roomId:t.roomId,bindingId:t.bindingId});return{roomName:s.room.name,memberCount:s.room.memberCount,members:s.room.members,agentsByIdentityId:s.agent?{[s.agentIdentityId]:s.agent}:{},agentSystemPromptsByIdentityId:s.agent?{[s.agentIdentityId]:s.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,i)=>{const t=p().machineToken;if(!t)throw new Error("machine_not_authorized");return L.issue({registryKey:e,machineCredential:t,activationCauseMessageId:i})},audit:new Ge}),f.setManagedRoomTurnPreparer(e=>N.prepareConversationTurn(e)),P.mkdirSync(X,{recursive:!0}),K(process.pid,d,{version:g}),W(process.pid,void 0,d),l.connect(),process.stdin.resume();let G=!1;const F=async()=>{if(!G){G=!0,console.log(I.gray(`
3
- Disconnecting...`)),y({level:"info",wsEvent:"daemon.stop"}),clearInterval(a);try{await f.cleanup(),A.stop(),D?.stop(),E?.stop(),l.disconnect(),await R.close(),z(process.pid,d),j(d)}finally{process.exit(0)}}};process.on("SIGINT",()=>{F()}),process.on("SIGTERM",()=>{F()})}),r.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>M()),r.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>M()),r.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>M());const ee=r.command("config").description("View or modify configuration");ee.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${se()}`);const n={...p()};if(n.machineToken&&(n.machineToken=n.machineToken.slice(0,12)+"..."),n.serverUrl){const a=De(n.serverUrl);console.log(`Region: ${a} (${_[a].label})`)}console.log(JSON.stringify(n,null,2))}),ee.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,n)=>{const a=p();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(I.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const m=n;a.serverUrl=ke(m),ie(a),console.log(I.green(`\u2713 Server set to ${_[m].label}`)),console.log(I.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(I.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),le(r),fe(r),Se(r),ve(r),Ie(r),ye(r),r.parse();
1
+ import{Command as ie}from"commander";import I from"chalk";import ae from"node:os";import _ from"node:fs";import{loadConfig as d,saveConfig as ce,getConfigPath as me,getShennianDir as le,resolveShennianPath as J,normalizeAutoUpgradePolicy as de}from"./config/index.js";import{CliRelayClient as pe}from"./relay/client.js";import{collectKnownLogSecrets as K,installCliLogRedaction as ge,sanitizeCliLogValue as ue}from"./log-redaction.js";import{registerPairCommand as he,runSmartStart as fe}from"./commands/pair.js";import{clearDaemonPidIfOwner as j,createDaemonInstanceId as Se,clearDaemonLauncher as W,findRunningDaemonProcessIds as ve,isRemoteAccessDisabled as Y,prepareRemoteAccessPause as Ie,stopPreparedRemoteAccessService as we,isIsolatedDaemonE2eInstance as ye,registerDaemonCommand as Re,writeDaemonPid as q,writeDaemonLauncher as Q}from"./commands/daemon.js";import{registerRuntimeCommand as Ce}from"./commands/runtime.js";import{registerManagerCommand as ke}from"./commands/manager.js";import{registerUpgradeCommand as Ee}from"./commands/upgrade.js";import{registerToolsCommand as De}from"./commands/tools.js";import{registerIntegrationCommand as Te}from"./commands/integration.js";import{registerRoomCommand as Ae}from"./commands/room.js";import{SessionManager as be}from"./session/manager.js";import{SERVERS as M,regionToUrl as Oe,urlToRegion as Ue}from"./region.js";import{getCurrentVersion as X,handleStartupCrashCheck as Ne}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Pe}from"./upgrade/scheduler.js";import{detectAgents as $e}from"./agents/detect.js";import{augmentProcessPath as Z}from"./env-path.js";Z();const ee=X();import{getCachedAgentInfos as _e,resolveAgentInfos as Me}from"./agents/model-registry.js";import{initCliLogReporter as xe,reportLog as w}from"./log-reporter.js";import{isNativeFusionEnabled as Le}from"./native-fusion/config.js";import{NativeSessionFusionService as Ve}from"./native-fusion/service.js";import{startDaemonLogRetention as Be}from"./daemon-log.js";import{startDaemonIpcServer as Ge}from"./daemon-ipc/server.js";import{resolveManagedRoomIpcPaths as He}from"./daemon-ipc/paths.js";import{RoomSessionRegistry as Fe}from"./room/session-registry.js";import{HttpRoomBindingClient as ze}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Je}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Ke}from"./room/host-session-observations.js";import{RoomMirrorSyncService as je}from"./room/mirror-sync-service.js";import{RoomActivationService as We}from"./room/activation-service.js";import{RoomActivationAudit as Ye}from"./room/activation-audit.js";import{RoomAttachmentCache as qe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Qe}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Xe}from"./room/managed-context.js";import{createManagedRoomExecutor as Ze}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as eo,SESSION_SYNC_ADVERTISED_CAPABILITIES as oo}from"@shennian/wire";import{getDaemonInstallationId as no}from"./session/store.js";import{synchronizeMachinePersonalAuthority as to}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as ro,ensurePersonalDataEpoch as so}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as io,DeletionSyncTerminalError as ao}from"./session/deletion-sync-state.js";const oe=le(),co=J("daemon.pid");function mo(){try{const o=_.readFileSync(co,"utf-8").trim();if(o.startsWith("{")){const a=JSON.parse(o),m=Number(a.pid);return Number.isInteger(m)&&m>0?m:null}const t=parseInt(o,10);return Number.isInteger(t)&&t>0?t:null}catch{return null}}function lo(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function x(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
+ `),process.exitCode=1}function po(o){const t=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&t.push(`reason=${o.reason}`),o.error&&t.push(`error=${o.error}`),t.join(" ")}async function ne(o,t=5e3){const a=Date.now();for(;Date.now()-a<t;){try{process.kill(o,0)}catch{return!0}await new Promise(m=>setTimeout(m,100))}try{return process.kill(o,0),!1}catch{return!0}}const r=new ie;r.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(ee),r.enablePositionalOptions(),Te(r),Ae(r),r.option("--api <url>","Server URL override").option("--name <name>","Machine name",ae.hostname()).action(async o=>{const t=d(),a=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??t.serverUrl??void 0;await fe(a,o.name)}),r.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const t=J("env.json");try{const e=JSON.parse(_.readFileSync(t,"utf-8"));for(const[s,n]of Object.entries(e))process.env[s]||(process.env[s]=n);Z()}catch{}ge(K(process.env,d()));const a=Be();Y()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(a),process.exit(0));const m=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),L=async(e,s)=>{console.log(`[${new Date().toISOString()}] ${s} (PID ${e})`),process.kill(e,"SIGTERM"),await ne(e)||(process.kill(e,"SIGKILL"),await ne(e,2e3))};try{const e=mo();if(e&&e!==process.pid)try{process.kill(e,0),m?await L(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const b=ye(process.env,oe)?[]:ve(process.pid);if(b.length>0)if(m)for(const e of b)try{await L(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${b[0]}), skipping duplicate start`),process.exit(0);const p=Se();q(process.pid,p,{version:ee}),Q(process.pid,void 0,p),process.on("exit",()=>{j(process.pid,p),W(p)}),await Ne()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));try{so()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const c=d(),u=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??c.serverUrl??M.cn.url,g=X(),O=$e(),S=O.map(e=>e.type),U=_e(O),y=new Fe,R=new Ke,N=new Xe(y),h=new ze(u),re=new qe({actionClient:h}),se=new Qe({actionClient:h});let P=null;const V=Ze({authority:N,actionClient:h,attachmentCache:re,attachmentUploader:se,observations:R,getMachineCredentials:()=>({machineToken:d().machineToken})}),C=await Ge({paths:He(),handlers:{"managed-room.execute":e=>V(e)}});let B=!1;const k=()=>{B||(B=!0,C.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",k),process.once("SIGTERM",k),!c.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await C.close(),process.exitCode=1;return}process.removeListener("SIGINT",k),process.removeListener("SIGTERM",k);const G=`${lo(u)}/relay/machine`,$=c.machineToken;if(!$)return;try{await to({serverUrl:u,machineToken:$,machineId:c.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await C.close(),process.exitCode=1;return}c.machineId&&xe(u,c.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${G}... (v${g}) agents: ${S.join(",")}`),w({level:"info",wsEvent:"daemon.start",metadata:{version:g,agents:S}});let E=null,D=null,T=0;const H=de(c.autoUpgrade),l=new pe({serverUrl:G,machineToken:$,cliVersion:g,agentList:S,autoUpgradePolicy:H,sessionSyncProtocolVersion:eo,sessionSyncCapabilities:oo,daemonInstallationId:no(),dataEpoch:ro,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),w({level:"info",wsEvent:"daemon.connected"}),U.some(n=>n.models.length>0)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:S,agents:U}}),Me(O,{serverUrl:c.serverUrl??u,authToken:c.machineToken??c.accessToken}).then(n=>{JSON.stringify(n)!==JSON.stringify(U)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:S,agents:n}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:n,clearUpgradeAttempt:i})=>{const v=n();i(),v&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${v.from} \u2192 ${v.to}`),l.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:v.from,to:v.to}}))}).catch(()=>{}),A.start(),D?.start();const e=++T,s=async()=>{try{await f.synchronizeSessionDeletions(),e===T&&l.getState()==="connected"&&E?.handleConnected()}catch(n){if(console.error("[session.deletions] startup catch-up failed",n),e===T&&l.getState()==="connected"){if(n instanceof ao)return;const i=n instanceof io?Math.max(1e3,n.retryAfterMs):5e3;setTimeout(()=>{s()},i).unref?.()}}};s()},onDisconnected:e=>{T+=1,D?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${po(e)}`),w({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),w({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),f.handleReq(e)}});E=Le()?new Ve(l):null,D=new je({registry:y,observations:R,actionClient:h,getMachineCredentials:()=>({machineToken:d().machineToken}),onMessages:async(e,s,n,i)=>{await P?.processMessages(e,s,n,i)}});const A=new Pe({currentVersion:g,machineId:c.machineId??"unknown-machine",serverUrl:c.serverUrl??u,getIdleState:()=>f.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:s}=await import("./commands/upgrade.js");await s(l,"auto-upgrade",e,{currentVersion:g})}});A.setPolicy(H);const f=new be(l,E,g,A,new Je({registry:y,observations:R,bindingClient:h,getMachineCredentials:()=>{const e=d();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${p}`}),{prepare:()=>Ie(),isDisabled:()=>Y(),shutdownAfterResponse:()=>{setTimeout(()=>{we(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>N.revokeToken(e),V);P=new We({registry:y,observations:R,enqueueConversationBatch:e=>f.enqueueManagedRoomBatch(e),loadContext:async e=>{const s=d().machineToken?.trim(),n=e.binding;if(!s||!n)throw new Error("managed_room_context_unavailable");const i=await h.status({machineToken:s,roomId:n.roomId,bindingId:n.bindingId});return{roomName:i.room.name,memberCount:i.room.memberCount,members:i.room.members,agentsByIdentityId:i.agent?{[i.agentIdentityId]:i.agent}:{},agentSystemPromptsByIdentityId:i.agent?{[i.agentIdentityId]:i.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,s)=>{const n=d().machineToken;if(!n)throw new Error("machine_not_authorized");return N.issue({registryKey:e,machineCredential:n,activationCauseMessageId:s})},audit:new Ye}),f.setManagedRoomTurnPreparer(e=>P.prepareConversationTurn(e)),_.mkdirSync(oe,{recursive:!0}),q(process.pid,p,{version:g}),Q(process.pid,void 0,p),l.connect(),process.stdin.resume();let F=!1;const z=async()=>{if(!F){F=!0,console.log(I.gray(`
3
+ Disconnecting...`)),w({level:"info",wsEvent:"daemon.stop"}),clearInterval(a);try{await f.cleanup(),A.stop(),E?.stop(),D?.stop(),l.disconnect(),await C.close(),j(process.pid,p),W(p)}finally{process.exit(0)}}};process.on("SIGINT",()=>{z()}),process.on("SIGTERM",()=>{z()})}),r.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>x()),r.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>x()),r.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>x());const te=r.command("config").description("View or modify configuration");te.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${me()}`);const o=d(),t=ue(o,K(process.env,o));if(o.serverUrl){const a=Ue(o.serverUrl);console.log(`Region: ${a} (${M[a].label})`)}console.log(JSON.stringify(t,null,2))}),te.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,t)=>{const a=d();if(o==="server"){t!=="cn"&&t!=="global"&&(console.error(I.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const m=t;a.serverUrl=Oe(m),ce(a),console.log(I.green(`\u2713 Server set to ${M[m].label}`)),console.log(I.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(I.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),he(r),Re(r),Ce(r),ke(r),Ee(r),De(r),r.parse();
@@ -0,0 +1,4 @@
1
+ export declare function redactCliLogText(value: string, knownSecrets?: readonly string[]): string;
2
+ export declare function sanitizeCliLogValue(value: unknown, knownSecrets?: readonly string[], key?: string, depth?: number, seen?: WeakSet<object>): unknown;
3
+ export declare function collectKnownLogSecrets(env: NodeJS.ProcessEnv, config: unknown): string[];
4
+ export declare function installCliLogRedaction(knownSecrets: readonly string[]): void;
@@ -0,0 +1 @@
1
+ const c="[redacted]";function f(t,e=[]){let o=t;for(const i of e)i.length>=6&&(o=o.split(i).join(c));return o.replace(/https?:\/\/[^\s"'<>]+/gi,i=>i.replace(/[?#].*$/,"")).replace(/\b(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(\/[^\s?#]*)(?:[?#][^\s]*)/gi,"$1 $2").replace(/\b(Authorization\s*[:=]\s*)(?:(?:Bearer|Basic)\s+)?[^\s,;}"']+/gi,`$1${c}`).replace(/\b((?:Cookie|Set-Cookie)\s*[:=]\s*)[^\r\n]+/gi,`$1${c}`).replace(/Bearer\s+[^\s,"'}]+/gi,`Bearer ${c}`).replace(/([?&](?:token|access|invite|code|signature|ticket|randstr|csrf)=)[^&#\s]+/gi,`$1${c}`).replace(/(["']?(?:token|tokenPreview|credentialPreview|secretPreview|accessToken|refreshToken|machineToken|pairToken|pushToken|shareToken|idToken|apiKey|clientSecret|deviceSecret|deviceSecretHash|browserState|inviteToken|inviteCode|shareCode|signature|code|smsCode|verificationCode|oneTimeCode|otp|ticket|randstr|csrf|csrfToken|xsrfToken|password|credentials?)["']?\s*[:=]\s*)(["']?)[^"'\s,;}]+\2/gi,`$1$2${c}$2`).replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g,c).replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,c).replace(/[A-Za-z0-9_-]{80,}/g,c)}function g(t){const e=t.replace(/[^a-z0-9]/gi,"").toLowerCase();return e==="apikey"||e==="apikeys"||e==="credentials"||e==="tokenpreview"||e==="credentialpreview"||e==="secretpreview"||e==="authorization"||e==="cookie"||e==="code"||e==="smscode"||e==="verificationcode"||e==="onetimecode"||e==="otp"||e==="invitecode"||e==="sharecode"||e==="ticket"||e==="randstr"||e==="csrf"||e==="csrftoken"||e==="xsrftoken"||e==="devicesecrethash"||e.endsWith("token")||e.endsWith("tokens")||e.endsWith("secret")||e.endsWith("secrets")||e.endsWith("password")||e.endsWith("signature")}function d(t,e=[],o="",i=0,s=new WeakSet){if(o&&g(o))return c;if(typeof t=="string")return f(t,e);if(t instanceof Error){const r=new Error(f(t.message,e));return r.name=t.name,r.stack=t.stack?f(t.stack,e):void 0,r}if(t&&typeof t=="object"){if(i>=10)return"[max-depth]";if(s.has(t))return"[circular]";if(s.add(t),Array.isArray(t)){const n=t.map(a=>d(a,e,"",i+1,s));return s.delete(t),n}const r=Object.fromEntries(Object.entries(t).map(([n,a])=>[n,d(a,e,n,i+1,s)]));return s.delete(t),r}return t}function h(t,e){const o=new Set,i=/(token|secret|password|api[_-]?key|authorization|cookie|signature)/i;for(const[r,n]of Object.entries(t))n&&n.length>=6&&i.test(r)&&o.add(n);const s=(r,n="",a=0)=>{if(!(a>8||r==null)){if(typeof r=="string"){r.length>=6&&i.test(n)&&o.add(r);return}if(typeof r=="object")for(const[k,l]of Object.entries(r))s(l,k,a+1)}};return s(e),[...o]}let p=!1;function T(t){if(!p){p=!0;for(const e of["log","info","warn","error"]){const o=console[e].bind(console);console[e]=(...i)=>{o(...i.map(s=>d(s,t)))}}}}export{h as collectKnownLogSecrets,T as installCliLogRedaction,f as redactCliLogText,d as sanitizeCliLogValue};
@@ -49,6 +49,7 @@ export declare class CliRelayClient {
49
49
  connect(): void;
50
50
  disconnect(): void;
51
51
  sendRes(res: ResFrame): void;
52
+ sendResAndWait(res: ResFrame): Promise<void>;
52
53
  sendEvent(event: EventFrame): void;
53
54
  sendReq(req: ReqFrame, timeoutMs?: number): Promise<ResFrame>;
54
55
  sendBufferedEvent(event: EventFrame, timeoutMs?: number): Promise<ResFrame | void>;
@@ -1 +1 @@
1
- import R from"ws";import{generateTraceId as d}from"../log-reporter.js";import{recordPersonalSyncLocalMetric as c}from"../personal-sync/metrics.js";class w extends Error{rejection;constructor(e){super(e.message||e.code),this.rejection=e,this.name="SessionSyncRejectedError"}}const y=9e4,S=3e4,P=1e3,A=.2,I=2e4,_=1e4,C=15e3,b=1e3,k=new Set([4001,4003,4004,4009,4402,4403,4406]),v=["upgrade_required","epoch_mismatch","token_revoked","ownership_transferred","invalid_identity"];function N(l,e){if(k.has(l))return!0;const t=e.trim().toLowerCase();return v.some(s=>t.startsWith(s))}class M{options;ws=null;state="disconnected";reconnectAttempts=0;heartbeatTimer=null;reconnectTimer=null;connectTimer=null;pingTimer=null;pongTimer=null;disposed=!1;closeTrigger;lastSocketError;sendBuffer=new Map;pendingAcks=new Map;pendingRequests=new Map;constructor(e){this.options=e}connect(){if(this.disposed)return;this.ws&&this.cleanup(!1),this.state="connecting";const e=this.options.serverUrl.includes("?")?"&":"?",t=this.options.cliVersion?`&v=${encodeURIComponent(this.options.cliVersion)}`:"",s=this.options.agentList?.length?`&agents=${encodeURIComponent(this.options.agentList.join(","))}`:"",i=this.options.autoUpgradePolicy?`&upgradePolicy=${encodeURIComponent(this.options.autoUpgradePolicy)}`:"",r=this.options.sessionSyncProtocolVersion?`&syncProtocol=${this.options.sessionSyncProtocolVersion}`:"",o=this.options.sessionSyncCapabilities?.length?`&syncCapabilities=${encodeURIComponent(this.options.sessionSyncCapabilities.join(","))}`:"",u=this.options.daemonInstallationId?`&daemonInstallationId=${encodeURIComponent(this.options.daemonInstallationId)}`:"",g=this.options.dataEpoch?`&dataEpoch=${encodeURIComponent(this.options.dataEpoch)}`:"",f=`${this.options.serverUrl}${e}token=${encodeURIComponent(this.options.machineToken)}${t}${s}${i}${r}${o}${u}${g}`,n=new R(f);this.ws=n,this.connectTimer=setTimeout(()=>{this.ws===n&&this.state==="connecting"&&(this.closeTrigger="connect-timeout",n.terminate())},C),n.on("open",()=>{this.ws===n&&(this.clearConnectTimer(),this.state="connected",this.reconnectAttempts=0,this.startHeartbeat(),this.startPing(n),this.flushSendBuffer(),this.options.onConnected?.())}),n.on("pong",()=>{this.ws===n&&(this.clearPongTimer(),this.resetHeartbeat())}),n.on("message",a=>{if(this.ws!==n)return;let h;try{h=JSON.parse(a.toString())}catch{return}this.handleFrame(h)}),n.on("close",(a,h)=>{if(this.ws!==n)return;const T=this.state,m=h.toString(),p=N(a,m),E={code:a,reason:m,error:this.lastSocketError,phase:T,trigger:this.closeTrigger??"socket-close",reconnectAttempt:this.reconnectAttempts+1};p&&(this.disposed=!0,c("permanentStops")),this.cleanup(p),this.options.onDisconnected?.(E),p||this.scheduleReconnect()}),n.on("error",a=>{this.lastSocketError=a.message,this.closeTrigger="socket-error",n.close()})}disconnect(){this.disposed=!0,this.cleanup(!0)}sendRes(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=d()),this.ws.send(JSON.stringify(e)))}sendEvent(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=d()),c("relayEventsSent"),this.ws.send(JSON.stringify(e)))}sendReq(e,t=6e4){return this.state!=="connected"||!this.ws?Promise.reject(new Error("Relay is not connected")):(e.traceId||(e.traceId=d()),c("relayRequestsSent"),new Promise((s,i)=>{const r=this.pendingRequests.get(e.id);r&&(clearTimeout(r.timer),r.reject(new Error("Superseded by a newer relay request")));const o=setTimeout(()=>{this.pendingRequests.delete(e.id),i(new Error("Relay request timed out"))},t);this.pendingRequests.set(e.id,{resolve:s,reject:i,timer:o}),this.ws?.send(JSON.stringify(e))}))}sendBufferedEvent(e,t=12e4){if(e.traceId||(e.traceId=d()),!e.id)return this.sendEvent(e),Promise.resolve();const s=e.id;return c("relayEventsSent"),new Promise((i,r)=>{const o=this.pendingAcks.get(s);o&&(clearTimeout(o.timer),o.reject(new Error("Superseded by a newer buffered event")));const u=setTimeout(()=>{this.pendingAcks.delete(s),this.sendBuffer.delete(s),r(new Error("Relay event acknowledgement timed out"))},t);this.pendingAcks.set(s,{resolve:i,reject:r,timer:u}),this.bufferEvent(e)})}sendAgentEvent(e){this.sendBufferedEvent(e).catch(()=>{})}getState(){return this.state}handleFrame(e){if(c("relayFramesReceived"),e.type==="res"&&e.rejection&&c("structuredRejections"),e.type==="event"&&e.event==="tick"){this.resetHeartbeat();return}if(e.type==="res"&&e.id){this.sendBuffer.delete(e.id);const t=this.pendingAcks.get(e.id);if(t)if(this.pendingAcks.delete(e.id),clearTimeout(t.timer),e.ok)t.resolve(e);else{const i=e.rejection;t.reject(i?new w(i):new Error(e.error??"Relay event failed"))}const s=this.pendingRequests.get(e.id);s&&(this.pendingRequests.delete(e.id),clearTimeout(s.timer),s.resolve(e));return}e.type==="req"&&this.options.onReq?.(e)}flushSendBuffer(){if(!(this.sendBuffer.size===0||!this.ws))for(const e of this.sendBuffer.values()){if(this.state!=="connected"||!this.ws)break;this.ws.send(JSON.stringify(e))}}bufferEvent(e){if(!e.id){this.sendEvent(e);return}if(this.sendBuffer.set(e.id,e),this.sendBuffer.size>b){const t=this.sendBuffer.keys().next().value;if(t){this.sendBuffer.delete(t);const s=this.pendingAcks.get(t);s&&(this.pendingAcks.delete(t),clearTimeout(s.timer),s.reject(new Error("Relay send buffer overflow")))}}this.state==="connected"&&this.ws&&this.ws.send(JSON.stringify(e))}clearConnectTimer(){this.connectTimer!==null&&(clearTimeout(this.connectTimer),this.connectTimer=null)}startPing(e){this.clearPing(),this.pingTimer=setInterval(()=>{if(!(this.ws!==e||this.state!=="connected")){this.pongTimer=setTimeout(()=>{this.closeTrigger="pong-timeout",e.close()},_);try{e.ping()}catch{this.clearPongTimer(),this.closeTrigger="socket-error",e.close()}}},I)}clearPing(){this.pingTimer!==null&&(clearInterval(this.pingTimer),this.pingTimer=null),this.clearPongTimer()}clearPongTimer(){this.pongTimer!==null&&(clearTimeout(this.pongTimer),this.pongTimer=null)}startHeartbeat(){this.clearHeartbeat(),this.heartbeatTimer=setTimeout(()=>{this.closeTrigger="heartbeat-timeout",this.ws?.close()},y)}resetHeartbeat(){this.startHeartbeat()}clearHeartbeat(){this.heartbeatTimer!==null&&(clearTimeout(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.disposed)return;const e=Math.min(P*Math.pow(2,this.reconnectAttempts),S),t=e*A*(Math.random()*2-1),s=Math.round(e+t);this.reconnectAttempts++,c("relayReconnects"),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},s)}cleanup(e){if(this.state="disconnected",this.closeTrigger=void 0,this.lastSocketError=void 0,this.clearConnectTimer(),this.clearHeartbeat(),this.clearPing(),this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){const t=this.ws;this.ws=null,t.removeAllListeners();try{t.close()}catch{}}for(const[t,s]of this.pendingRequests)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingRequests.delete(t);if(e)for(const[t,s]of this.pendingAcks)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingAcks.delete(t)}}export{M as CliRelayClient,w as SessionSyncRejectedError,N as isPermanentRelayClose};
1
+ import w from"ws";import{generateTraceId as h}from"../log-reporter.js";import{recordPersonalSyncLocalMetric as c}from"../personal-sync/metrics.js";class R extends Error{rejection;constructor(e){super(e.message||e.code),this.rejection=e,this.name="SessionSyncRejectedError"}}const y=9e4,S=3e4,P=1e3,I=.2,A=2e4,_=1e4,C=15e3,b=1e3,k=new Set([4001,4003,4004,4009,4402,4403,4406]),v=["upgrade_required","epoch_mismatch","token_revoked","ownership_transferred","invalid_identity"];function N(l,e){if(k.has(l))return!0;const t=e.trim().toLowerCase();return v.some(s=>t.startsWith(s))}class j{options;ws=null;state="disconnected";reconnectAttempts=0;heartbeatTimer=null;reconnectTimer=null;connectTimer=null;pingTimer=null;pongTimer=null;disposed=!1;closeTrigger;lastSocketError;sendBuffer=new Map;pendingAcks=new Map;pendingRequests=new Map;constructor(e){this.options=e}connect(){if(this.disposed)return;this.ws&&this.cleanup(!1),this.state="connecting";const e=this.options.serverUrl.includes("?")?"&":"?",t=this.options.cliVersion?`&v=${encodeURIComponent(this.options.cliVersion)}`:"",s=this.options.agentList?.length?`&agents=${encodeURIComponent(this.options.agentList.join(","))}`:"",i=this.options.autoUpgradePolicy?`&upgradePolicy=${encodeURIComponent(this.options.autoUpgradePolicy)}`:"",r=this.options.sessionSyncProtocolVersion?`&syncProtocol=${this.options.sessionSyncProtocolVersion}`:"",o=this.options.sessionSyncCapabilities?.length?`&syncCapabilities=${encodeURIComponent(this.options.sessionSyncCapabilities.join(","))}`:"",u=this.options.daemonInstallationId?`&daemonInstallationId=${encodeURIComponent(this.options.daemonInstallationId)}`:"",g=this.options.dataEpoch?`&dataEpoch=${encodeURIComponent(this.options.dataEpoch)}`:"",f=`${this.options.serverUrl}${e}token=${encodeURIComponent(this.options.machineToken)}${t}${s}${i}${r}${o}${u}${g}`,n=new w(f);this.ws=n,this.connectTimer=setTimeout(()=>{this.ws===n&&this.state==="connecting"&&(this.closeTrigger="connect-timeout",n.terminate())},C),n.on("open",()=>{this.ws===n&&(this.clearConnectTimer(),this.state="connected",this.reconnectAttempts=0,this.startHeartbeat(),this.startPing(n),this.flushSendBuffer(),this.options.onConnected?.())}),n.on("pong",()=>{this.ws===n&&(this.clearPongTimer(),this.resetHeartbeat())}),n.on("message",a=>{if(this.ws!==n)return;let d;try{d=JSON.parse(a.toString())}catch{return}this.handleFrame(d)}),n.on("close",(a,d)=>{if(this.ws!==n)return;const T=this.state,m=d.toString(),p=N(a,m),E={code:a,reason:m,error:this.lastSocketError,phase:T,trigger:this.closeTrigger??"socket-close",reconnectAttempt:this.reconnectAttempts+1};p&&(this.disposed=!0,c("permanentStops")),this.cleanup(p),this.options.onDisconnected?.(E),p||this.scheduleReconnect()}),n.on("error",a=>{this.lastSocketError=a.message,this.closeTrigger="socket-error",n.close()})}disconnect(){this.disposed=!0,this.cleanup(!0)}sendRes(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=h()),this.ws.send(JSON.stringify(e)))}sendResAndWait(e){if(this.state!=="connected"||!this.ws)return Promise.reject(new Error("Relay is not connected"));e.traceId||(e.traceId=h());const t=this.ws;return new Promise((s,i)=>{t.send(JSON.stringify(e),r=>{r?i(r):s()})})}sendEvent(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=h()),c("relayEventsSent"),this.ws.send(JSON.stringify(e)))}sendReq(e,t=6e4){return this.state!=="connected"||!this.ws?Promise.reject(new Error("Relay is not connected")):(e.traceId||(e.traceId=h()),c("relayRequestsSent"),new Promise((s,i)=>{const r=this.pendingRequests.get(e.id);r&&(clearTimeout(r.timer),r.reject(new Error("Superseded by a newer relay request")));const o=setTimeout(()=>{this.pendingRequests.delete(e.id),i(new Error("Relay request timed out"))},t);this.pendingRequests.set(e.id,{resolve:s,reject:i,timer:o}),this.ws?.send(JSON.stringify(e))}))}sendBufferedEvent(e,t=12e4){if(e.traceId||(e.traceId=h()),!e.id)return this.sendEvent(e),Promise.resolve();const s=e.id;return c("relayEventsSent"),new Promise((i,r)=>{const o=this.pendingAcks.get(s);o&&(clearTimeout(o.timer),o.reject(new Error("Superseded by a newer buffered event")));const u=setTimeout(()=>{this.pendingAcks.delete(s),this.sendBuffer.delete(s),r(new Error("Relay event acknowledgement timed out"))},t);this.pendingAcks.set(s,{resolve:i,reject:r,timer:u}),this.bufferEvent(e)})}sendAgentEvent(e){this.sendBufferedEvent(e).catch(()=>{})}getState(){return this.state}handleFrame(e){if(c("relayFramesReceived"),e.type==="res"&&e.rejection&&c("structuredRejections"),e.type==="event"&&e.event==="tick"){this.resetHeartbeat();return}if(e.type==="res"&&e.id){this.sendBuffer.delete(e.id);const t=this.pendingAcks.get(e.id);if(t)if(this.pendingAcks.delete(e.id),clearTimeout(t.timer),e.ok)t.resolve(e);else{const i=e.rejection;t.reject(i?new R(i):new Error(e.error??"Relay event failed"))}const s=this.pendingRequests.get(e.id);s&&(this.pendingRequests.delete(e.id),clearTimeout(s.timer),s.resolve(e));return}e.type==="req"&&this.options.onReq?.(e)}flushSendBuffer(){if(!(this.sendBuffer.size===0||!this.ws))for(const e of this.sendBuffer.values()){if(this.state!=="connected"||!this.ws)break;this.ws.send(JSON.stringify(e))}}bufferEvent(e){if(!e.id){this.sendEvent(e);return}if(this.sendBuffer.set(e.id,e),this.sendBuffer.size>b){const t=this.sendBuffer.keys().next().value;if(t){this.sendBuffer.delete(t);const s=this.pendingAcks.get(t);s&&(this.pendingAcks.delete(t),clearTimeout(s.timer),s.reject(new Error("Relay send buffer overflow")))}}this.state==="connected"&&this.ws&&this.ws.send(JSON.stringify(e))}clearConnectTimer(){this.connectTimer!==null&&(clearTimeout(this.connectTimer),this.connectTimer=null)}startPing(e){this.clearPing(),this.pingTimer=setInterval(()=>{if(!(this.ws!==e||this.state!=="connected")){this.pongTimer=setTimeout(()=>{this.closeTrigger="pong-timeout",e.close()},_);try{e.ping()}catch{this.clearPongTimer(),this.closeTrigger="socket-error",e.close()}}},A)}clearPing(){this.pingTimer!==null&&(clearInterval(this.pingTimer),this.pingTimer=null),this.clearPongTimer()}clearPongTimer(){this.pongTimer!==null&&(clearTimeout(this.pongTimer),this.pongTimer=null)}startHeartbeat(){this.clearHeartbeat(),this.heartbeatTimer=setTimeout(()=>{this.closeTrigger="heartbeat-timeout",this.ws?.close()},y)}resetHeartbeat(){this.startHeartbeat()}clearHeartbeat(){this.heartbeatTimer!==null&&(clearTimeout(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.disposed)return;const e=Math.min(P*Math.pow(2,this.reconnectAttempts),S),t=e*I*(Math.random()*2-1),s=Math.round(e+t);this.reconnectAttempts++,c("relayReconnects"),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},s)}cleanup(e){if(this.state="disconnected",this.closeTrigger=void 0,this.lastSocketError=void 0,this.clearConnectTimer(),this.clearHeartbeat(),this.clearPing(),this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){const t=this.ws;this.ws=null,t.removeAllListeners();try{t.close()}catch{}}for(const[t,s]of this.pendingRequests)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingRequests.delete(t);if(e)for(const[t,s]of this.pendingAcks)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingAcks.delete(t)}}export{j as CliRelayClient,R as SessionSyncRejectedError,N as isPermanentRelayClose};
@@ -1,4 +1,5 @@
1
- import type { RoomMessageView } from '@shennian/wire';
1
+ import { type AgentType, type ManagedAgentPermissionConfirmation, type ManagedAgentPermissionPolicy, type ManagedAgentRuntimeProtection, type RoomMessageView, type SessionMode } from '@shennian/wire';
2
+ import { type CompiledManagedAgentPolicy } from '../security/managed-agent-policy.js';
2
3
  import type { ManagedRoomEnqueueParams, ManagedRoomQueueContext, PreparedManagedRoomTurn } from '../session/queue.js';
3
4
  import type { RoomHostSessionObservationStore } from './host-session-observations.js';
4
5
  import { type RoomSessionRegistration, type RoomSessionRegistry } from './session-registry.js';
@@ -19,6 +20,9 @@ export type RoomActivationContext = {
19
20
  agentsByIdentityId?: Record<string, {
20
21
  displayName: string;
21
22
  description: string;
23
+ sessionMode: SessionMode;
24
+ permissionPolicy: ManagedAgentPermissionPolicy;
25
+ permissionConfirmation: ManagedAgentPermissionConfirmation | null;
22
26
  }>;
23
27
  agentSystemPromptsByIdentityId?: Record<string, string>;
24
28
  /** @deprecated ignored; retained only for registry/test compatibility during migration */
@@ -35,8 +39,26 @@ export declare class RoomActivationService {
35
39
  audit?: Pick<RoomActivationAudit, 'record'>;
36
40
  enqueueConversationBatch: (inputs: RoomConversationQueueInput[]) => Promise<void>;
37
41
  loadContext?: (registration: RoomSessionRegistration) => Promise<RoomActivationContext>;
42
+ resolveManagedAgentPolicy?: (input: {
43
+ registration: RoomSessionRegistration;
44
+ permissionPolicy: ManagedAgentPermissionPolicy;
45
+ permissionConfirmation: ManagedAgentPermissionConfirmation | null;
46
+ workDir: string;
47
+ }) => Promise<CompiledManagedAgentPolicy>;
38
48
  });
39
49
  processMessages(roomId: string, messages: RoomMessageView[], registrations: RoomSessionRegistration[], context?: RoomActivationContext): Promise<void>;
40
50
  prepareConversationTurn(contexts: ManagedRoomQueueContext[]): Promise<PreparedManagedRoomTurn>;
41
51
  private audit;
42
52
  }
53
+ export declare function resolveManagedAgentPolicy(input: {
54
+ registration: RoomSessionRegistration;
55
+ permissionPolicy: ManagedAgentPermissionPolicy;
56
+ permissionConfirmation: ManagedAgentPermissionConfirmation | null;
57
+ workDir: string;
58
+ }): Promise<CompiledManagedAgentPolicy>;
59
+ export declare function managedAgentPermissionConfirmationMatches(input: {
60
+ confirmation: ManagedAgentPermissionConfirmation | null;
61
+ policy: ManagedAgentPermissionPolicy;
62
+ runtimeAdapter: AgentType;
63
+ protection: ManagedAgentRuntimeProtection;
64
+ }): boolean;
@@ -1,8 +1,8 @@
1
- import{selectRoomActivationTargets as C}from"./activation-policy.js";import{composeManagedRoomPrompt as $}from"./managed-prompt-composer.js";const S=4,x=6e4,q=6,A=5*6e4;class z{deps;latestContexts=new Map;constructor(t){this.deps=t}async processMessages(t,i,o,a={roomName:"\u5F53\u524D\u7FA4\u804A"}){const c=[],d=[],y=new Map,h=new Set,w=[...i].sort((e,r)=>N(e.sequence,r.sequence));for(const e of o){const r=this.deps.registry.getByRegistryKey(e.registryKey);if(!r?.binding||r.binding.roomId!==t||r.binding.activationQueue.length===0&&!r.binding.activeActivation||(h.add(r.registryKey),r.binding.activationQueue.length===0))continue;const u=this.deps.observations.getBySource(r);if(!u?.workspacePath||u.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(r.registryKey,a);for(const n of r.binding.activationQueue)c.push({registration:r,input:{params:{sessionId:r.nianSessionId,text:n.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:_(r.runtimeAdapter),workDir:u.workspacePath,agentSessionId:r.sourceSessionKey,queueMessageId:`room-activation-${r.binding.bindingId}-${n.causeMessageId}`,clientMessageId:`room-activation-${r.binding.bindingId}-${n.causeMessageId}`},context:{registryKey:r.registryKey,causeMessageId:n.causeMessageId,rootCauseMessageId:n.rootCauseMessageId,sequence:n.sequence,hopCount:n.hopCount}}})}for(const e of w){const r=new Set(C(e,o).map(u=>u.registration.registryKey));for(const u of o){const n=this.deps.registry.getByRegistryKey(u.registryKey);if(!n?.binding||n.binding.roomId!==t||n.binding.status!=="active"&&n.binding.status!=="paused"||BigInt(e.sequence)<=BigInt(n.binding.lastActivatedSequence))continue;const p=y.get(n.registryKey)??[...n.binding.mentionContext??[]];if(y.set(n.registryKey,p),r.has(n.registryKey)){const g=(e.activationCause?.hopCount??0)+(e.author.principalType==="agent"?1:0),m=e.activationCause?.rootCauseMessageId??e.id;if(g>S)this.audit(n,e.id,m,g,"suppressed","max_hops");else{const b=this.deps.registry.claimActivation({registryKey:n.registryKey,windowMs:x,maxActivations:q,pauseMs:A,now:this.deps.now?.()});if(!b.allowed){b.newlyPaused&&this.audit(n,e.id,m,g,"suppressed","rate_limited"),d.push({registryKey:n.registryKey,sequence:e.sequence});continue}const l=this.deps.observations.getBySource(n);if(!l?.workspacePath||l.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const f=I([e]),M=n.binding.activationMode==="mention_only"?P([...p.map(K=>K.deliveryText),f]):f;this.latestContexts.set(n.registryKey,a),c.push({registration:n,input:{params:{sessionId:n.nianSessionId,text:M,agentType:_(n.runtimeAdapter),workDir:l.workspacePath,agentSessionId:n.sourceSessionKey,queueMessageId:`room-activation-${n.binding.bindingId}-${e.id}`,clientMessageId:`room-activation-${n.binding.bindingId}-${e.id}`},context:{registryKey:n.registryKey,causeMessageId:e.id,rootCauseMessageId:m,sequence:e.sequence,hopCount:g}}}),n.binding.activationMode==="mention_only"&&y.set(n.registryKey,[])}}else n.binding.activationMode==="mention_only"&&e.kind!=="system"&&e.author.membershipId!==n.binding.agentMembershipId&&(p.some(g=>g.sequence===e.sequence)||p.push({sequence:e.sequence,deliveryText:I([e])}));d.push({registryKey:n.registryKey,sequence:e.sequence})}}await this.deps.enqueueConversationBatch(c.map(e=>e.input));for(const e of h)this.deps.registry.retireLegacyActivationState(e,this.deps.now?.()??new Date);for(const[e,r]of y)this.deps.registry.replaceMentionContext(e,r,this.deps.now?.()??new Date);for(const e of c)this.audit(e.registration,e.input.context.causeMessageId,e.input.context.rootCauseMessageId,e.input.context.hopCount,"queued","conversation_queue");for(const e of d)this.deps.registry.advanceCursor({registryKey:e.registryKey,cursor:"activated",sequence:e.sequence})}async prepareConversationTurn(t){const i=t.at(-1);if(!i)throw new Error("managed_room_queue_context_missing");const o=this.deps.registry.getByRegistryKey(i.registryKey);if(!o?.binding||o.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const a=this.deps.loadContext?await this.deps.loadContext(o):this.latestContexts.get(i.registryKey);if(!a)throw new Error("managed_room_prompt_context_unavailable");const c=D(a,o),d=this.deps.issueManagedRoomContext(o.registryKey,i.causeMessageId);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(d))throw new Error("managed_room_context_unavailable");return this.audit(o,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"activated","conversation_queue_dispatch"),{systemPrompt:c.systemPrompt,roomContextRegistryKey:o.registryKey,managedRoomContextToken:d}}audit(t,i,o,a,c,d){t.binding&&this.deps.audit?.record({roomId:t.binding.roomId,bindingId:t.binding.bindingId,causeMessageId:i,rootCauseMessageId:o,hopCount:a,outcome:c,reason:d,at:this.deps.now?.()})}}function I(s){const t=s.map(i=>{const o=i.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",a=i.attachments.length>0?`
2
- ${i.attachments.map(k).join(`
3
- `)}`:"";return{speaker:i.author.displayName,body:`${o}${a}`}});return v(t)}function k(s){return`\u9644\u4EF6\uFF1A${`${B(s.name)} \xB7 ${T(s.byteSize)}`}\uFF5C${s.mimeType}\uFF5CattachmentId=${s.attachmentId}`}function B(s){return String(s||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function T(s){const t=Number(s);if(!Number.isFinite(t)||t<0)return`${s} B`;if(t<1024)return`${t} B`;const i=["KB","MB","GB","TB"];let o=t,a=-1;do o/=1024,a+=1;while(o>=1024&&a<i.length-1);const c=o>=100?0:o>=10?1:2;return`${o.toFixed(c).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${i[a]}`}function P(s){return v(s.map(R))}function R(s){const t=s.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const i=s.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(i)return{speaker:i[1],body:i[2]};const o=s.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(o)return{speaker:o[1],body:o[2]};const a=s.match(/^([^\n]+)\n([\s\S]*)$/);return a?{speaker:a[1],body:a[2]}:{speaker:"\u7FA4\u6210\u5458",body:s.trim()}}function v(s){if(s.length===1){const[t]=s;return`[\u7FA4\u6D88\u606F] <${t.speaker}>
4
- ${t.body}`}return`[\u7FA4\u6D88\u606F]
5
- ${s.map(t=>`<${t.speaker}>
6
- ${t.body}`).join(`
1
+ import{isAvailableBuiltinAgentType as A}from"@shennian/wire";import{getBuiltinAgentVersion as x,resolveBuiltinCommand as P}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as S,resolveCustomRuntimeExecutableFingerprint as K}from"../agents/custom-managed-runtime.js";import{loadConfig as $}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as k}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as q}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as B}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as T}from"./activation-policy.js";import{composeManagedRoomPrompt as E}from"./managed-prompt-composer.js";const R=4,D=6e4,N=6,F=5*6e4;class oe{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,o,t,a={roomName:"\u5F53\u524D\u7FA4\u804A"}){const c=[],d=[],g=new Map,p=new Set,u=[...o].sort((i,r)=>G(i.sequence,r.sequence));for(const i of t){const r=this.deps.registry.getByRegistryKey(i.registryKey);if(!r?.binding||r.binding.roomId!==e||r.binding.status!=="active"&&r.binding.status!=="paused"&&r.binding.activationQueue.length===0)continue;const m=a.agentsByIdentityId?.[r.binding.agentIdentityId];if(m&&m.sessionMode!=="standard")throw new Error("managed_agent_manager_mode_unsupported")}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||(p.add(r.registryKey),r.binding.activationQueue.length===0))continue;const m=this.deps.observations.getBySource(r);if(!m?.workspacePath||m.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(r.registryKey,a);for(const s of r.binding.activationQueue)c.push({registration:r,input:{params:{sessionId:r.nianSessionId,text:s.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:f(r.runtimeAdapter),sessionMode:"standard",workDir:m.workspacePath,agentSessionId:r.sourceSessionKey,queueMessageId:`room-activation-${r.binding.bindingId}-${s.causeMessageId}`,clientMessageId:`room-activation-${r.binding.bindingId}-${s.causeMessageId}`},context:{registryKey:r.registryKey,causeMessageId:s.causeMessageId,rootCauseMessageId:s.rootCauseMessageId,sequence:s.sequence,hopCount:s.hopCount}}})}for(const i of u){const r=new Set(T(i,t).map(m=>m.registration.registryKey));for(const m of t){const s=this.deps.registry.getByRegistryKey(m.registryKey);if(!s?.binding||s.binding.roomId!==e||s.binding.status!=="active"&&s.binding.status!=="paused"||BigInt(i.sequence)<=BigInt(s.binding.lastActivatedSequence))continue;const l=g.get(s.registryKey)??[...s.binding.mentionContext??[]];if(g.set(s.registryKey,l),r.has(s.registryKey)){const y=(i.activationCause?.hopCount??0)+(i.author.principalType==="agent"?1:0),h=i.activationCause?.rootCauseMessageId??i.id;if(y>R)this.audit(s,i.id,h,y,"suppressed","max_hops");else{const v=this.deps.registry.claimActivation({registryKey:s.registryKey,windowMs:D,maxActivations:N,pauseMs:F,now:this.deps.now?.()});if(!v.allowed){v.newlyPaused&&this.audit(s,i.id,h,y,"suppressed","rate_limited"),d.push({registryKey:s.registryKey,sequence:i.sequence});continue}const b=this.deps.observations.getBySource(s);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const I=w([i]),C=s.binding.activationMode==="mention_only"?L([...l.map(M=>M.deliveryText),I]):I;this.latestContexts.set(s.registryKey,a),c.push({registration:s,input:{params:{sessionId:s.nianSessionId,text:C,agentType:f(s.runtimeAdapter),sessionMode:"standard",workDir:b.workspacePath,agentSessionId:s.sourceSessionKey,queueMessageId:`room-activation-${s.binding.bindingId}-${i.id}`,clientMessageId:`room-activation-${s.binding.bindingId}-${i.id}`},context:{registryKey:s.registryKey,causeMessageId:i.id,rootCauseMessageId:h,sequence:i.sequence,hopCount:y}}}),s.binding.activationMode==="mention_only"&&g.set(s.registryKey,[])}}else s.binding.activationMode==="mention_only"&&i.kind!=="system"&&i.author.membershipId!==s.binding.agentMembershipId&&(l.some(y=>y.sequence===i.sequence)||l.push({sequence:i.sequence,deliveryText:w([i])}));d.push({registryKey:s.registryKey,sequence:i.sequence})}}await this.deps.enqueueConversationBatch(c.map(i=>i.input));for(const i of p)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 c)this.audit(i.registration,i.input.context.causeMessageId,i.input.context.rootCauseMessageId,i.input.context.hopCount,"queued","conversation_queue");for(const i of d)this.deps.registry.advanceCursor({registryKey:i.registryKey,cursor:"activated",sequence:i.sequence})}async prepareConversationTurn(e){const o=e.at(-1);if(!o)throw new Error("managed_room_queue_context_missing");const t=this.deps.registry.getByRegistryKey(o.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(o.registryKey);if(!a)throw new Error("managed_room_prompt_context_unavailable");const c=a.agentsByIdentityId?.[t.binding.agentIdentityId];if(!c?.permissionPolicy)throw new Error("managed_agent_policy_missing");if(c.sessionMode!=="standard")throw new Error("managed_agent_manager_mode_unsupported");const d=this.deps.observations.getBySource(t);if(!d?.workspacePath||d.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const g=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:t,permissionPolicy:c.permissionPolicy,permissionConfirmation:c.permissionConfirmation,workDir:d.workspacePath}):await j({registration:t,permissionPolicy:c.permissionPolicy,permissionConfirmation:c.permissionConfirmation,workDir:d.workspacePath}),p=Q(a,t,g),u=this.deps.issueManagedRoomContext(t.registryKey,o.causeMessageId);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(u))throw new Error("managed_room_context_unavailable");return this.audit(t,o.causeMessageId,o.rootCauseMessageId,o.hopCount,"activated","conversation_queue_dispatch"),{systemPrompt:p.systemPrompt,roomContextRegistryKey:t.registryKey,managedRoomContextToken:u,managedAgentPolicy:g,managedRoomPromptSnapshot:p.snapshot}}audit(e,o,t,a,c,d){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:o,rootCauseMessageId:t,hopCount:a,outcome:c,reason:d,at:this.deps.now?.()})}}function w(n){const e=n.map(o=>{const t=o.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",a=o.attachments.length>0?`
2
+ ${o.attachments.map(W).join(`
3
+ `)}`:"";return{speaker:o.author.displayName,body:`${t}${a}`}});return _(e)}function W(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 o=["KB","MB","GB","TB"];let t=e,a=-1;do t/=1024,a+=1;while(t>=1024&&a<o.length-1);const c=t>=100?0:t>=10?1:2;return`${t.toFixed(c).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${o[a]}`}function L(n){return _(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 o=n.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(o)return{speaker:o[1],body:o[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 _(n){if(n.length===1){const[e]=n;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
4
+ ${e.body}`}return`[\u7FA4\u6D88\u606F]
5
+ ${n.map(e=>`<${e.speaker}>
6
+ ${e.body}`).join(`
7
7
 
8
- `)}`}function D(s,t){const i=t.binding?s.agentsByIdentityId?.[t.binding.agentIdentityId]:void 0;if(!t.binding||!i)throw new Error("managed_room_prompt_incomplete");return $({agent:{displayName:i.displayName,description:i.description,systemPrompt:s.agentSystemPromptsByIdentityId?.[t.binding.agentIdentityId]},room:{name:s.roomName,activationMode:t.binding.activationMode,members:s.members}})}function _(s){return s==="claude_code"?"claude":s}function N(s,t){const i=BigInt(s),o=BigInt(t);return i<o?-1:i>o?1:0}export{z as RoomActivationService};
8
+ `)}`}function Q(n,e,o){const t=e.binding?n.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!t)throw new Error("managed_room_prompt_incomplete");return E({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:o.requestedPolicy,enforcement:o.enforcement,resolvedWorkDir:o.adapterPolicy.workDir}})}async function j(n){const e=f(n.registration.runtimeAdapter),o=A(e)?P(e):null,t=e.startsWith("custom:")?$().customAgents?.[e.slice(7)]:void 0;t&&S(e,t);const a=o?x(e,o):t?.caps.version,c=o?k(o):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:t?K(t):void 0},d={agentType:e,runtimeVersion:a,platform:c.platform,isWsl:c.isWsl,executableFingerprint:c.executableFingerprint,requireExecutableFingerprint:!0},g=B(d),p=X({confirmation:n.permissionConfirmation,policy:n.permissionPolicy,runtimeAdapter:e,protection:g}),u=await q({...d,policy:n.permissionPolicy,workDir:n.workDir,hostFullAccessConfirmed:p&&n.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:p&&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:o,runtimeAdapter:t,protection:a}=n;return!!(e&&e.policyVersion===o.version&&e.runtimeAdapter===t&&e.runtimeVersion===a.runtimeVersion&&e.platform===a.platform&&e.executableFingerprint===a.executableFingerprint&&e.hostFullAccess===(o.fileAccess==="host-full")&&e.limitedProtection===(a.level!=="hard"))}function f(n){return n==="claude_code"?"claude":n}function G(n,e){const o=BigInt(n),t=BigInt(e);return o<t?-1:o>t?1:0}export{oe as RoomActivationService,X as managedAgentPermissionConfirmationMatches,j as resolveManagedAgentPolicy};
@@ -24,8 +24,11 @@ export declare class RoomAttachmentCache {
24
24
  byteSize: string;
25
25
  sha256: string;
26
26
  };
27
+ signal?: AbortSignal;
27
28
  }): Promise<{
28
29
  path: string;
30
+ name: string;
31
+ mimeType: string;
29
32
  cached: boolean;
30
33
  byteSize: string;
31
34
  sha256: string;
@@ -1,2 +1,2 @@
1
- import u from"node:crypto";import s from"node:fs";import d from"node:path";import{resolveShennianPath as v}from"../config/index.js";import{RoomActionHttpError as N}from"./room-action-client.js";const C=100*1024*1024,D=2*1024*1024*1024,E=720*60*6e4,$=/^[A-Za-z0-9_.:@-]{1,191}$/;class r extends Error{code;constructor(e,n){super(n),this.code=e,this.name="RoomAttachmentCacheError"}}class U{deps;constructor(e){this.deps=e}async materialize(e){if(_(e.roomId,"room_id"),_(e.bindingId,"binding_id"),_(e.attachmentId,"attachment_id"),e.expected&&e.expected.downloadRef!==`shennian-room-attachment:${e.attachmentId}`)throw new r("attachment_reference_mismatch","Attachment reference mismatch.");e.expected&&b(e.expected.byteSize,e.expected.sha256);const n=await this.deps.actionClient.getAttachmentCapability({machineToken:e.machineToken,roomId:e.roomId,bindingId:e.bindingId,attachmentId:e.attachmentId});if(B(n,e.roomId,e.attachmentId),e.expected&&(n.byteSize!==e.expected.byteSize||n.sha256!==e.expected.sha256))throw new r("attachment_metadata_mismatch","Attachment metadata changed unexpectedly.");const a=this.deps.maxAttachmentBytes??C,c=Number(n.byteSize);if(!Number.isSafeInteger(c)||c>a)throw new r("attachment_too_large","Attachment exceeds local limit.");const f=u.createHash("sha256").update(e.contextKey??`${e.bindingId}\0${e.roomId}`,"utf8").digest("hex"),l=u.createHash("sha256").update(e.attachmentId,"utf8").digest("hex"),h=d.join(this.deps.rootDirectory??v("attachment-cache"),f,l);k(h);const o=d.join(h,n.safeName);if(H(o,c,n.sha256))return I(h,n,new Date),this.prune({preservePath:o}),{path:o,cached:!0,byteSize:n.byteSize,sha256:n.sha256};const w=d.join(h,`.download-${process.pid}-${u.randomBytes(8).toString("hex")}`);try{const i=await(this.deps.fetchImpl??fetch)(n.downloadUrl,{redirect:"error",signal:AbortSignal.timeout(6e4)});if(!i.ok||!i.body)throw new r("attachment_download_failed","Attachment download failed.");const m=i.headers.get("content-length");if(m&&Number(m)!==c)throw new r("attachment_size_mismatch","Attachment size mismatch.");const y=s.openSync(w,"wx",384),S=u.createHash("sha256");let A=0;try{const x=i.body.getReader();for(;;){const p=await x.read();if(p.done)break;if(A+=p.value.byteLength,A>c||A>a)throw await x.cancel(),new r("attachment_size_mismatch","Attachment size mismatch.");const g=Buffer.from(p.value);s.writeSync(y,g),S.update(g)}s.fsyncSync(y)}finally{s.closeSync(y)}if(A!==c)throw new r("attachment_size_mismatch","Attachment size mismatch.");if(S.digest("hex")!==n.sha256)throw new r("attachment_hash_mismatch","Attachment hash mismatch.");return s.renameSync(w,o),process.platform!=="win32"&&s.chmodSync(o,384),I(h,n,new Date),this.prune({preservePath:o}),{path:o,cached:!1,byteSize:n.byteSize,sha256:n.sha256}}catch(i){throw i instanceof r||i instanceof N?i:new r("attachment_download_failed","Attachment download failed.")}finally{try{s.unlinkSync(w)}catch{}}}prune(e={}){const n=this.deps.rootDirectory??v("attachment-cache"),a=T(n),c=e.preservePath?d.resolve(e.preservePath):null,f=this.deps.maxCacheAgeMs??E,l=this.deps.maxCacheBytes??D,h=(e.now??new Date).getTime()-f;let o=a.reduce((m,y)=>m+y.byteSize,0),w=0,i=0;for(const m of a.sort((y,S)=>y.lastAccessedAt-S.lastAccessedAt))d.resolve(m.filePath)!==c&&(m.lastAccessedAt>=h&&o<=l||(s.rmSync(m.directory,{recursive:!0,force:!0}),w+=1,i+=m.byteSize,o-=m.byteSize));return{removedEntries:w,removedBytes:i,remainingBytes:o}}}function _(t,e){if(!$.test(t))throw new r(`invalid_${e}`,`Invalid ${e}.`)}function b(t,e){if(!/^(0|[1-9]\d{0,15})$/.test(t)||!/^[a-f0-9]{64}$/.test(e))throw new r("invalid_attachment_metadata","Invalid attachment metadata.")}function B(t,e,n){let a;const c=Date.parse(t.expiresAt);try{a=new URL(t.downloadUrl)}catch{throw new r("invalid_attachment_capability","Invalid attachment capability.")}if(t.roomId!==e||t.attachmentId!==n||t.safeName.length<1||t.safeName.length>255||t.safeName!==t.safeName.trim()||t.safeName==="."||t.safeName===".."||/[\\/\0-\x1f\x7f]/.test(t.safeName)||a.protocol!=="https:"||(a.username||a.password||a.hash)||!Number.isFinite(c)||c<=Date.now()||c>Date.now()+5*6e4)throw new r("invalid_attachment_capability","Invalid attachment capability.");b(t.byteSize,t.sha256)}function k(t){s.mkdirSync(t,{recursive:!0,mode:448}),process.platform!=="win32"&&s.chmodSync(t,448)}function H(t,e,n){try{const a=s.lstatSync(t);return!a.isFile()||a.isSymbolicLink()||a.size!==e?!1:u.createHash("sha256").update(s.readFileSync(t)).digest("hex")===n}catch{return!1}}function I(t,e,n){const a=d.join(t,"metadata.json"),c=`${a}.tmp-${process.pid}-${u.randomBytes(6).toString("hex")}`;try{s.writeFileSync(c,`${JSON.stringify({schemaVersion:1,safeName:e.safeName,mimeType:e.mimeType,byteSize:e.byteSize,sha256:e.sha256,lastAccessedAt:n.toISOString()})}
2
- `,{encoding:"utf8",flag:"wx",mode:384}),s.renameSync(c,a),process.platform!=="win32"&&s.chmodSync(a,384)}finally{try{s.unlinkSync(c)}catch{}}}function T(t){if(!s.existsSync(t))return[];const e=[];for(const n of s.readdirSync(t)){if(!/^[a-f0-9]{64}$/.test(n))continue;const a=d.join(t,n);if(z(a))for(const c of s.readdirSync(a)){if(!/^[a-f0-9]{64}$/.test(c))continue;const f=d.join(a,c);if(!z(f))continue;const l=F(f);if(!l)continue;const h=d.join(f,l.safeName);let o;try{o=s.lstatSync(h)}catch{continue}!o.isFile()||o.isSymbolicLink()||e.push({directory:f,filePath:h,byteSize:o.size,lastAccessedAt:Date.parse(l.lastAccessedAt)})}}return e}function F(t){try{const e=d.join(t,"metadata.json"),n=s.lstatSync(e);if(!n.isFile()||n.isSymbolicLink())return null;const a=JSON.parse(s.readFileSync(e,"utf8"));return a.schemaVersion!==1||typeof a.safeName!="string"||/[\\/\0-\x1f\x7f]/.test(a.safeName)||typeof a.lastAccessedAt!="string"||!Number.isFinite(Date.parse(a.lastAccessedAt))?null:{safeName:a.safeName,lastAccessedAt:a.lastAccessedAt}}catch{return null}}function z(t){try{const e=s.lstatSync(t);return e.isDirectory()&&!e.isSymbolicLink()}catch{return!1}}export{U as RoomAttachmentCache,r as RoomAttachmentCacheError};
1
+ import u from"node:crypto";import s from"node:fs";import h from"node:path";import{resolveShennianPath as v}from"../config/index.js";import{RoomActionHttpError as T}from"./room-action-client.js";const E=100*1024*1024,C=2*1024*1024*1024,D=720*60*6e4,$=/^[A-Za-z0-9_.:@-]{1,191}$/;class i extends Error{code;constructor(e,a){super(a),this.code=e,this.name="RoomAttachmentCacheError"}}class X{deps;constructor(e){this.deps=e}async materialize(e){if(g(e.roomId,"room_id"),g(e.bindingId,"binding_id"),g(e.attachmentId,"attachment_id"),e.expected&&e.expected.downloadRef!==`shennian-room-attachment:${e.attachmentId}`)throw new i("attachment_reference_mismatch","Attachment reference mismatch.");e.expected&&I(e.expected.byteSize,e.expected.sha256);const a=await this.deps.actionClient.getAttachmentCapability({machineToken:e.machineToken,roomId:e.roomId,bindingId:e.bindingId,attachmentId:e.attachmentId,signal:e.signal});if(B(a,e.roomId,e.attachmentId),e.expected&&(a.byteSize!==e.expected.byteSize||a.sha256!==e.expected.sha256))throw new i("attachment_metadata_mismatch","Attachment metadata changed unexpectedly.");const n=this.deps.maxAttachmentBytes??E,o=Number(a.byteSize);if(!Number.isSafeInteger(o)||o>n)throw new i("attachment_too_large","Attachment exceeds local limit.");const d=u.createHash("sha256").update(e.contextKey??`${e.bindingId}\0${e.roomId}`,"utf8").digest("hex"),f=u.createHash("sha256").update(e.attachmentId,"utf8").digest("hex"),m=h.join(this.deps.rootDirectory??v("attachment-cache"),d,f);F(m);const c=h.join(m,a.safeName);if(H(c,o,a.sha256))return z(m,a,new Date),this.prune({preservePath:c}),{path:c,name:a.safeName,mimeType:a.mimeType,cached:!0,byteSize:a.byteSize,sha256:a.sha256};const l=h.join(m,`.download-${process.pid}-${u.randomBytes(8).toString("hex")}`);let y=null;try{y=k(e.signal,6e4);const r=await(this.deps.fetchImpl??fetch)(a.downloadUrl,{redirect:"error",signal:y.signal});if(!r.ok||!r.body)throw new i("attachment_download_failed","Attachment download failed.");const w=r.headers.get("content-length");if(w&&Number(w)!==o)throw new i("attachment_size_mismatch","Attachment size mismatch.");const S=s.openSync(l,"wx",384),_=u.createHash("sha256");let A=0;try{const x=r.body.getReader();for(;;){const p=await x.read();if(p.done)break;if(A+=p.value.byteLength,A>o||A>n)throw await x.cancel(),new i("attachment_size_mismatch","Attachment size mismatch.");const b=Buffer.from(p.value);s.writeSync(S,b),_.update(b)}s.fsyncSync(S)}finally{s.closeSync(S)}if(A!==o)throw new i("attachment_size_mismatch","Attachment size mismatch.");if(_.digest("hex")!==a.sha256)throw new i("attachment_hash_mismatch","Attachment hash mismatch.");return s.renameSync(l,c),process.platform!=="win32"&&s.chmodSync(c,384),z(m,a,new Date),this.prune({preservePath:c}),{path:c,name:a.safeName,mimeType:a.mimeType,cached:!1,byteSize:a.byteSize,sha256:a.sha256}}catch(r){throw r instanceof i||r instanceof T?r:e.signal?.aborted?new i("managed_room_context_revoked","Managed Room context was revoked."):new i("attachment_download_failed","Attachment download failed.")}finally{y?.dispose();try{s.unlinkSync(l)}catch{}}}prune(e={}){const a=this.deps.rootDirectory??v("attachment-cache"),n=L(a),o=e.preservePath?h.resolve(e.preservePath):null,d=this.deps.maxCacheAgeMs??D,f=this.deps.maxCacheBytes??C,m=(e.now??new Date).getTime()-d;let c=n.reduce((r,w)=>r+w.byteSize,0),l=0,y=0;for(const r of n.sort((w,S)=>w.lastAccessedAt-S.lastAccessedAt))h.resolve(r.filePath)!==o&&(r.lastAccessedAt>=m&&c<=f||(s.rmSync(r.directory,{recursive:!0,force:!0}),l+=1,y+=r.byteSize,c-=r.byteSize));return{removedEntries:l,removedBytes:y,remainingBytes:c}}}function k(t,e){const a=new AbortController,n=()=>a.abort();t?.aborted?a.abort():t?.addEventListener("abort",n,{once:!0});const o=setTimeout(()=>a.abort(),e);return o.unref?.(),{signal:a.signal,dispose:()=>{clearTimeout(o),t?.removeEventListener("abort",n)}}}function g(t,e){if(!$.test(t))throw new i(`invalid_${e}`,`Invalid ${e}.`)}function I(t,e){if(!/^(0|[1-9]\d{0,15})$/.test(t)||!/^[a-f0-9]{64}$/.test(e))throw new i("invalid_attachment_metadata","Invalid attachment metadata.")}function B(t,e,a){let n;const o=Date.parse(t.expiresAt);try{n=new URL(t.downloadUrl)}catch{throw new i("invalid_attachment_capability","Invalid attachment capability.")}if(t.roomId!==e||t.attachmentId!==a||t.safeName.length<1||t.safeName.length>255||t.safeName!==t.safeName.trim()||t.safeName==="."||t.safeName===".."||/[\\/\0-\x1f\x7f]/.test(t.safeName)||n.protocol!=="https:"||(n.username||n.password||n.hash)||!Number.isFinite(o)||o<=Date.now()||o>Date.now()+5*6e4)throw new i("invalid_attachment_capability","Invalid attachment capability.");I(t.byteSize,t.sha256)}function F(t){s.mkdirSync(t,{recursive:!0,mode:448}),process.platform!=="win32"&&s.chmodSync(t,448)}function H(t,e,a){try{const n=s.lstatSync(t);return!n.isFile()||n.isSymbolicLink()||n.size!==e?!1:u.createHash("sha256").update(s.readFileSync(t)).digest("hex")===a}catch{return!1}}function z(t,e,a){const n=h.join(t,"metadata.json"),o=`${n}.tmp-${process.pid}-${u.randomBytes(6).toString("hex")}`;try{s.writeFileSync(o,`${JSON.stringify({schemaVersion:1,safeName:e.safeName,mimeType:e.mimeType,byteSize:e.byteSize,sha256:e.sha256,lastAccessedAt:a.toISOString()})}
2
+ `,{encoding:"utf8",flag:"wx",mode:384}),s.renameSync(o,n),process.platform!=="win32"&&s.chmodSync(n,384)}finally{try{s.unlinkSync(o)}catch{}}}function L(t){if(!s.existsSync(t))return[];const e=[];for(const a of s.readdirSync(t)){if(!/^[a-f0-9]{64}$/.test(a))continue;const n=h.join(t,a);if(N(n))for(const o of s.readdirSync(n)){if(!/^[a-f0-9]{64}$/.test(o))continue;const d=h.join(n,o);if(!N(d))continue;const f=j(d);if(!f)continue;const m=h.join(d,f.safeName);let c;try{c=s.lstatSync(m)}catch{continue}!c.isFile()||c.isSymbolicLink()||e.push({directory:d,filePath:m,byteSize:c.size,lastAccessedAt:Date.parse(f.lastAccessedAt)})}}return e}function j(t){try{const e=h.join(t,"metadata.json"),a=s.lstatSync(e);if(!a.isFile()||a.isSymbolicLink())return null;const n=JSON.parse(s.readFileSync(e,"utf8"));return n.schemaVersion!==1||typeof n.safeName!="string"||/[\\/\0-\x1f\x7f]/.test(n.safeName)||typeof n.lastAccessedAt!="string"||!Number.isFinite(Date.parse(n.lastAccessedAt))?null:{safeName:n.safeName,lastAccessedAt:n.lastAccessedAt}}catch{return null}}function N(t){try{const e=s.lstatSync(t);return e.isDirectory()&&!e.isSymbolicLink()}catch{return!1}}export{X as RoomAttachmentCache,i as RoomAttachmentCacheError};
@@ -1 +1 @@
1
- import v from"node:crypto";import i from"node:fs";import e from"node:path";const k=100*1024*1024;class h extends Error{code;constructor(t,o){super(o),this.code=t,this.name="RoomAttachmentUploadError"}}class E{deps;constructor(t){this.deps=t}async upload(t){if(t.files.length<1||t.files.length>10)throw s("room_upload_file_count");const o=i.realpathSync(t.workspacePath),f=[];for(const[_,y]of t.files.entries()){const u=e.resolve(o,y);if(i.lstatSync(u).isSymbolicLink())throw s("room_upload_invalid_file");const n=i.realpathSync(u),c=e.relative(o,n);if(c===".."||c.startsWith(`..${e.sep}`)||e.isAbsolute(c))throw s("room_upload_outside_workspace");const r=i.lstatSync(n);if(!r.isFile()||r.size<1||r.size>k)throw s("room_upload_invalid_file");const d=i.readFileSync(n),g=v.createHash("sha256").update(d).digest("hex"),w=e.basename(n),l=A(w),b=l.startsWith("image/")?"image":l.startsWith("audio/")?"audio":"file",I=v.createHash("sha256").update(`${t.bindingId}\0${t.clientMessageId}\0${_}\0${g}`,"utf8").digest("hex"),x={machineToken:t.machineToken,roomId:t.roomId,bindingId:t.bindingId},p=await this.deps.actionClient.initializeAttachment({...x,clientUploadId:I,name:w,mediaKind:b,mimeType:l,byteSize:d.byteLength,sha256:g});let m;try{m=await(this.deps.fetch??fetch)(p.upload.url,{method:"PUT",headers:p.upload.headers,body:d,redirect:"error",signal:AbortSignal.timeout(12e4)})}catch{throw new h("room_upload_unavailable","Private attachment upload service is unavailable.")}if(!m.ok)throw new h(`room_upload_http_${m.status}`,"Private attachment upload failed.");const S=await this.deps.actionClient.completeAttachment({...x,attachmentId:p.attachment.id});f.push(S.attachment.attachmentId)}return f}}function A(a){const t=e.extname(a).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".heic":"image/heic",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".aac":"audio/aac",".ogg":"audio/ogg",".flac":"audio/flac",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".csv":"text/csv",".doc":"application/msword",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xls":"application/vnd.ms-excel",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".ppt":"application/vnd.ms-powerpoint",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation"}[t]??"application/octet-stream"}function s(a){return new h(a,"Room attachment file is not allowed.")}export{h as RoomAttachmentUploadError,E as RoomAttachmentUploader};
1
+ import x from"node:crypto";import n from"node:fs";import c from"node:path";const k=100*1024*1024;class p extends Error{code;constructor(e,a){super(a),this.code=e,this.name="RoomAttachmentUploadError"}}class R{deps;constructor(e){this.deps=e}async upload(e){if(e.files.length<1||e.files.length>10)throw s("room_upload_file_count");const a=n.realpathSync(e.workspacePath),t=[];for(const[i,r]of e.files.entries()){const l=c.resolve(a,r);if(n.lstatSync(l).isSymbolicLink())throw s("room_upload_invalid_file");const m=n.realpathSync(l),d=c.relative(a,m);if(d===".."||d.startsWith(`..${c.sep}`)||c.isAbsolute(d))throw s("room_upload_outside_workspace");if(L(d))throw s("room_upload_sensitive_file");const h=z(m),_=x.createHash("sha256").update(h).digest("hex"),v=c.basename(m),u=F(v),S=u.startsWith("image/")?"image":u.startsWith("audio/")?"audio":"file",A=x.createHash("sha256").update(`${e.bindingId}\0${e.clientMessageId}\0${i}\0${_}`,"utf8").digest("hex"),y={machineToken:e.machineToken,roomId:e.roomId,bindingId:e.bindingId},f=await this.deps.actionClient.initializeAttachment({...y,clientUploadId:A,name:v,mediaKind:S,mimeType:u,byteSize:h.byteLength,sha256:_});let g;try{g=await(this.deps.fetch??fetch)(f.upload.url,{method:"PUT",headers:f.upload.headers,body:new Uint8Array(h),redirect:"error",signal:AbortSignal.timeout(12e4)})}catch{throw new p("room_upload_unavailable","Private attachment upload service is unavailable.")}if(!g.ok)throw new p(`room_upload_http_${g.status}`,"Private attachment upload failed.");const I=await this.deps.actionClient.completeAttachment({...y,attachmentId:f.attachment.id});t.push(I.attachment.attachmentId)}return t}}function z(o){const e=n.lstatSync(o);if(!b(e))throw s("room_upload_invalid_file");let a=null;try{a=n.openSync(o,n.constants.O_RDONLY|(n.constants.O_NOFOLLOW??0));const t=n.fstatSync(a);if(!b(t)||!w(e,t))throw s("room_upload_file_changed");const i=n.readFileSync(a),r=n.fstatSync(a),l=n.lstatSync(o);if(!w(t,r)||!w(t,l)||t.size!==r.size||t.mtimeMs!==r.mtimeMs||i.byteLength!==t.size)throw s("room_upload_file_changed");return i}catch(t){throw t instanceof p?t:s("room_upload_invalid_file")}finally{a!==null&&n.closeSync(a)}}function b(o){return o.isFile()&&o.nlink===1&&o.size>0&&o.size<=k}function w(o,e){return o.dev===e.dev&&o.ino===e.ino}function L(o){const a=o.replaceAll("\\","/").split("/").filter(Boolean).map(i=>i.toLowerCase()),t=a.at(-1)??"";return t===".env"||t.startsWith(".env.")||t===".netrc"||t===".npmrc"||t.endsWith(".key")||t.endsWith(".pem")||a.includes(".ssh")||a.includes(".aws")||a.includes(".azure")?!0:a.some((i,r)=>i===".config"&&a[r+1]==="gcloud")}function F(o){const e=c.extname(o).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".heic":"image/heic",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".aac":"audio/aac",".ogg":"audio/ogg",".flac":"audio/flac",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".csv":"text/csv",".doc":"application/msword",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xls":"application/vnd.ms-excel",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".ppt":"application/vnd.ms-powerpoint",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation"}[e]??"application/octet-stream"}function s(o){return new p(o,"Room attachment file is not allowed.")}export{p as RoomAttachmentUploadError,R as RoomAttachmentUploader};
@@ -1,5 +1,5 @@
1
- import type { RoomActivationMode } from '@shennian/wire';
2
- export declare const MANAGED_ROOM_POLICY_VERSION = "managed-room-cli-v2";
1
+ import type { ManagedAgentEnforcement, ManagedAgentPermissionPolicy, AgentType, RoomActivationMode } from '@shennian/wire';
2
+ export declare const MANAGED_ROOM_POLICY_VERSION = "managed-room-surface-v3";
3
3
  export type ManagedRoomPromptInput = {
4
4
  agent: {
5
5
  displayName: string;
@@ -16,17 +16,30 @@ export type ManagedRoomPromptInput = {
16
16
  ownerDisplayName?: string | null;
17
17
  }>;
18
18
  };
19
+ runtimePermissions?: ManagedRoomRuntimePermissions;
20
+ runtimeAdapter?: AgentType;
19
21
  sessionModeInstructions?: string | null;
20
22
  };
23
+ export type ManagedRoomRuntimePermissions = {
24
+ /** User authorization ceiling; native over-grants must never widen model behavior. */
25
+ requestedPolicy: ManagedAgentPermissionPolicy;
26
+ enforcement: ManagedAgentEnforcement;
27
+ /** daemon-verified realpath from the immutable per-turn policy snapshot. */
28
+ resolvedWorkDir: string;
29
+ };
21
30
  export type ManagedRoomPromptSnapshot = {
22
31
  providerBaseVersion: 'provider-owned';
23
32
  managedRoomPolicyVersion: typeof MANAGED_ROOM_POLICY_VERSION;
24
33
  sessionModeInstructionsHash: string | null;
25
34
  agentSystemPromptHash: string | null;
35
+ runtimePermissionPolicyHash: string | null;
36
+ resolvedWorkDirHash: string | null;
37
+ enforcementLevel: ManagedAgentEnforcement['level'] | null;
26
38
  trustedRoomContextHash: string;
27
- allowedRoomCommands: readonly string[];
39
+ allowedRoomOperations: readonly string[];
28
40
  };
29
41
  export declare function composeManagedRoomPrompt(input: ManagedRoomPromptInput): {
30
42
  systemPrompt: string;
31
43
  snapshot: ManagedRoomPromptSnapshot;
32
44
  };
45
+ export declare function buildManagedAgentPermissionPrompt(input: ManagedRoomRuntimePermissions): string;