@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
package/dist/loader.js CHANGED
@@ -36,7 +36,7 @@ executionContext,
36
36
  // Lock helper
37
37
  withLock as withLockHelper,
38
38
  // Middleware system
39
- builtinRegistry, MiddlewareRegistry, buildMiddlewareChain, } from '@portel/photon-core';
39
+ builtinRegistry, MiddlewareRegistry, buildMiddlewareChain, detectNamespace, getCacheDir, } from '@portel/photon-core';
40
40
  import { getDefaultContext } from './context.js';
41
41
  import * as os from 'os';
42
42
  import { MarketplaceManager } from './marketplace-manager.js';
@@ -332,7 +332,7 @@ export class PhotonLoader {
332
332
  * Directory where MCP-specific dependencies are cached
333
333
  */
334
334
  getDependencyCacheDir(cacheKey) {
335
- return path.join(os.homedir(), '.cache', 'photon-mcp', 'dependencies', cacheKey);
335
+ return path.join(getCacheDir(), 'dependencies', cacheKey);
336
336
  }
337
337
  getBuildCacheDir(cacheKey) {
338
338
  return path.join(this.getDependencyCacheDir(cacheKey), '.build');
@@ -402,7 +402,11 @@ export class PhotonLoader {
402
402
  ? this.dependenciesEqual(metadata.dependencies, dependencies)
403
403
  : false;
404
404
  const resolvedCoreVersion = getResolvedPhotonCoreVersion();
405
- const coreVersionChanged = metadata?.photonCoreVersion !== resolvedCoreVersion;
405
+ // Compare versions tolerantly: "2.17.6" matches "^2.17.6" and vice versa
406
+ // This prevents unnecessary cache clears when createRequire fallback returns a range
407
+ const storedVersion = metadata?.photonCoreVersion || '';
408
+ const coreVersionChanged = storedVersion !== resolvedCoreVersion &&
409
+ storedVersion.replace(/^[\^~]/, '') !== resolvedCoreVersion.replace(/^[\^~]/, '');
406
410
  // Only clear dependency cache when deps or core version actually changed.
407
411
  // Source-only changes just need a build cache clear (recompile is fast).
408
412
  const needsDepClear = Boolean(metadata && (!depsMatch || coreVersionChanged));
@@ -457,24 +461,39 @@ export class PhotonLoader {
457
461
  const message = error instanceof Error ? error.message : String(error);
458
462
  return (message.includes('Cannot find package') ||
459
463
  message.includes('ERR_MODULE_NOT_FOUND') ||
460
- message.includes('Cannot find module'));
464
+ message.includes('Cannot find module') ||
465
+ message.includes('require is not defined'));
461
466
  }
462
467
  static parseDependenciesFromSource(source) {
463
468
  const deps = [];
469
+ // Only match @dependencies inside JSDoc blocks (/** ... */)
470
+ const jsdocBlocks = source.match(/\/\*\*[\s\S]*?\*\//g) || [];
471
+ const jsdocText = jsdocBlocks.join('\n');
464
472
  const regex = /@dependencies\s+([^\r\n]+)/g;
465
473
  let match;
466
- while ((match = regex.exec(source)) !== null) {
474
+ while ((match = regex.exec(jsdocText)) !== null) {
467
475
  const entries = match[1]
476
+ .replace(/\*\/$/, '') // strip trailing */ if on same line
468
477
  .split(',')
469
478
  .map((entry) => entry.trim())
470
479
  .filter(Boolean);
471
480
  for (const entry of entries) {
472
481
  const atIndex = entry.lastIndexOf('@');
482
+ let name;
483
+ let version;
473
484
  if (atIndex <= 0) {
485
+ // No version specified (e.g., "sharp") — default to latest
486
+ name = entry.trim();
487
+ version = '*';
488
+ }
489
+ else {
490
+ name = entry.slice(0, atIndex).trim();
491
+ version = entry.slice(atIndex + 1).trim();
492
+ }
493
+ // Validate: npm package names are lowercase, may have @scope/, no spaces
494
+ if (name.includes(' ') || !/^(@[a-z0-9-]+\/)?[a-z0-9._-]+$/.test(name)) {
474
495
  continue;
475
496
  }
476
- const name = entry.slice(0, atIndex).trim();
477
- let version = entry.slice(atIndex + 1).trim();
478
497
  // Trailing ? marks the dependency as optional (e.g. sharp@^0.33.0?)
479
498
  const optional = version.endsWith('?');
480
499
  if (optional) {
@@ -609,8 +628,9 @@ export class PhotonLoader {
609
628
  this.logger.warn(`⚠️ ${name} loaded with configuration warnings:`);
610
629
  this.logger.warn(String(configError));
611
630
  }
612
- // Set photon name for event source identification
631
+ // Set photon name and namespace for event source identification and data paths
613
632
  instance._photonName = name;
633
+ instance._photonNamespace = this.resolveNamespace(absolutePath);
614
634
  // Inject instance name for named instances (runtime concept, not code)
615
635
  instance.instanceName = options?.instanceName ?? '';
616
636
  // Inject file path for storage()/assets() resolution
@@ -686,9 +706,17 @@ export class PhotonLoader {
686
706
  timestamp: Date.now(),
687
707
  source: name,
688
708
  })
689
- .catch(() => { }); // Best-effort
709
+ .catch((e) => {
710
+ this.logger.debug('Failed to publish broker event', {
711
+ error: e?.message || e,
712
+ });
713
+ });
690
714
  })
691
- .catch(() => { });
715
+ .catch((e) => {
716
+ this.logger.debug('Failed to import photon-core for broker', {
717
+ error: e?.message || e,
718
+ });
719
+ });
692
720
  }
693
721
  };
694
722
  // Also inject render() — convenience wrapper around emit
@@ -702,11 +730,12 @@ export class PhotonLoader {
702
730
  };
703
731
  }
704
732
  if (caps.has('memory')) {
705
- // Inject lazy memory provider
733
+ // Inject lazy memory provider — capture baseDir from loader context
734
+ const memoryBaseDir = this.baseDir;
706
735
  Object.defineProperty(instance, 'memory', {
707
736
  get() {
708
737
  if (!this._memory) {
709
- this._memory = new MemoryProvider(name, this._sessionId);
738
+ this._memory = new MemoryProvider(name, this._sessionId, this._photonNamespace, memoryBaseDir);
710
739
  }
711
740
  return this._memory;
712
741
  },
@@ -904,6 +933,42 @@ export class PhotonLoader {
904
933
  }
905
934
  this.log(`🔌 Injected channel event infrastructure into ${name}`);
906
935
  }
936
+ // Inject this.channel() for channel notifications — works for both Photon
937
+ // subclasses and plain classes. Emits on '{photonName}:channel-push' daemon
938
+ // channel which the MCP server intercepts and translates to the client's
939
+ // notification method (e.g. notifications/claude/channel).
940
+ //
941
+ // this.channel(content, meta) — send a message to the client
942
+ // this.channel.respond(id, behavior) — respond to permission requests
943
+ // this.channel.onPermission(handler) — register permission request handler
944
+ if (typeof instance.emit === 'function') {
945
+ const emitFn = instance.emit.bind(instance);
946
+ // Permission handler stored locally — wired by server after load
947
+ let permissionHandler;
948
+ const channelFn = Object.assign((content, meta) => {
949
+ emitFn({
950
+ channel: `${name}:channel-push`,
951
+ event: 'channel',
952
+ data: { content, meta },
953
+ });
954
+ }, {
955
+ respond: (requestId, behavior) => {
956
+ emitFn({
957
+ channel: `${name}:channel-permission-response`,
958
+ event: 'permission-response',
959
+ data: { request_id: requestId, behavior },
960
+ });
961
+ },
962
+ onPermission: (handler) => {
963
+ permissionHandler = handler;
964
+ },
965
+ // Internal: called by the server when a permission request arrives
966
+ _dispatchPermission: (request) => {
967
+ permissionHandler?.(request);
968
+ },
969
+ });
970
+ instance.channel = channelFn;
971
+ }
907
972
  // Check @cli dependencies (required system CLI tools)
908
973
  if (tsContent) {
909
974
  await this.checkCLIDependencies(tsContent, name);
@@ -1037,6 +1102,7 @@ export class PhotonLoader {
1037
1102
  instance._photonConfigError = configError;
1038
1103
  }
1039
1104
  instance._photonName = name;
1105
+ instance._photonNamespace = this.resolveNamespace(absolutePath);
1040
1106
  instance.instanceName = options?.instanceName ?? '';
1041
1107
  // Inject file path for storage()/assets() resolution.
1042
1108
  // For preloaded modules (compiled binaries), remap to ~/.photon/ so storage()
@@ -1100,9 +1166,15 @@ export class PhotonLoader {
1100
1166
  timestamp: Date.now(),
1101
1167
  source: name,
1102
1168
  })
1103
- .catch(() => { });
1169
+ .catch((e) => {
1170
+ this.logger.debug('Failed to publish broker event', { error: e?.message || e });
1171
+ });
1104
1172
  })
1105
- .catch(() => { });
1173
+ .catch((e) => {
1174
+ this.logger.debug('Failed to import photon-core for broker', {
1175
+ error: e?.message || e,
1176
+ });
1177
+ });
1106
1178
  }
1107
1179
  };
