shennian 0.3.37 → 0.3.39

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 (214) hide show
  1. package/README.md +1 -0
  2. package/dist/publish-build-manifest.json +73 -168
  3. package/dist/src/agents/detect.js +1 -1
  4. package/dist/src/agents/manager.d.ts +36 -0
  5. package/dist/src/agents/manager.js +11 -6
  6. package/dist/src/agents/model-registry/service.js +1 -1
  7. package/dist/src/agents/workbuddy.js +2 -2
  8. package/dist/src/commands/agent.d.ts +6 -1
  9. package/dist/src/commands/agent.js +3 -6
  10. package/dist/src/commands/integration.d.ts +1 -0
  11. package/dist/src/commands/integration.js +2 -1
  12. package/dist/src/commands/manager.js +1 -1
  13. package/dist/src/commands/pair-authorization.d.ts +3 -0
  14. package/dist/src/commands/pair-authorization.js +1 -0
  15. package/dist/src/commands/pair-browser.js +1 -1
  16. package/dist/src/commands/room.d.ts +9 -17
  17. package/dist/src/commands/room.js +3 -8
  18. package/dist/src/commands/runtime.d.ts +2 -0
  19. package/dist/src/commands/runtime.js +6 -0
  20. package/dist/src/daemon-ipc/client.d.ts +1 -1
  21. package/dist/src/daemon-ipc/json.d.ts +3 -0
  22. package/dist/src/daemon-ipc/json.js +1 -0
  23. package/dist/src/daemon-ipc/protocol.d.ts +1 -1
  24. package/dist/src/daemon-ipc/server.d.ts +1 -1
  25. package/dist/src/index.js +3 -2
  26. package/dist/src/manager/registry.d.ts +21 -0
  27. package/dist/src/manager/registry.js +2 -2
  28. package/dist/src/manager/runtime.d.ts +5 -1
  29. package/dist/src/manager/runtime.js +14 -13
  30. package/dist/src/room/activation-service.d.ts +6 -0
  31. package/dist/src/room/activation-service.js +5 -8
  32. package/dist/src/room/attachment-cache.d.ts +2 -1
  33. package/dist/src/room/attachment-cache.js +2 -2
  34. package/dist/src/room/device-authorization-client.d.ts +39 -1
  35. package/dist/src/room/managed-context.d.ts +45 -0
  36. package/dist/src/room/managed-context.js +1 -0
  37. package/dist/src/room/managed-prompt-composer.d.ts +32 -0
  38. package/dist/src/room/managed-prompt-composer.js +24 -0
  39. package/dist/src/room/managed-room-ipc.d.ts +16 -0
  40. package/dist/src/room/managed-room-ipc.js +1 -0
  41. package/dist/src/room/mirror-sync-service.d.ts +2 -5
  42. package/dist/src/room/mirror-sync-service.js +1 -1
  43. package/dist/src/room/public-attachment-cache.d.ts +34 -0
  44. package/dist/src/room/public-attachment-cache.js +2 -0
  45. package/dist/src/room/public-room-client.d.ts +36 -0
  46. package/dist/src/room/public-room-client.js +1 -0
  47. package/dist/src/room/room-action-client.d.ts +3 -1
  48. package/dist/src/room/room-action-client.js +1 -1
  49. package/dist/src/room/session-registry.d.ts +7 -0
  50. package/dist/src/room/session-registry.js +2 -2
  51. package/dist/src/session/handlers/chat.d.ts +5 -1
  52. package/dist/src/session/handlers/chat.js +2 -2
  53. package/dist/src/session/manager.d.ts +1 -20
  54. package/dist/src/session/manager.js +1 -1
  55. package/dist/src/session/queue.js +3 -3
  56. package/dist/src/session/store.d.ts +9 -1
  57. package/dist/src/session/store.js +3 -3
  58. package/dist/src/session/types.d.ts +2 -1
  59. package/node_modules/@shennian/wire/dist/agent-capabilities.d.ts +0 -1
  60. package/node_modules/@shennian/wire/dist/agent-capabilities.js +0 -1
  61. package/node_modules/@shennian/wire/dist/agent-workspace.d.ts +0 -1
  62. package/node_modules/@shennian/wire/dist/agent-workspace.js +0 -1
  63. package/node_modules/@shennian/wire/dist/envelope.d.ts +0 -1
  64. package/node_modules/@shennian/wire/dist/envelope.js +0 -1
  65. package/node_modules/@shennian/wire/dist/events.d.ts +0 -1
  66. package/node_modules/@shennian/wire/dist/events.js +0 -1
  67. package/node_modules/@shennian/wire/dist/external-message-format.d.ts +0 -1
  68. package/node_modules/@shennian/wire/dist/external-message-format.js +0 -1
  69. package/node_modules/@shennian/wire/dist/frames.d.ts +0 -1
  70. package/node_modules/@shennian/wire/dist/frames.js +0 -1
  71. package/node_modules/@shennian/wire/dist/fs.d.ts +0 -1
  72. package/node_modules/@shennian/wire/dist/fs.js +0 -1
  73. package/node_modules/@shennian/wire/dist/index.d.ts +0 -1
  74. package/node_modules/@shennian/wire/dist/index.js +0 -1
  75. package/node_modules/@shennian/wire/dist/machine.d.ts +0 -1
  76. package/node_modules/@shennian/wire/dist/machine.js +0 -1
  77. package/node_modules/@shennian/wire/dist/message-payload.d.ts +0 -1
  78. package/node_modules/@shennian/wire/dist/message-payload.js +0 -1
  79. package/node_modules/@shennian/wire/dist/methods.d.ts +0 -1
  80. package/node_modules/@shennian/wire/dist/methods.js +0 -1
  81. package/node_modules/@shennian/wire/dist/native-session.d.ts +0 -1
  82. package/node_modules/@shennian/wire/dist/native-session.js +0 -1
  83. package/node_modules/@shennian/wire/dist/project.d.ts +0 -1
  84. package/node_modules/@shennian/wire/dist/project.js +0 -1
  85. package/node_modules/@shennian/wire/dist/room.d.ts +44 -1
  86. package/node_modules/@shennian/wire/dist/room.js +0 -1
  87. package/node_modules/@shennian/wire/dist/session.d.ts +18 -3
  88. package/node_modules/@shennian/wire/dist/session.js +0 -2
  89. package/node_modules/@shennian/wire/dist/tool-detail.d.ts +0 -1
  90. package/node_modules/@shennian/wire/dist/tool-detail.js +0 -1
  91. package/node_modules/@shennian/wire/dist/voice-chief.d.ts +0 -1
  92. package/node_modules/@shennian/wire/dist/voice-chief.js +0 -1
  93. package/node_modules/@shennian/wire/package.json +1 -0
  94. package/package.json +2 -2
  95. package/dist/src/integrations/claude-room-plugin-lifecycle.d.ts +0 -47
  96. package/dist/src/integrations/claude-room-plugin-lifecycle.js +0 -1
  97. package/dist/src/integrations/codex-room-plugin-lifecycle.d.ts +0 -49
  98. package/dist/src/integrations/codex-room-plugin-lifecycle.js +0 -1
  99. package/dist/src/integrations/room-plugin-bundles.d.ts +0 -23
  100. package/dist/src/integrations/room-plugin-bundles.js +0 -1
  101. package/dist/src/integrations/room-plugin-lifecycle-common.d.ts +0 -17
  102. package/dist/src/integrations/room-plugin-lifecycle-common.js +0 -1
  103. package/dist/src/integrations/room-plugin-shared.d.ts +0 -24
  104. package/dist/src/integrations/room-plugin-shared.js +0 -38
  105. package/dist/src/integrations/room-session-hook.d.ts +0 -30
  106. package/dist/src/integrations/room-session-hook.js +0 -2
  107. package/dist/src/integrations/workbuddy-plugin-bundle.d.ts +0 -16
  108. package/dist/src/integrations/workbuddy-plugin-bundle.js +0 -1
  109. package/dist/src/integrations/workbuddy-plugin-lifecycle.d.ts +0 -62
  110. package/dist/src/integrations/workbuddy-plugin-lifecycle.js +0 -2
  111. package/dist/src/integrations/workbuddy-session-hook.d.ts +0 -22
  112. package/dist/src/integrations/workbuddy-session-hook.js +0 -1
  113. package/dist/src/room/agent-tool.d.ts +0 -63
  114. package/dist/src/room/agent-tool.js +0 -1
  115. package/dist/src/room/authorized-action-coordinator.d.ts +0 -40
  116. package/dist/src/room/authorized-action-coordinator.js +0 -1
  117. package/dist/src/room/authorized-action-ipc.d.ts +0 -11
  118. package/dist/src/room/authorized-action-ipc.js +0 -1
  119. package/dist/src/room/host-session-observation-ipc.d.ts +0 -9
  120. package/dist/src/room/host-session-observation-ipc.js +0 -1
  121. package/dist/src/room/invocation-token-ipc.d.ts +0 -9
  122. package/dist/src/room/invocation-token-ipc.js +0 -1
  123. package/dist/src/room/invocation-token.d.ts +0 -53
  124. package/dist/src/room/invocation-token.js +0 -1
  125. package/dist/src/room/jsonl-mirror.d.ts +0 -73
  126. package/dist/src/room/jsonl-mirror.js +0 -6
  127. package/dist/src/room/managed-room-session-runtime.d.ts +0 -18
  128. package/dist/src/room/managed-room-session-runtime.js +0 -1
  129. package/dist/src/room/managed-room-session.d.ts +0 -144
  130. package/dist/src/room/managed-room-session.js +0 -2
  131. package/dist/src/room/operator-ipc.d.ts +0 -13
  132. package/dist/src/room/operator-ipc.js +0 -1
  133. package/dist/src/room/pending-authorized-actions.d.ts +0 -69
  134. package/dist/src/room/pending-authorized-actions.js +0 -3
  135. package/dist/src/room/room-action-ipc.d.ts +0 -34
  136. package/dist/src/room/room-action-ipc.js +0 -1
  137. package/dist/src/room/room-action-schema.d.ts +0 -3
  138. package/dist/src/room/room-action-schema.js +0 -1
  139. package/dist/src/room/room-authorized-action-executor.d.ts +0 -23
  140. package/dist/src/room/room-authorized-action-executor.js +0 -1
  141. package/dist/src/room/room-binding-ipc.d.ts +0 -31
  142. package/dist/src/room/room-binding-ipc.js +0 -1
  143. package/dist/src/room/session-registry-ipc.d.ts +0 -14
  144. package/dist/src/room/session-registry-ipc.js +0 -1
  145. package/dist/src/room/tool-schema.d.ts +0 -120
  146. package/dist/src/room/tool-schema.js +0 -1
  147. package/dist/src/room/tool-server.d.ts +0 -11
  148. package/dist/src/room/tool-server.js +0 -3
  149. package/node_modules/@shennian/wire/dist/__tests__/message-payload.test.d.ts +0 -2
  150. package/node_modules/@shennian/wire/dist/__tests__/message-payload.test.d.ts.map +0 -1
  151. package/node_modules/@shennian/wire/dist/__tests__/message-payload.test.js +0 -23
  152. package/node_modules/@shennian/wire/dist/__tests__/message-payload.test.js.map +0 -1
  153. package/node_modules/@shennian/wire/dist/__tests__/mock.test.d.ts +0 -2
  154. package/node_modules/@shennian/wire/dist/__tests__/mock.test.d.ts.map +0 -1
  155. package/node_modules/@shennian/wire/dist/__tests__/mock.test.js +0 -66
  156. package/node_modules/@shennian/wire/dist/__tests__/mock.test.js.map +0 -1
  157. package/node_modules/@shennian/wire/dist/__tests__/types.test.d.ts +0 -2
  158. package/node_modules/@shennian/wire/dist/__tests__/types.test.d.ts.map +0 -1
  159. package/node_modules/@shennian/wire/dist/__tests__/types.test.js +0 -159
  160. package/node_modules/@shennian/wire/dist/__tests__/types.test.js.map +0 -1
  161. package/node_modules/@shennian/wire/dist/agent-capabilities.d.ts.map +0 -1
  162. package/node_modules/@shennian/wire/dist/agent-capabilities.js.map +0 -1
  163. package/node_modules/@shennian/wire/dist/agent-workspace.d.ts.map +0 -1
  164. package/node_modules/@shennian/wire/dist/agent-workspace.js.map +0 -1
  165. package/node_modules/@shennian/wire/dist/envelope.d.ts.map +0 -1
  166. package/node_modules/@shennian/wire/dist/envelope.js.map +0 -1
  167. package/node_modules/@shennian/wire/dist/events.d.ts.map +0 -1
  168. package/node_modules/@shennian/wire/dist/events.js.map +0 -1
  169. package/node_modules/@shennian/wire/dist/external-message-format.d.ts.map +0 -1
  170. package/node_modules/@shennian/wire/dist/external-message-format.js.map +0 -1
  171. package/node_modules/@shennian/wire/dist/frames.d.ts.map +0 -1
  172. package/node_modules/@shennian/wire/dist/frames.js.map +0 -1
  173. package/node_modules/@shennian/wire/dist/fs.d.ts.map +0 -1
  174. package/node_modules/@shennian/wire/dist/fs.js.map +0 -1
  175. package/node_modules/@shennian/wire/dist/index.d.ts.map +0 -1
  176. package/node_modules/@shennian/wire/dist/index.js.map +0 -1
  177. package/node_modules/@shennian/wire/dist/machine.d.ts.map +0 -1
  178. package/node_modules/@shennian/wire/dist/machine.js.map +0 -1
  179. package/node_modules/@shennian/wire/dist/message-payload.d.ts.map +0 -1
  180. package/node_modules/@shennian/wire/dist/message-payload.js.map +0 -1
  181. package/node_modules/@shennian/wire/dist/methods.d.ts.map +0 -1
  182. package/node_modules/@shennian/wire/dist/methods.js.map +0 -1
  183. package/node_modules/@shennian/wire/dist/mock/fixtures.d.ts +0 -39
  184. package/node_modules/@shennian/wire/dist/mock/fixtures.d.ts.map +0 -1
  185. package/node_modules/@shennian/wire/dist/mock/fixtures.js +0 -99
  186. package/node_modules/@shennian/wire/dist/mock/fixtures.js.map +0 -1
  187. package/node_modules/@shennian/wire/dist/mock/index.d.ts +0 -3
  188. package/node_modules/@shennian/wire/dist/mock/index.d.ts.map +0 -1
  189. package/node_modules/@shennian/wire/dist/mock/index.js +0 -4
  190. package/node_modules/@shennian/wire/dist/mock/index.js.map +0 -1
  191. package/node_modules/@shennian/wire/dist/mock/routes.d.ts +0 -96
  192. package/node_modules/@shennian/wire/dist/mock/routes.d.ts.map +0 -1
  193. package/node_modules/@shennian/wire/dist/mock/routes.js +0 -40
  194. package/node_modules/@shennian/wire/dist/mock/routes.js.map +0 -1
  195. package/node_modules/@shennian/wire/dist/native-session.d.ts.map +0 -1
  196. package/node_modules/@shennian/wire/dist/native-session.js.map +0 -1
  197. package/node_modules/@shennian/wire/dist/project.d.ts.map +0 -1
  198. package/node_modules/@shennian/wire/dist/project.js.map +0 -1
  199. package/node_modules/@shennian/wire/dist/queue.d.ts +0 -15
  200. package/node_modules/@shennian/wire/dist/queue.d.ts.map +0 -1
  201. package/node_modules/@shennian/wire/dist/queue.js +0 -2
  202. package/node_modules/@shennian/wire/dist/queue.js.map +0 -1
  203. package/node_modules/@shennian/wire/dist/room.d.ts.map +0 -1
  204. package/node_modules/@shennian/wire/dist/room.js.map +0 -1
  205. package/node_modules/@shennian/wire/dist/session.d.ts.map +0 -1
  206. package/node_modules/@shennian/wire/dist/session.js.map +0 -1
  207. package/node_modules/@shennian/wire/dist/skills.d.ts +0 -61
  208. package/node_modules/@shennian/wire/dist/skills.d.ts.map +0 -1
  209. package/node_modules/@shennian/wire/dist/skills.js +0 -2
  210. package/node_modules/@shennian/wire/dist/skills.js.map +0 -1
  211. package/node_modules/@shennian/wire/dist/tool-detail.d.ts.map +0 -1
  212. package/node_modules/@shennian/wire/dist/tool-detail.js.map +0 -1
  213. package/node_modules/@shennian/wire/dist/voice-chief.d.ts.map +0 -1
  214. package/node_modules/@shennian/wire/dist/voice-chief.js.map +0 -1
