@portel/photon 1.18.0 → 1.20.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 (238) hide show
  1. package/dist/auto-ui/beam/routes/api-browse.d.ts.map +1 -1
  2. package/dist/auto-ui/beam/routes/api-browse.js +16 -4
  3. package/dist/auto-ui/beam/routes/api-browse.js.map +1 -1
  4. package/dist/auto-ui/beam/routes/api-config.js +4 -4
  5. package/dist/auto-ui/beam/routes/api-config.js.map +1 -1
  6. package/dist/auto-ui/beam/routes/api-marketplace.d.ts.map +1 -1
  7. package/dist/auto-ui/beam/routes/api-marketplace.js +14 -1
  8. package/dist/auto-ui/beam/routes/api-marketplace.js.map +1 -1
  9. package/dist/auto-ui/beam.d.ts.map +1 -1
  10. package/dist/auto-ui/beam.js +196 -77
  11. package/dist/auto-ui/beam.js.map +1 -1
  12. package/dist/auto-ui/bridge/index.d.ts.map +1 -1
  13. package/dist/auto-ui/bridge/index.js +17 -0
  14. package/dist/auto-ui/bridge/index.js.map +1 -1
  15. package/dist/auto-ui/streamable-http-transport.d.ts +1 -0
  16. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  17. package/dist/auto-ui/streamable-http-transport.js +64 -16
  18. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  19. package/dist/auto-ui/types.d.ts +12 -0
  20. package/dist/auto-ui/types.d.ts.map +1 -1
  21. package/dist/auto-ui/types.js.map +1 -1
  22. package/dist/beam-form.bundle.js +49 -6
  23. package/dist/beam-form.bundle.js.map +2 -2
  24. package/dist/beam.bundle.js +2090 -512
  25. package/dist/beam.bundle.js.map +4 -4
  26. package/dist/capability-negotiator.d.ts +67 -0
  27. package/dist/capability-negotiator.d.ts.map +1 -0
  28. package/dist/capability-negotiator.js +104 -0
  29. package/dist/capability-negotiator.js.map +1 -0
  30. package/dist/channel-manager.d.ts +122 -0
  31. package/dist/channel-manager.d.ts.map +1 -0
  32. package/dist/channel-manager.js +266 -0
  33. package/dist/channel-manager.js.map +1 -0
  34. package/dist/claude-code-plugin.js +1 -1
  35. package/dist/cli/commands/beam.d.ts.map +1 -1
  36. package/dist/cli/commands/beam.js +8 -2
  37. package/dist/cli/commands/beam.js.map +1 -1
  38. package/dist/cli/commands/changelog.d.ts +9 -0
  39. package/dist/cli/commands/changelog.d.ts.map +1 -0
  40. package/dist/cli/commands/changelog.js +133 -0
  41. package/dist/cli/commands/changelog.js.map +1 -0
  42. package/dist/cli/commands/maker.d.ts.map +1 -1
  43. package/dist/cli/commands/maker.js +23 -2
  44. package/dist/cli/commands/maker.js.map +1 -1
  45. package/dist/cli/commands/mcp.d.ts.map +1 -1
  46. package/dist/cli/commands/mcp.js +53 -0
  47. package/dist/cli/commands/mcp.js.map +1 -1
  48. package/dist/cli/commands/package.d.ts.map +1 -1
  49. package/dist/cli/commands/package.js +43 -9
  50. package/dist/cli/commands/package.js.map +1 -1
  51. package/dist/cli/commands/run.d.ts.map +1 -1
  52. package/dist/cli/commands/run.js +1 -0
  53. package/dist/cli/commands/run.js.map +1 -1
  54. package/dist/cli/commands/update.d.ts +3 -2
  55. package/dist/cli/commands/update.d.ts.map +1 -1
  56. package/dist/cli/commands/update.js +50 -43
  57. package/dist/cli/commands/update.js.map +1 -1
  58. package/dist/cli/index.d.ts.map +1 -1
  59. package/dist/cli/index.js +16 -2
  60. package/dist/cli/index.js.map +1 -1
  61. package/dist/cli-alias.js +1 -1
  62. package/dist/cli-alias.js.map +1 -1
  63. package/dist/context-store.d.ts +23 -33
  64. package/dist/context-store.d.ts.map +1 -1
  65. package/dist/context-store.js +147 -97
  66. package/dist/context-store.js.map +1 -1
  67. package/dist/context.d.ts +15 -10
  68. package/dist/context.d.ts.map +1 -1
  69. package/dist/context.js +37 -13
  70. package/dist/context.js.map +1 -1
  71. package/dist/daemon/client.d.ts.map +1 -1
  72. package/dist/daemon/client.js +12 -0
  73. package/dist/daemon/client.js.map +1 -1
  74. package/dist/daemon/server.js +34 -51
  75. package/dist/daemon/server.js.map +1 -1
  76. package/dist/daemon/worker-manager.d.ts.map +1 -1
  77. package/dist/daemon/worker-manager.js +21 -7
  78. package/dist/daemon/worker-manager.js.map +1 -1
  79. package/dist/data-migration.d.ts +27 -0
  80. package/dist/data-migration.d.ts.map +1 -0
  81. package/dist/data-migration.js +307 -0
  82. package/dist/data-migration.js.map +1 -0
  83. package/dist/editor-support/docblock-tag-catalog.d.ts.map +1 -1
  84. package/dist/editor-support/docblock-tag-catalog.js +6 -0
  85. package/dist/editor-support/docblock-tag-catalog.js.map +1 -1
  86. package/dist/loader.d.ts +13 -0
  87. package/dist/loader.d.ts.map +1 -1
  88. package/dist/loader.js +169 -22
  89. package/dist/loader.js.map +1 -1
  90. package/dist/marketplace-manager.d.ts +6 -0
  91. package/dist/marketplace-manager.d.ts.map +1 -1
  92. package/dist/marketplace-manager.js +185 -62
  93. package/dist/marketplace-manager.js.map +1 -1
  94. package/dist/namespace-migration.d.ts +1 -0
  95. package/dist/namespace-migration.d.ts.map +1 -1
  96. package/dist/namespace-migration.js +86 -0
  97. package/dist/namespace-migration.js.map +1 -1
  98. package/dist/photon-cli-runner.d.ts.map +1 -1
  99. package/dist/photon-cli-runner.js +47 -21
  100. package/dist/photon-cli-runner.js.map +1 -1
  101. package/dist/photon-doc-extractor.d.ts +1 -0
  102. package/dist/photon-doc-extractor.d.ts.map +1 -1
  103. package/dist/photon-doc-extractor.js +6 -0
  104. package/dist/photon-doc-extractor.js.map +1 -1
  105. package/dist/readme-syncer.d.ts.map +1 -1
  106. package/dist/readme-syncer.js +6 -1
  107. package/dist/readme-syncer.js.map +1 -1
  108. package/dist/resource-server.d.ts +105 -0
  109. package/dist/resource-server.d.ts.map +1 -0
  110. package/dist/resource-server.js +723 -0
  111. package/dist/resource-server.js.map +1 -0
  112. package/dist/serv/auth/jwt.d.ts +2 -0
  113. package/dist/serv/auth/jwt.d.ts.map +1 -1
  114. package/dist/serv/auth/jwt.js +11 -5
  115. package/dist/serv/auth/jwt.js.map +1 -1
  116. package/dist/serv/vault/token-vault.d.ts +2 -0
  117. package/dist/serv/vault/token-vault.d.ts.map +1 -1
  118. package/dist/serv/vault/token-vault.js +6 -0
  119. package/dist/serv/vault/token-vault.js.map +1 -1
  120. package/dist/server.d.ts +30 -119
  121. package/dist/server.d.ts.map +1 -1
  122. package/dist/server.js +252 -1122
  123. package/dist/server.js.map +1 -1
  124. package/dist/shared/audit.d.ts.map +1 -1
  125. package/dist/shared/audit.js +11 -4
  126. package/dist/shared/audit.js.map +1 -1
  127. package/dist/shared/security.d.ts +10 -0
  128. package/dist/shared/security.d.ts.map +1 -1
  129. package/dist/shared/security.js +27 -0
  130. package/dist/shared/security.js.map +1 -1
  131. package/dist/task-executor.d.ts +69 -0
  132. package/dist/task-executor.d.ts.map +1 -0
  133. package/dist/task-executor.js +182 -0
  134. package/dist/task-executor.js.map +1 -0
  135. package/dist/tasks/store.d.ts.map +1 -1
  136. package/dist/tasks/store.js +6 -2
  137. package/dist/tasks/store.js.map +1 -1
  138. package/dist/types/photon-instance.d.ts +50 -0
  139. package/dist/types/photon-instance.d.ts.map +1 -0
  140. package/dist/types/photon-instance.js +9 -0
  141. package/dist/types/photon-instance.js.map +1 -0
  142. package/dist/types/server-types.d.ts +61 -0
  143. package/dist/types/server-types.d.ts.map +1 -0
  144. package/dist/types/server-types.js +8 -0
  145. package/dist/types/server-types.js.map +1 -0
  146. package/dist/version-notify.d.ts +27 -0
  147. package/dist/version-notify.d.ts.map +1 -0
  148. package/dist/version-notify.js +142 -0
  149. package/dist/version-notify.js.map +1 -0
  150. package/package.json +3 -3
  151. package/dist/auto-ui/bridge/openai-shim.d.ts +0 -20
  152. package/dist/auto-ui/bridge/openai-shim.d.ts.map +0 -1
  153. package/dist/auto-ui/bridge/openai-shim.js +0 -231
  154. package/dist/auto-ui/bridge/openai-shim.js.map +0 -1
  155. package/dist/auto-ui/bridge/photon-app.d.ts +0 -162
  156. package/dist/auto-ui/bridge/photon-app.d.ts.map +0 -1
  157. package/dist/auto-ui/bridge/photon-app.js +0 -460
  158. package/dist/auto-ui/bridge/photon-app.js.map +0 -1
  159. package/dist/auto-ui/daemon-tools.d.ts +0 -45
  160. package/dist/auto-ui/daemon-tools.d.ts.map +0 -1
  161. package/dist/auto-ui/daemon-tools.js +0 -581
  162. package/dist/auto-ui/daemon-tools.js.map +0 -1
  163. package/dist/auto-ui/design-system/index.d.ts +0 -21
  164. package/dist/auto-ui/design-system/index.d.ts.map +0 -1
  165. package/dist/auto-ui/design-system/index.js +0 -27
  166. package/dist/auto-ui/design-system/index.js.map +0 -1
  167. package/dist/auto-ui/design-system/transaction-ui.d.ts +0 -70
  168. package/dist/auto-ui/design-system/transaction-ui.d.ts.map +0 -1
  169. package/dist/auto-ui/design-system/transaction-ui.js +0 -982
  170. package/dist/auto-ui/design-system/transaction-ui.js.map +0 -1
  171. package/dist/auto-ui/playground-server.d.ts +0 -7
  172. package/dist/auto-ui/playground-server.d.ts.map +0 -1
  173. package/dist/auto-ui/playground-server.js +0 -840
  174. package/dist/auto-ui/playground-server.js.map +0 -1
  175. package/dist/auto-ui/rendering/components.d.ts +0 -29
  176. package/dist/auto-ui/rendering/components.d.ts.map +0 -1
  177. package/dist/auto-ui/rendering/components.js +0 -1341
  178. package/dist/auto-ui/rendering/components.js.map +0 -1
  179. package/dist/auto-ui/rendering/field-analyzer.d.ts +0 -104
  180. package/dist/auto-ui/rendering/field-analyzer.d.ts.map +0 -1
  181. package/dist/auto-ui/rendering/field-analyzer.js +0 -447
  182. package/dist/auto-ui/rendering/field-analyzer.js.map +0 -1
  183. package/dist/auto-ui/rendering/field-renderers.d.ts +0 -64
  184. package/dist/auto-ui/rendering/field-renderers.d.ts.map +0 -1
  185. package/dist/auto-ui/rendering/field-renderers.js +0 -317
  186. package/dist/auto-ui/rendering/field-renderers.js.map +0 -1
  187. package/dist/auto-ui/rendering/index.d.ts +0 -28
  188. package/dist/auto-ui/rendering/index.d.ts.map +0 -1
  189. package/dist/auto-ui/rendering/index.js +0 -60
  190. package/dist/auto-ui/rendering/index.js.map +0 -1
  191. package/dist/auto-ui/rendering/layout-selector.d.ts +0 -60
  192. package/dist/auto-ui/rendering/layout-selector.d.ts.map +0 -1
  193. package/dist/auto-ui/rendering/layout-selector.js +0 -476
  194. package/dist/auto-ui/rendering/layout-selector.js.map +0 -1
  195. package/dist/markdown-utils.d.ts +0 -8
  196. package/dist/markdown-utils.d.ts.map +0 -1
  197. package/dist/markdown-utils.js +0 -64
  198. package/dist/markdown-utils.js.map +0 -1
  199. package/dist/mcp-client.d.ts +0 -9
  200. package/dist/mcp-client.d.ts.map +0 -1
  201. package/dist/mcp-client.js +0 -11
  202. package/dist/mcp-client.js.map +0 -1
  203. package/dist/mcp-elicitation.d.ts +0 -32
  204. package/dist/mcp-elicitation.d.ts.map +0 -1
  205. package/dist/mcp-elicitation.js +0 -26
  206. package/dist/mcp-elicitation.js.map +0 -1
  207. package/dist/photons/builder-compass.photon.d.ts +0 -167
  208. package/dist/photons/builder-compass.photon.d.ts.map +0 -1
  209. package/dist/photons/builder-compass.photon.js +0 -816
  210. package/dist/photons/builder-compass.photon.js.map +0 -1
  211. package/dist/photons/builder-compass.photon.ts +0 -1129
  212. package/dist/photons/docs/ui/docs.html +0 -441
  213. package/dist/photons/docs.photon.d.ts +0 -237
  214. package/dist/photons/docs.photon.d.ts.map +0 -1
  215. package/dist/photons/docs.photon.js +0 -483
  216. package/dist/photons/docs.photon.js.map +0 -1
  217. package/dist/photons/docs.photon.ts +0 -536
  218. package/dist/photons/slides.photon.d.ts +0 -212
  219. package/dist/photons/slides.photon.d.ts.map +0 -1
  220. package/dist/photons/slides.photon.js +0 -355
  221. package/dist/photons/slides.photon.js.map +0 -1
  222. package/dist/photons/slides.photon.ts +0 -370
  223. package/dist/photons/spreadsheet/ui/spreadsheet.html +0 -779
  224. package/dist/photons/spreadsheet.photon.d.ts +0 -554
  225. package/dist/photons/spreadsheet.photon.d.ts.map +0 -1
  226. package/dist/photons/spreadsheet.photon.js +0 -1050
  227. package/dist/photons/spreadsheet.photon.js.map +0 -1
  228. package/dist/photons/spreadsheet.photon.ts +0 -1239
  229. package/dist/photons/ui/builder-compass.html +0 -1199
  230. package/dist/photons/ui/builder-compass.photon.html +0 -380
  231. package/dist/security-scanner.d.ts +0 -52
  232. package/dist/security-scanner.d.ts.map +0 -1
  233. package/dist/security-scanner.js +0 -181
  234. package/dist/security-scanner.js.map +0 -1
  235. package/dist/shared/performance.d.ts +0 -65
  236. package/dist/shared/performance.d.ts.map +0 -1
  237. package/dist/shared/performance.js +0 -136
  238. package/dist/shared/performance.js.map +0 -1
