@portel/photon 1.17.6 → 1.19.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 (194) hide show
  1. package/dist/auto-ui/beam/photon-management.d.ts.map +1 -1
  2. package/dist/auto-ui/beam/photon-management.js +28 -1
  3. package/dist/auto-ui/beam/photon-management.js.map +1 -1
  4. package/dist/auto-ui/beam/routes/api-marketplace.d.ts.map +1 -1
  5. package/dist/auto-ui/beam/routes/api-marketplace.js +10 -5
  6. package/dist/auto-ui/beam/routes/api-marketplace.js.map +1 -1
  7. package/dist/auto-ui/beam.d.ts.map +1 -1
  8. package/dist/auto-ui/beam.js +14 -4
  9. package/dist/auto-ui/beam.js.map +1 -1
  10. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  11. package/dist/auto-ui/streamable-http-transport.js +259 -88
  12. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  13. package/dist/auto-ui/types.d.ts +2 -0
  14. package/dist/auto-ui/types.d.ts.map +1 -1
  15. package/dist/auto-ui/types.js +5 -0
  16. package/dist/auto-ui/types.js.map +1 -1
  17. package/dist/beam-form.bundle.js +5 -3
  18. package/dist/beam-form.bundle.js.map +2 -2
  19. package/dist/beam.bundle.js +912 -59
  20. package/dist/beam.bundle.js.map +3 -3
  21. package/dist/claude-code-plugin.js +1 -1
  22. package/dist/cli/commands/beam.d.ts.map +1 -1
  23. package/dist/cli/commands/beam.js +8 -2
  24. package/dist/cli/commands/beam.js.map +1 -1
  25. package/dist/cli/commands/changelog.d.ts +9 -0
  26. package/dist/cli/commands/changelog.d.ts.map +1 -0
  27. package/dist/cli/commands/changelog.js +133 -0
  28. package/dist/cli/commands/changelog.js.map +1 -0
  29. package/dist/cli/commands/maker.d.ts.map +1 -1
  30. package/dist/cli/commands/maker.js +23 -2
  31. package/dist/cli/commands/maker.js.map +1 -1
  32. package/dist/cli/commands/mcp.d.ts.map +1 -1
  33. package/dist/cli/commands/mcp.js +53 -0
  34. package/dist/cli/commands/mcp.js.map +1 -1
  35. package/dist/cli/commands/package.d.ts.map +1 -1
  36. package/dist/cli/commands/package.js +18 -2
  37. package/dist/cli/commands/package.js.map +1 -1
  38. package/dist/cli/commands/run.d.ts.map +1 -1
  39. package/dist/cli/commands/run.js +1 -0
  40. package/dist/cli/commands/run.js.map +1 -1
  41. package/dist/cli/commands/update.d.ts +3 -2
  42. package/dist/cli/commands/update.d.ts.map +1 -1
  43. package/dist/cli/commands/update.js +50 -43
  44. package/dist/cli/commands/update.js.map +1 -1
  45. package/dist/cli/index.d.ts.map +1 -1
  46. package/dist/cli/index.js +16 -2
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/cli-alias.js +1 -1
  49. package/dist/cli-alias.js.map +1 -1
  50. package/dist/context-store.d.ts +23 -33
  51. package/dist/context-store.d.ts.map +1 -1
  52. package/dist/context-store.js +147 -97
  53. package/dist/context-store.js.map +1 -1
  54. package/dist/context.d.ts +15 -10
  55. package/dist/context.d.ts.map +1 -1
  56. package/dist/context.js +37 -13
  57. package/dist/context.js.map +1 -1
  58. package/dist/daemon/server.js +4 -2
  59. package/dist/daemon/server.js.map +1 -1
  60. package/dist/data-migration.d.ts +27 -0
  61. package/dist/data-migration.d.ts.map +1 -0
  62. package/dist/data-migration.js +307 -0
  63. package/dist/data-migration.js.map +1 -0
  64. package/dist/editor-support/docblock-tag-catalog.d.ts.map +1 -1
  65. package/dist/editor-support/docblock-tag-catalog.js +6 -0
  66. package/dist/editor-support/docblock-tag-catalog.js.map +1 -1
  67. package/dist/loader.d.ts +10 -0
  68. package/dist/loader.d.ts.map +1 -1
  69. package/dist/loader.js +106 -20
  70. package/dist/loader.js.map +1 -1
  71. package/dist/marketplace-manager.d.ts.map +1 -1
  72. package/dist/marketplace-manager.js +25 -5
  73. package/dist/marketplace-manager.js.map +1 -1
  74. package/dist/photon-cli-runner.d.ts.map +1 -1
  75. package/dist/photon-cli-runner.js +56 -19
  76. package/dist/photon-cli-runner.js.map +1 -1
  77. package/dist/photon-doc-extractor.d.ts +1 -0
  78. package/dist/photon-doc-extractor.d.ts.map +1 -1
  79. package/dist/photon-doc-extractor.js +6 -0
  80. package/dist/photon-doc-extractor.js.map +1 -1
  81. package/dist/readme-syncer.d.ts.map +1 -1
  82. package/dist/readme-syncer.js +6 -1
  83. package/dist/readme-syncer.js.map +1 -1
  84. package/dist/server.d.ts +40 -0
  85. package/dist/server.d.ts.map +1 -1
  86. package/dist/server.js +298 -29
  87. package/dist/server.js.map +1 -1
  88. package/dist/shared/audit.js +4 -4
  89. package/dist/shared/audit.js.map +1 -1
  90. package/dist/tasks/executor.d.ts +47 -0
  91. package/dist/tasks/executor.d.ts.map +1 -0
  92. package/dist/tasks/executor.js +180 -0
  93. package/dist/tasks/executor.js.map +1 -0
  94. package/dist/tasks/store.d.ts +13 -6
  95. package/dist/tasks/store.d.ts.map +1 -1
  96. package/dist/tasks/store.js +56 -11
  97. package/dist/tasks/store.js.map +1 -1
  98. package/dist/tasks/types.d.ts +23 -2
  99. package/dist/tasks/types.d.ts.map +1 -1
  100. package/dist/tasks/types.js +23 -3
  101. package/dist/tasks/types.js.map +1 -1
  102. package/dist/version-notify.d.ts +27 -0
  103. package/dist/version-notify.d.ts.map +1 -0
  104. package/dist/version-notify.js +142 -0
  105. package/dist/version-notify.js.map +1 -0
  106. package/package.json +5 -4
  107. package/dist/auto-ui/bridge/openai-shim.d.ts +0 -20
  108. package/dist/auto-ui/bridge/openai-shim.d.ts.map +0 -1
  109. package/dist/auto-ui/bridge/openai-shim.js +0 -231
  110. package/dist/auto-ui/bridge/openai-shim.js.map +0 -1
  111. package/dist/auto-ui/bridge/photon-app.d.ts +0 -162
  112. package/dist/auto-ui/bridge/photon-app.d.ts.map +0 -1
  113. package/dist/auto-ui/bridge/photon-app.js +0 -460
  114. package/dist/auto-ui/bridge/photon-app.js.map +0 -1
  115. package/dist/auto-ui/daemon-tools.d.ts +0 -45
  116. package/dist/auto-ui/daemon-tools.d.ts.map +0 -1
  117. package/dist/auto-ui/daemon-tools.js +0 -581
  118. package/dist/auto-ui/daemon-tools.js.map +0 -1
  119. package/dist/auto-ui/design-system/index.d.ts +0 -21
  120. package/dist/auto-ui/design-system/index.d.ts.map +0 -1
  121. package/dist/auto-ui/design-system/index.js +0 -27
  122. package/dist/auto-ui/design-system/index.js.map +0 -1
  123. package/dist/auto-ui/design-system/transaction-ui.d.ts +0 -70
  124. package/dist/auto-ui/design-system/transaction-ui.d.ts.map +0 -1
  125. package/dist/auto-ui/design-system/transaction-ui.js +0 -982
  126. package/dist/auto-ui/design-system/transaction-ui.js.map +0 -1
  127. package/dist/auto-ui/playground-server.d.ts +0 -7
  128. package/dist/auto-ui/playground-server.d.ts.map +0 -1
  129. package/dist/auto-ui/playground-server.js +0 -840
  130. package/dist/auto-ui/playground-server.js.map +0 -1
  131. package/dist/auto-ui/rendering/components.d.ts +0 -29
  132. package/dist/auto-ui/rendering/components.d.ts.map +0 -1
  133. package/dist/auto-ui/rendering/components.js +0 -1341
  134. package/dist/auto-ui/rendering/components.js.map +0 -1
  135. package/dist/auto-ui/rendering/field-analyzer.d.ts +0 -104
  136. package/dist/auto-ui/rendering/field-analyzer.d.ts.map +0 -1
  137. package/dist/auto-ui/rendering/field-analyzer.js +0 -447
  138. package/dist/auto-ui/rendering/field-analyzer.js.map +0 -1
  139. package/dist/auto-ui/rendering/field-renderers.d.ts +0 -64
  140. package/dist/auto-ui/rendering/field-renderers.d.ts.map +0 -1
  141. package/dist/auto-ui/rendering/field-renderers.js +0 -317
  142. package/dist/auto-ui/rendering/field-renderers.js.map +0 -1
  143. package/dist/auto-ui/rendering/index.d.ts +0 -28
  144. package/dist/auto-ui/rendering/index.d.ts.map +0 -1
  145. package/dist/auto-ui/rendering/index.js +0 -60
  146. package/dist/auto-ui/rendering/index.js.map +0 -1
  147. package/dist/auto-ui/rendering/layout-selector.d.ts +0 -60
  148. package/dist/auto-ui/rendering/layout-selector.d.ts.map +0 -1
  149. package/dist/auto-ui/rendering/layout-selector.js +0 -476
  150. package/dist/auto-ui/rendering/layout-selector.js.map +0 -1
  151. package/dist/markdown-utils.d.ts +0 -8
  152. package/dist/markdown-utils.d.ts.map +0 -1
  153. package/dist/markdown-utils.js +0 -64
  154. package/dist/markdown-utils.js.map +0 -1
  155. package/dist/mcp-client.d.ts +0 -9
  156. package/dist/mcp-client.d.ts.map +0 -1
  157. package/dist/mcp-client.js +0 -11
  158. package/dist/mcp-client.js.map +0 -1
  159. package/dist/mcp-elicitation.d.ts +0 -32
  160. package/dist/mcp-elicitation.d.ts.map +0 -1
  161. package/dist/mcp-elicitation.js +0 -26
  162. package/dist/mcp-elicitation.js.map +0 -1
  163. package/dist/photons/builder-compass.photon.d.ts +0 -167
  164. package/dist/photons/builder-compass.photon.d.ts.map +0 -1
  165. package/dist/photons/builder-compass.photon.js +0 -816
  166. package/dist/photons/builder-compass.photon.js.map +0 -1
  167. package/dist/photons/builder-compass.photon.ts +0 -1129
  168. package/dist/photons/docs/ui/docs.html +0 -441
  169. package/dist/photons/docs.photon.d.ts +0 -237
  170. package/dist/photons/docs.photon.d.ts.map +0 -1
  171. package/dist/photons/docs.photon.js +0 -483
  172. package/dist/photons/docs.photon.js.map +0 -1
  173. package/dist/photons/docs.photon.ts +0 -536
  174. package/dist/photons/slides.photon.d.ts +0 -212
  175. package/dist/photons/slides.photon.d.ts.map +0 -1
  176. package/dist/photons/slides.photon.js +0 -355
  177. package/dist/photons/slides.photon.js.map +0 -1
  178. package/dist/photons/slides.photon.ts +0 -370
  179. package/dist/photons/spreadsheet/ui/spreadsheet.html +0 -779
  180. package/dist/photons/spreadsheet.photon.d.ts +0 -554
  181. package/dist/photons/spreadsheet.photon.d.ts.map +0 -1
  182. package/dist/photons/spreadsheet.photon.js +0 -1050
  183. package/dist/photons/spreadsheet.photon.js.map +0 -1
  184. package/dist/photons/spreadsheet.photon.ts +0 -1239
  185. package/dist/photons/ui/builder-compass.html +0 -1199
  186. package/dist/photons/ui/builder-compass.photon.html +0 -380
  187. package/dist/security-scanner.d.ts +0 -52
  188. package/dist/security-scanner.d.ts.map +0 -1
  189. package/dist/security-scanner.js +0 -181
  190. package/dist/security-scanner.js.map +0 -1
  191. package/dist/shared/performance.d.ts +0 -65
  192. package/dist/shared/performance.d.ts.map +0 -1
  193. package/dist/shared/performance.js +0 -136
  194. package/dist/shared/performance.js.map +0 -1
