@portel/photon 1.9.0 → 1.10.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 (294) hide show
  1. package/README.md +163 -210
  2. package/dist/async/dedup-map.d.ts +40 -0
  3. package/dist/async/dedup-map.d.ts.map +1 -0
  4. package/dist/async/dedup-map.js +80 -0
  5. package/dist/async/dedup-map.js.map +1 -0
  6. package/dist/async/index.d.ts +11 -0
  7. package/dist/async/index.d.ts.map +1 -0
  8. package/dist/async/index.js +11 -0
  9. package/dist/async/index.js.map +1 -0
  10. package/dist/async/loading-gate.d.ts +27 -0
  11. package/dist/async/loading-gate.d.ts.map +1 -0
  12. package/dist/async/loading-gate.js +48 -0
  13. package/dist/async/loading-gate.js.map +1 -0
  14. package/dist/async/with-timeout.d.ts +6 -0
  15. package/dist/async/with-timeout.d.ts.map +1 -0
  16. package/dist/async/with-timeout.js +17 -0
  17. package/dist/async/with-timeout.js.map +1 -0
  18. package/dist/auto-ui/beam/class-metadata.d.ts +52 -0
  19. package/dist/auto-ui/beam/class-metadata.d.ts.map +1 -0
  20. package/dist/auto-ui/beam/class-metadata.js +133 -0
  21. package/dist/auto-ui/beam/class-metadata.js.map +1 -0
  22. package/dist/auto-ui/beam/config.d.ts +13 -0
  23. package/dist/auto-ui/beam/config.d.ts.map +1 -0
  24. package/dist/auto-ui/beam/config.js +52 -0
  25. package/dist/auto-ui/beam/config.js.map +1 -0
  26. package/dist/auto-ui/beam/external-mcp.d.ts +37 -0
  27. package/dist/auto-ui/beam/external-mcp.d.ts.map +1 -0
  28. package/dist/auto-ui/beam/external-mcp.js +311 -0
  29. package/dist/auto-ui/beam/external-mcp.js.map +1 -0
  30. package/dist/auto-ui/beam/photon-management.d.ts +51 -0
  31. package/dist/auto-ui/beam/photon-management.d.ts.map +1 -0
  32. package/dist/auto-ui/beam/photon-management.js +310 -0
  33. package/dist/auto-ui/beam/photon-management.js.map +1 -0
  34. package/dist/auto-ui/beam/routes/api-browse.d.ts +17 -0
  35. package/dist/auto-ui/beam/routes/api-browse.d.ts.map +1 -0
  36. package/dist/auto-ui/beam/routes/api-browse.js +531 -0
  37. package/dist/auto-ui/beam/routes/api-browse.js.map +1 -0
  38. package/dist/auto-ui/beam/routes/api-config.d.ts +9 -0
  39. package/dist/auto-ui/beam/routes/api-config.d.ts.map +1 -0
  40. package/dist/auto-ui/beam/routes/api-config.js +494 -0
  41. package/dist/auto-ui/beam/routes/api-config.js.map +1 -0
  42. package/dist/auto-ui/beam/routes/api-marketplace.d.ts +8 -0
  43. package/dist/auto-ui/beam/routes/api-marketplace.d.ts.map +1 -0
  44. package/dist/auto-ui/beam/routes/api-marketplace.js +490 -0
  45. package/dist/auto-ui/beam/routes/api-marketplace.js.map +1 -0
  46. package/dist/auto-ui/beam/startup.d.ts +41 -0
  47. package/dist/auto-ui/beam/startup.d.ts.map +1 -0
  48. package/dist/auto-ui/beam/startup.js +98 -0
  49. package/dist/auto-ui/beam/startup.js.map +1 -0
  50. package/dist/auto-ui/beam/subscription.d.ts +35 -0
  51. package/dist/auto-ui/beam/subscription.d.ts.map +1 -0
  52. package/dist/auto-ui/beam/subscription.js +151 -0
  53. package/dist/auto-ui/beam/subscription.js.map +1 -0
  54. package/dist/auto-ui/beam/types.d.ts +103 -0
  55. package/dist/auto-ui/beam/types.d.ts.map +1 -0
  56. package/dist/auto-ui/beam/types.js +8 -0
  57. package/dist/auto-ui/beam/types.js.map +1 -0
  58. package/dist/auto-ui/beam.d.ts +2 -0
  59. package/dist/auto-ui/beam.d.ts.map +1 -1
  60. package/dist/auto-ui/beam.js +729 -2596
  61. package/dist/auto-ui/beam.js.map +1 -1
  62. package/dist/auto-ui/bridge/index.d.ts.map +1 -1
  63. package/dist/auto-ui/bridge/index.js +10 -2
  64. package/dist/auto-ui/bridge/index.js.map +1 -1
  65. package/dist/auto-ui/components/card.d.ts.map +1 -1
  66. package/dist/auto-ui/components/card.js +3 -1
  67. package/dist/auto-ui/components/card.js.map +1 -1
  68. package/dist/auto-ui/components/progress.d.ts.map +1 -1
  69. package/dist/auto-ui/components/progress.js.map +1 -1
  70. package/dist/auto-ui/daemon-tools.d.ts +1 -1
  71. package/dist/auto-ui/daemon-tools.d.ts.map +1 -1
  72. package/dist/auto-ui/daemon-tools.js +4 -3
  73. package/dist/auto-ui/daemon-tools.js.map +1 -1
  74. package/dist/auto-ui/photon-bridge.d.ts +6 -2
  75. package/dist/auto-ui/photon-bridge.d.ts.map +1 -1
  76. package/dist/auto-ui/photon-bridge.js +20 -8
  77. package/dist/auto-ui/photon-bridge.js.map +1 -1
  78. package/dist/auto-ui/platform-compat.d.ts.map +1 -1
  79. package/dist/auto-ui/platform-compat.js +4 -0
  80. package/dist/auto-ui/platform-compat.js.map +1 -1
  81. package/dist/auto-ui/streamable-http-transport.d.ts +4 -2
  82. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  83. package/dist/auto-ui/streamable-http-transport.js +120 -30
  84. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  85. package/dist/auto-ui/types.d.ts +4 -2
  86. package/dist/auto-ui/types.d.ts.map +1 -1
  87. package/dist/auto-ui/types.js.map +1 -1
  88. package/dist/beam.bundle.js +8225 -3999
  89. package/dist/beam.bundle.js.map +4 -4
  90. package/dist/cli/commands/alias.d.ts +14 -0
  91. package/dist/cli/commands/alias.d.ts.map +1 -0
  92. package/dist/cli/commands/alias.js +41 -0
  93. package/dist/cli/commands/alias.js.map +1 -0
  94. package/dist/cli/commands/audit.d.ts +9 -0
  95. package/dist/cli/commands/audit.d.ts.map +1 -0
  96. package/dist/cli/commands/audit.js +377 -0
  97. package/dist/cli/commands/audit.js.map +1 -0
  98. package/dist/cli/commands/beam.d.ts +20 -0
  99. package/dist/cli/commands/beam.d.ts.map +1 -0
  100. package/dist/cli/commands/beam.js +256 -0
  101. package/dist/cli/commands/beam.js.map +1 -0
  102. package/dist/cli/commands/config.d.ts +14 -0
  103. package/dist/cli/commands/config.d.ts.map +1 -0
  104. package/dist/cli/commands/config.js +165 -0
  105. package/dist/cli/commands/config.js.map +1 -0
  106. package/dist/cli/commands/daemon.d.ts +11 -0
  107. package/dist/cli/commands/daemon.d.ts.map +1 -0
  108. package/dist/cli/commands/daemon.js +108 -0
  109. package/dist/cli/commands/daemon.js.map +1 -0
  110. package/dist/cli/commands/doctor.d.ts +14 -0
  111. package/dist/cli/commands/doctor.d.ts.map +1 -0
  112. package/dist/cli/commands/doctor.js +257 -0
  113. package/dist/cli/commands/doctor.js.map +1 -0
  114. package/dist/cli/commands/host.d.ts +11 -0
  115. package/dist/cli/commands/host.d.ts.map +1 -0
  116. package/dist/cli/commands/host.js +96 -0
  117. package/dist/cli/commands/host.js.map +1 -0
  118. package/dist/cli/commands/info.d.ts +1 -1
  119. package/dist/cli/commands/info.d.ts.map +1 -1
  120. package/dist/cli/commands/info.js +16 -15
  121. package/dist/cli/commands/info.js.map +1 -1
  122. package/dist/cli/commands/init.d.ts +20 -0
  123. package/dist/cli/commands/init.d.ts.map +1 -0
  124. package/dist/cli/commands/init.js +774 -0
  125. package/dist/cli/commands/init.js.map +1 -0
  126. package/dist/cli/commands/maker.d.ts +12 -0
  127. package/dist/cli/commands/maker.d.ts.map +1 -0
  128. package/dist/cli/commands/maker.js +605 -0
  129. package/dist/cli/commands/maker.js.map +1 -0
  130. package/dist/cli/commands/mcp.d.ts +27 -0
  131. package/dist/cli/commands/mcp.d.ts.map +1 -0
  132. package/dist/cli/commands/mcp.js +390 -0
  133. package/dist/cli/commands/mcp.js.map +1 -0
  134. package/dist/cli/commands/package-app.d.ts +1 -1
  135. package/dist/cli/commands/package-app.d.ts.map +1 -1
  136. package/dist/cli/commands/package-app.js +5 -4
  137. package/dist/cli/commands/package-app.js.map +1 -1
  138. package/dist/cli/commands/package.d.ts +1 -1
  139. package/dist/cli/commands/package.d.ts.map +1 -1
  140. package/dist/cli/commands/package.js +134 -32
  141. package/dist/cli/commands/package.js.map +1 -1
  142. package/dist/cli/commands/run.d.ts +34 -0
  143. package/dist/cli/commands/run.d.ts.map +1 -0
  144. package/dist/cli/commands/run.js +334 -0
  145. package/dist/cli/commands/run.js.map +1 -0
  146. package/dist/cli/commands/search.d.ts +11 -0
  147. package/dist/cli/commands/search.d.ts.map +1 -0
  148. package/dist/cli/commands/search.js +60 -0
  149. package/dist/cli/commands/search.js.map +1 -0
  150. package/dist/cli/commands/serve.d.ts +11 -0
  151. package/dist/cli/commands/serve.d.ts.map +1 -0
  152. package/dist/cli/commands/serve.js +138 -0
  153. package/dist/cli/commands/serve.js.map +1 -0
  154. package/dist/cli/commands/test.d.ts +14 -0
  155. package/dist/cli/commands/test.d.ts.map +1 -0
  156. package/dist/cli/commands/test.js +51 -0
  157. package/dist/cli/commands/test.js.map +1 -0
  158. package/dist/cli/commands/update.d.ts +11 -0
  159. package/dist/cli/commands/update.d.ts.map +1 -0
  160. package/dist/cli/commands/update.js +72 -0
  161. package/dist/cli/commands/update.js.map +1 -0
  162. package/dist/cli/index.d.ts +14 -0
  163. package/dist/cli/index.d.ts.map +1 -0
  164. package/dist/cli/index.js +139 -0
  165. package/dist/cli/index.js.map +1 -0
  166. package/dist/cli-alias.js +2 -2
  167. package/dist/cli-alias.js.map +1 -1
  168. package/dist/cli.d.ts +3 -16
  169. package/dist/cli.d.ts.map +1 -1
  170. package/dist/cli.js +4 -2725
  171. package/dist/cli.js.map +1 -1
  172. package/dist/context-store.d.ts +13 -12
  173. package/dist/context-store.d.ts.map +1 -1
  174. package/dist/context-store.js +47 -23
  175. package/dist/context-store.js.map +1 -1
  176. package/dist/context.d.ts +35 -0
  177. package/dist/context.d.ts.map +1 -0
  178. package/dist/context.js +38 -0
  179. package/dist/context.js.map +1 -0
  180. package/dist/daemon/client.d.ts +25 -13
  181. package/dist/daemon/client.d.ts.map +1 -1
  182. package/dist/daemon/client.js +183 -135
  183. package/dist/daemon/client.js.map +1 -1
  184. package/dist/daemon/manager.d.ts +58 -26
  185. package/dist/daemon/manager.d.ts.map +1 -1
  186. package/dist/daemon/manager.js +348 -157
  187. package/dist/daemon/manager.js.map +1 -1
  188. package/dist/daemon/protocol.d.ts +9 -3
  189. package/dist/daemon/protocol.d.ts.map +1 -1
  190. package/dist/daemon/protocol.js +2 -0
  191. package/dist/daemon/protocol.js.map +1 -1
  192. package/dist/daemon/server.js +850 -200
  193. package/dist/daemon/server.js.map +1 -1
  194. package/dist/daemon/session-manager.d.ts +16 -2
  195. package/dist/daemon/session-manager.d.ts.map +1 -1
  196. package/dist/daemon/session-manager.js +65 -7
  197. package/dist/daemon/session-manager.js.map +1 -1
  198. package/dist/daemon/state-machine.d.ts +22 -0
  199. package/dist/daemon/state-machine.d.ts.map +1 -0
  200. package/dist/daemon/state-machine.js +48 -0
  201. package/dist/daemon/state-machine.js.map +1 -0
  202. package/dist/deploy/cloudflare.d.ts.map +1 -1
  203. package/dist/deploy/cloudflare.js +5 -5
  204. package/dist/deploy/cloudflare.js.map +1 -1
  205. package/dist/loader.d.ts +65 -7
  206. package/dist/loader.d.ts.map +1 -1
  207. package/dist/loader.js +587 -63
  208. package/dist/loader.js.map +1 -1
  209. package/dist/marketplace-manager.d.ts +84 -12
  210. package/dist/marketplace-manager.d.ts.map +1 -1
  211. package/dist/marketplace-manager.js +470 -26
  212. package/dist/marketplace-manager.js.map +1 -1
  213. package/dist/path-resolver.d.ts +3 -1
  214. package/dist/path-resolver.d.ts.map +1 -1
  215. package/dist/path-resolver.js +4 -3
  216. package/dist/path-resolver.js.map +1 -1
  217. package/dist/photon-cli-runner.d.ts +1 -1
  218. package/dist/photon-cli-runner.d.ts.map +1 -1
  219. package/dist/photon-cli-runner.js +34 -44
  220. package/dist/photon-cli-runner.js.map +1 -1
  221. package/dist/photon-doc-extractor.d.ts +1 -0
  222. package/dist/photon-doc-extractor.d.ts.map +1 -1
  223. package/dist/photon-doc-extractor.js +33 -12
  224. package/dist/photon-doc-extractor.js.map +1 -1
  225. package/dist/photons/maker.photon.d.ts.map +1 -1
  226. package/dist/photons/maker.photon.js +4 -4
  227. package/dist/photons/maker.photon.js.map +1 -1
  228. package/dist/photons/maker.photon.ts +4 -3
  229. package/dist/photons/marketplace.photon.d.ts.map +1 -1
  230. package/dist/photons/marketplace.photon.js +10 -27
  231. package/dist/photons/marketplace.photon.js.map +1 -1
  232. package/dist/photons/marketplace.photon.ts +14 -33
  233. package/dist/photons/tunnel.photon.d.ts.map +1 -1
  234. package/dist/photons/tunnel.photon.js +4 -8
  235. package/dist/photons/tunnel.photon.js.map +1 -1
  236. package/dist/photons/tunnel.photon.ts +4 -7
  237. package/dist/serv/session/kv-store.d.ts +1 -1
  238. package/dist/serv/session/kv-store.d.ts.map +1 -1
  239. package/dist/serv/session/store.d.ts.map +1 -1
  240. package/dist/serv/session/store.js +16 -14
  241. package/dist/serv/session/store.js.map +1 -1
  242. package/dist/serv/vault/token-vault.js +1 -1
  243. package/dist/serv/vault/token-vault.js.map +1 -1
  244. package/dist/server.d.ts +34 -12
  245. package/dist/server.d.ts.map +1 -1
  246. package/dist/server.js +364 -313
  247. package/dist/server.js.map +1 -1
  248. package/dist/shared/audit.d.ts +30 -0
  249. package/dist/shared/audit.d.ts.map +1 -0
  250. package/dist/shared/audit.js +89 -0
  251. package/dist/shared/audit.js.map +1 -0
  252. package/dist/shared/cli-sections.d.ts +0 -4
  253. package/dist/shared/cli-sections.d.ts.map +1 -1
  254. package/dist/shared/cli-sections.js +0 -6
  255. package/dist/shared/cli-sections.js.map +1 -1
  256. package/dist/shared/cli-utils.d.ts +2 -56
  257. package/dist/shared/cli-utils.d.ts.map +1 -1
  258. package/dist/shared/cli-utils.js +1 -87
  259. package/dist/shared/cli-utils.js.map +1 -1
  260. package/dist/shared/error-handler.d.ts +6 -72
  261. package/dist/shared/error-handler.d.ts.map +1 -1
  262. package/dist/shared/error-handler.js +22 -213
  263. package/dist/shared/error-handler.js.map +1 -1
  264. package/dist/shared/security.d.ts +0 -9
  265. package/dist/shared/security.d.ts.map +1 -1
  266. package/dist/shared/security.js +0 -30
  267. package/dist/shared/security.js.map +1 -1
  268. package/dist/shared-utils.d.ts +0 -26
  269. package/dist/shared-utils.d.ts.map +1 -1
  270. package/dist/shared-utils.js +0 -44
  271. package/dist/shared-utils.js.map +1 -1
  272. package/dist/shell-completions.d.ts +1 -1
  273. package/dist/shell-completions.d.ts.map +1 -1
  274. package/dist/shell-completions.js +5 -5
  275. package/dist/shell-completions.js.map +1 -1
  276. package/dist/template-manager.d.ts.map +1 -1
  277. package/dist/template-manager.js +14 -1
  278. package/dist/template-manager.js.map +1 -1
  279. package/dist/test-runner.d.ts +0 -12
  280. package/dist/test-runner.d.ts.map +1 -1
  281. package/dist/test-runner.js +4 -39
  282. package/dist/test-runner.js.map +1 -1
  283. package/dist/testing.d.ts +1 -1
  284. package/dist/testing.d.ts.map +1 -1
  285. package/dist/testing.js +2 -2
  286. package/dist/testing.js.map +1 -1
  287. package/dist/version-checker.d.ts +4 -4
  288. package/dist/version-checker.d.ts.map +1 -1
  289. package/dist/version-checker.js +33 -4
  290. package/dist/version-checker.js.map +1 -1
  291. package/dist/watcher.d.ts.map +1 -1
  292. package/dist/watcher.js +14 -12
  293. package/dist/watcher.js.map +1 -1
  294. package/package.json +24 -17