@@ -14,7 +14,7 @@ import * as path from 'path';
14
14
  import * as os from 'os';
15
15
  import { fileURLToPath } from 'url';
16
16
  import { createHash } from 'crypto';
17
- import { setSecurityHeaders, SimpleRateLimiter, escapeHtml } from '../shared/security.js';
17
+ import { setSecurityHeaders, SimpleRateLimiter, escapeHtml, getCorsOrigin, } from '../shared/security.js';
18
18
  /**
19
19
  * Check if shell integration has been installed (photon init cli).
20
20
  * Cached at module load since it won't change during a Beam session.
@@ -100,7 +100,7 @@ import { ensurePhotonEditorDeclaration, writePhotonEditorDeclaration, } from '..
100
100
  import { ensureDaemon } from '../daemon/manager.js';
101
101
  import { SchemaExtractor } from '@portel/photon-core';
102
102
  import { generateServerCard } from '../server-card.js';
103
- import { handleStreamableHTTP, broadcastNotification, broadcastToBeam, } from './streamable-http-transport.js';
103
+ import { handleStreamableHTTP, broadcastNotification, broadcastToBeam, stopSessionCleanup, } from './streamable-http-transport.js';
104
104
  import { getBundledPhotonPath, BEAM_BUNDLED_PHOTONS } from '../shared-utils.js';
105
105
  // BUNDLED_PHOTONS and getBundledPhotonPath are imported from shared-utils.js
106
106
  // Extracted modules (Phase 5)
@@ -116,14 +116,40 @@ import { configurePhotonViaMCP, reloadPhotonViaMCP, removePhotonViaMCP, updateMe
116
116
  import { generateAgentCard } from '../a2a/card-generator.js';
117
117
  // Delegate to extracted module
118
118
  const getConfigFilePath = getConfigFilePathFromModule;
119
- // Module-level state for external MCPs (shared with transport handler)
120
- const externalMCPs = [];
121
- const externalMCPClients = new Map();
122
- const externalMCPSDKClients = new Map();
119
+ // ═══════════════════════════════════════════════════════════════════════════════
120
+ // BEAM CONTEXT — all module-level mutable state lives here
121
+ // ═══════════════════════════════════════════════════════════════════════════════
122
+ class BeamContext {
123
+ /** External MCP server metadata */
124
+ externalMCPs = [];
125
+ /** Transport-level clients for external MCPs */
126
+ externalMCPClients = new Map();
127
+ /** SDK Client instances for tool calls with structuredContent */
128
+ externalMCPSDKClients = new Map();
129
+ /**
130
+ * Notification subscriptions per photon.
131
+ * Key: photon name, Value: list of event types this photon cares about
132
+ * Example: { "chat": ["mentions", "direct-messages"], "tasks": ["deadline", "assigned-to-me"] }
133
+ */
134
+ photonNotificationSubscriptions = new Map();
135
+ /**
136
+ * Track which state-changed channels we've already subscribed to,
137
+ * so dynamically discovered photons can be subscribed without duplicates.
138
+ */
139
+ subscribedStateChannels = new Set();
140
+ /** Convenience accessor matching the shape expected by external-mcp module */
141
+ get externalMCPState() {
142
+ return {
143
+ externalMCPs: this.externalMCPs,
144
+ externalMCPClients: this.externalMCPClients,
145
+ externalMCPSDKClients: this.externalMCPSDKClients,
146
+ };
147
+ }
148
+ }
149
+ const ctx = new BeamContext();
123
150
  // Delegates — external MCP management now in beam/external-mcp.ts
