shennian 0.2.137 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/README.md +3 -1
  2. package/dist/publish-build-manifest.json +273 -343
  3. package/dist/src/agents/command-spec.js +2 -2
  4. package/dist/src/agents/detect.js +1 -1
  5. package/dist/src/agents/model-registry/discovery.js +2 -2
  6. package/dist/src/agents/model-registry/parsers.d.ts +1 -0
  7. package/dist/src/agents/model-registry/parsers.js +4 -4
  8. package/dist/src/agents/native-capabilities.d.ts +10 -0
  9. package/dist/src/agents/native-capabilities.js +1 -0
  10. package/dist/src/agents/platform-instructions.js +5 -5
  11. package/dist/src/agents/workbuddy.d.ts +44 -0
  12. package/dist/src/agents/workbuddy.js +3 -0
  13. package/dist/src/commands/daemon.d.ts +1 -0
  14. package/dist/src/commands/daemon.js +9 -9
  15. package/dist/src/commands/integration.d.ts +2 -0
  16. package/dist/src/commands/integration.js +1 -0
  17. package/dist/src/commands/manager.js +1 -2
  18. package/dist/src/commands/pair-browser.d.ts +43 -0
  19. package/dist/src/commands/pair-browser.js +1 -0
  20. package/dist/src/commands/pair.d.ts +2 -0
  21. package/dist/src/commands/pair.js +11 -8
  22. package/dist/src/commands/room.d.ts +13 -0
  23. package/dist/src/commands/room.js +4 -0
  24. package/dist/src/commands/upgrade.d.ts +3 -0
  25. package/dist/src/commands/upgrade.js +1 -1
  26. package/dist/src/config/index.d.ts +3 -1
  27. package/dist/src/config/index.js +2 -1
  28. package/dist/src/daemon-ipc/client.d.ts +8 -0
  29. package/dist/src/daemon-ipc/client.js +3 -0
  30. package/dist/src/daemon-ipc/paths.d.ts +7 -0
  31. package/dist/src/daemon-ipc/paths.js +1 -0
  32. package/dist/src/daemon-ipc/protocol.d.ts +30 -0
  33. package/dist/src/daemon-ipc/protocol.js +1 -0
  34. package/dist/src/daemon-ipc/secret.d.ts +3 -0
  35. package/dist/src/daemon-ipc/secret.js +3 -0
  36. package/dist/src/daemon-ipc/server.d.ts +13 -0
  37. package/dist/src/daemon-ipc/server.js +3 -0
  38. package/dist/src/index.js +2 -2
  39. package/dist/src/integrations/claude-room-plugin-lifecycle.d.ts +47 -0
  40. package/dist/src/integrations/claude-room-plugin-lifecycle.js +1 -0
  41. package/dist/src/integrations/codex-room-plugin-lifecycle.d.ts +49 -0
  42. package/dist/src/integrations/codex-room-plugin-lifecycle.js +1 -0
  43. package/dist/src/integrations/room-plugin-bundles.d.ts +23 -0
  44. package/dist/src/integrations/room-plugin-bundles.js +1 -0
  45. package/dist/src/integrations/room-plugin-lifecycle-common.d.ts +17 -0
  46. package/dist/src/integrations/room-plugin-lifecycle-common.js +1 -0
  47. package/dist/src/integrations/room-plugin-shared.d.ts +23 -0
  48. package/dist/src/integrations/room-plugin-shared.js +2 -0
  49. package/dist/src/integrations/room-session-hook.d.ts +30 -0
  50. package/dist/src/integrations/room-session-hook.js +2 -0
  51. package/dist/src/integrations/workbuddy-plugin-bundle.d.ts +15 -0
  52. package/dist/src/integrations/workbuddy-plugin-bundle.js +1 -0
  53. package/dist/src/integrations/workbuddy-plugin-lifecycle.d.ts +52 -0
  54. package/dist/src/integrations/workbuddy-plugin-lifecycle.js +2 -0
  55. package/dist/src/integrations/workbuddy-session-hook.d.ts +22 -0
  56. package/dist/src/integrations/workbuddy-session-hook.js +1 -0
  57. package/dist/src/manager/prompt.d.ts +1 -1
  58. package/dist/src/manager/prompt.js +1 -1
  59. package/dist/src/manager/retired-external-channel-compat.d.ts +78 -0
  60. package/dist/src/manager/retired-external-channel-compat.js +1 -0
  61. package/dist/src/manager/runtime.d.ts +1 -3
  62. package/dist/src/manager/runtime.js +7 -7
  63. package/dist/src/relay/client.d.ts +2 -0
  64. package/dist/src/relay/client.js +1 -1
  65. package/dist/src/room/activation-audit.d.ts +15 -0
  66. package/dist/src/room/activation-audit.js +2 -0
  67. package/dist/src/room/activation-policy.d.ts +8 -0
  68. package/dist/src/room/activation-policy.js +1 -0
  69. package/dist/src/room/activation-service.d.ts +25 -0
  70. package/dist/src/room/activation-service.js +2 -0
  71. package/dist/src/room/attachment-cache.d.ts +40 -0
  72. package/dist/src/room/attachment-cache.js +2 -0
  73. package/dist/src/room/attachment-upload.d.ts +20 -0
  74. package/dist/src/room/attachment-upload.js +1 -0
  75. package/dist/src/room/authorized-action-coordinator.d.ts +40 -0
  76. package/dist/src/room/authorized-action-coordinator.js +1 -0
  77. package/dist/src/room/authorized-action-ipc.d.ts +11 -0
  78. package/dist/src/room/authorized-action-ipc.js +1 -0
  79. package/dist/src/room/device-authorization-client.d.ts +49 -0
  80. package/dist/src/room/device-authorization-client.js +1 -0
  81. package/dist/src/room/host-session-observation-ipc.d.ts +9 -0
  82. package/dist/src/room/host-session-observation-ipc.js +1 -0
  83. package/dist/src/room/host-session-observations.d.ts +54 -0
  84. package/dist/src/room/host-session-observations.js +2 -0
  85. package/dist/src/room/invite-bootstrap.d.ts +35 -0
  86. package/dist/src/room/invite-bootstrap.js +1 -0
  87. package/dist/src/room/invocation-token-ipc.d.ts +9 -0
  88. package/dist/src/room/invocation-token-ipc.js +1 -0
  89. package/dist/src/room/invocation-token.d.ts +53 -0
  90. package/dist/src/room/invocation-token.js +1 -0
  91. package/dist/src/room/jsonl-mirror.d.ts +73 -0
  92. package/dist/src/room/jsonl-mirror.js +6 -0
  93. package/dist/src/room/managed-room-session.d.ts +143 -0
  94. package/dist/src/room/managed-room-session.js +3 -0
  95. package/dist/src/room/mirror-sync-service.d.ts +33 -0
  96. package/dist/src/room/mirror-sync-service.js +1 -0
  97. package/dist/src/room/operator-ipc.d.ts +11 -0
  98. package/dist/src/room/operator-ipc.js +1 -0
  99. package/dist/src/room/pending-authorized-actions.d.ts +69 -0
  100. package/dist/src/room/pending-authorized-actions.js +3 -0
  101. package/dist/src/room/room-action-client.d.ts +208 -0
  102. package/dist/src/room/room-action-client.js +1 -0
  103. package/dist/src/room/room-action-ipc.d.ts +33 -0
  104. package/dist/src/room/room-action-ipc.js +1 -0
  105. package/dist/src/room/room-action-schema.d.ts +3 -0
  106. package/dist/src/room/room-action-schema.js +1 -0
  107. package/dist/src/room/room-authorized-action-executor.d.ts +23 -0
  108. package/dist/src/room/room-authorized-action-executor.js +1 -0
  109. package/dist/src/room/room-binding-client.d.ts +51 -0
  110. package/dist/src/room/room-binding-client.js +1 -0
  111. package/dist/src/room/room-binding-ipc.d.ts +29 -0
  112. package/dist/src/room/room-binding-ipc.js +1 -0
  113. package/dist/src/room/session-registry-ipc.d.ts +14 -0
  114. package/dist/src/room/session-registry-ipc.js +1 -0
  115. package/dist/src/room/session-registry.d.ts +128 -0
  116. package/dist/src/room/session-registry.js +2 -0
  117. package/dist/src/room/tool-schema.d.ts +114 -0
  118. package/dist/src/room/tool-schema.js +1 -0
  119. package/dist/src/room/tool-server.d.ts +5 -0
  120. package/dist/src/room/tool-server.js +3 -0
  121. package/dist/src/session/handlers/agent-capabilities.d.ts +3 -0
  122. package/dist/src/session/handlers/agent-capabilities.js +1 -0
  123. package/dist/src/session/handlers/agent-config.js +1 -1
  124. package/dist/src/session/handlers/chat.js +2 -2
  125. package/dist/src/session/handlers/control.js +1 -1
  126. package/dist/src/session/handlers/fs.d.ts +1 -0
  127. package/dist/src/session/handlers/fs.js +1 -1
  128. package/dist/src/session/manager.d.ts +16 -6
  129. package/dist/src/session/manager.js +1 -1
  130. package/dist/src/session/types.d.ts +5 -0
  131. package/dist/src/upgrade/engine.d.ts +14 -1
  132. package/dist/src/upgrade/engine.js +2 -2
  133. package/dist/src/upgrade/scheduler.d.ts +45 -0
  134. package/dist/src/upgrade/scheduler.js +1 -0
  135. package/node_modules/@shennian/wire/dist/agent-capabilities.d.ts +17 -0
  136. package/node_modules/@shennian/wire/dist/agent-capabilities.d.ts.map +1 -0
  137. package/node_modules/@shennian/wire/dist/agent-capabilities.js +2 -0
  138. package/node_modules/@shennian/wire/dist/agent-capabilities.js.map +1 -0
  139. package/node_modules/@shennian/wire/dist/index.d.ts +4 -1
  140. package/node_modules/@shennian/wire/dist/index.d.ts.map +1 -1
  141. package/node_modules/@shennian/wire/dist/index.js +4 -1
  142. package/node_modules/@shennian/wire/dist/index.js.map +1 -1
  143. package/node_modules/@shennian/wire/dist/machine.d.ts +1 -0
  144. package/node_modules/@shennian/wire/dist/machine.d.ts.map +1 -1
  145. package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
  146. package/node_modules/@shennian/wire/dist/methods.d.ts.map +1 -1
  147. package/node_modules/@shennian/wire/dist/project.d.ts +12 -0
  148. package/node_modules/@shennian/wire/dist/project.d.ts.map +1 -0
  149. package/node_modules/@shennian/wire/dist/project.js +2 -0
  150. package/node_modules/@shennian/wire/dist/project.js.map +1 -0
  151. package/node_modules/@shennian/wire/dist/room.d.ts +112 -0
  152. package/node_modules/@shennian/wire/dist/room.d.ts.map +1 -0
  153. package/node_modules/@shennian/wire/dist/room.js +4 -0
  154. package/node_modules/@shennian/wire/dist/room.js.map +1 -0
  155. package/node_modules/@shennian/wire/dist/session.d.ts +2 -2
  156. package/node_modules/@shennian/wire/dist/session.d.ts.map +1 -1
  157. package/node_modules/@shennian/wire/dist/session.js +1 -1
  158. package/node_modules/@shennian/wire/dist/session.js.map +1 -1
  159. package/node_modules/@shennian/wire/dist/voice-chief.d.ts +116 -0
  160. package/node_modules/@shennian/wire/dist/voice-chief.d.ts.map +1 -0
  161. package/node_modules/@shennian/wire/dist/voice-chief.js +4 -0
  162. package/node_modules/@shennian/wire/dist/voice-chief.js.map +1 -0
  163. package/package.json +3 -5
  164. package/dist/scripts/wechat-rpa-confirmation.mjs +0 -5
  165. package/dist/src/agents/pi-context.d.ts +0 -42
  166. package/dist/src/agents/pi-context.js +0 -12
  167. package/dist/src/agents/pi.d.ts +0 -69
  168. package/dist/src/agents/pi.js +0 -17
  169. package/dist/src/channels/base.d.ts +0 -144
  170. package/dist/src/channels/base.js +0 -1
  171. package/dist/src/channels/registry.d.ts +0 -8
  172. package/dist/src/channels/registry.js +0 -1
  173. package/dist/src/channels/reply-split.d.ts +0 -1
  174. package/dist/src/channels/reply-split.js +0 -10
  175. package/dist/src/channels/runtime.d.ts +0 -138
  176. package/dist/src/channels/runtime.js +0 -5
  177. package/dist/src/channels/secret-registry.d.ts +0 -35
  178. package/dist/src/channels/secret-registry.js +0 -1
  179. package/dist/src/channels/websocket.d.ts +0 -55
  180. package/dist/src/channels/websocket.js +0 -8
  181. package/dist/src/channels/wechat-channel/anchor.d.ts +0 -10
  182. package/dist/src/channels/wechat-channel/anchor.js +0 -1
  183. package/dist/src/channels/wechat-channel/automation-lane.d.ts +0 -35
  184. package/dist/src/channels/wechat-channel/automation-lane.js +0 -2
  185. package/dist/src/channels/wechat-channel/client.d.ts +0 -283
  186. package/dist/src/channels/wechat-channel/client.js +0 -1
  187. package/dist/src/channels/wechat-channel/cooldown.d.ts +0 -21
  188. package/dist/src/channels/wechat-channel/cooldown.js +0 -1
  189. package/dist/src/channels/wechat-channel/core/fixture-compare.d.ts +0 -52
  190. package/dist/src/channels/wechat-channel/core/fixture-compare.js +0 -1
  191. package/dist/src/channels/wechat-channel/core/media-action-plan.d.ts +0 -3
  192. package/dist/src/channels/wechat-channel/core/media-action-plan.js +0 -1
  193. package/dist/src/channels/wechat-channel/core/schema.d.ts +0 -67
  194. package/dist/src/channels/wechat-channel/core/schema.js +0 -1
  195. package/dist/src/channels/wechat-channel/core/trace-comparator.d.ts +0 -28
  196. package/dist/src/channels/wechat-channel/core/trace-comparator.js +0 -1
  197. package/dist/src/channels/wechat-channel/fingerprint.d.ts +0 -31
  198. package/dist/src/channels/wechat-channel/fingerprint.js +0 -1
  199. package/dist/src/channels/wechat-channel/helper-assets.d.ts +0 -103
  200. package/dist/src/channels/wechat-channel/helper-assets.js +0 -1
  201. package/dist/src/channels/wechat-channel/helper-client.d.ts +0 -87
  202. package/dist/src/channels/wechat-channel/helper-client.js +0 -3
  203. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +0 -122
  204. package/dist/src/channels/wechat-channel/helper-protocol.js +0 -1
  205. package/dist/src/channels/wechat-channel/human-coordination.d.ts +0 -25
  206. package/dist/src/channels/wechat-channel/human-coordination.js +0 -1
  207. package/dist/src/channels/wechat-channel/index.d.ts +0 -21
  208. package/dist/src/channels/wechat-channel/index.js +0 -1
  209. package/dist/src/channels/wechat-channel/ledger.d.ts +0 -48
  210. package/dist/src/channels/wechat-channel/ledger.js +0 -2
  211. package/dist/src/channels/wechat-channel/media-cache-resolver.d.ts +0 -42
  212. package/dist/src/channels/wechat-channel/media-cache-resolver.js +0 -1
  213. package/dist/src/channels/wechat-channel/media-resolver.d.ts +0 -186
  214. package/dist/src/channels/wechat-channel/media-resolver.js +0 -1
  215. package/dist/src/channels/wechat-channel/message-key.d.ts +0 -19
  216. package/dist/src/channels/wechat-channel/message-key.js +0 -1
  217. package/dist/src/channels/wechat-channel/observer.d.ts +0 -171
  218. package/dist/src/channels/wechat-channel/observer.js +0 -7
  219. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +0 -96
  220. package/dist/src/channels/wechat-channel/outbound-ledger.js +0 -2
  221. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +0 -74
  222. package/dist/src/channels/wechat-channel/outbound-sender.js +0 -1
  223. package/dist/src/channels/wechat-channel/pacing.d.ts +0 -3
  224. package/dist/src/channels/wechat-channel/pacing.js +0 -1
  225. package/dist/src/channels/wechat-channel/preflight.d.ts +0 -46
  226. package/dist/src/channels/wechat-channel/preflight.js +0 -1
  227. package/dist/src/channels/wechat-channel/runner.d.ts +0 -43
  228. package/dist/src/channels/wechat-channel/runner.js +0 -1
  229. package/dist/src/channels/wechat-channel/runtime.d.ts +0 -50
  230. package/dist/src/channels/wechat-channel/runtime.js +0 -1
  231. package/dist/src/channels/wechat-channel/scheduler.d.ts +0 -74
  232. package/dist/src/channels/wechat-channel/scheduler.js +0 -1
  233. package/dist/src/channels/wechat-channel/vector-store.d.ts +0 -44
  234. package/dist/src/channels/wechat-channel/vector-store.js +0 -1
  235. package/dist/src/channels/wechat-rpa/macos-flow.d.ts +0 -55
  236. package/dist/src/channels/wechat-rpa/macos-flow.js +0 -1
  237. package/dist/src/channels/wechat-rpa/macos.d.ts +0 -11
  238. package/dist/src/channels/wechat-rpa/macos.js +0 -21
  239. package/dist/src/channels/wechat-rpa/normalizer.d.ts +0 -49
  240. package/dist/src/channels/wechat-rpa/normalizer.js +0 -7
  241. package/dist/src/channels/wechat-rpa/product-channel.d.ts +0 -76
  242. package/dist/src/channels/wechat-rpa/product-channel.js +0 -16
  243. package/dist/src/channels/wechat-rpa-session-sync.d.ts +0 -26
  244. package/dist/src/channels/wechat-rpa-session-sync.js +0 -1
  245. package/dist/src/channels/wechat-rpa.d.ts +0 -104
  246. package/dist/src/channels/wechat-rpa.js +0 -6
  247. package/dist/src/channels/wecom.d.ts +0 -51
  248. package/dist/src/channels/wecom.js +0 -4
  249. package/dist/src/commands/external-attachments.d.ts +0 -9
  250. package/dist/src/commands/external-attachments.js +0 -1
  251. package/dist/src/commands/external.d.ts +0 -2
  252. package/dist/src/commands/external.js +0 -1
  253. package/dist/src/commands/helper-runtime-official-download.d.ts +0 -80
  254. package/dist/src/commands/helper-runtime-official-download.js +0 -1
  255. package/dist/src/commands/runtime.d.ts +0 -120
  256. package/dist/src/commands/runtime.js +0 -1
  257. package/dist/src/commands/wechat/command.d.ts +0 -3
  258. package/dist/src/commands/wechat/command.js +0 -1
  259. package/dist/src/commands/wechat/direct.d.ts +0 -14
  260. package/dist/src/commands/wechat/direct.js +0 -2
  261. package/dist/src/commands/wechat/doctor.d.ts +0 -2
  262. package/dist/src/commands/wechat/doctor.js +0 -1
  263. package/dist/src/commands/wechat/ipc.d.ts +0 -9
  264. package/dist/src/commands/wechat/ipc.js +0 -1
  265. package/dist/src/commands/wechat/operations.d.ts +0 -3
  266. package/dist/src/commands/wechat/operations.js +0 -4
  267. package/dist/src/commands/wechat/output.d.ts +0 -7
  268. package/dist/src/commands/wechat/output.js +0 -4
  269. package/dist/src/commands/wechat/types.d.ts +0 -142
  270. package/dist/src/commands/wechat/types.js +0 -1
  271. package/dist/src/commands/wechat/utils.d.ts +0 -16
  272. package/dist/src/commands/wechat/utils.js +0 -1
  273. package/dist/src/commands/wechat.d.ts +0 -6
  274. package/dist/src/commands/wechat.js +0 -1
  275. package/dist/src/devtools/wechat-channel-action-smoke.d.ts +0 -105
  276. package/dist/src/devtools/wechat-channel-action-smoke.js +0 -10
  277. package/dist/src/devtools/wechat-channel-download-report.d.ts +0 -111
  278. package/dist/src/devtools/wechat-channel-download-report.js +0 -2
  279. package/dist/src/devtools/wechat-channel-markdown-report.d.ts +0 -6
  280. package/dist/src/devtools/wechat-channel-markdown-report.js +0 -5
  281. package/dist/src/devtools/wechat-channel-product-steps.d.ts +0 -9
  282. package/dist/src/devtools/wechat-channel-product-steps.js +0 -40
  283. package/dist/src/session/handlers/skills.d.ts +0 -7
  284. package/dist/src/session/handlers/skills.js +0 -1
  285. package/dist/src/session/handlers/wechat-helper-runtime.d.ts +0 -33
  286. package/dist/src/session/handlers/wechat-helper-runtime.js +0 -1
  287. package/dist/src/skills/registry.d.ts +0 -16
  288. package/dist/src/skills/registry.js +0 -15
  289. package/dist/src/skills/setup.d.ts +0 -11
  290. package/dist/src/skills/setup.js +0 -1