package/dist/server.js CHANGED
@@ -7,7 +7,7 @@
7
7
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
8
8
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
9
  import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
10
- import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
10
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, GetTaskRequestSchema, ListTasksRequestSchema, CancelTaskRequestSchema, GetTaskPayloadRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
11
11
  import { readFileSync } from 'node:fs';
12
12
  import { readText } from './shared/io.js';
13
13
  import * as path from 'node:path';
@@ -26,6 +26,9 @@ import { isGlobalDaemonRunning, startGlobalDaemon } from './daemon/manager.js';
26
26
  import { PhotonDocExtractor } from './photon-doc-extractor.js';
27
27
  import { isLocalRequest, readBody, setSecurityHeaders } from './shared/security.js';
28
28
  import { audit } from './shared/audit.js';
29
+ import { createTask, getTask, updateTask, listTasks, registerController, getController, unregisterController, } from './tasks/store.js';
30
+ import { toWireFormat, relatedTaskMeta, TERMINAL_STATES } from './tasks/types.js';
31
+ import { runTaskExecution, resolveTaskInput, waitForTerminalOrInput } from './tasks/executor.js';
29
32
  export class HotReloadDisabledError extends Error {
30
33
  constructor(message) {
31
34
  super(message);
@@ -102,7 +105,8 @@ class BeamCompatTransport {
102
105
  }
103
106
  // Otherwise push to SSE stream if connected
104
107
  if (this.sseResponse && !this.sseResponse.writableEnded) {
105
- this.sseResponse.write(`data: ${JSON.stringify(message)}\n\n`);
108
+ const id = message.id ?? crypto.randomUUID();
109
+ this.sseResponse.write(`event: message\nid: ${id}\ndata: ${JSON.stringify(message)}\n\n`);
106
110
  }
107
111
  }
108
112
  async handleHTTP(req, res, url) {
@@ -201,7 +205,10 @@ class BeamCompatTransport {
201
205
  // Notifications have no id — fire-and-forget
202
206
  if (parsed.id === undefined) {
203
207
  this.onmessage?.(parsed, { sessionId: this.sessionId });
204
- res.writeHead(202);
208
+ res.writeHead(202, {
209
+ 'Access-Control-Allow-Origin': '*',
210
+ 'Mcp-Session-Id': this.sessionId,
211
+ });
205
212
  res.end();
206
213
  return;
207
214
  }
@@ -221,11 +228,11 @@ class BeamCompatTransport {
221
228
  }
222
229
  // DELETE — session termination (spec compliance)
223
230
  if (req.method === 'DELETE') {
224
- res.writeHead(200);
231
+ res.writeHead(200, { 'Access-Control-Allow-Origin': '*' });
225
232
  res.end();
226
233
  return;
227
234
  }
228
- res.writeHead(405);
235
+ res.writeHead(405, { 'Access-Control-Allow-Origin': '*' });
229
236
  res.end('Method not allowed');
230
237
  }
231
238
  }
@@ -306,10 +313,14 @@ export class PhotonServer {
306
313
  baseLoggerOptions.scope = this.devMode ? 'dev' : 'runtime';
307
314
  }
308
315
  this.logger = createLogger(baseLoggerOptions);
309
- this.loader = new PhotonLoader(true, this.logger.child({ component: 'photon-loader', scope: 'loader' }), options.workingDir);
316
+ const loaderVerbose = (baseLoggerOptions.level ?? 'info') !== 'warn' &&
317
+ (baseLoggerOptions.level ?? 'info') !== 'error';
318
+ this.loader = new PhotonLoader(loaderVerbose, this.logger.child({ component: 'photon-loader', scope: 'loader' }), options.workingDir);
310
319
  // Create MCP server instance
320
+ // When channelMode is set, declare claude/channel capability and use photon name as server name
321
+ const serverName = options.channelMode && options.channelName ? options.channelName : 'photon-mcp';
311
322
  this.server = new Server({
312
- name: 'photon-mcp',
323
+ name: serverName,
313
324
  version: PHOTON_VERSION,
314
325
  }, {
315
326
  capabilities: {
@@ -323,9 +334,32 @@ export class PhotonServer {
323
334
  listChanged: true, // We support hot reload notifications
324
335
  },
325
336
  logging: {}, // Required for notifications/message (used by render, log, etc.)
337
+ tasks: {
338
+ list: {},
339
+ cancel: {},
340
+ requests: {
341
+ tools: { call: {} },
342
+ },
343
+ },
326
344
  // Note: Server doesn't declare elicitation capability - that's a client capability
327
345
  // The server uses elicitInput() when the client has elicitation support
346
+ //
347
+ // Channel capabilities — declare only the protocols specified by @channel tag.
348
+ // e.g. @channel claude → { 'claude/channel': {}, 'claude/channel/permission': {} }
349
+ ...(options.channelMode && options.channelTargets?.length
350
+ ? {
351
+ experimental: Object.fromEntries(options.channelTargets.flatMap((t) => [
352
+ [`${t}/channel`, {}],
353
+ [`${t}/channel/permission`, {}],
354
+ ])),
355
+ }
356
+ : {}),
328
357
  },
358
+ ...(options.channelMode && options.channelInstructions
359
+ ? {
360
+ instructions: options.channelInstructions,
361
+ }
362
+ : {}),
329
363
  });
330
364
  // Set up protocol handlers
331
365
  this.setupHandlers();
@@ -456,6 +490,62 @@ export class PhotonServer {
456
490
  return true;
457
491
  return false;
458
492
  }
493
+ /**
494
+ * Get the channel notification method for the connected client.
495
+ * Each client uses its own notification namespace under the MCP experimental extension point.
496
+ * Returns undefined if the client is unknown or doesn't support channels.
497
+ */
498
+ /**
499
+ * Get the notification methods for all declared channel targets.
500
+ * Each target (e.g. 'claude') maps to its notification method.
501
+ */
502
+ getChannelNotificationMethods() {
503
+ const targets = this.options.channelTargets || [];
504
+ // Each target's notification method follows the pattern: notifications/{target}/channel
505
+ return targets.map((t) => `notifications/${t}/channel`);
506
+ }
507
+ /**
508
+ * Handle permission request from the client (e.g. Claude Code asking "Allow tool X?").
509
+ * Forwards to the photon instance via channel._dispatchPermission().
510
+ */
511
+ handlePermissionRequest(params) {
512
+ if (!params?.request_id || !params?.tool_name)
513
+ return;
514
+ const request = {
515
+ request_id: params.request_id,
516
+ tool_name: params.tool_name,
517
+ description: params.description || '',
518
+ input_preview: params.input_preview || '',
519
+ };
520
+ this.log('info', `Permission request: ${request.tool_name} (${request.request_id})`);
521
+ // Dispatch to the photon instance's permission handler
522
+ const instance = this.mcp;
523
+ if (instance?.channel?._dispatchPermission) {
524
+ instance.channel._dispatchPermission(request);
525
+ }
526
+ }
527
+ /**
528
+ * Send a permission response back to the client.
529
+ * Called by the photon instance (via this.channel.respond) when the user approves/denies.
530
+ */
531
+ respondToPermission(response) {
532
+ const targets = this.options.channelTargets || [];
533
+ for (const target of targets) {
534
+ const notification = {
535
+ method: `notifications/${target}/channel/permission`,
536
+ params: {
537
+ request_id: response.request_id,
538
+ behavior: response.behavior,
539
+ },
540
+ };
541
+ this.server.notification(notification).catch((e) => {
542
+ this.log('debug', 'Permission response failed', { error: getErrorMessage(e) });
543
+ });
544
+ for (const session of Array.from(this.sseSessions.values())) {
545
+ session.server.notification(notification).catch(() => { });
546
+ }
547
+ }
548
+ }
459
549
  /**
460
550
  * Log client identity and capabilities for debugging tier detection
461
551
  */
@@ -1340,6 +1430,27 @@ export class PhotonServer {
1340
1430
  };
1341
1431
  }
