@portel/photon 1.19.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 (146) 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.d.ts.map +1 -1
  5. package/dist/auto-ui/beam/routes/api-config.js +165 -24
  6. package/dist/auto-ui/beam/routes/api-config.js.map +1 -1
  7. package/dist/auto-ui/beam/routes/api-marketplace.d.ts.map +1 -1
  8. package/dist/auto-ui/beam/routes/api-marketplace.js +14 -1
  9. package/dist/auto-ui/beam/routes/api-marketplace.js.map +1 -1
  10. package/dist/auto-ui/beam.d.ts.map +1 -1
  11. package/dist/auto-ui/beam.js +187 -77
  12. package/dist/auto-ui/beam.js.map +1 -1
  13. package/dist/auto-ui/bridge/index.d.ts.map +1 -1
  14. package/dist/auto-ui/bridge/index.js +17 -0
  15. package/dist/auto-ui/bridge/index.js.map +1 -1
  16. package/dist/auto-ui/bridge/renderers.d.ts.map +1 -1
  17. package/dist/auto-ui/bridge/renderers.js +12 -4
  18. package/dist/auto-ui/bridge/renderers.js.map +1 -1
  19. package/dist/auto-ui/streamable-http-transport.d.ts +1 -0
  20. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  21. package/dist/auto-ui/streamable-http-transport.js +179 -44
  22. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  23. package/dist/auto-ui/types.d.ts +12 -0
  24. package/dist/auto-ui/types.d.ts.map +1 -1
  25. package/dist/auto-ui/types.js.map +1 -1
  26. package/dist/beam-form.bundle.js +63 -185
  27. package/dist/beam-form.bundle.js.map +4 -4
  28. package/dist/beam.bundle.js +2115 -761
  29. package/dist/beam.bundle.js.map +4 -4
  30. package/dist/capability-negotiator.d.ts +67 -0
  31. package/dist/capability-negotiator.d.ts.map +1 -0
  32. package/dist/capability-negotiator.js +104 -0
  33. package/dist/capability-negotiator.js.map +1 -0
  34. package/dist/channel-manager.d.ts +122 -0
  35. package/dist/channel-manager.d.ts.map +1 -0
  36. package/dist/channel-manager.js +266 -0
  37. package/dist/channel-manager.js.map +1 -0
  38. package/dist/cli/commands/beam.d.ts.map +1 -1
  39. package/dist/cli/commands/beam.js +47 -30
  40. package/dist/cli/commands/beam.js.map +1 -1
  41. package/dist/cli/commands/build.d.ts.map +1 -1
  42. package/dist/cli/commands/build.js +27 -2
  43. package/dist/cli/commands/build.js.map +1 -1
  44. package/dist/cli/commands/daemon.d.ts.map +1 -1
  45. package/dist/cli/commands/daemon.js +12 -6
  46. package/dist/cli/commands/daemon.js.map +1 -1
  47. package/dist/cli/commands/mcp.d.ts.map +1 -1
  48. package/dist/cli/commands/mcp.js +18 -6
  49. package/dist/cli/commands/mcp.js.map +1 -1
  50. package/dist/cli/commands/package.d.ts.map +1 -1
  51. package/dist/cli/commands/package.js +25 -7
  52. package/dist/cli/commands/package.js.map +1 -1
  53. package/dist/cli/commands/serve.d.ts.map +1 -1
  54. package/dist/cli/commands/serve.js +14 -2
  55. package/dist/cli/commands/serve.js.map +1 -1
  56. package/dist/cli-alias.d.ts.map +1 -1
  57. package/dist/cli-alias.js +2 -3
  58. package/dist/cli-alias.js.map +1 -1
  59. package/dist/context-store.d.ts +4 -4
  60. package/dist/context-store.d.ts.map +1 -1
  61. package/dist/context-store.js +18 -15
  62. package/dist/context-store.js.map +1 -1
  63. package/dist/context.d.ts +25 -2
  64. package/dist/context.d.ts.map +1 -1
  65. package/dist/context.js +69 -4
  66. package/dist/context.js.map +1 -1
  67. package/dist/daemon/client.d.ts.map +1 -1
  68. package/dist/daemon/client.js +16 -1
  69. package/dist/daemon/client.js.map +1 -1
  70. package/dist/daemon/manager.d.ts +2 -0
  71. package/dist/daemon/manager.d.ts.map +1 -1
  72. package/dist/daemon/manager.js +40 -8
  73. package/dist/daemon/manager.js.map +1 -1
  74. package/dist/daemon/server.js +89 -64
  75. package/dist/daemon/server.js.map +1 -1
  76. package/dist/daemon/worker-host.js +7 -0
  77. package/dist/daemon/worker-host.js.map +1 -1
  78. package/dist/daemon/worker-manager.d.ts.map +1 -1
  79. package/dist/daemon/worker-manager.js +79 -17
  80. package/dist/daemon/worker-manager.js.map +1 -1
  81. package/dist/daemon/worker-protocol.d.ts +3 -0
  82. package/dist/daemon/worker-protocol.d.ts.map +1 -1
  83. package/dist/deploy/cloudflare.d.ts.map +1 -1
  84. package/dist/deploy/cloudflare.js +2 -4
  85. package/dist/deploy/cloudflare.js.map +1 -1
  86. package/dist/loader.d.ts +11 -1
  87. package/dist/loader.d.ts.map +1 -1
  88. package/dist/loader.js +129 -13
  89. package/dist/loader.js.map +1 -1
  90. package/dist/marketplace-manager.d.ts +7 -1
  91. package/dist/marketplace-manager.d.ts.map +1 -1
  92. package/dist/marketplace-manager.js +165 -61
  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 +40 -21
  100. package/dist/photon-cli-runner.js.map +1 -1
  101. package/dist/photon-doc-extractor.d.ts.map +1 -1
  102. package/dist/photon-doc-extractor.js +59 -15
  103. package/dist/photon-doc-extractor.js.map +1 -1
  104. package/dist/resource-server.d.ts +105 -0
  105. package/dist/resource-server.d.ts.map +1 -0
  106. package/dist/resource-server.js +723 -0
  107. package/dist/resource-server.js.map +1 -0
  108. package/dist/serv/auth/jwt.d.ts +2 -0
  109. package/dist/serv/auth/jwt.d.ts.map +1 -1
  110. package/dist/serv/auth/jwt.js +11 -5
  111. package/dist/serv/auth/jwt.js.map +1 -1
  112. package/dist/serv/vault/token-vault.d.ts +2 -0
  113. package/dist/serv/vault/token-vault.d.ts.map +1 -1
  114. package/dist/serv/vault/token-vault.js +6 -0
  115. package/dist/serv/vault/token-vault.js.map +1 -1
  116. package/dist/server.d.ts +20 -149
  117. package/dist/server.d.ts.map +1 -1
  118. package/dist/server.js +246 -1233
  119. package/dist/server.js.map +1 -1
  120. package/dist/shared/audit.d.ts.map +1 -1
  121. package/dist/shared/audit.js +7 -0
  122. package/dist/shared/audit.js.map +1 -1
  123. package/dist/shared/security.d.ts +10 -0
  124. package/dist/shared/security.d.ts.map +1 -1
  125. package/dist/shared/security.js +27 -0
  126. package/dist/shared/security.js.map +1 -1
  127. package/dist/shared-utils.d.ts +4 -0
  128. package/dist/shared-utils.d.ts.map +1 -1
  129. package/dist/shared-utils.js +22 -0
  130. package/dist/shared-utils.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/template-manager.d.ts.map +1 -1
  136. package/dist/template-manager.js +56 -234
  137. package/dist/template-manager.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/package.json +3 -3