1108
1180
  // Also inject render() — convenience wrapper around emit
@@ -1116,10 +1188,11 @@ export class PhotonLoader {
1116
1188
  };
1117
1189
  }
1118
1190
  if (caps.has('memory')) {
1191
+ const memoryBaseDir = this.baseDir;
1119
1192
  Object.defineProperty(instance, 'memory', {
1120
1193
  get() {
1121
1194
  if (!this._memory) {
1122
- this._memory = new MemoryProvider(name, this._sessionId);
1195
+ this._memory = new MemoryProvider(name, this._sessionId, this._photonNamespace, memoryBaseDir);
1123
1196
  }
1124
1197
  return this._memory;
1125
1198
  },
@@ -1945,7 +2018,7 @@ export class PhotonLoader {
1945
2018
  }
1946
2019
  // If configured but failed, provide more specific error
1947
2020
  throw new Error(`MCP "${dep.name}" is configured but failed to connect: ${errorMsg}\n` +
1948
- `Check your ~/.photon/config.json configuration and ensure:\n` +
2021
+ `Check your config.json configuration and ensure:\n` +
1949
2022
  ` • The command/URL is correct\n` +
1950
2023
  ` • Required environment variables are set\n` +
1951
2024
  ` • The MCP server is accessible`);
@@ -1984,6 +2057,7 @@ export class PhotonLoader {
1984
2057
  }
1985
2058
  // Resolve the Photon path
1986
2059
  const resolvedPath = await this.resolvePhotonPath(dep, currentPhotonPath);
2060
+ await this.materializeSiblingDependencySymlink(dep, currentPhotonPath, resolvedPath);
1987
2061
  // Cache key includes instance name to allow multiple instances of the same photon
1988
2062
  const cacheKey = dep.instanceName ? `${resolvedPath}::${dep.instanceName}` : resolvedPath;
1989
2063
  // Check cache
@@ -2047,14 +2121,15 @@ export class PhotonLoader {
2047
2121
  * Resolve Photon dependency path based on source type
2048
2122
  */
2049
2123
  async resolvePhotonPath(dep, currentPhotonPath) {
2124
+ const resolvedCurrentPhotonPath = await this.resolveRealPhotonPath(currentPhotonPath);
2050
2125
  switch (dep.sourceType) {
2051
2126
  case 'local':
2052
2127
  if (dep.source.startsWith('./') || dep.source.startsWith('../')) {
2053
- return path.resolve(path.dirname(currentPhotonPath), dep.source);
2128
+ return path.resolve(path.dirname(resolvedCurrentPhotonPath), dep.source);
2054
2129
  }
2055
2130
  return dep.source;
2056
2131
  case 'marketplace':
2057
- return await this.resolveMarketplacePhoton(dep, currentPhotonPath);
2132
+ return await this.resolveMarketplacePhoton(dep, resolvedCurrentPhotonPath);
2058
2133
  case 'github':
2059
2134
  return await this.fetchGithubPhoton(dep);
2060
2135
  case 'npm':
@@ -2136,6 +2211,48 @@ export class PhotonLoader {
2136
2211
  throw new Error(`Photon "${dep.source}" not found in local paths or configured marketplaces. ` +
2137
2212
  `Checked: ${candidates.join(', ')}`);
2138
2213
  }
2214
+ async resolveRealPhotonPath(currentPhotonPath) {
2215
+ try {
2216
+ return await fs.realpath(currentPhotonPath);
2217
+ }
2218
+ catch {
2219
+ return currentPhotonPath;
2220
+ }
2221
+ }
2222
+ async materializeSiblingDependencySymlink(dep, currentPhotonPath, resolvedPath) {
2223
+ if (dep.sourceType !== 'marketplace')
2224
+ return;
2225
+ if (dep.source.includes(':'))
2226
+ return;
2227
+ if (dep.source.includes('/'))
2228
+ return;
2229
+ const realCurrentPhotonPath = await this.resolveRealPhotonPath(currentPhotonPath);
2230
+ if (realCurrentPhotonPath === currentPhotonPath)
2231
+ return;
2232
+ if (path.dirname(realCurrentPhotonPath) !== path.dirname(resolvedPath))
2233
+ return;
2234
+ const linkPath = path.join(this.baseDir, path.basename(resolvedPath));
2235
+ await this.createSymlinkIfMissing(resolvedPath, linkPath);
2236
+ const depName = path.basename(resolvedPath).replace(/\.photon\.(ts|js)$/, '');
2237
+ const sourceAssetDir = path.join(path.dirname(resolvedPath), depName);
2238
+ const targetAssetDir = path.join(this.baseDir, depName);
2239
+ if (existsSync(sourceAssetDir)) {
2240
+ await this.createSymlinkIfMissing(sourceAssetDir, targetAssetDir, 'dir');
2241
+ }
2242
+ }
2243
+ async createSymlinkIfMissing(sourcePath, targetPath, type) {
2244
+ try {
2245
+ const existing = await fs.lstat(targetPath).catch(() => null);
2246
+ if (existing)
2247
+ return;
2248
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
2249
+ symlinkSync(sourcePath, targetPath, type);
2250
+ this.log(`🔗 Materialized sibling photon symlink: ${path.basename(targetPath)}`);
2251
+ }
2252
+ catch (error) {
2253
+ this.log(`⚠️ Failed to materialize sibling symlink ${path.basename(targetPath)}: ${getErrorMessage(error)}`);
2254
+ }
2255
+ }
2139
2256
  normalizeMarketplaceSource(source) {
2140
2257
  let slugSource = source;
2141
2258
  let marketplaceHint;
@@ -2642,9 +2759,9 @@ Run: photon mcp ${mcpName} --config
2642
2759
  if (process.env.PHOTON_DEBUG_EMIT === '1') {
2643
2760
  console.error(`[EMIT-DEBUG] Sending event: method=${eventPayload.method}, channel=${eventData.channel}, hasMeta=${!!result?.__meta}`);
2644
2761
  }
2645
- // Cast to any - outputHandler is flexible and routes any object with channel property
2646
- void Promise.resolve(options.outputHandler(eventData)).catch(() => {
2647
- // Ignore output handler errors - don't break tool execution
2762
+ // Cast to DaemonEventEnvelope - outputHandler is flexible and routes any object with channel property
2763
+ void Promise.resolve(options.outputHandler(eventData)).catch((e) => {
2764
+ this.logger.debug('Output handler failed for event', { error: e?.message || e });
2648
2765
  });
2649
2766
  if (process.env.PHOTON_DEBUG_EMIT === '1') {
2650
2767
  console.error(`[EMIT-DEBUG] Event transmitted to outputHandler`);
@@ -2990,12 +3107,19 @@ Run: photon mcp ${mcpName} --config
2990
3107
  return; // No emit function, can't wrap methods
2991
3108
  }
2992
3109
  // Get all public method names from the instance
3110
+ // Skip runtime-injected methods (emit, render, push, ask) — these are
3111
+ // capability methods injected by the loader, not user-defined tools
3112
+ const RUNTIME_METHODS = new Set(['emit', 'render', 'channel', 'ask', 'call']);
2993
3113
  const proto = Object.getPrototypeOf(instance);
2994
3114
  const methodNames = Object.getOwnPropertyNames(proto).filter((name) => {
2995
3115
  // Skip constructor and private/protected methods
2996
3116
  if (name === 'constructor' || name.startsWith('_')) {
2997
3117
  return false;
2998
3118
  }
3119
+ // Skip runtime-injected capability methods
3120
+ if (RUNTIME_METHODS.has(name)) {
3121
+ return false;
3122
+ }
2999
3123
  const descriptor = Object.getOwnPropertyDescriptor(proto, name);
3000
3124
  return descriptor && typeof descriptor.value === 'function';
3001
3125
  });
@@ -3016,7 +3140,11 @@ Run: photon mcp ${mcpName} --config
3016
3140
  // Call the original method
3017
3141
  const result = original.apply(this, args);
3018
3142
  // Attach __meta to returned objects for audit trail
3019
- if (result && typeof result === 'object' && !Array.isArray(result) && !result.__meta) {
3143
+ const resultObj = result;
3144
+ if (resultObj &&
3145
+ typeof resultObj === 'object' &&
3146
+ !Array.isArray(resultObj) &&
3147
+ !resultObj.__meta) {
3020
3148
  const timestamp = new Date().toISOString();
3021
3149
  Object.defineProperty(result, '__meta', {
3022
3150
  value: {
@@ -3211,6 +3339,25 @@ Run: photon mcp ${mcpName} --config
3211
3339
  check.on('error', () => resolve(false));
3212
3340
  });
3213
3341
  }
3342
+ /**
3343
+ * Resolve the namespace for a photon based on its file path.
3344
+ * Extracts the directory name between baseDir and the photon file.
3345
+ *
3346
+ * Examples:
3347
+ * ~/.photon/portel-dev/todo.photon.ts → 'portel-dev'
3348
+ * ~/.photon/acme/todo.photon.ts → 'acme'
3349
+ * ~/.photon/todo.photon.ts → detected from git or 'local'
3350
+ */
3351
+ resolveNamespace(absolutePath) {
3352
+ const rel = path.relative(this.baseDir, absolutePath);
3353
+ const parts = rel.split(path.sep);
3354
+ // If file is in a subdirectory (namespace/photon.ts), use that as namespace
3355
+ if (parts.length >= 2) {
3356
+ return parts[0];
3357
+ }
3358
+ // Flat file at root — detect from git remote or default to 'local'
3359
+ return detectNamespace(this.baseDir);
3360
+ }
3214
3361
  /**
3215
3362
  * Discover and extract assets from a Photon file
3216
3363
  * Uses shared discoverAssets from photon-core for core logic,