@portel/photon 1.20.0 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/auto-ui/beam/routes/api-config.d.ts.map +1 -1
  2. package/dist/auto-ui/beam/routes/api-config.js +161 -20
  3. package/dist/auto-ui/beam/routes/api-config.js.map +1 -1
  4. package/dist/auto-ui/beam.d.ts.map +1 -1
  5. package/dist/auto-ui/beam.js +4 -3
  6. package/dist/auto-ui/beam.js.map +1 -1
  7. package/dist/auto-ui/bridge/renderers.d.ts.map +1 -1
  8. package/dist/auto-ui/bridge/renderers.js +12 -4
  9. package/dist/auto-ui/bridge/renderers.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 +115 -28
  12. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  13. package/dist/beam-form.bundle.js +19 -182
  14. package/dist/beam-form.bundle.js.map +4 -4
  15. package/dist/beam.bundle.js +743 -311
  16. package/dist/beam.bundle.js.map +4 -4
  17. package/dist/cli/commands/beam.d.ts.map +1 -1
  18. package/dist/cli/commands/beam.js +47 -30
  19. package/dist/cli/commands/beam.js.map +1 -1
  20. package/dist/cli/commands/build.d.ts.map +1 -1
  21. package/dist/cli/commands/build.js +27 -2
  22. package/dist/cli/commands/build.js.map +1 -1
  23. package/dist/cli/commands/daemon.d.ts.map +1 -1
  24. package/dist/cli/commands/daemon.js +12 -6
  25. package/dist/cli/commands/daemon.js.map +1 -1
  26. package/dist/cli/commands/mcp.d.ts.map +1 -1
  27. package/dist/cli/commands/mcp.js +18 -6
  28. package/dist/cli/commands/mcp.js.map +1 -1
  29. package/dist/cli/commands/serve.d.ts.map +1 -1
  30. package/dist/cli/commands/serve.js +14 -2
  31. package/dist/cli/commands/serve.js.map +1 -1
  32. package/dist/cli-alias.d.ts.map +1 -1
  33. package/dist/cli-alias.js +2 -3
  34. package/dist/cli-alias.js.map +1 -1
  35. package/dist/context-store.d.ts +4 -4
  36. package/dist/context-store.d.ts.map +1 -1
  37. package/dist/context-store.js +18 -15
  38. package/dist/context-store.js.map +1 -1
  39. package/dist/context.d.ts +25 -2
  40. package/dist/context.d.ts.map +1 -1
  41. package/dist/context.js +69 -4
  42. package/dist/context.js.map +1 -1
  43. package/dist/daemon/client.d.ts.map +1 -1
  44. package/dist/daemon/client.js +4 -1
  45. package/dist/daemon/client.js.map +1 -1
  46. package/dist/daemon/manager.d.ts +2 -0
  47. package/dist/daemon/manager.d.ts.map +1 -1
  48. package/dist/daemon/manager.js +40 -8
  49. package/dist/daemon/manager.js.map +1 -1
  50. package/dist/daemon/server.js +59 -15
  51. package/dist/daemon/server.js.map +1 -1
  52. package/dist/daemon/worker-host.js +7 -0
  53. package/dist/daemon/worker-host.js.map +1 -1
  54. package/dist/daemon/worker-manager.d.ts.map +1 -1
  55. package/dist/daemon/worker-manager.js +58 -10
  56. package/dist/daemon/worker-manager.js.map +1 -1
  57. package/dist/daemon/worker-protocol.d.ts +3 -0
  58. package/dist/daemon/worker-protocol.d.ts.map +1 -1
  59. package/dist/deploy/cloudflare.d.ts.map +1 -1
  60. package/dist/deploy/cloudflare.js +2 -4
  61. package/dist/deploy/cloudflare.js.map +1 -1
  62. package/dist/loader.d.ts +7 -0
  63. package/dist/loader.d.ts.map +1 -1
  64. package/dist/loader.js +56 -2
  65. package/dist/loader.js.map +1 -1
  66. package/dist/marketplace-manager.d.ts +1 -1
  67. package/dist/marketplace-manager.d.ts.map +1 -1
  68. package/dist/marketplace-manager.js +5 -4
  69. package/dist/marketplace-manager.js.map +1 -1
  70. package/dist/photon-cli-runner.d.ts.map +1 -1
  71. package/dist/photon-cli-runner.js +40 -21
  72. package/dist/photon-cli-runner.js.map +1 -1
  73. package/dist/photon-doc-extractor.d.ts.map +1 -1
  74. package/dist/photon-doc-extractor.js +59 -15
  75. package/dist/photon-doc-extractor.js.map +1 -1
  76. package/dist/server.d.ts.map +1 -1
  77. package/dist/server.js +14 -16
  78. package/dist/server.js.map +1 -1
  79. package/dist/shared-utils.d.ts +4 -0
  80. package/dist/shared-utils.d.ts.map +1 -1
  81. package/dist/shared-utils.js +22 -0
  82. package/dist/shared-utils.js.map +1 -1
  83. package/dist/template-manager.d.ts.map +1 -1
  84. package/dist/template-manager.js +56 -234
  85. package/dist/template-manager.js.map +1 -1
  86. package/package.json +2 -2