@@ -1 +1 @@
1
- import l from"node:fs";import p from"chalk";function u(){const o=process.env.SHENNIAN_MANAGER_IPC_URL,r=process.env.SHENNIAN_MANAGER_IPC_TOKEN,s=process.env.SHENNIAN_MANAGER_SESSION_ID;return(!o||!r||!s)&&(console.error(p.red("\u2717 This command must run inside a Shennian Manager Agent session.")),process.exit(1)),{url:o,token:r,managerSessionId:s}}async function t(o,r){const s=u(),a=await fetch(`${s.url}${o}`,{method:"POST",headers:{authorization:`Bearer ${s.token}`,"content-type":"application/json","x-shennian-manager-session-id":s.managerSessionId},body:JSON.stringify({...r,managerSessionId:s.managerSessionId})}),d=await a.json().catch(()=>({ok:!1,error:a.statusText}));if(!a.ok||!d.ok)throw new Error(d.error||`Manager IPC failed: ${a.status}`);return d}function m(o){console.log(JSON.stringify(o,null,2))}function g(o){return o.messageFile?l.readFileSync(o.messageFile,"utf-8"):o.message??""}function I(o){const r=o.command("manager").description("Manager Agent local tools"),s=r.command("sessions").description("Manage same-project worker sessions");s.command("list").description("List same-project sessions visible to this Manager").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/list",{});if(e.json)m(n);else{const c=n.sessions??[];for(const i of c)console.log(`${i.sessionId} ${i.agentType} ${i.role??""} ${i.status} ${i.title??i.summary??""}`)}}),s.command("start").description("Start a worker session in the same project").requiredOption("--agent <agent>","Worker agent type, e.g. codex, claude, gemini, cursor, opencode, pi, or custom:<name>").requiredOption("--workdir <path>","Worker workdir; must match Manager workdir").option("--model <model>","Worker model id").option("--message <text>","Worker prompt").option("--message-file <path>","Read worker prompt from file").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/start",{agentType:e.agent,workDir:e.workdir,modelId:e.model,message:g(e)});e.json?m(n):console.log(n.session?.sessionId??"ok")}),s.command("send").description("Queue a message for a managed worker; runs immediately if idle").requiredOption("--session-id <id>","Worker session id").option("--message <text>","Message text").option("--message-file <path>","Read message from file").option("--direct","Bypass queue and send immediately").action(async e=>{await t("/sessions/send",{sessionId:e.sessionId,message:g(e),enqueue:!e.direct}),console.log("ok")});const a=s.command("queue").description("Inspect and edit pending worker messages");a.command("list").description("List pending messages for a managed worker").requiredOption("--session-id <id>","Worker session id").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/queue",{sessionId:e.sessionId});if(e.json)m(n);else{const c=n.queue?.pending??[];for(const i of c)console.log(`${i.id} ${i.text.replace(/\s+/g," ").slice(0,120)}`)}}),a.command("edit").description("Edit a pending worker message").requiredOption("--session-id <id>","Worker session id").requiredOption("--message-id <id>","Queued message id").option("--message <text>","Replacement message text").option("--message-file <path>","Read replacement message from file").action(async e=>{await t("/sessions/queue/edit",{sessionId:e.sessionId,queueMessageId:e.messageId,message:g(e)}),console.log("ok")}),a.command("delete").description("Delete a pending worker message").requiredOption("--session-id <id>","Worker session id").requiredOption("--message-id <id>","Queued message id").action(async e=>{await t("/sessions/queue/delete",{sessionId:e.sessionId,queueMessageId:e.messageId}),console.log("ok")});const d=async e=>{await t("/sessions/stop",{sessionId:e.sessionId}),console.log("ok")};s.command("stop").aliases(["terminate","kill"]).description("Stop or terminate a running managed worker").requiredOption("--session-id <id>","Worker session id").action(d),s.command("read").description("Read a managed worker transcript").requiredOption("--session-id <id>","Worker session id").option("--limit <n>","Message limit","200").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/read",{sessionId:e.sessionId,limit:Number(e.limit)});e.json?m(n):m(n.messages??[])}),r.command("memory").description("Manager project memory helpers").command("path").description("Print project memory directory path").action(async()=>{const e=await t("/memory/path",{});console.log(e.path)})}export{I as registerManagerCommand};
1
+ import l from"node:fs";import p from"chalk";function u(){const o=process.env.SHENNIAN_MANAGER_IPC_URL,a=process.env.SHENNIAN_MANAGER_IPC_TOKEN,s=process.env.SHENNIAN_MANAGER_SESSION_ID;return(!o||!a||!s)&&(console.error(p.red("\u2717 This command must run inside a Shennian Manager Agent session.")),process.exit(1)),{url:o,token:a,managerSessionId:s}}async function t(o,a){const s=u(),r=await fetch(`${s.url}${o}`,{method:"POST",headers:{authorization:`Bearer ${s.token}`,"content-type":"application/json","x-shennian-manager-session-id":s.managerSessionId},body:JSON.stringify({...a,managerSessionId:s.managerSessionId})}),d=await r.json().catch(()=>({ok:!1,error:r.statusText}));if(!r.ok||!d.ok)throw new Error(d.error||`Manager IPC failed: ${r.status}`);return d}function m(o){console.log(JSON.stringify(o,null,2))}function g(o){return o.messageFile?l.readFileSync(o.messageFile,"utf-8"):o.message??""}function I(o){const a=o.command("manager",{hidden:!0}).description("Manager Session local tools"),s=a.command("sessions").description("Manage same-project worker sessions");s.command("list").description("List same-project sessions visible to this Manager").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/list",{});if(e.json)m(n);else{const c=n.sessions??[];for(const i of c)console.log(`${i.sessionId} ${i.agentType} ${i.role??""} ${i.status} ${i.title??i.summary??""}`)}}),s.command("start").description("Start a worker session in the same project").requiredOption("--agent <agent>","Worker agent type, e.g. codex, claude, gemini, cursor, opencode, pi, or custom:<name>").requiredOption("--workdir <path>","Worker workdir; must match Manager workdir").option("--model <model>","Worker model id").option("--message <text>","Worker prompt").option("--message-file <path>","Read worker prompt from file").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/start",{agentType:e.agent,workDir:e.workdir,modelId:e.model,message:g(e)});e.json?m(n):console.log(n.session?.sessionId??"ok")}),s.command("send").description("Queue a message for a managed worker; runs immediately if idle").requiredOption("--session-id <id>","Worker session id").option("--message <text>","Message text").option("--message-file <path>","Read message from file").option("--direct","Bypass queue and send immediately").action(async e=>{await t("/sessions/send",{sessionId:e.sessionId,message:g(e),enqueue:!e.direct}),console.log("ok")});const r=s.command("queue").description("Inspect and edit pending worker messages");r.command("list").description("List pending messages for a managed worker").requiredOption("--session-id <id>","Worker session id").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/queue",{sessionId:e.sessionId});if(e.json)m(n);else{const c=n.queue?.pending??[];for(const i of c)console.log(`${i.id} ${i.text.replace(/\s+/g," ").slice(0,120)}`)}}),r.command("edit").description("Edit a pending worker message").requiredOption("--session-id <id>","Worker session id").requiredOption("--message-id <id>","Queued message id").option("--message <text>","Replacement message text").option("--message-file <path>","Read replacement message from file").action(async e=>{await t("/sessions/queue/edit",{sessionId:e.sessionId,queueMessageId:e.messageId,message:g(e)}),console.log("ok")}),r.command("delete").description("Delete a pending worker message").requiredOption("--session-id <id>","Worker session id").requiredOption("--message-id <id>","Queued message id").action(async e=>{await t("/sessions/queue/delete",{sessionId:e.sessionId,queueMessageId:e.messageId}),console.log("ok")});const d=async e=>{await t("/sessions/stop",{sessionId:e.sessionId}),console.log("ok")};s.command("stop").aliases(["terminate","kill"]).description("Stop or terminate a running managed worker").requiredOption("--session-id <id>","Worker session id").action(d),s.command("read").description("Read a managed worker transcript").requiredOption("--session-id <id>","Worker session id").option("--limit <n>","Message limit","200").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/read",{sessionId:e.sessionId,limit:Number(e.limit)});e.json?m(n):m(n.messages??[])}),a.command("memory").description("Manager project memory helpers").command("path").description("Print project memory directory path").action(async()=>{const e=await t("/memory/path",{});console.log(e.path)})}export{I as registerManagerCommand};
@@ -0,0 +1,3 @@
1
+ import type { JsonValue } from '../daemon-ipc/json.js';
2
+ export declare function hashActionPayload(value: JsonValue): string;
3
+ export declare function hashPendingDeviceSecret(secret: string): string;
@@ -0,0 +1 @@
1
+ import o from"node:crypto";function s(t){return o.createHash("sha256").update(r(t),"utf8").digest("hex")}function a(t){return o.createHash("sha256").update(t,"utf8").digest("hex")}function r(t){if(t===null||typeof t=="boolean"||typeof t=="string")return JSON.stringify(t);if(typeof t=="number"){if(!Number.isFinite(t))throw new Error("Pair authorization payload numbers must be finite.");return JSON.stringify(t)}if(Array.isArray(t))return`[${t.map(r).join(",")}]`;const i=Object.getPrototypeOf(t);if(i!==Object.prototype&&i!==null)throw new Error("Pair authorization payload must be plain JSON.");return`{${Object.keys(t).sort().map(n=>`${JSON.stringify(n)}:${r(t[n])}`).join(",")}}`}export{s as hashActionPayload,a as hashPendingDeviceSecret};
@@ -1 +1 @@
1
- import I from"node:crypto";import{spawn as u}from"node:child_process";import{HttpDeviceAuthorizationClient as f}from"../room/device-authorization-client.js";import{hashActionPayload as A,hashPendingDeviceSecret as g}from"../room/pending-authorized-actions.js";const p=3e3;class n extends Error{code;constructor(r,t){super(t),this.code=r,this.name="BrowserPairError"}}async function E(e){const r=new f(e.serverUrl),t=I.randomBytes(32).toString("base64url"),a={action:"pair_machine",agentList:[...e.agentList].sort(),machineName:e.machineName,machineOs:e.machineOs},o=A(a),i=await r.create({deviceSecretHash:g(t),machineName:e.machineName,machineOs:e.machineOs,agentList:e.agentList,actionType:"machine.pair",actionSummary:"Pair this machine with your Shennian account",actionPayloadHash:o,returnTo:"/spaces"}),d=_(i.publicLoginUrl,e.serverUrl);e.emit?.({type:"pair.browser.created",intentId:i.loginIntentId,url:d,expiresAt:i.expiresAt}),await(e.openUrl??P)(d).catch(()=>!1);const l=new Date(i.expiresAt).getTime(),h=e.pollIntervalMs??p;let m=!1;for(;Date.now()<l;){if(e.signal?.aborted)throw await r.cancel({loginIntentId:i.loginIntentId,deviceSecret:t}).catch(()=>{}),new n("PAIR_CANCELLED","Pairing was cancelled");let s;try{s=await r.getStatus({loginIntentId:i.loginIntentId,deviceSecret:t})}catch{await w(h,e.signal);continue}if(s.actionPayloadHash!==o)throw new n("PAIR_PAYLOAD_MISMATCH","Authorization request changed unexpectedly");if(s.status==="denied")throw new n("PAIR_DENIED","Pairing request was denied");if(s.status==="expired")throw new n("PAIR_EXPIRED","Pairing request expired");if(s.status==="claimed")throw new n("PAIR_ALREADY_CLAIMED","Pairing credentials were already claimed");if(s.status==="approved"){const c=await r.claim({loginIntentId:i.loginIntentId,deviceSecret:t});if(c.actionPayloadHash!==o||!c.machineId||!c.machineToken)throw new n("PAIR_INVALID_CLAIM","Authorization did not return machine credentials");return e.emit?.({type:"pair.claimed"}),{machineId:c.machineId,machineToken:c.machineToken,machineName:e.machineName,publicLoginUrl:d}}m||(m=!0,e.emit?.({type:"pair.waiting"})),await w(h,e.signal)}throw new n("PAIR_TIMEOUT","Timed out waiting for browser authorization")}function N(e){if(e.browser&&e.token)throw new n("PAIR_MODE_CONFLICT","Choose either --browser or --token, not both");return e.browser?"browser":e.token||e.json||!e.stdinIsTty||!e.stdoutIsTty?"token":"prompt"}function O(e){return e.trim()==="2"?"token":"browser"}async function P(e){if(process.env.SSH_CONNECTION||process.env.SSH_TTY)return!1;const{command:r,args:t}=L(process.platform,e);return new Promise(a=>{const o=u(r,t,{detached:!0,stdio:"ignore",windowsHide:!0});o.once("error",()=>a(!1)),o.once("spawn",()=>{o.unref(),a(!0)})})}function L(e,r){return e==="darwin"?{command:"open",args:[r]}:e==="win32"?{command:"explorer.exe",args:[r]}:{command:"xdg-open",args:[r]}}function _(e,r){let t,a;try{t=new URL(e),a=new URL(r)}catch{throw new n("PAIR_INVALID_AUTHORIZATION_URL","Server returned an invalid authorization URL")}if(!["http:","https:"].includes(t.protocol)||t.hostname!==a.hostname||t.username||t.password||t.search||t.hash||!/^\/authorize\/[A-Za-z0-9_-]{8,128}$/.test(t.pathname))throw new n("PAIR_INVALID_AUTHORIZATION_URL","Server returned an unsafe authorization URL");return t.toString()}function w(e,r){return new Promise(t=>{if(r?.aborted)return t();const a=setTimeout(t,e);r?.addEventListener("abort",()=>{clearTimeout(a),t()},{once:!0})})}export{n as BrowserPairError,L as browserOpenCommand,_ as normalizePairAuthorizationUrl,P as openExternalUrl,O as parsePairModeChoice,N as resolvePairMode,E as runBrowserPairFlow};
1
+ import I from"node:crypto";import{spawn as u}from"node:child_process";import{HttpDeviceAuthorizationClient as f}from"../room/device-authorization-client.js";import{hashActionPayload as A,hashPendingDeviceSecret as g}from"./pair-authorization.js";const p=3e3;class n extends Error{code;constructor(r,t){super(t),this.code=r,this.name="BrowserPairError"}}async function E(e){const r=new f(e.serverUrl),t=I.randomBytes(32).toString("base64url"),a={action:"pair_machine",agentList:[...e.agentList].sort(),machineName:e.machineName,machineOs:e.machineOs},o=A(a),i=await r.create({deviceSecretHash:g(t),machineName:e.machineName,machineOs:e.machineOs,agentList:e.agentList,actionType:"machine.pair",actionSummary:"Pair this machine with your Shennian account",actionPayloadHash:o,returnTo:"/spaces"}),d=_(i.publicLoginUrl,e.serverUrl);e.emit?.({type:"pair.browser.created",intentId:i.loginIntentId,url:d,expiresAt:i.expiresAt}),await(e.openUrl??P)(d).catch(()=>!1);const l=new Date(i.expiresAt).getTime(),h=e.pollIntervalMs??p;let m=!1;for(;Date.now()<l;){if(e.signal?.aborted)throw await r.cancel({loginIntentId:i.loginIntentId,deviceSecret:t}).catch(()=>{}),new n("PAIR_CANCELLED","Pairing was cancelled");let s;try{s=await r.getStatus({loginIntentId:i.loginIntentId,deviceSecret:t})}catch{await w(h,e.signal);continue}if(s.actionPayloadHash!==o)throw new n("PAIR_PAYLOAD_MISMATCH","Authorization request changed unexpectedly");if(s.status==="denied")throw new n("PAIR_DENIED","Pairing request was denied");if(s.status==="expired")throw new n("PAIR_EXPIRED","Pairing request expired");if(s.status==="claimed")throw new n("PAIR_ALREADY_CLAIMED","Pairing credentials were already claimed");if(s.status==="approved"){const c=await r.claim({loginIntentId:i.loginIntentId,deviceSecret:t});if(c.actionPayloadHash!==o||!c.machineId||!c.machineToken)throw new n("PAIR_INVALID_CLAIM","Authorization did not return machine credentials");return e.emit?.({type:"pair.claimed"}),{machineId:c.machineId,machineToken:c.machineToken,machineName:e.machineName,publicLoginUrl:d}}m||(m=!0,e.emit?.({type:"pair.waiting"})),await w(h,e.signal)}throw new n("PAIR_TIMEOUT","Timed out waiting for browser authorization")}function N(e){if(e.browser&&e.token)throw new n("PAIR_MODE_CONFLICT","Choose either --browser or --token, not both");return e.browser?"browser":e.token||e.json||!e.stdinIsTty||!e.stdoutIsTty?"token":"prompt"}function O(e){return e.trim()==="2"?"token":"browser"}async function P(e){if(process.env.SSH_CONNECTION||process.env.SSH_TTY)return!1;const{command:r,args:t}=L(process.platform,e);return new Promise(a=>{const o=u(r,t,{detached:!0,stdio:"ignore",windowsHide:!0});o.once("error",()=>a(!1)),o.once("spawn",()=>{o.unref(),a(!0)})})}function L(e,r){return e==="darwin"?{command:"open",args:[r]}:e==="win32"?{command:"explorer.exe",args:[r]}:{command:"xdg-open",args:[r]}}function _(e,r){let t,a;try{t=new URL(e),a=new URL(r)}catch{throw new n("PAIR_INVALID_AUTHORIZATION_URL","Server returned an invalid authorization URL")}if(!["http:","https:"].includes(t.protocol)||t.hostname!==a.hostname||t.username||t.password||t.search||t.hash||!/^\/authorize\/[A-Za-z0-9_-]{8,128}$/.test(t.pathname))throw new n("PAIR_INVALID_AUTHORIZATION_URL","Server returned an unsafe authorization URL");return t.toString()}function w(e,r){return new Promise(t=>{if(r?.aborted)return t();const a=setTimeout(t,e);r?.addEventListener("abort",()=>{clearTimeout(a),t()},{once:!0})})}export{n as BrowserPairError,L as browserOpenCommand,_ as normalizePairAuthorizationUrl,P as openExternalUrl,O as parsePairModeChoice,N as resolvePairMode,E as runBrowserPairFlow};
@@ -1,22 +1,14 @@
1
- import { Command } from 'commander';
2
- import { callDaemonIpc } from '../daemon-ipc/client.js';
3
- type InvokeDaemon = typeof callDaemonIpc;
1
+ import type { Command } from 'commander';
2
+ import { PublicAttachmentCache } from '../room/public-attachment-cache.js';
3
+ import { PublicRoomClient } from '../room/public-room-client.js';
4
4
  type RoomCommandIo = {
5
- stdout: (text: string) => void;
6
- stderr: (text: string) => void;
5
+ write: (text: string) => void;
7
6
  fail: () => void;
8
7
  };
