shennian 0.3.1 → 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 (282) 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 +2 -1
  140. package/node_modules/@shennian/wire/dist/index.d.ts.map +1 -1
  141. package/node_modules/@shennian/wire/dist/index.js +2 -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/room.d.ts +112 -0
  148. package/node_modules/@shennian/wire/dist/room.d.ts.map +1 -0
  149. package/node_modules/@shennian/wire/dist/room.js +4 -0
  150. package/node_modules/@shennian/wire/dist/room.js.map +1 -0
  151. package/node_modules/@shennian/wire/dist/session.d.ts +2 -2
  152. package/node_modules/@shennian/wire/dist/session.d.ts.map +1 -1
  153. package/node_modules/@shennian/wire/dist/session.js +1 -1
  154. package/node_modules/@shennian/wire/dist/session.js.map +1 -1
  155. package/package.json +3 -5
  156. package/dist/scripts/wechat-rpa-confirmation.mjs +0 -5
  157. package/dist/src/agents/pi-context.d.ts +0 -42
  158. package/dist/src/agents/pi-context.js +0 -12
  159. package/dist/src/agents/pi.d.ts +0 -70
  160. package/dist/src/agents/pi.js +0 -17
  161. package/dist/src/channels/base.d.ts +0 -144
  162. package/dist/src/channels/base.js +0 -1
  163. package/dist/src/channels/registry.d.ts +0 -8
  164. package/dist/src/channels/registry.js +0 -1
  165. package/dist/src/channels/reply-split.d.ts +0 -1
  166. package/dist/src/channels/reply-split.js +0 -10
  167. package/dist/src/channels/runtime.d.ts +0 -138
  168. package/dist/src/channels/runtime.js +0 -5
  169. package/dist/src/channels/secret-registry.d.ts +0 -35
  170. package/dist/src/channels/secret-registry.js +0 -1
  171. package/dist/src/channels/websocket.d.ts +0 -55
  172. package/dist/src/channels/websocket.js +0 -8
  173. package/dist/src/channels/wechat-channel/anchor.d.ts +0 -10
  174. package/dist/src/channels/wechat-channel/anchor.js +0 -1
  175. package/dist/src/channels/wechat-channel/automation-lane.d.ts +0 -35
  176. package/dist/src/channels/wechat-channel/automation-lane.js +0 -2
  177. package/dist/src/channels/wechat-channel/client.d.ts +0 -283
  178. package/dist/src/channels/wechat-channel/client.js +0 -1
  179. package/dist/src/channels/wechat-channel/cooldown.d.ts +0 -21
  180. package/dist/src/channels/wechat-channel/cooldown.js +0 -1
  181. package/dist/src/channels/wechat-channel/core/fixture-compare.d.ts +0 -52
  182. package/dist/src/channels/wechat-channel/core/fixture-compare.js +0 -1
  183. package/dist/src/channels/wechat-channel/core/media-action-plan.d.ts +0 -3
  184. package/dist/src/channels/wechat-channel/core/media-action-plan.js +0 -1
  185. package/dist/src/channels/wechat-channel/core/schema.d.ts +0 -67
  186. package/dist/src/channels/wechat-channel/core/schema.js +0 -1
  187. package/dist/src/channels/wechat-channel/core/trace-comparator.d.ts +0 -28
  188. package/dist/src/channels/wechat-channel/core/trace-comparator.js +0 -1
  189. package/dist/src/channels/wechat-channel/fingerprint.d.ts +0 -31
  190. package/dist/src/channels/wechat-channel/fingerprint.js +0 -1
  191. package/dist/src/channels/wechat-channel/helper-assets.d.ts +0 -103
  192. package/dist/src/channels/wechat-channel/helper-assets.js +0 -1
  193. package/dist/src/channels/wechat-channel/helper-client.d.ts +0 -87
  194. package/dist/src/channels/wechat-channel/helper-client.js +0 -3
  195. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +0 -122
  196. package/dist/src/channels/wechat-channel/helper-protocol.js +0 -1
  197. package/dist/src/channels/wechat-channel/human-coordination.d.ts +0 -25
  198. package/dist/src/channels/wechat-channel/human-coordination.js +0 -1
  199. package/dist/src/channels/wechat-channel/index.d.ts +0 -21
  200. package/dist/src/channels/wechat-channel/index.js +0 -1
  201. package/dist/src/channels/wechat-channel/ledger.d.ts +0 -48
  202. package/dist/src/channels/wechat-channel/ledger.js +0 -2
  203. package/dist/src/channels/wechat-channel/media-cache-resolver.d.ts +0 -42
  204. package/dist/src/channels/wechat-channel/media-cache-resolver.js +0 -1
  205. package/dist/src/channels/wechat-channel/media-resolver.d.ts +0 -186
  206. package/dist/src/channels/wechat-channel/media-resolver.js +0 -1
  207. package/dist/src/channels/wechat-channel/message-key.d.ts +0 -19
  208. package/dist/src/channels/wechat-channel/message-key.js +0 -1
  209. package/dist/src/channels/wechat-channel/observer.d.ts +0 -171
  210. package/dist/src/channels/wechat-channel/observer.js +0 -7
  211. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +0 -96
  212. package/dist/src/channels/wechat-channel/outbound-ledger.js +0 -2
  213. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +0 -74
  214. package/dist/src/channels/wechat-channel/outbound-sender.js +0 -1
  215. package/dist/src/channels/wechat-channel/pacing.d.ts +0 -3
  216. package/dist/src/channels/wechat-channel/pacing.js +0 -1
  217. package/dist/src/channels/wechat-channel/preflight.d.ts +0 -46
  218. package/dist/src/channels/wechat-channel/preflight.js +0 -1
  219. package/dist/src/channels/wechat-channel/runner.d.ts +0 -43
  220. package/dist/src/channels/wechat-channel/runner.js +0 -1
  221. package/dist/src/channels/wechat-channel/runtime.d.ts +0 -50
  222. package/dist/src/channels/wechat-channel/runtime.js +0 -1
  223. package/dist/src/channels/wechat-channel/scheduler.d.ts +0 -74
  224. package/dist/src/channels/wechat-channel/scheduler.js +0 -1
  225. package/dist/src/channels/wechat-channel/vector-store.d.ts +0 -44
  226. package/dist/src/channels/wechat-channel/vector-store.js +0 -1
  227. package/dist/src/channels/wechat-rpa/macos-flow.d.ts +0 -55
  228. package/dist/src/channels/wechat-rpa/macos-flow.js +0 -1
  229. package/dist/src/channels/wechat-rpa/macos.d.ts +0 -11
  230. package/dist/src/channels/wechat-rpa/macos.js +0 -21
  231. package/dist/src/channels/wechat-rpa/normalizer.d.ts +0 -49
  232. package/dist/src/channels/wechat-rpa/normalizer.js +0 -7
  233. package/dist/src/channels/wechat-rpa/product-channel.d.ts +0 -76
  234. package/dist/src/channels/wechat-rpa/product-channel.js +0 -16
  235. package/dist/src/channels/wechat-rpa-session-sync.d.ts +0 -26
  236. package/dist/src/channels/wechat-rpa-session-sync.js +0 -1
  237. package/dist/src/channels/wechat-rpa.d.ts +0 -104
  238. package/dist/src/channels/wechat-rpa.js +0 -6
  239. package/dist/src/channels/wecom.d.ts +0 -51
  240. package/dist/src/channels/wecom.js +0 -4
  241. package/dist/src/commands/external-attachments.d.ts +0 -9
  242. package/dist/src/commands/external-attachments.js +0 -1
  243. package/dist/src/commands/external.d.ts +0 -2
  244. package/dist/src/commands/external.js +0 -1
  245. package/dist/src/commands/helper-runtime-official-download.d.ts +0 -80
  246. package/dist/src/commands/helper-runtime-official-download.js +0 -1
  247. package/dist/src/commands/runtime.d.ts +0 -120
  248. package/dist/src/commands/runtime.js +0 -1
  249. package/dist/src/commands/wechat/command.d.ts +0 -3
  250. package/dist/src/commands/wechat/command.js +0 -1
  251. package/dist/src/commands/wechat/direct.d.ts +0 -14
  252. package/dist/src/commands/wechat/direct.js +0 -2
  253. package/dist/src/commands/wechat/doctor.d.ts +0 -2
  254. package/dist/src/commands/wechat/doctor.js +0 -1
  255. package/dist/src/commands/wechat/ipc.d.ts +0 -9
  256. package/dist/src/commands/wechat/ipc.js +0 -1
  257. package/dist/src/commands/wechat/operations.d.ts +0 -3
  258. package/dist/src/commands/wechat/operations.js +0 -4
  259. package/dist/src/commands/wechat/output.d.ts +0 -7
  260. package/dist/src/commands/wechat/output.js +0 -4
  261. package/dist/src/commands/wechat/types.d.ts +0 -142
  262. package/dist/src/commands/wechat/types.js +0 -1
  263. package/dist/src/commands/wechat/utils.d.ts +0 -16
  264. package/dist/src/commands/wechat/utils.js +0 -1
  265. package/dist/src/commands/wechat.d.ts +0 -6
  266. package/dist/src/commands/wechat.js +0 -1
  267. package/dist/src/devtools/wechat-channel-action-smoke.d.ts +0 -105
  268. package/dist/src/devtools/wechat-channel-action-smoke.js +0 -10
  269. package/dist/src/devtools/wechat-channel-download-report.d.ts +0 -111
  270. package/dist/src/devtools/wechat-channel-download-report.js +0 -2
  271. package/dist/src/devtools/wechat-channel-markdown-report.d.ts +0 -6
  272. package/dist/src/devtools/wechat-channel-markdown-report.js +0 -5
  273. package/dist/src/devtools/wechat-channel-product-steps.d.ts +0 -9
  274. package/dist/src/devtools/wechat-channel-product-steps.js +0 -40
  275. package/dist/src/session/handlers/skills.d.ts +0 -7
  276. package/dist/src/session/handlers/skills.js +0 -1
  277. package/dist/src/session/handlers/wechat-helper-runtime.d.ts +0 -33
  278. package/dist/src/session/handlers/wechat-helper-runtime.js +0 -1
  279. package/dist/src/skills/registry.d.ts +0 -16
  280. package/dist/src/skills/registry.js +0 -15
  281. package/dist/src/skills/setup.d.ts +0 -11
  282. 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 {};