@agent-native/core 0.101.10 → 0.101.12

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 (91) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/client.ts +23 -1
  5. package/corpus/core/src/a2a/handlers.ts +65 -1
  6. package/corpus/core/src/a2a/server.ts +124 -0
  7. package/corpus/core/src/a2a/task-store.ts +253 -0
  8. package/corpus/core/src/a2a/types.ts +26 -0
  9. package/corpus/core/src/agent/production-agent.ts +5 -2
  10. package/corpus/core/src/coding-tools/run-code.ts +65 -12
  11. package/corpus/core/src/extensions/actions.ts +141 -7
  12. package/corpus/core/src/mcp/builtin-tools.ts +25 -1
  13. package/corpus/core/src/scripts/call-agent.ts +22 -3
  14. package/corpus/core/src/server/agent-chat-plugin.ts +86 -1
  15. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  16. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +59 -0
  17. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +5 -2
  18. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +13 -4
  19. package/corpus/templates/analytics/server/lib/real-data-actions.ts +63 -1
  20. package/corpus/templates/analytics/server/plugins/agent-chat.ts +30 -0
  21. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +23 -0
  22. package/corpus/templates/content/app/components/editor/MathRenderer.tsx +43 -0
  23. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +260 -4
  24. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +78 -0
  25. package/corpus/templates/content/app/global.css +53 -0
  26. package/corpus/templates/content/app/i18n/zh-TW.ts +13 -0
  27. package/corpus/templates/content/app/i18n-data.ts +163 -0
  28. package/corpus/templates/content/app/root.tsx +2 -0
  29. package/corpus/templates/content/changelog/2026-07-14-latex-equations-render-in-documents-public-pages-and-exports.md +6 -0
  30. package/corpus/templates/content/package.json +1 -0
  31. package/corpus/templates/content/shared/document-export.ts +296 -25
  32. package/corpus/templates/content/shared/inline-math.ts +128 -0
  33. package/corpus/templates/content/shared/math-rendering.ts +50 -0
  34. package/corpus/templates/content/shared/nfm.ts +17 -14
  35. package/corpus/templates/content/vite.config.ts +0 -1
  36. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +17 -0
  37. package/corpus/templates/mail/AGENTS.md +21 -19
  38. package/corpus/templates/mail/actions/create-attachment-upload.ts +49 -0
  39. package/corpus/templates/mail/changelog/2026-07-14-connected-agents-can-now-securely-upload-local-files-for-mai.md +6 -0
  40. package/corpus/templates/mail/server/handlers/media.ts +84 -61
  41. package/corpus/templates/mail/server/lib/attachment-upload-ticket.ts +234 -0
  42. package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -4
  43. package/corpus/templates/mail/server/lib/media-upload.ts +114 -0
  44. package/corpus/templates/mail/server/plugins/auth.ts +9 -1
  45. package/corpus/templates/mail/server/routes/api/media/attachment-upload/[uploadId].put.ts +1 -0
  46. package/dist/a2a/client.d.ts +5 -1
  47. package/dist/a2a/client.d.ts.map +1 -1
  48. package/dist/a2a/client.js +18 -1
  49. package/dist/a2a/client.js.map +1 -1
  50. package/dist/a2a/handlers.d.ts.map +1 -1
  51. package/dist/a2a/handlers.js +57 -8
  52. package/dist/a2a/handlers.js.map +1 -1
  53. package/dist/a2a/server.d.ts.map +1 -1
  54. package/dist/a2a/server.js +87 -1
  55. package/dist/a2a/server.js.map +1 -1
  56. package/dist/a2a/task-store.d.ts +27 -0
  57. package/dist/a2a/task-store.d.ts.map +1 -1
  58. package/dist/a2a/task-store.js +216 -0
  59. package/dist/a2a/task-store.js.map +1 -1
  60. package/dist/a2a/types.d.ts +24 -0
  61. package/dist/a2a/types.d.ts.map +1 -1
  62. package/dist/a2a/types.js.map +1 -1
  63. package/dist/agent/production-agent.d.ts +1 -0
  64. package/dist/agent/production-agent.d.ts.map +1 -1
  65. package/dist/agent/production-agent.js +5 -2
  66. package/dist/agent/production-agent.js.map +1 -1
  67. package/dist/coding-tools/run-code.js +64 -11
  68. package/dist/coding-tools/run-code.js.map +1 -1
  69. package/dist/collab/struct-routes.d.ts +1 -1
  70. package/dist/extensions/actions.d.ts.map +1 -1
  71. package/dist/extensions/actions.js +129 -7
  72. package/dist/extensions/actions.js.map +1 -1
  73. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  74. package/dist/mcp/builtin-tools.js +22 -3
  75. package/dist/mcp/builtin-tools.js.map +1 -1
  76. package/dist/notifications/routes.d.ts +2 -2
  77. package/dist/observability/routes.d.ts +5 -5
  78. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  79. package/dist/resources/handlers.d.ts +3 -3
  80. package/dist/scripts/call-agent.d.ts +1 -1
  81. package/dist/scripts/call-agent.d.ts.map +1 -1
  82. package/dist/scripts/call-agent.js +19 -1
  83. package/dist/scripts/call-agent.js.map +1 -1
  84. package/dist/secrets/routes.d.ts +9 -9
  85. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  86. package/dist/server/agent-chat-plugin.js +71 -2
  87. package/dist/server/agent-chat-plugin.js.map +1 -1
  88. package/dist/server/transcribe-voice.d.ts +1 -1
  89. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  90. package/package.json +1 -1
  91. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2258