9
- type ConfirmManagedAgent = (summary: string) => Promise<boolean>;
10
- type ManagedAgentIntentStore = {
11
- acquire: (intentKey: string) => string;
12
- complete: (intentKey: string, operationId: string) => void;
13
- };
14
- export declare function registerRoomCommand(program: Command, options?: {
15
- invoke?: InvokeDaemon;
8
+ type RoomCommandOptions = {
9
+ client?: PublicRoomClient;
10
+ cache?: PublicAttachmentCache;
16
11
  io?: RoomCommandIo;
17
- confirmManagedAgent?: ConfirmManagedAgent;
18
- managedAgentIntents?: ManagedAgentIntentStore;
19
- interactive?: boolean;
20
- }): void;
21
- export declare function createFileManagedAgentIntentStore(directory?: string): ManagedAgentIntentStore;
12
+ };
13
+ export declare function registerRoomCommand(program: Command, options?: RoomCommandOptions): void;
22
14
  export {};
@@ -1,8 +1,3 @@
1
- import g from"node:crypto";import i from"node:fs";import v from"node:path";import{createInterface as I}from"node:readline/promises";import{loadConfig as _,resolveShennianPath as k}from"../config/index.js";import{callDaemonIpc as R}from"../daemon-ipc/client.js";import{DaemonIpcError as u}from"../daemon-ipc/protocol.js";const $={stdout:e=>process.stdout.write(e),stderr:e=>process.stderr.write(e),fail:()=>{process.exitCode=1}};function G(e,n={}){const t=n.invoke??R,o=n.io??$,r=e.command("room").description("Inspect locally observed Shennian Rooms and manage safe Room operations");r.command("status").description("Show local Room integration status").action(async()=>{await w("status",t,o)}),r.command("list").description("List Rooms observed by this daemon on this machine").action(async()=>{await w("list",t,o)}),r.command("create").description("Create a private Room owned by the signed-in Shennian account").requiredOption("--name <name>","Room name").action(async a=>{await N(a.name,t,o)}),r.command("agent").description("Manage Agent conversations for a Room").command("create").description("Create a managed Agent conversation and join it to an explicit Room invite").requiredOption("--room <invite-url>","Exact Room invite URL; recent Rooms are never guessed").requiredOption("--agent <runtime>","codex, claude-code, or workbuddy").requiredOption("--workdir <absolute-path>","Absolute working directory for the new Agent").option("--model <model>","Agent model").option("--name <alias>","Agent display name").option("--mode <mode>","auto, mention, or paused","auto").option("--yes","Confirm creation in a non-interactive environment",!1).action(async a=>{await M(a,t,o,{confirm:n.confirmManagedAgent??D,interactive:n.interactive??!!(process.stdin.isTTY&&process.stdout.isTTY),intents:n.managedAgentIntents??b})})}async function w(e,n,t){try{const o=await n({method:"room-operator.inspect",params:{action:e}});t.stdout(`${JSON.stringify({ok:!0,command:`room.${e}`,result:o})}
2
- `)}catch(o){s(o,t)}}async function N(e,n,t){const o=e.trim();if(!o||o.length>80){s(new u("invalid_params","Room name must contain 1 to 80 characters."),t);return}try{const r=await n({method:"room-operator.create",params:{name:o},timeoutMs:15e3});t.stdout(`${JSON.stringify({ok:!0,command:"room.create",result:r})}
3
- `)}catch(r){s(r,t)}}async function M(e,n,t,o){const r=e.agent.trim().toLowerCase(),c=r==="claude-code"?"claude":r,a=e.mode.trim().toLowerCase(),y=a==="auto"?"auto_follow":a==="mention"?"mention_only":a,p=e.name?.trim()||T(c),m={room:e.room.trim(),agent:c,workDir:e.workdir.trim(),...e.model?.trim()?{modelId:e.model.trim()}:{},alias:p,activationMode:y};if(!["codex","claude","workbuddy"].includes(c)||!["auto_follow","mention_only","paused"].includes(y)||!m.room||!m.workDir||!p||p.length>80){s(new u("invalid_params","Managed Agent parameters are invalid."),t);return}if(!e.yes){if(!o.interactive){s(new u("confirmation_required","Creating an Agent starts a model process. Re-run with --yes after reviewing the target."),t);return}if(!await o.confirm(["Create and start this managed Agent?",`Room: ${x(m.room)}`,`Agent: ${r}`,`Workdir: ${m.workDir}`,`Model: ${e.model?.trim()||"(default)"}`,`Name: ${p}`,`Mode: ${a}`].join(`
4
- `))){s(new u("cancelled","Managed Agent creation was cancelled."),t);return}}const h=g.createHash("sha256").update(JSON.stringify({machineId:_().machineId??null,...m}),"utf8").digest("hex");let f;try{f=o.intents.acquire(h)}catch(d){s(d,t);return}try{const d=await n({method:"room-operator.agent-create",params:{...m,operationId:f},timeoutMs:9e4});try{o.intents.complete(h,f)}catch{}t.stdout(`${JSON.stringify({ok:!0,command:"room.agent.create",result:d})}
5
- `)}catch(d){s(d,t)}}const O=/^room_agent_[a-f0-9]{48}$/,E=k("runtime","room-agent-create-intents");function C(e=E){return{acquire:n=>{const t=A(e,n);try{return S(t)}catch(a){if(a.code!=="ENOENT")throw a}i.mkdirSync(e,{recursive:!0,mode:448});const o=i.lstatSync(e);if(!o.isDirectory()||o.isSymbolicLink())throw l();process.platform!=="win32"&&i.chmodSync(e,448);const r=`room_agent_${g.randomBytes(24).toString("hex")}`,c=`${t}.tmp-${process.pid}-${g.randomBytes(6).toString("hex")}`;try{return i.writeFileSync(c,`${JSON.stringify({operationId:r})}
6
- `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&i.chmodSync(c,384),i.linkSync(c,t),r}catch(a){if(a.code!=="EEXIST")throw a;return S(t)}finally{try{i.unlinkSync(c)}catch{}}},complete:(n,t)=>{const o=A(e,n);try{const r=i.lstatSync(o);if(!r.isFile()||r.isSymbolicLink())throw l();JSON.parse(i.readFileSync(o,"utf8")).operationId===t&&i.unlinkSync(o)}catch(r){if(r.code!=="ENOENT")throw r}}}}function S(e){const n=i.lstatSync(e);if(!n.isFile()||n.isSymbolicLink())throw l();const t=JSON.parse(i.readFileSync(e,"utf8"));if(typeof t.operationId!="string"||!O.test(t.operationId))throw l();return t.operationId}const b=C();function A(e,n){if(!/^[a-f0-9]{64}$/.test(n))throw l();return v.join(e,`${n}.json`)}function l(){return Object.assign(new Error("Managed Agent create intent storage is unsafe or corrupt."),{code:"unsafe_managed_agent_intent_store"})}function T(e){return e==="claude"?"Claude Code":e==="workbuddy"?"WorkBuddy":"Codex"}function x(e){try{return`${new URL(e).origin}/spaces/join/[capability]`}catch{return"[invalid Room reference]"}}async function D(e){const n=I({input:process.stdin,output:process.stdout});try{const t=await n.question(`${e}
7
- Continue? (y/N) `);return/^(?:y|yes)$/i.test(t.trim())}finally{n.close()}}function s(e,n){const t=e instanceof u?{code:e.code,message:e.message}:{code:"room_command_failed",message:"Shennian Room command failed."};n.stdout(`${JSON.stringify({ok:!1,error:t})}
8
- `),n.fail()}export{C as createFileManagedAgentIntentStore,G as registerRoomCommand};
1
+ import g from"node:crypto";import x from"node:fs";import R from"node:path";import{PublicAttachmentCache as L}from"../room/public-attachment-cache.js";import{PublicRoomClient as j,PublicRoomClientError as U}from"../room/public-room-client.js";import{callDaemonIpc as z}from"../daemon-ipc/client.js";import{DaemonIpcError as q}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_CONTEXT_ENV as D}from"../room/managed-context.js";const M="https://shennian.net/install.md",$={write:t=>{process.stdout.write(t)},fail:()=>{process.exitCode=1}};function K(t,e={}){const a=e.client??new j,r=e.cache??new L({client:a}),s=e.io??$,d=t.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 o=>p("room.create",s,async()=>{b("create");const n=o.name.trim();(!n||n.length>80)&&i("invalid_room_name","Room name must contain 1 to 80 characters.");const c=await a.create(n),m=u(c.room,"invalid_room_response");return typeof m.inviteUrl!="string"&&i("invalid_room_response","Shennian returned an invalid Room invitation."),{...c,sharing:{inviteUrl:m.inviteUrl,installGuideUrl:M}}})),d.command("join <invite-url>").description("Join or request access through an official Room invitation").action(async o=>p("room.join",s,async()=>(b("join"),a.join(k(o))))),d.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async o=>p("room.status",s,()=>{const n=_();return n?(o&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),y(n,"status",{})):a.status(w(o))})),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(o,n)=>{await p("room.read",s,async()=>{n.before&&n.after&&i("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),n.before&&!S(n.before)&&i("invalid_sequence","--before sequence is invalid."),n.after&&!S(n.after)&&i("invalid_sequence","--after sequence is invalid.");const c=Number(n.limit);(!Number.isInteger(c)||c<1||c>50)&&i("invalid_limit","--limit must be between 1 and 50.");const m=_();if(m)return o&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),y(m,"read",{...n.before?{before:n.before}:{},...n.after?{after:n.after}:{},limit:c});const l=w(o),f=await a.read(l,{before:n.before,after:n.after,limit:c});return T(f,l,r)})}),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(o,n)=>{await p("room.send",s,async()=>{const c=n.text?.trim()??"";!c&&n.file.length===0&&i("room_message_empty","Provide --text or at least one --file."),n.file.length>10&&i("room_attachments_too_many","A message can contain at most 10 files.");const m=_();if(m)return o&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),y(m,"send",{clientMessageId:`managed:${g.randomUUID()}`,text:c,files:n.file});const l=w(o),f=[];for(const I of n.file)f.push(await N(a,l,I));return a.send(l,{clientMessageId:`cli:${g.randomUUID()}`,...c?{text:c}:{},...f.length>0?{attachmentIds:f}:{}})})}),d.command("attachment").description("Download Room attachments through Shennian").command("get [room-ref] [attachment-id]").description("Download and verify one Room attachment").action(async(o,n)=>{await p("room.attachment.get",s,async()=>{const c=_(),m=c?o:n;return(!m||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(m))&&i("invalid_attachment_ref","attachment-id is invalid."),c?y(c,"attachment.get",{attachmentId:m}):r.materialize(w(o),m)})}),d.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async o=>p("room.leave",s,()=>(b("leave"),a.leave(A(o))))),d.command("list",{hidden:!0}).action(()=>h(s,"command_removed","Room listing is not available to Agents.",null)),d.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>h(s,"command_removed","Room search is not available to Agents.",null)),d.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>h(s,"command_removed","Managed Agents are created in the Shennian App or Web interface.",null))}async function p(t,e,a){try{const r=await a();e.write(`${JSON.stringify({ok:!0,command:t,result:r})}
2
+ `)}catch(r){if(r instanceof U){h(e,r.code,r.message,r.nextAction);return}if(r instanceof q){h(e,r.code,r.message,null);return}h(e,"room_command_failed","Shennian Room command failed.",null)}}function _(){return process.env[D]?.trim()||null}async function y(t,e,a){return z({method:"managed-room.execute",params:{context:t,action:e,...a},timeoutMs:e==="send"||e==="attachment.get"?18e4:15e3})}function b(t){_()&&i("managed_room_action_forbidden",`${t} is not available in managed Room context.`)}function w(t){return t||i("room_ref_required","Use an official Room URL returned by Shennian."),A(t)}function A(t){let e;try{e=new URL(t)}catch{i("invalid_room_ref","Use an official Room URL returned by Shennian.")}const a=e.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/);return(e.protocol!=="https:"||e.hostname!=="app.shennian.net"&&e.hostname!=="shennian.net"||e.port||e.username||e.password||e.search||e.hash||!a)&&i("invalid_room_ref","Use an official Room URL returned by Shennian."),a[1]}function k(t){let e;try{e=new URL(t)}catch{i("invalid_invite_url","Use an official Shennian Room invitation URL.")}const a=e.pathname.match(/^\/spaces\/join\/([A-Za-z0-9_-]{32,256})$/);return(e.protocol!=="https:"||e.hostname!=="shennian.net"||e.port||e.username||e.password||e.search||e.hash||!a)&&i("invalid_invite_url","Use an official Shennian Room invitation URL."),a[1]}function S(t){return/^(0|[1-9]\d{0,19})$/.test(t)}function C(t,e){return[...e,t]}async function N(t,e,a){const r=R.resolve(a);let s;try{s=await x.promises.lstat(r)}catch{i("file_not_found",`File does not exist: ${a}`)}(!s.isFile()||s.isSymbolicLink())&&i("invalid_file",`Not a regular file: ${a}`),(s.size<1||s.size>100*1024*1024)&&i("invalid_file_size","Each file must be between 1 byte and 100 MB.");const d=await x.promises.readFile(r),v=g.createHash("sha256").update(d).digest("hex"),o=O(r),n=await t.initializeAttachment(e,{clientUploadId:`cli:${g.randomUUID()}`,name:R.basename(r),mediaKind:o.startsWith("image/")?"image":o.startsWith("audio/")?"audio":"file",mimeType:o,byteSize:s.size,sha256:v}),c=u(n.attachment,"invalid_attachment_response"),m=u(n.upload,"invalid_attachment_response");(typeof c.id!="string"||typeof m.url!="string"||m.method!=="PUT")&&i("invalid_attachment_response","Shennian returned an invalid attachment response.");const l=u(m.headers,"invalid_attachment_response");return Object.values(l).some(f=>typeof f!="string")&&i("invalid_attachment_response","Shennian returned invalid upload headers."),E(m.url),await t.upload(m.url,l,d),await t.completeAttachment(e,c.id),c.id}function O(t){const e=R.extname(t).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"}[e]??"application/octet-stream"}function T(t,e,a){return Array.isArray(t.messages)||i("invalid_server_response","Shennian returned invalid messages."),{messages:t.messages.map(r=>{const s=u(r,"invalid_server_response"),d=Array.isArray(s.attachments)?s.attachments:[];return{...s,attachments:d.map(v=>{const o=u(v,"invalid_server_response");return(typeof o.attachmentId!="string"||typeof o.name!="string"||typeof o.mimeType!="string"||typeof o.byteSize!="string"||typeof o.sha256!="string")&&i("invalid_server_response","Shennian returned invalid attachment metadata."),{...o,...a.lookup(e,o)}})}})}}function u(t,e){return(!t||typeof t!="object"||Array.isArray(t))&&i(e,"Invalid structured response."),t}function E(t){let e;try{e=new URL(t)}catch{i("invalid_attachment_response","Invalid upload URL.")}const a=e.protocol==="http:"&&(e.hostname==="localhost"||e.hostname==="127.0.0.1");(e.protocol!=="https:"&&!a||e.username||e.password||e.hash)&&i("invalid_attachment_response","Invalid upload URL.")}function i(t,e){throw new U(t,e)}function h(t,e,a,r){t.write(`${JSON.stringify({ok:!1,error:e,message:a,nextAction:r})}
3
+ `),t.fail()}export{K as registerRoomCommand};
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerRuntimeCommand(program: Command): void;
@@ -0,0 +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.
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};
@@ -1,4 +1,4 @@
1
- import type { JsonValue } from '../room/pending-authorized-actions.js';
1
+ import type { JsonValue } from './json.js';
2
2
  import { type DaemonIpcPaths } from './paths.js';
3
3
  export declare function callDaemonIpc<T extends JsonValue = JsonValue>(input: {
4
4
  method: string;
@@ -0,0 +1,3 @@
1
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
2
+ [key: string]: JsonValue;
3
+ };
@@ -0,0 +1 @@
1
+
@@ -1,4 +1,4 @@
1
- import type { JsonValue } from '../room/pending-authorized-actions.js';
1
+ import type { JsonValue } from './json.js';
2
2
  export declare const DAEMON_IPC_VERSION: 1;
3
3
  export declare const DAEMON_IPC_MAX_REQUEST_BYTES: number;
4
4
  export type DaemonIpcRequest = {
@@ -1,4 +1,4 @@
1
- import type { JsonValue } from '../room/pending-authorized-actions.js';
1
+ import type { JsonValue } from './json.js';
2
2
  import { type DaemonIpcPaths } from './paths.js';
3
3
  export type DaemonIpcHandler = (params: JsonValue | undefined, context: {
4
4
  requestId: string;
package/dist/src/index.js CHANGED
@@ -1,2 +1,3 @@
1
- import{Command as me}from"commander";import S from"chalk";import U from"node:os";import O from"node:fs";import{loadConfig as i,saveConfig as j,getConfigPath as le,getShennianDir as de,resolveShennianPath as W,normalizeAutoUpgradePolicy as pe}from"./config/index.js";import{CliRelayClient as ge}from"./relay/client.js";import{registerPairCommand as ue,runSmartStart as he}from"./commands/pair.js";import{clearDaemonPidIfOwner as K,createDaemonInstanceId as fe,clearDaemonLauncher as q,findRunningDaemonProcessIds as ve,isRemoteAccessDisabled as Ie,isIsolatedDaemonE2eInstance as Se,registerDaemonCommand as we,writeDaemonPid as Q,writeDaemonLauncher as X}from"./commands/daemon.js";import{registerAgentCommand as ye}from"./commands/agent.js";import{registerManagerCommand as Re}from"./commands/manager.js";import{registerUpgradeCommand as ke}from"./commands/upgrade.js";import{registerToolsCommand as Ce}from"./commands/tools.js";import{registerIntegrationCommand as Te}from"./commands/integration.js";import{registerRoomCommand as Ae}from"./commands/room.js";import{SessionManager as De}from"./session/manager.js";import{SERVERS as N,regionToUrl as be,urlToRegion as $e}from"./region.js";import{getCurrentVersion as Y,handleStartupCrashCheck as Ee}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Ue}from"./upgrade/scheduler.js";import{detectAgents as Oe}from"./agents/detect.js";import{augmentProcessPath as Z}from"./env-path.js";Z();const ee=Y();import{getCachedAgentInfos as Ne,resolveAgentInfos as Me}from"./agents/model-registry.js";import{initCliLogReporter as Pe,reportLog as w}from"./log-reporter.js";import{isNativeFusionEnabled as xe}from"./native-fusion/config.js";import{NativeSessionFusionService as Le}from"./native-fusion/service.js";import{startDaemonLogRetention as _e}from"./daemon-log.js";import{startDaemonIpcServer as He}from"./daemon-ipc/server.js";import{createAuthorizedActionIpcHandlers as ze}from"./room/authorized-action-ipc.js";import{PendingAuthorizedActionStore as Ve}from"./room/pending-authorized-actions.js";import{HttpDeviceAuthorizationClient as Be}from"./room/device-authorization-client.js";import{AuthorizedActionCoordinator as Fe}from"./room/authorized-action-coordinator.js";import{RoomSessionRegistry as Ge}from"./room/session-registry.js";import{createRoomSessionRegistryIpcHandlers as Je}from"./room/session-registry-ipc.js";import{RoomInvocationTokenAuthority as je}from"./room/invocation-token.js";import{createRoomInvocationTokenIpcHandlers as We}from"./room/invocation-token-ipc.js";import{HttpRoomBindingClient as Ke}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as qe}from"./room/managed-room-binding.js";import{createRoomBindingIpcHandlers as Qe}from"./room/room-binding-ipc.js";import{RoomHostSessionObservationStore as Xe}from"./room/host-session-observations.js";import{createRoomHostSessionObservationIpcHandlers as Ye}from"./room/host-session-observation-ipc.js";import{runWorkBuddySessionHook as Ze}from"./integrations/workbuddy-session-hook.js";import{runRoomSessionHook as eo}from"./integrations/room-session-hook.js";import{RoomJsonlMirror as oe}from"./room/jsonl-mirror.js";import{RoomMirrorSyncService as oo}from"./room/mirror-sync-service.js";import{RoomActivationService as no}from"./room/activation-service.js";import{RoomActivationAudit as to}from"./room/activation-audit.js";import{RoomAttachmentCache as ro}from"./room/attachment-cache.js";import{RoomAttachmentUploader as io}from"./room/attachment-upload.js";import{runRoomToolServer as so}from"./room/tool-server.js";import{RoomAuthorizedActionExecutor as ao}from"./room/room-authorized-action-executor.js";import{createRoomOperatorIpcHandlers as co}from"./room/operator-ipc.js";import{createManagedRoomSessionRuntime as mo}from"./room/managed-room-session-runtime.js";import{openExternalUrl as lo}from"./commands/pair-browser.js";const ne=de(),po=W("daemon.pid");function go(){try{const o=O.readFileSync(po,"utf-8").trim();if(o.startsWith("{")){const s=JSON.parse(o),c=Number(s.pid);return Number.isInteger(c)&&c>0?c:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function uo(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function ho(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 te(o,n=5e3){const s=Date.now();for(;Date.now()-s<n;){try{process.kill(o,0)}catch{return!0}await new Promise(c=>setTimeout(c,100))}try{return process.kill(o,0),!1}catch{return!0}}const t=new me;t.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(ee),t.enablePositionalOptions(),Te(t),Ae(t),t.option("--api <url>","Server URL override").option("--name <name>","Machine name",U.hostname()).action(async o=>{const n=i(),s=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await he(s,o.name)}),t.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=W("env.json");try{const e=JSON.parse(O.readFileSync(n,"utf-8"));for(const[r,d]of Object.entries(e))process.env[r]||(process.env[r]=d);Z()}catch{}const s=_e();Ie()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(s),process.exit(0));const c=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),M=async(e,r)=>{console.log(`[${new Date().toISOString()}] ${r} (PID ${e})`),process.kill(e,"SIGTERM"),await te(e)||(process.kill(e,"SIGKILL"),await te(e,2e3))};try{const e=go();if(e&&e!==process.pid)try{process.kill(e,0),c?await M(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 A=Se(process.env,ne)?[]:ve(process.pid);if(A.length>0)if(c)for(const e of A)try{await M(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${A[0]}), skipping duplicate start`),process.exit(0);const m=fe();Q(process.pid,m,{version:ee}),X(process.pid,void 0,m),process.on("exit",()=>{K(process.pid,m),q(m)}),await Ee()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));let a=i();const g=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??a.serverUrl??N.cn.url,f=Y(),D=Oe(),v=D.map(e=>e.type),b=Ne(D),P=new Ve,l=new Ge,u=new Xe,x=new je(l),p=new Ke(g),ie=new ro({actionClient:p}),se=new io({actionClient:p}),L=new Be(g);let $=null,E=null;const _=await He({handlers:{...ze({store:P,authorizationClient:L,machineName:U.hostname(),machineOs:`${process.platform} ${U.release()}`,agentList:v,openPublicLoginUrl:lo}),...Je({registry:l,observations:u,sessionResolver:p,getMachineCredentials:()=>{const e=i();return{machineId:e.machineId,machineToken:e.machineToken}}}),...Ye({store:u,registry:l,getMachineId:()=>i().machineId,onRegistrationIdle:e=>{$?.drain(e).catch(r=>{console.error("[room.activation] idle drain failed",r)})}}),...We({authority:x,getMachineCredentials:()=>{const e=i();return{machineId:e.machineId,machineToken:e.machineToken}}}),...Qe({authority:x,registry:l,bindingClient:p,getMachineCredentials:()=>{const e=i();return{machineId:e.machineId,machineToken:e.machineToken}},mirrorFactory:e=>new oe(e),attachmentCache:ie,attachmentUploader:se,observations:u}),...co({registry:l,actionClient:p,getMachineCredentials:()=>{const e=i();return{machineId:e.machineId,machineToken:e.machineToken}},managedRoomSessions:{execute:(e,r)=>{if(!E)throw Object.assign(new Error("Managed Room runtime is still starting."),{code:"managed_room_initializing"});return E.execute(e,r)}}})}}),ae=new ao({registry:l,observations:u,actionClient:p,getMachineCredentials:()=>{const e=i();return{machineId:e.machineId,machineToken:e.machineToken}}}),H=new Fe(P,L,ae,({machineId:e,machineToken:r})=>{j({...i(),machineId:e,machineToken:r,serverUrl:g})});let y=!1;const R=()=>{y||(y=!0,_.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",R),process.once("SIGTERM",R),!a.machineToken){for(console.log(`[${new Date().toISOString()}] Waiting for browser device authorization...`);!y&&!a.machineToken;)await H.runOnce(),a=i(),a.machineToken||await new Promise(e=>setTimeout(e,1e3));if(y)return}process.removeListener("SIGINT",R),process.removeListener("SIGTERM",R);const z=setInterval(()=>{H.runOnce().catch(e=>{console.error("[room.authorization] pending action retry failed",e)})},1e3);z.unref?.();const V=`${uo(g)}/relay/machine`,B=a.machineToken;if(!B)return;a.machineId&&Pe(g,a.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${V}... (v${f}) agents: ${v.join(",")}`),w({level:"info",wsEvent:"daemon.start",metadata:{version:f,agents:v}});let k=null,C=null;const F=pe(a.autoUpgrade),h=new ge({serverUrl:V,machineToken:B,cliVersion:f,agentList:v,autoUpgradePolicy:F,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),w({level:"info",wsEvent:"daemon.connected"}),b.some(e=>e.models.length>0)&&h.sendEvent({type:"event",event:"machine.agents",payload:{agentList:v,agents:b}}),Me(D,{serverUrl:a.serverUrl??g,authToken:a.machineToken??a.accessToken}).then(e=>{JSON.stringify(e)!==JSON.stringify(b)&&h.sendEvent({type:"event",event:"machine.agents",payload:{agentList:v,agents:e}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:e,clearUpgradeAttempt:r})=>{const d=e();r(),d&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${d.from} \u2192 ${d.to}`),h.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:d.from,to:d.to}}))}).catch(()=>{}),T.start(),k?.handleConnected(),C?.start()},onDisconnected:e=>{C?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${ho(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}),I.handleReq(e)}});k=xe()?new Le(h):null,C=new oo({registry:l,observations:u,actionClient:p,getMachineCredentials:()=>({machineToken:i().machineToken}),mirrorFactory:e=>new oe(e),onMessages:async(e,r,d,ce)=>{await $?.processMessages(e,r,d,ce)}});const T=new Ue({currentVersion:f,machineId:a.machineId??"unknown-machine",serverUrl:a.serverUrl??g,getIdleState:()=>I.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:r}=await import("./commands/upgrade.js");await r(h,"auto-upgrade",e,{currentVersion:f})}});T.setPolicy(F);const I=new De(h,k,f,T,new qe({registry:l,observations:u,bindingClient:p,getMachineCredentials:()=>{const e=i();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${m}`}));E=mo({sessionManager:I,registry:l,observations:u,bindingClient:p,serverUrl:g,getMachineCredentials:()=>{const e=i();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${m}`}),$=new no({registry:l,observations:u,activate:e=>I.activateRoomSession(e),abort:e=>I.abortRoomSession(e),audit:new to}),O.mkdirSync(ne,{recursive:!0}),Q(process.pid,m,{version:f}),X(process.pid,void 0,m),h.connect(),process.stdin.resume();let G=!1;const J=async()=>{if(!G){G=!0,console.log(S.gray(`
2
- Disconnecting...`)),w({level:"info",wsEvent:"daemon.stop"}),clearInterval(s),clearInterval(z);try{await I.cleanup(),T.stop(),k?.stop(),C?.stop(),h.disconnect(),await _.close(),K(process.pid,m),q(m)}finally{process.exit(0)}}};process.on("SIGINT",()=>{J()}),process.on("SIGTERM",()=>{J()})}),t.command("workbuddy-session-hook",{hidden:!0}).description("(internal) Receive a trusted WorkBuddy plugin lifecycle event").action(async()=>{await Ze()}),t.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").action(async o=>{o!=="codex"&&o!=="claude_code"&&o!=="workbuddy"||await eo(o)}),t.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").action(async()=>{await so()});const re=t.command("config").description("View or modify configuration");re.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${le()}`);const n={...i()};if(n.machineToken&&(n.machineToken=n.machineToken.slice(0,12)+"..."),n.serverUrl){const s=$e(n.serverUrl);console.log(`Region: ${s} (${N[s].label})`)}console.log(JSON.stringify(n,null,2))}),re.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 s=i();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(S.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=n;s.serverUrl=be(c),j(s),console.log(S.green(`\u2713 Server set to ${N[c].label}`)),console.log(S.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(S.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),ue(t),we(t),ye(t),Re(t),ke(t),Ce(t),t.parse();
1
+ import{Command as oe}from"commander";import v from"chalk";import ne from"node:os";import $ from"node:fs";import{loadConfig as d,saveConfig as te,getConfigPath as re,getShennianDir as ie,resolveShennianPath as B,normalizeAutoUpgradePolicy as se}from"./config/index.js";import{CliRelayClient as ae}from"./relay/client.js";import{registerPairCommand as ce,runSmartStart as me}from"./commands/pair.js";import{clearDaemonPidIfOwner as F,createDaemonInstanceId as le,clearDaemonLauncher as G,findRunningDaemonProcessIds as de,isRemoteAccessDisabled as pe,isIsolatedDaemonE2eInstance as ge,registerDaemonCommand as ue,writeDaemonPid as J,writeDaemonLauncher as j}from"./commands/daemon.js";import{registerAgentCommand as he}from"./commands/agent.js";import{registerRuntimeCommand as fe}from"./commands/runtime.js";import{registerManagerCommand as ve}from"./commands/manager.js";import{registerUpgradeCommand as Se}from"./commands/upgrade.js";import{registerToolsCommand as we}from"./commands/tools.js";import{registerIntegrationCommand as Ie}from"./commands/integration.js";import{registerRoomCommand as Re}from"./commands/room.js";import{SessionManager as ye}from"./session/manager.js";import{SERVERS as A,regionToUrl as ke,urlToRegion as Ce}from"./region.js";import{getCurrentVersion as K,handleStartupCrashCheck as De}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Te}from"./upgrade/scheduler.js";import{detectAgents as Ue}from"./agents/detect.js";import{augmentProcessPath as z}from"./env-path.js";z();const W=K();import{getCachedAgentInfos as Ee,resolveAgentInfos as $e}from"./agents/model-registry.js";import{initCliLogReporter as Ae,reportLog as S}from"./log-reporter.js";import{isNativeFusionEnabled as be}from"./native-fusion/config.js";import{NativeSessionFusionService as Oe}from"./native-fusion/service.js";import{startDaemonLogRetention as Ne}from"./daemon-log.js";import{startDaemonIpcServer as Pe}from"./daemon-ipc/server.js";import{RoomSessionRegistry as Me}from"./room/session-registry.js";import{HttpRoomBindingClient as Le}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as _e}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as xe}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Ve}from"./room/mirror-sync-service.js";import{RoomActivationService as He}from"./room/activation-service.js";import{RoomActivationAudit as Be}from"./room/activation-audit.js";import{RoomAttachmentCache as Fe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Ge}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Je}from"./room/managed-context.js";import{createManagedRoomIpcHandlers as je}from"./room/managed-room-ipc.js";const q=ie(),Ke=B("daemon.pid");function ze(){try{const o=$.readFileSync(Ke,"utf-8").trim();if(o.startsWith("{")){const r=JSON.parse(o),s=Number(r.pid);return Number.isInteger(s)&&s>0?s:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function We(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function b(){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 qe(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 Q(o,n=5e3){const r=Date.now();for(;Date.now()-r<n;){try{process.kill(o,0)}catch{return!0}await new Promise(s=>setTimeout(s,100))}try{return process.kill(o,0),!1}catch{return!0}}const t=new oe;t.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(W),t.enablePositionalOptions(),Ie(t),Re(t),t.option("--api <url>","Server URL override").option("--name <name>","Machine name",ne.hostname()).action(async o=>{const n=d(),r=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await me(r,o.name)}),t.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=B("env.json");try{const e=JSON.parse($.readFileSync(n,"utf-8"));for(const[i,c]of Object.entries(e))process.env[i]||(process.env[i]=c);z()}catch{}const r=Ne();pe()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(r),process.exit(0));const s=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),O=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await Q(e)||(process.kill(e,"SIGKILL"),await Q(e,2e3))};try{const e=ze();if(e&&e!==process.pid)try{process.kill(e,0),s?await O(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 D=ge(process.env,q)?[]:de(process.pid);if(D.length>0)if(s)for(const e of D)try{await O(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${D[0]}), skipping duplicate start`),process.exit(0);const m=le();J(process.pid,m,{version:W}),j(process.pid,void 0,m),process.on("exit",()=>{F(process.pid,m),G(m)}),await De()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));const a=d(),g=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??a.serverUrl??A.cn.url,p=K(),T=Ue(),u=T.map(e=>e.type),U=Ee(T),w=new Me,I=new xe,N=new Je(w),h=new Le(g),Y=new Fe({actionClient:h}),Z=new Ge({actionClient:h});let P=null;const E=await Pe({handlers:{...je({authority:N,actionClient:h,attachmentCache:Y,attachmentUploader:Z,observations:I,getMachineCredentials:()=>({machineToken:d().machineToken})})}});let M=!1;const R=()=>{M||(M=!0,E.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",R),process.once("SIGTERM",R),!a.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await E.close(),process.exitCode=1;return}process.removeListener("SIGINT",R),process.removeListener("SIGTERM",R);const L=`${We(g)}/relay/machine`,_=a.machineToken;if(!_)return;a.machineId&&Ae(g,a.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${L}... (v${p}) agents: ${u.join(",")}`),S({level:"info",wsEvent:"daemon.start",metadata:{version:p,agents:u}});let y=null,k=null;const x=se(a.autoUpgrade),l=new ae({serverUrl:L,machineToken:_,cliVersion:p,agentList:u,autoUpgradePolicy:x,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),S({level:"info",wsEvent:"daemon.connected"}),U.some(e=>e.models.length>0)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:u,agents:U}}),$e(T,{serverUrl:a.serverUrl??g,authToken:a.machineToken??a.accessToken}).then(e=>{JSON.stringify(e)!==JSON.stringify(U)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:u,agents:e}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:e,clearUpgradeAttempt:i})=>{const c=e();i(),c&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${c.from} \u2192 ${c.to}`),l.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:c.from,to:c.to}}))}).catch(()=>{}),C.start(),y?.handleConnected(),k?.start()},onDisconnected:e=>{k?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${qe(e)}`),S({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}`),S({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),f.handleReq(e)}});y=be()?new Oe(l):null,k=new Ve({registry:w,observations:I,actionClient:h,getMachineCredentials:()=>({machineToken:d().machineToken}),onMessages:async(e,i,c,ee)=>{await P?.processMessages(e,i,c,ee)}});const C=new Te({currentVersion:p,machineId:a.machineId??"unknown-machine",serverUrl:a.serverUrl??g,getIdleState:()=>f.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(l,"auto-upgrade",e,{currentVersion:p})}});C.setPolicy(x);const f=new ye(l,y,p,C,new _e({registry:w,observations:I,bindingClient:h,getMachineCredentials:()=>{const e=d();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${m}`}));P=new He({registry:w,observations:I,activate:e=>f.activateRoomSession(e),issueManagedRoomContext:e=>{const i=d().machineToken;if(!i)throw new Error("machine_not_authorized");return N.issue({registryKey:e,machineCredential:i})},abort:e=>f.abortRoomSession(e),audit:new Be}),$.mkdirSync(q,{recursive:!0}),J(process.pid,m,{version:p}),j(process.pid,void 0,m),l.connect(),process.stdin.resume();let V=!1;const H=async()=>{if(!V){V=!0,console.log(v.gray(`
3
+ Disconnecting...`)),S({level:"info",wsEvent:"daemon.stop"}),clearInterval(r);try{await f.cleanup(),C.stop(),y?.stop(),k?.stop(),l.disconnect(),await E.close(),F(process.pid,m),G(m)}finally{process.exit(0)}}};process.on("SIGINT",()=>{H()}),process.on("SIGTERM",()=>{H()})}),t.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>b()),t.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(()=>b()),t.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>b());const X=t.command("config").description("View or modify configuration");X.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${re()}`);const n={...d()};if(n.machineToken&&(n.machineToken=n.machineToken.slice(0,12)+"..."),n.serverUrl){const r=Ce(n.serverUrl);console.log(`Region: ${r} (${A[r].label})`)}console.log(JSON.stringify(n,null,2))}),X.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 r=d();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(v.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const s=n;r.serverUrl=ke(s),te(r),console.log(v.green(`\u2713 Server set to ${A[s].label}`)),console.log(v.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(v.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),ce(t),ue(t),he(t),fe(t),ve(t),Se(t),we(t),t.parse();
@@ -28,6 +28,10 @@ export type ManagerRecord = {
28
28
  workDir: string;
29
29
  machineId?: string | null;
30
30
  modelId: 'codex' | 'claude';
31
+ /** Real provider for new Manager-mode sessions; legacy sessions use manager. */
32
+ agentType?: 'codex' | 'claude' | 'manager';
33
+ /** Real provider model for new Manager-mode sessions. */
34
+ providerModelId?: string | null;
31
35
  defaultWorkerAgentType?: AgentType | null;
32
36
  defaultWorkerModelId?: string | null;
33
37
  status: ManagerStatus;
@@ -36,6 +40,17 @@ export type ManagerRecord = {
36
40
  createdAt: string;
37
41
  updatedAt: string;
38
42
  };
43
+ export type ManagerInboxEvent = {
44
+ id: string;
45
+ managerSessionId: string;
46
+ workerSessionId: string;
47
+ kind: 'worker.final' | 'worker.error' | 'worker.aborted' | 'worker.health';
48
+ priority: 'normal' | 'high';
49
+ runId?: string | null;
50
+ summary: string;
51
+ createdAt: string;
52
+ updatedAt: string;
53
+ };
39
54
  export type ManagerVisibleSessionRecord = Omit<ManagedWorkerRecord, 'managedBy'> & {
40
55
  managedBy: string | null;
41
56
  };
@@ -51,6 +66,7 @@ type RegistryFile = {
51
66
  managers: Record<string, ManagerRecord>;
52
67
  workers: Record<string, ManagedWorkerRecord>;
53
68
  replyTargets: Record<string, ReplyTargetRecord>;
69
+ inbox: Record<string, ManagerInboxEvent[]>;
54
70
  };
55
71
  export declare class ManagerRegistry {
56
72
  load(): RegistryFile;
@@ -61,11 +77,16 @@ export declare class ManagerRegistry {
61
77
  workDir: string;
62
78
  machineId?: string | null;
63
79
  modelId: 'codex' | 'claude';
80
+ agentType?: 'codex' | 'claude' | 'manager';
81
+ providerModelId?: string | null;
64
82
  defaultWorkerAgentType?: AgentType | null;
65
83
  defaultWorkerModelId?: string | null;
66
84
  status?: ManagerStatus;
67
85
  }): ManagerRecord;
68
86
  getManager(sessionId: string): ManagerRecord | undefined;
87
+ enqueueInboxEvent(input: Omit<ManagerInboxEvent, 'id' | 'createdAt' | 'updatedAt'>): ManagerInboxEvent | null;
88
+ listInboxEvents(managerSessionId: string): ManagerInboxEvent[];
89
+ removeInboxEvents(managerSessionId: string, eventIds: readonly string[]): void;
69
90
  addWorker(input: {
70
91
  managerSessionId: string;
71
92
  sessionId?: string;
@@ -1,2 +1,2 @@
1
- import u from"node:fs";import h from"node:path";import{randomUUID as y}from"node:crypto";import{extractPayloadText as k,isAgentHiddenPayload as A,isSystemControlPayload as T,isToolPayload as w}from"@shennian/wire";import{resolveShennianPath as v}from"../config/index.js";import{listSessionRecords as I,readMessages as M}from"../session/store.js";import{listProjectedSessions as W}from"../session/projection.js";const g=v("manager-registry.json");function l(){return new Date().toISOString()}function D(){return{managers:{},workers:{},replyTargets:{}}}class O{load(){try{const e=JSON.parse(u.readFileSync(g,"utf-8"));return{managers:e.managers??{},workers:e.workers??{},replyTargets:e.replyTargets??{}}}catch{return D()}}save(e){u.mkdirSync(h.dirname(g),{recursive:!0}),u.writeFileSync(g,JSON.stringify(e,null,2))}upsertManager(e){const t=this.load(),r=t.managers[e.sessionId],a=l(),s={sessionId:e.sessionId,agentSessionId:e.agentSessionId??r?.agentSessionId??null,workDir:e.workDir,machineId:e.machineId??r?.machineId??null,modelId:e.modelId,defaultWorkerAgentType:e.defaultWorkerAgentType??r?.defaultWorkerAgentType??null,defaultWorkerModelId:e.defaultWorkerModelId??r?.defaultWorkerModelId??null,status:e.status??r?.status??"idle",managedWorkerSessionIds:r?.managedWorkerSessionIds??[],attachedExternalChannels:r?.attachedExternalChannels??[],createdAt:r?.createdAt??a,updatedAt:a};return t.managers[e.sessionId]=s,this.save(t),s}getManager(e){return this.load().managers[e]}addWorker(e){const t=this.load(),r=t.managers[e.managerSessionId];if(!r)throw new Error("Manager runtime is not registered");const a=e.sessionId??`sess_worker_${y()}`,s=l(),o={sessionId:a,agentType:e.agentType,workDir:e.workDir,managedBy:e.managerSessionId,status:"running",createdAt:s,updatedAt:s,lastActivityAt:s,summary:e.summary??null,agentSessionId:null,runId:null};return t.workers[a]=o,r.managedWorkerSessionIds.includes(a)||r.managedWorkerSessionIds.push(a),r.updatedAt=s,this.save(t),o}updateWorker(e,t){const r=this.load(),a=r.workers[e];if(!a)return;const s={...a,...t,updatedAt:l()};return r.workers[e]=s,this.save(r),s}listWorkers(e,t={}){const r=this.load(),a=r.managers[e];if(!a)return[];const s=Object.values(r.workers).filter(n=>n.workDir===a.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").map(n=>this.decorateManagedWorker(n,t.runningSessionIds)),o=new Set(s.map(n=>n.sessionId)),i=I().filter(n=>n.workDir===a.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").filter(n=>!o.has(n.sessionId)).map(n=>this.sessionRecordToWorker(n,t.runningSessionIds)),c=new Set([...s,...i].map(n=>n.sessionId)),f=(t.projectedSessions??W()).filter(n=>n.workDir===a.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").filter(n=>!c.has(n.id)).map(n=>this.projectedSessionToWorker(n,t.runningSessionIds));return[...s,...i,...f].sort((n,S)=>S.lastActivityAt.localeCompare(n.lastActivityAt))}getWorkerForManager(e,t){const r=this.load(),a=r.managers[e],s=r.workers[t];if(!a)return;if(s)return s.workDir!==a.workDir||s.agentType==="manager"?void 0:this.decorateManagedWorker(s);const o=I().find(i=>i.sessionId===t);if(!(!o||o.workDir!==a.workDir||o.agentType==="manager"))return this.sessionRecordToWorker(o)}decorateManagedWorker(e,t){const r=t?.has(e.sessionId)?"running":e.status,a=this.readTranscriptSummary(e.sessionId,e.summary??null),s=a.lastMessagePreview??e.lastMessagePreview??e.summary??null,o=e.userGoal??a.userGoal??null;return{...e,managedBy:e.managedBy??null,status:r,title:e.title??o??s??e.summary??e.sessionId,userGoal:o,lastMessagePreview:s,summary:e.summary??s,canResume:e.canResume??e.status!=="aborted",source:e.source??"manager_registry",role:"managed_worker"}}sessionRecordToWorker(e,t){const r=this.readTranscriptSummary(e.sessionId,e.lastMessagePreview??null),a=r.lastMessagePreview??e.lastMessagePreview??null,s=r.userGoal,o=t?.has(e.sessionId)?"running":e.status==="failed"?"error":e.status==="completed"?"final":"idle";return{sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,managedBy:null,status:o,createdAt:e.createdAt,updatedAt:e.updatedAt,lastActivityAt:e.lastActivityAt,title:s??a??e.sessionId,userGoal:s,lastMessagePreview:a,summary:a??s??null,agentSessionId:e.agentSessionId??null,runId:null,canResume:!!e.agentSessionId,source:"local_index",role:"project_session"}}projectedSessionToWorker(e,t){const r=e.lastMessagePreview??null,a=e.title?.trim()||r,s=t?.has(e.id)?"running":e.status==="failed"?"error":e.status==="completed"?"final":"idle";return{sessionId:e.id,agentType:e.agentType,workDir:e.workDir,managedBy:null,status:s,createdAt:e.createdAt,updatedAt:e.updatedAt,lastActivityAt:e.lastActivityAt||e.updatedAt,title:a??e.id,userGoal:a??null,lastMessagePreview:r,summary:r??a??null,agentSessionId:e.agentSessionId??null,runId:e.activity?.runId??null,canResume:!0,source:"app_projection",role:"project_session"}}readTranscriptSummary(e,t){const r=M(e,{limit:200});if(!r.length)return{userGoal:null,lastMessagePreview:t};const a=[...r].sort((i,c)=>i.ts-c.ts),s=a.find(i=>i.role==="user"),o=[...a].reverse().find(i=>m(i));return{userGoal:s?p(m(s),160):null,lastMessagePreview:p(m(o),180)??t}}createReplyTarget(e){const t=this.load(),r={replyTarget:`rt_${y()}`,managerSessionId:e.managerSessionId,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId??null,createdAt:l()};return t.replyTargets[r.replyTarget]=r,this.save(t),r}getReplyTarget(e){return this.load().replyTargets[e]}getLatestReplyTargetForManager(e){return Object.values(this.load().replyTargets).filter(t=>t.managerSessionId===e).sort((t,r)=>r.createdAt.localeCompare(t.createdAt))[0]}}function m(d){return!d||w(d.payload)||A(d.payload)||T(d.payload)?null:k(d.payload).replace(/\r\n/g,`
2
- `).trim()||null}function p(d,e){return d?d.length>e?`${d.slice(0,e)}...`:d:null}export{O as ManagerRegistry};
1
+ import g from"node:fs";import S from"node:path";import{randomUUID as m}from"node:crypto";import{extractPayloadText as A,isAgentHiddenPayload as w,isSystemControlPayload as T,isToolPayload as v}from"@shennian/wire";import{resolveShennianPath as M}from"../config/index.js";import{listSessionRecords as p,readMessages as W}from"../session/store.js";import{listProjectedSessions as D}from"../session/projection.js";const y=M("manager-registry.json");function u(){return new Date().toISOString()}function x(){return{managers:{},workers:{},replyTargets:{},inbox:{}}}class E{load(){try{const e=JSON.parse(g.readFileSync(y,"utf-8"));return{managers:e.managers??{},workers:e.workers??{},replyTargets:e.replyTargets??{},inbox:e.inbox??{}}}catch{return x()}}save(e){g.mkdirSync(S.dirname(y),{recursive:!0}),g.writeFileSync(y,JSON.stringify(e,null,2))}upsertManager(e){const t=this.load(),r=t.managers[e.sessionId],a=u(),s={sessionId:e.sessionId,agentSessionId:e.agentSessionId??r?.agentSessionId??null,workDir:e.workDir,machineId:e.machineId??r?.machineId??null,modelId:e.modelId,agentType:e.agentType??r?.agentType??"manager",providerModelId:e.providerModelId??r?.providerModelId??null,defaultWorkerAgentType:e.defaultWorkerAgentType??r?.defaultWorkerAgentType??null,defaultWorkerModelId:e.defaultWorkerModelId??r?.defaultWorkerModelId??null,status:e.status??r?.status??"idle",managedWorkerSessionIds:r?.managedWorkerSessionIds??[],attachedExternalChannels:r?.attachedExternalChannels??[],createdAt:r?.createdAt??a,updatedAt:a};return t.managers[e.sessionId]=s,this.save(t),s}getManager(e){return this.load().managers[e]}enqueueInboxEvent(e){const t=this.load(),r=t.managers[e.managerSessionId],a=t.workers[e.workerSessionId];if(!r||!a||a.managedBy!==r.sessionId||a.workDir!==r.workDir)return null;const s=u(),o=t.inbox[e.managerSessionId]??[],d=o.findIndex(c=>c.workerSessionId===e.workerSessionId&&(e.kind==="worker.health"?c.kind==="worker.health":c.kind===e.kind&&c.runId===e.runId)),l=d>=0?{...o[d],...e,updatedAt:s}:{...e,id:`mie_${m()}`,createdAt:s,updatedAt:s};return d>=0?o[d]=l:o.push(l),t.inbox[e.managerSessionId]=o,this.save(t),l}listInboxEvents(e){return[...this.load().inbox[e]??[]].sort((t,r)=>t.createdAt.localeCompare(r.createdAt))}removeInboxEvents(e,t){if(!t.length)return;const r=this.load(),a=new Set(t);r.inbox[e]=(r.inbox[e]??[]).filter(s=>!a.has(s.id)),this.save(r)}addWorker(e){const t=this.load(),r=t.managers[e.managerSessionId];if(!r)throw new Error("Manager runtime is not registered");const a=e.sessionId??`sess_worker_${m()}`,s=u(),o={sessionId:a,agentType:e.agentType,workDir:e.workDir,managedBy:e.managerSessionId,status:"running",createdAt:s,updatedAt:s,lastActivityAt:s,summary:e.summary??null,agentSessionId:null,runId:null};return t.workers[a]=o,r.managedWorkerSessionIds.includes(a)||r.managedWorkerSessionIds.push(a),r.updatedAt=s,this.save(t),o}updateWorker(e,t){const r=this.load(),a=r.workers[e];if(!a)return;const s={...a,...t,updatedAt:u()};return r.workers[e]=s,this.save(r),s}listWorkers(e,t={}){const r=this.load(),a=r.managers[e];if(!a)return[];const s=Object.values(r.workers).filter(n=>n.workDir===a.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").map(n=>this.decorateManagedWorker(n,t.runningSessionIds)),o=new Set(s.map(n=>n.sessionId)),d=p().filter(n=>n.workDir===a.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").filter(n=>!o.has(n.sessionId)).map(n=>this.sessionRecordToWorker(n,t.runningSessionIds)),l=new Set([...s,...d].map(n=>n.sessionId)),h=(t.projectedSessions??D()).filter(n=>n.workDir===a.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").filter(n=>!l.has(n.id)).map(n=>this.projectedSessionToWorker(n,t.runningSessionIds));return[...s,...d,...h].sort((n,k)=>k.lastActivityAt.localeCompare(n.lastActivityAt))}getWorkerForManager(e,t){const r=this.load(),a=r.managers[e],s=r.workers[t];if(!a)return;if(s)return s.workDir!==a.workDir||s.agentType==="manager"?void 0:this.decorateManagedWorker(s);const o=p().find(d=>d.sessionId===t);if(!(!o||o.workDir!==a.workDir||o.agentType==="manager"))return this.sessionRecordToWorker(o)}decorateManagedWorker(e,t){const r=t?.has(e.sessionId)?"running":e.status,a=this.readTranscriptSummary(e.sessionId,e.summary??null),s=a.lastMessagePreview??e.lastMessagePreview??e.summary??null,o=e.userGoal??a.userGoal??null;return{...e,managedBy:e.managedBy??null,status:r,title:e.title??o??s??e.summary??e.sessionId,userGoal:o,lastMessagePreview:s,summary:e.summary??s,canResume:e.canResume??e.status!=="aborted",source:e.source??"manager_registry",role:"managed_worker"}}sessionRecordToWorker(e,t){const r=this.readTranscriptSummary(e.sessionId,e.lastMessagePreview??null),a=r.lastMessagePreview??e.lastMessagePreview??null,s=r.userGoal,o=t?.has(e.sessionId)?"running":e.status==="failed"?"error":e.status==="completed"?"final":"idle";return{sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,managedBy:null,status:o,createdAt:e.createdAt,updatedAt:e.updatedAt,lastActivityAt:e.lastActivityAt,title:s??a??e.sessionId,userGoal:s,lastMessagePreview:a,summary:a??s??null,agentSessionId:e.agentSessionId??null,runId:null,canResume:!!e.agentSessionId,source:"local_index",role:"project_session"}}projectedSessionToWorker(e,t){const r=e.lastMessagePreview??null,a=e.title?.trim()||r,s=t?.has(e.id)?"running":e.status==="failed"?"error":e.status==="completed"?"final":"idle";return{sessionId:e.id,agentType:e.agentType,workDir:e.workDir,managedBy:null,status:s,createdAt:e.createdAt,updatedAt:e.updatedAt,lastActivityAt:e.lastActivityAt||e.updatedAt,title:a??e.id,userGoal:a??null,lastMessagePreview:r,summary:r??a??null,agentSessionId:e.agentSessionId??null,runId:e.activity?.runId??null,canResume:!0,source:"app_projection",role:"project_session"}}readTranscriptSummary(e,t){const r=W(e,{limit:200});if(!r.length)return{userGoal:null,lastMessagePreview:t};const a=[...r].sort((d,l)=>d.ts-l.ts),s=a.find(d=>d.role==="user"),o=[...a].reverse().find(d=>I(d));return{userGoal:s?f(I(s),160):null,lastMessagePreview:f(I(o),180)??t}}createReplyTarget(e){const t=this.load(),r={replyTarget:`rt_${m()}`,managerSessionId:e.managerSessionId,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId??null,createdAt:u()};return t.replyTargets[r.replyTarget]=r,this.save(t),r}getReplyTarget(e){return this.load().replyTargets[e]}getLatestReplyTargetForManager(e){return Object.values(this.load().replyTargets).filter(t=>t.managerSessionId===e).sort((t,r)=>r.createdAt.localeCompare(t.createdAt))[0]}}function I(i){return!i||v(i.payload)||w(i.payload)||T(i.payload)?null:A(i.payload).replace(/\r\n/g,`
2
+ `).trim()||null}function f(i,e){return i?i.length>e?`${i.slice(0,e)}...`:i:null}export{E as ManagerRegistry};
@@ -43,6 +43,8 @@ export declare class ManagerRuntimeService {
43
43
  agentSessionId?: string | null;
44
44
  workDir: string;
45
45
  modelId: 'codex' | 'claude';
46
+ agentType?: 'codex' | 'claude' | 'manager';
47
+ providerModelId?: string | null;
46
48
  defaultWorkerAgentType?: AgentType | null;
47
49
  defaultWorkerModelId?: string | null;
48
50
  status?: 'idle' | 'running' | 'interrupting';
@@ -63,13 +65,15 @@ export declare class ManagerRuntimeService {
63
65
  private dispatchChatEnqueue;
64
66
  private tryDirectWeChatRpaReply;
65
67
  private sendManagedWeComReply;
66
- private wakeManagerForWorker;
68
+ private enqueueWorkerEvent;
67
69
  private handleExternalMessage;
68
70
  private dispatchExternalMessage;
69
71
  private scanWorkerHealth;
70
72
  private interruptAndResumeManager;
71
73
  bindManagerAdapterEvents(sessionId: string, adapter: AgentAdapter): void;
72
74
  updateManagerStatus(sessionId: string, status: 'idle' | 'running' | 'interrupting'): void;
75
+ private drainManagerInbox;
76
+ private dispatchManagerContinuation;
73
77
  getExternalChannelStatus(managerSessionId: string): ExternalChannelSessionStatus | null;
74
78
  getManagerExternalChannelSystemPrompt(managerSessionId: string): string;
75
79
  }