1342
1432
  }
1433
+ // ── Task mode: when params contain task field, run async ──
1434
+ const taskField = request.params?.task;
1435
+ if (taskField && this.mcp) {
1436
+ const { name: toolName, arguments: args } = request.params;
1437
+ const ttl = typeof taskField.ttl === 'number' ? taskField.ttl : undefined;
1438
+ const task = createTask(this.mcp.name, toolName, args, ttl);
1439
+ const controller = new AbortController();
1440
+ registerController(task.id, controller);
1441
+ const executeFn = async (taskInputProvider, outputHandler) => {
1442
+ return this.loader.executeTool(this.mcp, toolName, args || {}, {
1443
+ outputHandler,
1444
+ inputProvider: taskInputProvider,
1445
+ });
1446
+ };
1447
+ runTaskExecution(task.id, executeFn, {
1448
+ signal: controller.signal,
1449
+ });
1450
+ return {
1451
+ content: [{ type: 'text', text: JSON.stringify({ task: toWireFormat(task) }, null, 2) }],
1452
+ };
1453
+ }
1343
1454
  try {
1344
1455
  return await this.handleCallTool(ctx, request);
1345
1456
  }
@@ -1386,6 +1497,129 @@ export class PhotonServer {
1386
1497
  this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1387
1498
  return this.handleReadResource(request);
1388
1499
  });
