@portel/photon 1.4.0 → 1.5.1

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 (379) hide show
  1. package/README.md +287 -1160
  2. package/dist/auto-ui/beam.d.ts +9 -0
  3. package/dist/auto-ui/beam.d.ts.map +1 -0
  4. package/dist/auto-ui/beam.js +2381 -0
  5. package/dist/auto-ui/beam.js.map +1 -0
  6. package/dist/auto-ui/components/card.d.ts +13 -0
  7. package/dist/auto-ui/components/card.d.ts.map +1 -0
  8. package/dist/auto-ui/components/card.js +64 -0
  9. package/dist/auto-ui/components/card.js.map +1 -0
  10. package/dist/auto-ui/components/form.d.ts +15 -0
  11. package/dist/auto-ui/components/form.d.ts.map +1 -0
  12. package/dist/auto-ui/components/form.js +72 -0
  13. package/dist/auto-ui/components/form.js.map +1 -0
  14. package/dist/auto-ui/components/list.d.ts +13 -0
  15. package/dist/auto-ui/components/list.d.ts.map +1 -0
  16. package/dist/auto-ui/components/list.js +58 -0
  17. package/dist/auto-ui/components/list.js.map +1 -0
  18. package/dist/auto-ui/components/progress.d.ts +18 -0
  19. package/dist/auto-ui/components/progress.d.ts.map +1 -0
  20. package/dist/auto-ui/components/progress.js +125 -0
  21. package/dist/auto-ui/components/progress.js.map +1 -0
  22. package/dist/auto-ui/components/table.d.ts +13 -0
  23. package/dist/auto-ui/components/table.d.ts.map +1 -0
  24. package/dist/auto-ui/components/table.js +82 -0
  25. package/dist/auto-ui/components/table.js.map +1 -0
  26. package/dist/auto-ui/components/tree.d.ts +13 -0
  27. package/dist/auto-ui/components/tree.d.ts.map +1 -0
  28. package/dist/auto-ui/components/tree.js +61 -0
  29. package/dist/auto-ui/components/tree.js.map +1 -0
  30. package/dist/auto-ui/daemon-tools.d.ts +45 -0
  31. package/dist/auto-ui/daemon-tools.d.ts.map +1 -0
  32. package/dist/auto-ui/daemon-tools.js +580 -0
  33. package/dist/auto-ui/daemon-tools.js.map +1 -0
  34. package/dist/auto-ui/design-system/index.d.ts +21 -0
  35. package/dist/auto-ui/design-system/index.d.ts.map +1 -0
  36. package/dist/auto-ui/design-system/index.js +27 -0
  37. package/dist/auto-ui/design-system/index.js.map +1 -0
  38. package/dist/auto-ui/design-system/tokens.d.ts +9 -0
  39. package/dist/auto-ui/design-system/tokens.d.ts.map +1 -0
  40. package/dist/auto-ui/design-system/tokens.js +27 -0
  41. package/dist/auto-ui/design-system/tokens.js.map +1 -0
  42. package/dist/auto-ui/design-system/transaction-ui.d.ts +70 -0
  43. package/dist/auto-ui/design-system/transaction-ui.d.ts.map +1 -0
  44. package/dist/auto-ui/design-system/transaction-ui.js +982 -0
  45. package/dist/auto-ui/design-system/transaction-ui.js.map +1 -0
  46. package/dist/auto-ui/frontend/index.html +84 -0
  47. package/dist/auto-ui/index.d.ts +21 -0
  48. package/dist/auto-ui/index.d.ts.map +1 -0
  49. package/dist/auto-ui/index.js +25 -0
  50. package/dist/auto-ui/index.js.map +1 -0
  51. package/dist/auto-ui/openapi-generator.d.ts +71 -0
  52. package/dist/auto-ui/openapi-generator.d.ts.map +1 -0
  53. package/dist/auto-ui/openapi-generator.js +223 -0
  54. package/dist/auto-ui/openapi-generator.js.map +1 -0
  55. package/dist/auto-ui/photon-bridge.d.ts +159 -0
  56. package/dist/auto-ui/photon-bridge.d.ts.map +1 -0
  57. package/dist/auto-ui/photon-bridge.js +262 -0
  58. package/dist/auto-ui/photon-bridge.js.map +1 -0
  59. package/dist/auto-ui/photon-host.d.ts +113 -0
  60. package/dist/auto-ui/photon-host.d.ts.map +1 -0
  61. package/dist/auto-ui/photon-host.js +284 -0
  62. package/dist/auto-ui/photon-host.js.map +1 -0
  63. package/dist/auto-ui/platform-compat.d.ts +71 -0
  64. package/dist/auto-ui/platform-compat.d.ts.map +1 -0
  65. package/dist/auto-ui/platform-compat.js +574 -0
  66. package/dist/auto-ui/platform-compat.js.map +1 -0
  67. package/dist/auto-ui/playground-html.d.ts +15 -0
  68. package/dist/auto-ui/playground-html.d.ts.map +1 -0
  69. package/dist/auto-ui/playground-html.js +1113 -0
  70. package/dist/auto-ui/playground-html.js.map +1 -0
  71. package/dist/auto-ui/playground-server.d.ts +7 -0
  72. package/dist/auto-ui/playground-server.d.ts.map +1 -0
  73. package/dist/auto-ui/playground-server.js +840 -0
  74. package/dist/auto-ui/playground-server.js.map +1 -0
  75. package/dist/auto-ui/registry.d.ts +13 -0
  76. package/dist/auto-ui/registry.d.ts.map +1 -0
  77. package/dist/auto-ui/registry.js +62 -0
  78. package/dist/auto-ui/registry.js.map +1 -0
  79. package/dist/auto-ui/renderer.d.ts +14 -0
  80. package/dist/auto-ui/renderer.d.ts.map +1 -0
  81. package/dist/auto-ui/renderer.js +88 -0
  82. package/dist/auto-ui/renderer.js.map +1 -0
  83. package/dist/auto-ui/rendering/components.d.ts +29 -0
  84. package/dist/auto-ui/rendering/components.d.ts.map +1 -0
  85. package/dist/auto-ui/rendering/components.js +773 -0
  86. package/dist/auto-ui/rendering/components.js.map +1 -0
  87. package/dist/auto-ui/rendering/field-analyzer.d.ts +48 -0
  88. package/dist/auto-ui/rendering/field-analyzer.d.ts.map +1 -0
  89. package/dist/auto-ui/rendering/field-analyzer.js +270 -0
  90. package/dist/auto-ui/rendering/field-analyzer.js.map +1 -0
  91. package/dist/auto-ui/rendering/field-renderers.d.ts +64 -0
  92. package/dist/auto-ui/rendering/field-renderers.d.ts.map +1 -0
  93. package/dist/auto-ui/rendering/field-renderers.js +317 -0
  94. package/dist/auto-ui/rendering/field-renderers.js.map +1 -0
  95. package/dist/auto-ui/rendering/index.d.ts +28 -0
  96. package/dist/auto-ui/rendering/index.d.ts.map +1 -0
  97. package/dist/auto-ui/rendering/index.js +60 -0
  98. package/dist/auto-ui/rendering/index.js.map +1 -0
  99. package/dist/auto-ui/rendering/layout-selector.d.ts +48 -0
  100. package/dist/auto-ui/rendering/layout-selector.d.ts.map +1 -0
  101. package/dist/auto-ui/rendering/layout-selector.js +352 -0
  102. package/dist/auto-ui/rendering/layout-selector.js.map +1 -0
  103. package/dist/auto-ui/rendering/template-engine.d.ts +41 -0
  104. package/dist/auto-ui/rendering/template-engine.d.ts.map +1 -0
  105. package/dist/auto-ui/rendering/template-engine.js +238 -0
  106. package/dist/auto-ui/rendering/template-engine.js.map +1 -0
  107. package/dist/auto-ui/streamable-http-transport.d.ts +79 -0
  108. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -0
  109. package/dist/auto-ui/streamable-http-transport.js +1314 -0
  110. package/dist/auto-ui/streamable-http-transport.js.map +1 -0
  111. package/dist/auto-ui/types.d.ts +310 -0
  112. package/dist/auto-ui/types.d.ts.map +1 -0
  113. package/dist/auto-ui/types.js +71 -0
  114. package/dist/auto-ui/types.js.map +1 -0
  115. package/dist/beam.bundle.js +13506 -0
  116. package/dist/beam.bundle.js.map +7 -0
  117. package/dist/claude-code-plugin.d.ts.map +1 -1
  118. package/dist/claude-code-plugin.js +30 -30
  119. package/dist/claude-code-plugin.js.map +1 -1
  120. package/dist/cli/commands/info.d.ts +11 -0
  121. package/dist/cli/commands/info.d.ts.map +1 -0
  122. package/dist/cli/commands/info.js +313 -0
  123. package/dist/cli/commands/info.js.map +1 -0
  124. package/dist/cli/commands/marketplace.d.ts +11 -0
  125. package/dist/cli/commands/marketplace.d.ts.map +1 -0
  126. package/dist/cli/commands/marketplace.js +198 -0
  127. package/dist/cli/commands/marketplace.js.map +1 -0
  128. package/dist/cli/commands/package-app.d.ts +9 -0
  129. package/dist/cli/commands/package-app.d.ts.map +1 -0
  130. package/dist/cli/commands/package-app.js +191 -0
  131. package/dist/cli/commands/package-app.js.map +1 -0
  132. package/dist/cli/commands/package.d.ts +11 -0
  133. package/dist/cli/commands/package.d.ts.map +1 -0
  134. package/dist/cli/commands/package.js +573 -0
  135. package/dist/cli/commands/package.js.map +1 -0
  136. package/dist/cli-alias.d.ts.map +1 -1
  137. package/dist/cli-alias.js +30 -28
  138. package/dist/cli-alias.js.map +1 -1
  139. package/dist/cli-formatter.d.ts +8 -24
  140. package/dist/cli-formatter.d.ts.map +1 -1
  141. package/dist/cli-formatter.js +8 -325
  142. package/dist/cli-formatter.js.map +1 -1
  143. package/dist/cli.d.ts +15 -1
  144. package/dist/cli.d.ts.map +1 -1
  145. package/dist/cli.js +1157 -1132
  146. package/dist/cli.js.map +1 -1
  147. package/dist/daemon/client.d.ts +81 -0
  148. package/dist/daemon/client.d.ts.map +1 -1
  149. package/dist/daemon/client.js +583 -13
  150. package/dist/daemon/client.js.map +1 -1
  151. package/dist/daemon/manager.d.ts +46 -12
  152. package/dist/daemon/manager.d.ts.map +1 -1
  153. package/dist/daemon/manager.js +102 -61
  154. package/dist/daemon/manager.js.map +1 -1
  155. package/dist/daemon/protocol.d.ts +74 -6
  156. package/dist/daemon/protocol.d.ts.map +1 -1
  157. package/dist/daemon/protocol.js +76 -1
  158. package/dist/daemon/protocol.js.map +1 -1
  159. package/dist/daemon/server.d.ts +6 -6
  160. package/dist/daemon/server.js +778 -117
  161. package/dist/daemon/server.js.map +1 -1
  162. package/dist/daemon/session-manager.d.ts +8 -1
  163. package/dist/daemon/session-manager.d.ts.map +1 -1
  164. package/dist/daemon/session-manager.js +32 -9
  165. package/dist/daemon/session-manager.js.map +1 -1
  166. package/dist/deploy/cloudflare.d.ts +12 -0
  167. package/dist/deploy/cloudflare.d.ts.map +1 -0
  168. package/dist/deploy/cloudflare.js +216 -0
  169. package/dist/deploy/cloudflare.js.map +1 -0
  170. package/dist/index.d.ts +1 -0
  171. package/dist/index.d.ts.map +1 -1
  172. package/dist/index.js +3 -0
  173. package/dist/index.js.map +1 -1
  174. package/dist/loader.d.ts +172 -15
  175. package/dist/loader.d.ts.map +1 -1
  176. package/dist/loader.js +1132 -267
  177. package/dist/loader.js.map +1 -1
  178. package/dist/markdown-utils.d.ts +8 -0
  179. package/dist/markdown-utils.d.ts.map +1 -0
  180. package/dist/markdown-utils.js +63 -0
  181. package/dist/markdown-utils.js.map +1 -0
  182. package/dist/marketplace-manager.d.ts +10 -0
  183. package/dist/marketplace-manager.d.ts.map +1 -1
  184. package/dist/marketplace-manager.js +112 -28
  185. package/dist/marketplace-manager.js.map +1 -1
  186. package/dist/mcp-client.d.ts +9 -0
  187. package/dist/mcp-client.d.ts.map +1 -0
  188. package/dist/mcp-client.js +11 -0
  189. package/dist/mcp-client.js.map +1 -0
  190. package/dist/mcp-elicitation.d.ts +32 -0
  191. package/dist/mcp-elicitation.d.ts.map +1 -0
  192. package/dist/mcp-elicitation.js +26 -0
  193. package/dist/mcp-elicitation.js.map +1 -0
  194. package/dist/path-resolver.d.ts +9 -12
  195. package/dist/path-resolver.d.ts.map +1 -1
  196. package/dist/path-resolver.js +13 -43
  197. package/dist/path-resolver.js.map +1 -1
  198. package/dist/photon-cli-runner.d.ts.map +1 -1
  199. package/dist/photon-cli-runner.js +216 -73
  200. package/dist/photon-cli-runner.js.map +1 -1
  201. package/dist/photon-doc-extractor.d.ts +88 -0
  202. package/dist/photon-doc-extractor.d.ts.map +1 -1
  203. package/dist/photon-doc-extractor.js +536 -27
  204. package/dist/photon-doc-extractor.js.map +1 -1
  205. package/dist/photons/maker.photon.d.ts +182 -0
  206. package/dist/photons/maker.photon.d.ts.map +1 -0
  207. package/dist/photons/maker.photon.js +504 -0
  208. package/dist/photons/maker.photon.js.map +1 -0
  209. package/dist/photons/maker.photon.ts +626 -0
  210. package/dist/photons/marketplace.photon.d.ts +110 -0
  211. package/dist/photons/marketplace.photon.d.ts.map +1 -0
  212. package/dist/photons/marketplace.photon.js +260 -0
  213. package/dist/photons/marketplace.photon.js.map +1 -0
  214. package/dist/photons/marketplace.photon.ts +378 -0
  215. package/dist/photons/tunnel.photon.d.ts +80 -0
  216. package/dist/photons/tunnel.photon.d.ts.map +1 -0
  217. package/dist/photons/tunnel.photon.js +269 -0
  218. package/dist/photons/tunnel.photon.js.map +1 -0
  219. package/dist/photons/tunnel.photon.ts +345 -0
  220. package/dist/security-scanner.d.ts.map +1 -1
  221. package/dist/security-scanner.js +18 -15
  222. package/dist/security-scanner.js.map +1 -1
  223. package/dist/serv/auth/jwt.d.ts +89 -0
  224. package/dist/serv/auth/jwt.d.ts.map +1 -0
  225. package/dist/serv/auth/jwt.js +239 -0
  226. package/dist/serv/auth/jwt.js.map +1 -0
  227. package/dist/serv/auth/oauth.d.ts +117 -0
  228. package/dist/serv/auth/oauth.d.ts.map +1 -0
  229. package/dist/serv/auth/oauth.js +395 -0
  230. package/dist/serv/auth/oauth.js.map +1 -0
  231. package/dist/serv/auth/well-known.d.ts +60 -0
  232. package/dist/serv/auth/well-known.d.ts.map +1 -0
  233. package/dist/serv/auth/well-known.js +154 -0
  234. package/dist/serv/auth/well-known.js.map +1 -0
  235. package/dist/serv/db/d1-client.d.ts +65 -0
  236. package/dist/serv/db/d1-client.d.ts.map +1 -0
  237. package/dist/serv/db/d1-client.js +137 -0
  238. package/dist/serv/db/d1-client.js.map +1 -0
  239. package/dist/serv/db/d1-stores.d.ts +62 -0
  240. package/dist/serv/db/d1-stores.d.ts.map +1 -0
  241. package/dist/serv/db/d1-stores.js +307 -0
  242. package/dist/serv/db/d1-stores.js.map +1 -0
  243. package/dist/serv/index.d.ts +114 -0
  244. package/dist/serv/index.d.ts.map +1 -0
  245. package/dist/serv/index.js +172 -0
  246. package/dist/serv/index.js.map +1 -0
  247. package/dist/serv/local.d.ts +118 -0
  248. package/dist/serv/local.d.ts.map +1 -0
  249. package/dist/serv/local.js +392 -0
  250. package/dist/serv/local.js.map +1 -0
  251. package/dist/serv/middleware/auth.d.ts +66 -0
  252. package/dist/serv/middleware/auth.d.ts.map +1 -0
  253. package/dist/serv/middleware/auth.js +178 -0
  254. package/dist/serv/middleware/auth.js.map +1 -0
  255. package/dist/serv/middleware/tenant.d.ts +94 -0
  256. package/dist/serv/middleware/tenant.d.ts.map +1 -0
  257. package/dist/serv/middleware/tenant.js +152 -0
  258. package/dist/serv/middleware/tenant.js.map +1 -0
  259. package/dist/serv/runtime/executor.d.ts +76 -0
  260. package/dist/serv/runtime/executor.d.ts.map +1 -0
  261. package/dist/serv/runtime/executor.js +105 -0
  262. package/dist/serv/runtime/executor.js.map +1 -0
  263. package/dist/serv/runtime/index.d.ts +8 -0
  264. package/dist/serv/runtime/index.d.ts.map +1 -0
  265. package/dist/serv/runtime/index.js +10 -0
  266. package/dist/serv/runtime/index.js.map +1 -0
  267. package/dist/serv/runtime/oauth-context.d.ts +121 -0
  268. package/dist/serv/runtime/oauth-context.d.ts.map +1 -0
  269. package/dist/serv/runtime/oauth-context.js +153 -0
  270. package/dist/serv/runtime/oauth-context.js.map +1 -0
  271. package/dist/serv/session/kv-store.d.ts +54 -0
  272. package/dist/serv/session/kv-store.d.ts.map +1 -0
  273. package/dist/serv/session/kv-store.js +149 -0
  274. package/dist/serv/session/kv-store.js.map +1 -0
  275. package/dist/serv/session/store.d.ts +113 -0
  276. package/dist/serv/session/store.d.ts.map +1 -0
  277. package/dist/serv/session/store.js +284 -0
  278. package/dist/serv/session/store.js.map +1 -0
  279. package/dist/serv/types/index.d.ts +147 -0
  280. package/dist/serv/types/index.d.ts.map +1 -0
  281. package/dist/serv/types/index.js +8 -0
  282. package/dist/serv/types/index.js.map +1 -0
  283. package/dist/serv/vault/token-vault.d.ts +102 -0
  284. package/dist/serv/vault/token-vault.d.ts.map +1 -0
  285. package/dist/serv/vault/token-vault.js +177 -0
  286. package/dist/serv/vault/token-vault.js.map +1 -0
  287. package/dist/server.d.ts +173 -0
  288. package/dist/server.d.ts.map +1 -1
  289. package/dist/server.js +1622 -86
  290. package/dist/server.js.map +1 -1
  291. package/dist/shared/cli-sections.d.ts +6 -0
  292. package/dist/shared/cli-sections.d.ts.map +1 -0
  293. package/dist/shared/cli-sections.js +16 -0
  294. package/dist/shared/cli-sections.js.map +1 -0
  295. package/dist/shared/cli-utils.d.ts +81 -0
  296. package/dist/shared/cli-utils.d.ts.map +1 -0
  297. package/dist/shared/cli-utils.js +174 -0
  298. package/dist/shared/cli-utils.js.map +1 -0
  299. package/dist/shared/config-docs.d.ts +6 -0
  300. package/dist/shared/config-docs.d.ts.map +1 -0
  301. package/dist/shared/config-docs.js +6 -0
  302. package/dist/shared/config-docs.js.map +1 -0
  303. package/dist/shared/error-handler.d.ts +128 -0
  304. package/dist/shared/error-handler.d.ts.map +1 -0
  305. package/dist/shared/error-handler.js +342 -0
  306. package/dist/shared/error-handler.js.map +1 -0
  307. package/dist/shared/logger.d.ts +42 -0
  308. package/dist/shared/logger.d.ts.map +1 -0
  309. package/dist/shared/logger.js +123 -0
  310. package/dist/shared/logger.js.map +1 -0
  311. package/dist/shared/performance.d.ts +65 -0
  312. package/dist/shared/performance.d.ts.map +1 -0
  313. package/dist/shared/performance.js +136 -0
  314. package/dist/shared/performance.js.map +1 -0
  315. package/dist/shared/task-runner.d.ts +2 -0
  316. package/dist/shared/task-runner.d.ts.map +1 -0
  317. package/dist/shared/task-runner.js +16 -0
  318. package/dist/shared/task-runner.js.map +1 -0
  319. package/dist/shared/validation.d.ts +6 -0
  320. package/dist/shared/validation.d.ts.map +1 -0
  321. package/dist/shared/validation.js +6 -0
  322. package/dist/shared/validation.js.map +1 -0
  323. package/dist/shared-utils.d.ts +63 -0
  324. package/dist/shared-utils.d.ts.map +1 -0
  325. package/dist/shared-utils.js +123 -0
  326. package/dist/shared-utils.js.map +1 -0
  327. package/dist/template-manager.d.ts +23 -2
  328. package/dist/template-manager.d.ts.map +1 -1
  329. package/dist/template-manager.js +177 -88
  330. package/dist/template-manager.js.map +1 -1
  331. package/dist/test-client.d.ts.map +1 -1
  332. package/dist/test-client.js +10 -8
  333. package/dist/test-client.js.map +1 -1
  334. package/dist/test-runner.d.ts +52 -0
  335. package/dist/test-runner.d.ts.map +1 -0
  336. package/dist/test-runner.js +785 -0
  337. package/dist/test-runner.js.map +1 -0
  338. package/dist/testing.d.ts +103 -0
  339. package/dist/testing.d.ts.map +1 -0
  340. package/dist/testing.js +163 -0
  341. package/dist/testing.js.map +1 -0
  342. package/dist/version-checker.d.ts.map +1 -1
  343. package/dist/version-checker.js +2 -2
  344. package/dist/version-checker.js.map +1 -1
  345. package/dist/version.d.ts +2 -0
  346. package/dist/version.d.ts.map +1 -0
  347. package/dist/version.js +5 -0
  348. package/dist/version.js.map +1 -0
  349. package/dist/watcher.d.ts +6 -3
  350. package/dist/watcher.d.ts.map +1 -1
  351. package/dist/watcher.js +49 -10
  352. package/dist/watcher.js.map +1 -1
  353. package/package.json +47 -7
  354. package/templates/cloudflare/worker.ts.template +381 -0
  355. package/templates/cloudflare/wrangler.toml.template +9 -0
  356. package/dist/base.d.ts +0 -58
  357. package/dist/base.d.ts.map +0 -1
  358. package/dist/base.js +0 -92
  359. package/dist/base.js.map +0 -1
  360. package/dist/dependency-manager.d.ts +0 -49
  361. package/dist/dependency-manager.d.ts.map +0 -1
  362. package/dist/dependency-manager.js +0 -165
  363. package/dist/dependency-manager.js.map +0 -1
  364. package/dist/registry-manager.d.ts +0 -76
  365. package/dist/registry-manager.d.ts.map +0 -1
  366. package/dist/registry-manager.js +0 -220
  367. package/dist/registry-manager.js.map +0 -1
  368. package/dist/schema-extractor.d.ts +0 -110
  369. package/dist/schema-extractor.d.ts.map +0 -1
  370. package/dist/schema-extractor.js +0 -727
  371. package/dist/schema-extractor.js.map +0 -1
  372. package/dist/test-marketplace-sources.d.ts +0 -5
  373. package/dist/test-marketplace-sources.d.ts.map +0 -1
  374. package/dist/test-marketplace-sources.js +0 -53
  375. package/dist/test-marketplace-sources.js.map +0 -1
  376. package/dist/types.d.ts +0 -109
  377. package/dist/types.d.ts.map +0 -1
  378. package/dist/types.js +0 -12
  379. package/dist/types.js.map +0 -1