@@ -0,0 +1,143 @@
1
+ import type { RoomActivationMode, RoomBindingStatus, RoomRuntimeAdapter } from '@shennian/wire';
2
+ export type ManagedRoomSessionAgent = 'codex' | 'claude' | 'workbuddy';
3
+ export type ManagedRoomSessionInput = {
4
+ inviteUrl: string;
5
+ agent: ManagedRoomSessionAgent;
6
+ workDir: string;
7
+ alias: string;
8
+ mode?: RoomActivationMode;
9
+ modelId?: string;
10
+ };
11
+ export type ManagedRoomSessionPhase = 'initialized' | 'session_created' | 'native_session_ready' | 'session_registered' | 'joined';
12
+ export type ManagedRoomSessionFailureStage = 'validate_input' | 'create_session' | 'wait_for_native_session' | 'register_session' | 'join_room';
13
+ type NormalizedInput = Required<Pick<ManagedRoomSessionInput, 'inviteUrl' | 'agent' | 'workDir' | 'alias' | 'mode'>> & {
14
+ modelId: string | null;
15
+ };
16
+ export type ManagedRoomSessionRegistration = {
17
+ registryKey: string;
18
+ };
19
+ export type ManagedRoomJoinResult = {
20
+ outcome: 'joined' | 'pending_approval';
21
+ room: {
22
+ id: string;
23
+ name: string;
24
+ webUrl: string;
25
+ };
26
+ binding: {
27
+ id: string;
28
+ agentIdentityId: string;
29
+ agentMembershipId: string | null;
30
+ status: RoomBindingStatus;
31
+ activationMode: RoomActivationMode;
32
+ };
33
+ idempotent: boolean;
34
+ };
35
+ export type ManagedRoomSessionOperation = {
36
+ schemaVersion: 1;
37
+ operationId: string;
38
+ inputFingerprint: string;
39
+ input: NormalizedInput;
40
+ runtimeAdapter: RoomRuntimeAdapter;
41
+ phase: ManagedRoomSessionPhase;
42
+ nianSessionId: string | null;
43
+ sourceSessionKey: string | null;
44
+ hostProcessIdentity: string | null;
45
+ registryKey: string | null;
46
+ result: ManagedRoomJoinResult | null;
47
+ createdAt: string;
48
+ updatedAt: string;
49
+ };
50
+ export interface ManagedRoomSessionOperationStore {
51
+ load(operationId: string): ManagedRoomSessionOperation | null;
52
+ save(operation: ManagedRoomSessionOperation): void;
53
+ }
54
+ export type ManagedRoomSessionDependencies = {
55
+ operations: ManagedRoomSessionOperationStore;
56
+ /**
57
+ * Must be idempotent for creationKey. A successful call followed by a daemon
58
+ * crash may be repeated before the operation record reaches disk.
59
+ */
60
+ createManagedSession(input: {
61
+ creationKey: string;
62
+ agent: ManagedRoomSessionAgent;
63
+ workDir: string;
64
+ modelId: string | null;
65
+ initialMessage: string;
66
+ }): Promise<{
67
+ nianSessionId: string;
68
+ }>;
69
+ /** Resolves only the native Session created for nianSessionId; never a recent Session. */
70
+ waitForNativeSession(input: {
71
+ nianSessionId: string;
72
+ runtimeAdapter: RoomRuntimeAdapter;
73
+ timeoutMs: number;
74
+ }): Promise<{
75
+ sourceSessionKey: string;
76
+ hostProcessIdentity: string;
77
+ }>;
78
+ /** Persists the trusted observation and exact managed Session Registry record. */
79
+ registerManagedSession(input: {
80
+ nianSessionId: string;
81
+ runtimeAdapter: RoomRuntimeAdapter;
82
+ sourceSessionKey: string;
83
+ hostProcessIdentity: string;
84
+ workDir: string;
85
+ capabilities: {
86
+ canIdentifySession: true;
87
+ canBindAutomatically: true;
88
+ canOpenEmbeddedRoomPage: false;
89
+ canResumeIdleSession: true;
90
+ canActivateWhileHostOpen: true;
91
+ };
92
+ }): Promise<ManagedRoomSessionRegistration>;
93
+ /**
94
+ * Uses daemon-owned machine credentials internally. Public callers never
95
+ * supply Room, Binding, Identity, native Session, or machine credential IDs.
96
+ */
97
+ joinRegisteredSession(input: {
98
+ bindingIntentId: string;
99
+ registryKey: string;
100
+ inviteUrl: string;
101
+ agentAlias: string;
102
+ activationMode: RoomActivationMode;
103
+ activationCapability: 'managed';
104
+ }): Promise<ManagedRoomJoinResult>;
105
+ allowedInviteOrigins?: string[];
106
+ nativeSessionTimeoutMs?: number;
107
+ now?: () => Date;
108
+ };
109
+ export declare class ManagedRoomSessionError extends Error {
110
+ readonly code: string;
111
+ readonly stage: ManagedRoomSessionFailureStage;
112
+ readonly operationId: string | null;
113
+ readonly phase: ManagedRoomSessionPhase;
114
+ readonly nianSessionId: string | null;
115
+ constructor(code: string, message: string, stage: ManagedRoomSessionFailureStage, operationId: string | null, phase: ManagedRoomSessionPhase, nianSessionId: string | null);
116
+ }
117
+ export declare class ManagedRoomSessionOrchestrator {
118
+ private readonly deps;
119
+ private readonly mutexTails;
120
+ constructor(deps: ManagedRoomSessionDependencies);
121
+ execute(operationId: string, rawInput: ManagedRoomSessionInput): Promise<{
122
+ operationId: string;
123
+ phase: 'joined';
124
+ nianSessionId: string;
125
+ result: ManagedRoomJoinResult;
126
+ }>;
127
+ private executeExclusive;
128
+ private save;
129
+ }
130
+ export declare class InMemoryManagedRoomSessionOperationStore implements ManagedRoomSessionOperationStore {
131
+ private readonly records;
132
+ load(operationId: string): ManagedRoomSessionOperation | null;
133
+ save(operation: ManagedRoomSessionOperation): void;
134
+ }
135
+ /** Private per-operation files allow daemon restart recovery without one operation overwriting another. */
136
+ export declare class FileManagedRoomSessionOperationStore implements ManagedRoomSessionOperationStore {
137
+ readonly directory: string;
138
+ constructor(directory: string);
139
+ load(operationId: string): ManagedRoomSessionOperation | null;
140
+ save(operation: ManagedRoomSessionOperation): void;
141
+ private filePath;
142
+ }
143
+ export {};
@@ -0,0 +1,3 @@
1
+ import S from"node:crypto";import a from"node:fs";import y from"node:path";class h extends Error{code;stage;operationId;phase;nianSessionId;constructor(i,o,t,r,n,s){super(o),this.code=i,this.stage=t,this.operationId=r,this.phase=n,this.nianSessionId=s,this.name="ManagedRoomSessionError"}}const j=/^[A-Za-z0-9_-]{8,96}$/,R=/^[A-Za-z0-9_.:@/-]{1,512}$/,w=/^[a-f0-9]{64}$/,k=/^[A-Za-z0-9_-]{43}$/,D=new Set(["inviteUrl","agent","workDir","alias","mode","modelId"]),K=new Set(["codex","claude","workbuddy"]),N=new Set(["auto_follow","mention_only","paused"]),P=["https://shennian.net"],$=3e4;class B{deps;mutexTails=new Map;constructor(i){this.deps=i}async execute(i,o){_(i);let t;const r=this.mutexTails.get(i)??Promise.resolve(),n=new Promise(l=>{t=l}),s=r.then(()=>n);this.mutexTails.set(i,s),await r;try{return await this.executeExclusive(i,o)}finally{t(),this.mutexTails.get(i)===s&&this.mutexTails.delete(i)}}async executeExclusive(i,o){let t;try{t=I(o,this.deps.allowedInviteOrigins??P)}catch(s){throw m(s,"validate_input",i,null)}const r=b(t);let n=this.deps.operations.load(i);if(n&&n.inputFingerprint!==r)throw new h("managed_room_operation_conflict","This operation ID was already used with different inputs.","validate_input",i,n.phase,n.nianSessionId);if(!n){const s=(this.deps.now?.()??new Date).toISOString();n={schemaVersion:1,operationId:i,inputFingerprint:r,input:t,runtimeAdapter:v(t.agent),phase:"initialized",nianSessionId:null,sourceSessionKey:null,hostProcessIdentity:null,registryKey:null,result:null,createdAt:s,updatedAt:s},this.save(n)}if(n.phase==="joined")return E(n);if(n.phase==="initialized")try{const s=await this.deps.createManagedSession({creationKey:`room-managed-session:${i}`,agent:t.agent,workDir:t.workDir,modelId:t.modelId,initialMessage:U()});c(s.nianSessionId,"nian_session_id"),n.nianSessionId=s.nianSessionId,n.phase="session_created",this.save(n)}catch(s){throw m(s,"create_session",i,n)}if(n.phase==="session_created")try{const s=await this.deps.waitForNativeSession({nianSessionId:n.nianSessionId,runtimeAdapter:n.runtimeAdapter,timeoutMs:this.deps.nativeSessionTimeoutMs??$});A(s.sourceSessionKey),c(s.hostProcessIdentity,"host_process_identity"),n.sourceSessionKey=s.sourceSessionKey,n.hostProcessIdentity=s.hostProcessIdentity,n.phase="native_session_ready",this.save(n)}catch(s){throw m(s,"wait_for_native_session",i,n)}if(n.phase==="native_session_ready")try{const s=await this.deps.registerManagedSession({nianSessionId:n.nianSessionId,runtimeAdapter:n.runtimeAdapter,sourceSessionKey:n.sourceSessionKey,hostProcessIdentity:n.hostProcessIdentity,workDir:t.workDir,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}});if(!w.test(s.registryKey))throw u("registry_key");n.registryKey=s.registryKey,n.phase="session_registered",this.save(n)}catch(s){throw m(s,"register_session",i,n)}if(n.phase==="session_registered")try{n.result=await this.deps.joinRegisteredSession({bindingIntentId:`room-managed-session-join:${i}`,registryKey:n.registryKey,inviteUrl:t.inviteUrl,agentAlias:t.alias,activationMode:t.mode,activationCapability:"managed"}),O(n.result),n.phase="joined",this.save(n)}catch(s){throw m(s,"join_room",i,n)}return E(n)}save(i){i.updatedAt=(this.deps.now?.()??new Date).toISOString(),this.deps.operations.save(g(i))}}class G{records=new Map;load(i){const o=this.records.get(i);return o?g(o):null}save(i){this.records.set(i.operationId,g(i))}}class Z{directory;constructor(i){this.directory=i}load(i){_(i);const o=this.filePath(i);try{const t=a.lstatSync(o);if(!t.isFile()||t.isSymbolicLink())throw p();process.platform!=="win32"&&(t.mode&63)!==0&&a.chmodSync(o,384);const r=JSON.parse(a.readFileSync(o,"utf8"));return M(r,i),g(r)}catch(t){if(t.code==="ENOENT")return null;throw t}}save(i){_(i.operationId),M(i,i.operationId),a.mkdirSync(this.directory,{recursive:!0,mode:448});const o=a.lstatSync(this.directory);if(!o.isDirectory()||o.isSymbolicLink())throw p();process.platform!=="win32"&&a.chmodSync(this.directory,448);const t=this.filePath(i.operationId),r=`${t}.tmp-${process.pid}-${S.randomBytes(6).toString("hex")}`;try{if(a.writeFileSync(r,`${JSON.stringify(i,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&a.chmodSync(r,384),process.platform==="win32"&&a.existsSync(t)){const n=a.lstatSync(t);if(!n.isFile()||n.isSymbolicLink())throw p();a.unlinkSync(t)}a.renameSync(r,t)}finally{try{a.unlinkSync(r)}catch{}}}filePath(i){return y.join(this.directory,`${i}.json`)}}function I(e,i){(!e||typeof e!="object"||Array.isArray(e))&&d("input");const o=e;Object.keys(o).find(T=>!D.has(T))&&d("unsupported_field");const r=o.agent;(typeof r!="string"||!K.has(r))&&d("agent");const n=f(o.workDir,"work_dir",4096);(!y.isAbsolute(n)||y.normalize(n)!==n)&&d("work_dir");const s=f(o.alias,"alias",80),l=o.mode??"auto_follow";(typeof l!="string"||!N.has(l))&&d("mode");const x=o.modelId==null?null:f(o.modelId,"model_id",191);return{inviteUrl:F(o.inviteUrl,i),agent:r,workDir:n,alias:s,mode:l,modelId:x}}function F(e,i){const o=f(e,"invite_url",2048);let t;try{t=new URL(o)}catch{d("invite_url")}(!new Set(i.map(l=>new URL(l).origin)).has(t.origin)||t.username||t.password||t.search||t.hash)&&d("invite_url");const n=t.pathname.split("/").filter(Boolean),s=n.at(-1);return(!s||!k.test(s)||n.at(-2)!=="join")&&d("invite_url"),t.toString()}function f(e,i,o){typeof e!="string"&&d(i);const t=e.trim();return(!t||t.length>o||/[\0\r\n]/.test(t))&&d(i),t}function _(e){if(!j.test(e))throw new h("invalid_managed_room_operation_id","Managed Room operation ID is invalid.","validate_input",null,"initialized",null)}function v(e){return e==="claude"?"claude_code":e}function b(e){return S.createHash("sha256").update(JSON.stringify(e),"utf8").digest("hex")}function c(e,i){if(!R.test(e))throw u(i)}function A(e){if(!e||e.length>1024||/[\0\r\n]/.test(e))throw u("source_session_key")}function O(e){if(c(e.room?.id,"room_id"),c(e.binding?.id,"binding_id"),c(e.binding?.agentIdentityId,"agent_identity_id"),e.binding.agentMembershipId!==null&&c(e.binding.agentMembershipId,"agent_membership_id"),e.outcome!=="joined"&&e.outcome!=="pending_approval")throw u("join_outcome");if(e.outcome==="joined"&&e.binding.status!=="active"&&e.binding.status!=="paused"||e.outcome==="pending_approval"&&e.binding.status!=="pending_approval"||e.binding.status==="pending_approval"&&e.binding.agentMembershipId!==null||(e.binding.status==="active"||e.binding.status==="paused")&&e.binding.agentMembershipId===null)throw u("join_binding_state")}function U(){return["\u8FD9\u662F\u4E00\u4E2A\u7531\u795E\u5FF5\u521B\u5EFA\u7684\u53D7\u7BA1 Agent \u5BF9\u8BDD\u3002","\u8BF7\u5148\u4FDD\u6301\u7A7A\u95F2\uFF1B\u795E\u5FF5\u6B63\u5728\u4E3A\u8BE5\u7CBE\u786E\u5BF9\u8BDD\u5EFA\u7ACB\u7FA4\u804A\u8FDE\u63A5\uFF0C\u5B8C\u6210\u540E\u4F1A\u53D1\u9001\u540E\u7EED\u6D88\u606F\u3002"].join(`
3
+ `)}function E(e){if(!e.nianSessionId||!e.result||e.phase!=="joined")throw u("operation_state");return{operationId:e.operationId,phase:"joined",nianSessionId:e.nianSessionId,result:g(e.result)}}function m(e,i,o,t){if(e instanceof h)return e;const r=e&&typeof e=="object"&&"code"in e&&typeof e.code=="string"?e.code:`managed_room_${i}_failed`;return new h(r,`Managed Room Session failed during ${i}. The created conversation was preserved for retry.`,i,o,t?.phase??"initialized",t?.nianSessionId??null)}function d(e){throw new h(`invalid_managed_room_${e}`,`Managed Room Session ${e} is invalid.`,"validate_input",null,"initialized",null)}function u(e){return Object.assign(new Error(`Managed Room dependency returned an invalid ${e}.`),{code:`invalid_managed_room_${e}`})}function M(e,i){if(!e||e.schemaVersion!==1||e.operationId!==i||!/^[a-f0-9]{64}$/.test(e.inputFingerprint)||!["initialized","session_created","native_session_ready","session_registered","joined"].includes(e.phase)||!e.input||v(e.input.agent)!==e.runtimeAdapter)throw p();const o=I(e.input,[new URL(e.input.inviteUrl).origin]);if(b(o)!==e.inputFingerprint||(e.nianSessionId!==null&&c(e.nianSessionId,"nian_session_id"),e.sourceSessionKey!==null&&A(e.sourceSessionKey),e.hostProcessIdentity!==null&&c(e.hostProcessIdentity,"host_process_identity"),e.registryKey!==null&&!w.test(e.registryKey)))throw p();e.result!==null&&O(e.result)}function p(){return Object.assign(new Error("Managed Room Session operation store is unsafe or corrupt."),{code:"unsafe_managed_room_operation_store"})}function g(e){return JSON.parse(JSON.stringify(e))}export{Z as FileManagedRoomSessionOperationStore,G as InMemoryManagedRoomSessionOperationStore,h as ManagedRoomSessionError,B as ManagedRoomSessionOrchestrator};
@@ -0,0 +1,33 @@
1
+ import { HttpRoomActionClient } from './room-action-client.js';
2
+ import { RoomJsonlMirror } from './jsonl-mirror.js';
3
+ import { RoomSessionRegistry, type RoomSessionRegistration } from './session-registry.js';
4
+ import type { RoomMessageView } from '@shennian/wire';
5
+ type Mirror = Pick<RoomJsonlMirror, 'append' | 'markAccessRevoked' | 'readState' | 'recordSyncError'>;
6
+ type Timer = ReturnType<typeof setTimeout>;
7
+ export declare class RoomMirrorSyncService {
8
+ private readonly deps;
9
+ private timer;
10
+ private running;
11
+ private started;
12
+ constructor(deps: {
13
+ registry: RoomSessionRegistry;
14
+ actionClient: Pick<HttpRoomActionClient, 'status' | 'read'>;
15
+ getMachineCredentials: () => {
16
+ machineToken?: string;
17
+ };
18
+ mirrorFactory?: (roomId: string) => Mirror;
19
+ initialDelayMs?: number;
20
+ intervalMs?: number;
21
+ maxPagesPerRun?: number;
22
+ setTimeout?: (handler: () => void, delayMs: number) => Timer;
23
+ clearTimeout?: (timer: Timer) => void;
24
+ onMessages?: (roomId: string, messages: RoomMessageView[], registrations: RoomSessionRegistration[]) => Promise<void>;
25
+ });
26
+ start(): void;
27
+ stop(): void;
28
+ runOnce(): Promise<void>;
29
+ private syncRoom;
30
+ private pullPages;
31
+ private arm;
32
+ }
33
+ export {};
@@ -0,0 +1 @@
1
+ import{RoomActionHttpError as m}from"./room-action-client.js";import{RoomJsonlMirror as h}from"./jsonl-mirror.js";class S{deps;timer=null;running=!1;started=!1;constructor(e){this.deps=e}start(){this.started||(this.started=!0,this.arm(this.deps.initialDelayMs??0))}stop(){this.started=!1,this.timer&&(this.deps.clearTimeout??clearTimeout)(this.timer),this.timer=null}async runOnce(){if(this.running)return;const e=this.deps.getMachineCredentials().machineToken?.trim();if(e){this.running=!0;try{const i=l(this.deps.registry.list());for(const[r,o]of i)await this.syncRoom(r,o,e)}finally{this.running=!1}}}async syncRoom(e,i,r){const o=(this.deps.mirrorFactory??(t=>new h(t)))(e),u=i.filter(t=>t.binding&&(t.binding.status==="active"||t.binding.status==="paused"));if(u.length===0){i.some(t=>t.binding?.status==="detached")&&!i.some(t=>t.binding?.status==="pending_approval")&&o.markAccessRevoked();return}let a=0,c=null;for(const t of u){const d=t.binding,g={machineToken:r,roomId:e,bindingId:d.bindingId};try{const s=await this.deps.actionClient.status(g);if(this.deps.registry.setBinding({registryKey:t.registryKey,bindingId:s.bindingId,roomId:s.roomId,agentIdentityId:s.agentIdentityId,agentMembershipId:s.agentMembershipId,status:s.status,activationMode:s.activationMode}),s.status==="detached"){a+=1;continue}if(s.status==="pending_approval")continue;c??=g}catch(s){if(p(s)){this.deps.registry.detachBinding(t.registryKey),a+=1;continue}o.recordSyncError(f(s));return}}if(c){const t=i.map(d=>this.deps.registry.getByRegistryKey(d.registryKey)??d);await this.pullPages(o,c,e,t);return}a===u.length&&o.markAccessRevoked()}async pullPages(e,i,r,o){const u=this.deps.maxPagesPerRun??100;let a=e.readState().lastSyncedSequence;for(let c=0;c<u;c+=1){const t=await this.deps.actionClient.read({...i,afterSequence:a,limit:100}),d=e.append({messages:t.messages,historyFromSequence:t.historyFromSequence,roomName:t.roomName});if(this.deps.onMessages&&await this.deps.onMessages(r,t.messages,o),!t.hasMore)return;if(d.lastSyncedSequence===a)throw new m("room_sync_no_progress","Room mirror synchronization made no progress.");a=d.lastSyncedSequence}}arm(e){if(!this.started||this.timer)return;const i=this.deps.setTimeout??setTimeout;this.timer=i(()=>{this.timer=null,this.runOnce().catch(()=>{}).finally(()=>{this.started&&this.arm(this.deps.intervalMs??5e3)})},e),this.timer.unref?.()}}function l(n){const e=new Map;for(const i of n){if(!i.binding||i.lifecycleState==="closed")continue;const r=e.get(i.binding.roomId)??[];r.push(i),e.set(i.binding.roomId,r)}return e}function p(n){return n instanceof m&&(n.code==="room_binding_unavailable"||n.code==="room_action_http_403")}function f(n){return n instanceof m?n.code:"room_sync_failed"}export{S as RoomMirrorSyncService};
@@ -0,0 +1,11 @@
1
+ import type { DaemonIpcHandler } from '../daemon-ipc/server.js';
2
+ import type { RoomSessionRegistry } from './session-registry.js';
3
+ import type { HttpRoomActionClient } from './room-action-client.js';
4
+ export declare function createRoomOperatorIpcHandlers(input: {
5
+ registry: Pick<RoomSessionRegistry, 'list'>;
6
+ actionClient: Pick<HttpRoomActionClient, 'createOperatorRoom'>;
7
+ getMachineCredentials: () => {
8
+ machineId?: string;
9
+ machineToken?: string;
10
+ };
11
+ }): Record<string, DaemonIpcHandler>;
@@ -0,0 +1 @@
1
+ import{DaemonIpcError as r}from"../daemon-ipc/protocol.js";function w(e){return{"room-operator.inspect":n=>{const o=u(n),t=e.getMachineCredentials(),i=t.machineId?.trim(),l=!!(i&&t.machineToken?.trim()),c=i?e.registry.list().filter(s=>s.machineId===i):[],m=c.filter(s=>s.binding!==null),d=h(m);return a(o==="status"?{source:"local_registry",signedIn:l,registeredSessions:c.length,boundSessions:m.length,rooms:d.length}:{source:"local_registry",complete:!1,rooms:d})},"room-operator.create":async n=>{const o=f(n),t=e.getMachineCredentials();if(!t.machineId?.trim()||!t.machineToken?.trim())throw new r("cli_not_signed_in","Sign in to the Shennian CLI first.");try{return a(await e.actionClient.createOperatorRoom({machineToken:t.machineToken,name:o}))}catch(i){throw g(i)}}}}function a(e){return JSON.parse(JSON.stringify(e))}function u(e){if(!e||Array.isArray(e)||typeof e!="object")throw new r("invalid_params","Room operator parameters are required.");const n=e;if(Object.keys(n).length!==1||n.action!=="status"&&n.action!=="list")throw new r("invalid_params","Room operator action is invalid.");return n.action}function f(e){if(!e||Array.isArray(e)||typeof e!="object")throw new r("invalid_params","Room operator parameters are required.");const n=e;if(Object.keys(n).length!==1||typeof n.name!="string")throw new r("invalid_params","Room operator create parameters are invalid.");const o=n.name.trim();if(!o||o.length>80)throw new r("invalid_params","Room name must contain 1 to 80 characters.");return o}function g(e){return e&&typeof e=="object"&&"code"in e&&typeof e.code=="string"&&"message"in e&&typeof e.message=="string"?new r(e.code,e.message):new r("room_operator_failed","Shennian Room operator action failed.")}function h(e){const n=new Map;for(const o of e){const t=o.binding;if(!t)continue;const i=n.get(t.roomId)??{sessions:0,runtimes:new Set,statuses:new Set,modes:new Set};i.sessions+=1,i.runtimes.add(o.runtimeAdapter),i.statuses.add(t.status),i.modes.add(t.activationMode),n.set(t.roomId,i)}return[...n.entries()].sort(([o],[t])=>o.localeCompare(t)).map(([o,t])=>({roomId:o,sessions:t.sessions,runtimes:[...t.runtimes].sort(),bindingStatuses:[...t.statuses].sort(),activationModes:[...t.modes].sort()}))}export{w as createRoomOperatorIpcHandlers};
@@ -0,0 +1,69 @@
1
+ export type AuthorizedActionStatus = 'waiting_user' | 'approved' | 'executing' | 'completed' | 'failed' | 'expired';
2
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
3
+ [key: string]: JsonValue;
4
+ };
5
+ export type PendingAuthorizedAction = {
6
+ schemaVersion: 1;
7
+ id: string;
8
+ loginIntentId: string;
9
+ publicLoginUrl: string;
10
+ actionType: string;
11
+ canonicalActionPayload: JsonValue;
12
+ actionPayloadHash: string;
13
+ idempotencyKey: string;
14
+ deviceSecretHash: string;
15
+ status: AuthorizedActionStatus;
16
+ expiresAt: string;
17
+ createdAt: string;
18
+ updatedAt: string;
19
+ browserOpenedAt?: string;
20
+ approvedAt?: string;
21
+ executingAt?: string;
22
+ completedAt?: string;
23
+ failedAt?: string;
24
+ errorCode?: string;
25
+ result?: JsonValue;
26
+ };
27
+ type CreatePendingActionInput = {
28
+ id?: string;
29
+ loginIntentId: string;
30
+ publicLoginUrl: string;
31
+ actionType: string;
32
+ actionPayload: JsonValue;
33
+ idempotencyKey: string;
34
+ deviceSecret: string;
35
+ expiresAt: string;
36
+ now?: Date;
37
+ };
38
+ export declare class PendingAuthorizedActionError extends Error {
39
+ readonly code: string;
40
+ constructor(code: string, message: string);
41
+ }
42
+ export declare function canonicalizeActionPayload(value: JsonValue): string;
43
+ export declare function hashActionPayload(value: JsonValue): string;
44
+ export declare function hashPendingDeviceSecret(secret: string): string;
45
+ export declare class PendingAuthorizedActionStore {
46
+ readonly directory: string;
47
+ constructor(rootDir?: string);
48
+ create(input: CreatePendingActionInput): PendingAuthorizedAction;
49
+ get(id: string): PendingAuthorizedAction | null;
50
+ list(): PendingAuthorizedAction[];
51
+ findByIdempotencyKey(idempotencyKey: string): PendingAuthorizedAction | null;
52
+ listRecoverable(now?: Date): PendingAuthorizedAction[];
53
+ readDeviceSecret(id: string): string;
54
+ markApproved(id: string, now?: Date): PendingAuthorizedAction;
55
+ markBrowserOpened(id: string, now?: Date): PendingAuthorizedAction;
56
+ markExecuting(id: string, now?: Date): PendingAuthorizedAction;
57
+ markCompleted(id: string, result: JsonValue, now?: Date): PendingAuthorizedAction;
58
+ markFailed(id: string, errorCode: string, now?: Date): PendingAuthorizedAction;
59
+ private transition;
60
+ private update;
61
+ private ensureDirectory;
62
+ private recordPath;
63
+ private secretPath;
64
+ private writeRecord;
65
+ private writeSecret;
66
+ private atomicWrite;
67
+ private removeSecret;
68
+ }
69
+ export {};
@@ -0,0 +1,3 @@
1
+ import d from"node:crypto";import c from"node:fs";import m from"node:path";import{resolveShennianPath as b}from"../config/index.js";const u=/^[A-Za-z0-9_-]{8,128}$/,w=/^[a-z][a-z0-9_.-]{0,63}$/,S=/^[a-f0-9]{64}$/,f=new Set(["completed","failed","expired"]);class r extends Error{code;constructor(e,t){super(t),this.code=e,this.name="PendingAuthorizedActionError"}}function I(i){return l(i)}function _(i){return d.createHash("sha256").update(I(i),"utf8").digest("hex")}function v(i){return d.createHash("sha256").update(i,"utf8").digest("hex")}class k{directory;constructor(e=b("runtime","pending-authorized-actions")){this.directory=e}create(e){if(y(e.loginIntentId,"loginIntentId"),O(e.publicLoginUrl,e.loginIntentId),!w.test(e.actionType))throw new r("invalid_action_type","Invalid authorized action type.");if(!e.idempotencyKey.trim()||e.idempotencyKey.length>191)throw new r("invalid_idempotency_key","Invalid idempotency key.");if(e.deviceSecret.length<32||e.deviceSecret.length>256)throw new r("invalid_device_secret","Invalid device secret.");const t=new Date(e.expiresAt);if(!Number.isFinite(t.getTime()))throw new r("invalid_expiry","Invalid authorization expiry.");this.ensureDirectory();const n=x(e.actionPayload),o=_(n),a=this.list().find(p=>p.idempotencyKey===e.idempotencyKey);if(a){if(a.actionType!==e.actionType||a.actionPayloadHash!==o)throw new r("idempotency_payload_conflict","The idempotency key is already bound to a different action.");return a}const s=(e.now??new Date).toISOString(),h=e.id??d.randomUUID();y(h,"id");const g={schemaVersion:1,id:h,loginIntentId:e.loginIntentId,publicLoginUrl:e.publicLoginUrl,actionType:e.actionType,canonicalActionPayload:n,actionPayloadHash:o,idempotencyKey:e.idempotencyKey,deviceSecretHash:v(e.deviceSecret),status:"waiting_user",expiresAt:t.toISOString(),createdAt:s,updatedAt:s};this.writeSecret(h,e.deviceSecret);try{this.writeRecord(g)}catch(p){throw this.removeSecret(h),p}return g}get(e){y(e,"id");try{const t=this.recordPath(e),n=c.lstatSync(t);if(!n.isFile()||n.isSymbolicLink())throw new r("unsafe_pending_action","Pending action is not a regular file.");return A(JSON.parse(c.readFileSync(t,"utf8")))}catch(t){if(N(t))return null;throw t instanceof r?t:new r("corrupt_pending_action","Pending authorized action is corrupt.")}}list(){return this.ensureDirectory(),c.readdirSync(this.directory).filter(e=>e.endsWith(".json")&&!e.includes(".tmp-")).map(e=>this.get(e.slice(0,-5))).filter(e=>e!==null).sort((e,t)=>e.createdAt.localeCompare(t.createdAt))}findByIdempotencyKey(e){return this.list().find(t=>t.idempotencyKey===e)??null}listRecoverable(e=new Date){const t=[];for(const n of this.list())if(!f.has(n.status)){if(new Date(n.expiresAt)<=e){this.update(n.id,{status:"expired",errorCode:"authorization_expired"},e),this.removeSecret(n.id);continue}t.push(n)}return t}readDeviceSecret(e){const t=this.get(e);if(!t||f.has(t.status))throw new r("pending_action_unavailable","Pending action is unavailable.");let n;try{const o=this.secretPath(e),a=c.lstatSync(o);if(!a.isFile()||a.isSymbolicLink())throw new r("unsafe_device_secret","Device authorization secret is not a regular file.");n=c.readFileSync(o,"utf8").trim()}catch(o){throw o instanceof r?o:new r("device_secret_missing","Device authorization secret is missing.")}if(!$(n,t.deviceSecretHash))throw new r("device_secret_mismatch","Device authorization secret is invalid.");return n}markApproved(e,t=new Date){return this.transition(e,["waiting_user","approved"],"approved",{approvedAt:t.toISOString()},t)}markBrowserOpened(e,t=new Date){const n=this.get(e);if(!n)throw new r("pending_action_not_found","Pending action not found.");return n.browserOpenedAt||n.status!=="waiting_user"?n:this.update(e,{browserOpenedAt:t.toISOString()},t)}markExecuting(e,t=new Date){const n=this.transition(e,["approved"],"executing",{executingAt:t.toISOString()},t);return this.removeSecret(e),n}markCompleted(e,t,n=new Date){const o=this.transition(e,["executing","completed"],"completed",{completedAt:n.toISOString(),result:t,errorCode:void 0},n);return this.removeSecret(e),o}markFailed(e,t,n=new Date){const o=this.transition(e,["waiting_user","approved","executing","failed"],"failed",{failedAt:n.toISOString(),errorCode:t.slice(0,120)},n);return this.removeSecret(e),o}transition(e,t,n,o,a){const s=this.get(e);if(!s)throw new r("pending_action_not_found","Pending action not found.");if(!t.includes(s.status))throw new r("invalid_pending_action_transition",`Cannot transition ${s.status} to ${n}.`);return s.status===n&&f.has(n)?s:this.update(e,{...o,status:n},a)}update(e,t,n){const o=this.get(e);if(!o)throw new r("pending_action_not_found","Pending action not found.");const a=A({...o,...t,id:o.id,updatedAt:n.toISOString()});return this.writeRecord(a),a}ensureDirectory(){c.mkdirSync(this.directory,{recursive:!0,mode:448}),process.platform!=="win32"&&c.chmodSync(this.directory,448)}recordPath(e){return m.join(this.directory,`${e}.json`)}secretPath(e){return m.join(this.directory,`${e}.secret`)}writeRecord(e){this.atomicWrite(this.recordPath(e.id),`${JSON.stringify(e,null,2)}
2
+ `)}writeSecret(e,t){this.atomicWrite(this.secretPath(e),`${t}
3
+ `)}atomicWrite(e,t){this.ensureDirectory();const n=`${e}.tmp-${process.pid}-${d.randomBytes(6).toString("hex")}`;try{c.writeFileSync(n,t,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&c.chmodSync(n,384),c.renameSync(n,e),process.platform!=="win32"&&c.chmodSync(e,384)}finally{try{c.unlinkSync(n)}catch{}}}removeSecret(e){try{c.unlinkSync(this.secretPath(e))}catch{}}}function l(i){if(i===null||typeof i=="boolean"||typeof i=="string")return JSON.stringify(i);if(typeof i=="number"){if(!Number.isFinite(i))throw new r("invalid_action_payload","Numbers must be finite.");return JSON.stringify(i)}if(Array.isArray(i))return`[${i.map(l).join(",")}]`;if(typeof i!="object")throw new r("invalid_action_payload","Action payload must be JSON.");const e=Object.getPrototypeOf(i);if(e!==Object.prototype&&e!==null)throw new r("invalid_action_payload","Action payload must be plain JSON.");return`{${Object.keys(i).sort().map(t=>`${JSON.stringify(t)}:${l(i[t])}`).join(",")}}`}function x(i){return JSON.parse(l(i))}function y(i,e){if(!u.test(i))throw new r(`invalid_${e}`,`Invalid ${e}.`)}function A(i){if(!i||typeof i!="object")throw new r("corrupt_pending_action","Pending action must be an object.");const e=i;if(e.schemaVersion!==1||typeof e.id!="string"||!u.test(e.id)||typeof e.loginIntentId!="string"||!u.test(e.loginIntentId)||typeof e.publicLoginUrl!="string"||!P(e.publicLoginUrl,e.loginIntentId)||typeof e.actionType!="string"||!w.test(e.actionType)||typeof e.idempotencyKey!="string"||!e.idempotencyKey||typeof e.actionPayloadHash!="string"||!S.test(e.actionPayloadHash)||typeof e.deviceSecretHash!="string"||!S.test(e.deviceSecretHash)||!["waiting_user","approved","executing","completed","failed","expired"].includes(e.status??"")||typeof e.expiresAt!="string"||!Number.isFinite(new Date(e.expiresAt).getTime())||typeof e.createdAt!="string"||typeof e.updatedAt!="string")throw new r("corrupt_pending_action","Pending action has invalid fields.");if(e.browserOpenedAt!==void 0&&(typeof e.browserOpenedAt!="string"||!Number.isFinite(new Date(e.browserOpenedAt).getTime())))throw new r("corrupt_pending_action","Pending action has invalid browser state.");if(_(e.canonicalActionPayload)!==e.actionPayloadHash)throw new r("action_payload_tampered","Pending action payload hash mismatch.");return e}function O(i,e){if(!P(i,e))throw new r("invalid_public_login_url","Invalid Shennian authorization URL.")}function P(i,e){try{const t=new URL(i);return t.pathname!==`/authorize/${e}`||t.search||t.hash?!1:t.protocol==="https:"&&(t.hostname==="shennian.net"||t.hostname.endsWith(".shennian.net"))?!0:t.protocol==="http:"&&D(t.hostname)}catch{return!1}}function D(i){return i==="127.0.0.1"||i==="localhost"||i==="::1"||/^10\./.test(i)||/^192\.168\./.test(i)||/^172\.(1[6-9]|2\d|3[0-1])\./.test(i)}function $(i,e){const t=Buffer.from(v(i),"hex"),n=Buffer.from(e,"hex");return t.length===n.length&&d.timingSafeEqual(t,n)}function N(i){return typeof i=="object"&&i!==null&&"code"in i&&i.code==="ENOENT"}export{r as PendingAuthorizedActionError,k as PendingAuthorizedActionStore,I as canonicalizeActionPayload,_ as hashActionPayload,v as hashPendingDeviceSecret};
@@ -0,0 +1,208 @@
1
+ import type { RoomActivationMode, RoomMessageView } from '@shennian/wire';
2
+ type FetchLike = typeof fetch;
3
+ type BindingTarget = {
4
+ machineToken: string;
5
+ roomId: string;
6
+ bindingId: string;
7
+ };
8
+ export type RemoteRoomAgentStatus = {
9
+ bindingId: string;
10
+ roomId: string;
11
+ agentIdentityId: string;
12
+ agentMembershipId: string | null;
13
+ status: 'pending_approval' | 'active' | 'paused' | 'detached';
14
+ activationMode: RoomActivationMode;
15
+ lastDeliveredSequence: string;
16
+ lastActivatedSequence: string;
17
+ room: {
18
+ name: string;
19
+ status: string;
20
+ lastSequence: string;
21
+ };
22
+ };
23
+ export type RemoteRoomJoinResult = {
24
+ outcome: 'joined' | 'pending_approval';
25
+ room: {
26
+ id: string;
27
+ name: string;
28
+ webUrl: string;
29
+ };
30
+ agentIdentity: {
31
+ id: string;
32
+ displayName: string;
33
+ };
34
+ binding: {
35
+ id: string;
36
+ roomId: string;
37
+ agentIdentityId: string;
38
+ agentMembershipId: string | null;
39
+ status: 'pending_approval' | 'active' | 'paused';
40
+ activationMode: RoomActivationMode;
41
+ };
42
+ joinRequestId?: string;
43
+ idempotent: boolean;
44
+ };
45
+ export type RemoteRoomAttachmentCapability = {
46
+ attachmentId: string;
47
+ roomId: string;
48
+ safeName: string;
49
+ mimeType: string;
50
+ byteSize: string;
51
+ sha256: string;
52
+ downloadUrl: string;
53
+ expiresAt: string;
54
+ };
55
+ export type RemoteRoomAttachmentUpload = {
56
+ attachment: {
57
+ id: string;
58
+ roomId: string;
59
+ name: string;
60
+ mimeType: string;
61
+ byteSize: string;
62
+ sha256: string;
63
+ status: 'pending' | 'uploaded';
64
+ };
65
+ upload: {
66
+ method: 'PUT';
67
+ url: string;
68
+ headers: Record<string, string>;
69
+ expiresAt: string;
70
+ };
71
+ idempotent: boolean;
72
+ };
73
+ export type RemoteRoomCreateResult = {
74
+ outcome: 'created';
75
+ room: {
76
+ id: string;
77
+ name: string;
78
+ visibility: 'private';
79
+ joinPolicy: 'approval';
80
+ webUrl: string;
81
+ inviteUrl: string;
82
+ };
83
+ agentIdentity: {
84
+ id: string;
85
+ displayName: string;
86
+ };
87
+ binding: {
88
+ id: string;
89
+ roomId: string;
90
+ agentIdentityId: string;
91
+ agentMembershipId: string;
92
+ status: 'active' | 'paused';
93
+ activationMode: RoomActivationMode;
94
+ };
95
+ idempotent: boolean;
96
+ };
97
+ export type RemoteOperatorRoomCreateResult = {
98
+ outcome: 'created';
99
+ room: {
100
+ id: string;
101
+ name: string;
102
+ visibility: 'private';
103
+ joinPolicy: 'approval';
104
+ webUrl: string;
105
+ };
106
+ };
107
+ export declare class RoomActionHttpError extends Error {
108
+ readonly code: string;
109
+ constructor(code: string, message: string);
110
+ }
111
+ export declare class HttpRoomActionClient {
112
+ private readonly fetchImpl;
113
+ private readonly baseUrl;
114
+ constructor(serverUrl: string, fetchImpl?: FetchLike);
115
+ createOperatorRoom(input: {
116
+ machineToken: string;
117
+ name: string;
118
+ }): Promise<RemoteOperatorRoomCreateResult>;
119
+ resolveNativeSession(input: {
120
+ machineToken: string;
121
+ runtimeAdapter: 'codex' | 'claude_code' | 'workbuddy';
122
+ sourceSessionKey: string;
123
+ }): Promise<{
124
+ nianSessionId: string;
125
+ created: boolean;
126
+ }>;
127
+ createRoom(input: {
128
+ machineToken: string;
129
+ bindingIntentId: string;
130
+ nianSessionId: string;
131
+ runtimeAdapter: 'codex' | 'claude_code' | 'workbuddy';
132
+ sessionRegistryKey: string;
133
+ name: string;
134
+ agentAlias: string;
135
+ activationCapability: 'managed' | 'idle_resume' | 'notify_only';
136
+ activationMode: RoomActivationMode;
137
+ }): Promise<RemoteRoomCreateResult>;
138
+ joinRoom(input: {
139
+ machineToken: string;
140
+ bindingIntentId: string;
141
+ nianSessionId: string;
142
+ runtimeAdapter: 'codex' | 'claude_code' | 'workbuddy';
143
+ sessionRegistryKey: string;
144
+ inviteToken: string;
145
+ message?: string;
146
+ agentAlias: string;
147
+ activationCapability: 'managed' | 'idle_resume' | 'notify_only';
148
+ activationMode: RoomActivationMode;
149
+ }): Promise<RemoteRoomJoinResult>;
150
+ status(target: BindingTarget): Promise<RemoteRoomAgentStatus>;
151
+ getAttachmentCapability(target: BindingTarget & {
152
+ attachmentId: string;
153
+ }): Promise<RemoteRoomAttachmentCapability>;
154
+ initializeAttachment(target: BindingTarget & {
155
+ clientUploadId: string;
156
+ name: string;
157
+ mediaKind: 'image' | 'audio' | 'file';
158
+ mimeType: string;
159
+ byteSize: number;
160
+ sha256: string;
161
+ }): Promise<RemoteRoomAttachmentUpload>;
162
+ completeAttachment(target: BindingTarget & {
163
+ attachmentId: string;
164
+ }): Promise<{
165
+ attachment: {
166
+ attachmentId: string;
167
+ mediaKind: 'image' | 'audio' | 'file';
168
+ name: string;
169
+ mimeType: string;
170
+ byteSize: string;
171
+ sha256: string;
172
+ status: 'ready';
173
+ };
174
+ idempotent: boolean;
175
+ }>;
176
+ read(target: BindingTarget & {
177
+ afterSequence: string;
178
+ limit: number;
179
+ }): Promise<{
180
+ messages: RoomMessageView[];
181
+ nextSequence: string;
182
+ hasMore: boolean;
183
+ historyFromSequence: string;
184
+ roomName: string;
185
+ }>;
186
+ send(target: BindingTarget & {
187
+ clientMessageId: string;
188
+ text: string;
189
+ mentionMembershipIds?: string[];
190
+ replyToMessageId?: string;
191
+ activationCauseMessageId?: string;
192
+ attachmentIds?: string[];
193
+ }): Promise<{
194
+ message: RoomMessageView;
195
+ idempotent: boolean;
196
+ }>;
197
+ mode(target: BindingTarget & {
198
+ activationMode: RoomActivationMode;
199
+ }): Promise<{
200
+ bindingId: string;
201
+ roomId: string;
202
+ status: 'active' | 'paused';
203
+ activationMode: RoomActivationMode;
204
+ idempotent: boolean;
205
+ }>;
206
+ private request;
207
+ }
208
+ export {};
@@ -0,0 +1 @@
1
+ class c extends Error{code;constructor(e,o){super(o),this.code=e,this.name="RoomActionHttpError"}}class j{fetchImpl;baseUrl;constructor(e,o=fetch){this.fetchImpl=o,this.baseUrl=e.replace(/\/+$/,"")}async createOperatorRoom(e){let o;try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/operator/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({name:e.name}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await m(o);if(!o.ok)throw p(o.status,t);return I(t,e,this.baseUrl)}async resolveNativeSession(e){let o;try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/resolve-session`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await m(o);if(!o.ok)throw p(o.status,t);(!t||typeof t!="object")&&a();const n=t;return(typeof n.nianSessionId!="string"||!/^[A-Za-z0-9_.:@/-]{1,512}$/.test(n.nianSessionId)||typeof n.created!="boolean")&&a(),n}async createRoom(e){let o;try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,name:e.name,agentAlias:e.agentAlias,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await m(o);if(!o.ok)throw p(o.status,t);return g(t,e,this.baseUrl)}async joinRoom(e){let o;try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/join`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,inviteToken:e.inviteToken,message:e.message,agentAlias:e.agentAlias,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await m(o);if(!o.ok)throw p(o.status,t);return v(t,e,this.baseUrl)}async status(e){const o=await this.request(e,"status");return b(o,e)}async getAttachmentCapability(e){const o=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/download-capability`);(!o||typeof o!="object")&&a();const t=o;return(t.attachmentId!==e.attachmentId||t.roomId!==e.roomId||typeof t.safeName!="string"||!S(t.safeName)||typeof t.mimeType!="string"||t.mimeType.length<1||t.mimeType.length>191||typeof t.byteSize!="string"||!d(t.byteSize)||typeof t.sha256!="string"||!/^[a-f0-9]{64}$/.test(t.sha256)||!h(t.downloadUrl)||typeof t.expiresAt!="string"||!Number.isFinite(Date.parse(t.expiresAt)))&&a(),t}async initializeAttachment(e){const o=await this.request(e,"attachments/init",{method:"POST",body:JSON.stringify({clientUploadId:e.clientUploadId,name:e.name,mediaKind:e.mediaKind,mimeType:e.mimeType,byteSize:e.byteSize,sha256:e.sha256})});(!o||typeof o!="object")&&a();const t=o,n=t.attachment,r=t.upload;return(!n||typeof n.id!="string"||n.roomId!==e.roomId||n.name.length<1||n.mimeType!==e.mimeType||n.byteSize!==String(e.byteSize)||n.sha256!==e.sha256||n.status!=="pending"&&n.status!=="uploaded"||!r||r.method!=="PUT"||!h(r.url)||!r.headers||r.headers["content-type"]!==e.mimeType||r.headers["x-oss-meta-sha256"]!==e.sha256||typeof r.expiresAt!="string"||Date.parse(r.expiresAt)<=Date.now()||typeof t.idempotent!="boolean")&&a(),t}async completeAttachment(e){const o=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/complete`,{method:"POST"});(!o||typeof o!="object")&&a();const t=o;typeof t.idempotent!="boolean"&&a();const n=t.attachment;return(!n||n.attachmentId!==e.attachmentId||n.mediaKind!=="image"&&n.mediaKind!=="audio"&&n.mediaKind!=="file"||typeof n.name!="string"||typeof n.mimeType!="string"||typeof n.byteSize!="string"||!d(n.byteSize)||typeof n.sha256!="string"||!/^[a-f0-9]{64}$/.test(n.sha256)||n.status!=="ready")&&a(),{attachment:n,idempotent:t.idempotent}}async read(e){const o=new URLSearchParams({after:e.afterSequence,limit:String(e.limit)}),t=await this.request(e,`messages?${o}`);(!t||typeof t!="object")&&a();const n=t;return(!Array.isArray(n.messages)||typeof n.nextSequence!="string"||!d(n.nextSequence)||typeof n.hasMore!="boolean"||typeof n.historyFromSequence!="string"||!d(n.historyFromSequence)||typeof n.roomName!="string")&&a(),{messages:n.messages.map(s=>l(s,e.roomId)),nextSequence:n.nextSequence,hasMore:n.hasMore,historyFromSequence:n.historyFromSequence,roomName:n.roomName}}async send(e){const o=await this.request(e,"messages",{method:"POST",body:JSON.stringify({clientMessageId:e.clientMessageId,text:e.text,mentionMembershipIds:e.mentionMembershipIds,replyToMessageId:e.replyToMessageId,activationCauseMessageId:e.activationCauseMessageId,attachmentIds:e.attachmentIds})});(!o||typeof o!="object")&&a();const t=o;return typeof t.idempotent!="boolean"&&a(),{message:l(t.message,e.roomId),idempotent:t.idempotent}}async mode(e){const o=await this.request(e,"mode",{method:"PATCH",body:JSON.stringify({activationMode:e.activationMode})});(!o||typeof o!="object")&&a();const t=o;return(t.bindingId!==e.bindingId||t.roomId!==e.roomId||t.status!=="active"&&t.status!=="paused"||!f(t.activationMode)||t.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&a(),t}async request(e,o,t={}){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/${o}`,{...t,headers:{authorization:`Bearer ${e.machineToken}`,...t.body?{"content-type":"application/json"}:{}},signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}if(!n.ok){let r=`room_action_http_${n.status}`;try{const s=await n.json();typeof s.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(s.error)&&(r=s.error)}catch{}throw new c(r,"Shennian Room action failed.")}try{return await n.json()}catch{a()}}}function b(i,e){(!i||typeof i!="object")&&a();const o=i,t=o.room;(o.bindingId!==e.bindingId||o.roomId!==e.roomId||typeof o.agentIdentityId!="string"||o.agentMembershipId!==null&&typeof o.agentMembershipId!="string"||o.status!=="pending_approval"&&o.status!=="active"&&o.status!=="paused"&&o.status!=="detached"||!f(o.activationMode)||typeof o.lastDeliveredSequence!="string"||!d(o.lastDeliveredSequence)||typeof o.lastActivatedSequence!="string"||!d(o.lastActivatedSequence)||!t||typeof t!="object")&&a(),(o.status==="pending_approval"&&o.agentMembershipId!==null||(o.status==="active"||o.status==="paused")&&typeof o.agentMembershipId!="string")&&a();const n=t;return(typeof n.name!="string"||typeof n.status!="string"||typeof n.lastSequence!="string"||!d(n.lastSequence))&&a(),o}function l(i,e){(!i||typeof i!="object")&&a();const o=i;if((typeof o.id!="string"||o.roomId!==e||typeof o.sequence!="string"||!d(o.sequence)||!o.author||typeof o.author.membershipId!="string"||!Array.isArray(o.attachments)||typeof o.createdAt!="string")&&a(),o.activationCause!=null){const t=o.activationCause;(typeof t.causeMessageId!="string"||typeof t.rootCauseMessageId!="string"||!Number.isInteger(t.hopCount)||t.hopCount<1||t.hopCount>32)&&a()}return o}function g(i,e,o){(!i||typeof i!="object")&&a();const t=i,n=t.room,r=t.agentIdentity,s=t.binding;return(t.outcome!=="created"||!n||typeof n.id!="string"||n.name!==e.name||n.visibility!=="private"||n.joinPolicy!=="approval"||!y(n.webUrl,o,`/spaces/${n.id}`)||!w(n.inviteUrl,o)||!r||typeof r.id!="string"||r.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==n.id||s.agentIdentityId!==r.id||typeof s.agentMembershipId!="string"||s.status!=="active"&&s.status!=="paused"||s.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&a(),t}function I(i,e,o){(!i||typeof i!="object")&&a();const t=i,n=t.room;return(t.outcome!=="created"||!n||typeof n.id!="string"||n.name!==e.name||n.visibility!=="private"||n.joinPolicy!=="approval"||!y(n.webUrl,o,`/spaces/${n.id}`))&&a(),t}function v(i,e,o){(!i||typeof i!="object")&&a();const t=i,n=t.room,r=t.agentIdentity,s=t.binding;return(t.outcome!=="joined"&&t.outcome!=="pending_approval"||!n||typeof n.id!="string"||typeof n.name!="string"||!y(n.webUrl,o,`/spaces/${n.id}`)||!r||typeof r.id!="string"||r.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==n.id||s.agentIdentityId!==r.id||s.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&a(),(t.outcome==="pending_approval"&&(s.status!=="pending_approval"||s.agentMembershipId!==null||typeof t.joinRequestId!="string")||t.outcome==="joined"&&(s.status!=="active"&&s.status!=="paused"||typeof s.agentMembershipId!="string"||t.joinRequestId!==void 0))&&a(),t}function d(i){return/^(0|[1-9]\d{0,19})$/.test(i)}function S(i){return i.length<=255&&i===i.trim()&&i!=="."&&i!==".."&&!/[\\/\0-\x1f\x7f]/.test(i)}function h(i){if(typeof i!="string")return!1;try{const e=new URL(i);return e.protocol==="https:"&&!e.username&&!e.password&&!e.hash}catch{return!1}}function f(i){return i==="auto_follow"||i==="mention_only"||i==="paused"}function a(){throw new c("invalid_room_action_response","Invalid Room action response.")}async function m(i){try{return await i.json()}catch{a()}}function p(i,e){let o=`room_action_http_${i}`;if(e&&typeof e=="object"){const t=e.error;typeof t=="string"&&/^[a-z0-9_-]{1,80}$/.test(t)&&(o=t)}return new c(o,"Shennian Room action failed.")}function y(i,e,o){return u(i,e,t=>t.pathname===o)}function w(i,e){return u(i,e,o=>/^\/spaces\/join\/[A-Za-z0-9_-]{43}$/.test(o.pathname))}function u(i,e,o){if(typeof i!="string")return!1;try{const t=new URL(i),n=new URL(e);return t.hostname===n.hostname&&(t.protocol==="https:"||n.protocol==="http:"&&t.protocol==="http:")&&!t.username&&!t.password&&!t.search&&!t.hash&&o(t)}catch{return!1}}export{j as HttpRoomActionClient,c as RoomActionHttpError};
@@ -0,0 +1,33 @@
1
+ import type { DaemonIpcHandler } from '../daemon-ipc/server.js';
2
+ import { RoomInvocationTokenAuthority } from './invocation-token.js';
3
+ import type { RoomAttachmentCache } from './attachment-cache.js';
4
+ import { HttpRoomActionClient } from './room-action-client.js';
5
+ import { RoomSessionRegistry } from './session-registry.js';
6
+ import type { RoomAttachmentUploader } from './attachment-upload.js';
7
+ import type { RoomHostSessionObservationStore } from './host-session-observations.js';
8
+ type RoomMirrorWriter = {
9
+ append(input: {
10
+ messages: Awaited<ReturnType<HttpRoomActionClient['read']>>['messages'];
11
+ historyFromSequence: string;
12
+ roomName?: string | null;
13
+ }): unknown;
14
+ markAccessRevoked(): void;
15
+ search(input: {
16
+ query: string;
17
+ limit?: number;
18
+ }): unknown;
19
+ };
20
+ export declare function createRoomActionIpcHandlers(input: {
21
+ authority: RoomInvocationTokenAuthority;
22
+ registry: RoomSessionRegistry;
23
+ actionClient: Pick<HttpRoomActionClient, 'createRoom' | 'joinRoom' | 'read' | 'send' | 'mode' | 'status'>;
24
+ getMachineCredentials: () => {
25
+ machineId?: string;
26
+ machineToken?: string;
27
+ };
28
+ mirrorFactory?: (roomId: string) => RoomMirrorWriter;
29
+ attachmentCache?: Pick<RoomAttachmentCache, 'materialize'>;
30
+ attachmentUploader?: Pick<RoomAttachmentUploader, 'upload'>;
31
+ observations?: RoomHostSessionObservationStore;
32
+ }): Record<string, DaemonIpcHandler>;
33
+ export {};
@@ -0,0 +1 @@
1
+ import{DaemonIpcError as a}from"../daemon-ipc/protocol.js";import{normalizeRoomActionParameters as R}from"./room-action-schema.js";function B(e){return{"room-action.execute":async o=>{const h=T(o),l=e.getMachineCredentials();if(!l.machineId?.trim()||!l.machineToken?.trim())throw new a("machine_not_authorized","This machine is not authorized yet.");const s=p(h.registryKey,"registryKey",64),A=p(h.invocationToken,"invocationToken",128),d=S(h.action),u=R(d,h.actionParameters);return e.registry.runExclusive(s,async()=>{let y;try{y=e.authority.consume({invocationToken:A,machineId:l.machineId,machineCredential:l.machineToken,registryKey:s,action:d,targetTool:"shennian_room",actionParameters:u})}catch(t){throw C(t)}const r=e.registry.getByRegistryKey(s),i=r?.binding;if(!r)throw new a("room_session_unavailable","The exact Agent session is unavailable.");if(d==="room.create"){if(i&&i.status!=="detached")throw new a("session_already_bound","This Agent session is already bound to a Room.");if(!y.bindingIntentId)throw new a("binding_intent_missing","Room create intent is missing.");const t=u,n=await e.actionClient.createRoom({machineToken:l.machineToken,bindingIntentId:y.bindingIntentId,nianSessionId:r.nianSessionId,runtimeAdapter:r.runtimeAdapter,sessionRegistryKey:s,name:t.name,agentAlias:t.agentAlias,activationCapability:_(r.capabilities),activationMode:t.activationMode});return e.registry.setBinding({registryKey:s,bindingId:n.binding.id,roomId:n.room.id,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode}),g(n)}if(d==="room.join"){if(i&&i.status!=="detached")throw new a("session_already_bound","This Agent session is already bound to a Room.");if(!y.bindingIntentId)throw new a("binding_intent_missing","Room join intent is missing.");const t=u,n=await e.actionClient.joinRoom({machineToken:l.machineToken,bindingIntentId:y.bindingIntentId,nianSessionId:r.nianSessionId,runtimeAdapter:r.runtimeAdapter,sessionRegistryKey:s,inviteToken:k(t.inviteUrl),message:t.message,agentAlias:t.agentAlias,activationCapability:_(r.capabilities),activationMode:t.activationMode});return e.registry.setBinding({registryKey:s,bindingId:n.binding.id,roomId:n.room.id,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode}),g(n)}if(!i)throw new a("room_not_bound","This Agent session is not bound to a Room.");if(d!=="room.status"&&(i.status==="detached"||i.status==="pending_approval"))throw new a(i.status==="pending_approval"?"room_binding_pending_approval":"room_binding_detached",i.status==="pending_approval"?"This Agent is waiting for Room approval.":"This Agent Room binding must be repaired before use.");const I={machineToken:l.machineToken,roomId:i.roomId,bindingId:i.bindingId};if(d==="room.status"){const t=await e.actionClient.status(I);e.registry.setBinding({registryKey:s,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:t.status,activationMode:t.activationMode}),t.status==="detached"&&e.mirrorFactory?.(t.roomId).markAccessRevoked();const n=e.registry.getByRegistryKey(s),c=n?.binding,m=e.observations?.getBySource(r);return g({...t,hostSession:{state:n?.lifecycleState??"closed",lastReportedEvent:m?.lastEventName??null,lastActiveAt:m?.lastActiveAt??null,lastIdleAt:m?.lastIdleAt??null,closedAt:m?.closedAt??null,closedConfirmed:m?.lifecycleState==="closed",activationCapability:_(r.capabilities)},localActivation:{queued:c?.activationQueue.length??0,inFlight:!!c?.activeActivation,pausedUntil:c?.loopBreaker.pausedUntil??null}})}if(d==="room.read"){const t=u;if(t.query){const b=e.mirrorFactory?.(i.roomId);if(!b)throw new a("room_mirror_unavailable","Room mirror is unavailable.");return g({source:"local_mirror",matches:b.search({query:t.query,limit:t.limit})})}const n=await e.actionClient.read({...I,afterSequence:t.afterSequence,limit:t.limit});e.mirrorFactory?.(i.roomId).append({messages:n.messages,historyFromSequence:n.historyFromSequence,roomName:n.roomName});const c=BigInt(n.nextSequence)>BigInt(i.lastDeliveredSequence)?n.nextSequence:i.lastDeliveredSequence;e.registry.advanceCursor({registryKey:s,cursor:"delivered",sequence:c});const m=[];for(const b of t.attachmentIds??[]){const v=n.messages.flatMap(w=>w.attachments).find(w=>w.attachmentId===b);if(!v||v.status!=="ready")throw new a("attachment_not_in_read_result","Requested attachment is not available in this read result.");if(!e.attachmentCache)throw new a("attachment_cache_unavailable","Room attachment cache is unavailable.");m.push({attachmentId:b,...await e.attachmentCache.materialize({...I,attachmentId:b,expected:{downloadRef:v.downloadRef,byteSize:v.byteSize,sha256:v.sha256}})})}return g({...n,materializedAttachments:m})}if(d==="room.send"){const t=u;let n;if(t.files?.length){const c=e.observations?.getBySource(r);if(!c?.workspacePath||!e.attachmentUploader)throw new a("room_attachment_upload_unavailable","The exact Agent workspace is unavailable for attachment upload.");n=await e.attachmentUploader.upload({...I,workspacePath:c.workspacePath,clientMessageId:t.clientMessageId,files:t.files})}return g(await e.actionClient.send({...I,clientMessageId:t.clientMessageId,text:t.text,mentionMembershipIds:t.mentionMembershipIds,replyToMessageId:t.replyToMessageId,attachmentIds:n,activationCauseMessageId:i.activeActivation?.causeMessageId}))}const M=u,f=await e.actionClient.mode({...I,...M});return e.registry.setBinding({registryKey:s,bindingId:i.bindingId,roomId:i.roomId,agentIdentityId:i.agentIdentityId,agentMembershipId:i.agentMembershipId,status:f.status,activationMode:f.activationMode}),g(f)})}}}function T(e){if(!e||Array.isArray(e)||typeof e!="object")throw new a("invalid_params","Room action parameters are required.");return e}function p(e,o,h){if(typeof e!="string"||!e.trim()||e.length>h)throw new a("invalid_params",`${o} is invalid.`);return e.trim()}function S(e){const o=p(e,"action",64);if(o!=="room.read"&&o!=="room.create"&&o!=="room.join"&&o!=="room.send"&&o!=="room.mode"&&o!=="room.status")throw new a("room_action_not_implemented","Room action is not available yet.");return o}function k(e){const o=new URL(e).pathname.split("/").at(-1);if(!o||!/^[A-Za-z0-9_-]{43}$/.test(o))throw new a("invalid_params","inviteUrl is invalid.");return o}function _(e){return e.canResumeIdleSession||e.canActivateWhileHostOpen?"idle_resume":"notify_only"}function C(e){return e&&typeof e=="object"&&"code"in e&&typeof e.code=="string"&&"message"in e&&typeof e.message=="string"?new a(e.code,e.message):new a("room_invocation_failed","Room invocation failed.")}function g(e){return JSON.parse(JSON.stringify(e))}export{B as createRoomActionIpcHandlers};
@@ -0,0 +1,3 @@
1
+ import type { RoomInvocationAction } from './invocation-token.js';
2
+ import type { JsonValue } from './pending-authorized-actions.js';
3
+ export declare function normalizeRoomActionParameters(action: Exclude<RoomInvocationAction, 'room.bind'>, value: JsonValue | undefined): JsonValue;
@@ -0,0 +1 @@
1
+ import{DaemonIpcError as p}from"../daemon-ipc/protocol.js";function I(t,n){const e=h(n);switch(t){case"room.status":return m(e,[]),{};case"room.read":{m(e,["afterSequence","limit","attachmentIds","query"]);const s=d(e.query,"query",200),r=e.afterSequence??"0",a=e.limit??50;(typeof r!="string"||!/^(0|[1-9]\d{0,19})$/.test(r))&&i("afterSequence"),(typeof a!="number"||!Number.isInteger(a)||a<1||a>100)&&i("limit");const c=u(e.attachmentIds,"attachmentIds",10);return s&&(e.afterSequence!=null||c)&&i("parameters"),s?{query:s,limit:a}:{afterSequence:r,limit:a,...c?{attachmentIds:c}:{}}}case"room.send":{m(e,["clientMessageId","text","files","mentionMembershipIds","replyToMessageId"]);const s=o(e.clientMessageId,"clientMessageId",191),r=d(e.text,"text",2e4),a=g(e.files,"files",10);!r&&!a&&i("parameters");const c=u(e.mentionMembershipIds,"mentionMembershipIds",20),l=d(e.replyToMessageId,"replyToMessageId",191);return{clientMessageId:s,text:r??"",...a?{files:a}:{},...c?{mentionMembershipIds:c}:{},...l?{replyToMessageId:l}:{}}}case"room.mode":return m(e,["activationMode"]),{activationMode:f(e.activationMode)};case"room.create":return m(e,["name","agentAlias","activationMode"]),{name:o(e.name,"name",80),agentAlias:o(e.agentAlias,"agentAlias",80),activationMode:f(e.activationMode??"auto_follow")};case"room.join":return m(e,["inviteUrl","agentAlias","message","activationMode"]),{inviteUrl:y(e.inviteUrl),agentAlias:o(e.agentAlias,"agentAlias",80),activationMode:f(e.activationMode??"auto_follow"),...d(e.message,"message",500)?{message:d(e.message,"message",500)}:{}}}}function g(t,n,e){return t==null?void 0:((!Array.isArray(t)||t.length<1||t.length>e)&&i(n),t.map(r=>((typeof r!="string"||!r.trim()||r.length>4096||/[\0\r\n]/.test(r))&&i(n),r.trim())))}function h(t){return t==null?{}:((Array.isArray(t)||typeof t!="object")&&i("parameters"),t)}function m(t,n){const e=new Set(n);Object.keys(t).some(s=>!e.has(s))&&i("parameters")}function o(t,n,e){return(typeof t!="string"||!t.trim()||t.length>e||/[\0\r]/.test(t))&&i(n),t.trim()}function d(t,n,e){if(!(t==null||t===""))return o(t,n,e)}function u(t,n,e){if(t==null)return;(!Array.isArray(t)||t.length>e)&&i(n);const s=t.map(r=>o(r,n,191));return new Set(s).size!==s.length&&i(n),s.length>0?s:void 0}function f(t){if(t==="auto"||t==="auto_follow")return"auto_follow";if(t==="mention"||t==="mention_only")return"mention_only";if(t==="paused")return"paused";i("activationMode")}function y(t){const n=o(t,"inviteUrl",2048);let e;try{e=new URL(n)}catch{i("inviteUrl")}return(e.protocol!=="https:"||e.hostname!=="shennian.net"||!/^\/spaces\/join\/[A-Za-z0-9_-]{43}$/.test(e.pathname)||e.username||e.password||e.search||e.hash)&&i("inviteUrl"),e.toString()}function i(t){throw new p("invalid_params",`${t} is invalid.`)}export{I as normalizeRoomActionParameters};
@@ -0,0 +1,23 @@
1
+ import type { AuthorizedActionExecutor } from './authorized-action-coordinator.js';
2
+ import type { RoomHostSessionObservationStore } from './host-session-observations.js';
3
+ import type { HttpRoomBindingClient } from './room-binding-client.js';
4
+ import type { JsonValue, PendingAuthorizedAction } from './pending-authorized-actions.js';
5
+ import type { RoomSessionRegistry } from './session-registry.js';
6
+ type Credentials = {
7
+ machineId: string;
8
+ machineToken: string;
9
+ };
10
+ export declare class RoomAuthorizedActionExecutor implements AuthorizedActionExecutor {
11
+ private readonly deps;
12
+ constructor(deps: {
13
+ registry: RoomSessionRegistry;
14
+ observations: RoomHostSessionObservationStore;
15
+ actionClient: Pick<HttpRoomBindingClient, 'resolveNativeSession' | 'createRoom' | 'joinRoom'>;
16
+ getMachineCredentials: () => {
17
+ machineId?: string;
18
+ machineToken?: string;
19
+ };
20
+ });
21
+ execute(action: PendingAuthorizedAction, claimed?: Credentials): Promise<JsonValue>;
22
+ }
23
+ export {};