124
- const externalMCPState = { externalMCPs, externalMCPClients, externalMCPSDKClients };
125
- const loadExternalMCPs = (config) => loadExternalMCPsFromModule(config, externalMCPState);
126
- const reconnectExternalMCP = (name) => reconnectExternalMCPFromModule(name, externalMCPState);
151
+ const loadExternalMCPs = (config) => loadExternalMCPsFromModule(config, ctx.externalMCPState);
152
+ const reconnectExternalMCP = (name) => reconnectExternalMCPFromModule(name, ctx.externalMCPState);
127
153
  // Delegates to extracted config module
128
154
  const migrateConfig = migrateConfigFromModule;
129
155
  const loadConfig = loadConfigFromModule;
@@ -135,19 +161,8 @@ const extractClassMetadataFromSource = extractClassMetadataFromModule;
135
161
  const applyMethodVisibility = applyMethodVisibilityFromModule;
136
162
  const extractCspFromSource = extractCspFromModule;
137
163
  // ═══════════════════════════════════════════════════════════════════════════════
138
- // NOTIFICATION SUBSCRIPTIONS
164
+ // NOTIFICATION SUBSCRIPTIONS (state lives in ctx: BeamContext)
139
165
  // ═══════════════════════════════════════════════════════════════════════════════
140
- /**
141
- * Map to store notification subscriptions per photon
142
- * Key: photon name, Value: list of event types this photon cares about
143
- * Example: { "chat": ["mentions", "direct-messages"], "tasks": ["deadline", "assigned-to-me"] }
144
- */
145
- const photonNotificationSubscriptions = new Map();
146
- /**
147
- * Track which state-changed channels we've already subscribed to,
148
- * so dynamically discovered photons can be subscribed without duplicates.
149
- */
150
- const subscribedStateChannels = new Set();
151
166
  /**
152
167
  * Generate the service worker JS that validates the Beam backend
153
168
  * on PWA launch and shows a diagnostic page if something is wrong.
@@ -460,6 +475,16 @@ const BOOT_PAGE = \`<!DOCTYPE html>
460
475
  export async function startBeam(rawWorkingDir, port) {
461
476
  const workingDir = path.resolve(rawWorkingDir);
462
477
  const { PHOTON_VERSION } = await import('../version.js');
478
+ // Run startup migrations (fast no-op when already applied)
479
+ try {
480
+ const { runNamespaceMigration } = await import('../namespace-migration.js');
481
+ await runNamespaceMigration();
482
+ const { runDataMigration } = await import('../data-migration.js');
483
+ await runDataMigration();
484
+ }
485
+ catch {
486
+ // Non-critical
487
+ }
463
488
  // StartupSequencer manages ordered output during startup
464
489
  const startup = new StartupSequencer(PHOTON_VERSION, workingDir);
465
490
  const isTTY = process.stderr.isTTY;
@@ -513,7 +538,7 @@ export async function startBeam(rawWorkingDir, port) {
513
538
  }
514
539
  // Build photon list with short names plus a numeric suffix for duplicates.
515
540
  // Also track resolved paths from namespace scan.
516
- const namespacePaths = new Map(); // displayName → filePath
541
+ const photonRouteMeta = new Map(); // displayName → route metadata
517
542
  const userPhotonList = [];
518
543
  const duplicateIndex = new Map();
519
544
  for (const p of userPhotonListDetailed) {
@@ -522,7 +547,12 @@ export async function startBeam(rawWorkingDir, port) {
522
547
  duplicateIndex.set(p.name, nextIndex);
523
548
  const displayName = duplicateCount > 1 ? `${p.name} (${nextIndex})` : p.name;
524
549
  userPhotonList.push(displayName);
525
- namespacePaths.set(displayName, p.filePath);
550
+ photonRouteMeta.set(displayName, {
551
+ filePath: p.filePath,
552
+ shortName: p.name,
553
+ namespace: p.namespace || undefined,
554
+ qualifiedName: p.qualifiedName || undefined,
555
+ });
526
556
  }
527
557
  // Add bundled photons with their paths
528
558
  const bundledPhotonPaths = new Map();
@@ -562,10 +592,11 @@ export async function startBeam(rawWorkingDir, port) {
562
592
  // Helper: load a single photon, returning the info to push into photons[]
563
593
  async function loadSinglePhoton(name) {
564
594
  const photonPath = bundledPhotonPaths.get(name) ||
565
- namespacePaths.get(name) ||
595
+ photonRouteMeta.get(name)?.filePath ||
566
596
  (await resolvePhotonPath(name, workingDir));
567
597
  if (!photonPath)
568
598
  return null;
599
+ const routeMeta = photonRouteMeta.get(name);
569
600
  // Apply saved config to environment before loading
570
601
  if (savedConfig.photons[name]) {
571
602
  for (const [key, value] of Object.entries(savedConfig.photons[name])) {
@@ -580,7 +611,9 @@ export async function startBeam(rawWorkingDir, port) {
580
611
  let isInternal;
581
612
  try {
582
613
  source = await readText(photonPath);
583
- await ensurePhotonEditorDeclaration(photonPath, source, workingDir).catch(() => { });
614
+ await ensurePhotonEditorDeclaration(photonPath, source, workingDir).catch((e) => {
615
+ logger.debug(`Failed to ensure editor declaration for ${photonPath}: ${e?.message || e}`);
616
+ });
584
617
  }
585
618
  catch {
586
619
  // Can't read source
@@ -662,11 +695,11 @@ export async function startBeam(rawWorkingDir, port) {
662
695
  mcp.schemas = schemas;
663
696
  // Store notification subscriptions per photon
664
697
  if (metadata.notificationSubscriptions?.watchFor) {
665
- photonNotificationSubscriptions.set(name, metadata.notificationSubscriptions.watchFor);
698
+ ctx.photonNotificationSubscriptions.set(name, metadata.notificationSubscriptions.watchFor);
666
699
  }
667
700
  else {
668
701
  // Clear previous subscription if photon no longer has @notify-on
669
- photonNotificationSubscriptions.delete(name);
702
+ ctx.photonNotificationSubscriptions.delete(name);
670
703
  }
671
704
  // Get UI assets for linking
672
705
  const uiAssets = mcp.assets?.ui || [];
@@ -804,6 +837,9 @@ export async function startBeam(rawWorkingDir, port) {
804
837
  return {
805
838
  id: generatePhotonId(photonPath),
806
839
  name,
840
+ ...(routeMeta?.shortName ? { shortName: routeMeta.shortName } : {}),
841
+ ...(routeMeta?.namespace ? { namespace: routeMeta.namespace } : {}),
842
+ ...(routeMeta?.qualifiedName ? { qualifiedName: routeMeta.qualifiedName } : {}),
807
843
  path: photonPath,
808
844
  configured: true,
809
845
  methods,
@@ -834,6 +870,9 @@ export async function startBeam(rawWorkingDir, port) {
834
870
  return {
835
871
  id: generatePhotonId(photonPath),
836
872
  name,
873
+ ...(routeMeta?.shortName ? { shortName: routeMeta.shortName } : {}),
874
+ ...(routeMeta?.namespace ? { namespace: routeMeta.namespace } : {}),
875
+ ...(routeMeta?.qualifiedName ? { qualifiedName: routeMeta.qualifiedName } : {}),
837
876
  path: photonPath,
838
877
  configured: false,
839
878
  label: prettifyName(name),
@@ -859,6 +898,7 @@ export async function startBeam(rawWorkingDir, port) {
859
898
  if (!photon || !photon.configured)
860
899
  return null;
861
900
  const photonDir = path.dirname(photon.path);
901
+ const photonBaseName = path.basename(photon.path, '.photon.ts');
862
902
  const asset = photon.assets?.ui?.find((u) => u.id === uiId);
863
903
  let uiPath;
864
904
  if (asset?.resolvedPath) {
@@ -866,8 +906,8 @@ export async function startBeam(rawWorkingDir, port) {
866
906
  }
867
907
  else {
868
908
  // Prefer .photon.html, then .photon.md, fall back to .html
869
- const photonHtmlPath = path.join(photonDir, photonName, 'ui', `${uiId}.photon.html`);
870
- const photonMdPath = path.join(photonDir, photonName, 'ui', `${uiId}.photon.md`);
909
+ const photonHtmlPath = path.join(photonDir, photonBaseName, 'ui', `${uiId}.photon.html`);
910
+ const photonMdPath = path.join(photonDir, photonBaseName, 'ui', `${uiId}.photon.md`);
871
911
  try {
872
912
  await fs.access(photonHtmlPath);
873
913
  uiPath = photonHtmlPath;
@@ -878,7 +918,7 @@ export async function startBeam(rawWorkingDir, port) {
878
918
  uiPath = photonMdPath;
879
919
  }
880
920
  catch {
881
- uiPath = path.join(photonDir, photonName, 'ui', `${uiId}.html`);
921
+ uiPath = path.join(photonDir, photonBaseName, 'ui', `${uiId}.html`);
882
922
  }
883
923
  }
884
924
  }
@@ -894,7 +934,7 @@ export async function startBeam(rawWorkingDir, port) {
894
934
  // Convention: format-<name> maps to assets/formats/<name>.html
895
935
  if (uiId.startsWith('format-')) {
896
936
  const formatName = uiId.slice('format-'.length);
897
- const formatPath = path.join(photonDir, photonName, 'assets', 'formats', `${formatName}.html`);
937
+ const formatPath = path.join(photonDir, photonBaseName, 'assets', 'formats', `${formatName}.html`);
898
938
  try {
899
939
  const content = await readText(formatPath);
900
940
  return { content, isPhotonTemplate: false };
@@ -931,9 +971,9 @@ export async function startBeam(rawWorkingDir, port) {
931
971
  savedConfig,
932
972
  photons,
933
973
  photonMCPs,
934
- externalMCPs,
935
- externalMCPClients,
936
- externalMCPSDKClients,
974
+ externalMCPs: ctx.externalMCPs,
975
+ externalMCPClients: ctx.externalMCPClients,
976
+ externalMCPSDKClients: ctx.externalMCPSDKClients,
937
977
  channelSubscriptions: new Map(),
938
978
  channelEventBuffers: new Map(),
939
979
  sessionViewState: new Map(),
@@ -983,10 +1023,11 @@ export async function startBeam(rawWorkingDir, port) {
983
1023
  baseUrl: `http://${req.headers.host}`,
984
1024
  version: PHOTON_VERSION,
985
1025
  });
986
- res.writeHead(200, {
987
- 'Content-Type': 'application/json',
988
- 'Access-Control-Allow-Origin': '*',
989
- });
1026
+ const cardHeaders = { 'Content-Type': 'application/json' };
1027
+ const cardCorsOrigin = getCorsOrigin(req);
1028
+ if (cardCorsOrigin)
1029
+ cardHeaders['Access-Control-Allow-Origin'] = cardCorsOrigin;
1030
+ res.writeHead(200, cardHeaders);
990
1031
  res.end(JSON.stringify(card));
991
1032
  return;
992
1033
  }
@@ -1037,9 +1078,9 @@ export async function startBeam(rawWorkingDir, port) {
1037
1078
  const handled = await handleStreamableHTTP(req, res, {
1038
1079
  photons, // Pass all photons including unconfigured for configurationSchema
1039
1080
  photonMCPs,
1040
- externalMCPs,
1041
- externalMCPClients,
1042
- externalMCPSDKClients, // SDK clients for tool calls with structuredContent
1081
+ externalMCPs: ctx.externalMCPs,
1082
+ externalMCPClients: ctx.externalMCPClients,
1083
+ externalMCPSDKClients: ctx.externalMCPSDKClients, // SDK clients for tool calls with structuredContent
1043
1084
  reconnectExternalMCP,
1044
1085
  loadUIAsset,
1045
1086
  workingDir,
@@ -1049,8 +1090,12 @@ export async function startBeam(rawWorkingDir, port) {
1049
1090
  reloadPhoton: async (photonName) => {
1050
1091
  return reloadPhotonViaMCP(photonName, photons, photonMCPs, loader, savedConfig, broadcastPhotonChange, activeLoads, (name, path, isStateful) => {
1051
1092
  if (isStateful) {
1052
- subscribeStatefulPhoton(name).catch(() => { });
1053
- reloadDaemonPhoton(name, path, workingDir).catch(() => { });
1093
+ subscribeStatefulPhoton(name).catch((e) => {
1094
+ logger.debug(`Failed to subscribe stateful photon ${name}: ${e?.message || e}`);
1095
+ });
1096
+ reloadDaemonPhoton(name, path, workingDir).catch((e) => {
1097
+ logger.debug(`Failed to reload daemon photon ${name}: ${e?.message || e}`);
1098
+ });
1054
1099
  }
1055
1100
  });
1056
1101
  },
@@ -1474,9 +1519,27 @@ export async function startBeam(rawWorkingDir, port) {
1474
1519
  appEl.appendChild(iframe);
1475
1520
  initBridge(iframe, bridgeMethod);
1476
1521
  } catch (err) {
1477
- appEl.innerHTML = '<div class="status-page show"><div class="icon">⚠️</div>'
1478
- + '<h2>Failed to load</h2><p>' + err.message + '</p>'
1479
- + '<button class="retry-btn" onclick="checkAndLoad()">Retry</button></div>';
1522
+ appEl.innerHTML = '';
1523
+ const statusDiv = document.createElement('div');
1524
+ statusDiv.className = 'status-page show';
1525
+
1526
+ const icon = document.createElement('div');
1527
+ icon.className = 'icon';
1528
+ icon.textContent = '⚠️';
1529
+
1530
+ const h2 = document.createElement('h2');
1531
+ h2.textContent = 'Failed to load';
1532
+
1533
+ const p = document.createElement('p');
1534
+ p.textContent = err.message;
1535
+
1536
+ const btn = document.createElement('button');
1537
+ btn.className = 'retry-btn';
1538
+ btn.textContent = 'Retry';
1539
+ btn.addEventListener('click', () => checkAndLoad());
1540
+
1541
+ statusDiv.append(icon, h2, p, btn);
1542
+ appEl.appendChild(statusDiv);
1480
1543
  }
1481
1544
  }
1482
1545
 
@@ -1851,14 +1914,35 @@ export async function startBeam(rawWorkingDir, port) {
1851
1914
  const relativePath = path.relative(workingDir, changedPath);
1852
1915
  // React to .photon.ts file changes — both top-level and namespaced subdirectories.
1853
1916
  // Top-level: foo.photon.ts → "foo"
1854
- // Namespaced: portel/gitbox.photon.ts → "portel/gitbox"
1917
+ // Namespaced: Arul-/git-box.photon.ts → "git-box" (short name only)
1855
1918
  if (relativePath.endsWith('.photon.ts')) {
1856
- return relativePath.slice(0, -'.photon.ts'.length);
1919
+ // For namespaced paths, look up by file path first to respect disambiguated
1920
+ // names like "chat (1)" / "chat (2)" assigned at startup
1921
+ let resolvedPath;
1922
+ try {
1923
+ resolvedPath = realpathSync(changedPath);
1924
+ }
1925
+ catch {
1926
+ resolvedPath = changedPath;
1927
+ }
1928
+ const byPath = photons.find((p) => {
1929
+ try {
1930
+ return realpathSync(p.path) === resolvedPath;
1931
+ }
1932
+ catch {
1933
+ return p.path === changedPath;
1934
+ }
1935
+ });
1936
+ if (byPath)
1937
+ return byPath.name;
1938
+ // New photon — derive short name from filename
1939
+ const withoutExt = relativePath.slice(0, -'.photon.ts'.length);
1940
+ const slashIndex = withoutExt.lastIndexOf(path.sep);
1941
+ return slashIndex >= 0 ? withoutExt.slice(slashIndex + 1) : withoutExt;
1857
1942
  }
1858
- // Detect asset file changes for local (non-symlinked) photons.
1859
- // Asset folders live at <workingDir>/<photonName>/ for local photons.
1860
- // NOTE: Do NOT match runtime data directories (state/, media/, auth/) — only
1861
- // photon asset directories are relevant here, identified by the loaded photons list.
1943
+ // Detect asset changes for local (non-symlinked) photons.
1944
+ // Runtime data now lives in .data/ (filtered above), so any remaining
1945
+ // file under {photonName}/ is a legitimate asset (ui/, etc.).
1862
1946
  for (const p of photons) {
1863
1947
  if (relativePath.startsWith(p.name + path.sep)) {
1864
1948
  return p.name;
@@ -1886,9 +1970,37 @@ export async function startBeam(rawWorkingDir, port) {
1886
1970
  try {
1887
1971
  const photonIndex = photons.findIndex((p) => p.name === photonName);
1888
1972
  const isNewPhoton = photonIndex === -1;
1889
- const photonPath = isNewPhoton
1890
- ? path.join(workingDir, `${photonName}.photon.ts`)
1891
- : photons[photonIndex].path;
1973
+ let photonPath;
1974
+ if (!isNewPhoton) {
1975
+ photonPath = photons[photonIndex].path;
1976
+ }
1977
+ else {
1978
+ // Try flat path first, then search namespace subdirectories
1979
+ const flatPath = path.join(workingDir, `${photonName}.photon.ts`);
1980
+ if (existsSync(flatPath)) {
1981
+ photonPath = flatPath;
1982
+ }
1983
+ else {
1984
+ // Search namespace dirs (one level deep) for the photon file
1985
+ let found = null;
1986
+ try {
1987
+ const entries = await fs.readdir(workingDir, { withFileTypes: true });
1988
+ for (const entry of entries) {
1989
+ if (!entry.isDirectory() || entry.name.startsWith('.'))
1990
+ continue;
1991
+ const candidate = path.join(workingDir, entry.name, `${photonName}.photon.ts`);
1992
+ if (existsSync(candidate)) {
1993
+ found = candidate;
1994
+ break;
1995
+ }
1996
+ }
1997
+ }
1998
+ catch {
1999
+ // readdir failed
2000
+ }
2001
+ photonPath = found || flatPath;
2002
+ }
2003
+ }
1892
2004
  const previouslyConfigured = !isNewPhoton && photons[photonIndex]?.configured === true;
1893
2005
  // Handle file deletion - if file no longer exists and photon is in list, remove it
1894
2006
  if (!isNewPhoton && photonPath && !existsSync(photonPath)) {
@@ -1961,7 +2073,9 @@ export async function startBeam(rawWorkingDir, port) {
1961
2073
  let constructorParams = [];
1962
2074
  try {
1963
2075
  const source = await readText(photonPath);
1964
- await writePhotonEditorDeclaration(photonPath, source, workingDir).catch(() => { });
2076
+ await writePhotonEditorDeclaration(photonPath, source, workingDir).catch((e) => {
2077
+ logger.debug(`Failed to write editor declaration for ${photonPath}: ${e?.message || e}`);
2078
+ });
1965
2079
  const params = extractor.extractConstructorParams(source);
1966
2080
  constructorParams = params
1967
2081
  .filter((p) => p.isPrimitive)
@@ -2016,10 +2130,10 @@ export async function startBeam(rawWorkingDir, port) {
2016
2130
  mcp.schemas = schemas; // Store schemas for result rendering
2017
2131
  // Update notification subscriptions for reloaded photon
2018
2132
  if (reloadMetadata.notificationSubscriptions?.watchFor) {
2019
- photonNotificationSubscriptions.set(photonName, reloadMetadata.notificationSubscriptions.watchFor);
2133
+ ctx.photonNotificationSubscriptions.set(photonName, reloadMetadata.notificationSubscriptions.watchFor);
2020
2134
  }
2021
2135
  else {
2022
- photonNotificationSubscriptions.delete(photonName);
2136
+ ctx.photonNotificationSubscriptions.delete(photonName);
2023
2137
  }
2024
2138
  const lifecycleMethods = ['onInitialize', 'onShutdown', 'constructor'];
2025
2139
  const uiAssets = mcp.assets?.ui || [];
@@ -2242,6 +2356,9 @@ export async function startBeam(rawWorkingDir, port) {
2242
2356
  const watcher = watch(workingDir, { recursive: true }, (eventType, filename) => {
2243
2357
  if (!filename)
2244
2358
  return;
2359
+ // Skip all runtime data — .data/ contains all photon data, never source code
2360
+ if (filename.startsWith('.data' + path.sep) || filename === '.data')
2361
+ return;
2245
2362
  const fullPath = path.join(workingDir, filename);
2246
2363
  logger.debug(`📂 File event: ${eventType} ${filename}`);
2247
2364
  const photonName = getPhotonForPath(fullPath);
@@ -2375,7 +2492,7 @@ export async function startBeam(rawWorkingDir, port) {
2375
2492
  }
2376
2493
  // Load external MCPs from config
2377
2494
  const externalMCPList = await loadExternalMCPs(savedConfig);
2378
- externalMCPs.push(...externalMCPList);
2495
+ ctx.externalMCPs.push(...externalMCPList);
2379
2496
  // Mark startup complete — flushes queued output and restores console
2380
2497
  startup.ready();
2381
2498
  // Notify connected clients that photon list is now available
@@ -2386,9 +2503,9 @@ export async function startBeam(rawWorkingDir, port) {
2386
2503
  const instanceNames = ['default'];
2387
2504
  for (const instanceName of instanceNames) {
2388
2505
  const channel = `${photonName}:${instanceName}:state-changed`;
2389
- if (subscribedStateChannels.has(channel))
2506
+ if (ctx.subscribedStateChannels.has(channel))
2390
2507
  continue;
2391
- subscribedStateChannels.add(channel);
2508
+ ctx.subscribedStateChannels.add(channel);
2392
2509
  subscribeChannel(photonName, channel, (message) => {
2393
2510
  // Sync Beam's local instance from the daemon-persisted state file BEFORE
2394
2511
  // notifying the frontend. The daemon persists state to disk after mutations,
@@ -2479,7 +2596,7 @@ export async function startBeam(rawWorkingDir, port) {
2479
2596
  // Subscribe to notifications channel (always-on, not just active)
2480
2597
  const notificationChannel = `${photonName}:${instanceName}:notifications`;
2481
2598
  // Get this photon's notification subscriptions from @notify-on tags
2482
- const watchFor = photonNotificationSubscriptions.get(photonName);
2599
+ const watchFor = ctx.photonNotificationSubscriptions.get(photonName);
2483
2600
  subscribeChannel(photonName, notificationChannel, (message) => {
2484
2601
  // Check if this photon cares about this notification type
2485
2602
  if (!watchFor || !watchFor.includes(message?.type)) {
@@ -2666,14 +2783,14 @@ export async function startBeam(rawWorkingDir, port) {
2666
2783
  // Remove MCPs — do all synchronous Map mutations first, then close async
2667
2784
  const removedSdkClients = [];
2668
2785
  for (const name of removed) {
2669
- const idx = externalMCPs.findIndex((m) => m.name === name);
2786
+ const idx = ctx.externalMCPs.findIndex((m) => m.name === name);
2670
2787
  if (idx !== -1)
2671
- externalMCPs.splice(idx, 1);
2672
- const sdkClient = externalMCPSDKClients.get(name);
2788
+ ctx.externalMCPs.splice(idx, 1);
2789
+ const sdkClient = ctx.externalMCPSDKClients.get(name);
2673
2790
  if (sdkClient)
2674
2791
  removedSdkClients.push({ name, client: sdkClient });
2675
- externalMCPSDKClients.delete(name);
2676
- externalMCPClients.delete(name);
2792
+ ctx.externalMCPSDKClients.delete(name);
2793
+ ctx.externalMCPClients.delete(name);
2677
2794
  logger.info(`🔌 Removed external MCP: ${name}`);
2678
2795
  }
2679
2796
  // Close SDK clients after all Maps are consistent
@@ -2692,7 +2809,7 @@ export async function startBeam(rawWorkingDir, port) {
2692
2809
  mcpServers: Object.fromEntries(added.map((k) => [k, newServers[k]])),
2693
2810
  };
2694
2811
  const newMCPs = await loadExternalMCPs(addConfig);
2695
- externalMCPs.push(...newMCPs);
2812
+ ctx.externalMCPs.push(...newMCPs);
2696
2813
  for (const m of newMCPs) {
2697
2814
  logger.info(`🔌 Added external MCP: ${m.name} (${m.connected ? m.methods.length + ' tools' : 'failed'})`);
2698
2815
  }
@@ -2700,14 +2817,14 @@ export async function startBeam(rawWorkingDir, port) {
2700
2817
  // Reconnect modified MCPs — synchronous cleanup first, then async reconnect
2701
2818
  const modifiedSdkClients = [];
2702
2819
  for (const name of modified) {
2703
- const idx = externalMCPs.findIndex((m) => m.name === name);
2820
+ const idx = ctx.externalMCPs.findIndex((m) => m.name === name);
2704
2821
  if (idx !== -1)
2705
- externalMCPs.splice(idx, 1);
2706
- const sdkClient = externalMCPSDKClients.get(name);
2822
+ ctx.externalMCPs.splice(idx, 1);
2823
+ const sdkClient = ctx.externalMCPSDKClients.get(name);
2707
2824
  if (sdkClient)
2708
2825
  modifiedSdkClients.push({ name, client: sdkClient });
2709
- externalMCPSDKClients.delete(name);
2710
- externalMCPClients.delete(name);
2826
+ ctx.externalMCPSDKClients.delete(name);
2827
+ ctx.externalMCPClients.delete(name);
2711
2828
  }
2712
2829
  // Close old SDK clients
2713
2830
  for (const { client } of modifiedSdkClients) {
@@ -2725,7 +2842,7 @@ export async function startBeam(rawWorkingDir, port) {
2725
2842
  mcpServers: { [name]: newServers[name] },
2726
2843
  };
2727
2844
  const reconnected = await loadExternalMCPs(modConfig);
2728
- externalMCPs.push(...reconnected);
2845
+ ctx.externalMCPs.push(...reconnected);
2729
2846
  logger.info(`🔌 Reconnected external MCP: ${name}`);
2730
2847
  }
2731
2848
  // Update savedConfig
@@ -2753,9 +2870,11 @@ export async function startBeam(rawWorkingDir, port) {
2753
2870
  * Closes all external MCP SDK clients to prevent ugly tracebacks on shutdown.
2754
2871
  */
2755
2872
  export async function stopBeam() {
2873
+ // Stop session cleanup timer
2874
+ stopSessionCleanup();
2756
2875
  // Close all SDK clients gracefully
2757
2876
  const closePromises = [];
2758
- for (const [, client] of externalMCPSDKClients) {
2877
+ for (const [, client] of ctx.externalMCPSDKClients) {
2759
2878
  closePromises.push(client.close().catch(() => {
2760
2879
  // Ignore close errors - process is exiting anyway
2761
2880
  }));
@@ -2764,7 +2883,7 @@ export async function stopBeam() {
2764
2883
  if (closePromises.length > 0) {
2765
2884
  await withTimeout(Promise.all(closePromises), 1000, 'MCP client close timeout').catch(() => { }); // Timeout during shutdown is expected
2766
2885
  }
2767
- externalMCPSDKClients.clear();
2768
- externalMCPClients.clear();
2886
+ ctx.externalMCPSDKClients.clear();
2887
+ ctx.externalMCPClients.clear();
2769
2888
  }
2770
2889
  //# sourceMappingURL=beam.js.map