@@ -1,215 +1,876 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Daemon Server
3
+ * Global Photon Daemon Server
4
4
  *
5
- * Runs as a background process for stateful photons
6
- * - Loads and initializes the photon
7
- * - Listens on Unix socket / named pipe
8
- * - Handles command requests
9
- * - Manages idle timeout and shutdown
5
+ * Single daemon process for all photons.
6
+ * - Listens on global Unix socket / named pipe (~/.photon/daemon.sock)
7
+ * - Handles requests for multiple photons via photonName field
8
+ * - Lazy-initializes SessionManagers per photon on first request
9
+ * - Provides pub/sub, locks, scheduled jobs, and webhooks
10
10
  */
11
11
  import * as net from 'net';
12
+ import * as http from 'http';
12
13
  import * as fs from 'fs';
13
14
  import { SessionManager } from './session-manager.js';
14
- // Command line args: photonName photonPath socketPath
15
- const photonName = process.argv[2];
16
- const photonPath = process.argv[3];
17
- const socketPath = process.argv[4];
18
- if (!photonName || !photonPath || !socketPath) {
19
- console.error('[daemon-server] Missing required arguments');
15
+ import { isValidDaemonRequest, } from './protocol.js';
16
+ import { setPromptHandler } from '@portel/photon-core';
17
+ import { createLogger } from '../shared/logger.js';
18
+ import { getErrorMessage } from '../shared/error-handler.js';
19
+ // Command line args: socketPath (global daemon only needs socket path)
20
+ const socketPath = process.argv[2];
21
+ const logger = createLogger({
22
+ component: 'daemon-server',
23
+ scope: 'global',
24
+ minimal: true,
25
+ });
26
+ if (!socketPath) {
27
+ logger.error('Missing required argument: socketPath');
20
28
  process.exit(1);
21
29
  }
22
- let sessionManager = null;
30
+ // Map of photonName -> SessionManager (lazy initialized)
31
+ const sessionManagers = new Map();
32
+ const photonPaths = new Map(); // photonName -> photonPath
23
33
  let idleTimeout = 600000; // 10 minutes default
24
34
  let idleTimer = null;
25
- /**
26
- * Initialize session manager
27
- */
28
- async function initializeSessionManager() {
29
- try {
30
- console.error(`[daemon-server] Initializing session manager for: ${photonName}`);
31
- sessionManager = new SessionManager(photonPath, photonName, idleTimeout);
32
- console.error(`[daemon-server] Session manager initialized`);
33
- console.error(`[daemon-server] Session timeout: ${idleTimeout}ms`);
34
- // Start idle timer if timeout is set
35
- if (idleTimeout > 0) {
36
- startIdleTimer();
35
+ // Track pending prompts waiting for user input
36
+ const pendingPrompts = new Map();
37
+ // Channel subscriptions for pub/sub
38
+ const channelSubscriptions = new Map();
39
+ const EVENT_BUFFER_SIZE = 30;
40
+ const channelEventBuffers = new Map();
41
+ function bufferEvent(channel, message) {
42
+ let buffer = channelEventBuffers.get(channel);
43
+ if (!buffer) {
44
+ buffer = { events: [], nextId: 1 };
45
+ channelEventBuffers.set(channel, buffer);
46
+ }
47
+ const eventId = buffer.nextId++;
48
+ const event = {
49
+ id: eventId,
50
+ channel,
51
+ message,
52
+ timestamp: Date.now(),
53
+ };
54
+ buffer.events.push(event);
55
+ // Keep only last N events (circular buffer)
56
+ if (buffer.events.length > EVENT_BUFFER_SIZE) {
57
+ buffer.events.shift();
58
+ }
59
+ return eventId;
60
+ }
61
+ function getEventsSince(channel, lastEventId) {
62
+ const buffer = channelEventBuffers.get(channel);
63
+ if (!buffer || buffer.events.length === 0) {
64
+ return { events: [], refreshNeeded: false };
65
+ }
66
+ const oldestEvent = buffer.events[0];
67
+ // If lastEventId is older than our oldest buffered event, refresh needed
68
+ if (lastEventId < oldestEvent.id) {
69
+ return { events: [], refreshNeeded: true };
70
+ }
71
+ // Find events to replay
72
+ const events = buffer.events.filter((e) => e.id > lastEventId);
73
+ return { events, refreshNeeded: false };
74
+ }
75
+ // ════════════════════════════════════════════════════════════════════════════════
76
+ // DISTRIBUTED LOCKS
77
+ // ════════════════════════════════════════════════════════════════════════════════
78
+ const activeLocks = new Map();
79
+ const DEFAULT_LOCK_TIMEOUT = 30000;
80
+ function acquireLock(lockName, holder, timeout = DEFAULT_LOCK_TIMEOUT) {
81
+ const now = Date.now();
82
+ const existing = activeLocks.get(lockName);
83
+ if (existing && existing.expiresAt > now) {
84
+ if (existing.holder !== holder) {
85
+ return false;
86
+ }
87
+ existing.expiresAt = now + timeout;
88
+ return true;
89
+ }
90
+ activeLocks.set(lockName, {
91
+ name: lockName,
92
+ holder,
93
+ acquiredAt: now,
94
+ expiresAt: now + timeout,
95
+ });
96
+ logger.info('Lock acquired', { lockName, holder, timeout });
97
+ return true;
98
+ }
99
+ function releaseLock(lockName, holder) {
100
+ const existing = activeLocks.get(lockName);
101
+ if (!existing)
102
+ return true;
103
+ if (existing.holder !== holder)
104
+ return false;
105
+ activeLocks.delete(lockName);
106
+ logger.info('Lock released', { lockName, holder });
107
+ return true;
108
+ }
109
+ function cleanupExpiredLocks() {
110
+ const now = Date.now();
111
+ for (const [name, lock] of activeLocks.entries()) {
112
+ if (lock.expiresAt <= now) {
113
+ activeLocks.delete(name);
114
+ logger.info('Lock expired', { lockName: name, holder: lock.holder });
37
115
  }
38
116
  }
117
+ }
118
+ setInterval(cleanupExpiredLocks, 10000);
119
+ // ════════════════════════════════════════════════════════════════════════════════
120
+ // SCHEDULED JOBS
121
+ // ════════════════════════════════════════════════════════════════════════════════
122
+ const scheduledJobs = new Map();
123
+ const jobTimers = new Map();
124
+ function parseCron(cron) {
125
+ const parts = cron.trim().split(/\s+/);
126
+ if (parts.length !== 5) {
127
+ return { isValid: false, nextRun: 0 };
128
+ }
129
+ const [minute, hour] = parts;
130
+ const now = new Date();
131
+ const nextDate = new Date(now);
132
+ nextDate.setSeconds(0);
133
+ nextDate.setMilliseconds(0);
134
+ if (minute === '*' && hour === '*') {
135
+ nextDate.setMinutes(nextDate.getMinutes() + 1);
136
+ }
137
+ else if (minute.startsWith('*/')) {
138
+ const interval = parseInt(minute.slice(2));
139
+ const currentMinute = nextDate.getMinutes();
140
+ const nextMinute = Math.ceil((currentMinute + 1) / interval) * interval;
141
+ nextDate.setMinutes(nextMinute);
142
+ }
143
+ else if (hour === '*') {
144
+ const targetMinute = parseInt(minute);
145
+ if (nextDate.getMinutes() >= targetMinute) {
146
+ nextDate.setHours(nextDate.getHours() + 1);
147
+ }
148
+ nextDate.setMinutes(targetMinute);
149
+ }
150
+ else {
151
+ const targetMinute = parseInt(minute);
152
+ const targetHour = parseInt(hour);
153
+ nextDate.setMinutes(targetMinute);
154
+ nextDate.setHours(targetHour);
155
+ if (nextDate <= now) {
156
+ nextDate.setDate(nextDate.getDate() + 1);
157
+ }
158
+ }
159
+ return { isValid: true, nextRun: nextDate.getTime() };
160
+ }
161
+ function scheduleJob(job) {
162
+ const { isValid, nextRun } = parseCron(job.cron);
163
+ if (!isValid) {
164
+ logger.error('Invalid cron expression', { jobId: job.id, cron: job.cron });
165
+ return false;
166
+ }
167
+ job.nextRun = nextRun;
168
+ scheduledJobs.set(job.id, job);
169
+ const existingTimer = jobTimers.get(job.id);
170
+ if (existingTimer) {
171
+ clearTimeout(existingTimer);
172
+ }
173
+ const delay = nextRun - Date.now();
174
+ const timer = setTimeout(() => runJob(job.id), delay);
175
+ jobTimers.set(job.id, timer);
176
+ logger.info('Job scheduled', {
177
+ jobId: job.id,
178
+ method: job.method,
179
+ photon: job.photonName,
180
+ nextRun: new Date(nextRun).toISOString(),
181
+ });
182
+ return true;
183
+ }
184
+ async function runJob(jobId) {
185
+ const job = scheduledJobs.get(jobId);
186
+ if (!job)
187
+ return;
188
+ const sessionManager = sessionManagers.get(job.photonName);
189
+ if (!sessionManager) {
190
+ logger.warn('Cannot run job - photon not initialized', { jobId, photon: job.photonName });
191
+ scheduleJob(job); // Reschedule anyway
192
+ return;
193
+ }
194
+ logger.info('Running scheduled job', { jobId, method: job.method, photon: job.photonName });
195
+ try {
196
+ const session = await sessionManager.getOrCreateSession('scheduler', 'scheduler');
197
+ await sessionManager.loader.executeTool(session.instance, job.method, job.args || {});
198
+ job.lastRun = Date.now();
199
+ job.runCount++;
200
+ publishToChannel(`jobs:${job.photonName}`, {
201
+ event: 'job-completed',
202
+ jobId,
203
+ method: job.method,
204
+ runCount: job.runCount,
205
+ });
206
+ logger.info('Job completed', { jobId, method: job.method, runCount: job.runCount });
207
+ }
39
208
  catch (error) {
40
- console.error(`[daemon-server] Failed to initialize session manager: ${error.message}`);
41
- process.exit(1);
209
+ logger.error('Job failed', { jobId, method: job.method, error: getErrorMessage(error) });
210
+ publishToChannel(`jobs:${job.photonName}`, {
211
+ event: 'job-failed',
212
+ jobId,
213
+ method: job.method,
214
+ error: getErrorMessage(error),
215
+ });
42
216
  }
217
+ scheduleJob(job);
43
218
  }
44
- /**
45
- * Start idle timer
46
- */
47
- function startIdleTimer() {
48
- if (idleTimer) {
49
- clearTimeout(idleTimer);
219
+ function unscheduleJob(jobId) {
220
+ const timer = jobTimers.get(jobId);
221
+ if (timer) {
222
+ clearTimeout(timer);
223
+ jobTimers.delete(jobId);
50
224
  }
51
- if (idleTimeout <= 0) {
52
- return; // Idle timeout disabled
225
+ const existed = scheduledJobs.delete(jobId);
226
+ if (existed) {
227
+ logger.info('Job unscheduled', { jobId });
53
228
  }
54
- idleTimer = setTimeout(() => {
55
- if (!sessionManager)
229
+ return existed;
230
+ }
231
+ // ════════════════════════════════════════════════════════════════════════════════
232
+ // WEBHOOK HTTP SERVER
233
+ // ════════════════════════════════════════════════════════════════════════════════
234
+ let webhookServer = null;
235
+ const WEBHOOK_PORT = parseInt(process.env.PHOTON_WEBHOOK_PORT || '0');
236
+ function startWebhookServer(port) {
237
+ if (port <= 0)
238
+ return;
239
+ webhookServer = http.createServer(async (req, res) => {
240
+ res.setHeader('Access-Control-Allow-Origin', '*');
241
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
242
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Webhook-Secret, X-Photon-Name');
243
+ if (req.method === 'OPTIONS') {
244
+ res.writeHead(204);
245
+ res.end();
56
246
  return;
57
- const lastActivity = sessionManager.getLastActivity();
58
- const idleTime = Date.now() - lastActivity;
59
- if (idleTime >= idleTimeout) {
60
- console.error(`[daemon-server] Idle timeout reached (${idleTime}ms). Shutting down.`);
61
- shutdown();
62
247
  }
63
- else {
64
- // Restart timer with remaining time
65
- startIdleTimer();
248
+ // Parse URL: /webhook/{photonName}/{method}
249
+ const url = new URL(req.url || '/', `http://localhost:${port}`);
250
+ const pathParts = url.pathname.split('/').filter(Boolean);
251
+ if (pathParts[0] !== 'webhook' || !pathParts[1] || !pathParts[2]) {
252
+ res.writeHead(404, { 'Content-Type': 'application/json' });
253
+ res.end(JSON.stringify({ error: 'Not found. Use /webhook/{photonName}/{method}' }));
254
+ return;
66
255
  }
67
- }, idleTimeout);
256
+ const photonName = pathParts[1];
257
+ const method = pathParts[2];
258
+ const expectedSecret = process.env.PHOTON_WEBHOOK_SECRET;
259
+ if (expectedSecret) {
260
+ const providedSecret = req.headers['x-webhook-secret'];
261
+ if (providedSecret !== expectedSecret) {
262
+ res.writeHead(401, { 'Content-Type': 'application/json' });
263
+ res.end(JSON.stringify({ error: 'Invalid webhook secret' }));
264
+ return;
265
+ }
266
+ }
267
+ let body = '';
268
+ req.on('data', (chunk) => {
269
+ body += chunk;
270
+ });
271
+ req.on('end', async () => {
272
+ let args = {};
273
+ try {
274
+ if (body) {
275
+ args = JSON.parse(body);
276
+ }
277
+ args._webhook = {
278
+ method: req.method,
279
+ headers: req.headers,
280
+ query: Object.fromEntries(url.searchParams),
281
+ timestamp: Date.now(),
282
+ };
283
+ }
284
+ catch {
285
+ res.writeHead(400, { 'Content-Type': 'application/json' });
286
+ res.end(JSON.stringify({ error: 'Invalid JSON body' }));
287
+ return;
288
+ }
289
+ const sessionManager = sessionManagers.get(photonName);
290
+ if (!sessionManager) {
291
+ res.writeHead(503, { 'Content-Type': 'application/json' });
292
+ res.end(JSON.stringify({ error: `Photon '${photonName}' not initialized` }));
293
+ return;
294
+ }
295
+ try {
296
+ const session = await sessionManager.getOrCreateSession('webhook', 'webhook');
297
+ const result = await sessionManager.loader.executeTool(session.instance, method, args);
298
+ logger.info('Webhook executed', { photon: photonName, method });
299
+ publishToChannel(`webhooks:${photonName}`, {
300
+ event: 'webhook-received',
301
+ method,
302
+ timestamp: Date.now(),
303
+ });
304
+ res.writeHead(200, { 'Content-Type': 'application/json' });
305
+ res.end(JSON.stringify({ success: true, data: result }));
306
+ }
307
+ catch (error) {
308
+ logger.error('Webhook execution failed', {
309
+ photon: photonName,
310
+ method,
311
+ error: getErrorMessage(error),
312
+ });
313
+ res.writeHead(500, { 'Content-Type': 'application/json' });
314
+ res.end(JSON.stringify({ error: getErrorMessage(error) }));
315
+ }
316
+ });
317
+ });
318
+ webhookServer.listen(port, () => {
319
+ logger.info('Webhook server started', { port });
320
+ });
321
+ webhookServer.on('error', (error) => {
322
+ logger.error('Webhook server error', { error: getErrorMessage(error) });
323
+ });
68
324
  }
69
- /**
70
- * Reset idle timer (called on each activity)
71
- */
72
- function resetIdleTimer() {
73
- startIdleTimer();
325
+ // ════════════════════════════════════════════════════════════════════════════════
326
+ // PUB/SUB
327
+ // ════════════════════════════════════════════════════════════════════════════════
328
+ function cleanupSocketSubscriptions(socket) {
329
+ for (const [channel, subs] of channelSubscriptions.entries()) {
330
+ subs.delete(socket);
331
+ if (subs.size === 0) {
332
+ channelSubscriptions.delete(channel);
333
+ }
334
+ }
74
335
  }
75
- /**
76
- * Handle incoming command request
77
- */
78
- async function handleRequest(request) {
336
+ function publishToChannel(channel, message, excludeSocket) {
337
+ // Buffer the event for replay
338
+ const eventId = bufferEvent(channel, message);
339
+ const payload = JSON.stringify({
340
+ type: 'channel_message',
341
+ id: `ch_${eventId}`,
342
+ eventId,
343
+ channel,
344
+ message,
345
+ }) + '\n';
346
+ const sentSockets = new Set();
347
+ // Send to exact channel subscribers
348
+ const exactSubscribers = channelSubscriptions.get(channel);
349
+ if (exactSubscribers) {
350
+ for (const socket of exactSubscribers) {
351
+ if (socket !== excludeSocket && !socket.destroyed && !sentSockets.has(socket)) {
352
+ try {
353
+ socket.write(payload);
354
+ sentSockets.add(socket);
355
+ }
356
+ catch {
357
+ // Socket write failed
358
+ }
359
+ }
360
+ }
361
+ }
362
+ // Send to wildcard subscribers
363
+ const channelPrefix = channel.split(':')[0];
364
+ if (channelPrefix) {
365
+ const wildcardChannel = `${channelPrefix}:*`;
366
+ const wildcardSubscribers = channelSubscriptions.get(wildcardChannel);
367
+ if (wildcardSubscribers) {
368
+ for (const socket of wildcardSubscribers) {
369
+ if (socket !== excludeSocket && !socket.destroyed && !sentSockets.has(socket)) {
370
+ try {
371
+ socket.write(payload);
372
+ sentSockets.add(socket);
373
+ }
374
+ catch {
375
+ // Socket write failed
376
+ }
377
+ }
378
+ }
379
+ }
380
+ }
381
+ logger.debug('Published to channel', {
382
+ channel,
383
+ eventId,
384
+ exactSubs: exactSubscribers?.size || 0,
385
+ wildcardSubs: channelSubscriptions.get(`${channelPrefix}:*`)?.size || 0,
386
+ });
387
+ return eventId;
388
+ }
389
+ // ════════════════════════════════════════════════════════════════════════════════
390
+ // SESSION MANAGER (Lazy Initialization)
391
+ // ════════════════════════════════════════════════════════════════════════════════
392
+ async function getOrCreateSessionManager(photonName, photonPath) {
393
+ let manager = sessionManagers.get(photonName);
394
+ if (manager) {
395
+ return manager;
396
+ }
397
+ // Need photonPath to initialize
398
+ const storedPath = photonPaths.get(photonName);
399
+ const pathToUse = photonPath || storedPath;
400
+ if (!pathToUse) {
401
+ logger.warn('Cannot initialize photon - no path provided', { photonName });
402
+ return null;
403
+ }
404
+ try {
405
+ logger.info('Initializing session manager', { photonName, photonPath: pathToUse });
406
+ manager = new SessionManager(pathToUse, photonName, idleTimeout, logger.child({ scope: photonName }));
407
+ sessionManagers.set(photonName, manager);
408
+ photonPaths.set(photonName, pathToUse);
409
+ logger.info('Session manager initialized', { photonName });
410
+ return manager;
411
+ }
412
+ catch (error) {
413
+ logger.error('Failed to initialize session manager', {
414
+ photonName,
415
+ error: getErrorMessage(error),
416
+ });
417
+ return null;
418
+ }
419
+ }
420
+ // ════════════════════════════════════════════════════════════════════════════════
421
+ // PROMPT HANDLER
422
+ // ════════════════════════════════════════════════════════════════════════════════
423
+ function createSocketPromptHandler(socket, requestId) {
424
+ return async (message, defaultValue) => {
425
+ return new Promise((resolve, reject) => {
426
+ pendingPrompts.set(requestId, {
427
+ resolve: (value) => resolve(value),
428
+ reject,
429
+ });
430
+ const promptResponse = {
431
+ type: 'prompt',
432
+ id: requestId,
433
+ prompt: {
434
+ type: 'text',
435
+ message,
436
+ default: defaultValue,
437
+ },
438
+ };
439
+ socket.write(JSON.stringify(promptResponse) + '\n');
440
+ });
441
+ };
442
+ }
443
+ // ════════════════════════════════════════════════════════════════════════════════
444
+ // REQUEST HANDLER
445
+ // ════════════════════════════════════════════════════════════════════════════════
446
+ async function handleRequest(request, socket) {
79
447
  resetIdleTimer();
80
448
  if (request.type === 'ping') {
449
+ return { type: 'pong', id: request.id };
450
+ }
451
+ if (request.type === 'shutdown') {
452
+ shutdown();
453
+ return { type: 'result', id: request.id, success: true, data: { message: 'Shutting down' } };
454
+ }
455
+ // Handle hot-reload request
456
+ if (request.type === 'reload') {
457
+ const photonName = request.photonName;
458
+ const photonPath = request.photonPath;
459
+ if (!photonName || !photonPath) {
460
+ return {
461
+ type: 'error',
462
+ id: request.id,
463
+ error: 'photonName and photonPath required for reload',
464
+ };
465
+ }
466
+ const result = await reloadPhoton(photonName, photonPath);
81
467
  return {
82
- type: 'pong',
468
+ type: 'result',
83
469
  id: request.id,
470
+ success: result.success,
471
+ data: result.success
472
+ ? { message: 'Reload complete', sessionsUpdated: result.sessionsUpdated }
473
+ : { error: result.error },
84
474
  };
85
475
  }
86
- if (request.type === 'shutdown') {
87
- shutdown();
476
+ // Handle prompt response
477
+ if (request.type === 'prompt_response') {
478
+ const pending = pendingPrompts.get(request.id);
479
+ if (pending) {
480
+ pendingPrompts.delete(request.id);
481
+ pending.resolve(request.promptValue ?? null);
482
+ }
483
+ return null;
484
+ }
485
+ // Handle channel subscribe
486
+ if (request.type === 'subscribe') {
487
+ const channel = request.channel;
488
+ const lastEventId = request.lastEventId;
489
+ let subs = channelSubscriptions.get(channel);
490
+ if (!subs) {
491
+ subs = new Set();
492
+ channelSubscriptions.set(channel, subs);
493
+ }
494
+ subs.add(socket);
495
+ logger.info('Client subscribed to channel', { channel, subscribers: subs.size });
496
+ // Replay missed events if lastEventId provided
497
+ if (lastEventId !== undefined) {
498
+ const parsedLastEventId = parseInt(String(lastEventId), 10) || 0;
499
+ const { events, refreshNeeded } = getEventsSince(channel, parsedLastEventId);
500
+ if (refreshNeeded) {
501
+ // Send refresh-needed signal
502
+ socket.write(JSON.stringify({
503
+ type: 'refresh_needed',
504
+ id: request.id,
505
+ channel,
506
+ }) + '\n');
507
+ logger.info('Replay: refresh needed', { channel, lastEventId });
508
+ }
509
+ else if (events.length > 0) {
510
+ // Replay events
511
+ for (const event of events) {
512
+ socket.write(JSON.stringify({
513
+ type: 'channel_message',
514
+ id: `replay_${event.id}`,
515
+ eventId: event.id,
516
+ channel: event.channel,
517
+ message: event.message,
518
+ replay: true,
519
+ }) + '\n');
520
+ }
521
+ logger.info('Replayed events', { channel, count: events.length });
522
+ }
523
+ }
524
+ return {
525
+ type: 'result',
526
+ id: request.id,
527
+ success: true,
528
+ data: { subscribed: true, channel },
529
+ };
530
+ }
531
+ // Handle channel unsubscribe
532
+ if (request.type === 'unsubscribe') {
533
+ const channel = request.channel;
534
+ const subs = channelSubscriptions.get(channel);
535
+ if (subs) {
536
+ subs.delete(socket);
537
+ if (subs.size === 0) {
538
+ channelSubscriptions.delete(channel);
539
+ }
540
+ }
541
+ logger.info('Client unsubscribed from channel', { channel });
542
+ return { type: 'result', id: request.id, success: true, data: { unsubscribed: true, channel } };
543
+ }
544
+ // Handle channel publish
545
+ if (request.type === 'publish') {
546
+ const channel = request.channel;
547
+ const message = request.message;
548
+ const eventId = publishToChannel(channel, message, socket);
88
549
  return {
89
550
  type: 'result',
90
551
  id: request.id,
91
552
  success: true,
92
- data: { message: 'Shutting down' },
553
+ data: { published: true, channel, eventId },
554
+ };
555
+ }
556
+ // Handle get_events_since (for event replay)
557
+ if (request.type === 'get_events_since') {
558
+ const channel = request.channel;
559
+ const parsedLastEventId = parseInt(String(request.lastEventId || '0'), 10) || 0;
560
+ const { events, refreshNeeded } = getEventsSince(channel, parsedLastEventId);
561
+ return {
562
+ type: 'result',
563
+ id: request.id,
564
+ success: true,
565
+ data: { events, refreshNeeded },
566
+ };
567
+ }
568
+ // Handle lock acquisition
569
+ if (request.type === 'lock') {
570
+ const lockName = request.lockName;
571
+ const holder = request.sessionId || request.id;
572
+ const timeout = request.lockTimeout || DEFAULT_LOCK_TIMEOUT;
573
+ const acquired = acquireLock(lockName, holder, timeout);
574
+ return {
575
+ type: 'result',
576
+ id: request.id,
577
+ success: acquired,
578
+ data: {
579
+ acquired,
580
+ lockName,
581
+ holder,
582
+ ...(acquired ? {} : { reason: 'Lock held by another client' }),
583
+ },
584
+ };
585
+ }
586
+ // Handle lock release
587
+ if (request.type === 'unlock') {
588
+ const lockName = request.lockName;
589
+ const holder = request.sessionId || request.id;
590
+ const released = releaseLock(lockName, holder);
591
+ return {
592
+ type: 'result',
593
+ id: request.id,
594
+ success: released,
595
+ data: {
596
+ released,
597
+ lockName,
598
+ ...(released ? {} : { reason: 'Cannot release lock held by another client' }),
599
+ },
600
+ };
601
+ }
602
+ // Handle list locks
603
+ if (request.type === 'list_locks') {
604
+ const locks = Array.from(activeLocks.values());
605
+ return { type: 'result', id: request.id, success: true, data: { locks } };
606
+ }
607
+ // Handle job scheduling
608
+ if (request.type === 'schedule') {
609
+ const photonName = request.photonName;
610
+ if (!photonName) {
611
+ return { type: 'error', id: request.id, error: 'photonName required for scheduling' };
612
+ }
613
+ const job = {
614
+ id: request.jobId,
615
+ method: request.method,
616
+ args: request.args,
617
+ cron: request.cron,
618
+ runCount: 0,
619
+ createdAt: Date.now(),
620
+ createdBy: request.sessionId,
621
+ photonName,
622
+ };
623
+ const scheduled = scheduleJob(job);
624
+ return {
625
+ type: 'result',
626
+ id: request.id,
627
+ success: scheduled,
628
+ data: scheduled
629
+ ? { scheduled: true, jobId: job.id, nextRun: job.nextRun }
630
+ : { scheduled: false, reason: 'Invalid cron expression' },
93
631
  };
94
632
  }
633
+ // Handle job unscheduling
634
+ if (request.type === 'unschedule') {
635
+ const jobId = request.jobId;
636
+ const unscheduled = unscheduleJob(jobId);
637
+ return { type: 'result', id: request.id, success: true, data: { unscheduled, jobId } };
638
+ }
639
+ // Handle list jobs
640
+ if (request.type === 'list_jobs') {
641
+ const jobs = Array.from(scheduledJobs.values());
642
+ return { type: 'result', id: request.id, success: true, data: { jobs } };
643
+ }
644
+ // Handle command execution
95
645
  if (request.type === 'command') {
96
646
  if (!request.method) {
97
- return {
98
- type: 'error',
99
- id: request.id,
100
- error: 'Method name required',
101
- };
647
+ return { type: 'error', id: request.id, error: 'Method name required' };
102
648
  }
649
+ const photonName = request.photonName;
650
+ if (!photonName) {
651
+ return { type: 'error', id: request.id, error: 'photonName required for commands' };
652
+ }
653
+ const sessionManager = await getOrCreateSessionManager(photonName, request.photonPath);
103
654
  if (!sessionManager) {
104
655
  return {
105
656
  type: 'error',
106
657
  id: request.id,
107
- error: 'Session manager not initialized',
658
+ error: `Cannot initialize photon '${photonName}'. Provide photonPath in request.`,
108
659
  };
109
660
  }
110
661
  try {
111
- // Get or create session for this client
112
662
  const session = await sessionManager.getOrCreateSession(request.sessionId, request.clientType);
113
- console.error(`[daemon-server] Executing: ${request.method} (session: ${session.id})`);
114
- // Execute method on session's photon instance using loader
115
- const result = await sessionManager.loader.executeTool(session.instance, request.method, request.args || {});
116
- return {
117
- type: 'result',
118
- id: request.id,
119
- success: true,
120
- data: result,
663
+ logger.info('Executing request', {
664
+ method: request.method,
665
+ photon: photonName,
666
+ sessionId: session.id,
667
+ });
668
+ setPromptHandler(createSocketPromptHandler(socket, request.id));
669
+ const outputHandler = (emit) => {
670
+ if (emit && typeof emit === 'object' && emit.channel) {
671
+ publishToChannel(emit.channel, emit, socket);
672
+ logger.debug('Published to channel', { channel: emit.channel });
673
+ }
121
674
  };
675
+ const result = await sessionManager.loader.executeTool(session.instance, request.method, request.args || {}, { outputHandler });
676
+ setPromptHandler(null);
677
+ return { type: 'result', id: request.id, success: true, data: result };
122
678
  }
123
679
  catch (error) {
124
- console.error(`[daemon-server] Error executing ${request.method}: ${error.message}`);
125
- return {
126
- type: 'error',
127
- id: request.id,
128
- error: error.message,
129
- };
680
+ logger.error('Error executing request', {
681
+ method: request.method,
682
+ error: getErrorMessage(error),
683
+ });
684
+ setPromptHandler(null);
685
+ return { type: 'error', id: request.id, error: getErrorMessage(error) };
130
686
  }
131
687
  }
132
- return {
133
- type: 'error',
134
- id: request.id,
135
- error: `Unknown request type: ${request.type}`,
136
- };
688
+ return { type: 'error', id: request.id, error: `Unknown request type: ${request.type}` };
137
689
  }
138
- /**
139
- * Start IPC server
140
- */
690
+ // ════════════════════════════════════════════════════════════════════════════════
691
+ // HOT RELOAD
692
+ // ════════════════════════════════════════════════════════════════════════════════
693
+ async function reloadPhoton(photonName, newPhotonPath) {
694
+ try {
695
+ logger.info('Hot-reloading photon', { photonName, path: newPhotonPath });
696
+ const sessionManager = sessionManagers.get(photonName);
697
+ if (!sessionManager) {
698
+ // First time - just register the path
699
+ photonPaths.set(photonName, newPhotonPath);
700
+ return { success: true, sessionsUpdated: 0 };
701
+ }
702
+ await sessionManager.loader.reloadFile(newPhotonPath);
703
+ const sessions = sessionManager.getSessions();
704
+ let updatedCount = 0;
705
+ for (const session of sessions) {
706
+ try {
707
+ const newInstance = await sessionManager.loader.loadFile(newPhotonPath);
708
+ const oldInstance = session.instance;
709
+ if (oldInstance && typeof oldInstance === 'object') {
710
+ for (const key of Object.keys(oldInstance)) {
711
+ const value = oldInstance[key];
712
+ if (typeof value !== 'function' && key !== 'constructor') {
713
+ try {
714
+ newInstance[key] = value;
715
+ }
716
+ catch {
717
+ // Some properties may be read-only
718
+ }
719
+ }
720
+ }
721
+ }
722
+ if (sessionManager.updateSessionInstance(session.id, newInstance)) {
723
+ updatedCount++;
724
+ }
725
+ }
726
+ catch (err) {
727
+ logger.error('Failed to update session instance', {
728
+ sessionId: session.id,
729
+ error: getErrorMessage(err),
730
+ });
731
+ }
732
+ }
733
+ publishToChannel(`system:${photonName}`, {
734
+ event: 'photon-reloaded',
735
+ timestamp: Date.now(),
736
+ sessionsUpdated: updatedCount,
737
+ });
738
+ logger.info('Photon reloaded successfully', { photonName, sessionsUpdated: updatedCount });
739
+ return { success: true, sessionsUpdated: updatedCount };
740
+ }
741
+ catch (error) {
742
+ const errorMessage = getErrorMessage(error);
743
+ logger.error('Photon reload failed', { photonName, error: errorMessage });
744
+ return { success: false, error: errorMessage };
745
+ }
746
+ }
747
+ // ════════════════════════════════════════════════════════════════════════════════
748
+ // IDLE TIMER
749
+ // ════════════════════════════════════════════════════════════════════════════════
750
+ function startIdleTimer() {
751
+ if (idleTimer) {
752
+ clearTimeout(idleTimer);
753
+ }
754
+ if (idleTimeout <= 0)
755
+ return;
756
+ idleTimer = setTimeout(() => {
757
+ let activeSubscribers = 0;
758
+ for (const subs of channelSubscriptions.values()) {
759
+ activeSubscribers += subs.size;
760
+ }
761
+ if (activeSubscribers > 0) {
762
+ logger.debug('Active channel subscribers, staying alive', { activeSubscribers });
763
+ startIdleTimer();
764
+ return;
765
+ }
766
+ // Check if any session manager has recent activity
767
+ let lastActivity = 0;
768
+ for (const manager of sessionManagers.values()) {
769
+ const activity = manager.getLastActivity();
770
+ if (activity > lastActivity) {
771
+ lastActivity = activity;
772
+ }
773
+ }
774
+ const idleTime = Date.now() - lastActivity;
775
+ if (idleTime >= idleTimeout && sessionManagers.size > 0) {
776
+ logger.warn('Idle timeout reached, shutting down', { idleTime });
777
+ shutdown();
778
+ }
779
+ else {
780
+ startIdleTimer();
781
+ }
782
+ }, idleTimeout);
783
+ }
784
+ function resetIdleTimer() {
785
+ startIdleTimer();
786
+ }
787
+ // ════════════════════════════════════════════════════════════════════════════════
788
+ // SERVER
789
+ // ════════════════════════════════════════════════════════════════════════════════
141
790
  function startServer() {
142
791
  const server = net.createServer((socket) => {
143
- console.error('[daemon-server] Client connected');
792
+ logger.info('Client connected');
144
793
  let buffer = '';
145
794
  socket.on('data', async (chunk) => {
146
795
  buffer += chunk.toString();
147
- // Process complete JSON messages (newline-delimited)
148
796
  const lines = buffer.split('\n');
149
- buffer = lines.pop() || ''; // Keep incomplete line in buffer
797
+ buffer = lines.pop() || '';
150
798
  for (const line of lines) {
151
799
  if (!line.trim())
152
800
  continue;
153
801
  try {
154
- const request = JSON.parse(line);
155
- const response = await handleRequest(request);
156
- socket.write(JSON.stringify(response) + '\n');
802
+ const parsed = JSON.parse(line);
803
+ if (!isValidDaemonRequest(parsed)) {
804
+ socket.write(JSON.stringify({ type: 'error', id: 'unknown', error: 'Invalid request format' }) +
805
+ '\n');
806
+ continue;
807
+ }
808
+ const request = parsed;
809
+ const response = await handleRequest(request, socket);
810
+ if (response !== null) {
811
+ socket.write(JSON.stringify(response) + '\n');
812
+ }
157
813
  }
158
814
  catch (error) {
159
- console.error(`[daemon-server] Error processing request: ${error.message}`);
160
- socket.write(JSON.stringify({
161
- type: 'error',
162
- id: 'unknown',
163
- error: error.message,
164
- }) + '\n');
815
+ logger.error('Error processing request', { error: getErrorMessage(error) });
816
+ socket.write(JSON.stringify({ type: 'error', id: 'unknown', error: getErrorMessage(error) }) + '\n');
165
817
  }
166
818
  }
167
819
  });
168
820
  socket.on('end', () => {
169
- console.error('[daemon-server] Client disconnected');
821
+ logger.info('Client disconnected');
822
+ cleanupSocketSubscriptions(socket);
170
823
  });
171
824
  socket.on('error', (error) => {
172
- console.error(`[daemon-server] Socket error: ${error.message}`);
825
+ logger.warn('Socket error', { error: getErrorMessage(error) });
826
+ cleanupSocketSubscriptions(socket);
827
+ });
828
+ socket.on('close', () => {
829
+ cleanupSocketSubscriptions(socket);
173
830
  });
174
831
  });
175
832
  server.listen(socketPath, () => {
176
- console.error(`[daemon-server] Listening on ${socketPath}`);
177
- console.error(`[daemon-server] PID: ${process.pid}`);
833
+ logger.info('Global Photon daemon listening', { socketPath, pid: process.pid });
178
834
  });
179
835
  server.on('error', (error) => {
180
- console.error(`[daemon-server] Server error: ${error.message}`);
836
+ logger.error('Server error', { error: getErrorMessage(error) });
181
837
  process.exit(1);
182
838
  });
183
- // Graceful shutdown handlers
184
839
  process.on('SIGTERM', shutdown);
185
840
  process.on('SIGINT', shutdown);
186
841
  }
187
- /**
188
- * Shutdown daemon
189
- */
190
842
  function shutdown() {
191
- console.error('[daemon-server] Shutting down...');
843
+ logger.info('Shutting down global daemon');
192
844
  if (idleTimer) {
193
845
  clearTimeout(idleTimer);
194
846
  }
195
- // Destroy session manager
196
- if (sessionManager) {
197
- sessionManager.destroy();
847
+ for (const timer of jobTimers.values()) {
848
+ clearTimeout(timer);
849
+ }
850
+ jobTimers.clear();
851
+ scheduledJobs.clear();
852
+ activeLocks.clear();
853
+ for (const manager of sessionManagers.values()) {
854
+ manager.destroy();
855
+ }
856
+ sessionManagers.clear();
857
+ if (webhookServer) {
858
+ webhookServer.close();
198
859
  }
199
- // Clean up socket file (Unix only)
200
860
  if (fs.existsSync(socketPath) && process.platform !== 'win32') {
201
861
  try {
202
862
  fs.unlinkSync(socketPath);
203
863
  }
204
- catch (error) {
864
+ catch {
205
865
  // Ignore cleanup errors
206
866
  }
207
867
  }
208
868
  process.exit(0);
209
869
  }
210
870
  // Main execution
211
- (async () => {
212
- await initializeSessionManager();
871
+ (() => {
213
872
  startServer();
873
+ startWebhookServer(WEBHOOK_PORT);
874
+ startIdleTimer();
214
875
  })();
215
876
  //# sourceMappingURL=server.js.map