31
- - template files: 5665
31
+ - template files: 5674
@@ -1,5 +1,17 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.101.12
4
+
5
+ ### Patch Changes
6
+
7
+ - 2ff004e: Fix workspace-file resolution and run-code paging/routing edge cases. `contentFromWorkspaceFile` now resolves the same file the run-code `workspaceRead`/`workspaceWrite` bridge sees (bridge scope first, then Resources), and fails closed on a bridge read error instead of silently falling back to a possibly-different same-path Resources body. `workspaceRead` returns null instead of a silently truncated prefix when a later page fails, and the run-code bridge now returns a distinct "not registered" (404) error for unknown tools instead of a misleading read-only access error.
8
+
9
+ ## 0.101.11
10
+
11
+ ### Patch Changes
12
+
13
+ - fd0ddb8: Wait for agent-card and A2A routes to finish initializing on cold starts, preserve human approval gates across A2A tasks, and carry exact one-time chat authorization through authenticated agent delegation.
14
+
3
15
  ## 0.101.10
4
16
 
5
17
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.101.10",
3
+ "version": "0.101.12",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -2,6 +2,7 @@ import * as jose from "jose";
2
2
 
3
3
  import { ssrfSafeFetch } from "../extensions/url-safety.js";
4
4
  import type {
5
+ A2AApprovedAction,
5
6
  AgentCard,
6
7
  JsonRpcRequest,
7
8
  JsonRpcResponse,
@@ -227,6 +228,7 @@ export class A2AClient {
227
228
  opts?: {
228
229
  contextId?: string;
229
230
  metadata?: Record<string, unknown>;
231
+ approvedActions?: A2AApprovedAction[];
230
232
  /**
231
233
  * If true, ask the server to return the task immediately in `working`
232
234
  * state and process the handler in the background. The caller should
@@ -242,6 +244,9 @@ export class A2AClient {
242
244
  message,
243
245
  contextId: opts?.contextId,
244
246
  metadata: opts?.metadata,
247
+ ...(opts?.approvedActions?.length
248
+ ? { approvedActions: opts.approvedActions }
249
+ : {}),
245
250
  ...(opts?.async ? { async: true } : {}),
246
251
  });
247
252
 
@@ -281,6 +286,7 @@ export class A2AClient {
281
286
  opts?: {
282
287
  contextId?: string;
283
288
  metadata?: Record<string, unknown>;
289
+ approvedActions?: A2AApprovedAction[];
284
290
  /** Total time to wait for completion. Default 5 min. */
285
291
  timeoutMs?: number;
286
292
  /** Poll interval. Default 2s. */
@@ -292,10 +298,18 @@ export class A2AClient {
292
298
  const submitted = await this.send(message, {
293
299
  contextId: opts?.contextId,
294
300
  metadata: opts?.metadata,
301
+ ...(opts?.approvedActions?.length
302
+ ? { approvedActions: opts.approvedActions }
303
+ : {}),
295
304
  async: true,
296
305
  });
297
306
 
298
- const terminalStates = new Set(["completed", "failed", "canceled"]);
307
+ const terminalStates = new Set([
308
+ "completed",
309
+ "failed",
310
+ "canceled",
311
+ "input-required",
312
+ ]);
299
313
  if (terminalStates.has(submitted.status.state)) return submitted;
300
314
 
301
315
  const timeoutMs = opts?.timeoutMs ?? 5 * 60_000;
@@ -542,6 +556,8 @@ export async function callAgent(
542
556
  orgSecret?: string;
543
557
  /** Origin used to build links back to the receiving app. */
544
558
  requestOrigin?: string;
559
+ /** Exact downstream actions explicitly authorized in the caller's chat. */
560
+ approvedActions?: A2AApprovedAction[];
545
561
  /**
546
562
  * Use async/poll instead of a single blocking POST. Recommended for
547
563
  * cross-app calls that may exceed a synchronous serverless request budget.
@@ -602,6 +618,9 @@ export async function callAgent(
602
618
  task = await client.sendAndWait(message, {
603
619
  contextId: opts?.contextId,
604
620
  metadata,
621
+ ...(opts?.approvedActions?.length
622
+ ? { approvedActions: opts.approvedActions }
623
+ : {}),
605
624
  timeoutMs: opts?.timeoutMs,
606
625
  pollIntervalMs: opts?.pollIntervalMs,
607
626
  onUpdate: opts?.onUpdate,
@@ -610,6 +629,9 @@ export async function callAgent(
610
629
  task = await client.send(message, {
611
630
  contextId: opts?.contextId,
612
631
  metadata,
632
+ ...(opts?.approvedActions?.length
633
+ ? { approvedActions: opts.approvedActions }
634
+ : {}),
613
635
  });
614
636
  }
615
637
 
@@ -27,8 +27,10 @@ import {
27
27
  settleProcessingA2ATask,
28
28
  touchQueuedA2ATaskDispatch,
29
29
  touchProcessingA2ATask,
30
+ pauseProcessingA2ATask,
30
31
  } from "./task-store.js";
31
32
  import type {
33
+ A2AApprovedAction,
32
34
  A2AConfig,
33
35
  A2AHandler,
34
36
  A2AHandlerContext,
@@ -46,6 +48,30 @@ const PORTABLE_FALLBACK_HANDOFF_TIMEOUT_MS = 1_000;
46
48
  const A2A_QUEUED_DISPATCH_STUCK_AFTER_MS = 10_000;
47
49
  const A2A_PROCESSING_STUCK_AFTER_MS = 5 * 60 * 1000;
48
50
  const A2A_PROCESSING_HEARTBEAT_MS = 30_000;
51
+ const MAX_A2A_APPROVED_ACTIONS = 10;
52
+
53
+ function trustedApprovedActions(
54
+ value: unknown,
55
+ event: any | undefined,
56
+ ): A2AApprovedAction[] | undefined {
57
+ // Static API keys and unsigned requests do not prove which user authorized
58
+ // a consequential action. Only a verified identity-bearing JWT may carry
59
+ // chat authorization across the A2A boundary.
60
+ if (!event?.context?.__a2aVerifiedEmail || !Array.isArray(value)) {
61
+ return undefined;
62
+ }
63
+ const approved = value
64
+ .slice(0, MAX_A2A_APPROVED_ACTIONS)
65
+ .filter(
66
+ (candidate): candidate is A2AApprovedAction =>
67
+ !!candidate &&
68
+ typeof candidate === "object" &&
69
+ typeof (candidate as Record<string, unknown>).tool === "string" &&
70
+ !!(candidate as Record<string, unknown>).tool,
71
+ )
72
+ .map((candidate) => ({ tool: candidate.tool, input: candidate.input }));
73
+ return approved.length > 0 ? approved : undefined;
74
+ }
49
75
 
50
76
  /**
51
77
  * Request origin is routing/link context, not an identity signal. Accept only
@@ -250,6 +276,9 @@ export async function processA2ATaskFromQueue(
250
276
  | Record<string, unknown>
251
277
  | null
252
278
  | undefined) ?? undefined;
279
+ const approvedActions = Array.isArray(processorMeta.approvedActions)
280
+ ? (processorMeta.approvedActions as A2AApprovedAction[])
281
+ : undefined;
253
282
 
254
283
  const resolvedOrgId = await resolveVerifiedA2AOrgId(
255
284
  verifiedEmail,
@@ -281,6 +310,7 @@ export async function processA2ATaskFromQueue(
281
310
  contextId,
282
311
  callerMetadata,
283
312
  event,
313
+ approvedActions,
284
314
  ),
285
315
  );
286
316
  } catch (err: any) {
@@ -386,6 +416,7 @@ function makeHandlerContext(
386
416
  contextId?: string,
387
417
  metadata?: Record<string, unknown>,
388
418
  event?: any,
419
+ approvedActions?: A2AApprovedAction[],
389
420
  ): {
390
421
  context: A2AHandlerContext;
391
422
  artifacts: Artifact[];
@@ -396,6 +427,7 @@ function makeHandlerContext(
396
427
  contextId,
397
428
  metadata,
398
429
  event,
430
+ approvedActions,
399
431
  writeArtifact(name, content, mimeType) {
400
432
  const artifact: Artifact = {
401
433
  name,
@@ -491,12 +523,14 @@ async function runHandlerAndPersist(
491
523
  contextId: string | undefined,
492
524
  metadata: Record<string, unknown> | undefined,
493
525
  event?: any,
526
+ approvedActions?: A2AApprovedAction[],
494
527
  ): Promise<void> {
495
528
  const { context, artifacts } = makeHandlerContext(
496
529
  taskId,
497
530
  contextId,
498
531
  metadata,
499
532
  event,
533
+ approvedActions,
500
534
  );
501
535
  try {
502
536
  const result = getHandler(config)(message, context);
@@ -510,6 +544,10 @@ async function runHandlerAndPersist(
510
544
  for await (const msg of result as AsyncGenerator<Message>) {
511
545
  lastMessage = msg;
512
546
  }
547
+ if (lastMessage?.metadata?.agentNativeTaskState === "input-required") {
548
+ await pauseProcessingA2ATask(taskId, lastMessage);
549
+ return;
550
+ }
513
551
  await settleProcessingA2ATask(taskId, {
514
552
  state: "completed",
515
553
  message: lastMessage,
@@ -520,6 +558,10 @@ async function runHandlerAndPersist(
520
558
 
521
559
  const handlerResult = await (result as Promise<A2AHandlerResult>);
522
560
  const allArtifacts = [...artifacts, ...(handlerResult.artifacts ?? [])];
561
+ if (handlerResult.taskState === "input-required") {
562
+ await pauseProcessingA2ATask(taskId, handlerResult.message);
563
+ return;
564
+ }
523
565
  await settleProcessingA2ATask(taskId, {
524
566
  state: "completed",
525
567
  message: handlerResult.message,
@@ -555,6 +597,7 @@ async function handleSend(
555
597
 
556
598
  const contextId = params.contextId as string | undefined;
557
599
  const metadata = params.metadata as Record<string, unknown> | undefined;
600
+ const approvedActions = trustedApprovedActions(params.approvedActions, event);
558
601
 
559
602
  // The JWT-verified caller email (set by mountA2A in server.ts) is the
560
603
  // single source of truth for task ownership — bound at creation, checked
@@ -619,6 +662,7 @@ async function handleSend(
619
662
  ...(requestOrigin ? { requestOrigin } : {}),
620
663
  contextId: contextId ?? null,
621
664
  callerMetadata: safeMetadata ?? null,
665
+ approvedActions: approvedActions ?? null,
622
666
  },
623
667
  };
624
668
  const task = await createTask(
@@ -642,7 +686,10 @@ async function handleSend(
642
686
  console.error("[a2a] Failed to dispatch process-task:", err);
643
687
  }
644
688
 
645
- return { ...jsonRpcResult(0, working ?? task), _id: 0 };
689
+ return {
690
+ ...jsonRpcResult(0, sanitizeTaskForResponse(working ?? task)),
691
+ _id: 0,
692
+ };
646
693
  }
647
694
 
648
695
  return withA2ARequestContext(metadata, event, async () => {
@@ -659,6 +706,7 @@ async function handleSend(
659
706
  contextId,
660
707
  trustedA2AMetadata(metadata, event),
661
708
  event,
709
+ approvedActions,
662
710
  );
663
711
 
664
712
  try {
@@ -673,6 +721,13 @@ async function handleSend(
673
721
  for await (const msg of result as AsyncGenerator<Message>) {
674
722
  lastMessage = msg;
675
723
  }
724
+ if (lastMessage?.metadata?.agentNativeTaskState === "input-required") {
725
+ const updated = await updateTask(task.id, {
726
+ state: "input-required",
727
+ message: lastMessage,
728
+ });
729
+ return { ...jsonRpcResult(0, updated), _id: 0 };
730
+ }
676
731
  const updated = await updateTask(task.id, {
677
732
  state: "completed",
678
733
  message: lastMessage,
@@ -686,6 +741,13 @@ async function handleSend(
686
741
  ...ctx.artifacts,
687
742
  ...(handlerResult.artifacts ?? []),
688
743
  ];
744
+ if (handlerResult.taskState === "input-required") {
745
+ const updated = await updateTask(task.id, {
746
+ state: "input-required",
747
+ message: handlerResult.message,
748
+ });
749
+ return { ...jsonRpcResult(0, updated), _id: 0 };
750
+ }
689
751
  const updated = await updateTask(task.id, {
690
752
  state: "completed",
691
753
  message: handlerResult.message,
@@ -725,6 +787,7 @@ async function handleStream(
725
787
 
726
788
  const contextId = params.contextId as string | undefined;
727
789
  const metadata = params.metadata as Record<string, unknown> | undefined;
790
+ const approvedActions = trustedApprovedActions(params.approvedActions, event);
728
791
  const ownerEmailForTask =
729
792
  (event?.context?.__a2aVerifiedEmail as string | undefined) ?? null;
730
793
 
@@ -743,6 +806,7 @@ async function handleStream(
743
806
  contextId,
744
807
  trustedA2AMetadata(metadata, event),
745
808
  event,
809
+ approvedActions,
746
810
  );
747
811
 
748
812
  try {
@@ -1,23 +1,31 @@
1
1
  import {
2
2
  defineEventHandler,
3
+ setResponseHeader,
3
4
  setResponseStatus,
4
5
  getMethod,
5
6
  getRequestHeader,
6
7
  } from "h3";
7
8
  import * as jose from "jose";
8
9
 
10
+ import { redactArgsToJson } from "../audit/redact.js";
9
11
  import {
10
12
  extractBearerToken,
11
13
  verifyInternalToken,
12
14
  } from "../integrations/internal-token.js";
13
15
  import { getH3App } from "../server/framework-request-handler.js";
14
16
  import { readBody } from "../server/h3-helpers.js";
17
+ import { isSameOriginRequest } from "../server/request-origin.js";
15
18
  import { generateAgentCard } from "./agent-card.js";
16
19
  import {
17
20
  hasConfiguredA2ASecret,
18
21
  isA2AProductionRuntime,
19
22
  } from "./auth-policy.js";
20
23
  import { handleJsonRpcH3, processA2ATaskFromQueue } from "./handlers.js";
24
+ import {
25
+ claimA2AApproval,
26
+ getA2AApprovalForOwner,
27
+ settleA2AApproval,
28
+ } from "./task-store.js";
21
29
  import type { A2AConfig } from "./types.js";
22
30
 
23
31
  /**
@@ -235,6 +243,122 @@ export function mountA2A(
235
243
  }),
236
244
  );
237
245
 
246
+ // Human-only continuation for consequential A2A tool calls. The opaque id
247
+ // is a lookup handle, not authorization: every read and mutation also
248
+ // requires the task owner's live browser session. Ordinary A2A bearer
249
+ // tokens never reach this control plane and cannot approve themselves.
250
+ getH3App(nitroApp).use(
251
+ `${routePrefix}/a2a/approvals`,
252
+ defineEventHandler(async (event) => {
253
+ const approvalId = (event.path || "")
254
+ .split("?")[0]
255
+ .replace(/^\//, "")
256
+ .split("/")[0];
257
+ if (!approvalId) {
258
+ setResponseStatus(event, 404);
259
+ return { error: "Approval not found" };
260
+ }
261
+
262
+ const { getSession } = await import("../server/auth.js");
263
+ const session = await getSession(event);
264
+ if (!session?.email) {
265
+ setResponseStatus(event, 401);
266
+ return { error: "Sign in to review this approval" };
267
+ }
268
+
269
+ if (getMethod(event) === "GET") {
270
+ const approval = await getA2AApprovalForOwner(
271
+ approvalId,
272
+ session.email,
273
+ session.orgId ?? null,
274
+ );
275
+ if (!approval) {
276
+ setResponseStatus(event, 404);
277
+ return { error: "Approval not found" };
278
+ }
279
+ const safeInput = redactArgsToJson(approval.input) ?? "{}";
280
+ const esc = (value: string) =>
281
+ value.replace(
282
+ /[&<>"']/g,
283
+ (char) =>
284
+ ({
285
+ "&": "&amp;",
286
+ "<": "&lt;",
287
+ ">": "&gt;",
288
+ '"': "&quot;",
289
+ "'": "&#39;",
290
+ })[char] ?? char,
291
+ );
292
+ setResponseHeader(event, "content-type", "text/html; charset=utf-8");
293
+ setResponseHeader(event, "cache-control", "no-store");
294
+ setResponseHeader(event, "x-frame-options", "DENY");
295
+ setResponseHeader(
296
+ event,
297
+ "content-security-policy",
298
+ "frame-ancestors 'none'",
299
+ );
300
+ setResponseHeader(event, "referrer-policy", "no-referrer");
301
+ const expired = approval.expiresAt <= Date.now();
302
+ const active = approval.status === "pending" && !expired;
303
+ const displayStatus = expired ? "expired" : approval.status;
304
+ return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Review agent action</title><style>body{font:15px/1.5 ui-sans-serif,system-ui;background:#f7f7f5;color:#171717;margin:0}.card{max-width:680px;margin:8vh auto;background:white;border:1px solid #ddd;border-radius:14px;padding:28px;box-shadow:0 12px 36px #0001}h1{font-size:22px;margin:0 0 8px}p{color:#555}pre{white-space:pre-wrap;word-break:break-word;background:#f4f4f2;padding:16px;border-radius:9px;max-height:45vh;overflow:auto}button{background:#171717;color:white;border:0;border-radius:8px;padding:11px 16px;font-weight:650;cursor:pointer}button[disabled]{opacity:.5;cursor:default}#status{margin-left:10px;color:#555}</style></head><body><main class="card"><h1>Review agent action</h1><p>This action is paused until you approve it. Approval can be used once.</p><h2>${esc(approval.tool)}</h2><pre>${esc(safeInput)}</pre><button id="approve" ${active ? "" : "disabled"}>${active ? "Approve and run" : esc(displayStatus)}</button><span id="status">${active ? "" : esc(approval.result ?? displayStatus)}</span><script>document.getElementById('approve').onclick=async function(){this.disabled=true;document.getElementById('status').textContent='Running…';try{const r=await fetch(location.href,{method:'POST',credentials:'include',headers:{'content-type':'application/json'}});const j=await r.json();document.getElementById('status').textContent=j.output||j.error||'Done';}catch(e){document.getElementById('status').textContent='Could not run the action.'}}</script></main></body></html>`;
305
+ }
306
+
307
+ if (getMethod(event) !== "POST") {
308
+ setResponseStatus(event, 405);
309
+ return { error: "Method not allowed" };
310
+ }
311
+ if (!isSameOriginRequest(event)) {
312
+ setResponseStatus(event, 403);
313
+ return { error: "Cross-origin approval denied" };
314
+ }
315
+ if (!config.executeApproval) {
316
+ setResponseStatus(event, 501);
317
+ return { error: "Approval execution is not configured" };
318
+ }
319
+
320
+ const approval = await claimA2AApproval(
321
+ approvalId,
322
+ session.email,
323
+ session.orgId ?? null,
324
+ );
325
+ if (!approval) {
326
+ const current = await getA2AApprovalForOwner(
327
+ approvalId,
328
+ session.email,
329
+ session.orgId ?? null,
330
+ );
331
+ setResponseStatus(event, current ? 409 : 404);
332
+ return {
333
+ error: current
334
+ ? `Approval is ${current.status} and cannot be run again`
335
+ : "Approval not found",
336
+ };
337
+ }
338
+
339
+ const { runWithRequestContext } =
340
+ await import("../server/request-context.js");
341
+ let execution: { status: "completed" | "failed"; output: string };
342
+ try {
343
+ execution = await runWithRequestContext(
344
+ { userEmail: session.email, orgId: session.orgId ?? undefined },
345
+ () => config.executeApproval!(approval),
346
+ );
347
+ } catch (error) {
348
+ execution = {
349
+ status: "failed",
350
+ output:
351
+ error instanceof Error
352
+ ? error.message
353
+ : "Approval execution failed",
354
+ };
355
+ }
356
+ await settleA2AApproval(approval.id, execution.status, execution.output);
357
+ if (execution.status === "failed") setResponseStatus(event, 500);
358
+ return execution;
359
+ }),
360
+ );
361
+
238
362
  // Async-mode processor route. MUST be mounted BEFORE the `/a2a` catch-all
239
363
  // below, since h3's `.use()` matches by prefix and `/a2a` would otherwise
240
364
  // swallow `/a2a/_process-task` and return a JSON-RPC "Invalid token" error