@@ -172,6 +172,90 @@ function parseDurationToMs(duration) {
172
172
  return 5 * 60 * 1000;
173
173
  }
174
174
  }
175
+ // ── Elicitation lifecycle helpers ──
176
+ /** Duration before an unanswered elicitation moves to pending approvals */
177
+ const ELICITATION_DEFER_MS = 30_000; // 30 seconds
178
+ /** Maximum time an elicitation stays alive in pending queue */
179
+ const ELICITATION_EXPIRY_MS = 30 * 60 * 1000; // 30 minutes
180
+ /** Interval between progress keepalive broadcasts */
181
+ const KEEPALIVE_INTERVAL_MS = 25_000; // 25 seconds (under 30s SDK default)
182
+ /** Clean up all timers associated with a pending elicitation */
183
+ function cleanupElicitation(pending) {
184
+ if (pending.timer)
185
+ clearTimeout(pending.timer);
186
+ if (pending.deferTimer)
187
+ clearTimeout(pending.deferTimer);
188
+ if (pending.keepaliveInterval)
189
+ clearInterval(pending.keepaliveInterval);
190
+ }
191
+ /**
192
+ * Set up two-phase timeout for an elicitation:
193
+ * Phase 1 (30s): Modal shown to user. If no response, move to pending queue.
194
+ * Phase 2 (30min): Keepalive progress notifications sent. Final expiry cancels.
195
+ */
196
+ function setupElicitationTimeout(elicitationId, pending, resolve) {
197
+ const photon = pending.photonName || 'unknown';
198
+ const method = pending.methodName || 'unknown';
199
+ const message = pending.message || 'Approval required';
200
+ // Phase 1: After 30s without response, defer to pending queue
201
+ pending.deferTimer = setTimeout(() => {
202
+ // Only defer if still pending (user may have responded)
203
+ if (!pendingElicitations.has(elicitationId))
204
+ return;
205
+ const approvalId = elicitationId; // reuse ID for linking
206
+ pending.approvalId = approvalId;
207
+ const expiresAt = new Date(Date.now() + ELICITATION_EXPIRY_MS).toISOString();
208
+ // Write to persistent approval storage (fire-and-forget, non-blocking)
209
+ void addApproval({
210
+ id: approvalId,
211
+ photon,
212
+ method,
213
+ message,
214
+ status: 'pending',
215
+ createdAt: new Date().toISOString(),
216
+ expiresAt,
217
+ });
218
+ // Tell Beam frontend to close modal and show badge
219
+ broadcastToBeam('beam/elicitation-deferred', {
220
+ elicitationId,
221
+ approvalId,
222
+ photon,
223
+ method,
224
+ message,
225
+ expiresAt,
226
+ });
227
+ // Start progress keepalives to prevent external MCP client timeout
228
+ pending.keepaliveInterval = setInterval(() => {
229
+ if (!pendingElicitations.has(elicitationId)) {
230
+ if (pending.keepaliveInterval)
231
+ clearInterval(pending.keepaliveInterval);
232
+ return;
233
+ }
234
+ broadcastNotification('notifications/progress', {
235
+ progressToken: `approval_${elicitationId}`,
236
+ progress: 0,
237
+ total: 0,
238
+ message: `Waiting for user approval: ${message}`,
239
+ });
240
+ }, KEEPALIVE_INTERVAL_MS);
241
+ // Phase 2: Final expiry after 30 minutes
242
+ pending.timer = setTimeout(() => {
243
+ if (pendingElicitations.has(elicitationId)) {
244
+ cleanupElicitation(pending);
245
+ pendingElicitations.delete(elicitationId);
246
+ // Mark approval as expired on disk
247
+ void resolveApproval(photon, approvalId, 'rejected');
248
+ // Notify frontend
249
+ broadcastToBeam('beam/approval-resolved', {
250
+ approvalId,
251
+ photon,
252
+ status: 'expired',
253
+ });
254
+ resolve({ action: 'cancel' });
255
+ }
256
+ }, ELICITATION_EXPIRY_MS);
257
+ }, ELICITATION_DEFER_MS);
258
+ }
175
259
  // Clean up old sessions periodically (30 min timeout)
