@viewportai/daemon 0.1.0

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 (350) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +157 -0
  3. package/bin/vpd.js +3 -0
  4. package/dist/adapters/claude.d.ts +119 -0
  5. package/dist/adapters/claude.d.ts.map +1 -0
  6. package/dist/adapters/claude.js +621 -0
  7. package/dist/adapters/claude.js.map +1 -0
  8. package/dist/adapters/codex-event-normalizers.d.ts +10 -0
  9. package/dist/adapters/codex-event-normalizers.d.ts.map +1 -0
  10. package/dist/adapters/codex-event-normalizers.js +208 -0
  11. package/dist/adapters/codex-event-normalizers.js.map +1 -0
  12. package/dist/adapters/codex-sdk-loader.d.ts +47 -0
  13. package/dist/adapters/codex-sdk-loader.d.ts.map +1 -0
  14. package/dist/adapters/codex-sdk-loader.js +19 -0
  15. package/dist/adapters/codex-sdk-loader.js.map +1 -0
  16. package/dist/adapters/codex.d.ts +56 -0
  17. package/dist/adapters/codex.d.ts.map +1 -0
  18. package/dist/adapters/codex.js +267 -0
  19. package/dist/adapters/codex.js.map +1 -0
  20. package/dist/adapters/gemini-cli.d.ts +49 -0
  21. package/dist/adapters/gemini-cli.d.ts.map +1 -0
  22. package/dist/adapters/gemini-cli.js +181 -0
  23. package/dist/adapters/gemini-cli.js.map +1 -0
  24. package/dist/adapters/pty.d.ts +56 -0
  25. package/dist/adapters/pty.d.ts.map +1 -0
  26. package/dist/adapters/pty.js +223 -0
  27. package/dist/adapters/pty.js.map +1 -0
  28. package/dist/agents/aider.d.ts +9 -0
  29. package/dist/agents/aider.d.ts.map +1 -0
  30. package/dist/agents/aider.js +37 -0
  31. package/dist/agents/aider.js.map +1 -0
  32. package/dist/agents/built-in.d.ts +4 -0
  33. package/dist/agents/built-in.d.ts.map +1 -0
  34. package/dist/agents/built-in.js +6 -0
  35. package/dist/agents/built-in.js.map +1 -0
  36. package/dist/agents/claude.d.ts +12 -0
  37. package/dist/agents/claude.d.ts.map +1 -0
  38. package/dist/agents/claude.js +93 -0
  39. package/dist/agents/claude.js.map +1 -0
  40. package/dist/agents/codex.d.ts +6 -0
  41. package/dist/agents/codex.d.ts.map +1 -0
  42. package/dist/agents/codex.js +64 -0
  43. package/dist/agents/codex.js.map +1 -0
  44. package/dist/agents/command-detection.d.ts +6 -0
  45. package/dist/agents/command-detection.d.ts.map +1 -0
  46. package/dist/agents/command-detection.js +12 -0
  47. package/dist/agents/command-detection.js.map +1 -0
  48. package/dist/agents/gemini.d.ts +6 -0
  49. package/dist/agents/gemini.d.ts.map +1 -0
  50. package/dist/agents/gemini.js +36 -0
  51. package/dist/agents/gemini.js.map +1 -0
  52. package/dist/cli/agent-commands.d.ts +2 -0
  53. package/dist/cli/agent-commands.d.ts.map +1 -0
  54. package/dist/cli/agent-commands.js +87 -0
  55. package/dist/cli/agent-commands.js.map +1 -0
  56. package/dist/cli/args.d.ts +9 -0
  57. package/dist/cli/args.d.ts.map +1 -0
  58. package/dist/cli/args.js +34 -0
  59. package/dist/cli/args.js.map +1 -0
  60. package/dist/cli/command-shared.d.ts +53 -0
  61. package/dist/cli/command-shared.d.ts.map +1 -0
  62. package/dist/cli/command-shared.js +239 -0
  63. package/dist/cli/command-shared.js.map +1 -0
  64. package/dist/cli/commands.d.ts +20 -0
  65. package/dist/cli/commands.d.ts.map +1 -0
  66. package/dist/cli/commands.js +20 -0
  67. package/dist/cli/commands.js.map +1 -0
  68. package/dist/cli/daemon-client.d.ts +30 -0
  69. package/dist/cli/daemon-client.d.ts.map +1 -0
  70. package/dist/cli/daemon-client.js +161 -0
  71. package/dist/cli/daemon-client.js.map +1 -0
  72. package/dist/cli/daemon-lifecycle.d.ts +47 -0
  73. package/dist/cli/daemon-lifecycle.d.ts.map +1 -0
  74. package/dist/cli/daemon-lifecycle.js +262 -0
  75. package/dist/cli/daemon-lifecycle.js.map +1 -0
  76. package/dist/cli/daemon-settings.d.ts +9 -0
  77. package/dist/cli/daemon-settings.d.ts.map +1 -0
  78. package/dist/cli/daemon-settings.js +168 -0
  79. package/dist/cli/daemon-settings.js.map +1 -0
  80. package/dist/cli/directory-commands.d.ts +4 -0
  81. package/dist/cli/directory-commands.d.ts.map +1 -0
  82. package/dist/cli/directory-commands.js +190 -0
  83. package/dist/cli/directory-commands.js.map +1 -0
  84. package/dist/cli/hook-command.d.ts +14 -0
  85. package/dist/cli/hook-command.d.ts.map +1 -0
  86. package/dist/cli/hook-command.js +96 -0
  87. package/dist/cli/hook-command.js.map +1 -0
  88. package/dist/cli/install-command.d.ts +2 -0
  89. package/dist/cli/install-command.d.ts.map +1 -0
  90. package/dist/cli/install-command.js +91 -0
  91. package/dist/cli/install-command.js.map +1 -0
  92. package/dist/cli/lifecycle-commands.d.ts +10 -0
  93. package/dist/cli/lifecycle-commands.d.ts.map +1 -0
  94. package/dist/cli/lifecycle-commands.js +524 -0
  95. package/dist/cli/lifecycle-commands.js.map +1 -0
  96. package/dist/cli/listen-target.d.ts +13 -0
  97. package/dist/cli/listen-target.d.ts.map +1 -0
  98. package/dist/cli/listen-target.js +102 -0
  99. package/dist/cli/listen-target.js.map +1 -0
  100. package/dist/cli/orchestration-commands.d.ts +8 -0
  101. package/dist/cli/orchestration-commands.d.ts.map +1 -0
  102. package/dist/cli/orchestration-commands.js +340 -0
  103. package/dist/cli/orchestration-commands.js.map +1 -0
  104. package/dist/cli/permission-commands.d.ts +2 -0
  105. package/dist/cli/permission-commands.d.ts.map +1 -0
  106. package/dist/cli/permission-commands.js +138 -0
  107. package/dist/cli/permission-commands.js.map +1 -0
  108. package/dist/cli/runtime-toolchain.d.ts +35 -0
  109. package/dist/cli/runtime-toolchain.d.ts.map +1 -0
  110. package/dist/cli/runtime-toolchain.js +184 -0
  111. package/dist/cli/runtime-toolchain.js.map +1 -0
  112. package/dist/cli/service-commands.d.ts +19 -0
  113. package/dist/cli/service-commands.d.ts.map +1 -0
  114. package/dist/cli/service-commands.js +273 -0
  115. package/dist/cli/service-commands.js.map +1 -0
  116. package/dist/cli/session-commands.d.ts +3 -0
  117. package/dist/cli/session-commands.d.ts.map +1 -0
  118. package/dist/cli/session-commands.js +146 -0
  119. package/dist/cli/session-commands.js.map +1 -0
  120. package/dist/cli/setup-command.d.ts +12 -0
  121. package/dist/cli/setup-command.d.ts.map +1 -0
  122. package/dist/cli/setup-command.js +223 -0
  123. package/dist/cli/setup-command.js.map +1 -0
  124. package/dist/cli/supervisor-protocol.d.ts +20 -0
  125. package/dist/cli/supervisor-protocol.d.ts.map +1 -0
  126. package/dist/cli/supervisor-protocol.js +5 -0
  127. package/dist/cli/supervisor-protocol.js.map +1 -0
  128. package/dist/cli/supervisor.d.ts +10 -0
  129. package/dist/cli/supervisor.d.ts.map +1 -0
  130. package/dist/cli/supervisor.js +218 -0
  131. package/dist/cli/supervisor.js.map +1 -0
  132. package/dist/cli/worktree-commands.d.ts +2 -0
  133. package/dist/cli/worktree-commands.d.ts.map +1 -0
  134. package/dist/cli/worktree-commands.js +250 -0
  135. package/dist/cli/worktree-commands.js.map +1 -0
  136. package/dist/cli/ws-client.d.ts +20 -0
  137. package/dist/cli/ws-client.d.ts.map +1 -0
  138. package/dist/cli/ws-client.js +103 -0
  139. package/dist/cli/ws-client.js.map +1 -0
  140. package/dist/core/agent-registry.d.ts +128 -0
  141. package/dist/core/agent-registry.d.ts.map +1 -0
  142. package/dist/core/agent-registry.js +131 -0
  143. package/dist/core/agent-registry.js.map +1 -0
  144. package/dist/core/config-schema.d.ts +77 -0
  145. package/dist/core/config-schema.d.ts.map +1 -0
  146. package/dist/core/config-schema.js +66 -0
  147. package/dist/core/config-schema.js.map +1 -0
  148. package/dist/core/config.d.ts +111 -0
  149. package/dist/core/config.d.ts.map +1 -0
  150. package/dist/core/config.js +244 -0
  151. package/dist/core/config.js.map +1 -0
  152. package/dist/core/daemon.d.ts +113 -0
  153. package/dist/core/daemon.d.ts.map +1 -0
  154. package/dist/core/daemon.js +197 -0
  155. package/dist/core/daemon.js.map +1 -0
  156. package/dist/core/discovered-sessions.d.ts +7 -0
  157. package/dist/core/discovered-sessions.d.ts.map +1 -0
  158. package/dist/core/discovered-sessions.js +39 -0
  159. package/dist/core/discovered-sessions.js.map +1 -0
  160. package/dist/core/error-codes.d.ts +31 -0
  161. package/dist/core/error-codes.d.ts.map +1 -0
  162. package/dist/core/error-codes.js +30 -0
  163. package/dist/core/error-codes.js.map +1 -0
  164. package/dist/core/errors.d.ts +16 -0
  165. package/dist/core/errors.d.ts.map +1 -0
  166. package/dist/core/errors.js +43 -0
  167. package/dist/core/errors.js.map +1 -0
  168. package/dist/core/events.d.ts +183 -0
  169. package/dist/core/events.d.ts.map +1 -0
  170. package/dist/core/events.js +61 -0
  171. package/dist/core/events.js.map +1 -0
  172. package/dist/core/interfaces.d.ts +115 -0
  173. package/dist/core/interfaces.d.ts.map +1 -0
  174. package/dist/core/interfaces.js +9 -0
  175. package/dist/core/interfaces.js.map +1 -0
  176. package/dist/core/logger.d.ts +11 -0
  177. package/dist/core/logger.d.ts.map +1 -0
  178. package/dist/core/logger.js +17 -0
  179. package/dist/core/logger.js.map +1 -0
  180. package/dist/core/metrics.d.ts +24 -0
  181. package/dist/core/metrics.d.ts.map +1 -0
  182. package/dist/core/metrics.js +32 -0
  183. package/dist/core/metrics.js.map +1 -0
  184. package/dist/core/output.d.ts +13 -0
  185. package/dist/core/output.d.ts.map +1 -0
  186. package/dist/core/output.js +22 -0
  187. package/dist/core/output.js.map +1 -0
  188. package/dist/core/permission-coordinator.d.ts +67 -0
  189. package/dist/core/permission-coordinator.d.ts.map +1 -0
  190. package/dist/core/permission-coordinator.js +209 -0
  191. package/dist/core/permission-coordinator.js.map +1 -0
  192. package/dist/core/session-manager.d.ts +121 -0
  193. package/dist/core/session-manager.d.ts.map +1 -0
  194. package/dist/core/session-manager.js +354 -0
  195. package/dist/core/session-manager.js.map +1 -0
  196. package/dist/core/session-state-file.d.ts +20 -0
  197. package/dist/core/session-state-file.d.ts.map +1 -0
  198. package/dist/core/session-state-file.js +49 -0
  199. package/dist/core/session-state-file.js.map +1 -0
  200. package/dist/core/types.d.ts +250 -0
  201. package/dist/core/types.d.ts.map +1 -0
  202. package/dist/core/types.js +88 -0
  203. package/dist/core/types.js.map +1 -0
  204. package/dist/directories/manager.d.ts +32 -0
  205. package/dist/directories/manager.d.ts.map +1 -0
  206. package/dist/directories/manager.js +86 -0
  207. package/dist/directories/manager.js.map +1 -0
  208. package/dist/discovery/claude.d.ts +29 -0
  209. package/dist/discovery/claude.d.ts.map +1 -0
  210. package/dist/discovery/claude.js +55 -0
  211. package/dist/discovery/claude.js.map +1 -0
  212. package/dist/discovery/codex.d.ts +11 -0
  213. package/dist/discovery/codex.d.ts.map +1 -0
  214. package/dist/discovery/codex.js +365 -0
  215. package/dist/discovery/codex.js.map +1 -0
  216. package/dist/discovery/gemini.d.ts +15 -0
  217. package/dist/discovery/gemini.d.ts.map +1 -0
  218. package/dist/discovery/gemini.js +151 -0
  219. package/dist/discovery/gemini.js.map +1 -0
  220. package/dist/discovery/jsonl-reader.d.ts +122 -0
  221. package/dist/discovery/jsonl-reader.d.ts.map +1 -0
  222. package/dist/discovery/jsonl-reader.js +622 -0
  223. package/dist/discovery/jsonl-reader.js.map +1 -0
  224. package/dist/discovery/watcher.d.ts +29 -0
  225. package/dist/discovery/watcher.d.ts.map +1 -0
  226. package/dist/discovery/watcher.js +383 -0
  227. package/dist/discovery/watcher.js.map +1 -0
  228. package/dist/hooks/index.d.ts +10 -0
  229. package/dist/hooks/index.d.ts.map +1 -0
  230. package/dist/hooks/index.js +8 -0
  231. package/dist/hooks/index.js.map +1 -0
  232. package/dist/hooks/installers/base.d.ts +27 -0
  233. package/dist/hooks/installers/base.d.ts.map +1 -0
  234. package/dist/hooks/installers/base.js +9 -0
  235. package/dist/hooks/installers/base.js.map +1 -0
  236. package/dist/hooks/installers/claude.d.ts +18 -0
  237. package/dist/hooks/installers/claude.d.ts.map +1 -0
  238. package/dist/hooks/installers/claude.js +120 -0
  239. package/dist/hooks/installers/claude.js.map +1 -0
  240. package/dist/hooks/router.d.ts +63 -0
  241. package/dist/hooks/router.d.ts.map +1 -0
  242. package/dist/hooks/router.js +259 -0
  243. package/dist/hooks/router.js.map +1 -0
  244. package/dist/hooks/supervision.d.ts +27 -0
  245. package/dist/hooks/supervision.d.ts.map +1 -0
  246. package/dist/hooks/supervision.js +67 -0
  247. package/dist/hooks/supervision.js.map +1 -0
  248. package/dist/hooks/types.d.ts +171 -0
  249. package/dist/hooks/types.d.ts.map +1 -0
  250. package/dist/hooks/types.js +148 -0
  251. package/dist/hooks/types.js.map +1 -0
  252. package/dist/index.d.ts +17 -0
  253. package/dist/index.d.ts.map +1 -0
  254. package/dist/index.js +117 -0
  255. package/dist/index.js.map +1 -0
  256. package/dist/permissions/engine.d.ts +23 -0
  257. package/dist/permissions/engine.d.ts.map +1 -0
  258. package/dist/permissions/engine.js +43 -0
  259. package/dist/permissions/engine.js.map +1 -0
  260. package/dist/plugins/loader.d.ts +44 -0
  261. package/dist/plugins/loader.d.ts.map +1 -0
  262. package/dist/plugins/loader.js +177 -0
  263. package/dist/plugins/loader.js.map +1 -0
  264. package/dist/server/auth.d.ts +32 -0
  265. package/dist/server/auth.d.ts.map +1 -0
  266. package/dist/server/auth.js +78 -0
  267. package/dist/server/auth.js.map +1 -0
  268. package/dist/server/discovered-watch-key.d.ts +5 -0
  269. package/dist/server/discovered-watch-key.d.ts.map +1 -0
  270. package/dist/server/discovered-watch-key.js +31 -0
  271. package/dist/server/discovered-watch-key.js.map +1 -0
  272. package/dist/server/hello-builder.d.ts +17 -0
  273. package/dist/server/hello-builder.d.ts.map +1 -0
  274. package/dist/server/hello-builder.js +71 -0
  275. package/dist/server/hello-builder.js.map +1 -0
  276. package/dist/server/http-server.d.ts +30 -0
  277. package/dist/server/http-server.d.ts.map +1 -0
  278. package/dist/server/http-server.js +561 -0
  279. package/dist/server/http-server.js.map +1 -0
  280. package/dist/server/message-normalizers.d.ts +11 -0
  281. package/dist/server/message-normalizers.d.ts.map +1 -0
  282. package/dist/server/message-normalizers.js +104 -0
  283. package/dist/server/message-normalizers.js.map +1 -0
  284. package/dist/server/pairing-offers.d.ts +78 -0
  285. package/dist/server/pairing-offers.d.ts.map +1 -0
  286. package/dist/server/pairing-offers.js +502 -0
  287. package/dist/server/pairing-offers.js.map +1 -0
  288. package/dist/server/rate-limiter.d.ts +21 -0
  289. package/dist/server/rate-limiter.d.ts.map +1 -0
  290. package/dist/server/rate-limiter.js +61 -0
  291. package/dist/server/rate-limiter.js.map +1 -0
  292. package/dist/server/ring-buffer.d.ts +34 -0
  293. package/dist/server/ring-buffer.d.ts.map +1 -0
  294. package/dist/server/ring-buffer.js +73 -0
  295. package/dist/server/ring-buffer.js.map +1 -0
  296. package/dist/server/security.d.ts +22 -0
  297. package/dist/server/security.d.ts.map +1 -0
  298. package/dist/server/security.js +123 -0
  299. package/dist/server/security.js.map +1 -0
  300. package/dist/server/ws-command-handlers.d.ts +25 -0
  301. package/dist/server/ws-command-handlers.d.ts.map +1 -0
  302. package/dist/server/ws-command-handlers.js +218 -0
  303. package/dist/server/ws-command-handlers.js.map +1 -0
  304. package/dist/server/ws-daemon-event-bridge.d.ts +22 -0
  305. package/dist/server/ws-daemon-event-bridge.d.ts.map +1 -0
  306. package/dist/server/ws-daemon-event-bridge.js +321 -0
  307. package/dist/server/ws-daemon-event-bridge.js.map +1 -0
  308. package/dist/server/ws-limits.d.ts +2 -0
  309. package/dist/server/ws-limits.d.ts.map +1 -0
  310. package/dist/server/ws-limits.js +12 -0
  311. package/dist/server/ws-limits.js.map +1 -0
  312. package/dist/server/ws-protocol.d.ts +248 -0
  313. package/dist/server/ws-protocol.d.ts.map +1 -0
  314. package/dist/server/ws-protocol.js +157 -0
  315. package/dist/server/ws-protocol.js.map +1 -0
  316. package/dist/server/ws-server.d.ts +26 -0
  317. package/dist/server/ws-server.d.ts.map +1 -0
  318. package/dist/server/ws-server.js +290 -0
  319. package/dist/server/ws-server.js.map +1 -0
  320. package/dist/startup-agents.d.ts +6 -0
  321. package/dist/startup-agents.d.ts.map +1 -0
  322. package/dist/startup-agents.js +97 -0
  323. package/dist/startup-agents.js.map +1 -0
  324. package/dist/startup-prereqs.d.ts +29 -0
  325. package/dist/startup-prereqs.d.ts.map +1 -0
  326. package/dist/startup-prereqs.js +209 -0
  327. package/dist/startup-prereqs.js.map +1 -0
  328. package/dist/startup-watchers.d.ts +7 -0
  329. package/dist/startup-watchers.d.ts.map +1 -0
  330. package/dist/startup-watchers.js +196 -0
  331. package/dist/startup-watchers.js.map +1 -0
  332. package/dist/startup.d.ts +20 -0
  333. package/dist/startup.d.ts.map +1 -0
  334. package/dist/startup.js +335 -0
  335. package/dist/startup.js.map +1 -0
  336. package/dist/tracking/git-tracker.d.ts +52 -0
  337. package/dist/tracking/git-tracker.d.ts.map +1 -0
  338. package/dist/tracking/git-tracker.js +277 -0
  339. package/dist/tracking/git-tracker.js.map +1 -0
  340. package/dist/tracking/noop-tracker.d.ts +27 -0
  341. package/dist/tracking/noop-tracker.d.ts.map +1 -0
  342. package/dist/tracking/noop-tracker.js +42 -0
  343. package/dist/tracking/noop-tracker.js.map +1 -0
  344. package/docs/configuration.md +75 -0
  345. package/docs/developer-workflows.md +107 -0
  346. package/docs/protocol-matrix.json +155 -0
  347. package/docs/releasing.md +65 -0
  348. package/docs/security.md +48 -0
  349. package/docs/testing.md +112 -0
  350. package/package.json +84 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Message normalizers — convert internal daemon types to wire-format update objects.