1500
+ // ── MCP Tasks handlers (2025-11-25 spec) ──
1501
+ this.server.setRequestHandler(GetTaskRequestSchema, async (request) => {
1502
+ const { taskId } = request.params;
1503
+ const task = getTask(taskId);
1504
+ if (!task)
1505
+ throw new Error(`Task not found: ${taskId}`);
1506
+ return toWireFormat(task);
1507
+ });
1508
+ this.server.setRequestHandler(ListTasksRequestSchema, async (request) => {
1509
+ const allTasks = listTasks();
1510
+ const cursor = request.params?.cursor;
1511
+ const offset = cursor ? parseInt(cursor, 10) || 0 : 0;
1512
+ const pageSize = 50;
1513
+ const page = allTasks.slice(offset, offset + pageSize);
1514
+ const nextCursor = offset + pageSize < allTasks.length ? String(offset + pageSize) : undefined;
1515
+ return {
1516
+ tasks: page.map(toWireFormat),
1517
+ ...(nextCursor && { nextCursor }),
1518
+ };
1519
+ });
1520
+ this.server.setRequestHandler(CancelTaskRequestSchema, async (request) => {
1521
+ const { taskId } = request.params;
1522
+ const task = getTask(taskId);
1523
+ if (!task)
1524
+ throw new Error(`Task not found: ${taskId}`);
1525
+ if (TERMINAL_STATES.includes(task.state)) {
1526
+ throw new Error(`Cannot cancel task in terminal state: ${task.state}`);
1527
+ }
1528
+ const controller = getController(taskId);
1529
+ if (controller)
1530
+ controller.abort();
1531
+ const updated = updateTask(taskId, {
1532
+ state: 'cancelled',
1533
+ statusMessage: 'The task was cancelled by request.',
1534
+ });
1535
+ unregisterController(taskId);
1536
+ return toWireFormat(updated);
1537
+ });
1538
+ this.server.setRequestHandler(GetTaskPayloadRequestSchema, async (request) => {
1539
+ const { taskId } = request.params;
1540
+ const task = getTask(taskId);
1541
+ if (!task)
1542
+ throw new Error(`Task not found: ${taskId}`);
1543
+ // Helper to format terminal task result
1544
+ const formatResult = (t) => {
1545
+ if (t.state === 'failed') {
1546
+ return {
1547
+ content: [{ type: 'text', text: t.error || 'Task failed' }],
1548
+ isError: true,
1549
+ _meta: relatedTaskMeta(taskId),
1550
+ };
1551
+ }
1552
+ if (t.state === 'cancelled') {
1553
+ return {
1554
+ content: [{ type: 'text', text: 'Task was cancelled.' }],
1555
+ isError: false,
1556
+ _meta: relatedTaskMeta(taskId),
1557
+ };
1558
+ }
1559
+ // Completed
1560
+ if (t.result && typeof t.result === 'object' && 'content' in t.result) {
1561
+ return { ...t.result, _meta: relatedTaskMeta(taskId) };
1562
+ }
1563
+ const text = typeof t.result === 'string' ? t.result : JSON.stringify(t.result ?? null);
1564
+ return {
1565
+ content: [{ type: 'text', text }],
1566
+ isError: false,
1567
+ _meta: relatedTaskMeta(taskId),
1568
+ };
1569
+ };
1570
+ // Already terminal — return immediately
1571
+ if (TERMINAL_STATES.includes(task.state)) {
1572
+ return formatResult(task);
1573
+ }
1574
+ // If input_required, try to get input via elicitation
1575
+ if (task.state === 'input_required' && task.input) {
1576
+ const inputProvider = this.createMCPInputProvider(this.server);
1577
+ try {
1578
+ const value = await inputProvider(task.input);
1579
+ resolveTaskInput(taskId, value);
1580
+ }
1581
+ catch {
1582
+ resolveTaskInput(taskId, null);
1583
+ }
1584
+ }
1585
+ // Block until terminal (max 5 min per call)
1586
+ try {
1587
+ const abortController = new AbortController();
1588
+ const timeout = setTimeout(() => abortController.abort(), 300000);
1589
+ try {
1590
+ while (true) {
1591
+ const current = await waitForTerminalOrInput(taskId, abortController.signal);
1592
+ if (TERMINAL_STATES.includes(current.state)) {
1593
+ return formatResult(current);
1594
+ }
1595
+ if (current.state === 'input_required' && current.input) {
1596
+ const inputProvider = this.createMCPInputProvider(this.server);
1597
+ try {
1598
+ const value = await inputProvider(current.input);
1599
+ resolveTaskInput(taskId, value);
1600
+ }
1601
+ catch {
1602
+ resolveTaskInput(taskId, null);
1603
+ }
1604
+ }
1605
+ }
1606
+ }
1607
+ finally {
1608
+ clearTimeout(timeout);
1609
+ }
1610
+ }
1611
+ catch {
1612
+ const current = getTask(taskId);
1613
+ if (current && TERMINAL_STATES.includes(current.state)) {
1614
+ return formatResult(current);
1615
+ }
1616
+ return {
1617
+ content: [{ type: 'text', text: `Task ${taskId} is still running.` }],
1618
+ isError: false,
1619
+ _meta: relatedTaskMeta(taskId),
1620
+ };
1621
+ }
1622
+ });
1389
1623
  }