@@ -68,6 +68,28 @@ function decodeJWTCaller(authHeader) {
68
68
  const sessions = new Map();
69
69
  const pendingElicitations = new Map();
70
70
  const APPROVALS_DIR = join(homedir(), '.photon', 'state');
71
+ // Simple async mutex for file operations
72
+ function createMutex() {
73
+ let locked = Promise.resolve();
74
+ return {
75
+ async acquire(fn) {
76
+ let release;
77
+ const next = new Promise((resolve) => {
78
+ release = resolve;
79
+ });
80
+ const prev = locked;
81
+ locked = next;
82
+ await prev;
83
+ try {
84
+ return await fn();
85
+ }
86
+ finally {
87
+ release();
88
+ }
89
+ },
90
+ };
91
+ }
92
+ const approvalsMutex = createMutex();
71
93
  function approvalsPath(photonName) {
72
94
  return join(APPROVALS_DIR, photonName, 'approvals.json');
73
95
  }
@@ -92,18 +114,22 @@ async function saveApprovals(photonName, approvals) {
92
114
  await writeFile(approvalsPath(photonName), JSON.stringify(approvals, null, 2));
93
115
  }
94
116
  async function addApproval(approval) {
95
- const approvals = await loadApprovals(approval.photon);
96
- approvals.push(approval);
97
- await saveApprovals(approval.photon, approvals);
117
+ return approvalsMutex.acquire(async () => {
118
+ const approvals = await loadApprovals(approval.photon);
119
+ approvals.push(approval);
120
+ await saveApprovals(approval.photon, approvals);
121
+ });
98
122
  }
99
123
  async function resolveApproval(photonName, approvalId, status) {
100
- const approvals = await loadApprovals(photonName);
101
- const idx = approvals.findIndex((a) => a.id === approvalId);
102
- if (idx === -1)
103
- return undefined;
104
- approvals[idx].status = status;
105
- await saveApprovals(photonName, approvals);
106
- return approvals[idx];
124
+ return approvalsMutex.acquire(async () => {
125
+ const approvals = await loadApprovals(photonName);
126
+ const idx = approvals.findIndex((a) => a.id === approvalId);
127
+ if (idx === -1)
128
+ return undefined;
129
+ approvals[idx].status = status;
130
+ await saveApprovals(photonName, approvals);
131
+ return approvals[idx];
132
+ });
107
133
  }
108
134
  async function getAllPendingApprovals(photonNames) {
109
135
  const all = [];
@@ -146,16 +172,114 @@ function parseDurationToMs(duration) {
146
172
  return 5 * 60 * 1000;
147
173
  }
148
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
+ }
149
259
  // Clean up old sessions periodically (30 min timeout)
150
260
  const SESSION_TIMEOUT_MS = 30 * 60 * 1000;
151
- setInterval(() => {
152
- const now = Date.now();
153
- for (const [id, session] of sessions) {
154
- if (now - session.lastActivity.getTime() > SESSION_TIMEOUT_MS) {
155
- sessions.delete(id);
261
+ let sessionCleanupInterval = null;
262
+ function startSessionCleanup() {
263
+ if (sessionCleanupInterval)
264
+ return;
265
+ sessionCleanupInterval = setInterval(() => {
266
+ const now = Date.now();
267
+ for (const [id, session] of sessions) {
268
+ if (now - session.lastActivity.getTime() > SESSION_TIMEOUT_MS) {
269
+ sessions.delete(id);
270
+ }
156
271
  }
272
+ }, 60 * 1000);
273
+ sessionCleanupInterval.unref();
274
+ }
275
+ export function stopSessionCleanup() {
276
+ if (sessionCleanupInterval) {
277
+ clearInterval(sessionCleanupInterval);
278
+ sessionCleanupInterval = null;
157
279
  }
158
- }, 60 * 1000);
280
+ }
281
+ // Start cleanup on module load
282
+ startSessionCleanup();
159
283
  function getOrCreateSession(sessionId) {
160
284
  if (sessionId && sessions.has(sessionId)) {
161
285
  const session = sessions.get(sessionId);
@@ -506,8 +630,17 @@ const handlers = {
506
630
  };
507
631
  }
508
632
  pendingElicitations.delete(elicitationId);
509
- if (pending.timer)
510
- 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
+ }
511
644
  if (params?.cancelled) {
512
645
  pending.reject(new Error('Elicitation cancelled by user'));
513
646
  }
@@ -540,8 +673,7 @@ const handlers = {
540
673
  const pending = pendingElicitations.get(params.approvalId);
541
674
  if (pending) {
542
675
  pendingElicitations.delete(params.approvalId);
543
- if (pending.timer)
544
- clearTimeout(pending.timer);
676
+ cleanupElicitation(pending);
545
677
  if (params.approved) {
546
678
  pending.resolve(true);
547
679
  }
@@ -630,11 +762,19 @@ const handlers = {
630
762
  inputSchema: method.params || { type: 'object', properties: {} },
631
763
  'x-photon-id': photon.id, // Unique ID (hash of path) for subscriptions
632
764
  'x-photon-path': photon.path, // File path for View Source
765
+ // Editable when the photon file sits directly in the base dir (user-owned).
766
+ // Marketplace-installed photons live in a subdirectory and are read-only.
767
+ 'x-photon-editable': photon.path
768
+ ? dirname(photon.path) === (ctx.workingDir || '')
769
+ : false,
633
770
  'x-photon-description': photon.description,
634
771
  'x-photon-icon': photon.icon,
635
772
  'x-photon-internal': photon.internal,
636
773
  'x-photon-stateful': photon.stateful || false,
637
774
  'x-photon-has-settings': photon.hasSettings || false,
775
+ 'x-photon-short-name': photon.shortName,
776
+ 'x-photon-namespace': photon.namespace,
777
+ 'x-photon-qualified-name': photon.qualifiedName,
638
778
  'x-photon-install-source': photon.installSource,
639
779
  'x-photon-prompt-count': photon.promptCount ?? 0,
640
780
  'x-photon-resource-count': photon.resourceCount ?? 0,
@@ -1111,7 +1251,7 @@ const handlers = {
1111
1251
  const elicitResult = await requestBeamElicitation({
1112
1252
  ask: 'confirm',
1113
1253
  message: `"${methodName}" is a destructive operation. Continue?`,
1114
- });
1254
+ }, { photonName: serverName, methodName });
1115
1255
  if (elicitResult.action !== 'accept' || elicitResult.content === false) {
1116
1256
  return {
1117
1257
  jsonrpc: '2.0',
@@ -1155,7 +1295,7 @@ const handlers = {
1155
1295
  ask: 'select',
1156
1296
  message: 'Select an instance',
1157
1297
  options: selectOptions,
1158
- });
1298
+ }, { photonName: serverName, methodName });
1159
1299
  if (elicitResult.action !== 'accept' || !elicitResult.content) {
1160
1300
  return {
1161
1301
  jsonrpc: '2.0',
@@ -1173,7 +1313,7 @@ const handlers = {
1173
1313
  ask: 'text',
1174
1314
  message: 'Enter a name for the new instance',
1175
1315
  placeholder: 'e.g. groceries, work, personal',
1176
- });
1316
+ }, { photonName: serverName, methodName });
1177
1317
  if (nameResult.action !== 'accept' || !nameResult.content) {
1178
1318
  return {
1179
1319
  jsonrpc: '2.0',
@@ -2052,7 +2192,7 @@ const handlers = {
2052
2192
  }
2053
2193
  // If input_required right now, handle elicitation before waiting
2054
2194
  if (task.state === 'input_required' && task.input) {
2055
- const elicitResult = await requestBeamElicitation(task.input);
2195
+ const elicitResult = await requestBeamElicitation(task.input, { photonName: task.photon || 'task', methodName: task.method || taskId });
2056
2196
  if (elicitResult.action === 'accept') {
2057
2197
  resolveTaskInput(taskId, elicitResult.content);
2058
2198
  }
@@ -2073,7 +2213,7 @@ const handlers = {
2073
2213
  }
2074
2214
  if (current.state === 'input_required' && current.input) {
2075
2215
  // Send elicitation to the client
2076
- const elicitResult = await requestBeamElicitation(current.input);
2216
+ const elicitResult = await requestBeamElicitation(current.input, { photonName: task.photon || 'task', methodName: task.method || taskId });
2077
2217
  if (elicitResult.action === 'accept') {
2078
2218
  resolveTaskInput(taskId, elicitResult.content);
2079
2219
  }
@@ -2360,7 +2500,7 @@ async function handleBeamRemove(req, ctx, args) {
2360
2500
  const elicitResult = await requestBeamElicitation({
2361
2501
  ask: 'confirm',
2362
2502
  message: `Remove "${photonName}"? The photon and its assets will be moved to trash.`,
2363
- });
2503
+ }, { photonName, methodName: 'remove' });
2364
2504
  if (elicitResult.action !== 'accept' || elicitResult.content === false) {
2365
2505
  return {
2366
2506
  jsonrpc: '2.0',
@@ -3368,8 +3508,7 @@ export function sendToSession(sessionId, method, params) {
3368
3508
  */
3369
3509
  export function requestExternalElicitation(mcpName, request) {
3370
3510
  const elicitationId = randomUUID();
3371
- return new Promise((resolve, reject) => {
3372
- // Store pending elicitation
3511
+ return new Promise((resolve) => {
3373
3512
  const pending = {
3374
3513
  resolve: (value) => {
3375
3514
  resolve({ action: 'accept', content: value });
@@ -3382,7 +3521,10 @@ export function requestExternalElicitation(mcpName, request) {
3382
3521
  resolve({ action: 'decline' });
3383
3522
  }
3384
3523
  },
3385
- sessionId: '', // External MCP elicitations aren't tied to a specific session
3524
+ sessionId: '',
3525
+ photonName: mcpName,
3526
+ methodName: 'elicitation',
3527
+ message: request.message,
3386
3528
  };
3387
3529
  pendingElicitations.set(elicitationId, pending);
3388
3530
  // Broadcast elicitation request to all Beam clients
@@ -3394,13 +3536,8 @@ export function requestExternalElicitation(mcpName, request) {
3394
3536
  schema: request.requestedSchema,
3395
3537
  url: request.url,
3396
3538
  });
3397
- // Timeout after 5 minutes (timer cleared on normal resolve)
3398
- pending.timer = setTimeout(() => {
3399
- if (pendingElicitations.has(elicitationId)) {
3400
- pendingElicitations.delete(elicitationId);
3401
- resolve({ action: 'cancel' });
3402
- }
3403
- }, 300000);
3539
+ // Two-phase timeout: 30s modal pending queue with keepalives → 30min expiry
3540
+ setupElicitationTimeout(elicitationId, pending, resolve);
3404
3541
  });
3405
3542
  }
3406
3543
  /**
@@ -3408,7 +3545,7 @@ export function requestExternalElicitation(mcpName, request) {
3408
3545
  * Unlike requestExternalElicitation which uses MCP form/url mode, this sends
3409
3546
  * the ask type directly so the elicitation modal renders the appropriate UI.
3410
3547
  */
3411
- function requestBeamElicitation(data) {
3548
+ function requestBeamElicitation(data, context) {
3412
3549
  const elicitationId = randomUUID();
3413
3550
  return new Promise((resolve) => {
3414
3551
  const pending = {
@@ -3424,6 +3561,9 @@ function requestBeamElicitation(data) {
3424
3561
  }
3425
3562
  },
3426
3563
  sessionId: '',
3564
+ photonName: context?.photonName,
3565
+ methodName: context?.methodName,
3566
+ message: data.message,
3427
3567
  };
3428
3568
  pendingElicitations.set(elicitationId, pending);
3429
3569
  // Broadcast with Photon-native ask format (not MCP form mode)
@@ -3431,13 +3571,8 @@ function requestBeamElicitation(data) {
3431
3571
  elicitationId,
3432
3572
  ...data,
3433
3573
  });
3434
- // Timeout after 5 minutes (timer cleared on normal resolve)
3435
- pending.timer = setTimeout(() => {
3436
- if (pendingElicitations.has(elicitationId)) {
3437
- pendingElicitations.delete(elicitationId);
3438
- resolve({ action: 'cancel' });
3439
- }
3440
- }, 300000);
3574
+ // Two-phase timeout: 30s modal pending queue with keepalives → 30min expiry
3575
+ setupElicitationTimeout(elicitationId, pending, resolve);
3441
3576
  });
3442
3577
  }
3443
3578
  //# sourceMappingURL=streamable-http-transport.js.map