@axiom-lattice/gateway 6.0.1 → 7.0.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 (145) hide show
  1. package/README.md +0 -1
  2. package/dist/a2a-standard-DP53K7RC.mjs +3416 -0
  3. package/dist/a2a-standard-DP53K7RC.mjs.map +1 -0
  4. package/dist/{chunk-YKTXW6F6.mjs → chunk-2SR4F3ZD.mjs} +65 -1
  5. package/dist/chunk-2SR4F3ZD.mjs.map +1 -0
  6. package/dist/{chunk-QUT2DS4R.mjs → chunk-J6B2VFJ7.mjs} +17 -4
  7. package/dist/chunk-J6B2VFJ7.mjs.map +1 -0
  8. package/dist/embed/embed-page-AgentDetailSideApp-KNVDO3FZ.js +1 -0
  9. package/dist/embed/embed-page-AgentGovernanceWorkbench-QSKWVADP.js +1 -0
  10. package/dist/embed/embed-page-MessageGraph-VGXX4OWU.js +6 -0
  11. package/dist/embed/embed-page-ReactChart-PIISVBLU.js +1 -0
  12. package/dist/embed/{embed-page-ReactInfographic-5P36JLSX.js → embed-page-ReactInfographic-677NVKOU.js} +1 -1
  13. package/dist/embed/embed-page-SkillFlow-JLI5MEZE.js +1 -0
  14. package/dist/embed/{embed-page-TasksSideApp-DA4LUTMD.js → embed-page-TasksSideApp-D7FFG2OC.js} +1 -1
  15. package/dist/embed/embed-page-ThreadMaintenancePanel-FSQVVIIG.js +1 -0
  16. package/dist/embed/{embed-page-TopologyRuntimeView-NFBHAICN.js → embed-page-TopologyRuntimeView-IS6HEO4F.js} +1 -1
  17. package/dist/embed/{embed-page-WorkflowAutomationView-43YGTXGF.js → embed-page-WorkflowAutomationView-IDVSQTJB.js} +1 -1
  18. package/dist/embed/embed-page-all-JEPF6WHD.js +1 -0
  19. package/dist/embed/{embed-page-architectureDiagram-VXUJARFQ-THUN2PFL.js → embed-page-architectureDiagram-VXUJARFQ-TYJ3NO2K.js} +1 -1
  20. package/dist/embed/embed-page-aspnet-GIV4ESY6.js +1 -0
  21. package/dist/embed/{embed-page-blockDiagram-VD42YOAC-K7OX6LIO.js → embed-page-blockDiagram-VD42YOAC-L4J3TZWR.js} +1 -1
  22. package/dist/embed/{embed-page-c4Diagram-YG6GDRKO-DJDAF5AO.js → embed-page-c4Diagram-YG6GDRKO-CP2IXU3Z.js} +1 -1
  23. package/dist/embed/{embed-page-chunk-FD2PRLMW.js → embed-page-chunk-2VJ7T35I.js} +1 -1
  24. package/dist/embed/embed-page-chunk-37LGEEJU.js +21 -0
  25. package/dist/embed/embed-page-chunk-436REIWY.js +1 -0
  26. package/dist/embed/embed-page-chunk-4W34QOXT.js +517 -0
  27. package/dist/embed/{embed-page-chunk-JUZPOR5Y.js → embed-page-chunk-5WWOKLDB.js} +1 -1
  28. package/dist/embed/{embed-page-chunk-HYPA2PHA.js → embed-page-chunk-6IIMW7OG.js} +1 -1
  29. package/dist/embed/{embed-page-chunk-XKGF5D6L.js → embed-page-chunk-7CJDHJEH.js} +1 -1
  30. package/dist/embed/{embed-page-chunk-J5AHS4PO.js → embed-page-chunk-7GLQRV3J.js} +1 -1
  31. package/dist/embed/{embed-page-chunk-SQ4GANYE.js → embed-page-chunk-AEDAOTEO.js} +1 -1
  32. package/dist/embed/embed-page-chunk-BANVRQT7.js +322 -0
  33. package/dist/embed/{embed-page-chunk-C4ZV4IXK.js → embed-page-chunk-BOEX26H7.js} +2 -2
  34. package/dist/embed/{embed-page-chunk-A2AHDN6K.js → embed-page-chunk-DQJVWZOB.js} +1 -1
  35. package/dist/embed/embed-page-chunk-EYYKNTX7.js +8 -0
  36. package/dist/embed/{embed-page-chunk-DTRFJLVZ.js → embed-page-chunk-F5QVZLK5.js} +1 -1
  37. package/dist/embed/{embed-page-chunk-GKHIPXZ2.js → embed-page-chunk-FEMHLB6I.js} +1 -1
  38. package/dist/embed/{embed-page-chunk-UHH575CK.js → embed-page-chunk-JFMBLJXY.js} +1 -1
  39. package/dist/embed/{embed-page-chunk-G2RO6IBN.js → embed-page-chunk-KXVXKWED.js} +1 -1
  40. package/dist/embed/embed-page-chunk-N66H4DNH.js +6657 -0
  41. package/dist/embed/{embed-page-chunk-WTOAQDQN.js → embed-page-chunk-NFMLKSPM.js} +1 -1
  42. package/dist/embed/{embed-page-chunk-PLNJRORR.js → embed-page-chunk-NVFNRWRN.js} +1 -1
  43. package/dist/embed/{embed-page-chunk-V2GBFWLG.js → embed-page-chunk-S2KVN3RB.js} +1 -1
  44. package/dist/embed/{embed-page-chunk-QULNM5O2.js → embed-page-chunk-SRMYHWL3.js} +1 -1
  45. package/dist/embed/{embed-page-chunk-D5GP7FHH.js → embed-page-chunk-TVVAPR7I.js} +1 -1
  46. package/dist/embed/{embed-page-chunk-WFUFTRWA.js → embed-page-chunk-WK77HIEN.js} +1 -1
  47. package/dist/embed/{embed-page-chunk-XSKNS3LY.js → embed-page-chunk-WWXRO4ZL.js} +1 -1
  48. package/dist/embed/{embed-page-chunk-BVSC3MSO.js → embed-page-chunk-XFV3N2LD.js} +1 -1
  49. package/dist/embed/{embed-page-chunk-VY6MAPHR.js → embed-page-chunk-XSKDYUNQ.js} +1 -1
  50. package/dist/embed/{embed-page-chunk-V7R2Y72J.js → embed-page-chunk-YJUCDIYK.js} +1 -1
  51. package/dist/embed/embed-page-chunk-ZQQFWEWO.js +1 -0
  52. package/dist/embed/embed-page-classDiagram-2ON5EDUG-MHNSVPAN.js +1 -0
  53. package/dist/embed/embed-page-classDiagram-v2-WZHVMYZB-QCMEVMYH.js +1 -0
  54. package/dist/embed/{embed-page-code-highlighter-IPU5ZNPJ.js → embed-page-code-highlighter-66CSPTFK.js} +1 -1
  55. package/dist/embed/embed-page-cshtml-RYMZMLKB.js +1 -0
  56. package/dist/embed/{embed-page-dagre-6UL2VRFP-UQFZMKYW.js → embed-page-dagre-6UL2VRFP-LTZ2DVVO.js} +1 -1
  57. package/dist/embed/{embed-page-diagram-PSM6KHXK-4IKXWNA4.js → embed-page-diagram-PSM6KHXK-JHYXSMCB.js} +1 -1
  58. package/dist/embed/{embed-page-diagram-QEK2KX5R-245T32UF.js → embed-page-diagram-QEK2KX5R-2YGDFFTU.js} +1 -1
  59. package/dist/embed/{embed-page-diagram-S2PKOQOG-BOA5MJA5.js → embed-page-diagram-S2PKOQOG-6EW6MDJW.js} +1 -1
  60. package/dist/embed/{embed-page-en-WB2EPS63.js → embed-page-en-2SWQHPAC.js} +1 -1
  61. package/dist/embed/embed-page-en-3Z4N5KXB.js +1 -0
  62. package/dist/embed/embed-page-en-7IHJFS63.js +1 -0
  63. package/dist/embed/embed-page-en-7NF4GCC2.js +1 -0
  64. package/dist/embed/embed-page-en-BLF5THT6.js +1 -0
  65. package/dist/embed/embed-page-en-ETUCVCSC.js +1 -0
  66. package/dist/embed/{embed-page-en-UWBIHR6I.js → embed-page-en-H4P63IAK.js} +1 -1
  67. package/dist/embed/embed-page-en-WUFULCUS.js +1 -0
  68. package/dist/embed/{embed-page-en-S4MXFPIU.js → embed-page-en-X6B6RVNL.js} +1 -1
  69. package/dist/embed/embed-page-en-XUMHP5CL.js +1 -0
  70. package/dist/embed/{embed-page-erDiagram-Q2GNP2WA-S4J4SQPR.js → embed-page-erDiagram-Q2GNP2WA-KSUS6MHW.js} +1 -1
  71. package/dist/embed/{embed-page-flowDiagram-NV44I4VS-ZFIJXKMW.js → embed-page-flowDiagram-NV44I4VS-AYXRDKAJ.js} +1 -1
  72. package/dist/embed/{embed-page-ganttDiagram-JELNMOA3-7MSIEWL4.js → embed-page-ganttDiagram-JELNMOA3-AV7N2N3T.js} +1 -1
  73. package/dist/embed/{embed-page-gitGraphDiagram-NY62KEGX-F6KE6PZK.js → embed-page-gitGraphDiagram-NY62KEGX-IBVOXVO5.js} +1 -1
  74. package/dist/embed/embed-page-infoDiagram-WHAUD3N6-ZULKOSOZ.js +2 -0
  75. package/dist/embed/{embed-page-journeyDiagram-XKPGCS4Q-6VTW6GQ3.js → embed-page-journeyDiagram-XKPGCS4Q-3GILI7Y4.js} +1 -1
  76. package/dist/embed/{embed-page-kanban-definition-3W4ZIXB7-7IZYCLQZ.js → embed-page-kanban-definition-3W4ZIXB7-UGO3J7IH.js} +1 -1
  77. package/dist/embed/{embed-page-mermaid-R7ZBUE3D.js → embed-page-mermaid-KEBBKHQH.js} +2 -2
  78. package/dist/embed/{embed-page-mindmap-definition-VGOIOE7T-GKTOT34A.js → embed-page-mindmap-definition-VGOIOE7T-VXXZJS3Z.js} +1 -1
  79. package/dist/embed/{embed-page-pieDiagram-ADFJNKIX-VDPALO42.js → embed-page-pieDiagram-ADFJNKIX-ZFAWYA2N.js} +1 -1
  80. package/dist/embed/{embed-page-requirementDiagram-UZGBJVZJ-LTZH6RTD.js → embed-page-requirementDiagram-UZGBJVZJ-2QBINXR7.js} +1 -1
  81. package/dist/embed/{embed-page-sequenceDiagram-WL72ISMW-LV6Q4SHW.js → embed-page-sequenceDiagram-WL72ISMW-3MGTESG4.js} +1 -1
  82. package/dist/embed/{embed-page-stateDiagram-FKZM4ZOC-U4NHQDNP.js → embed-page-stateDiagram-FKZM4ZOC-W6VPIMB5.js} +1 -1
  83. package/dist/embed/embed-page-stateDiagram-v2-4FDKWEC3-QYJ47QBY.js +1 -0
  84. package/dist/embed/{embed-page-tsx-PNW7N5T4.js → embed-page-tsx-LGDDERM2.js} +1 -1
  85. package/dist/embed/{embed-page-xychartDiagram-PRI3JC2R-VZSIXBFK.js → embed-page-xychartDiagram-PRI3JC2R-7XKT4R7V.js} +1 -1
  86. package/dist/embed/embed-page-zh-D3IC6BGA.js +1 -0
  87. package/dist/embed/{embed-page-zh-SSMSF5LE.js → embed-page-zh-EQ5X2M32.js} +1 -1
  88. package/dist/embed/embed-page-zh-G75LDQU4.js +1 -0
  89. package/dist/embed/embed-page-zh-GXE63PQ7.js +1 -0
  90. package/dist/embed/{embed-page-zh-ACZNJ4X4.js → embed-page-zh-IB3422D4.js} +1 -1
  91. package/dist/embed/embed-page-zh-KJXKOUIY.js +1 -0
  92. package/dist/embed/{embed-page-zh-NKHP3CFU.js → embed-page-zh-N2STRBUG.js} +1 -1
  93. package/dist/embed/{embed-page-zh-XEYNKCTC.js → embed-page-zh-PRMID3YQ.js} +1 -1
  94. package/dist/embed/embed-page-zh-RMGMUSDH.js +1 -0
  95. package/dist/embed/embed-page-zh-RMSS6PXD.js +1 -0
  96. package/dist/embed/embed-page.js +1 -1
  97. package/dist/index.d.mts +1513 -252
  98. package/dist/index.d.ts +1513 -252
  99. package/dist/index.js +17568 -5813
  100. package/dist/index.js.map +1 -1
  101. package/dist/index.mjs +31824 -22070
  102. package/dist/index.mjs.map +1 -1
  103. package/dist/{open-MDC4VCR7.mjs → open-NTH7AGD3.mjs} +41 -78
  104. package/dist/open-NTH7AGD3.mjs.map +1 -0
  105. package/dist/{open-exposure-OX5NKWDG.mjs → open-exposure-AGNG4MC3.mjs} +13 -3
  106. package/dist/{open-exposure-OX5NKWDG.mjs.map → open-exposure-AGNG4MC3.mjs.map} +1 -1
  107. package/dist/{open-files-FS5AN6Q6.mjs → open-files-V3R2BFTV.mjs} +4 -4
  108. package/package.json +9 -8
  109. package/dist/a2a-standard-3OZFXDCW.mjs +0 -1413
  110. package/dist/a2a-standard-3OZFXDCW.mjs.map +0 -1
  111. package/dist/chunk-K32XEQLH.mjs +0 -28
  112. package/dist/chunk-K32XEQLH.mjs.map +0 -1
  113. package/dist/chunk-QUT2DS4R.mjs.map +0 -1
  114. package/dist/chunk-YKTXW6F6.mjs.map +0 -1
  115. package/dist/embed/embed-page-AgentDetailSideApp-JYVZKSUU.js +0 -1
  116. package/dist/embed/embed-page-AgentGovernanceWorkbench-SD2EHW2B.js +0 -1
  117. package/dist/embed/embed-page-MessageGraph-GLQXKAVC.js +0 -6
  118. package/dist/embed/embed-page-ReactChart-74MHCE2K.js +0 -1
  119. package/dist/embed/embed-page-SkillFlow-NDAK3GAD.js +0 -1
  120. package/dist/embed/embed-page-TeamWorkspace-DUB3L6V6.js +0 -748
  121. package/dist/embed/embed-page-TeamWorkspace-YQFWXDLI.css +0 -1
  122. package/dist/embed/embed-page-all-NEJVJ4G7.js +0 -1
  123. package/dist/embed/embed-page-aspnet-SRK67AH2.js +0 -1
  124. package/dist/embed/embed-page-chunk-2PFKLMTR.js +0 -21
  125. package/dist/embed/embed-page-chunk-DWIBLGBW.js +0 -2
  126. package/dist/embed/embed-page-chunk-ESQ5V77Q.js +0 -1
  127. package/dist/embed/embed-page-chunk-MHGVD4EV.js +0 -525
  128. package/dist/embed/embed-page-chunk-P477ZTUO.js +0 -7009
  129. package/dist/embed/embed-page-classDiagram-2ON5EDUG-VYHHMIKZ.js +0 -1
  130. package/dist/embed/embed-page-classDiagram-v2-WZHVMYZB-5AISLXUF.js +0 -1
  131. package/dist/embed/embed-page-cshtml-ZY7LJSTH.js +0 -1
  132. package/dist/embed/embed-page-en-2WLCQIB6.js +0 -1
  133. package/dist/embed/embed-page-en-7WXWAWGG.js +0 -1
  134. package/dist/embed/embed-page-en-ICVL5QAC.js +0 -1
  135. package/dist/embed/embed-page-en-OLZHSUC3.js +0 -1
  136. package/dist/embed/embed-page-infoDiagram-WHAUD3N6-FQMKXERB.js +0 -2
  137. package/dist/embed/embed-page-stateDiagram-v2-4FDKWEC3-G3EWQCOW.js +0 -1
  138. package/dist/embed/embed-page-zh-EYLHUD5G.js +0 -1
  139. package/dist/embed/embed-page-zh-FPYCTKQK.js +0 -1
  140. package/dist/embed/embed-page-zh-OPUVZZSQ.js +0 -1
  141. package/dist/mcpTransportRegistry-VXUVJKBC.mjs +0 -14
  142. package/dist/mcpTransportRegistry-VXUVJKBC.mjs.map +0 -1
  143. package/dist/open-MDC4VCR7.mjs.map +0 -1
  144. /package/dist/embed/{embed-page-chunk-34FVRFPW.js → embed-page-chunk-UH47IEAR.js} +0 -0
  145. /package/dist/{open-files-FS5AN6Q6.mjs.map → open-files-V3R2BFTV.mjs.map} +0 -0