1390
1624
  /**
1391
1625
  * Format tool result as text
@@ -1733,6 +1967,8 @@ export class PhotonServer {
1733
1967
  const metadata = await extractor.extractFullMetadata();
1734
1968
  const isStateful = metadata.stateful;
1735
1969
  // Start daemon for stateful photons (enables cross-client communication)
1970
+ // Channel mode also uses the daemon — the singleton instance holds the
1971
+ // bot connection, and multiple MCP clients subscribe to its events
1736
1972
  if (isStateful) {
1737
1973
  const photonName = metadata.name;
1738
1974
  this.daemonName = photonName; // Store for subscription
@@ -1767,7 +2003,9 @@ export class PhotonServer {
1767
2003
  this.mcp = await this.loader.loadFile(this.options.filePath);
1768
2004
  }
1769
2005
  }
1770
- // Subscribe to daemon channels for cross-process notifications
2006
+ // Subscribe to daemon channels for cross-process notifications.
2007
+ // In channel mode, handleChannelMessage intercepts 'channel-push' events
2008
+ // and translates them to notifications/claude/channel for the connected client.
1771
2009
  await this.subscribeToChannels();
1772
2010
  // Start with the appropriate transport
1773
2011
  const transport = this.options.transport || 'stdio';
@@ -1828,6 +2066,40 @@ export class PhotonServer {
1828
2066
  if (process.env.PHOTON_DEBUG_EVENTS === '1') {
1829
2067
  console.error(`[PHOTON-SERVER] Received daemon message on ${String(msg.channel)}: event=${String(msg.event)}`);
1830
2068
  }
2069
+ // Channel permission responses — photon called this.channel.respond()
2070
+ if (this.options.channelMode && String(msg.channel).endsWith(':channel-permission-response')) {
2071
+ const data = msg.data;
2072
+ if (data?.request_id && data?.behavior) {
2073
+ this.respondToPermission({
2074
+ request_id: data.request_id,
2075
+ behavior: data.behavior,
2076
+ });
2077
+ }
2078
+ return;
2079
+ }
2080
+ // Channel events — translate to client-specific channel notifications.
2081
+ // Each declared target (e.g. 'claude') gets its own notification method.
2082
+ if (this.options.channelMode && String(msg.channel).endsWith(':channel-push')) {
2083
+ const pushData = msg.data;
2084
+ const methods = this.getChannelNotificationMethods();
2085
+ if (methods.length === 0)
2086
+ return;
2087
+ const content = typeof pushData?.content === 'string' ? pushData.content : '';
2088
+ const meta = pushData?.meta || {};
2089
+ try {
2090
+ for (const method of methods) {
2091
+ const notification = { method, params: { content, meta } };
2092
+ await this.server.notification(notification);
2093
+ for (const session of Array.from(this.sseSessions.values())) {
2094
+ await session.server.notification(notification);
2095
+ }
2096
+ }
2097
+ }
2098
+ catch (e) {
2099
+ this.log('debug', 'Channel notification failed', { error: getErrorMessage(e) });
2100
+ }
2101
+ return; // Don't also forward as a regular photon event
2102
+ }
1831
2103
  // Use STANDARD notification with embedded photon data
1832
2104
  // Claude Desktop will forward this (it's a standard notification)
1833
2105
  // Our bridge extracts _photon and routes to the appropriate event handler
@@ -1877,9 +2149,15 @@ export class PhotonServer {
1877
2149
  interceptTransportForRawCapabilities(transport, targetServer) {
1878
2150
  const origOnMessage = transport.onmessage;
1879
2151
  transport.onmessage = (message, extra) => {
1880
- // Capture raw capabilities from initialize request
1881
- if (message?.method === 'initialize' && message?.params?.capabilities) {
1882
- this.rawClientCapabilities.set(targetServer, message.params.capabilities);
2152
+ // Capture raw capabilities and client name from initialize request
2153
+ if (message?.method === 'initialize' && message?.params) {
2154
+ if (message.params.capabilities) {
2155
+ this.rawClientCapabilities.set(targetServer, message.params.capabilities);
2156
+ }
2157
+ }
2158
+ // Intercept channel permission requests from the client
2159
+ if (this.options.channelMode && message?.method?.endsWith('/channel/permission_request')) {
2160
+ this.handlePermissionRequest(message.params);
1883
2161
  }
1884
2162
  origOnMessage?.(message, extra);
1885
2163
  };
@@ -1898,15 +2176,16 @@ export class PhotonServer {
1898
2176
  */