176
260
  const SESSION_TIMEOUT_MS = 30 * 60 * 1000;
177
261
  let sessionCleanupInterval = null;
@@ -546,8 +630,17 @@ const handlers = {
546
630
  };
547
631
  }
548
632
  pendingElicitations.delete(elicitationId);
549
- if (pending.timer)
550
- clearTimeout(pending.timer);
633
+ cleanupElicitation(pending);
634
+ // If this elicitation was deferred to approvals, resolve the approval on disk too
635
+ if (pending.approvalId) {
636
+ const status = params?.cancelled ? 'rejected' : 'approved';
637
+ await resolveApproval(pending.photonName || 'unknown', pending.approvalId, status);
638
+ broadcastToBeam('beam/approval-resolved', {
639
+ approvalId: pending.approvalId,
640
+ photon: pending.photonName || 'unknown',
641
+ status,
642
+ });
643
+ }
551
644
  if (params?.cancelled) {
552
645
  pending.reject(new Error('Elicitation cancelled by user'));
553
646
  }
@@ -580,8 +673,7 @@ const handlers = {
580
673
  const pending = pendingElicitations.get(params.approvalId);
581
674
  if (pending) {
582
675
  pendingElicitations.delete(params.approvalId);
583
- if (pending.timer)
584
- clearTimeout(pending.timer);
676
+ cleanupElicitation(pending);
585
677
  if (params.approved) {
586
678
  pending.resolve(true);
587
679
  }
@@ -1159,7 +1251,7 @@ const handlers = {
1159
1251
  const elicitResult = await requestBeamElicitation({
1160
1252
  ask: 'confirm',
1161
1253
  message: `"${methodName}" is a destructive operation. Continue?`,
1162
- });
1254
+ }, { photonName: serverName, methodName });
1163
1255
  if (elicitResult.action !== 'accept' || elicitResult.content === false) {
1164
1256
  return {
1165
1257
  jsonrpc: '2.0',
@@ -1203,7 +1295,7 @@ const handlers = {
1203
1295
  ask: 'select',
1204
1296
  message: 'Select an instance',
1205
1297
  options: selectOptions,
1206
- });
1298
+ }, { photonName: serverName, methodName });
1207
1299
  if (elicitResult.action !== 'accept' || !elicitResult.content) {
1208
1300
  return {
1209
1301
  jsonrpc: '2.0',
@@ -1221,7 +1313,7 @@ const handlers = {
1221
1313
  ask: 'text',
1222
1314
  message: 'Enter a name for the new instance',
1223
1315
  placeholder: 'e.g. groceries, work, personal',
1224
- });
1316
+ }, { photonName: serverName, methodName });
1225
1317
  if (nameResult.action !== 'accept' || !nameResult.content) {
1226
1318
  return {
1227
1319
  jsonrpc: '2.0',
@@ -2100,7 +2192,7 @@ const handlers = {
2100
2192
  }
2101
2193
  // If input_required right now, handle elicitation before waiting
2102
2194
  if (task.state === 'input_required' && task.input) {
2103
- const elicitResult = await requestBeamElicitation(task.input);
2195
+ const elicitResult = await requestBeamElicitation(task.input, { photonName: task.photon || 'task', methodName: task.method || taskId });
2104
2196
  if (elicitResult.action === 'accept') {
2105
2197
  resolveTaskInput(taskId, elicitResult.content);
2106
2198
  }
@@ -2121,7 +2213,7 @@ const handlers = {
2121
2213
  }
2122
2214
  if (current.state === 'input_required' && current.input) {
2123
2215
  // Send elicitation to the client
2124
- const elicitResult = await requestBeamElicitation(current.input);
2216
+ const elicitResult = await requestBeamElicitation(current.input, { photonName: task.photon || 'task', methodName: task.method || taskId });
2125
2217
  if (elicitResult.action === 'accept') {
2126
2218
  resolveTaskInput(taskId, elicitResult.content);
2127
2219
  }
@@ -2408,7 +2500,7 @@ async function handleBeamRemove(req, ctx, args) {
2408
2500
  const elicitResult = await requestBeamElicitation({
2409
2501
  ask: 'confirm',
2410
2502
  message: `Remove "${photonName}"? The photon and its assets will be moved to trash.`,
2411
- });
2503
+ }, { photonName, methodName: 'remove' });
2412
2504
  if (elicitResult.action !== 'accept' || elicitResult.content === false) {
2413
2505
  return {
2414
2506
  jsonrpc: '2.0',
@@ -3416,8 +3508,7 @@ export function sendToSession(sessionId, method, params) {
3416
3508
  */
3417
3509
  export function requestExternalElicitation(mcpName, request) {
3418
3510
  const elicitationId = randomUUID();
3419
- return new Promise((resolve, reject) => {
3420
- // Store pending elicitation
3511
+ return new Promise((resolve) => {
3421
3512
  const pending = {
3422
3513
  resolve: (value) => {
3423
3514
  resolve({ action: 'accept', content: value });
@@ -3430,7 +3521,10 @@ export function requestExternalElicitation(mcpName, request) {
3430
3521
  resolve({ action: 'decline' });
3431
3522
  }
3432
3523
  },
3433
- sessionId: '', // External MCP elicitations aren't tied to a specific session
3524
+ sessionId: '',
3525
+ photonName: mcpName,
3526
+ methodName: 'elicitation',
3527
+ message: request.message,
3434
3528
  };
3435
3529
  pendingElicitations.set(elicitationId, pending);
3436
3530
  // Broadcast elicitation request to all Beam clients
@@ -3442,13 +3536,8 @@ export function requestExternalElicitation(mcpName, request) {
3442
3536
  schema: request.requestedSchema,
3443
3537
  url: request.url,
3444
3538
  });
3445
- // Timeout after 5 minutes (timer cleared on normal resolve)
3446
- pending.timer = setTimeout(() => {
3447
- if (pendingElicitations.has(elicitationId)) {
3448
- pendingElicitations.delete(elicitationId);
3449
- resolve({ action: 'cancel' });
3450
- }
3451
- }, 300000);
3539
+ // Two-phase timeout: 30s modal pending queue with keepalives → 30min expiry
3540
+ setupElicitationTimeout(elicitationId, pending, resolve);
3452
3541
  });
3453
3542
  }
3454
3543
  /**
@@ -3456,7 +3545,7 @@ export function requestExternalElicitation(mcpName, request) {
3456
3545
  * Unlike requestExternalElicitation which uses MCP form/url mode, this sends
3457
3546
  * the ask type directly so the elicitation modal renders the appropriate UI.
3458
3547
  */
3459
- function requestBeamElicitation(data) {
3548
+ function requestBeamElicitation(data, context) {
3460
3549
  const elicitationId = randomUUID();
3461
3550
  return new Promise((resolve) => {
3462
3551
  const pending = {
@@ -3472,6 +3561,9 @@ function requestBeamElicitation(data) {
3472
3561
  }
3473
3562
  },
3474
3563
  sessionId: '',
3564
+ photonName: context?.photonName,
3565
+ methodName: context?.methodName,
3566
+ message: data.message,
3475
3567
  };
3476
3568
  pendingElicitations.set(elicitationId, pending);
3477
3569
  // Broadcast with Photon-native ask format (not MCP form mode)
@@ -3479,13 +3571,8 @@ function requestBeamElicitation(data) {
3479
3571
  elicitationId,
3480
3572
  ...data,
3481
3573
  });
3482
- // Timeout after 5 minutes (timer cleared on normal resolve)
3483
- pending.timer = setTimeout(() => {
3484
- if (pendingElicitations.has(elicitationId)) {
3485
- pendingElicitations.delete(elicitationId);
3486
- resolve({ action: 'cancel' });
3487
- }
3488
- }, 300000);
3574
+ // Two-phase timeout: 30s modal pending queue with keepalives → 30min expiry
3575
+ setupElicitationTimeout(elicitationId, pending, resolve);
3489
3576
  });
3490
3577
  }
3491
3578
  //# sourceMappingURL=streamable-http-transport.js.map