3
+ *
4
+ * These are pure functions that transform SessionMessage, Step, and PermissionRequest
5
+ * into the Record<string, unknown> payloads sent inside session-update messages.
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // SessionMessage → update object
9
+ // ---------------------------------------------------------------------------
10
+ export function messageToUpdate(msg) {
11
+ switch (msg.type) {
12
+ case 'agent_message':
13
+ return {
14
+ updateType: 'agent-message',
15
+ messageId: msg.messageId,
16
+ text: msg.text,
17
+ timestamp: msg.timestamp,
18
+ };
19
+ case 'agent_message_chunk':
20
+ return {
21
+ updateType: 'agent-message-chunk',
22
+ messageId: msg.messageId,
23
+ text: msg.text,
24
+ timestamp: msg.timestamp,
25
+ };
26
+ case 'agent_thought_chunk':
27
+ return {
28
+ updateType: 'agent-thought-chunk',
29
+ messageId: msg.messageId,
30
+ text: msg.text,
31
+ timestamp: msg.timestamp,
32
+ };
33
+ case 'user_message':
34
+ return {
35
+ updateType: 'user-message',
36
+ messageId: msg.messageId,
37
+ text: msg.text,
38
+ timestamp: msg.timestamp,
39
+ };
40
+ case 'tool_call':
41
+ return {
42
+ updateType: 'tool-call',
43
+ toolCallId: msg.toolCallId,
44
+ toolName: msg.toolName,
45
+ title: msg.title,
46
+ input: msg.input,
47
+ detail: msg.detail,
48
+ status: msg.status,
49
+ timestamp: msg.timestamp,
50
+ };
51
+ case 'tool_call_update':
52
+ return {
53
+ updateType: 'tool-call-update',
54
+ toolCallId: msg.toolCallId,
55
+ status: msg.status,
56
+ title: msg.title,
57
+ output: msg.output,
58
+ timestamp: msg.timestamp,
59
+ };
60
+ case 'token_usage':
61
+ return {
62
+ updateType: 'token-usage',
63
+ inputTokens: msg.inputTokens,
64
+ outputTokens: msg.outputTokens,
65
+ costUsd: msg.totalCostUsd,
66
+ timestamp: msg.timestamp,
67
+ };
68
+ case 'system_status':
69
+ return {
70
+ updateType: 'system-status',
71
+ status: msg.status,
72
+ timestamp: msg.timestamp,
73
+ };
74
+ default:
75
+ return { updateType: 'unknown', timestamp: Date.now() };
76
+ }
77
+ }
78
+ // ---------------------------------------------------------------------------
79
+ // Step → update object
80
+ // ---------------------------------------------------------------------------
81
+ export function stepToUpdate(step) {
82
+ return {
83
+ updateType: 'step-committed',
84
+ step: step.step,
85
+ sha: step.sha,
86
+ toolName: step.toolName,
87
+ description: step.description,
88
+ timestamp: step.timestamp,
89
+ };
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // PermissionRequest → update object
93
+ // ---------------------------------------------------------------------------
94
+ export function permissionToUpdate(request) {
95
+ return {
96
+ updateType: 'permission-request',
97
+ requestId: request.requestId,
98
+ toolName: request.toolName,
99
+ description: request.description,
100
+ input: request.input,
101
+ timestamp: Date.now(),
102
+ };
103
+ }
104
+ //# sourceMappingURL=message-normalizers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-normalizers.js","sourceRoot":"","sources":["../../src/server/message-normalizers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ export interface PairingOfferConnection {
2
+ host: string;
3
+ port: number;
4
+ listen: string;
5
+ socketPath?: string;
6
+ profile: 'local' | 'lan' | 'relay';
7
+ }
8
+ export interface PairingOfferPublicPayload extends PairingOfferConnection {
9
+ offerId: string;
10
+ createdAt: number;
11
+ expiresAt: number;
12
+ trustAnchor: string;
13
+ daemonDeviceId: string;
14
+ }
15
+ export interface PairingOfferIssuedPayload extends PairingOfferPublicPayload {
16
+ redeemSecret: string;
17
+ daemonPublicKey: string;
18
+ }
19
+ export interface PairingOfferRedeemedPayload {
20
+ offerId: string;
21
+ token: string;
22
+ trustAnchor: string;
23
+ daemonDeviceId: string;
24
+ daemonPublicKey: string;
25
+ peerId: string;
26
+ serverSignature: string;
27
+ connection: PairingOfferConnection;
28
+ expiresAt: number;
29
+ createdAt: number;
30
+ }
31
+ export interface PairingTrustAnchorPublic {
32
+ id: string;
33
+ createdAt: number;
34
+ fingerprint: string;
35
+ }
36
+ export interface PairingDaemonIdentityPublic {
37
+ deviceId: string;
38
+ createdAt: number;
39
+ fingerprint: string;
40
+ publicKey: string;
41
+ }
42
+ export interface PairingClientIdentity {
43
+ peerId: string;
44
+ publicKey: string;
45
+ privateKey: string;
46
+ }
47
+ export interface PairingRedeemProof {
48
+ peerId: string;
49
+ clientPublicKey: string;
50
+ clientProof: string;
51
+ }
52
+ export declare function getOrCreateTrustAnchor(): Promise<PairingTrustAnchorPublic>;
53
+ export declare function getOrCreateDaemonIdentity(): Promise<PairingDaemonIdentityPublic>;
54
+ export declare function createPairingClientIdentity(): PairingClientIdentity;
55
+ export declare function createPairingRedeemProof(input: {
56
+ offerId: string;
57
+ redeemSecret: string;
58
+ trustAnchor: string;
59
+ clientIdentity: PairingClientIdentity;
60
+ }): PairingRedeemProof;
61
+ export declare function readAuthToken(): Promise<string | null>;
62
+ export declare function rotateAuthToken(): Promise<{
63
+ token: string;
64
+ previousTokenExisted: boolean;
65
+ }>;
66
+ export declare function issuePairingOffer(input: {
67
+ connection: PairingOfferConnection;
68
+ ttlSeconds: number;
69
+ }): Promise<PairingOfferIssuedPayload>;
70
+ export declare function listPairingOffers(): Promise<Array<PairingOfferPublicPayload & {
71
+ revokedAt?: number;
72
+ redeemedAt?: number;
73
+ active: boolean;
74
+ expired: boolean;
75
+ }>>;
76
+ export declare function revokePairingOffer(offerId: string): Promise<boolean>;
77
+ export declare function redeemPairingOffer(offerId: string, redeemSecret: string, expectedTrustAnchor?: string, clientPublicKey?: string, clientProof?: string): Promise<PairingOfferRedeemedPayload | null>;
78
+ //# sourceMappingURL=pairing-offers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing-offers.d.ts","sourceRoot":"","sources":["../../src/server/pairing-offers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,502 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { configDir } from '../core/config.js';
5
+ const MAX_STORED_OFFERS = 200;
6
+ const MAX_FAILED_REDEEM_ATTEMPTS = 5;
7
+ function pairingStorePath() {
8
+ return path.join(configDir(), 'pairing-offers.json');
9
+ }
10
+ function pairingAuditPath() {
11
+ return path.join(configDir(), 'pairing-audit.jsonl');
12
+ }
13
+ function authTokenPath() {
14
+ return path.join(configDir(), 'auth-token');
15
+ }
16
+ function trustAnchorPath() {
17
+ return path.join(configDir(), 'pairing-trust-anchor.json');
18
+ }
19
+ function daemonIdentityPath() {
20
+ return path.join(configDir(), 'pairing-device-identity.json');
21
+ }
22
+ function peerBindingPath() {
23
+ return path.join(configDir(), 'pairing-peers.json');
24
+ }
25
+ async function readStore() {
26
+ try {
27
+ const raw = await fs.readFile(pairingStorePath(), 'utf-8');
28
+ const parsed = JSON.parse(raw);
29
+ if (!Array.isArray(parsed.offers)) {
30
+ return { version: 1, offers: [] };
31
+ }
32
+ return {
33
+ version: 1,
34
+ offers: parsed.offers.filter((item) => item && typeof item.offerId === 'string'),
35
+ };
36
+ }
37
+ catch {
38
+ return { version: 1, offers: [] };
39
+ }
40
+ }
41
+ async function writeStore(store) {
42
+ await fs.mkdir(configDir(), { recursive: true });
43
+ const compacted = compactOffers(store.offers);
44
+ await fs.writeFile(pairingStorePath(), JSON.stringify({ version: 1, offers: compacted }, null, 2) + '\n', 'utf-8');
45
+ }
46
+ function compactOffers(offers) {
47
+ const now = Date.now();
48
+ const fresh = offers.filter((offer) => {
49
+ if (!offer.expiresAt || offer.expiresAt <= now - 24 * 60 * 60 * 1000) {
50
+ return false;
51
+ }
52
+ return true;
53
+ });
54
+ if (fresh.length <= MAX_STORED_OFFERS)
55
+ return fresh;
56
+ return fresh.slice(fresh.length - MAX_STORED_OFFERS);
57
+ }
58
+ async function appendAudit(event) {
59
+ await fs.mkdir(configDir(), { recursive: true });
60
+ const line = JSON.stringify({ timestamp: Date.now(), ...event });
61
+ await fs.appendFile(pairingAuditPath(), `${line}\n`, 'utf-8');
62
+ }
63
+ function trustAnchorFingerprint(secret) {
64
+ const digest = crypto.createHash('sha256').update(secret).digest('hex');
65
+ return digest.match(/.{1,4}/g)?.join(':') ?? digest;
66
+ }
67
+ function keyFingerprint(publicKey) {
68
+ const digest = crypto.createHash('sha256').update(publicKey).digest('hex');
69
+ return digest.match(/.{1,4}/g)?.join(':') ?? digest;
70
+ }
71
+ async function readTrustAnchorRecord() {
72
+ try {
73
+ const raw = await fs.readFile(trustAnchorPath(), 'utf-8');
74
+ const parsed = JSON.parse(raw);
75
+ if (parsed &&
76
+ parsed.version === 1 &&
77
+ typeof parsed.id === 'string' &&
78
+ typeof parsed.createdAt === 'number' &&
79
+ typeof parsed.secret === 'string' &&
80
+ parsed.secret.length > 0) {
81
+ return parsed;
82
+ }
83
+ return null;
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ async function writeTrustAnchorRecord(record) {
90
+ await fs.mkdir(configDir(), { recursive: true });
91
+ await fs.writeFile(trustAnchorPath(), JSON.stringify(record, null, 2) + '\n', {
92
+ mode: 0o600,
93
+ });
94
+ }
95
+ export async function getOrCreateTrustAnchor() {
96
+ const existing = await readTrustAnchorRecord();
97
+ if (existing) {
98
+ return {
99
+ id: existing.id,
100
+ createdAt: existing.createdAt,
101
+ fingerprint: trustAnchorFingerprint(existing.secret),
102
+ };
103
+ }
104
+ const created = {
105
+ version: 1,
106
+ id: crypto.randomUUID(),
107
+ createdAt: Date.now(),
108
+ secret: crypto.randomBytes(32).toString('hex'),
109
+ };
110
+ await writeTrustAnchorRecord(created);
111
+ await appendAudit({
112
+ event: 'pair_trust_anchor_created',
113
+ trustAnchorId: created.id,
114
+ trustAnchor: trustAnchorFingerprint(created.secret),
115
+ });
116
+ return {
117
+ id: created.id,
118
+ createdAt: created.createdAt,
119
+ fingerprint: trustAnchorFingerprint(created.secret),
120
+ };
121
+ }
122
+ async function readDaemonIdentity() {
123
+ try {
124
+ const raw = await fs.readFile(daemonIdentityPath(), 'utf-8');
125
+ const parsed = JSON.parse(raw);
126
+ if (parsed &&
127
+ parsed.version === 1 &&
128
+ typeof parsed.deviceId === 'string' &&
129
+ typeof parsed.createdAt === 'number' &&
130
+ typeof parsed.publicKey === 'string' &&
131
+ typeof parsed.privateKey === 'string') {
132
+ return parsed;
133
+ }
134
+ return null;
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ }
140
+ async function writeDaemonIdentity(identity) {
141
+ await fs.mkdir(configDir(), { recursive: true });
142
+ await fs.writeFile(daemonIdentityPath(), JSON.stringify(identity, null, 2) + '\n', {
143
+ mode: 0o600,
144
+ });
145
+ }
146
+ export async function getOrCreateDaemonIdentity() {
147
+ const existing = await readDaemonIdentity();
148
+ if (existing) {
149
+ return {
150
+ deviceId: existing.deviceId,
151
+ createdAt: existing.createdAt,
152
+ fingerprint: keyFingerprint(existing.publicKey),
153
+ publicKey: existing.publicKey,
154
+ };
155
+ }
156
+ const keypair = crypto.generateKeyPairSync('ed25519');
157
+ const publicKey = keypair.publicKey.export({ type: 'spki', format: 'pem' }).toString().trim();
158
+ const privateKey = keypair.privateKey.export({ type: 'pkcs8', format: 'pem' }).toString().trim();
159
+ const created = {
160
+ version: 1,
161
+ deviceId: crypto.randomUUID(),
162
+ createdAt: Date.now(),
163
+ publicKey,
164
+ privateKey,
165
+ };
166
+ await writeDaemonIdentity(created);
167
+ await appendAudit({
168
+ event: 'pair_device_identity_created',
169
+ deviceId: created.deviceId,
170
+ fingerprint: keyFingerprint(created.publicKey),
171
+ });
172
+ return {
173
+ deviceId: created.deviceId,
174
+ createdAt: created.createdAt,
175
+ fingerprint: keyFingerprint(created.publicKey),
176
+ publicKey: created.publicKey,
177
+ };
178
+ }
179
+ function canonicalRedeemPayload(input) {
180
+ return [
181
+ 'viewport-pair-redeem-v1',
182
+ input.offerId,
183
+ input.redeemSecret,
184
+ input.trustAnchor,
185
+ input.clientPublicKey,
186
+ ].join('\n');
187
+ }
188
+ function peerIdFromPublicKey(publicKey) {
189
+ return crypto.createHash('sha256').update(publicKey).digest('hex');
190
+ }
191
+ async function readPeerBindings() {
192
+ try {
193
+ const raw = await fs.readFile(peerBindingPath(), 'utf-8');
194
+ const parsed = JSON.parse(raw);
195
+ if (!parsed || parsed.version !== 1 || !Array.isArray(parsed.peers)) {
196
+ return { version: 1, peers: [] };
197
+ }
198
+ return {
199
+ version: 1,
200
+ peers: parsed.peers.filter((item) => item &&
201
+ typeof item.peerId === 'string' &&
202
+ typeof item.publicKey === 'string' &&
203
+ typeof item.firstPairedAt === 'number'),
204
+ };
205
+ }
206
+ catch {
207
+ return { version: 1, peers: [] };
208
+ }
209
+ }
210
+ async function writePeerBindings(store) {
211
+ await fs.mkdir(configDir(), { recursive: true });
212
+ await fs.writeFile(peerBindingPath(), JSON.stringify(store, null, 2) + '\n', {
213
+ mode: 0o600,
214
+ });
215
+ }
216
+ async function upsertPeerBinding(input) {
217
+ const store = await readPeerBindings();
218
+ const now = Date.now();
219
+ const existing = store.peers.find((peer) => peer.peerId === input.peerId);
220
+ if (existing) {
221
+ existing.publicKey = input.publicKey;
222
+ existing.lastPairedAt = now;
223
+ existing.lastOfferId = input.offerId;
224
+ existing.trustAnchor = input.trustAnchor;
225
+ }
226
+ else {
227
+ store.peers.push({
228
+ peerId: input.peerId,
229
+ publicKey: input.publicKey,
230
+ firstPairedAt: now,
231
+ lastPairedAt: now,
232
+ lastOfferId: input.offerId,
233
+ trustAnchor: input.trustAnchor,
234
+ });
235
+ }
236
+ await writePeerBindings(store);
237
+ }
238
+ export function createPairingClientIdentity() {
239
+ const keypair = crypto.generateKeyPairSync('ed25519');
240
+ const publicKey = keypair.publicKey.export({ type: 'spki', format: 'pem' }).toString().trim();
241
+ const privateKey = keypair.privateKey.export({ type: 'pkcs8', format: 'pem' }).toString().trim();
242
+ return {
243
+ peerId: peerIdFromPublicKey(publicKey),
244
+ publicKey,
245
+ privateKey,
246
+ };
247
+ }
248
+ export function createPairingRedeemProof(input) {
249
+ const normalizedClientPublicKey = input.clientIdentity.publicKey.trim();
250
+ const normalizedClientPrivateKey = input.clientIdentity.privateKey.trim();
251
+ const payload = canonicalRedeemPayload({
252
+ offerId: input.offerId,
253
+ redeemSecret: input.redeemSecret,
254
+ trustAnchor: input.trustAnchor,
255
+ clientPublicKey: normalizedClientPublicKey,
256
+ });
257
+ const signature = crypto.sign(null, Buffer.from(payload, 'utf-8'), crypto.createPrivateKey(normalizedClientPrivateKey));
258
+ return {
259
+ peerId: peerIdFromPublicKey(normalizedClientPublicKey),
260
+ clientPublicKey: normalizedClientPublicKey,
261
+ clientProof: signature.toString('base64url'),
262
+ };
263
+ }
264
+ export async function readAuthToken() {
265
+ try {
266
+ const token = (await fs.readFile(authTokenPath(), 'utf-8')).trim();
267
+ return token || null;
268
+ }
269
+ catch {
270
+ return null;
271
+ }
272
+ }
273
+ export async function rotateAuthToken() {
274
+ const previous = await readAuthToken();
275
+ const token = crypto.randomBytes(32).toString('hex');
276
+ await fs.mkdir(configDir(), { recursive: true });
277
+ await fs.writeFile(authTokenPath(), `${token}\n`, { mode: 0o600 });
278
+ await appendAudit({ event: 'auth_token_rotated' });
279
+ return { token, previousTokenExisted: previous !== null };
280
+ }
281
+ export async function issuePairingOffer(input) {
282
+ const ttlSeconds = Math.min(3600, Math.max(30, Math.floor(input.ttlSeconds)));
283
+ const createdAt = Date.now();
284
+ const expiresAt = createdAt + ttlSeconds * 1000;
285
+ const offerId = crypto.randomUUID();
286
+ const redeemSecret = crypto.randomBytes(16).toString('hex');
287
+ const token = await readAuthToken();
288
+ if (!token) {
289
+ throw new Error('No auth token available for pairing offer');
290
+ }
291
+ const trustAnchor = await getOrCreateTrustAnchor();
292
+ const daemonIdentity = await getOrCreateDaemonIdentity();
293
+ const store = await readStore();
294
+ store.offers.push({
295
+ offerId,
296
+ createdAt,
297
+ expiresAt,
298
+ redeemSecretHash: hashSecret(redeemSecret),
299
+ token,
300
+ trustAnchor: trustAnchor.fingerprint,
301
+ daemonDeviceId: daemonIdentity.deviceId,
302
+ daemonPublicKey: daemonIdentity.publicKey,
303
+ connection: input.connection,
304
+ });
305
+ await writeStore(store);
306
+ await appendAudit({
307
+ event: 'pair_offer_issued',
308
+ offerId,
309
+ createdAt,
310
+ expiresAt,
311
+ profile: input.connection.profile,
312
+ listen: input.connection.listen,
313
+ trustAnchor: trustAnchor.fingerprint,
314
+ daemonDeviceId: daemonIdentity.deviceId,
315
+ });
316
+ return {
317
+ offerId,
318
+ createdAt,
319
+ expiresAt,
320
+ redeemSecret,
321
+ trustAnchor: trustAnchor.fingerprint,
322
+ daemonDeviceId: daemonIdentity.deviceId,
323
+ daemonPublicKey: daemonIdentity.publicKey,
324
+ ...input.connection,
325
+ };
326
+ }
327
+ export async function listPairingOffers() {
328
+ const store = await readStore();
329
+ const now = Date.now();
330
+ return store.offers
331
+ .map((offer) => {
332
+ const expired = offer.expiresAt <= now;
333
+ const active = !expired && !offer.revokedAt && !offer.redeemedAt;
334
+ return {
335
+ offerId: offer.offerId,
336
+ createdAt: offer.createdAt,
337
+ expiresAt: offer.expiresAt,
338
+ trustAnchor: offer.trustAnchor,
339
+ daemonDeviceId: offer.daemonDeviceId,
340
+ host: offer.connection.host,
341
+ port: offer.connection.port,
342
+ listen: offer.connection.listen,
343
+ socketPath: offer.connection.socketPath,
344
+ profile: offer.connection.profile,
345
+ revokedAt: offer.revokedAt,
346
+ redeemedAt: offer.redeemedAt,
347
+ active,
348
+ expired,
349
+ };
350
+ })
351
+ .sort((a, b) => b.createdAt - a.createdAt);
352
+ }
353
+ export async function revokePairingOffer(offerId) {
354
+ const store = await readStore();
355
+ const offer = store.offers.find((item) => item.offerId === offerId);
356
+ if (!offer)
357
+ return false;
358
+ if (!offer.revokedAt) {
359
+ offer.revokedAt = Date.now();
360
+ await writeStore(store);
361
+ await appendAudit({ event: 'pair_offer_revoked', offerId: offer.offerId });
362
+ }
363
+ return true;
364
+ }
365
+ export async function redeemPairingOffer(offerId, redeemSecret, expectedTrustAnchor, clientPublicKey, clientProof) {
366
+ if (!redeemSecret || redeemSecret.trim().length === 0) {
367
+ return null;
368
+ }
369
+ const store = await readStore();
370
+ const offer = store.offers.find((item) => item.offerId === offerId);
371
+ if (!offer)
372
+ return null;
373
+ const now = Date.now();
374
+ const expired = offer.expiresAt <= now;
375
+ if (expired || offer.revokedAt || offer.redeemedAt || offer.lockedAt) {
376
+ return null;
377
+ }
378
+ if (!clientPublicKey || !clientProof) {
379
+ await appendAudit({
380
+ event: 'pair_offer_redeem_failed',
381
+ offerId: offer.offerId,
382
+ reason: 'missing_client_identity_proof',
383
+ });
384
+ return null;
385
+ }
386
+ if (expectedTrustAnchor && offer.trustAnchor !== expectedTrustAnchor) {
387
+ await appendAudit({
388
+ event: 'pair_offer_redeem_failed',
389
+ offerId: offer.offerId,
390
+ reason: 'trust_anchor_mismatch',
391
+ expectedTrustAnchor,
392
+ offeredTrustAnchor: offer.trustAnchor,
393
+ });
394
+ return null;
395
+ }
396
+ try {
397
+ const payload = canonicalRedeemPayload({
398
+ offerId: offer.offerId,
399
+ redeemSecret,
400
+ trustAnchor: offer.trustAnchor,
401
+ clientPublicKey,
402
+ });
403
+ const verified = crypto.verify(null, Buffer.from(payload, 'utf-8'), crypto.createPublicKey(clientPublicKey), Buffer.from(clientProof, 'base64url'));
404
+ if (!verified) {
405
+ await appendAudit({
406
+ event: 'pair_offer_redeem_failed',
407
+ offerId: offer.offerId,
408
+ reason: 'client_proof_invalid',
409
+ });
410
+ return null;
411
+ }
412
+ }
413
+ catch {
414
+ await appendAudit({
415
+ event: 'pair_offer_redeem_failed',
416
+ offerId: offer.offerId,
417
+ reason: 'client_proof_invalid',
418
+ });
419
+ return null;
420
+ }
421
+ if (typeof offer.redeemSecretHash !== 'string' || offer.redeemSecretHash.length === 0) {
422
+ offer.lockedAt = now;
423
+ await writeStore(store);
424
+ await appendAudit({
425
+ event: 'pair_offer_redeem_failed',
426
+ offerId: offer.offerId,
427
+ reason: 'missing_redeem_secret_hash',
428
+ });
429
+ return null;
430
+ }
431
+ const proofValid = secureSecretCompare(offer.redeemSecretHash, hashSecret(redeemSecret));
432
+ if (!proofValid) {
433
+ offer.failedRedeemAttempts = (offer.failedRedeemAttempts ?? 0) + 1;
434
+ if (offer.failedRedeemAttempts >= MAX_FAILED_REDEEM_ATTEMPTS) {
435
+ offer.lockedAt = now;
436
+ }
437
+ await writeStore(store);
438
+ await appendAudit({
439
+ event: 'pair_offer_redeem_failed',
440
+ offerId: offer.offerId,
441
+ attempts: offer.failedRedeemAttempts,
442
+ locked: !!offer.lockedAt,
443
+ });
444
+ return null;
445
+ }
446
+ offer.redeemedAt = now;
447
+ await writeStore(store);
448
+ const peerId = peerIdFromPublicKey(clientPublicKey);
449
+ await upsertPeerBinding({
450
+ peerId,
451
+ publicKey: clientPublicKey,
452
+ offerId: offer.offerId,
453
+ trustAnchor: offer.trustAnchor,
454
+ });
455
+ const daemonIdentity = await readDaemonIdentity();
456
+ const daemonPrivateKey = daemonIdentity
457
+ ? crypto.createPrivateKey(daemonIdentity.privateKey)
458
+ : undefined;
459
+ const redeemEnvelope = [
460
+ 'viewport-pair-redeem-response-v1',
461
+ offer.offerId,
462
+ peerId,
463
+ offer.trustAnchor,
464
+ String(offer.expiresAt),
465
+ ].join('\n');
466
+ const serverSignature = daemonPrivateKey
467
+ ? crypto
468
+ .sign(null, Buffer.from(redeemEnvelope, 'utf-8'), daemonPrivateKey)
469
+ .toString('base64url')
470
+ : '';
471
+ await appendAudit({
472
+ event: 'pair_offer_redeemed',
473
+ offerId: offer.offerId,
474
+ peerId,
475
+ daemonDeviceId: offer.daemonDeviceId,
476
+ });
477
+ return {
478
+ offerId: offer.offerId,
479
+ token: offer.token,
480
+ trustAnchor: offer.trustAnchor,
481
+ daemonDeviceId: offer.daemonDeviceId,
482
+ daemonPublicKey: offer.daemonPublicKey,
483
+ peerId,
484
+ serverSignature,
485
+ connection: offer.connection,
486
+ expiresAt: offer.expiresAt,
487
+ createdAt: offer.createdAt,
488
+ };
489
+ }
490
+ function hashSecret(secret) {
491
+ return crypto.createHash('sha256').update(secret).digest('hex');
492
+ }
493
+ function secureSecretCompare(a, b) {
494
+ const left = Buffer.from(a, 'utf-8');
495
+ const right = Buffer.from(b, 'utf-8');
496
+ if (left.length !== right.length) {
497
+ crypto.timingSafeEqual(left, left);
498
+ return false;
499
+ }
500
+ return crypto.timingSafeEqual(left, right);
501
+ }
502
+ //# sourceMappingURL=pairing-offers.js.map