1899
2177
  async startSSE() {
1900
2178
  const port = this.options.port || 3000;
1901
- const useStreamableHTTP = !!this.options.embeddedAssets;
1902
2179
  const ssePath = '/mcp';
1903
2180
  const messagesPath = '/mcp/messages';
1904
- // For compiled binaries with Beam UI, use a minimal Streamable HTTP transport
1905
- // that matches what the Beam frontend sends:
1906
- // POST /mcp — JSON-RPC request, Accept: application/json JSON response
1907
- // GET /mcp?sessionId=X EventSource for server-to-client notifications
2181
+ // Always use Streamable HTTP transport for SSE mode.
2182
+ // The legacy SSE transport (endpoint event + /mcp/messages?sessionId=) is deprecated
2183
+ // in the MCP spec and not supported by modern clients (e.g. llama.cpp).
2184
+ // Streamable HTTP uses the standard protocol:
2185
+ // POST /mcp — JSON-RPC request → JSON response
2186
+ // GET /mcp — SSE stream for server-to-client notifications
1908
2187
  let beamTransport = null;
1909
- if (useStreamableHTTP) {
2188
+ {
1910
2189
  const photonName = this.mcp?.name || 'photon';
1911
2190
  beamTransport = new BeamCompatTransport(photonName, {
1912
2191
  description: this.mcp?.description,
@@ -1978,7 +2257,7 @@ export class PhotonServer {
1978
2257
  res.writeHead(204, {
1979
2258
  'Access-Control-Allow-Origin': '*',
1980
2259
  'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
1981
- 'Access-Control-Allow-Headers': 'Content-Type, Accept, Mcp-Session-Id',
2260
+ 'Access-Control-Allow-Headers': 'Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version',
1982
2261
  'Access-Control-Expose-Headers': 'Mcp-Session-Id',
1983
2262
  });
1984
2263
  res.end();
@@ -2345,17 +2624,7 @@ export class PhotonServer {
2345
2624
  });
2346
2625
  await new Promise((resolve) => {
2347
2626
  this.httpServer.listen(port, () => {
2348
- this.log('info', `${this.mcp.name} MCP server listening`, {
2349
- transport: 'sse',
2350
- port,
2351
- devMode: this.devMode,
2352
- });
2353
- this.log('debug', 'SSE endpoints ready', {
2354
- baseUrl: `http://localhost:${port}`,
2355
- ssePath,
2356
- messagesPath,
2357
- playground: this.devMode ? `http://localhost:${port}/playground` : undefined,
2358
- });
2627
+ process.stdout.write(`⚡ ${this.mcp.name} http://localhost:${port}${ssePath}\n`);
2359
2628
  resolve();
2360
2629
  });
2361
2630
  });