@@ -8,7 +8,7 @@
8
8
  import * as net from 'net';
9
9
  import * as crypto from 'crypto';
10
10
  import * as readline from 'readline';
11
- import { getGlobalSocketPath, restartGlobalDaemon } from './manager.js';
11
+ import { getGlobalSocketPath, ensureDaemon } from './manager.js';
12
12
  import { createLogger } from '../shared/logger.js';
13
13
  import { getErrorMessage } from '../shared/error-handler.js';
14
14
  // Generate session ID for this process
@@ -46,12 +46,12 @@ export async function sendCommand(photonName, method, args, options) {
46
46
  const maxRetries = options?.maxRetries ?? 1;
47
47
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
48
48
  try {
49
- return await sendCommandDirect(photonName, method, args, options?.photonPath, options?.sessionId, options?.instanceName);
49
+ return await sendCommandDirect(photonName, method, args, options?.photonPath, options?.sessionId, options?.instanceName, options?.workingDir, options?.targetInstance, options?.clientType);
50
50
  }
51
51
  catch (error) {
52
52
  if (isDaemonConnectionError(error) && attempt < maxRetries) {
53
- logger.info('Daemon unreachable, auto-restarting...');
54
- await restartGlobalDaemon();
53
+ logger.info(`Daemon unreachable (${photonName}/${method}), retrying...`);
54
+ await ensureDaemon();
55
55
  continue;
56
56
  }
57
57
  throw error;
@@ -61,14 +61,14 @@ export async function sendCommand(photonName, method, args, options) {
61
61
  /**
62
62
  * Send command directly to daemon (no retry logic)
63
63
  */
64
- async function sendCommandDirect(photonName, method, args, photonPath, sessionId, instanceName) {
64
+ async function sendCommandDirect(photonName, method, args, photonPath, sessionId, instanceName, workingDir, targetInstance, clientType) {
65
65
  const socketPath = getGlobalSocketPath();
66
66
  const requestId = `req_${Date.now()}_${Math.random()}`;
67
67
  return new Promise((resolve, reject) => {
68
68
  const client = net.createConnection(socketPath);
69
69
  let buffer = '';
70
70
  let responseReceived = false;
71
- const timeout = setTimeout(() => {
71
+ let currentTimeout = setTimeout(() => {
72
72
  if (!responseReceived) {
73
73
  client.destroy();
74
74
  reject(new Error('Request timeout'));
@@ -81,73 +81,77 @@ async function sendCommandDirect(photonName, method, args, photonPath, sessionId
81
81
  photonName,
82
82
  photonPath,
83
83
  sessionId: sessionId || SESSION_ID,
84
- clientType: 'cli',
84
+ clientType: clientType || 'cli',
85
85
  method,
86
86
  args,
87
87
  instanceName,
88
+ targetInstance,
89
+ workingDir,
88
90
  };
89
91
  client.write(JSON.stringify(request) + '\n');
90
92
  });
91
- client.on('data', async (chunk) => {
92
- buffer += chunk.toString();
93
- // Process complete JSON messages (newline-delimited)
94
- const lines = buffer.split('\n');
95
- buffer = lines.pop() || '';
96
- for (const line of lines) {
97
- if (!line.trim())
98
- continue;
99
- try {
100
- const response = JSON.parse(line);
101
- if (response.id === requestId) {
102
- // Handle prompt request from daemon
103
- if (response.type === 'prompt' && response.prompt) {
104
- // Reset timeout while waiting for user input
105
- clearTimeout(timeout);
106
- // Get user input via readline
107
- const userInput = await promptUser(response.prompt.message, response.prompt.default);
108
- // Send prompt response back to daemon
109
- const promptResponse = {
110
- type: 'prompt_response',
111
- id: requestId,
112
- promptValue: userInput,
113
- };
114
- client.write(JSON.stringify(promptResponse) + '\n');
115
- // Restart timeout for next response
116
- setTimeout(() => {
117
- if (!responseReceived) {
118
- client.destroy();
119
- reject(new Error('Request timeout'));
120
- }
121
- }, 120000);
122
- }
123
- // Handle final result
124
- else if (response.type === 'result') {
125
- responseReceived = true;
126
- clearTimeout(timeout);
127
- client.destroy();
128
- resolve(response.data);
129
- }
130
- // Handle error
131
- else if (response.type === 'error') {
132
- responseReceived = true;
133
- clearTimeout(timeout);
134
- client.destroy();
135
- reject(new Error(response.error || 'Unknown error'));
93
+ client.on('data', (chunk) => {
94
+ void (async () => {
95
+ buffer += chunk.toString();
96
+ // Process complete JSON messages (newline-delimited)
97
+ const lines = buffer.split('\n');
98
+ buffer = lines.pop() || '';
99
+ for (const line of lines) {
100
+ if (!line.trim())
101
+ continue;
102
+ try {
103
+ const response = JSON.parse(line);
104
+ if (response.id === requestId) {
105
+ // Handle prompt request from daemon
106
+ if (response.type === 'prompt' && response.prompt) {
107
+ // Reset timeout while waiting for user input
108
+ clearTimeout(currentTimeout);
109
+ // Get user input via readline
110
+ const userInput = await promptUser(response.prompt.message, response.prompt.default);
111
+ // Send prompt response back to daemon
112
+ const promptResponse = {
113
+ type: 'prompt_response',
114
+ id: requestId,
115
+ promptValue: userInput,
116
+ };
117
+ client.write(JSON.stringify(promptResponse) + '\n');
118
+ // Restart timeout for next response — store handle so it can be cleared
119
+ currentTimeout = setTimeout(() => {
120
+ if (!responseReceived) {
121
+ client.destroy();
122
+ reject(new Error('Request timeout'));
123
+ }
124
+ }, 120000);
125
+ }
126
+ // Handle final result
127
+ else if (response.type === 'result') {
128
+ responseReceived = true;
129
+ clearTimeout(currentTimeout);
130
+ client.destroy();
131
+ resolve(response.data);
132
+ }
133
+ // Handle error
134
+ else if (response.type === 'error') {
135
+ responseReceived = true;
136
+ clearTimeout(currentTimeout);
137
+ client.destroy();
138
+ reject(new Error(response.error || 'Unknown error'));
139
+ }
136
140
  }
137
141
  }
142
+ catch (error) {
143
+ logger.warn('Failed to parse daemon response', { error: getErrorMessage(error) });
144
+ }
138
145
  }
139
- catch (error) {
140
- logger.warn('Failed to parse daemon response', { error: getErrorMessage(error) });
141
- }
142
- }
146
+ })();
143
147
  });
144
148
  client.on('error', (error) => {
145
- clearTimeout(timeout);
149
+ clearTimeout(currentTimeout);
146
150
  client.destroy();
147
151
  reject(new Error(`Connection error: ${getErrorMessage(error)}`));
148
152
  });
149
153
  client.on('end', () => {
150
- clearTimeout(timeout);
154
+ clearTimeout(currentTimeout);
151
155
  client.destroy();
152
156
  if (!responseReceived) {
153
157
  reject(new Error('Connection closed before receiving response'));
@@ -179,6 +183,7 @@ export async function subscribeChannel(photonName, channel, handler, options) {
179
183
  channel,
180
184
  clientType: 'beam',
181
185
  lastEventId: lastSeenEventId,
186
+ workingDir: options?.workingDir,
182
187
  };
183
188
  client.write(JSON.stringify(request) + '\n');
184
189
  });
@@ -228,8 +233,10 @@ export async function subscribeChannel(photonName, channel, handler, options) {
228
233
  reject(new Error(response.error || 'Subscription failed'));
229
234
  }
230
235
  }
231
- catch {
232
- // Ignore parse errors for partial messages
236
+ catch (e) {
237
+ // Lines are fully buffered (split on '\n'), so a parse error here
238
+ // indicates actual protocol corruption — log it.
239
+ logger.warn('Failed to parse daemon channel message', { error: getErrorMessage(e) });
233
240
  }
234
241
  }
235
242
  });
@@ -237,9 +244,9 @@ export async function subscribeChannel(photonName, channel, handler, options) {
237
244
  if (!subscribed) {
238
245
  if (options?.reconnect && !cancelled) {
239
246
  // Initial connection failed — retry (e.g. daemon not running yet)
240
- resolve((() => {
247
+ resolve(() => {
241
248
  cancelled = true;
242
- }));
249
+ });
243
250
  scheduleReconnect();
244
251
  }
245
252
  else {
@@ -253,9 +260,9 @@ export async function subscribeChannel(photonName, channel, handler, options) {
253
260
  client.on('end', () => {
254
261
  if (!subscribed) {
255
262
  if (options?.reconnect && !cancelled) {
256
- resolve((() => {
263
+ resolve(() => {
257
264
  cancelled = true;
258
- }));
265
+ });
259
266
  scheduleReconnect();
260
267
  }
261
268
  else {
@@ -276,27 +283,31 @@ export async function subscribeChannel(photonName, channel, handler, options) {
276
283
  reconnectAttempts++;
277
284
  const delay = Math.min(1000 * Math.pow(2, reconnectAttempts - 1), 30000);
278
285
  if (reconnectAttempts === 1) {
279
- logger.info(`Subscription lost for ${channel}, reconnecting...`);
286
+ logger.debug(`Subscription lost for ${channel}, reconnecting...`);
280
287
  }
281
288
  else {
282
289
  logger.debug(`Reconnecting ${channel} in ${delay}ms (attempt ${reconnectAttempts})`);
283
290
  }
284
- setTimeout(async () => {
285
- if (cancelled)
286
- return;
287
- try {
288
- // Restart daemon if needed
289
- await restartGlobalDaemon();
290
- await new Promise((r) => setTimeout(r, 500));
291
- await connect();
292
- reconnectAttempts = 0;
293
- logger.info(`Reconnected subscription for ${channel}`);
294
- options?.onReconnect?.();
295
- }
296
- catch {
297
- if (!cancelled)
298
- scheduleReconnect();
299
- }
291
+ setTimeout(() => {
292
+ void (async () => {
293
+ if (cancelled)
294
+ return;
295
+ try {
296
+ // Try reconnecting to existing daemon first; only start if it's down
297
+ await ensureDaemon();
298
+ await connect();
299
+ reconnectAttempts = 0;
300
+ logger.debug(`Reconnected subscription for ${channel}`);
301
+ options?.onReconnect?.();
302
+ }
303
+ catch (e) {
304
+ logger.debug(`Reconnect attempt ${reconnectAttempts} failed for ${channel}`, {
305
+ error: getErrorMessage(e),
306
+ });
307
+ if (!cancelled)
308
+ scheduleReconnect();
309
+ }
310
+ })();
300
311
  }, delay);
301
312
  };
302
313
  return connect();
@@ -304,7 +315,7 @@ export async function subscribeChannel(photonName, channel, handler, options) {
304
315
  /**
305
316
  * Publish a message to a channel on a daemon
306
317
  */
307
- export async function publishToChannel(photonName, channel, message) {
318
+ export async function publishToChannel(photonName, channel, message, workingDir) {
308
319
  const socketPath = getGlobalSocketPath();
309
320
  const requestId = `pub_${Date.now()}_${Math.random().toString(36).slice(2)}`;
310
321
  return new Promise((resolve, reject) => {
@@ -320,6 +331,7 @@ export async function publishToChannel(photonName, channel, message) {
320
331
  photonName,
321
332
  channel,
322
333
  message,
334
+ workingDir,
323
335
  };
324
336
  client.write(JSON.stringify(request) + '\n');
325
337
  });
@@ -337,8 +349,8 @@ export async function publishToChannel(photonName, channel, message) {
337
349
  }
338
350
  }
339
351
  }
340
- catch {
341
- // Ignore parse errors
352
+ catch (e) {
353
+ logger.warn('Failed to parse daemon response', { error: getErrorMessage(e) });
342
354
  }
343
355
  });
344
356
  client.on('error', (error) => {
@@ -352,7 +364,7 @@ export async function publishToChannel(photonName, channel, message) {
352
364
  * Acquire a distributed lock
353
365
  * Returns true if lock acquired, false if already held
354
366
  */
355
- export async function acquireLock(photonName, lockName, timeout) {
367
+ export async function acquireLock(photonName, lockName, timeout, workingDir) {
356
368
  const socketPath = getGlobalSocketPath();
357
369
  const requestId = `lock_${Date.now()}_${Math.random().toString(36).slice(2)}`;
358
370
  return new Promise((resolve, reject) => {
@@ -369,6 +381,7 @@ export async function acquireLock(photonName, lockName, timeout) {
369
381
  sessionId: SESSION_ID,
370
382
  lockName,
371
383
  lockTimeout: timeout,
384
+ workingDir,
372
385
  };
373
386
  client.write(JSON.stringify(request) + '\n');
374
387
  });
@@ -386,8 +399,8 @@ export async function acquireLock(photonName, lockName, timeout) {
386
399
  }
387
400
  }
388
401
  }
389
- catch {
390
- // Ignore parse errors
402
+ catch (e) {
403
+ logger.warn('Failed to parse daemon response', { error: getErrorMessage(e) });
391
404
  }
392
405
  });
393
406
  client.on('error', (error) => {
@@ -401,7 +414,7 @@ export async function acquireLock(photonName, lockName, timeout) {
401
414
  * Release a distributed lock
402
415
  * Returns true if lock released, false if not held by this session
403
416
  */
404
- export async function releaseLock(photonName, lockName) {
417
+ export async function releaseLock(photonName, lockName, workingDir) {
405
418
  const socketPath = getGlobalSocketPath();
406
419
  const requestId = `unlock_${Date.now()}_${Math.random().toString(36).slice(2)}`;
407
420
  return new Promise((resolve, reject) => {
@@ -417,6 +430,7 @@ export async function releaseLock(photonName, lockName) {
417
430
  photonName,
418
431
  sessionId: SESSION_ID,
419
432
  lockName,
433
+ workingDir,
420
434
  };
421
435
  client.write(JSON.stringify(request) + '\n');
422
436
  });
@@ -434,8 +448,8 @@ export async function releaseLock(photonName, lockName) {
434
448
  }
435
449
  }
436
450
  }
437
- catch {
438
- // Ignore parse errors
451
+ catch (e) {
452
+ logger.warn('Failed to parse daemon response', { error: getErrorMessage(e) });
439
453
  }
440
454
  });
441
455
  client.on('error', (error) => {
@@ -479,8 +493,8 @@ export async function listLocks(photonName) {
479
493
  }
480
494
  }
481
495
  }
482
- catch {
483
- // Ignore parse errors
496
+ catch (e) {
497
+ logger.warn('Failed to parse daemon response', { error: getErrorMessage(e) });
484
498
  }
485
499
  });
486
500
  client.on('error', (error) => {
@@ -529,8 +543,8 @@ export async function scheduleJob(photonName, jobId, method, cron, args) {
529
543
  }
530
544
  }
531
545
  }
532
- catch {
533
- // Ignore parse errors
546
+ catch (e) {
547
+ logger.warn('Failed to parse daemon response', { error: getErrorMessage(e) });
534
548
  }
535
549
  });
536
550
  client.on('error', (error) => {
@@ -575,8 +589,8 @@ export async function unscheduleJob(photonName, jobId) {
575
589
  }
576
590
  }
577
591
  }
578
- catch {
579
- // Ignore parse errors
592
+ catch (e) {
593
+ logger.warn('Failed to parse daemon response', { error: getErrorMessage(e) });
580
594
  }
581
595
  });
582
596
  client.on('error', (error) => {
@@ -620,8 +634,8 @@ export async function listJobs(photonName) {
620
634
  }
621
635
  }
622
636
  }
623
- catch {
624
- // Ignore parse errors
637
+ catch (e) {
638
+ logger.warn('Failed to parse daemon response', { error: getErrorMessage(e) });
625
639
  }
626
640
  });
627
641
  client.on('error', (error) => {
@@ -632,99 +646,91 @@ export async function listJobs(photonName) {
632
646
  });
633
647
  }
634
648
  /**
635
- * Reload the daemon's photon code without losing state
636
- * Called by dev server after hot-reload to sync daemon
649
+ * Ping daemon to check if it's responsive
637
650
  */
638
- export async function reloadDaemon(photonName, photonPath) {
651
+ export async function pingDaemon(photonName) {
639
652
  const socketPath = getGlobalSocketPath();
640
- const requestId = `reload_${Date.now()}_${Math.random().toString(36).slice(2)}`;
641
- return new Promise((resolve, reject) => {
653
+ const requestId = `ping_${Date.now()}`;
654
+ return new Promise((resolve) => {
642
655
  const client = net.createConnection(socketPath);
643
656
  const timeout = setTimeout(() => {
644
657
  client.destroy();
645
- reject(new Error('Reload request timeout'));
646
- }, 30000); // 30 second timeout for reload
658
+ resolve(false);
659
+ }, 5000);
647
660
  client.on('connect', () => {
648
661
  const request = {
649
- type: 'reload',
662
+ type: 'ping',
650
663
  id: requestId,
651
664
  photonName,
652
- photonPath,
653
665
  };
654
666
  client.write(JSON.stringify(request) + '\n');
655
667
  });
656
668
  client.on('data', (chunk) => {
657
669
  try {
658
670
  const response = JSON.parse(chunk.toString().trim());
659
- if (response.id === requestId) {
671
+ if (response.id === requestId && response.type === 'pong') {
660
672
  clearTimeout(timeout);
661
673
  client.destroy();
662
- if (response.type === 'result') {
663
- const data = response.data;
664
- resolve({
665
- success: response.success ?? true,
666
- error: data?.error,
667
- sessionsUpdated: data?.sessionsUpdated,
668
- });
669
- }
670
- else {
671
- resolve({ success: false, error: response.error || 'Reload failed' });
672
- }
674
+ resolve(true);
673
675
  }
674
676
  }
675
- catch {
677
+ catch (error) {
676
678
  // Ignore parse errors
677
679
  }
678
680
  });
679
- client.on('error', (error) => {
681
+ client.on('error', () => {
680
682
  clearTimeout(timeout);
681
683
  client.destroy();
682
- reject(new Error(`Connection error: ${getErrorMessage(error)}`));
684
+ resolve(false);
685
+ });
686
+ client.on('end', () => {
687
+ clearTimeout(timeout);
688
+ client.destroy();
689
+ resolve(false);
683
690
  });
684
691
  });
685
692
  }
686
693
  /**
687
- * Ping daemon to check if it's responsive
694
+ * Query daemon health status (uptime, memory, sessions, etc.)
688
695
  */
689
- export async function pingDaemon(photonName) {
696
+ export async function queryDaemonStatus() {
690
697
  const socketPath = getGlobalSocketPath();
691
- const requestId = `ping_${Date.now()}`;
698
+ const requestId = `status_${Date.now()}`;
692
699
  return new Promise((resolve) => {
693
700
  const client = net.createConnection(socketPath);
694
701
  const timeout = setTimeout(() => {
695
702
  client.destroy();
696
- resolve(false);
703
+ resolve(null);
697
704
  }, 5000);
698
705
  client.on('connect', () => {
699
706
  const request = {
700
- type: 'ping',
707
+ type: 'status',
701
708
  id: requestId,
702
- photonName,
703
709
  };
704
710
  client.write(JSON.stringify(request) + '\n');
705
711
  });
706
712
  client.on('data', (chunk) => {
707
713
  try {
708
714
  const response = JSON.parse(chunk.toString().trim());
709
- if (response.id === requestId && response.type === 'pong') {
715
+ if (response.id === requestId && response.type === 'result' && response.data) {
710
716
  clearTimeout(timeout);
711
717
  client.destroy();
712
- resolve(true);
718
+ resolve(response.data);
713
719
  }
714
720
  }
715
- catch (error) {
721
+ catch {
716
722
  // Ignore parse errors
717
723
  }
718
724
  });
719
725
  client.on('error', () => {
720
726
  clearTimeout(timeout);
721
727
  client.destroy();
722
- resolve(false);
728
+ resolve(null);
723
729
  });
724
730
  client.on('end', () => {
725
731
  clearTimeout(timeout);
726
732
  client.destroy();
727
- resolve(false);
733
+ resolve(null);
728
734
  });
729
735
  });
730
736
  }
@@ -732,6 +738,48 @@ export async function pingDaemon(photonName) {
732
738
  * Get events since a specific timestamp for a channel
733
739
  * Used for explicit delta sync when client has missed events
734
740
  */
741
+ /**
742
+ * Clear cached instances for a photon in a given workingDir.
743
+ * Called by Beam when starting with a fresh workingDir to avoid stale in-memory state.
744
+ */
745
+ export async function clearInstances(photonName, workingDir) {
746
+ const socketPath = getGlobalSocketPath();
747
+ const requestId = `clearinst_${Date.now()}_${Math.random().toString(36).slice(2)}`;
748
+ return new Promise((resolve) => {
749
+ const client = net.createConnection(socketPath);
750
+ const timeout = setTimeout(() => {
751
+ client.destroy();
752
+ resolve(false);
753
+ }, 5000);
754
+ client.on('connect', () => {
755
+ const request = {
756
+ type: 'clear_instances',
757
+ id: requestId,
758
+ photonName,
759
+ workingDir,
760
+ };
761
+ client.write(JSON.stringify(request) + '\n');
762
+ });
763
+ client.on('data', (chunk) => {
764
+ try {
765
+ const response = JSON.parse(chunk.toString().trim());
766
+ if (response.id === requestId) {
767
+ clearTimeout(timeout);
768
+ client.destroy();
769
+ resolve(response.success ?? true);
770
+ }
771
+ }
772
+ catch (e) {
773
+ logger.warn('Failed to parse daemon response', { error: getErrorMessage(e) });
774
+ }
775
+ });
776
+ client.on('error', () => {
777
+ clearTimeout(timeout);
778
+ client.destroy();
779
+ resolve(false); // Daemon not running — nothing to clear
780
+ });
781
+ });
782
+ }
735
783
  export async function getEventsSince(photonName, channel, lastEventId) {
736
784
  const socketPath = getGlobalSocketPath();
737
785
  const requestId = `getevents_${Date.now()}_${Math.random().toString(36).slice(2)}`;
@@ -769,8 +817,8 @@ export async function getEventsSince(photonName, channel, lastEventId) {
769
817
  }
770
818
  }
771
819
  }
772
- catch {
773
- // Ignore parse errors
820
+ catch (e) {
821
+ logger.warn('Failed to parse daemon response', { error: getErrorMessage(e) });
774
822
  }
775
823
  });
776
824
  client.on('error', (error) => {