@@ -0,0 +1,3416 @@
1
+ import {
2
+ buildAgentCardForAssistant
3
+ } from "./chunk-J6B2VFJ7.mjs";
4
+ import {
5
+ formatFileRefsSection,
6
+ getRunCancelRouter,
7
+ resolveRunTaskMaxActivePerTenant,
8
+ saveProjectFile,
9
+ wakeRunTaskQueue
10
+ } from "./chunk-2SR4F3ZD.mjs";
11
+ import {
12
+ A2AAuthError
13
+ } from "./chunk-GPHOYR3X.mjs";
14
+ import "./chunk-NFEGQTCC.mjs";
15
+
16
+ // src/routes/a2a-standard.ts
17
+ import { v4 as uuidv42 } from "uuid";
18
+ import {
19
+ A2AError as A2AError3,
20
+ DefaultExecutionEventBus as DefaultExecutionEventBus2,
21
+ DefaultRequestHandler,
22
+ JsonRpcTransportHandler,
23
+ RequestContext
24
+ } from "@a2a-js/sdk/server";
25
+ import {
26
+ agentInstanceManager as agentInstanceManager2,
27
+ ChunkCursorExpiredError,
28
+ chunkBufferStreamKey,
29
+ createTaskLifecycleService,
30
+ getChunkBuffer,
31
+ getStoreLattice
32
+ } from "@axiom-lattice/core";
33
+ import { MessageChunkTypes as MessageChunkTypes3 } from "@axiom-lattice/protocols";
34
+
35
+ // src/services/a2a/A2APushNotificationStore.ts
36
+ import { decrypt, encrypt } from "@axiom-lattice/core";
37
+
38
+ // src/services/a2a/A2APushNotificationUrl.ts
39
+ import { lookup } from "dns/promises";
40
+ import { A2AError } from "@a2a-js/sdk/server";
41
+ import { assertSafeUpstreamUrl } from "@axiom-lattice/core";
42
+ var defaultA2APushHostResolver = async (hostname) => {
43
+ const records = await lookup(hostname, { all: true });
44
+ return records.map((record) => ({
45
+ address: record.address,
46
+ family: record.family
47
+ }));
48
+ };
49
+ function normalizeHost(value) {
50
+ return value.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "").replace(/\.$/, "").split("%")[0] ?? "";
51
+ }
52
+ function ipv4Parts(hostname) {
53
+ const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
54
+ if (!match) return null;
55
+ const parts = match.slice(1).map(Number);
56
+ return parts.every((part) => part >= 0 && part <= 255) ? parts : null;
57
+ }
58
+ function isReservedIpv4(hostname) {
59
+ const parts = ipv4Parts(hostname);
60
+ if (!parts) return false;
61
+ const [a, b, c] = parts;
62
+ if (a === 0 || a === 10 || a === 127) return true;
63
+ if (a === 100 && b >= 64 && b <= 127) return true;
64
+ if (a === 169 && b === 254) return true;
65
+ if (a === 172 && b >= 16 && b <= 31) return true;
66
+ if (a === 192 && b === 0 && c === 0) return true;
67
+ if (a === 192 && b === 0 && c === 2) return true;
68
+ if (a === 192 && b === 88 && c === 99) return true;
69
+ if (a === 192 && b === 168) return true;
70
+ if (a === 198 && (b === 18 || b === 19)) return true;
71
+ if (a === 198 && b === 51 && c === 100) return true;
72
+ if (a === 203 && b === 0 && c === 113) return true;
73
+ return a >= 224;
74
+ }
75
+ function isReservedIpv6(hostname) {
76
+ const host = normalizeHost(hostname);
77
+ if (!host.includes(":")) return false;
78
+ if (host.startsWith("::")) return true;
79
+ if (host.startsWith("fc") || host.startsWith("fd")) return true;
80
+ if (/^fe[89ab]/.test(host)) return true;
81
+ if (/^fec[0-9a-f]/.test(host)) return true;
82
+ if (host.startsWith("ff")) return true;
83
+ if (host.startsWith("100:")) return true;
84
+ if (host.startsWith("2001:db8")) return true;
85
+ return false;
86
+ }
87
+ function isBlockedPushAddress(address) {
88
+ const host = normalizeHost(address);
89
+ if (ipv4Parts(host)) return isReservedIpv4(host);
90
+ if (!host.includes(":")) return true;
91
+ return isReservedIpv6(host);
92
+ }
93
+ function isBlockedHostname(hostname) {
94
+ const host = normalizeHost(hostname);
95
+ if (host === "localhost" || host === "localhost.localdomain" || host.endsWith(".localhost") || host === "ip6-localhost" || host === "ip6-loopback") {
96
+ return true;
97
+ }
98
+ if (host === "metadata" || host === "metadata.google.internal" || host === "metadata.google.com" || host === "instance-data" || host === "instance-data.ec2.internal") {
99
+ return true;
100
+ }
101
+ return isReservedIpv4(host) || isReservedIpv6(host);
102
+ }
103
+ function toHostAddress(value) {
104
+ if (typeof value === "string") {
105
+ return { address: value, family: value.includes(":") ? 6 : 4 };
106
+ }
107
+ if (typeof value !== "object" || value === null) return null;
108
+ const record = value;
109
+ if (typeof record.address !== "string") return null;
110
+ return {
111
+ address: record.address,
112
+ family: typeof record.family === "number" ? record.family : record.address.includes(":") ? 6 : 4
113
+ };
114
+ }
115
+ async function assertSafePushNotificationUrl(raw, resolveHost = defaultA2APushHostResolver) {
116
+ let url;
117
+ try {
118
+ ({ url } = await assertSafeUpstreamUrl(raw));
119
+ } catch (error) {
120
+ throw A2AError.invalidParams(
121
+ error instanceof Error ? `Push notification URL is not allowed: ${error.message}` : "Push notification URL is not allowed"
122
+ );
123
+ }
124
+ if (url.protocol !== "https:") {
125
+ throw A2AError.invalidParams("Push notification URL must use HTTPS");
126
+ }
127
+ const hostname = normalizeHost(url.hostname);
128
+ if (isBlockedHostname(hostname)) {
129
+ throw A2AError.invalidParams(
130
+ "Push notification URL must not target a private, loopback, link-local, or metadata host"
131
+ );
132
+ }
133
+ let addresses;
134
+ try {
135
+ addresses = await resolveHost(hostname);
136
+ } catch (error) {
137
+ throw A2AError.invalidParams(
138
+ error instanceof Error ? `Push notification URL could not be resolved: ${error.message}` : "Push notification URL could not be resolved"
139
+ );
140
+ }
141
+ if (addresses.length === 0) {
142
+ throw A2AError.invalidParams("Push notification URL did not resolve");
143
+ }
144
+ const normalized = [];
145
+ for (const record of addresses) {
146
+ const resolved = toHostAddress(record);
147
+ if (!resolved) {
148
+ throw A2AError.invalidParams(
149
+ "Push notification URL resolved to an invalid address"
150
+ );
151
+ }
152
+ const address = normalizeHost(resolved.address);
153
+ if (isBlockedPushAddress(address)) {
154
+ throw A2AError.invalidParams(
155
+ "Push notification URL resolves to a private, loopback, link-local, or reserved address"
156
+ );
157
+ }
158
+ normalized.push({ address, family: resolved.family });
159
+ }
160
+ return { url, addresses: normalized };
161
+ }
162
+
163
+ // src/services/a2a/A2APushNotificationStore.ts
164
+ var NAMESPACE_ROOT = "a2a-push";
165
+ var PAGE_SIZE = 100;
166
+ function isRecord(value) {
167
+ return typeof value === "object" && value !== null && !Array.isArray(value);
168
+ }
169
+ function normalizeConfigId(taskId, value) {
170
+ if (typeof value !== "string") return taskId;
171
+ return value.trim() || taskId;
172
+ }
173
+ var A2APushNotificationStore = class {
174
+ constructor(options) {
175
+ this.store = options.store;
176
+ this.scope = options.scope;
177
+ this.resolveHost = options.resolveHost ?? defaultA2APushHostResolver;
178
+ }
179
+ namespace() {
180
+ return [NAMESPACE_ROOT, this.scope.tenantId, this.scope.assistantId];
181
+ }
182
+ key(taskId, configId) {
183
+ return `${taskId}:${configId}`;
184
+ }
185
+ async save(taskId, pushNotificationConfig) {
186
+ await assertSafePushNotificationUrl(
187
+ pushNotificationConfig.url,
188
+ this.resolveHost
189
+ );
190
+ const configId = normalizeConfigId(taskId, pushNotificationConfig.id);
191
+ const value = {
192
+ taskId,
193
+ configId,
194
+ url: pushNotificationConfig.url
195
+ };
196
+ const token = pushNotificationConfig.token;
197
+ if (typeof token === "string" && token.length > 0) {
198
+ value.tokenEnc = encrypt(token);
199
+ }
200
+ const authentication = pushNotificationConfig.authentication;
201
+ if (authentication) {
202
+ const schemes = Array.isArray(authentication.schemes) ? authentication.schemes.filter((scheme) => typeof scheme === "string") : [];
203
+ const storedAuth = { schemes };
204
+ if (typeof authentication.credentials === "string" && authentication.credentials.length > 0) {
205
+ storedAuth.credentialsEnc = encrypt(authentication.credentials);
206
+ }
207
+ value.authentication = storedAuth;
208
+ }
209
+ await this.store.put(this.scope.tenantId, {
210
+ namespace: this.namespace(),
211
+ key: this.key(taskId, configId),
212
+ value
213
+ });
214
+ }
215
+ async load(taskId) {
216
+ const prefix = `${taskId}:`;
217
+ const items = await this.searchTask(taskId);
218
+ const configs = [];
219
+ for (const item of items) {
220
+ if (!item.key.startsWith(prefix)) continue;
221
+ try {
222
+ configs.push(await this.parse(item, taskId));
223
+ } catch (error) {
224
+ console.warn(
225
+ `[a2a] skipping invalid push notification config "${item.key}"`,
226
+ error
227
+ );
228
+ }
229
+ }
230
+ return configs.sort((left, right) => (left.id ?? "").localeCompare(right.id ?? ""));
231
+ }
232
+ /**
233
+ * Read every config for a task. The generic store limit is only a page size;
234
+ * this walks offsets so a task with more than 1000 configs is never truncated.
235
+ */
236
+ async searchTask(taskId) {
237
+ const items = [];
238
+ let offset = 0;
239
+ for (; ; ) {
240
+ const page = await this.store.search(this.scope.tenantId, {
241
+ namespacePrefix: this.namespace(),
242
+ filter: { taskId },
243
+ limit: PAGE_SIZE,
244
+ offset
245
+ });
246
+ items.push(...page);
247
+ if (page.length < PAGE_SIZE) return items;
248
+ offset += page.length;
249
+ }
250
+ }
251
+ async parse(item, taskId) {
252
+ const value = item.value;
253
+ if (typeof value.url !== "string") {
254
+ throw new Error("stored config has no URL");
255
+ }
256
+ await assertSafePushNotificationUrl(value.url, this.resolveHost);
257
+ const configId = normalizeConfigId(
258
+ taskId,
259
+ typeof value.configId === "string" ? value.configId : item.key.slice(taskId.length + 1)
260
+ );
261
+ const config = { id: configId, url: value.url };
262
+ if (typeof value.tokenEnc === "string" && value.tokenEnc.length > 0) {
263
+ config.token = decrypt(value.tokenEnc);
264
+ }
265
+ if (isRecord(value.authentication)) {
266
+ const schemes = Array.isArray(value.authentication.schemes) ? value.authentication.schemes.filter(
267
+ (scheme) => typeof scheme === "string"
268
+ ) : [];
269
+ const authentication = {
270
+ schemes
271
+ };
272
+ if (typeof value.authentication.credentialsEnc === "string" && value.authentication.credentialsEnc.length > 0) {
273
+ authentication.credentials = decrypt(value.authentication.credentialsEnc);
274
+ }
275
+ config.authentication = authentication;
276
+ }
277
+ return config;
278
+ }
279
+ /**
280
+ * Delete one config. The SDK always supplies a config id; when it is omitted
281
+ * the default config is `${taskId}:${taskId}` (not every config for the task).
282
+ */
283
+ async delete(taskId, configId) {
284
+ await this.store.delete(
285
+ this.scope.tenantId,
286
+ this.namespace(),
287
+ this.key(taskId, normalizeConfigId(taskId, configId))
288
+ );
289
+ }
290
+ };
291
+
292
+ // src/services/a2a/A2APushNotificationSender.ts
293
+ import { request as httpsRequest } from "https";
294
+ var DEFAULT_TIMEOUT_MS = 5e3;
295
+ var TOKEN_HEADER = "X-A2A-Notification-Token";
296
+ function withTimeout(promise, timeoutMs) {
297
+ return new Promise((resolve, reject) => {
298
+ const timer = setTimeout(() => {
299
+ reject(new Error("Push notification request timed out"));
300
+ }, timeoutMs);
301
+ promise.then(
302
+ (value) => {
303
+ clearTimeout(timer);
304
+ resolve(value);
305
+ },
306
+ (error) => {
307
+ clearTimeout(timer);
308
+ reject(error);
309
+ }
310
+ );
311
+ });
312
+ }
313
+ function createPinnedLookup(address, family) {
314
+ return (_hostname, options, callback) => {
315
+ if (options?.all) {
316
+ callback(null, [{ address, family }]);
317
+ return;
318
+ }
319
+ callback(null, address, family);
320
+ };
321
+ }
322
+ function createPinnedHttpsRequester() {
323
+ return ({ url, addresses, headers, body, timeoutMs }) => new Promise((resolve, reject) => {
324
+ const pinned = addresses[0];
325
+ if (!pinned) {
326
+ reject(new Error("No validated push notification address"));
327
+ return;
328
+ }
329
+ const parsed = new URL(url);
330
+ const family = pinned.includes(":") ? 6 : 4;
331
+ const request = httpsRequest(parsed, {
332
+ method: "POST",
333
+ agent: false,
334
+ headers: {
335
+ ...headers,
336
+ "Content-Length": Buffer.byteLength(body)
337
+ },
338
+ lookup: createPinnedLookup(pinned, family),
339
+ servername: parsed.hostname
340
+ }, (response) => {
341
+ response.resume();
342
+ resolve({ statusCode: response.statusCode ?? 0 });
343
+ });
344
+ const timeout = setTimeout(() => {
345
+ request.destroy(new Error("Push notification request timed out"));
346
+ }, timeoutMs);
347
+ request.on("close", () => clearTimeout(timeout));
348
+ request.on("error", (error) => {
349
+ clearTimeout(timeout);
350
+ reject(error);
351
+ });
352
+ request.end(body);
353
+ });
354
+ }
355
+ var A2APushNotificationSender = class {
356
+ constructor(options) {
357
+ this.store = options.store;
358
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
359
+ this.request = options.request ?? createPinnedHttpsRequester();
360
+ this.resolveHost = options.resolveHost ?? defaultA2APushHostResolver;
361
+ this.logger = options.logger;
362
+ }
363
+ async send(task) {
364
+ let configs;
365
+ try {
366
+ configs = await this.store.load(task.id);
367
+ } catch (error) {
368
+ this.logger?.error?.(
369
+ `[a2a] failed to load push notification configs for task ${task.id}`,
370
+ error
371
+ );
372
+ return;
373
+ }
374
+ if (!configs || configs.length === 0) return;
375
+ await Promise.all(configs.map(async (config) => {
376
+ await this.deliver(task, config);
377
+ }));
378
+ }
379
+ async deliver(task, config) {
380
+ let target;
381
+ try {
382
+ target = await assertSafePushNotificationUrl(config.url, this.resolveHost);
383
+ } catch (error) {
384
+ this.logger?.warn?.(
385
+ `[a2a] skipping unsafe push notification URL for task ${task.id}`,
386
+ error
387
+ );
388
+ return;
389
+ }
390
+ try {
391
+ const headers = {
392
+ "Content-Type": "application/json"
393
+ };
394
+ if (config.token) {
395
+ headers[TOKEN_HEADER] = config.token;
396
+ }
397
+ const authentication = config.authentication;
398
+ if (authentication?.credentials) {
399
+ const schemes = authentication.schemes.map((scheme) => scheme.toLowerCase());
400
+ if (schemes.includes("bearer")) {
401
+ headers.Authorization = `Bearer ${authentication.credentials}`;
402
+ } else if (schemes.includes("basic")) {
403
+ headers.Authorization = `Basic ${authentication.credentials}`;
404
+ }
405
+ }
406
+ const response = await withTimeout(this.request({
407
+ url: target.url.toString(),
408
+ addresses: target.addresses.map((record) => record.address),
409
+ headers,
410
+ body: JSON.stringify(task),
411
+ timeoutMs: this.timeoutMs
412
+ }), this.timeoutMs);
413
+ if (response.statusCode < 200 || response.statusCode >= 300) {
414
+ throw new Error(
415
+ `Push notification endpoint returned HTTP ${response.statusCode}`
416
+ );
417
+ }
418
+ } catch (error) {
419
+ this.logger?.error?.(
420
+ `[a2a] push notification failed for task ${task.id} to ${config.url}`,
421
+ error
422
+ );
423
+ }
424
+ }
425
+ };
426
+
427
+ // src/services/a2a/A2ATaskStoreAdapter.ts
428
+ import { createHash } from "crypto";
429
+ import { isDeepStrictEqual } from "util";
430
+ import {
431
+ createTaskRunBridge
432
+ } from "@axiom-lattice/core";
433
+ var TASK_STATUS_TO_A2A_STATE = {
434
+ pending: "submitted",
435
+ in_progress: "working",
436
+ review: "input-required",
437
+ interrupted: "input-required",
438
+ completed: "completed",
439
+ failed: "failed",
440
+ cancelled: "canceled"
441
+ };
442
+ var TITLE_MAX_LENGTH = 80;
443
+ var A2ATaskScopeConflictError = class extends Error {
444
+ constructor(taskId) {
445
+ super(`A2A task ${taskId} belongs to another scope`);
446
+ this.code = "A2A_TASK_SCOPE_CONFLICT";
447
+ this.name = "A2ATaskScopeConflictError";
448
+ this.taskId = taskId;
449
+ }
450
+ };
451
+ function mapTaskStatusToA2AState(status) {
452
+ return TASK_STATUS_TO_A2A_STATE[status];
453
+ }
454
+ function asString(value) {
455
+ return typeof value === "string" ? value : void 0;
456
+ }
457
+ function asRecord(value) {
458
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
459
+ }
460
+ function asStringArray(value) {
461
+ if (!Array.isArray(value)) return void 0;
462
+ const strings = value.filter((item) => typeof item === "string");
463
+ return strings.length > 0 ? strings : void 0;
464
+ }
465
+ function extractText(parts) {
466
+ return parts.filter((part) => part.kind === "text").map((part) => part.text).join("\n");
467
+ }
468
+ function truncate(text, maxLength) {
469
+ if (text.length <= maxLength) return text;
470
+ return `${text.slice(0, maxLength)}\u2026`;
471
+ }
472
+ function extractTitleAndDescription(task) {
473
+ const firstUserMessage = task.history?.find((message) => message.role === "user");
474
+ const text = firstUserMessage ? extractText(firstUserMessage.parts) : "";
475
+ return {
476
+ title: truncate(text, TITLE_MAX_LENGTH),
477
+ description: text ? [
478
+ "## Objective",
479
+ "",
480
+ text,
481
+ "",
482
+ "## Acceptance Criteria",
483
+ "",
484
+ "Complete the requested work and return the result through A2A."
485
+ ].join("\n") : ""
486
+ };
487
+ }
488
+ function isFileRef(value) {
489
+ return typeof value === "object" && value !== null && typeof value.uri === "string";
490
+ }
491
+ function artifactFileRefs(artifacts) {
492
+ const refs = [];
493
+ for (const artifact of artifacts ?? []) {
494
+ for (const part of artifact.parts) {
495
+ if (part.kind !== "file") continue;
496
+ const file = part.file;
497
+ if (!("uri" in file)) continue;
498
+ refs.push({
499
+ uri: file.uri,
500
+ ...file.name ? { name: file.name } : {},
501
+ ...file.mimeType ? { mimeType: file.mimeType } : {},
502
+ addedBy: "agent"
503
+ });
504
+ }
505
+ }
506
+ return refs;
507
+ }
508
+ function extractFiles(task) {
509
+ const inputFiles = task.metadata?.inputFiles;
510
+ const inputRefs = Array.isArray(inputFiles) ? inputFiles.filter(isFileRef) : [];
511
+ return [...inputRefs, ...artifactFileRefs(task.artifacts)];
512
+ }
513
+ function extractArtifactText(artifacts) {
514
+ return (artifacts ?? []).map((artifact) => extractText(artifact.parts)).filter((text) => text.length > 0).join("\n");
515
+ }
516
+ function extractFinalTurnAgentText(history) {
517
+ const messages = history ?? [];
518
+ let lastUserIndex = -1;
519
+ for (let i = 0; i < messages.length; i += 1) {
520
+ if (messages[i].role === "user") lastUserIndex = i;
521
+ }
522
+ return messages.slice(lastUserIndex + 1).filter((message) => message.role === "agent").map((message) => extractText(message.parts)).filter((text) => text.length > 0).join("");
523
+ }
524
+ function isFailureState(state) {
525
+ return state === "failed" || state === "rejected";
526
+ }
527
+ function extractFailureReason(task) {
528
+ if (!isFailureState(task.status.state) || !task.status.message) return void 0;
529
+ return extractText(task.status.message.parts);
530
+ }
531
+ function observationEventKey(task, result, failureReason) {
532
+ const digest = createHash("sha256").update(JSON.stringify({
533
+ state: task.status.state,
534
+ timestamp: task.status.timestamp,
535
+ message: task.status.message,
536
+ result,
537
+ failureReason
538
+ })).digest("hex");
539
+ return `a2a-status-observation:${task.id}:${digest}`;
540
+ }
541
+ function snapshotEventKey(task) {
542
+ const digest = createHash("sha256").update(JSON.stringify(task)).digest("hex");
543
+ return `a2a-task-snapshot:${task.id}:${digest}`;
544
+ }
545
+ function isGovernedObservation(state) {
546
+ return state === "completed" || state === "failed" || state === "rejected" || state === "input-required" || state === "canceled";
547
+ }
548
+ function isA2ATaskSnapshot(value) {
549
+ return typeof value === "object" && value !== null && typeof value.id === "string" && typeof value.status === "object" && value.status !== null;
550
+ }
551
+ function snapshotSequence(value) {
552
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
553
+ }
554
+ var A2ATaskStoreAdapter = class {
555
+ constructor(options) {
556
+ this.snapshotSequences = /* @__PURE__ */ new Map();
557
+ this.taskStore = options.taskStore;
558
+ this.workItemStore = options.workItemStore;
559
+ this.lifecycle = options.lifecycle;
560
+ this.scope = options.scope;
561
+ this.runBridge = createTaskRunBridge({
562
+ lifecycle: this.lifecycle,
563
+ taskStore: this.taskStore,
564
+ workItemStore: this.workItemStore
565
+ });
566
+ }
567
+ /**
568
+ * Persist an SDK task by projecting it onto a `TaskItem`, creating it when
569
+ * the id is unseen or updating it otherwise. A full snapshot of the SDK task
570
+ * is stored under `context.a2aTask` so `load` can restore it.
571
+ */
572
+ async save(task) {
573
+ let lastError;
574
+ for (let attempt = 0; attempt < 3; attempt += 1) {
575
+ try {
576
+ return await this.saveOnce(task);
577
+ } catch (error) {
578
+ lastError = error;
579
+ if (!isA2AConcurrentWrite(error) || attempt === 2) throw error;
580
+ await new Promise((resolve) => setTimeout(resolve, 10 * (attempt + 1)));
581
+ }
582
+ }
583
+ throw lastError;
584
+ }
585
+ async saveOnce(task) {
586
+ const { tenantId } = this.scope;
587
+ const { title, description } = extractTitleAndDescription(task);
588
+ const files = extractFiles(task);
589
+ const result = task.status.state === "completed" ? extractArtifactText(task.artifacts) || extractFinalTurnAgentText(task.history) : void 0;
590
+ const failureReason = extractFailureReason(task);
591
+ const a2aRunId = asString(task.metadata?.a2aRunId) ?? asString(task.metadata?.runId);
592
+ const context = {
593
+ a2aContextId: task.contextId,
594
+ ...a2aRunId ? { a2aRunId } : {},
595
+ threadId: asString(task.metadata?.threadId),
596
+ a2aTask: task
597
+ };
598
+ let existing = await this.taskStore.getById(tenantId, task.id);
599
+ if (existing) {
600
+ this.requireScope(existing, task.id);
601
+ await this.recordSnapshot(task, existing);
602
+ existing = await this.persistRunLink(existing, context, a2aRunId, task);
603
+ } else {
604
+ try {
605
+ existing = await this.taskStore.create({
606
+ id: task.id,
607
+ tenantId,
608
+ ownerType: "agent",
609
+ ownerId: this.scope.assistantId,
610
+ projectId: this.scope.projectId,
611
+ ...this.scope.workspaceId ? { workspaceId: this.scope.workspaceId } : {},
612
+ title,
613
+ description,
614
+ sourceId: "a2a",
615
+ status: "pending",
616
+ priority: "medium",
617
+ dependencies: asStringArray(task.metadata?.dependencies),
618
+ files,
619
+ context
620
+ });
621
+ } catch (error) {
622
+ existing = await this.taskStore.getById(tenantId, task.id);
623
+ if (!existing) throw error;
624
+ this.requireScope(existing, task.id);
625
+ }
626
+ await this.recordSnapshot(task, existing);
627
+ }
628
+ const actor = `a2a:${this.scope.assistantId}`;
629
+ const threadId = asString(task.metadata?.threadId);
630
+ if (task.status.state === "working") {
631
+ existing = await this.refresh(existing);
632
+ const resumesInterruption = existing.status === "interrupted" && task.metadata?.a2aResume === true;
633
+ if (existing.status === "pending" || resumesInterruption) {
634
+ await this.requireLifecycleSuccess("start", task.id, await this.runBridge.beginRun({
635
+ tenantId,
636
+ task: existing,
637
+ actor,
638
+ ...threadId ? { threadId } : {}
639
+ }));
640
+ }
641
+ return;
642
+ }
643
+ if (!isGovernedObservation(task.status.state)) return;
644
+ existing = await this.refresh(existing);
645
+ await this.recordObservation(task, existing, result, failureReason);
646
+ if (["completed", "failed", "cancelled"].includes(existing.status)) return;
647
+ if (task.status.state === "input-required" && this.isExactInterruptionReplay(task, existing)) return;
648
+ if (task.status.state === "completed" && existing.status === "interrupted") {
649
+ const resumed = await this.lifecycle.resumeInterruption({
650
+ tenantId,
651
+ taskId: task.id,
652
+ actor,
653
+ ...threadId ? { threadId } : {}
654
+ });
655
+ await this.requireLifecycleSuccess("resume", task.id, resumed);
656
+ existing = await this.refresh(existing);
657
+ }
658
+ if (existing.status === "pending") {
659
+ const started = await this.requireLifecycleSuccess("start", task.id, await this.runBridge.beginRun({
660
+ tenantId,
661
+ task: existing,
662
+ actor,
663
+ ...threadId ? { threadId } : {}
664
+ }));
665
+ existing = started.task;
666
+ }
667
+ await this.applyObservation(task, existing, result, failureReason);
668
+ }
669
+ /** Re-read the canonical task immediately before a lifecycle write. */
670
+ async refresh(fallback) {
671
+ const current = await this.taskStore.getById(this.scope.tenantId, fallback.id);
672
+ if (!current) return fallback;
673
+ this.requireScope(current, fallback.id);
674
+ return current;
675
+ }
676
+ /**
677
+ * Persist the A2A↔run link on an existing TaskItem without replacing
678
+ * service-owned context (interruption audit, lifecycle fields, snapshots).
679
+ */
680
+ async persistRunLink(item, context, a2aRunId, task) {
681
+ if (a2aRunId === void 0) return item;
682
+ const current = item.context ?? {};
683
+ if (current.a2aRunId === a2aRunId) return item;
684
+ const next = {
685
+ ...current,
686
+ a2aContextId: context.a2aContextId,
687
+ threadId: context.threadId,
688
+ a2aRunId
689
+ };
690
+ const updated = await this.taskStore.update(this.scope.tenantId, task.id, { context: next });
691
+ return updated ?? item;
692
+ }
693
+ /**
694
+ * Complete a run result through the lifecycle, resuming an interrupted
695
+ * TaskItem first when the run succeeded after HITL.
696
+ *
697
+ * @throws When the canonical task cannot transition (for example a review
698
+ * interruption that still requires explicit approval).
699
+ */
700
+ async completeRun(task, result) {
701
+ const { tenantId } = this.scope;
702
+ const actor = `a2a:${this.scope.assistantId}`;
703
+ const threadId = asString(task.metadata?.threadId);
704
+ let item = await this.taskStore.getById(tenantId, task.id);
705
+ if (!item) {
706
+ throw new Error(`A2A completed run cannot reconcile missing task ${task.id}`);
707
+ }
708
+ if (item.status === "interrupted") {
709
+ const resumed = await this.lifecycle.resumeInterruption({
710
+ tenantId,
711
+ taskId: task.id,
712
+ actor,
713
+ ...threadId ? { threadId } : {}
714
+ });
715
+ if (!resumed.success) {
716
+ throw new Error(
717
+ `A2A completed run cannot resume interruption: ${resumed.code}: ${resumed.error}`
718
+ );
719
+ }
720
+ item = await this.taskStore.getById(tenantId, task.id);
721
+ if (!item) throw new Error(`A2A task ${task.id} disappeared after resume`);
722
+ }
723
+ if (item.status === "pending") {
724
+ const started = await this.runBridge.beginRun({
725
+ tenantId,
726
+ task: item,
727
+ actor,
728
+ ...threadId ? { threadId } : {}
729
+ });
730
+ if (!started.success) {
731
+ throw new Error(
732
+ `A2A completed run cannot start task: ${started.code}: ${started.error}`
733
+ );
734
+ }
735
+ item = started.task;
736
+ }
737
+ if (item.status !== "in_progress" && item.status !== "completed") {
738
+ throw new Error(
739
+ `A2A completed run cannot complete canonical ${item.status} task ${task.id}`
740
+ );
741
+ }
742
+ const resultArtifact = {
743
+ artifactId: `${task.id}:result`,
744
+ name: "result",
745
+ parts: [{ kind: "text", text: result.trim() || "(no output)" }]
746
+ };
747
+ const completed = {
748
+ ...task,
749
+ artifacts: [
750
+ ...(task.artifacts ?? []).filter(
751
+ (artifact) => artifact.artifactId !== resultArtifact.artifactId && artifact.name !== "result"
752
+ ),
753
+ resultArtifact
754
+ ],
755
+ status: { state: "completed", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
756
+ };
757
+ await this.save(completed);
758
+ const loaded = await this.load(task.id);
759
+ if (!loaded || loaded.status.state !== "completed") {
760
+ throw new Error(`A2A task ${task.id} did not persist as completed`);
761
+ }
762
+ return loaded;
763
+ }
764
+ async recordSnapshot(task, item) {
765
+ const incomingSequence = snapshotSequence(task.metadata?.snapshotSequence) ?? 0;
766
+ const currentSequence = this.snapshotSequences.get(task.id) ?? 0;
767
+ const sequence = Math.max(incomingSequence, currentSequence) + 1;
768
+ this.snapshotSequences.set(task.id, sequence);
769
+ const sequencedTask = {
770
+ ...task,
771
+ metadata: { ...task.metadata ?? {}, snapshotSequence: sequence }
772
+ };
773
+ const eventKey = snapshotEventKey(sequencedTask);
774
+ const workspaceId = item.workspaceId ?? this.scope.workspaceId;
775
+ await this.workItemStore.createIfAbsentByEventKey({
776
+ tenantId: this.scope.tenantId,
777
+ taskId: task.id,
778
+ projectId: this.scope.projectId,
779
+ action: "activity",
780
+ actor: `a2a:${this.scope.assistantId}`,
781
+ summary: "A2A task snapshot",
782
+ detail: { type: "a2a_task_snapshot", snapshotSequence: sequence, eventKey, task: sequencedTask },
783
+ eventKey,
784
+ ...asString(task.metadata?.threadId) ? { threadId: asString(task.metadata?.threadId) } : {},
785
+ ...workspaceId ? { workspaceId } : {}
786
+ });
787
+ }
788
+ async recordObservation(task, item, result, failureReason) {
789
+ const workspaceId = item.workspaceId ?? this.scope.workspaceId;
790
+ await this.workItemStore.createIfAbsentByEventKey({
791
+ tenantId: this.scope.tenantId,
792
+ taskId: task.id,
793
+ projectId: this.scope.projectId,
794
+ action: "activity",
795
+ actor: `a2a:${this.scope.assistantId}`,
796
+ summary: `A2A observed ${task.status.state}`,
797
+ detail: {
798
+ type: "a2a_status_observation",
799
+ state: task.status.state,
800
+ ...result === void 0 ? {} : { result },
801
+ ...failureReason === void 0 ? {} : { failureReason },
802
+ ...task.status.message ? { message: extractText(task.status.message.parts) } : {}
803
+ },
804
+ eventKey: observationEventKey(task, result, failureReason),
805
+ ...asString(task.metadata?.threadId) ? { threadId: asString(task.metadata?.threadId) } : {},
806
+ ...workspaceId ? { workspaceId } : {}
807
+ });
808
+ }
809
+ async applyObservation(task, item, result, failureReason) {
810
+ const actor = `a2a:${this.scope.assistantId}`;
811
+ item = await this.refresh(item);
812
+ const threadId = asString(task.metadata?.threadId);
813
+ if (task.status.state === "canceled") {
814
+ await this.requireLifecycleSuccess("cancel", task.id, await this.runBridge.settleRun({
815
+ tenantId: this.scope.tenantId,
816
+ task: item,
817
+ actor,
818
+ ...threadId ? { threadId } : {},
819
+ outcome: { kind: "cancelled", summary: "A2A observed canceled" }
820
+ }));
821
+ return;
822
+ }
823
+ if (isFailureState(task.status.state)) {
824
+ await this.requireLifecycleSuccess("fail", task.id, await this.runBridge.settleRun({
825
+ tenantId: this.scope.tenantId,
826
+ task: item,
827
+ actor,
828
+ ...threadId ? { threadId } : {},
829
+ outcome: {
830
+ kind: "failed",
831
+ lifecycleHandled: false,
832
+ error: failureReason?.trim() || `A2A observed ${task.status.state}`
833
+ }
834
+ }));
835
+ return;
836
+ }
837
+ if (task.status.state === "input-required") {
838
+ const observed = asRecord(task.metadata?.interruption);
839
+ const observedType = asString(observed?.type);
840
+ const interruptionType = observedType === "review_required" || observedType === "user_decision" ? "user_decision" : "missing_input";
841
+ const summary = asString(observed?.summary) || (task.status.message ? extractText(task.status.message.parts).trim() : "") || "A2A requires input";
842
+ await this.requireLifecycleSuccess("interrupt", task.id, await this.lifecycle.interruptTask({
843
+ tenantId: this.scope.tenantId,
844
+ taskId: task.id,
845
+ type: interruptionType,
846
+ summary,
847
+ actor,
848
+ ...threadId ? { threadId } : {}
849
+ }));
850
+ if (observedType === "review_required" && observed) {
851
+ await this.preserveObservedInterruption(task.id, observed);
852
+ }
853
+ return;
854
+ }
855
+ await this.requireLifecycleSuccess("complete", task.id, await this.lifecycle.completeAgentTask({
856
+ tenantId: this.scope.tenantId,
857
+ taskId: task.id,
858
+ result: result?.trim() || "(no output)",
859
+ beliefImpact: [],
860
+ actor,
861
+ ...threadId ? { threadId } : {}
862
+ }));
863
+ }
864
+ /**
865
+ * Preserve a review interruption's canonical type/metadata after the generic
866
+ * lifecycle interrupt (which only models missing_input/user_decision).
867
+ */
868
+ async preserveObservedInterruption(taskId, observed) {
869
+ const item = await this.taskStore.getById(this.scope.tenantId, taskId);
870
+ if (!item) return;
871
+ const context = item.context ?? {};
872
+ const current = asRecord(context.interruption) ?? {};
873
+ await this.taskStore.update(this.scope.tenantId, taskId, {
874
+ context: {
875
+ ...context,
876
+ interruption: { ...current, ...observed, type: "review_required" }
877
+ }
878
+ });
879
+ }
880
+ async requireLifecycleSuccess(operation, taskId, result) {
881
+ if (result.success) {
882
+ if (result.warnings?.length) {
883
+ console.warn({ event: "a2a:lifecycle:warnings", operation, taskId, warnings: result.warnings });
884
+ }
885
+ return result;
886
+ }
887
+ throw new Error(`A2A lifecycle ${operation} failed: ${result.code}: ${result.error}`);
888
+ }
889
+ isExactInterruptionReplay(task, item) {
890
+ const observed = task.metadata?.interruption;
891
+ const canonical = item.context?.interruption;
892
+ if (item.status !== "interrupted" || !canonical || typeof canonical !== "object") return false;
893
+ if (observed && typeof observed === "object") return isDeepStrictEqual(observed, canonical);
894
+ const observedSummary = task.status.message ? extractText(task.status.message.parts).trim() : "";
895
+ return observedSummary.length > 0 && canonical.summary === observedSummary;
896
+ }
897
+ /**
898
+ * Restore an SDK task from the stored snapshot, overlaying the current
899
+ * `TaskItem.status` onto `task.status.state`. Returns `undefined` when the
900
+ * task is unknown to the tenant or has no A2A snapshot.
901
+ */
902
+ async load(taskId) {
903
+ const item = await this.taskStore.getById(this.scope.tenantId, taskId);
904
+ if (!item || !this.isInScope(item)) return void 0;
905
+ const pageSize = 100;
906
+ let selected;
907
+ for (let offset = 0; ; offset += pageSize) {
908
+ const activities = await this.workItemStore.list({
909
+ tenantId: this.scope.tenantId,
910
+ taskId,
911
+ action: "activity",
912
+ order: "desc",
913
+ limit: pageSize,
914
+ offset
915
+ });
916
+ for (const activity of activities) {
917
+ if (activity.detail?.type !== "a2a_task_snapshot" || !isA2ATaskSnapshot(activity.detail.task)) continue;
918
+ const sequence = snapshotSequence(activity.detail.snapshotSequence) ?? snapshotSequence(activity.detail.task.metadata?.snapshotSequence);
919
+ const candidate = {
920
+ task: activity.detail.task,
921
+ sequence,
922
+ eventKey: asString(activity.detail.eventKey) ?? activity.eventKey ?? snapshotEventKey(activity.detail.task),
923
+ statusTimestamp: Date.parse(activity.detail.task.status.timestamp ?? ""),
924
+ createdAt: activity.createdAt,
925
+ id: activity.id
926
+ };
927
+ if (!selected || this.isLaterSnapshot(candidate, selected)) selected = candidate;
928
+ }
929
+ if (activities.length < pageSize) break;
930
+ }
931
+ const snapshot = selected?.task ?? item.context?.a2aTask;
932
+ if (!isA2ATaskSnapshot(snapshot)) return void 0;
933
+ const restoredSequence = selected?.sequence ?? snapshotSequence(snapshot.metadata?.snapshotSequence);
934
+ if (restoredSequence !== void 0) {
935
+ this.snapshotSequences.set(taskId, Math.max(this.snapshotSequences.get(taskId) ?? 0, restoredSequence));
936
+ }
937
+ const state = mapTaskStatusToA2AState(item.status);
938
+ const interruption = item.context?.interruption;
939
+ const contextRunId = asString(item.context?.a2aRunId);
940
+ const contextThreadId = asString(item.context?.threadId);
941
+ const metadata = {
942
+ ...snapshot.metadata ?? {},
943
+ ...contextRunId === void 0 ? {} : { a2aRunId: contextRunId },
944
+ ...contextThreadId === void 0 ? {} : { threadId: contextThreadId },
945
+ ...interruption && typeof interruption === "object" ? { interruption } : {}
946
+ };
947
+ const nonResultArtifacts = (snapshot.artifacts ?? []).filter((artifact) => artifact.name !== "result");
948
+ const artifacts = item.status === "completed" && item.result?.trim() ? [...nonResultArtifacts, {
949
+ artifactId: `${item.id}:result`,
950
+ name: "result",
951
+ parts: [{ kind: "text", text: item.result }]
952
+ }] : nonResultArtifacts;
953
+ const message = state === "failed" && item.failureReason?.trim() ? {
954
+ kind: "message",
955
+ messageId: `${item.id}:failure`,
956
+ role: "agent",
957
+ parts: [{ kind: "text", text: item.failureReason }]
958
+ } : interruption && typeof interruption === "object" && typeof interruption.summary === "string" ? {
959
+ kind: "message",
960
+ messageId: `${item.id}:interruption`,
961
+ role: "agent",
962
+ parts: [{ kind: "text", text: interruption.summary }]
963
+ } : snapshot.status.message;
964
+ return {
965
+ ...snapshot,
966
+ metadata,
967
+ ...artifacts.length > 0 ? { artifacts } : { artifacts: void 0 },
968
+ status: {
969
+ ...snapshot.status,
970
+ state,
971
+ ...message ? { message } : {}
972
+ }
973
+ };
974
+ }
975
+ isInScope(item) {
976
+ return item.tenantId === this.scope.tenantId && item.projectId === this.scope.projectId && item.ownerType === "agent" && item.ownerId === this.scope.assistantId && item.sourceId === "a2a";
977
+ }
978
+ requireScope(item, taskId) {
979
+ if (!this.isInScope(item)) throw new A2ATaskScopeConflictError(taskId);
980
+ }
981
+ isLaterSnapshot(candidate, selected) {
982
+ if (candidate.sequence !== void 0 || selected.sequence !== void 0) {
983
+ if (candidate.sequence === void 0) return false;
984
+ if (selected.sequence === void 0) return true;
985
+ if (candidate.sequence !== selected.sequence) return candidate.sequence > selected.sequence;
986
+ const candidateTimestamp = Number.isNaN(candidate.statusTimestamp) ? Number.NEGATIVE_INFINITY : candidate.statusTimestamp;
987
+ const selectedTimestamp = Number.isNaN(selected.statusTimestamp) ? Number.NEGATIVE_INFINITY : selected.statusTimestamp;
988
+ if (candidateTimestamp !== selectedTimestamp) return candidateTimestamp > selectedTimestamp;
989
+ return candidate.eventKey > selected.eventKey;
990
+ }
991
+ return candidate.createdAt.getTime() > selected.createdAt.getTime() || candidate.createdAt.getTime() === selected.createdAt.getTime() && candidate.id > selected.id;
992
+ }
993
+ };
994
+ function isA2AConcurrentWrite(error) {
995
+ const message = error instanceof Error ? error.message : String(error);
996
+ return message.includes("TASK_STATUS_CONFLICT") || message.includes("TASK_STORE_WRITE_FAILED") || message.includes("Task changed concurrently");
997
+ }
998
+
999
+ // src/services/a2a/AxiomAgentExecutor.ts
1000
+ import { v4 as uuidv4 } from "uuid";
1001
+ import { agentInstanceManager, isOfficialInterruptId } from "@axiom-lattice/core";
1002
+ import { MessageChunkTypes as MessageChunkTypes2 } from "@axiom-lattice/protocols";
1003
+ import { A2AError as A2AError2 } from "@a2a-js/sdk/server";
1004
+
1005
+ // src/services/a2a/a2aFileRefs.ts
1006
+ function filePartsToRefs(parts, addedBy) {
1007
+ const refs = [];
1008
+ for (const part of parts) {
1009
+ if (part.kind !== "file") continue;
1010
+ const file = part.file;
1011
+ if ("bytes" in file) {
1012
+ throw new Error(
1013
+ "A2A file parts with inline bytes are not supported by this pure function \u2014 bytes ingest requires filePartsToRefsWithIngest with an A2AFileIngestor; provide a file uri otherwise"
1014
+ );
1015
+ }
1016
+ refs.push({
1017
+ uri: file.uri,
1018
+ ...file.name ? { name: file.name } : {},
1019
+ ...file.mimeType ? { mimeType: file.mimeType } : {},
1020
+ addedBy
1021
+ });
1022
+ }
1023
+ return refs;
1024
+ }
1025
+ async function filePartsToRefsWithIngest(parts, addedBy, ingestor, dedupeKeyBase, projectId) {
1026
+ if (!ingestor) {
1027
+ return filePartsToRefs(parts, addedBy);
1028
+ }
1029
+ const refs = [];
1030
+ for (let index = 0; index < parts.length; index += 1) {
1031
+ const part = parts[index];
1032
+ if (part.kind !== "file") continue;
1033
+ const dedupeKey = `${dedupeKeyBase}:${index}`;
1034
+ refs.push(await ingestor.ingest(part, dedupeKey, projectId, addedBy));
1035
+ }
1036
+ return refs;
1037
+ }
1038
+
1039
+ // src/services/a2a/A2ARunService.ts
1040
+ import { randomUUID } from "crypto";
1041
+ import {
1042
+ extractAgentRunInputMessageIds
1043
+ } from "@axiom-lattice/core";
1044
+ import { MessageChunkTypes } from "@axiom-lattice/protocols";
1045
+ var TERMINAL_STREAM_CHUNK_TYPES = /* @__PURE__ */ new Set([
1046
+ MessageChunkTypes.MESSAGE_COMPLETED,
1047
+ MessageChunkTypes.MESSAGE_FAILED,
1048
+ MessageChunkTypes.INTERRUPT
1049
+ ]);
1050
+ async function* stopAtTerminalChunks(source) {
1051
+ for await (const chunk of source) {
1052
+ yield chunk;
1053
+ if (TERMINAL_STREAM_CHUNK_TYPES.has(chunk.type)) return;
1054
+ }
1055
+ }
1056
+ var A2ARunNotFoundError = class extends Error {
1057
+ constructor(runId) {
1058
+ super(`A2A run not found: ${runId}`);
1059
+ this.runId = runId;
1060
+ /** Stable error code for API/protocol mapping. */
1061
+ this.code = "A2A_RUN_NOT_FOUND";
1062
+ this.name = "A2ARunNotFoundError";
1063
+ }
1064
+ };
1065
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
1066
+ "success",
1067
+ "error",
1068
+ "interrupted",
1069
+ "cancelled",
1070
+ "timeout"
1071
+ ]);
1072
+ function isTerminal(status) {
1073
+ return TERMINAL_STATUSES.has(status);
1074
+ }
1075
+ function mapStatus(status) {
1076
+ switch (status) {
1077
+ case "pending":
1078
+ case "running":
1079
+ case "success":
1080
+ case "error":
1081
+ case "interrupted":
1082
+ case "timeout":
1083
+ return status;
1084
+ case "cancelled":
1085
+ return "interrupted";
1086
+ }
1087
+ }
1088
+ function messageIdFromRun(run) {
1089
+ if (typeof run.input?.id === "string" && run.input.id.length > 0) {
1090
+ return run.input.id;
1091
+ }
1092
+ if (run.inputMessageIds.length > 0) return run.inputMessageIds[0];
1093
+ return run.runId;
1094
+ }
1095
+ var A2ARunService = class {
1096
+ constructor(deps) {
1097
+ this.ledger = deps.ledger;
1098
+ this.chunkSource = deps.chunkSource;
1099
+ this.wake = deps.wake ?? wakeRunTaskQueue;
1100
+ this.getLatestCursor = deps.getLatestCursor;
1101
+ this.getRunCancelRouter = deps.getRunCancelRouter ?? (() => getRunCancelRouter());
1102
+ this.idFactory = deps.idFactory ?? randomUUID;
1103
+ this.now = deps.now ?? Date.now;
1104
+ this.sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1105
+ this.pollIntervalMs = deps.pollIntervalMs ?? 200;
1106
+ this.joinTimeoutMs = deps.joinTimeoutMs ?? 3e4;
1107
+ this.cancelTimeoutMs = deps.cancelTimeoutMs ?? 1e4;
1108
+ this.maxActiveRunsForTenant = deps.maxActiveRunsForTenant;
1109
+ }
1110
+ /**
1111
+ * Creates exactly one pending `run_api` row for an A2A request.
1112
+ *
1113
+ * @param input Tenant/project/agent scope, A2A ids, and message or command.
1114
+ * @returns The run id, chunk-stream anchor/cursor, and pending status.
1115
+ * @remarks The stream cursor is captured before the row is committed so
1116
+ * already-emitted chunks cannot be lost. The queue wake is best-effort and
1117
+ * never awaited: a hung wake must not delay the protocol response.
1118
+ */
1119
+ async create(input) {
1120
+ const streamCursor = await this.captureLatestCursor(
1121
+ input.threadId,
1122
+ input.assistantId
1123
+ );
1124
+ const runId = this.idFactory();
1125
+ const hasCommand = input.command !== void 0;
1126
+ const messageId = hasCommand ? runId : input.message.id?.trim() || this.idFactory();
1127
+ const runInput = hasCommand ? null : { id: messageId, message: input.message.content };
1128
+ await this.ledger.createPendingRun({
1129
+ runId,
1130
+ tenantId: input.tenantId,
1131
+ ...input.workspaceId === void 0 ? {} : { workspaceId: input.workspaceId },
1132
+ projectId: input.projectId,
1133
+ assistantId: input.assistantId,
1134
+ threadId: input.threadId,
1135
+ input: runInput,
1136
+ command: input.command ?? null,
1137
+ config: input.config ?? null,
1138
+ metadata: {
1139
+ ...input.metadata ?? {},
1140
+ a2aTaskId: input.a2aTaskId,
1141
+ a2aContextId: input.threadId
1142
+ },
1143
+ inputMessageIds: runInput ? extractAgentRunInputMessageIds(runInput) : [],
1144
+ ...this.maxActiveRunsForTenant === void 0 ? {} : { maxActiveRunsForTenant: this.maxActiveRunsForTenant }
1145
+ });
1146
+ this.wakeSafely();
1147
+ return {
1148
+ runId,
1149
+ messageId,
1150
+ ...streamCursor === void 0 ? {} : { streamCursor },
1151
+ status: "pending",
1152
+ terminal: false
1153
+ };
1154
+ }
1155
+ /**
1156
+ * Waits for a `run_api` run to reach a terminal lifecycle state.
1157
+ *
1158
+ * @param timeoutMs Blocking budget (default {@link joinTimeoutMs}).
1159
+ * @returns The terminal handle, or a non-terminal `timeout` handle when the
1160
+ * budget ends.
1161
+ * @throws {A2ARunNotFoundError} When the run is absent, legacy-sourced, or out
1162
+ * of scope.
1163
+ */
1164
+ async join(tenantId, threadId, assistantId, runId, timeoutMs) {
1165
+ const budget = timeoutMs ?? this.joinTimeoutMs;
1166
+ const deadline = this.now() + Math.max(0, budget);
1167
+ for (; ; ) {
1168
+ const run = await this.readScoped(tenantId, threadId, assistantId, runId);
1169
+ if (!run) throw new A2ARunNotFoundError(runId);
1170
+ if (isTerminal(run.status)) return this.toHandle(run);
1171
+ const remaining = deadline - this.now();
1172
+ if (remaining <= 0) {
1173
+ return { ...this.toHandle(run), status: "timeout", terminal: false };
1174
+ }
1175
+ await this.sleep(Math.max(1, Math.min(this.pollIntervalMs, remaining)));
1176
+ }
1177
+ }
1178
+ /**
1179
+ * Requests cancellation of one exact `run_api` run and waits (bounded) for
1180
+ * terminal state.
1181
+ *
1182
+ * @throws {A2ARunNotFoundError} When the run is absent, legacy-sourced, or out
1183
+ * of scope.
1184
+ */
1185
+ async cancel(tenantId, threadId, assistantId, runId) {
1186
+ const run = await this.readScoped(tenantId, threadId, assistantId, runId);
1187
+ if (!run) throw new A2ARunNotFoundError(runId);
1188
+ if (isTerminal(run.status)) return;
1189
+ const router = this.getRunCancelRouter();
1190
+ let result;
1191
+ if (router) {
1192
+ result = await router.requestCancelById({
1193
+ tenantId,
1194
+ assistantId,
1195
+ threadId,
1196
+ runId,
1197
+ reason: "a2a.cancel"
1198
+ });
1199
+ } else {
1200
+ result = await this.ledger.requestCancel(tenantId, runId);
1201
+ }
1202
+ if (result === "not_found") throw new A2ARunNotFoundError(runId);
1203
+ if (result === "terminal") return;
1204
+ await this.waitForTerminal(tenantId, threadId, assistantId, runId);
1205
+ }
1206
+ /**
1207
+ * Attaches to the chunk stream for a run's message id.
1208
+ *
1209
+ * @param cursor Thread-stream position from the run handle. When supplied and
1210
+ * supported, the cursor-backed thread stream is used instead of message-id
1211
+ * anchoring (required for command runs).
1212
+ * @param scope Optional exact `run_api` scope guard, validated on first read.
1213
+ */
1214
+ stream(threadId, assistantId, messageId, cursor, scope) {
1215
+ const useCursorStream = typeof cursor === "string" && cursor.trim() !== "" && this.chunkSource.streamThread !== void 0;
1216
+ const createSource = () => useCursorStream ? this.chunkSource.streamThread(threadId, assistantId, cursor) : this.chunkSource.stream(threadId, assistantId, messageId);
1217
+ if (!scope) return createSource();
1218
+ return this.guardStream(
1219
+ createSource,
1220
+ threadId,
1221
+ assistantId,
1222
+ scope
1223
+ );
1224
+ }
1225
+ /**
1226
+ * Reads the authoritative `run_api` ledger record.
1227
+ *
1228
+ * @param threadId Optional scope guard; legacy runs are always rejected.
1229
+ * @param assistantId Optional scope guard.
1230
+ */
1231
+ async getRun(tenantId, runId, threadId, assistantId) {
1232
+ const run = await this.ledger.getRun(tenantId, runId);
1233
+ if (!run || run.source !== "run_api") return void 0;
1234
+ if (run.tenantId !== tenantId) return void 0;
1235
+ if (threadId !== void 0 && run.threadId !== threadId) return void 0;
1236
+ if (assistantId !== void 0 && run.assistantId !== assistantId) {
1237
+ return void 0;
1238
+ }
1239
+ return run;
1240
+ }
1241
+ async waitForTerminal(tenantId, threadId, assistantId, runId) {
1242
+ const deadline = this.now() + this.cancelTimeoutMs;
1243
+ for (; ; ) {
1244
+ const run = await this.readScoped(tenantId, threadId, assistantId, runId);
1245
+ if (!run || isTerminal(run.status)) return;
1246
+ const remaining = deadline - this.now();
1247
+ if (remaining <= 0) return;
1248
+ await this.sleep(Math.max(1, Math.min(this.pollIntervalMs, remaining)));
1249
+ }
1250
+ }
1251
+ async readScoped(tenantId, threadId, assistantId, runId) {
1252
+ const run = await this.ledger.getRun(tenantId, runId);
1253
+ if (!run || run.source !== "run_api" || run.tenantId !== tenantId || run.threadId !== threadId || run.assistantId !== assistantId) {
1254
+ return void 0;
1255
+ }
1256
+ return run;
1257
+ }
1258
+ toHandle(run) {
1259
+ return {
1260
+ runId: run.runId,
1261
+ messageId: messageIdFromRun(run),
1262
+ status: mapStatus(run.status),
1263
+ terminal: isTerminal(run.status)
1264
+ };
1265
+ }
1266
+ async captureLatestCursor(threadId, assistantId) {
1267
+ if (!this.getLatestCursor) return void 0;
1268
+ try {
1269
+ const cursor = await this.getLatestCursor(threadId, assistantId);
1270
+ if (typeof cursor !== "string" || cursor.trim() === "") return void 0;
1271
+ return cursor;
1272
+ } catch {
1273
+ return void 0;
1274
+ }
1275
+ }
1276
+ /**
1277
+ * Lazily verifies the exact `run_api` scope before opening the chunk source.
1278
+ * Validation is deferred so `stream()` remains synchronous and an unused
1279
+ * stream never touches the ledger.
1280
+ */
1281
+ guardStream(createSource, threadId, assistantId, scope) {
1282
+ return {
1283
+ [Symbol.asyncIterator]: () => {
1284
+ let validation;
1285
+ let inner;
1286
+ const validate = () => {
1287
+ validation ?? (validation = (async () => {
1288
+ const run = await this.readScoped(
1289
+ scope.tenantId,
1290
+ threadId,
1291
+ assistantId,
1292
+ scope.runId
1293
+ );
1294
+ if (!run) throw new A2ARunNotFoundError(scope.runId);
1295
+ })());
1296
+ return validation;
1297
+ };
1298
+ const source = () => {
1299
+ inner ?? (inner = createSource()[Symbol.asyncIterator]());
1300
+ return inner;
1301
+ };
1302
+ const settleValidation = async () => {
1303
+ if (!validation) return;
1304
+ try {
1305
+ await validation;
1306
+ } catch {
1307
+ }
1308
+ };
1309
+ return {
1310
+ next: async () => {
1311
+ await validate();
1312
+ return source().next();
1313
+ },
1314
+ return: async (value) => {
1315
+ await settleValidation();
1316
+ if (!inner) return { done: true, value };
1317
+ return inner.return ? inner.return(value) : { done: true, value };
1318
+ },
1319
+ throw: async (error) => {
1320
+ await settleValidation();
1321
+ if (!inner) throw error;
1322
+ return inner.throw ? inner.throw(error) : Promise.reject(error);
1323
+ }
1324
+ };
1325
+ }
1326
+ };
1327
+ }
1328
+ wakeSafely() {
1329
+ try {
1330
+ void this.wake().catch(() => void 0);
1331
+ } catch {
1332
+ }
1333
+ }
1334
+ };
1335
+
1336
+ // src/services/a2a/AxiomAgentExecutor.ts
1337
+ var A2ABlockingDeadlineError = class extends Error {
1338
+ constructor() {
1339
+ super("A2A blocking execution deadline expired");
1340
+ this.name = "A2ABlockingDeadlineError";
1341
+ }
1342
+ };
1343
+ var TERMINAL_A2A_STATES = /* @__PURE__ */ new Set([
1344
+ "completed",
1345
+ "failed",
1346
+ "canceled",
1347
+ "rejected"
1348
+ ]);
1349
+ function isoNow() {
1350
+ return (/* @__PURE__ */ new Date()).toISOString();
1351
+ }
1352
+ function asString2(value) {
1353
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1354
+ }
1355
+ function asRecord2(value) {
1356
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1357
+ }
1358
+ function extractText2(parts) {
1359
+ return parts.filter((part) => part.kind === "text").map((part) => part.text).join("\n");
1360
+ }
1361
+ function dataParts(parts) {
1362
+ return parts.filter(
1363
+ (part) => part.kind === "data"
1364
+ );
1365
+ }
1366
+ function soleOfficialInterruptId(task) {
1367
+ const ids = (task?.artifacts ?? []).filter((artifact) => artifact.name === "interrupt").flatMap((artifact) => artifact.parts).filter((part) => part.kind === "data").map((part) => asString2(part.data.id)).filter((id) => id !== void 0 && isOfficialInterruptId(id));
1368
+ return ids.length === 1 ? ids[0] : void 0;
1369
+ }
1370
+ function agentMessage(text) {
1371
+ return {
1372
+ kind: "message",
1373
+ messageId: uuidv4(),
1374
+ role: "agent",
1375
+ parts: [{ kind: "text", text }]
1376
+ };
1377
+ }
1378
+ function assistantHasGovernedTask(assistant) {
1379
+ const graph = assistant.graphDefinition;
1380
+ const middleware = graph && typeof graph === "object" ? graph.middleware : void 0;
1381
+ const taskMiddleware = Array.isArray(middleware) ? middleware.find((entry) => !!entry && typeof entry === "object" && entry.type === "task" && entry.enabled === true) : void 0;
1382
+ const allowedTools = taskMiddleware?.allowedTools;
1383
+ return !!taskMiddleware && (allowedTools === void 0 || Array.isArray(allowedTools) && (allowedTools.length === 0 || allowedTools.includes("manage_task")));
1384
+ }
1385
+ var AxiomAgentExecutor = class {
1386
+ constructor(options) {
1387
+ this.scope = options.scope;
1388
+ this.assistant = options.assistant;
1389
+ this.taskStore = options.taskStore;
1390
+ this.runService = options.runService;
1391
+ this.ingestor = options.ingestor;
1392
+ this.blocking = options.blocking ?? true;
1393
+ this.getInterrupts = options.getInterrupts ?? ((threadId) => agentInstanceManager.getAgent(this.agentParams(threadId)).getInterrupts());
1394
+ this.joinTimeoutMs = options.joinTimeoutMs ?? 3e4;
1395
+ this.overallTimeoutMs = this.blocking ? options.overallTimeoutMs ?? 10 * 6e4 : Number.POSITIVE_INFINITY;
1396
+ }
1397
+ /** Execute one A2A request through the durable run service. */
1398
+ async execute(ctx, bus) {
1399
+ const existing = ctx.task;
1400
+ const threadId = asString2(existing?.metadata?.threadId) ?? ctx.contextId;
1401
+ const dependencies = ctx.referenceTasks?.map((task) => task.id) ?? [];
1402
+ const resume = this.isResume(existing);
1403
+ const text = extractText2(ctx.userMessage.parts);
1404
+ const governed = this.hasGovernedTaskCapability();
1405
+ const resumeCanonicalTask = resume && !governed;
1406
+ if (existing && !resume) {
1407
+ throw A2AError2.invalidParams(
1408
+ `Task ${existing.id} is not waiting for input and cannot be resumed`
1409
+ );
1410
+ }
1411
+ if (!this.taskStore) {
1412
+ const initialTask2 = this.taskFor(ctx, threadId, [], dependencies, {
1413
+ handle: void 0,
1414
+ resume: false
1415
+ });
1416
+ bus.publish(initialTask2);
1417
+ await this.failTask(ctx, bus, initialTask2, new Error("A2A execution requires a canonical task store"));
1418
+ return;
1419
+ }
1420
+ if (existing && TERMINAL_A2A_STATES.has(existing.status.state)) {
1421
+ throw A2AError2.invalidParams(
1422
+ `Task ${existing.id} is terminal (${existing.status.state}) and cannot be modified`
1423
+ );
1424
+ }
1425
+ const interruption = asRecord2(existing?.metadata?.interruption);
1426
+ const reviewInterruption = interruption?.type === "review_required";
1427
+ const command = resume ? this.resumeCommand(
1428
+ ctx.userMessage.parts,
1429
+ text,
1430
+ reviewInterruption,
1431
+ soleOfficialInterruptId(existing)
1432
+ ) : void 0;
1433
+ if (reviewInterruption && !command) {
1434
+ bus.publish(agentMessage("Review response must be exactly 'approve', 'reject', or 'reject: <note>'."));
1435
+ return;
1436
+ }
1437
+ if (resume && !command) {
1438
+ throw A2AError2.invalidParams(`Task ${ctx.taskId} resume requires text or one DataPart`);
1439
+ }
1440
+ let inputFiles;
1441
+ try {
1442
+ inputFiles = await filePartsToRefsWithIngest(
1443
+ ctx.userMessage.parts,
1444
+ "user",
1445
+ this.ingestor,
1446
+ ctx.taskId,
1447
+ this.scope.projectId
1448
+ );
1449
+ } catch (err) {
1450
+ const initialTask2 = this.taskFor(ctx, threadId, [], dependencies, {
1451
+ handle: void 0,
1452
+ resume: false
1453
+ });
1454
+ await this.taskStore.save(initialTask2);
1455
+ bus.publish(initialTask2);
1456
+ await this.failTask(ctx, bus, initialTask2, err);
1457
+ return;
1458
+ }
1459
+ const requestText = this.requestText(ctx.taskId, text, inputFiles, governed, resume);
1460
+ const runInput = {
1461
+ tenantId: this.scope.tenantId,
1462
+ projectId: this.scope.projectId,
1463
+ ...this.scope.workspaceId ? { workspaceId: this.scope.workspaceId } : {},
1464
+ assistantId: this.scope.assistantId,
1465
+ threadId,
1466
+ a2aTaskId: ctx.taskId,
1467
+ message: { id: ctx.userMessage.messageId, role: "user", content: requestText },
1468
+ ...command ? { command } : {},
1469
+ config: {
1470
+ taskId: ctx.taskId,
1471
+ execution: { taskId: ctx.taskId }
1472
+ }
1473
+ };
1474
+ let handle;
1475
+ try {
1476
+ handle = await this.runService.create(runInput);
1477
+ } catch (err) {
1478
+ const initialTask2 = this.taskFor(ctx, threadId, inputFiles, dependencies, {
1479
+ handle: void 0,
1480
+ resume: false
1481
+ });
1482
+ await this.taskStore.save(initialTask2);
1483
+ bus.publish(initialTask2);
1484
+ await this.failTask(ctx, bus, initialTask2, err);
1485
+ return;
1486
+ }
1487
+ const projection = {
1488
+ ctx,
1489
+ ...existing ? { existing } : {},
1490
+ threadId,
1491
+ inputFiles,
1492
+ dependencies,
1493
+ handle,
1494
+ governed,
1495
+ resume: resumeCanonicalTask
1496
+ };
1497
+ const initialTask = this.taskFor(ctx, threadId, inputFiles, dependencies, {
1498
+ handle,
1499
+ resume: resumeCanonicalTask
1500
+ });
1501
+ await this.taskStore.save(initialTask);
1502
+ bus.publish(initialTask);
1503
+ if (!this.blocking) {
1504
+ void this.projectRun(bus, projection).catch((err) => {
1505
+ console.warn({
1506
+ event: "a2a:run:background_projection_failed",
1507
+ taskId: ctx.taskId,
1508
+ runId: handle.runId,
1509
+ error: err instanceof Error ? err.message : String(err)
1510
+ });
1511
+ });
1512
+ return;
1513
+ }
1514
+ await this.projectRun(bus, projection);
1515
+ }
1516
+ async projectRun(bus, projection) {
1517
+ const { ctx, threadId, handle } = projection;
1518
+ let outputText = "";
1519
+ let streamError;
1520
+ let dropChunks = false;
1521
+ const iterator = this.runService.stream(
1522
+ threadId,
1523
+ this.scope.assistantId,
1524
+ handle.messageId,
1525
+ handle.streamCursor,
1526
+ { tenantId: this.scope.tenantId, runId: handle.runId }
1527
+ )[Symbol.asyncIterator]();
1528
+ const consume = (async () => {
1529
+ try {
1530
+ for (; ; ) {
1531
+ const next = await iterator.next();
1532
+ if (next.done) return;
1533
+ if (dropChunks) return;
1534
+ const chunk = next.value;
1535
+ const chunkText = chunk.data?.content ?? "";
1536
+ if (chunk.type === MessageChunkTypes2.AI && chunkText) {
1537
+ outputText += chunkText;
1538
+ }
1539
+ if ((chunk.type === MessageChunkTypes2.AI || chunk.type === MessageChunkTypes2.TOOL) && chunkText) {
1540
+ bus.publish({
1541
+ kind: "status-update",
1542
+ taskId: ctx.taskId,
1543
+ contextId: ctx.contextId,
1544
+ status: {
1545
+ state: "working",
1546
+ message: agentMessage(chunkText),
1547
+ timestamp: isoNow()
1548
+ },
1549
+ final: false
1550
+ });
1551
+ }
1552
+ }
1553
+ } catch (err) {
1554
+ streamError = err;
1555
+ }
1556
+ })();
1557
+ let terminal;
1558
+ try {
1559
+ terminal = await this.waitForTerminal(handle.runId, threadId);
1560
+ } catch (err) {
1561
+ await iterator.return?.();
1562
+ await consume.catch(() => void 0);
1563
+ const baseTask = this.taskFor(ctx, threadId, projection.inputFiles, projection.dependencies, projection);
1564
+ if (err instanceof A2ABlockingDeadlineError) {
1565
+ try {
1566
+ await this.runService.cancel(
1567
+ this.scope.tenantId,
1568
+ threadId,
1569
+ this.scope.assistantId,
1570
+ handle.runId
1571
+ );
1572
+ } catch (cancelError) {
1573
+ console.warn({
1574
+ event: "a2a:timeout:cancel_failed",
1575
+ taskId: ctx.taskId,
1576
+ runId: handle.runId,
1577
+ error: cancelError instanceof Error ? cancelError.message : String(cancelError)
1578
+ });
1579
+ }
1580
+ await this.failTask(ctx, bus, baseTask, new Error("A2A run timed out"));
1581
+ return;
1582
+ }
1583
+ await this.failTask(ctx, bus, baseTask, err);
1584
+ return;
1585
+ }
1586
+ const run = await this.runService.getRun(
1587
+ this.scope.tenantId,
1588
+ handle.runId,
1589
+ threadId,
1590
+ this.scope.assistantId
1591
+ ).catch(() => void 0);
1592
+ const runRecord = asRecord2(run);
1593
+ const abortStream = runRecord?.cancelRequested === true || terminal.status === "timeout";
1594
+ if (abortStream) {
1595
+ dropChunks = true;
1596
+ await iterator.return?.();
1597
+ }
1598
+ await consume.catch(() => void 0);
1599
+ if (runRecord?.cancelRequested === true) {
1600
+ await this.finishCanceled(bus, projection);
1601
+ return;
1602
+ }
1603
+ switch (terminal.status) {
1604
+ case "success":
1605
+ await this.finishCompleted(bus, projection, outputText);
1606
+ return;
1607
+ case "interrupted":
1608
+ await this.finishInputRequired(bus, projection);
1609
+ return;
1610
+ case "error": {
1611
+ const canonical = this.taskStore ? await this.taskStore.load(ctx.taskId) : void 0;
1612
+ const canonicalMessage = canonical?.status.state === "failed" && canonical.status.message ? extractText2(canonical.status.message.parts).trim() : "";
1613
+ const message = canonicalMessage || asString2(runRecord?.error) || (streamError instanceof Error ? streamError.message : void 0) || "A2A run failed";
1614
+ await this.failTask(
1615
+ ctx,
1616
+ bus,
1617
+ this.taskFor(ctx, threadId, projection.inputFiles, projection.dependencies, projection),
1618
+ new Error(message)
1619
+ );
1620
+ return;
1621
+ }
1622
+ case "timeout":
1623
+ await this.failTask(
1624
+ ctx,
1625
+ bus,
1626
+ this.taskFor(ctx, threadId, projection.inputFiles, projection.dependencies, projection),
1627
+ new Error("A2A run timed out")
1628
+ );
1629
+ return;
1630
+ default:
1631
+ await this.failTask(
1632
+ ctx,
1633
+ bus,
1634
+ this.taskFor(ctx, threadId, projection.inputFiles, projection.dependencies, projection),
1635
+ new Error(`A2A run ended in unexpected state ${terminal.status}`)
1636
+ );
1637
+ }
1638
+ }
1639
+ async waitForTerminal(runId, threadId) {
1640
+ const deadline = Date.now() + Math.max(0, this.overallTimeoutMs);
1641
+ for (; ; ) {
1642
+ const remaining = deadline - Date.now();
1643
+ if (remaining <= 0) {
1644
+ throw new A2ABlockingDeadlineError();
1645
+ }
1646
+ const handle = await this.runService.join(
1647
+ this.scope.tenantId,
1648
+ threadId,
1649
+ this.scope.assistantId,
1650
+ runId,
1651
+ Math.min(this.joinTimeoutMs, remaining)
1652
+ );
1653
+ if (handle.terminal) return handle;
1654
+ }
1655
+ }
1656
+ async finishCompleted(bus, projection, outputText) {
1657
+ const { ctx, threadId, existing } = projection;
1658
+ const canonical = this.taskStore ? await this.taskStore.load(ctx.taskId) : void 0;
1659
+ const canonicalState = canonical?.status.state;
1660
+ if (canonicalState === "failed" || canonicalState === "rejected" || canonicalState === "canceled") {
1661
+ bus.publish({
1662
+ kind: "status-update",
1663
+ taskId: ctx.taskId,
1664
+ contextId: ctx.contextId,
1665
+ status: {
1666
+ state: canonicalState === "canceled" ? "canceled" : "failed",
1667
+ ...canonical?.status.message ? { message: canonical.status.message } : {},
1668
+ timestamp: isoNow()
1669
+ },
1670
+ final: true
1671
+ });
1672
+ return;
1673
+ }
1674
+ const canonicalResult = canonical?.artifacts?.find((artifact) => artifact.name === "result");
1675
+ const canonicalText = canonicalResult ? extractText2(canonicalResult.parts).trim() : "";
1676
+ const resultText = canonicalText || outputText;
1677
+ const resultArtifact = {
1678
+ artifactId: uuidv4(),
1679
+ name: "result",
1680
+ parts: [{ kind: "text", text: resultText }]
1681
+ };
1682
+ const completed = {
1683
+ ...this.taskFor(ctx, threadId, projection.inputFiles, projection.dependencies, projection),
1684
+ artifacts: [...existing?.artifacts ?? [], resultArtifact],
1685
+ status: { state: "completed", timestamp: isoNow() }
1686
+ };
1687
+ if (this.taskStore) await this.taskStore.save(completed);
1688
+ bus.publish({
1689
+ kind: "artifact-update",
1690
+ taskId: ctx.taskId,
1691
+ contextId: ctx.contextId,
1692
+ artifact: resultArtifact
1693
+ });
1694
+ bus.publish({
1695
+ kind: "status-update",
1696
+ taskId: ctx.taskId,
1697
+ contextId: ctx.contextId,
1698
+ status: { state: "completed", timestamp: isoNow() },
1699
+ final: true
1700
+ });
1701
+ }
1702
+ async finishInputRequired(bus, projection) {
1703
+ const { ctx, threadId } = projection;
1704
+ let interrupts = [];
1705
+ try {
1706
+ interrupts = await this.getInterrupts(threadId) ?? [];
1707
+ } catch (err) {
1708
+ console.warn({
1709
+ event: "a2a:interrupts:read_failed",
1710
+ taskId: ctx.taskId,
1711
+ threadId,
1712
+ error: err instanceof Error ? err.message : String(err)
1713
+ });
1714
+ }
1715
+ const canonical = this.taskStore ? await this.taskStore.load(ctx.taskId) : void 0;
1716
+ const summary = canonical?.status.message ? extractText2(canonical.status.message.parts).trim() : "";
1717
+ const interruptArtifact = interrupts.length === 0 ? void 0 : {
1718
+ artifactId: `${ctx.taskId}:interrupt`,
1719
+ name: "interrupt",
1720
+ parts: interrupts.map((interrupt) => {
1721
+ const payload = asRecord2(interrupt.payload);
1722
+ const schema = asRecord2(payload?.response_schema) ?? payload?.response_schema;
1723
+ return {
1724
+ kind: "data",
1725
+ data: {
1726
+ id: interrupt.interruptId,
1727
+ value: interrupt.payload,
1728
+ ...schema === void 0 ? {} : { response_schema: schema }
1729
+ }
1730
+ };
1731
+ })
1732
+ };
1733
+ const baseTask = this.taskFor(
1734
+ ctx,
1735
+ threadId,
1736
+ projection.inputFiles,
1737
+ projection.dependencies,
1738
+ projection
1739
+ );
1740
+ const pending = {
1741
+ ...baseTask,
1742
+ ...interruptArtifact ? {
1743
+ artifacts: [
1744
+ ...(baseTask.artifacts ?? []).filter(
1745
+ (artifact) => artifact.artifactId !== interruptArtifact.artifactId
1746
+ ),
1747
+ interruptArtifact
1748
+ ]
1749
+ } : {},
1750
+ status: {
1751
+ state: "input-required",
1752
+ ...summary ? { message: agentMessage(summary) } : {},
1753
+ timestamp: isoNow()
1754
+ }
1755
+ };
1756
+ if (this.taskStore) await this.taskStore.save(pending);
1757
+ if (interruptArtifact) {
1758
+ bus.publish({
1759
+ kind: "artifact-update",
1760
+ taskId: ctx.taskId,
1761
+ contextId: ctx.contextId,
1762
+ artifact: interruptArtifact
1763
+ });
1764
+ }
1765
+ bus.publish({
1766
+ kind: "status-update",
1767
+ taskId: ctx.taskId,
1768
+ contextId: ctx.contextId,
1769
+ status: {
1770
+ state: "input-required",
1771
+ ...summary ? { message: agentMessage(summary) } : {},
1772
+ timestamp: isoNow()
1773
+ },
1774
+ final: true
1775
+ });
1776
+ }
1777
+ async finishCanceled(bus, projection) {
1778
+ await this.persistCanceled(projection.ctx.taskId, projection.existing, projection);
1779
+ bus.publish({
1780
+ kind: "status-update",
1781
+ taskId: projection.ctx.taskId,
1782
+ contextId: projection.ctx.contextId,
1783
+ status: { state: "canceled", timestamp: isoNow() },
1784
+ final: true
1785
+ });
1786
+ }
1787
+ async failTask(ctx, bus, baseTask, err) {
1788
+ const failedTask = {
1789
+ ...baseTask,
1790
+ status: {
1791
+ state: "failed",
1792
+ message: agentMessage(err instanceof Error ? err.message : String(err)),
1793
+ timestamp: isoNow()
1794
+ }
1795
+ };
1796
+ if (this.taskStore) {
1797
+ try {
1798
+ await this.taskStore.save(failedTask);
1799
+ } catch (saveError) {
1800
+ console.warn({
1801
+ event: "a2a:settle:save_failed",
1802
+ taskId: ctx.taskId,
1803
+ error: saveError instanceof Error ? saveError.message : String(saveError)
1804
+ });
1805
+ }
1806
+ }
1807
+ bus.publish({
1808
+ kind: "status-update",
1809
+ taskId: ctx.taskId,
1810
+ contextId: ctx.contextId,
1811
+ status: failedTask.status,
1812
+ final: true
1813
+ });
1814
+ }
1815
+ taskFor(ctx, threadId, inputFiles, dependencies, projection) {
1816
+ const existing = ctx.task;
1817
+ const metadata = {
1818
+ ...existing?.metadata ?? {},
1819
+ threadId,
1820
+ dependencies,
1821
+ inputFiles,
1822
+ ...projection?.handle ? { a2aRunId: projection.handle.runId } : {},
1823
+ ...projection?.handle?.messageId !== void 0 ? { a2aMessageId: projection.handle.messageId } : {},
1824
+ ...projection?.resume ? { a2aResume: true } : {}
1825
+ };
1826
+ if (projection?.handle) {
1827
+ if (projection.handle.streamCursor !== void 0) {
1828
+ metadata.a2aStreamCursor = projection.handle.streamCursor;
1829
+ } else {
1830
+ delete metadata.a2aStreamCursor;
1831
+ }
1832
+ }
1833
+ return {
1834
+ kind: "task",
1835
+ id: ctx.taskId,
1836
+ contextId: ctx.contextId,
1837
+ ...existing?.history ? { history: existing.history } : {},
1838
+ ...existing?.artifacts ? { artifacts: existing.artifacts } : {},
1839
+ status: { state: "working", timestamp: isoNow() },
1840
+ metadata
1841
+ };
1842
+ }
1843
+ requestText(taskId, text, inputFiles, governed, resume) {
1844
+ const requestText = inputFiles.length > 0 ? `${text}
1845
+
1846
+ ${formatFileRefsSection(inputFiles)}` : text;
1847
+ if (!governed || resume) return requestText;
1848
+ return [
1849
+ `A2A governed task instruction: get and update the existing TaskItem ${taskId}; do not create a replacement task.`,
1850
+ "Before doing the requested work, initialize its canonical description through manage_task with ## Objective, ## Acceptance Criteria, and ## Belief State sections.",
1851
+ "Keep lifecycle status truthful. Change status only through manage_task set_status; complete with a nonblank result and structured beliefImpact, and use interrupted, failed, or cancelled when appropriate. Use manage_task update for content fields only.",
1852
+ "",
1853
+ requestText
1854
+ ].join("\n");
1855
+ }
1856
+ isResume(task) {
1857
+ return task?.status.state === "input-required";
1858
+ }
1859
+ resumeCommand(parts, text, reviewInterruption, soleInterruptId) {
1860
+ if (reviewInterruption) return this.reviewCommand(text);
1861
+ const data = dataParts(parts);
1862
+ if (data.length > 0) {
1863
+ const officialIds = data.map((part) => {
1864
+ const id = asString2(part.data.id);
1865
+ return id !== void 0 && isOfficialInterruptId(id) ? id : void 0;
1866
+ });
1867
+ if (data.length > 1 && officialIds.every((id) => id !== void 0)) {
1868
+ const resume = {};
1869
+ for (let index = 0; index < data.length; index += 1) {
1870
+ resume[officialIds[index]] = data[index].data.value;
1871
+ }
1872
+ return { resume };
1873
+ }
1874
+ const sole = data[0];
1875
+ if (sole && "value" in sole.data) {
1876
+ if (data.length === 1 && officialIds[0] !== void 0) {
1877
+ return { resume: { [officialIds[0]]: sole.data.value } };
1878
+ }
1879
+ return { resume: sole.data.value };
1880
+ }
1881
+ }
1882
+ if (text.trim().length > 0) {
1883
+ return {
1884
+ resume: soleInterruptId === void 0 ? text : { [soleInterruptId]: text }
1885
+ };
1886
+ }
1887
+ return void 0;
1888
+ }
1889
+ reviewCommand(text) {
1890
+ const trimmed = text.trim();
1891
+ const normalized = trimmed.toLowerCase();
1892
+ if (normalized === "approve") return { resume: { action: "approve" } };
1893
+ if (normalized === "reject") {
1894
+ return { resume: { action: "reject", data: { note: "Changes requested." } } };
1895
+ }
1896
+ const prefixedNote = /^reject:\s*(.+)$/is.exec(trimmed)?.[1]?.trim();
1897
+ if (prefixedNote) {
1898
+ return { resume: { action: "reject", data: { note: prefixedNote } } };
1899
+ }
1900
+ return void 0;
1901
+ }
1902
+ agentParams(threadId) {
1903
+ return {
1904
+ assistant_id: this.scope.assistantId,
1905
+ thread_id: threadId,
1906
+ tenant_id: this.scope.tenantId,
1907
+ project_id: this.scope.projectId,
1908
+ ...this.scope.workspaceId ? { workspace_id: this.scope.workspaceId } : {}
1909
+ };
1910
+ }
1911
+ hasGovernedTaskCapability() {
1912
+ return assistantHasGovernedTask(this.assistant);
1913
+ }
1914
+ /**
1915
+ * Cancel the exact durable run persisted on the A2A task, then persist the
1916
+ * terminal canceled state. No agent instance is aborted directly.
1917
+ */
1918
+ async cancelTask(taskId, bus) {
1919
+ const task = this.taskStore ? await this.taskStore.load(taskId) : void 0;
1920
+ const threadId = asString2(task?.metadata?.threadId);
1921
+ const runId = asString2(task?.metadata?.a2aRunId) ?? asString2(task?.metadata?.runId);
1922
+ if (threadId && runId) {
1923
+ try {
1924
+ await this.runService.cancel(
1925
+ this.scope.tenantId,
1926
+ threadId,
1927
+ this.scope.assistantId,
1928
+ runId
1929
+ );
1930
+ } catch (err) {
1931
+ if (!(err instanceof A2ARunNotFoundError)) {
1932
+ console.warn({
1933
+ event: "a2a:cancel:failed",
1934
+ taskId,
1935
+ threadId,
1936
+ runId,
1937
+ error: err instanceof Error ? err.message : String(err)
1938
+ });
1939
+ }
1940
+ }
1941
+ }
1942
+ await this.persistCanceled(taskId, task);
1943
+ bus.publish({
1944
+ kind: "status-update",
1945
+ taskId,
1946
+ contextId: task?.contextId ?? "",
1947
+ status: { state: "canceled", timestamp: isoNow() },
1948
+ final: true
1949
+ });
1950
+ }
1951
+ async persistCanceled(taskId, task, projection) {
1952
+ const canceledTask = {
1953
+ ...task ?? {
1954
+ kind: "task",
1955
+ id: taskId,
1956
+ contextId: "",
1957
+ status: { state: "working" }
1958
+ },
1959
+ ...projection ? {
1960
+ metadata: {
1961
+ ...task?.metadata ?? {},
1962
+ threadId: projection.threadId,
1963
+ dependencies: projection.dependencies,
1964
+ inputFiles: projection.inputFiles,
1965
+ ...projection.handle ? { a2aRunId: projection.handle.runId } : {}
1966
+ }
1967
+ } : {},
1968
+ status: { state: "canceled", timestamp: isoNow() }
1969
+ };
1970
+ if (this.taskStore) {
1971
+ try {
1972
+ await this.taskStore.save(canceledTask);
1973
+ } catch (saveError) {
1974
+ console.warn({
1975
+ event: "a2a:settle:save_failed",
1976
+ taskId,
1977
+ error: saveError instanceof Error ? saveError.message : String(saveError)
1978
+ });
1979
+ }
1980
+ }
1981
+ return canceledTask;
1982
+ }
1983
+ };
1984
+
1985
+ // src/services/a2a/A2AFileIngestor.ts
1986
+ var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
1987
+ var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
1988
+ ".exe",
1989
+ ".sh",
1990
+ ".bat",
1991
+ ".cmd",
1992
+ ".com",
1993
+ ".msi",
1994
+ ".js",
1995
+ ".mjs",
1996
+ ".cjs",
1997
+ ".ps1",
1998
+ ".vbs",
1999
+ ".dll",
2000
+ ".so",
2001
+ ".dylib"
2002
+ ]);
2003
+ var EXECUTABLE_MIME_TYPES = /* @__PURE__ */ new Set([
2004
+ "application/x-msdownload",
2005
+ "application/x-msdos-program",
2006
+ "application/x-executable",
2007
+ "application/x-sh",
2008
+ "application/x-bat",
2009
+ "application/x-shellscript",
2010
+ "text/x-shellscript",
2011
+ "application/javascript",
2012
+ "application/x-javascript",
2013
+ "text/javascript"
2014
+ ]);
2015
+ function decodeBase64(value) {
2016
+ return new Uint8Array(Buffer.from(value, "base64"));
2017
+ }
2018
+ function maxEncodedLengthFor(maxBytes) {
2019
+ return Math.ceil(maxBytes * 4 / 3) + 4;
2020
+ }
2021
+ function sanitizePathSegment(segment, fallback) {
2022
+ const cleaned = segment.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/\.{2,}/g, ".").replace(/^[.-]+|[.-]+$/g, "");
2023
+ return cleaned.length > 0 ? cleaned : fallback;
2024
+ }
2025
+ function storageLocationFor(dedupeKey, name) {
2026
+ const separatorIndex = dedupeKey.lastIndexOf(":");
2027
+ const keyBase = separatorIndex >= 0 ? dedupeKey.slice(0, separatorIndex) : dedupeKey;
2028
+ const keyIndex = separatorIndex >= 0 ? dedupeKey.slice(separatorIndex + 1) : "0";
2029
+ return {
2030
+ path: `a2a/${sanitizePathSegment(keyBase, "task")}`,
2031
+ name: `${sanitizePathSegment(keyIndex, "0")}-${sanitizePathSegment(name, "file")}`
2032
+ };
2033
+ }
2034
+ function extensionOf(name) {
2035
+ return name.includes(".") ? name.slice(name.lastIndexOf(".")).toLowerCase() : "";
2036
+ }
2037
+ function assertAllowedFileType(name, mimeType) {
2038
+ if (EXECUTABLE_EXTENSIONS.has(extensionOf(name))) {
2039
+ throw new Error(`Refusing to ingest executable file "${name}"`);
2040
+ }
2041
+ if (mimeType && EXECUTABLE_MIME_TYPES.has(mimeType.toLowerCase())) {
2042
+ throw new Error(`Refusing to ingest executable file "${name}"`);
2043
+ }
2044
+ }
2045
+ var A2AFileIngestor = class {
2046
+ constructor(options) {
2047
+ this.cache = /* @__PURE__ */ new Map();
2048
+ this.saveProjectFile = options.saveProjectFile;
2049
+ this.maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
2050
+ }
2051
+ /**
2052
+ * Project a single file part onto a {@link TaskFileRef}, ingesting bytes when
2053
+ * present. Idempotent per `dedupeKey`: a repeated key returns the cached URI
2054
+ * without re-writing storage, and even across ingestor instances the key
2055
+ * maps to a deterministic storage path (`a2a/<taskId>/<partIndex>-<name>`),
2056
+ * so request retries overwrite the same object.
2057
+ *
2058
+ * NOTE: the key is based on `taskId` + part index rather than the internal
2059
+ * message id because the executor must ingest files *before* `addMessage`
2060
+ * returns a messageId. A client retrying with a different taskId for the
2061
+ * same message writes a second copy; that is accepted and documented.
2062
+ */
2063
+ async ingest(part, dedupeKey, projectId, addedBy) {
2064
+ const file = part.file;
2065
+ if ("uri" in file) {
2066
+ return {
2067
+ uri: file.uri,
2068
+ ...file.name ? { name: file.name } : {},
2069
+ ...file.mimeType ? { mimeType: file.mimeType } : {},
2070
+ addedBy
2071
+ };
2072
+ }
2073
+ const name = file.name ?? "file";
2074
+ assertAllowedFileType(name, file.mimeType);
2075
+ if (file.bytes.length > maxEncodedLengthFor(this.maxBytes)) {
2076
+ throw new Error(
2077
+ `A2A file part exceeds the ${this.maxBytes} byte ingest limit`
2078
+ );
2079
+ }
2080
+ const bytes = decodeBase64(file.bytes);
2081
+ if (bytes.length > this.maxBytes) {
2082
+ throw new Error(
2083
+ `A2A file part exceeds the ${this.maxBytes} byte ingest limit`
2084
+ );
2085
+ }
2086
+ const cached = this.cache.get(dedupeKey);
2087
+ if (cached !== void 0) {
2088
+ return {
2089
+ uri: cached,
2090
+ ...file.name ? { name: file.name } : {},
2091
+ ...file.mimeType ? { mimeType: file.mimeType } : {},
2092
+ addedBy
2093
+ };
2094
+ }
2095
+ const location = storageLocationFor(dedupeKey, name);
2096
+ const { uri } = await this.saveProjectFile({
2097
+ projectId,
2098
+ name: location.name,
2099
+ path: location.path,
2100
+ bytes,
2101
+ ...file.mimeType ? { mimeType: file.mimeType } : {}
2102
+ });
2103
+ this.cache.set(dedupeKey, uri);
2104
+ return {
2105
+ uri,
2106
+ ...file.name ? { name: file.name } : {},
2107
+ ...file.mimeType ? { mimeType: file.mimeType } : {},
2108
+ addedBy
2109
+ };
2110
+ }
2111
+ };
2112
+
2113
+ // src/services/a2a/A2ARuntimeRegistry.ts
2114
+ import {
2115
+ DefaultExecutionEventBus
2116
+ } from "@a2a-js/sdk/server";
2117
+ var DEFAULT_MAX_SCOPES = 1e3;
2118
+ var DEFAULT_IDLE_TTL_MS = 15 * 60 * 1e3;
2119
+ var A2ARuntimeCapacityError = class extends Error {
2120
+ constructor(maxScopes) {
2121
+ super(`A2A runtime scope capacity of ${maxScopes} is exhausted`);
2122
+ this.name = "A2ARuntimeCapacityError";
2123
+ }
2124
+ };
2125
+ var ScopedExecutionEventBusManager = class {
2126
+ constructor(touch) {
2127
+ this.buses = /* @__PURE__ */ new Map();
2128
+ this.touch = touch;
2129
+ }
2130
+ createOrGetByTaskId(taskId) {
2131
+ this.touch();
2132
+ const existing = this.buses.get(taskId);
2133
+ if (existing) return existing;
2134
+ const bus = new DefaultExecutionEventBus();
2135
+ this.buses.set(taskId, bus);
2136
+ return bus;
2137
+ }
2138
+ getByTaskId(taskId) {
2139
+ this.touch();
2140
+ return this.buses.get(taskId);
2141
+ }
2142
+ cleanupByTaskId(taskId) {
2143
+ this.touch();
2144
+ const bus = this.buses.get(taskId);
2145
+ bus?.removeAllListeners();
2146
+ this.buses.delete(taskId);
2147
+ }
2148
+ get activeBusCount() {
2149
+ return this.buses.size;
2150
+ }
2151
+ close() {
2152
+ for (const bus of this.buses.values()) {
2153
+ bus.removeAllListeners();
2154
+ }
2155
+ this.buses.clear();
2156
+ }
2157
+ };
2158
+ var A2ARuntimeRegistry = class {
2159
+ constructor(options = {}) {
2160
+ this.entries = /* @__PURE__ */ new Map();
2161
+ this.closed = false;
2162
+ this.maxScopes = options.maxScopes ?? DEFAULT_MAX_SCOPES;
2163
+ this.idleTtlMs = options.idleTtlMs ?? DEFAULT_IDLE_TTL_MS;
2164
+ this.now = options.now ?? Date.now;
2165
+ }
2166
+ /**
2167
+ * Acquire and pin the runtime manager for an authorization scope.
2168
+ *
2169
+ * @param scope Exact tenant, project, and assistant authorization scope.
2170
+ * @returns A lease whose manager is shared only within that scope.
2171
+ */
2172
+ acquire(scope) {
2173
+ if (this.closed) throw new Error("A2A runtime registry is closed");
2174
+ const now = this.now();
2175
+ this.pruneExpired(now);
2176
+ const key = JSON.stringify([
2177
+ scope.tenantId,
2178
+ scope.projectId,
2179
+ scope.assistantId
2180
+ ]);
2181
+ const existing = this.entries.get(key);
2182
+ const entry = existing ?? this.createEntry(key, now);
2183
+ entry.lastAccessedAt = now;
2184
+ entry.leaseCount += 1;
2185
+ let released = false;
2186
+ return {
2187
+ manager: entry.manager,
2188
+ release: () => {
2189
+ if (released) return;
2190
+ released = true;
2191
+ entry.leaseCount -= 1;
2192
+ entry.lastAccessedAt = this.now();
2193
+ }
2194
+ };
2195
+ }
2196
+ close() {
2197
+ if (this.closed) return;
2198
+ this.closed = true;
2199
+ for (const entry of this.entries.values()) {
2200
+ entry.manager.close();
2201
+ }
2202
+ this.entries.clear();
2203
+ }
2204
+ pruneExpired(now) {
2205
+ for (const [key, entry] of this.entries) {
2206
+ if (!this.isActive(entry) && now - entry.lastAccessedAt > this.idleTtlMs) {
2207
+ entry.manager.close();
2208
+ this.entries.delete(key);
2209
+ }
2210
+ }
2211
+ }
2212
+ makeCapacity() {
2213
+ if (this.entries.size < this.maxScopes) return;
2214
+ let oldest;
2215
+ for (const candidate of this.entries) {
2216
+ if (this.isActive(candidate[1])) continue;
2217
+ if (!oldest || candidate[1].lastAccessedAt < oldest[1].lastAccessedAt) {
2218
+ oldest = candidate;
2219
+ }
2220
+ }
2221
+ if (!oldest) throw new A2ARuntimeCapacityError(this.maxScopes);
2222
+ oldest[1].manager.close();
2223
+ this.entries.delete(oldest[0]);
2224
+ }
2225
+ createEntry(key, now) {
2226
+ this.makeCapacity();
2227
+ const entry = {
2228
+ lastAccessedAt: now,
2229
+ leaseCount: 0,
2230
+ manager: new ScopedExecutionEventBusManager(() => {
2231
+ entry.lastAccessedAt = this.now();
2232
+ })
2233
+ };
2234
+ this.entries.set(key, entry);
2235
+ return entry;
2236
+ }
2237
+ isActive(entry) {
2238
+ return entry.leaseCount > 0 || entry.manager.activeBusCount > 0;
2239
+ }
2240
+ };
2241
+
2242
+ // src/routes/a2a-standard.ts
2243
+ var TERMINAL_A2A_STATES2 = /* @__PURE__ */ new Set([
2244
+ "completed",
2245
+ "failed",
2246
+ "canceled",
2247
+ "rejected"
2248
+ ]);
2249
+ function agentParamsFor(scope, threadId, assistantId) {
2250
+ return {
2251
+ assistant_id: assistantId,
2252
+ thread_id: threadId,
2253
+ tenant_id: scope.tenantId,
2254
+ project_id: scope.projectId,
2255
+ ...scope.workspaceId ? { workspace_id: scope.workspaceId } : {}
2256
+ };
2257
+ }
2258
+ function chunksFromThreadStream(buffer, key, cursor) {
2259
+ return (async function* () {
2260
+ for await (const record of buffer.streamThread(
2261
+ key,
2262
+ cursor === void 0 ? {} : { cursor }
2263
+ )) {
2264
+ if (record.kind === "end") return;
2265
+ if (record.kind !== "chunk") continue;
2266
+ yield record.chunk;
2267
+ if (record.chunk.type === MessageChunkTypes3.MESSAGE_COMPLETED || record.chunk.type === MessageChunkTypes3.MESSAGE_FAILED || record.chunk.type === MessageChunkTypes3.INTERRUPT) {
2268
+ return;
2269
+ }
2270
+ }
2271
+ })();
2272
+ }
2273
+ function runServiceChunkSource(scope) {
2274
+ return {
2275
+ stream: (threadId, assistantId, messageId) => {
2276
+ const buffer = getChunkBuffer("default");
2277
+ if (buffer) {
2278
+ return stopAtTerminalChunks(
2279
+ chunksFromThreadStream(
2280
+ buffer,
2281
+ chunkBufferStreamKey(scope.tenantId, assistantId, threadId)
2282
+ )
2283
+ );
2284
+ }
2285
+ return stopAtTerminalChunks(
2286
+ agentInstanceManager2.getAgent(agentParamsFor(scope, threadId, assistantId)).chunkStream(
2287
+ messageId,
2288
+ [MessageChunkTypes3.MESSAGE_COMPLETED, MessageChunkTypes3.MESSAGE_FAILED]
2289
+ )
2290
+ );
2291
+ },
2292
+ streamThread: (threadId, assistantId, cursor) => {
2293
+ const buffer = getChunkBuffer("default");
2294
+ if (!buffer) return (async function* () {
2295
+ })();
2296
+ return chunksFromThreadStream(
2297
+ buffer,
2298
+ chunkBufferStreamKey(scope.tenantId, assistantId, threadId),
2299
+ cursor
2300
+ );
2301
+ }
2302
+ };
2303
+ }
2304
+ function messageText(content) {
2305
+ if (typeof content === "string") return content;
2306
+ if (!Array.isArray(content)) return "";
2307
+ return content.map((part) => {
2308
+ if (typeof part === "string") return part;
2309
+ if (typeof part !== "object" || part === null) return "";
2310
+ const record = part;
2311
+ if (typeof record.text === "string") return record.text;
2312
+ if (record.type === "text" && typeof record.value === "string") {
2313
+ return record.value;
2314
+ }
2315
+ return "";
2316
+ }).join("");
2317
+ }
2318
+ function normalizeCheckpointMessages(messages) {
2319
+ if (!Array.isArray(messages)) return [];
2320
+ const normalized = [];
2321
+ messages.forEach((message, index) => {
2322
+ if (typeof message !== "object" || message === null) return;
2323
+ const record = message;
2324
+ const type = typeof record.getType === "function" ? record.getType() : asString3(record.type) ?? asString3(record.role);
2325
+ const role = type === "human" || type === "user" ? "user" : type === "ai" || type === "assistant" ? "agent" : void 0;
2326
+ if (!role) return;
2327
+ const text = messageText(record.content);
2328
+ if (!text) return;
2329
+ normalized.push({
2330
+ kind: "message",
2331
+ messageId: asString3(record.id) ?? `a2a-checkpoint:${index}`,
2332
+ role,
2333
+ parts: [{ kind: "text", text }]
2334
+ });
2335
+ });
2336
+ return normalized;
2337
+ }
2338
+ function defaultStateResolver() {
2339
+ return {
2340
+ getCurrentMessages: async (threadId, assistantId, scope) => {
2341
+ const agent = agentInstanceManager2.getAgent(
2342
+ agentParamsFor(scope, threadId, assistantId)
2343
+ );
2344
+ const state = await agent.getCurrentState();
2345
+ return normalizeCheckpointMessages(state?.values?.messages);
2346
+ },
2347
+ getInterrupts: async (threadId, assistantId, scope) => await agentInstanceManager2.getAgent(agentParamsFor(scope, threadId, assistantId)).getInterrupts() ?? []
2348
+ };
2349
+ }
2350
+ function blockingFor(body) {
2351
+ const params = typeof body === "object" && body !== null ? body.params : void 0;
2352
+ const configuration = typeof params === "object" && params !== null ? params.configuration : void 0;
2353
+ const blocking = typeof configuration === "object" && configuration !== null ? configuration.blocking : void 0;
2354
+ return blocking !== false;
2355
+ }
2356
+ async function resumeValidationError(taskStore, body) {
2357
+ const method = typeof body === "object" && body !== null ? body.method : void 0;
2358
+ if (method !== "message/send" && method !== "message/stream") return void 0;
2359
+ const params = body.params;
2360
+ const message = typeof params === "object" && params !== null ? params.message : void 0;
2361
+ const taskId = typeof message === "object" && message !== null ? message.taskId : void 0;
2362
+ if (typeof taskId !== "string" || taskId.length === 0) return void 0;
2363
+ const task = await taskStore.load(taskId);
2364
+ if (!task) return void 0;
2365
+ if (task.status.state === "input-required") return void 0;
2366
+ if (TERMINAL_A2A_STATES2.has(task.status.state)) return void 0;
2367
+ return `Task ${taskId} is not waiting for input and cannot be resumed`;
2368
+ }
2369
+ var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set([
2370
+ "success",
2371
+ "error",
2372
+ "interrupted",
2373
+ "cancelled",
2374
+ "timeout"
2375
+ ]);
2376
+ var RESUBSCRIBE_SETTLE_TIMEOUT_MS = 1e4;
2377
+ function asString3(value) {
2378
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2379
+ }
2380
+ function requestMethod(body) {
2381
+ return typeof body === "object" && body !== null ? asString3(body.method) : void 0;
2382
+ }
2383
+ function requestTaskId(body) {
2384
+ if (typeof body !== "object" || body === null) return void 0;
2385
+ const record = body;
2386
+ const params = typeof record.params === "object" && record.params !== null ? record.params : void 0;
2387
+ if (!params) return void 0;
2388
+ if (record.method === "tasks/get" || record.method === "tasks/cancel" || record.method === "tasks/resubscribe") {
2389
+ return asString3(params.id);
2390
+ }
2391
+ if (record.method === "message/send" || record.method === "message/stream") {
2392
+ const message = typeof params.message === "object" && params.message !== null ? params.message : void 0;
2393
+ return asString3(message?.taskId);
2394
+ }
2395
+ return void 0;
2396
+ }
2397
+ function isTerminalA2AState(state) {
2398
+ return TERMINAL_A2A_STATES2.has(state);
2399
+ }
2400
+ function agentMessage2(text) {
2401
+ return {
2402
+ kind: "message",
2403
+ messageId: `a2a-run-projection:${Date.now()}`,
2404
+ role: "agent",
2405
+ parts: [{ kind: "text", text }]
2406
+ };
2407
+ }
2408
+ function projectRunState(run) {
2409
+ if (run.cancelRequested === true || run.status === "cancelled") {
2410
+ return { state: "canceled" };
2411
+ }
2412
+ switch (run.status) {
2413
+ case "success":
2414
+ return { state: "completed" };
2415
+ case "interrupted":
2416
+ return { state: "input-required" };
2417
+ case "error":
2418
+ case "timeout":
2419
+ return {
2420
+ state: "failed",
2421
+ ...asString3(run.error) ? { message: agentMessage2(asString3(run.error)) } : {}
2422
+ };
2423
+ default:
2424
+ return void 0;
2425
+ }
2426
+ }
2427
+ function resultTextFromTask(task) {
2428
+ const artifact = task.artifacts?.find((item) => item.name === "result");
2429
+ if (!artifact) return "";
2430
+ return artifact.parts.filter((part) => part.kind === "text").map((part) => part.text).join("");
2431
+ }
2432
+ function resultArtifactFor(task, result) {
2433
+ return {
2434
+ artifactId: `${task.id}:result`,
2435
+ name: "result",
2436
+ parts: [{ kind: "text", text: result.trim() || "(no output)" }]
2437
+ };
2438
+ }
2439
+ function withResult(task, result, runId, threadId) {
2440
+ const artifact = resultArtifactFor(task, result);
2441
+ return {
2442
+ ...task,
2443
+ metadata: { ...task.metadata ?? {}, a2aRunId: runId, threadId },
2444
+ artifacts: [
2445
+ ...(task.artifacts ?? []).filter(
2446
+ (item) => item.artifactId !== artifact.artifactId && item.name !== "result"
2447
+ ),
2448
+ artifact
2449
+ ],
2450
+ status: { state: "completed", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
2451
+ };
2452
+ }
2453
+ function deriveInterruption(task, interrupts) {
2454
+ const existing = task.metadata?.interruption;
2455
+ if (existing && typeof existing === "object") {
2456
+ return existing;
2457
+ }
2458
+ if (interrupts.length === 0) return void 0;
2459
+ const review = interrupts.some((interrupt) => interrupt.type === "task_review");
2460
+ const clarify = interrupts.some((interrupt) => interrupt.type === "clarify");
2461
+ const summary = task.status.message ? task.status.message.parts.filter((part) => part.kind === "text").map((part) => part.text).join("").trim() : "";
2462
+ return {
2463
+ type: review ? "review_required" : clarify ? "missing_input" : "user_decision",
2464
+ summary: summary || "A2A requires input"
2465
+ };
2466
+ }
2467
+ function interruptArtifactFor(task, interrupts) {
2468
+ if (interrupts.length === 0) return void 0;
2469
+ return {
2470
+ artifactId: `${task.id}:interrupt`,
2471
+ name: "interrupt",
2472
+ parts: interrupts.map((interrupt) => {
2473
+ const payload = interrupt.payload;
2474
+ const record = typeof payload === "object" && payload !== null && !Array.isArray(payload) ? payload : void 0;
2475
+ const schema = record?.response_schema;
2476
+ return {
2477
+ kind: "data",
2478
+ data: {
2479
+ id: interrupt.interruptId,
2480
+ value: payload,
2481
+ ...schema === void 0 ? {} : { response_schema: schema }
2482
+ }
2483
+ };
2484
+ })
2485
+ };
2486
+ }
2487
+ async function reconcileTaskFromRun(taskStore, runService, stateResolver, scope, body) {
2488
+ const taskId = requestTaskId(body);
2489
+ if (!taskId) return void 0;
2490
+ let task;
2491
+ try {
2492
+ task = await taskStore.load(taskId);
2493
+ } catch {
2494
+ return void 0;
2495
+ }
2496
+ if (!task) return void 0;
2497
+ if (isTerminalA2AState(task.status.state)) {
2498
+ if (task.status.state !== "completed") return task;
2499
+ }
2500
+ const runId = asString3(task.metadata?.a2aRunId);
2501
+ if (!runId) return task;
2502
+ const threadId = asString3(task.metadata?.threadId) ?? task.contextId;
2503
+ const run = await runService.getRun(
2504
+ scope.tenantId,
2505
+ runId,
2506
+ threadId,
2507
+ scope.assistantId
2508
+ ).catch(() => void 0);
2509
+ if (!run || !TERMINAL_RUN_STATUSES.has(run.status)) return task;
2510
+ const projection = projectRunState(run);
2511
+ if (!projection) return task;
2512
+ if (projection.state === "completed") {
2513
+ const existingResult = resultTextFromTask(task).trim();
2514
+ const missingResult = existingResult === "" || existingResult.toLowerCase() === "(no output)";
2515
+ let result = existingResult;
2516
+ if (missingResult) {
2517
+ try {
2518
+ const messages = await stateResolver.getCurrentMessages(
2519
+ threadId,
2520
+ scope.assistantId,
2521
+ scope
2522
+ );
2523
+ const finalAgent = [...messages].reverse().find((message) => message.role === "agent");
2524
+ const text = finalAgent ? finalAgent.parts.filter((part) => part.kind === "text").map((part) => part.text).join("").trim() : "";
2525
+ if (text) result = text;
2526
+ } catch {
2527
+ }
2528
+ }
2529
+ if (task.status.state === "completed" && !missingResult) return task;
2530
+ const completedTask = withResult(task, result || "(no output)", runId, threadId);
2531
+ if (taskStore.completeRun) {
2532
+ return taskStore.completeRun(completedTask, result || "(no output)");
2533
+ }
2534
+ await taskStore.save(completedTask);
2535
+ return await taskStore.load(taskId) ?? completedTask;
2536
+ }
2537
+ if (projection.state === "input-required") {
2538
+ let interrupts = [];
2539
+ try {
2540
+ const resolved = await stateResolver.getInterrupts(
2541
+ threadId,
2542
+ scope.assistantId,
2543
+ scope
2544
+ );
2545
+ interrupts = Array.isArray(resolved) ? resolved : [];
2546
+ } catch {
2547
+ }
2548
+ const interruption = deriveInterruption(task, interrupts);
2549
+ const artifact = interruptArtifactFor(task, interrupts);
2550
+ const next2 = {
2551
+ ...task,
2552
+ metadata: {
2553
+ ...task.metadata ?? {},
2554
+ a2aRunId: runId,
2555
+ threadId,
2556
+ ...interruption ? { interruption } : {}
2557
+ },
2558
+ ...artifact ? {
2559
+ artifacts: [
2560
+ ...(task.artifacts ?? []).filter(
2561
+ (item) => item.artifactId !== artifact.artifactId
2562
+ ),
2563
+ artifact
2564
+ ]
2565
+ } : {},
2566
+ status: {
2567
+ state: "input-required",
2568
+ ...task.status.message ? { message: task.status.message } : {},
2569
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2570
+ }
2571
+ };
2572
+ await taskStore.save(next2);
2573
+ return await taskStore.load(taskId) ?? next2;
2574
+ }
2575
+ if (task.status.state === projection.state) return task;
2576
+ const next = {
2577
+ ...task,
2578
+ metadata: { ...task.metadata ?? {}, a2aRunId: runId, threadId },
2579
+ status: {
2580
+ state: projection.state,
2581
+ ...projection.message ? { message: projection.message } : {},
2582
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2583
+ }
2584
+ };
2585
+ await taskStore.save(next);
2586
+ return await taskStore.load(taskId) ?? next;
2587
+ }
2588
+ async function cancelTaskFromRun(taskStore, runService, scope, body) {
2589
+ if (requestMethod(body) !== "tasks/cancel") return void 0;
2590
+ const taskId = requestTaskId(body);
2591
+ if (!taskId) return void 0;
2592
+ const task = await taskStore.load(taskId);
2593
+ if (!task || isTerminalA2AState(task.status.state)) return void 0;
2594
+ const runId = asString3(task.metadata?.a2aRunId);
2595
+ if (!runId) return void 0;
2596
+ const threadId = asString3(task.metadata?.threadId) ?? task.contextId;
2597
+ const before = await runService.getRun(
2598
+ scope.tenantId,
2599
+ runId,
2600
+ threadId,
2601
+ scope.assistantId
2602
+ );
2603
+ if (!before) throw A2AError3.taskNotCancelable(taskId);
2604
+ if (TERMINAL_RUN_STATUSES.has(before.status)) {
2605
+ if (before.status !== "interrupted" && before.cancelRequested !== true) {
2606
+ throw A2AError3.taskNotCancelable(taskId);
2607
+ }
2608
+ } else {
2609
+ await runService.cancel(scope.tenantId, threadId, scope.assistantId, runId);
2610
+ const after = await runService.getRun(
2611
+ scope.tenantId,
2612
+ runId,
2613
+ threadId,
2614
+ scope.assistantId
2615
+ );
2616
+ if (!after) throw A2AError3.taskNotCancelable(taskId);
2617
+ if (!TERMINAL_RUN_STATUSES.has(after.status)) {
2618
+ throw new Error(`Run ${runId} did not reach a terminal state after cancellation`);
2619
+ }
2620
+ if (after.status !== "interrupted" && after.status !== "cancelled" && after.cancelRequested !== true) {
2621
+ throw A2AError3.taskNotCancelable(taskId);
2622
+ }
2623
+ }
2624
+ const canceled = {
2625
+ ...task,
2626
+ metadata: { ...task.metadata ?? {}, a2aRunId: runId, threadId },
2627
+ status: { state: "canceled", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
2628
+ };
2629
+ await taskStore.save(canceled);
2630
+ return await taskStore.load(taskId) ?? canceled;
2631
+ }
2632
+ function getHeader(value) {
2633
+ if (Array.isArray(value)) return value[0];
2634
+ return value;
2635
+ }
2636
+ function getSessionUser(request) {
2637
+ return request.user;
2638
+ }
2639
+ function buildBaseUrl(request) {
2640
+ const protocol = getHeader(request.headers["x-forwarded-proto"]) ?? request.protocol;
2641
+ const forwardedHost = getHeader(request.headers["x-forwarded-host"]);
2642
+ const host = forwardedHost ? forwardedHost.split(",")[0].trim() : request.host;
2643
+ return `${protocol}://${host}`;
2644
+ }
2645
+ function makeFileIngestor(tenantId, maxFileBytes) {
2646
+ return new A2AFileIngestor({
2647
+ saveProjectFile: (params) => saveProjectFile({ tenantId, ...params }),
2648
+ ...maxFileBytes !== void 0 ? { maxBytes: maxFileBytes } : {}
2649
+ });
2650
+ }
2651
+ async function resolveWorkspaceId(getProjectStore, tenantId, projectId) {
2652
+ if (!projectId) return void 0;
2653
+ try {
2654
+ const project = await getProjectStore().getProjectById(tenantId, projectId);
2655
+ return project?.workspaceId;
2656
+ } catch {
2657
+ return void 0;
2658
+ }
2659
+ }
2660
+ function extractRequestId(body) {
2661
+ if (typeof body === "object" && body !== null && "id" in body) {
2662
+ const id = body.id;
2663
+ if (typeof id === "string" || typeof id === "number" || id === null) {
2664
+ return id;
2665
+ }
2666
+ }
2667
+ return null;
2668
+ }
2669
+ function authErrorToRpcCode(statusCode) {
2670
+ switch (statusCode) {
2671
+ case 401:
2672
+ return -32001;
2673
+ case 403:
2674
+ return -32002;
2675
+ case 404:
2676
+ return -32003;
2677
+ default:
2678
+ return -32603;
2679
+ }
2680
+ }
2681
+ function jsonRpcError(id, code, message) {
2682
+ return { jsonrpc: "2.0", id, error: { code, message } };
2683
+ }
2684
+ var DEFAULT_A2A_TASKS_PAGE_SIZE = 50;
2685
+ var MAX_A2A_TASKS_PAGE_SIZE = 200;
2686
+ var TASKS_LIST_STATUSES = {
2687
+ submitted: ["pending"],
2688
+ working: ["in_progress"],
2689
+ "input-required": ["interrupted", "review"],
2690
+ completed: ["completed"],
2691
+ failed: ["failed"],
2692
+ canceled: ["cancelled"],
2693
+ rejected: ["failed"],
2694
+ pending: ["pending"],
2695
+ in_progress: ["in_progress"],
2696
+ review: ["review"],
2697
+ interrupted: ["interrupted"],
2698
+ cancelled: ["cancelled"]
2699
+ };
2700
+ function asRecord3(value) {
2701
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
2702
+ }
2703
+ function encodePageToken(offset) {
2704
+ return Buffer.from(String(offset), "utf8").toString("base64url");
2705
+ }
2706
+ function decodePageToken(token) {
2707
+ if (!/^[A-Za-z0-9_-]+$/.test(token)) return void 0;
2708
+ const text = Buffer.from(token, "base64url").toString("utf8");
2709
+ if (!/^\d+$/.test(text)) return void 0;
2710
+ const offset = Number(text);
2711
+ return Number.isSafeInteger(offset) && offset >= 0 ? offset : void 0;
2712
+ }
2713
+ function parseTasksListQuery(body) {
2714
+ const record = asRecord3(body);
2715
+ const rawParams = record?.params;
2716
+ if (rawParams !== void 0 && rawParams !== null && asRecord3(rawParams) === void 0) {
2717
+ return { ok: false, message: "params must be an object" };
2718
+ }
2719
+ const params = asRecord3(rawParams) ?? {};
2720
+ let pageSize = DEFAULT_A2A_TASKS_PAGE_SIZE;
2721
+ if (params.pageSize !== void 0) {
2722
+ const value = params.pageSize;
2723
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > MAX_A2A_TASKS_PAGE_SIZE) {
2724
+ return {
2725
+ ok: false,
2726
+ message: `pageSize must be an integer between 1 and ${MAX_A2A_TASKS_PAGE_SIZE}`
2727
+ };
2728
+ }
2729
+ pageSize = value;
2730
+ }
2731
+ let offset = 0;
2732
+ if (params.pageToken !== void 0) {
2733
+ const token = params.pageToken;
2734
+ const decoded = typeof token === "string" ? decodePageToken(token) : void 0;
2735
+ if (decoded === void 0) {
2736
+ return { ok: false, message: "pageToken is invalid" };
2737
+ }
2738
+ offset = decoded;
2739
+ }
2740
+ let statuses;
2741
+ if (params.status !== void 0) {
2742
+ const value = params.status;
2743
+ if (typeof value !== "string" || !Object.prototype.hasOwnProperty.call(TASKS_LIST_STATUSES, value)) {
2744
+ return { ok: false, message: "status is invalid" };
2745
+ }
2746
+ statuses = TASKS_LIST_STATUSES[value];
2747
+ }
2748
+ return {
2749
+ ok: true,
2750
+ query: { pageSize, offset, ...statuses ? { statuses } : {} }
2751
+ };
2752
+ }
2753
+ function isA2ATaskSnapshot2(value, id) {
2754
+ return typeof value === "object" && value !== null && value.id === id && typeof value.status === "object" && value.status !== null;
2755
+ }
2756
+ function isCanonicalA2ATaskItem(item, scope) {
2757
+ return item.tenantId === scope.tenantId && item.ownerType === "agent" && item.ownerId === scope.assistantId && item.projectId === scope.projectId && item.sourceId === "a2a";
2758
+ }
2759
+ async function listCanonicalA2ATasks(internalTasks, taskStore, scope, query) {
2760
+ const statuses = query.statuses;
2761
+ const storeStatus = statuses?.length === 1 ? statuses[0] : void 0;
2762
+ const fetchSize = query.pageSize + 1;
2763
+ const tasks = [];
2764
+ const seen = /* @__PURE__ */ new Set();
2765
+ let cursor = query.offset;
2766
+ for (; ; ) {
2767
+ const rows = await internalTasks.list({
2768
+ tenantId: scope.tenantId,
2769
+ ownerType: "agent",
2770
+ ownerId: scope.assistantId,
2771
+ projectId: scope.projectId,
2772
+ sourceId: "a2a",
2773
+ ...storeStatus ? { status: storeStatus } : {},
2774
+ limit: fetchSize,
2775
+ offset: cursor
2776
+ });
2777
+ if (rows.length === 0) break;
2778
+ let nextPageToken;
2779
+ for (let index = 0; index < rows.length; index += 1) {
2780
+ const item = rows[index];
2781
+ if (!isCanonicalA2ATaskItem(item, scope)) continue;
2782
+ if (statuses && !statuses.includes(item.status)) continue;
2783
+ const restored = await taskStore.load(item.id).catch(() => void 0);
2784
+ if (!restored || !isA2ATaskSnapshot2(restored, item.id)) continue;
2785
+ if (seen.has(restored.id)) continue;
2786
+ if (tasks.length < query.pageSize) {
2787
+ seen.add(restored.id);
2788
+ tasks.push(restored);
2789
+ continue;
2790
+ }
2791
+ nextPageToken = encodePageToken(cursor + index);
2792
+ break;
2793
+ }
2794
+ if (nextPageToken !== void 0) {
2795
+ return { tasks, nextPageToken };
2796
+ }
2797
+ cursor += rows.length;
2798
+ if (rows.length < fetchSize) break;
2799
+ }
2800
+ return { tasks };
2801
+ }
2802
+ function isAsyncGenerator(value) {
2803
+ return typeof value === "object" && value !== null && typeof value[Symbol.asyncIterator] === "function";
2804
+ }
2805
+ var SSE_HEADERS = {
2806
+ "Content-Type": "text/event-stream",
2807
+ "Cache-Control": "no-cache",
2808
+ Connection: "keep-alive",
2809
+ "Access-Control-Allow-Origin": "*",
2810
+ "A2A-Version": "0.3"
2811
+ };
2812
+ function registerA2AStandardRoutes(app, deps) {
2813
+ const authService = deps.authService;
2814
+ const resolveTasks = deps.getTaskStoreLattice ?? (() => getStoreLattice("default", "task").store);
2815
+ const resolveTaskWorkItems = deps.getTaskWorkItemStore ?? (() => getStoreLattice("default", "taskWorkItem").store);
2816
+ const getAssistantStore = deps.getAssistantStore ?? (() => getStoreLattice("default", "assistant").store);
2817
+ const getProjectStore = deps.getProjectStore ?? (() => getStoreLattice("default", "project").store);
2818
+ const resolveRunLedger = deps.getAgentRunLedger ?? (() => getStoreLattice("default", "agentRun").store);
2819
+ const resolveStore = deps.getStore ?? (() => getStoreLattice("default", "store").store);
2820
+ const hasPushStore = () => {
2821
+ try {
2822
+ return Boolean(resolveStore());
2823
+ } catch {
2824
+ return false;
2825
+ }
2826
+ };
2827
+ const createPushStore = (scope) => {
2828
+ const store = resolveStore();
2829
+ if (!store) return void 0;
2830
+ return new A2APushNotificationStore({
2831
+ store,
2832
+ scope: {
2833
+ tenantId: scope.tenantId,
2834
+ assistantId: scope.assistantId
2835
+ },
2836
+ ...deps.resolvePushHost ? { resolveHost: deps.resolvePushHost } : {}
2837
+ });
2838
+ };
2839
+ const unavailableRunLedger = () => ({
2840
+ createPendingRun: async () => {
2841
+ throw new Error("A2A run ledger is not configured");
2842
+ },
2843
+ getRun: async () => void 0,
2844
+ requestCancel: async () => "not_found"
2845
+ });
2846
+ const runServiceFor = (scope) => {
2847
+ if (deps.createRunService) return deps.createRunService(scope);
2848
+ let ledger;
2849
+ try {
2850
+ ledger = resolveRunLedger();
2851
+ } catch {
2852
+ ledger = unavailableRunLedger();
2853
+ }
2854
+ const maxActiveRunsForTenant = resolveRunTaskMaxActivePerTenant();
2855
+ return new A2ARunService({
2856
+ ledger,
2857
+ chunkSource: runServiceChunkSource(scope),
2858
+ ...maxActiveRunsForTenant === void 0 ? {} : { maxActiveRunsForTenant },
2859
+ getLatestCursor: async (threadId, assistantId) => {
2860
+ const buffer = getChunkBuffer("default");
2861
+ if (!buffer) return void 0;
2862
+ const key = chunkBufferStreamKey(scope.tenantId, assistantId, threadId);
2863
+ const status = await buffer.getThreadStatus(key);
2864
+ if (status !== "active") return void 0;
2865
+ return buffer.latestCursor(key);
2866
+ }
2867
+ });
2868
+ };
2869
+ const stateResolver = deps.stateResolver ?? defaultStateResolver();
2870
+ const runtimeRegistry = deps.runtimeRegistry ?? new A2ARuntimeRegistry();
2871
+ const taskStoreFor = (scope, internalTasks, workItems) => new A2ATaskStoreAdapter({
2872
+ taskStore: internalTasks,
2873
+ workItemStore: workItems,
2874
+ lifecycle: createTaskLifecycleService({
2875
+ taskStore: internalTasks,
2876
+ workItemStore: workItems
2877
+ }),
2878
+ scope
2879
+ });
2880
+ app.addHook("onClose", async () => {
2881
+ runtimeRegistry.close();
2882
+ });
2883
+ const handleTaskResubscribe = async (request, reply, requestId, scope, taskStore) => {
2884
+ reply.header("A2A-Version", "0.3");
2885
+ const body = asRecord3(request.body);
2886
+ const params = asRecord3(body?.params);
2887
+ const taskId = asString3(params?.id);
2888
+ if (!taskId) {
2889
+ reply.send(jsonRpcError(requestId, -32602, "Invalid params: id is required"));
2890
+ return;
2891
+ }
2892
+ let task;
2893
+ try {
2894
+ task = await taskStore.load(taskId);
2895
+ } catch (err) {
2896
+ request.log.error({ err }, "a2a:jsonrpc:resubscribe");
2897
+ reply.status(500).send(jsonRpcError(requestId, -32603, "Internal error"));
2898
+ return;
2899
+ }
2900
+ if (!task) {
2901
+ reply.send({
2902
+ jsonrpc: "2.0",
2903
+ id: requestId,
2904
+ error: A2AError3.taskNotFound(taskId).toJSONRPCError()
2905
+ });
2906
+ return;
2907
+ }
2908
+ let runtimeLease;
2909
+ try {
2910
+ runtimeLease = runtimeRegistry.acquire(scope);
2911
+ } catch (err) {
2912
+ if (err instanceof A2ARuntimeCapacityError) {
2913
+ request.log.error({ err, scope }, "a2a:jsonrpc:runtime_capacity");
2914
+ reply.status(503).send(jsonRpcError(requestId, -32603, "A2A runtime capacity exhausted"));
2915
+ return;
2916
+ }
2917
+ throw err;
2918
+ }
2919
+ reply.hijack();
2920
+ reply.raw.writeHead(200, SSE_HEADERS);
2921
+ const writeFrame = async (frame, event) => {
2922
+ if (reply.raw.writableEnded || reply.raw.destroyed) return;
2923
+ const data = `${event ? `event: ${event}
2924
+ ` : ""}data: ${JSON.stringify(frame)}
2925
+
2926
+ `;
2927
+ if (reply.raw.write(data)) return;
2928
+ await new Promise((resolve) => {
2929
+ const done = () => {
2930
+ reply.raw.off("drain", done);
2931
+ reply.raw.off("close", done);
2932
+ resolve();
2933
+ };
2934
+ reply.raw.once("drain", done);
2935
+ reply.raw.once("close", done);
2936
+ });
2937
+ };
2938
+ const resultFrame = (result) => ({
2939
+ jsonrpc: "2.0",
2940
+ id: requestId,
2941
+ result
2942
+ });
2943
+ let disconnected = false;
2944
+ let iterator;
2945
+ const closeIterator = async () => {
2946
+ if (!iterator) return;
2947
+ const current = iterator;
2948
+ iterator = void 0;
2949
+ try {
2950
+ await current.return?.();
2951
+ } catch {
2952
+ }
2953
+ };
2954
+ const onClose = () => {
2955
+ disconnected = true;
2956
+ void closeIterator();
2957
+ };
2958
+ reply.raw.once("close", onClose);
2959
+ try {
2960
+ await writeFrame(resultFrame(task));
2961
+ if (disconnected || isTerminalA2AState(task.status.state)) return;
2962
+ const runId = asString3(task.metadata?.a2aRunId);
2963
+ if (!runId) return;
2964
+ const threadId = asString3(task.metadata?.threadId) ?? task.contextId;
2965
+ const messageId = asString3(task.metadata?.a2aMessageId);
2966
+ const cursor = asString3(task.metadata?.a2aStreamCursor);
2967
+ const runService = runServiceFor(scope);
2968
+ const reconcileCanonicalTask = async () => {
2969
+ const reconciled = await reconcileTaskFromRun(
2970
+ taskStore,
2971
+ runService,
2972
+ stateResolver,
2973
+ scope,
2974
+ request.body
2975
+ ).catch(() => void 0);
2976
+ return reconciled ?? taskStore.load(taskId).catch(() => void 0);
2977
+ };
2978
+ const run = await runService.getRun(scope.tenantId, runId, threadId, scope.assistantId).catch(() => void 0);
2979
+ if (!run) return;
2980
+ if (TERMINAL_RUN_STATUSES.has(run.status)) {
2981
+ const canonical2 = await reconcileCanonicalTask();
2982
+ if (canonical2 && !disconnected) await writeFrame(resultFrame(canonical2));
2983
+ return;
2984
+ }
2985
+ if (!messageId) return;
2986
+ const source = runService.stream(
2987
+ threadId,
2988
+ scope.assistantId,
2989
+ messageId,
2990
+ cursor,
2991
+ { tenantId: scope.tenantId, runId }
2992
+ );
2993
+ iterator = source[Symbol.asyncIterator]();
2994
+ try {
2995
+ for (; ; ) {
2996
+ if (disconnected) return;
2997
+ const next = await iterator.next();
2998
+ if (next.done) break;
2999
+ const chunk = next.value;
3000
+ const text = chunk.data?.content ?? "";
3001
+ if (chunk.type === MessageChunkTypes3.AI && text) {
3002
+ await writeFrame(resultFrame({
3003
+ kind: "status-update",
3004
+ taskId: task.id,
3005
+ contextId: task.contextId,
3006
+ status: {
3007
+ state: "working",
3008
+ message: agentMessage2(text),
3009
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3010
+ },
3011
+ final: false
3012
+ }));
3013
+ }
3014
+ if (chunk.type === MessageChunkTypes3.MESSAGE_COMPLETED || chunk.type === MessageChunkTypes3.MESSAGE_FAILED || chunk.type === MessageChunkTypes3.INTERRUPT) {
3015
+ break;
3016
+ }
3017
+ }
3018
+ } finally {
3019
+ await closeIterator();
3020
+ }
3021
+ if (disconnected) return;
3022
+ await runService.join(
3023
+ scope.tenantId,
3024
+ threadId,
3025
+ scope.assistantId,
3026
+ runId,
3027
+ RESUBSCRIBE_SETTLE_TIMEOUT_MS
3028
+ ).catch(() => void 0);
3029
+ if (disconnected) return;
3030
+ const canonical = await reconcileCanonicalTask();
3031
+ if (canonical) await writeFrame(resultFrame(canonical));
3032
+ } catch (err) {
3033
+ if (err instanceof ChunkCursorExpiredError) {
3034
+ await writeFrame({
3035
+ jsonrpc: "2.0",
3036
+ id: requestId,
3037
+ error: {
3038
+ code: -32603,
3039
+ message: "CURSOR_EXPIRED",
3040
+ data: { code: "CURSOR_EXPIRED" }
3041
+ }
3042
+ }, "error");
3043
+ return;
3044
+ }
3045
+ request.log.error({ err }, "a2a:jsonrpc:resubscribe:stream");
3046
+ await writeFrame({
3047
+ jsonrpc: "2.0",
3048
+ id: requestId,
3049
+ error: A2AError3.internalError(
3050
+ err instanceof Error ? err.message : "Streaming error."
3051
+ ).toJSONRPCError()
3052
+ }, "error");
3053
+ } finally {
3054
+ reply.raw.off("close", onClose);
3055
+ await closeIterator();
3056
+ runtimeLease.release();
3057
+ if (!reply.raw.writableEnded) reply.raw.end();
3058
+ }
3059
+ };
3060
+ const handleAgentCard = async (request, reply) => {
3061
+ const auth = await authService.authenticate(
3062
+ getHeader(request.headers.authorization) ?? getHeader(request.headers["x-api-key"])
3063
+ );
3064
+ if (!auth.authenticated) {
3065
+ reply.status(401).send({ error: "Unauthorized" });
3066
+ return;
3067
+ }
3068
+ let assistant;
3069
+ try {
3070
+ assistant = await authService.assertAgentAccess(
3071
+ auth,
3072
+ request.params.assistantId
3073
+ );
3074
+ } catch (err) {
3075
+ if (err instanceof A2AAuthError) {
3076
+ reply.status(err.statusCode).send({ error: err.message });
3077
+ return;
3078
+ }
3079
+ request.log.error({ err }, "a2a:agent-card:auth");
3080
+ reply.status(500).send({ error: "Internal error" });
3081
+ return;
3082
+ }
3083
+ reply.header("A2A-Version", "0.3");
3084
+ const officialAlias = request.url.split("?")[0].startsWith("/a2a/");
3085
+ reply.status(200).send(
3086
+ buildAgentCardForAssistant({
3087
+ assistant,
3088
+ baseUrl: buildBaseUrl(request),
3089
+ pushNotifications: hasPushStore(),
3090
+ ...officialAlias ? { jsonRpcPath: `/a2a/${assistant.id}` } : {}
3091
+ })
3092
+ );
3093
+ };
3094
+ app.get(
3095
+ "/api/a2a/agents/:assistantId/.well-known/agent-card.json",
3096
+ handleAgentCard
3097
+ );
3098
+ app.get(
3099
+ "/a2a/:assistantId/.well-known/agent-card.json",
3100
+ handleAgentCard
3101
+ );
3102
+ const handleJsonRpc = async (request, reply) => {
3103
+ const requestId = extractRequestId(request.body);
3104
+ const auth = await authService.authenticate(
3105
+ getHeader(request.headers.authorization) ?? getHeader(request.headers["x-api-key"])
3106
+ );
3107
+ if (!auth.authenticated) {
3108
+ reply.status(401).send(jsonRpcError(requestId, -32001, "Unauthorized"));
3109
+ return;
3110
+ }
3111
+ let assistant;
3112
+ try {
3113
+ assistant = await authService.assertAgentAccess(
3114
+ auth,
3115
+ request.params.assistantId
3116
+ );
3117
+ } catch (err) {
3118
+ if (err instanceof A2AAuthError) {
3119
+ reply.status(err.statusCode).send(
3120
+ jsonRpcError(
3121
+ requestId,
3122
+ authErrorToRpcCode(err.statusCode),
3123
+ err.message
3124
+ )
3125
+ );
3126
+ return;
3127
+ }
3128
+ request.log.error({ err }, "a2a:jsonrpc:auth");
3129
+ reply.status(500).send(jsonRpcError(requestId, -32603, "Internal error"));
3130
+ return;
3131
+ }
3132
+ const tenantId = auth.tenantId ?? "default";
3133
+ const scope = {
3134
+ tenantId,
3135
+ projectId: auth.projectId ?? "",
3136
+ workspaceId: await resolveWorkspaceId(getProjectStore, tenantId, auth.projectId),
3137
+ assistantId: assistant.id
3138
+ };
3139
+ const internalTasks = resolveTasks();
3140
+ const workItems = resolveTaskWorkItems();
3141
+ const taskStore = taskStoreFor(scope, internalTasks, workItems);
3142
+ const method = requestMethod(request.body);
3143
+ if (method === "tasks/list" || method === "agent/getExtendedAgentCard" || method === "agent/getAuthenticatedExtendedCard") {
3144
+ reply.header("A2A-Version", "0.3");
3145
+ if (method === "tasks/list") {
3146
+ const parsed = parseTasksListQuery(request.body);
3147
+ if (!parsed.ok) {
3148
+ reply.send(jsonRpcError(requestId, -32602, parsed.message));
3149
+ return;
3150
+ }
3151
+ try {
3152
+ const result2 = await listCanonicalA2ATasks(
3153
+ internalTasks,
3154
+ taskStore,
3155
+ scope,
3156
+ parsed.query
3157
+ );
3158
+ reply.send({ jsonrpc: "2.0", id: requestId, result: result2 });
3159
+ } catch (err) {
3160
+ request.log.error({ err }, "a2a:jsonrpc:tasks-list");
3161
+ reply.send(jsonRpcError(requestId, -32603, "Internal error"));
3162
+ }
3163
+ return;
3164
+ }
3165
+ const extendedCard = buildAgentCardForAssistant({
3166
+ assistant,
3167
+ baseUrl: buildBaseUrl(request),
3168
+ pushNotifications: hasPushStore(),
3169
+ ...request.url.split("?")[0].startsWith("/a2a/") ? { jsonRpcPath: `/a2a/${assistant.id}` } : {}
3170
+ });
3171
+ reply.send({ jsonrpc: "2.0", id: requestId, result: extendedCard });
3172
+ return;
3173
+ }
3174
+ if (method === "tasks/resubscribe") {
3175
+ await handleTaskResubscribe(request, reply, requestId, scope, taskStore);
3176
+ return;
3177
+ }
3178
+ const ingestor = makeFileIngestor(scope.tenantId, deps.maxFileBytes);
3179
+ const runService = runServiceFor(scope);
3180
+ const executor = new AxiomAgentExecutor({
3181
+ scope,
3182
+ assistant,
3183
+ taskStore,
3184
+ ingestor,
3185
+ runService,
3186
+ blocking: blockingFor(request.body),
3187
+ getInterrupts: (threadId) => stateResolver.getInterrupts(threadId, assistant.id, scope)
3188
+ });
3189
+ try {
3190
+ await reconcileTaskFromRun(
3191
+ taskStore,
3192
+ runService,
3193
+ stateResolver,
3194
+ scope,
3195
+ request.body
3196
+ );
3197
+ const canceled = await cancelTaskFromRun(
3198
+ taskStore,
3199
+ runService,
3200
+ scope,
3201
+ request.body
3202
+ );
3203
+ if (canceled) {
3204
+ reply.header("A2A-Version", "0.3");
3205
+ reply.send({ jsonrpc: "2.0", id: requestId, result: canceled });
3206
+ return;
3207
+ }
3208
+ } catch (err) {
3209
+ if (err instanceof A2AError3) {
3210
+ reply.send(jsonRpcError(requestId, err.code, err.message));
3211
+ return;
3212
+ }
3213
+ request.log.error({ err }, "a2a:jsonrpc:reconcile");
3214
+ reply.status(500).send(jsonRpcError(requestId, -32603, "Internal error"));
3215
+ return;
3216
+ }
3217
+ const resumeError = await resumeValidationError(taskStore, request.body).catch(() => void 0);
3218
+ if (resumeError !== void 0) {
3219
+ reply.send(jsonRpcError(requestId, -32602, resumeError));
3220
+ return;
3221
+ }
3222
+ let pushStore;
3223
+ try {
3224
+ pushStore = createPushStore(scope);
3225
+ } catch (err) {
3226
+ request.log.warn({ err }, "a2a:jsonrpc:push-store-unavailable");
3227
+ }
3228
+ const card = buildAgentCardForAssistant({
3229
+ assistant,
3230
+ baseUrl: buildBaseUrl(request),
3231
+ pushNotifications: pushStore !== void 0,
3232
+ ...request.url.split("?")[0].startsWith("/a2a/") ? { jsonRpcPath: `/a2a/${assistant.id}` } : {}
3233
+ });
3234
+ let runtimeLease;
3235
+ try {
3236
+ runtimeLease = runtimeRegistry.acquire(scope);
3237
+ } catch (err) {
3238
+ if (err instanceof A2ARuntimeCapacityError) {
3239
+ request.log.error({ err, scope }, "a2a:jsonrpc:runtime_capacity");
3240
+ reply.status(503).send(jsonRpcError(requestId, -32603, "A2A runtime capacity exhausted"));
3241
+ return;
3242
+ }
3243
+ throw err;
3244
+ }
3245
+ let result;
3246
+ try {
3247
+ const pushSender = pushStore ? deps.getPushNotificationSender?.(pushStore) ?? new A2APushNotificationSender({
3248
+ store: pushStore,
3249
+ ...deps.resolvePushHost ? { resolveHost: deps.resolvePushHost } : {}
3250
+ }) : void 0;
3251
+ const handler = new DefaultRequestHandler(
3252
+ card,
3253
+ taskStore,
3254
+ executor,
3255
+ runtimeLease.manager,
3256
+ pushStore,
3257
+ pushSender
3258
+ );
3259
+ const transport = new JsonRpcTransportHandler(handler);
3260
+ reply.header("A2A-Version", "0.3");
3261
+ result = await transport.handle(request.body);
3262
+ } catch (err) {
3263
+ runtimeLease.release();
3264
+ if (err instanceof A2AError3) {
3265
+ reply.send(jsonRpcError(requestId, err.code, err.message));
3266
+ return;
3267
+ }
3268
+ request.log.error({ err }, "a2a:jsonrpc:handler");
3269
+ reply.status(500).send(jsonRpcError(requestId, -32603, "Internal error"));
3270
+ return;
3271
+ }
3272
+ if (isAsyncGenerator(result)) {
3273
+ reply.hijack();
3274
+ reply.raw.writeHead(200, SSE_HEADERS);
3275
+ try {
3276
+ for await (const evt of result) {
3277
+ reply.raw.write(`data: ${JSON.stringify(evt)}
3278
+
3279
+ `);
3280
+ }
3281
+ } catch (err) {
3282
+ request.log.error({ err }, "a2a:jsonrpc:stream");
3283
+ const errorResponse = {
3284
+ jsonrpc: "2.0",
3285
+ id: requestId,
3286
+ error: A2AError3.internalError(
3287
+ err instanceof Error ? err.message : "Streaming error."
3288
+ ).toJSONRPCError()
3289
+ };
3290
+ reply.raw.write(`event: error
3291
+ data: ${JSON.stringify(errorResponse)}
3292
+
3293
+ `);
3294
+ } finally {
3295
+ runtimeLease.release();
3296
+ reply.raw.end();
3297
+ }
3298
+ return;
3299
+ }
3300
+ runtimeLease.release();
3301
+ reply.send(result);
3302
+ };
3303
+ app.post(
3304
+ "/api/a2a/agents/:assistantId/jsonrpc",
3305
+ handleJsonRpc
3306
+ );
3307
+ app.post(
3308
+ "/a2a/:assistantId",
3309
+ handleJsonRpc
3310
+ );
3311
+ app.post(
3312
+ "/api/a2a/agents/:assistantId/test-call",
3313
+ async (request, reply) => {
3314
+ const user = getSessionUser(request);
3315
+ if (!user?.tenantId) {
3316
+ reply.status(401).send({ success: false, error: "Unauthorized" });
3317
+ return;
3318
+ }
3319
+ const tenantId = user.tenantId;
3320
+ const body = request.body ?? {};
3321
+ const { projectId, message } = body;
3322
+ if (!projectId) {
3323
+ reply.status(400).send({ error: "projectId is required" });
3324
+ return;
3325
+ }
3326
+ if (!message || !message.trim()) {
3327
+ reply.status(400).send({ error: "message is required" });
3328
+ return;
3329
+ }
3330
+ const assistant = await getAssistantStore().getAssistantById(
3331
+ tenantId,
3332
+ request.params.assistantId
3333
+ );
3334
+ if (!assistant) {
3335
+ reply.status(404).send({ error: "Assistant not found" });
3336
+ return;
3337
+ }
3338
+ const project = await getProjectStore().getProjectById(
3339
+ tenantId,
3340
+ projectId
3341
+ );
3342
+ if (!project || project.tenantId !== tenantId) {
3343
+ reply.status(403).send({ error: "Project not found" });
3344
+ return;
3345
+ }
3346
+ const scope = {
3347
+ tenantId,
3348
+ projectId,
3349
+ workspaceId: project.workspaceId,
3350
+ assistantId: assistant.id
3351
+ };
3352
+ const ingestor = makeFileIngestor(scope.tenantId, deps.maxFileBytes);
3353
+ const internalTasks = resolveTasks();
3354
+ const workItems = resolveTaskWorkItems();
3355
+ const taskStore = taskStoreFor(scope, internalTasks, workItems);
3356
+ const executor = new AxiomAgentExecutor({
3357
+ scope,
3358
+ assistant,
3359
+ taskStore,
3360
+ ingestor,
3361
+ runService: runServiceFor(scope),
3362
+ blocking: true,
3363
+ getInterrupts: (threadId) => stateResolver.getInterrupts(threadId, assistant.id, scope)
3364
+ });
3365
+ const taskId = uuidv42();
3366
+ const contextId = uuidv42();
3367
+ const userMessage = {
3368
+ kind: "message",
3369
+ messageId: uuidv42(),
3370
+ role: "user",
3371
+ parts: [{ kind: "text", text: message }]
3372
+ };
3373
+ const requestContext = new RequestContext(userMessage, taskId, contextId);
3374
+ const bus = new DefaultExecutionEventBus2();
3375
+ reply.hijack();
3376
+ reply.raw.writeHead(200, SSE_HEADERS);
3377
+ const sendEvent = (event, data) => {
3378
+ reply.raw.write(`event: ${event}
3379
+ data: ${JSON.stringify(data)}
3380
+
3381
+ `);
3382
+ };
3383
+ bus.on("event", (event) => {
3384
+ sendEvent(event.kind, event);
3385
+ });
3386
+ try {
3387
+ await executor.execute(requestContext, bus);
3388
+ } catch (err) {
3389
+ request.log.error({ err }, "a2a:test-call:execution");
3390
+ try {
3391
+ await taskStore.save({
3392
+ kind: "task",
3393
+ id: taskId,
3394
+ contextId,
3395
+ status: { state: "failed", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
3396
+ });
3397
+ } catch (saveError) {
3398
+ request.log.error({ err: saveError }, "a2a:test-call:settle");
3399
+ }
3400
+ sendEvent("status-update", {
3401
+ kind: "status-update",
3402
+ taskId,
3403
+ contextId,
3404
+ status: { state: "failed", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
3405
+ final: true
3406
+ });
3407
+ } finally {
3408
+ reply.raw.end();
3409
+ }
3410
+ }
3411
+ );
3412
+ }
3413
+ export {
3414
+ registerA2AStandardRoutes
3415
+ };
3416
+ //# sourceMappingURL=a2a-standard-DP53K7RC.mjs.map