@axiom-lattice/gateway 10.1.0 → 10.2.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 (110) hide show
  1. package/dist/{a2a-standard-NOAVPYXJ.mjs → a2a-standard-OANCQYQ2.mjs} +8 -4
  2. package/dist/a2a-standard-OANCQYQ2.mjs.map +1 -0
  3. package/dist/{chunk-A7YNAU5L.mjs → chunk-4XWI7U3L.mjs} +106 -1
  4. package/dist/chunk-4XWI7U3L.mjs.map +1 -0
  5. package/dist/{chunk-PHWGCRW2.mjs → chunk-67CDUQM3.mjs} +16 -2
  6. package/dist/chunk-67CDUQM3.mjs.map +1 -0
  7. package/dist/{chunk-T2HIWZGP.mjs → chunk-BIMO2ILH.mjs} +1 -1
  8. package/dist/chunk-BIMO2ILH.mjs.map +1 -0
  9. package/dist/embed/{embed-page-AgentDetailSideApp-5L5GY5TY.js → embed-page-AgentDetailSideApp-WAATX6D3.js} +1 -1
  10. package/dist/embed/{embed-page-AgentGovernanceWorkbench-RKNSCQUM.js → embed-page-AgentGovernanceWorkbench-MOIYXL5S.js} +1 -1
  11. package/dist/embed/{embed-page-LangfuseDashboard-QHWHMJVB.js → embed-page-LangfuseDashboard-7ZWWE45Q.js} +1 -1
  12. package/dist/embed/{embed-page-MessageGraph-HDJYCYZV.js → embed-page-MessageGraph-JG554SNZ.js} +1 -1
  13. package/dist/embed/{embed-page-ReactChart-KJS73V5Q.js → embed-page-ReactChart-WHOMS6I6.js} +1 -1
  14. package/dist/embed/{embed-page-ReactInfographic-677NVKOU.js → embed-page-ReactInfographic-5P36JLSX.js} +1 -1
  15. package/dist/embed/{embed-page-SkillFlow-ULY3XZ3A.js → embed-page-SkillFlow-RP54B2A5.js} +1 -1
  16. package/dist/embed/{embed-page-TasksSideApp-X2YZGU7K.js → embed-page-TasksSideApp-6TCN3T73.js} +1 -1
  17. package/dist/embed/{embed-page-ThreadMaintenancePanel-N5IFOOFT.js → embed-page-ThreadMaintenancePanel-WNWYNASM.js} +1 -1
  18. package/dist/embed/{embed-page-TopologyRuntimeView-LZTSYJE5.js → embed-page-TopologyRuntimeView-UMC4XOUL.js} +1 -1
  19. package/dist/embed/{embed-page-WorkflowAutomationView-KIKV62O2.js → embed-page-WorkflowAutomationView-PUOPYYGB.js} +1 -1
  20. package/dist/embed/embed-page-all-NEJVJ4G7.js +1 -0
  21. package/dist/embed/{embed-page-architectureDiagram-VXUJARFQ-TYJ3NO2K.js → embed-page-architectureDiagram-VXUJARFQ-THUN2PFL.js} +1 -1
  22. package/dist/embed/embed-page-aspnet-SRK67AH2.js +1 -0
  23. package/dist/embed/{embed-page-blockDiagram-VD42YOAC-L4J3TZWR.js → embed-page-blockDiagram-VD42YOAC-K7OX6LIO.js} +1 -1
  24. package/dist/embed/{embed-page-c4Diagram-YG6GDRKO-CP2IXU3Z.js → embed-page-c4Diagram-YG6GDRKO-DJDAF5AO.js} +1 -1
  25. package/dist/embed/embed-page-chunk-4GCFDWE7.js +6997 -0
  26. package/dist/embed/{embed-page-chunk-3FNSGFP2.js → embed-page-chunk-5EENS2J7.js} +16 -16
  27. package/dist/embed/{embed-page-chunk-3XPHW2C7.js → embed-page-chunk-5O5IKZOC.js} +1 -1
  28. package/dist/embed/{embed-page-chunk-DQJVWZOB.js → embed-page-chunk-A2AHDN6K.js} +1 -1
  29. package/dist/embed/embed-page-chunk-AFYXBC77.js +217 -0
  30. package/dist/embed/{embed-page-chunk-F5QVZLK5.js → embed-page-chunk-DTRFJLVZ.js} +1 -1
  31. package/dist/embed/embed-page-chunk-ESQ5V77Q.js +1 -0
  32. package/dist/embed/{embed-page-chunk-2VJ7T35I.js → embed-page-chunk-FD2PRLMW.js} +1 -1
  33. package/dist/embed/{embed-page-chunk-KXVXKWED.js → embed-page-chunk-G2RO6IBN.js} +1 -1
  34. package/dist/embed/{embed-page-chunk-BBIV2YA2.js → embed-page-chunk-G6UTU7IC.js} +1 -1
  35. package/dist/embed/{embed-page-chunk-7GLQRV3J.js → embed-page-chunk-J5AHS4PO.js} +1 -1
  36. package/dist/embed/{embed-page-chunk-5WWOKLDB.js → embed-page-chunk-JUZPOR5Y.js} +1 -1
  37. package/dist/embed/{embed-page-chunk-5HWN5HSA.js → embed-page-chunk-ML6AVDQQ.js} +1 -1
  38. package/dist/embed/{embed-page-chunk-YBP3MD23.js → embed-page-chunk-OBN4XHZP.js} +1 -1
  39. package/dist/embed/{embed-page-chunk-NVFNRWRN.js → embed-page-chunk-PLNJRORR.js} +1 -1
  40. package/dist/embed/{embed-page-chunk-FF2HNPMW.js → embed-page-chunk-QRFTGCB2.js} +1 -1
  41. package/dist/embed/{embed-page-chunk-4BPMGDDV.js → embed-page-chunk-RU45VIPM.js} +1 -1
  42. package/dist/embed/{embed-page-chunk-AEDAOTEO.js → embed-page-chunk-SQ4GANYE.js} +1 -1
  43. package/dist/embed/embed-page-chunk-TZPX2O54.js +7 -0
  44. package/dist/embed/{embed-page-chunk-YJUCDIYK.js → embed-page-chunk-V7R2Y72J.js} +1 -1
  45. package/dist/embed/{embed-page-chunk-XSKDYUNQ.js → embed-page-chunk-VY6MAPHR.js} +1 -1
  46. package/dist/embed/{embed-page-chunk-WK77HIEN.js → embed-page-chunk-WFUFTRWA.js} +1 -1
  47. package/dist/embed/{embed-page-chunk-7CJDHJEH.js → embed-page-chunk-XKGF5D6L.js} +1 -1
  48. package/dist/embed/{embed-page-chunk-WWXRO4ZL.js → embed-page-chunk-XSKNS3LY.js} +1 -1
  49. package/dist/embed/{embed-page-chunk-DI5UAV2I.js → embed-page-chunk-ZVTX7OVB.js} +1 -1
  50. package/dist/embed/embed-page-classDiagram-2ON5EDUG-VYHHMIKZ.js +1 -0
  51. package/dist/embed/embed-page-classDiagram-v2-WZHVMYZB-5AISLXUF.js +1 -0
  52. package/dist/embed/{embed-page-code-highlighter-4XNLEZAM.js → embed-page-code-highlighter-ZZBZ7CEQ.js} +1 -1
  53. package/dist/embed/embed-page-cshtml-ZY7LJSTH.js +1 -0
  54. package/dist/embed/{embed-page-dagre-6UL2VRFP-LTZ2DVVO.js → embed-page-dagre-6UL2VRFP-UQFZMKYW.js} +1 -1
  55. package/dist/embed/{embed-page-diagram-PSM6KHXK-JHYXSMCB.js → embed-page-diagram-PSM6KHXK-4IKXWNA4.js} +1 -1
  56. package/dist/embed/{embed-page-diagram-QEK2KX5R-2YGDFFTU.js → embed-page-diagram-QEK2KX5R-245T32UF.js} +1 -1
  57. package/dist/embed/{embed-page-diagram-S2PKOQOG-6EW6MDJW.js → embed-page-diagram-S2PKOQOG-BOA5MJA5.js} +1 -1
  58. package/dist/embed/embed-page-en-5E2A4Q7G.js +1 -0
  59. package/dist/embed/embed-page-en-AEJHFSAV.js +1 -0
  60. package/dist/embed/{embed-page-en-3Z4N5KXB.js → embed-page-en-QULE3S6E.js} +1 -1
  61. package/dist/embed/{embed-page-erDiagram-Q2GNP2WA-KSUS6MHW.js → embed-page-erDiagram-Q2GNP2WA-S4J4SQPR.js} +1 -1
  62. package/dist/embed/{embed-page-flowDiagram-NV44I4VS-AYXRDKAJ.js → embed-page-flowDiagram-NV44I4VS-ZFIJXKMW.js} +1 -1
  63. package/dist/embed/{embed-page-ganttDiagram-JELNMOA3-AV7N2N3T.js → embed-page-ganttDiagram-JELNMOA3-7MSIEWL4.js} +1 -1
  64. package/dist/embed/{embed-page-gitGraphDiagram-NY62KEGX-IBVOXVO5.js → embed-page-gitGraphDiagram-NY62KEGX-F6KE6PZK.js} +1 -1
  65. package/dist/embed/embed-page-infoDiagram-WHAUD3N6-FQMKXERB.js +2 -0
  66. package/dist/embed/{embed-page-journeyDiagram-XKPGCS4Q-3GILI7Y4.js → embed-page-journeyDiagram-XKPGCS4Q-6VTW6GQ3.js} +1 -1
  67. package/dist/embed/{embed-page-kanban-definition-3W4ZIXB7-UGO3J7IH.js → embed-page-kanban-definition-3W4ZIXB7-7IZYCLQZ.js} +1 -1
  68. package/dist/embed/{embed-page-mermaid-QZJOSGTZ.js → embed-page-mermaid-O5AJMXHC.js} +2 -2
  69. package/dist/embed/{embed-page-mindmap-definition-VGOIOE7T-VXXZJS3Z.js → embed-page-mindmap-definition-VGOIOE7T-GKTOT34A.js} +1 -1
  70. package/dist/embed/{embed-page-pieDiagram-ADFJNKIX-ZFAWYA2N.js → embed-page-pieDiagram-ADFJNKIX-VDPALO42.js} +1 -1
  71. package/dist/embed/{embed-page-requirementDiagram-UZGBJVZJ-2QBINXR7.js → embed-page-requirementDiagram-UZGBJVZJ-LTZH6RTD.js} +1 -1
  72. package/dist/embed/{embed-page-sequenceDiagram-WL72ISMW-3MGTESG4.js → embed-page-sequenceDiagram-WL72ISMW-LV6Q4SHW.js} +1 -1
  73. package/dist/embed/{embed-page-stateDiagram-FKZM4ZOC-W6VPIMB5.js → embed-page-stateDiagram-FKZM4ZOC-U4NHQDNP.js} +1 -1
  74. package/dist/embed/embed-page-stateDiagram-v2-4FDKWEC3-G3EWQCOW.js +1 -0
  75. package/dist/embed/{embed-page-tsx-LGDDERM2.js → embed-page-tsx-PNW7N5T4.js} +1 -1
  76. package/dist/embed/{embed-page-xychartDiagram-PRI3JC2R-7XKT4R7V.js → embed-page-xychartDiagram-PRI3JC2R-VZSIXBFK.js} +1 -1
  77. package/dist/embed/embed-page-zh-EZHTNARQ.js +1 -0
  78. package/dist/embed/embed-page-zh-TOINMHLQ.js +1 -0
  79. package/dist/embed/{embed-page-zh-GXE63PQ7.js → embed-page-zh-WT752BVZ.js} +1 -1
  80. package/dist/embed/embed-page.js +1 -1
  81. package/dist/embed/mcp-app-page.js +32 -32
  82. package/dist/index.d.mts +145 -1
  83. package/dist/index.d.ts +145 -1
  84. package/dist/index.js +8154 -1466
  85. package/dist/index.js.map +1 -1
  86. package/dist/index.mjs +7093 -491
  87. package/dist/index.mjs.map +1 -1
  88. package/dist/{mcp-app-shares-SHPBJQXS.mjs → mcp-app-shares-VN6G3LNW.mjs} +2 -2
  89. package/dist/{mcp-apps-QVUK6UBA.mjs → mcp-apps-MZECSNN2.mjs} +2 -2
  90. package/package.json +11 -7
  91. package/dist/a2a-standard-NOAVPYXJ.mjs.map +0 -1
  92. package/dist/chunk-A7YNAU5L.mjs.map +0 -1
  93. package/dist/chunk-PHWGCRW2.mjs.map +0 -1
  94. package/dist/chunk-T2HIWZGP.mjs.map +0 -1
  95. package/dist/embed/embed-page-all-JEPF6WHD.js +0 -1
  96. package/dist/embed/embed-page-aspnet-GIV4ESY6.js +0 -1
  97. package/dist/embed/embed-page-chunk-54M4265G.js +0 -211
  98. package/dist/embed/embed-page-chunk-5QZRLAW2.js +0 -7
  99. package/dist/embed/embed-page-chunk-MVT6CPWE.js +0 -6986
  100. package/dist/embed/embed-page-chunk-ZQQFWEWO.js +0 -1
  101. package/dist/embed/embed-page-classDiagram-2ON5EDUG-MHNSVPAN.js +0 -1
  102. package/dist/embed/embed-page-classDiagram-v2-WZHVMYZB-QCMEVMYH.js +0 -1
  103. package/dist/embed/embed-page-cshtml-RYMZMLKB.js +0 -1
  104. package/dist/embed/embed-page-en-2S7Q4NKN.js +0 -1
  105. package/dist/embed/embed-page-infoDiagram-WHAUD3N6-ZULKOSOZ.js +0 -2
  106. package/dist/embed/embed-page-stateDiagram-v2-4FDKWEC3-QYJ47QBY.js +0 -1
  107. package/dist/embed/embed-page-zh-GX6IEO57.js +0 -1
  108. /package/dist/embed/{embed-page-chunk-UH47IEAR.js → embed-page-chunk-34FVRFPW.js} +0 -0
  109. /package/dist/{mcp-app-shares-SHPBJQXS.mjs.map → mcp-app-shares-VN6G3LNW.mjs.map} +0 -0
  110. /package/dist/{mcp-apps-QVUK6UBA.mjs.map → mcp-apps-MZECSNN2.mjs.map} +0 -0
@@ -24,6 +24,46 @@ var DEFAULT_RATE_LIMIT_POLICIES = {
24
24
  windowMs: 6e4,
25
25
  dimensions: ["ip"]
26
26
  },
27
+ "auth.oidc.begin": {
28
+ limit: 60,
29
+ windowMs: 6e4,
30
+ dimensions: ["ip"]
31
+ },
32
+ "auth.oidc.callback": {
33
+ limit: 30,
34
+ windowMs: 6e4,
35
+ dimensions: ["ip"]
36
+ },
37
+ "auth.oidc.exchange": {
38
+ limit: 60,
39
+ windowMs: 6e4,
40
+ dimensions: ["ip"]
41
+ },
42
+ "invite.link.resolve": {
43
+ limit: 60,
44
+ windowMs: 6e4,
45
+ dimensions: ["ip"]
46
+ },
47
+ "invite.link.claim": {
48
+ limit: 30,
49
+ windowMs: 6e4,
50
+ dimensions: ["ip"]
51
+ },
52
+ "branding.read": {
53
+ limit: 300,
54
+ windowMs: 6e4,
55
+ dimensions: ["ip"]
56
+ },
57
+ "auth.oidc.email_completion": {
58
+ limit: 30,
59
+ windowMs: 6e4,
60
+ dimensions: ["ip"]
61
+ },
62
+ "oidc-provider.test": {
63
+ limit: 20,
64
+ windowMs: 6e4,
65
+ dimensions: ["user", "ip"]
66
+ },
27
67
  "open.api": {
28
68
  limit: 6e4,
29
69
  windowMs: 6e4,
@@ -268,11 +308,74 @@ function mergeRateLimitPolicies(config = {}, logger = console) {
268
308
  }
269
309
  return merged;
270
310
  }
311
+ function platformRateLimitOverrides(settings) {
312
+ const snapshot = settings.getSnapshot();
313
+ const sources = snapshot.sources;
314
+ const rateLimits = snapshot.effective.rateLimits;
315
+ const overrides = {};
316
+ if (sources.openApiPerMinute === "platform") {
317
+ overrides["open.api"] = { limit: rateLimits.openApiPerMinute };
318
+ }
319
+ if (sources.openAgentPerMinute === "platform") {
320
+ overrides["open.agent"] = { limit: rateLimits.openAgentPerMinute };
321
+ }
322
+ const a2a = {};
323
+ if (sources.a2aJsonRpcPerMinute === "platform") {
324
+ a2a.limit = rateLimits.a2aJsonRpcPerMinute;
325
+ }
326
+ if (sources.a2aJsonRpcIpPerMinute === "platform") {
327
+ a2a.dimensionLimits = { ip: rateLimits.a2aJsonRpcIpPerMinute };
328
+ }
329
+ if (Object.keys(a2a).length > 0) overrides["a2a.jsonrpc"] = a2a;
330
+ return overrides;
331
+ }
332
+ function mergePolicyOverrides(base, overrides) {
333
+ const merged = { ...base };
334
+ for (const [name, value] of Object.entries(overrides)) {
335
+ const existing = merged[name];
336
+ if (!existing) {
337
+ merged[name] = { ...value };
338
+ continue;
339
+ }
340
+ const dimensionLimits = {
341
+ ...existing.dimensionLimits ?? {},
342
+ ...value.dimensionLimits ?? {}
343
+ };
344
+ merged[name] = {
345
+ ...existing,
346
+ ...value,
347
+ ...Object.keys(dimensionLimits).length > 0 ? { dimensionLimits } : {}
348
+ };
349
+ }
350
+ return merged;
351
+ }
271
352
  var RateLimitService = class {
272
353
  constructor(options) {
273
354
  this.options = options;
274
355
  this.warnedPolicies = /* @__PURE__ */ new Set();
275
356
  }
357
+ /** Replace the active policy map, e.g. after platform settings change. */
358
+ updatePolicies(policies) {
359
+ this.options.policies = policies;
360
+ }
361
+ /**
362
+ * Rebuild the active policy map on top of the host config overrides.
363
+ *
364
+ * Use this for platform settings changes so host-supplied
365
+ * `rateLimits.policies` are preserved instead of being lost by a full-map
366
+ * replacement.
367
+ */
368
+ updatePlatformPolicies(overrides) {
369
+ this.options.policies = mergeRateLimitPolicies(
370
+ {
371
+ policies: mergePolicyOverrides(
372
+ this.options.basePolicyOverrides ?? {},
373
+ overrides
374
+ )
375
+ },
376
+ this.options.logger
377
+ );
378
+ }
276
379
  /**
277
380
  * Consume one request against a named policy.
278
381
  *
@@ -350,6 +453,7 @@ function createRateLimitService(config = {}, logger = console) {
350
453
  maxEntries: config.maxInMemoryEntries ?? DEFAULT_MAX_ENTRIES
351
454
  }),
352
455
  policies: mergeRateLimitPolicies(config, logger),
456
+ basePolicyOverrides: config.policies ?? {},
353
457
  enabled: config.enabled ?? process.env.AXIOM_RATE_LIMIT_DISABLED !== "true",
354
458
  logger
355
459
  });
@@ -370,8 +474,9 @@ function getRateLimitService() {
370
474
  export {
371
475
  claimDedupKey,
372
476
  incrementRateCounter,
477
+ platformRateLimitOverrides,
373
478
  createRateLimitService,
374
479
  setRateLimitService,
375
480
  getRateLimitService
376
481
  };
377
- //# sourceMappingURL=chunk-A7YNAU5L.mjs.map
482
+ //# sourceMappingURL=chunk-4XWI7U3L.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/rate-limit/RateLimitService.ts","../src/services/rate-limit/defaultPolicies.ts","../src/router/middlewares/channelSharedState.ts","../src/services/rate-limit/RateLimitStore.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { PlatformSettingsProvider } from \"@axiom-lattice/protocols\";\nimport {\n DEFAULT_RATE_LIMIT_POLICIES,\n legacyOpenRateLimitPolicies,\n} from \"./defaultPolicies\";\nimport {\n HybridRateLimitStore,\n RedisRateLimitStore,\n} from \"./RateLimitStore\";\nimport type {\n RateLimitConfig,\n RateLimitDecision,\n RateLimitDimension,\n RateLimitPolicy,\n RateLimitStore,\n} from \"./types\";\n\nconst DIMENSIONS: ReadonlySet<RateLimitDimension> = new Set([\n \"ip\",\n \"token\",\n \"user\",\n \"tenant\",\n \"credential\",\n \"key\",\n \"session\",\n]);\nconst DEFAULT_MAX_ENTRIES = 10_000;\n\n/** SHA-256 a dimension value so secrets never appear in keys. */\nfunction hashDimension(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\n/** Validate one policy before it can be used for enforcement. */\nfunction normalizePolicy(\n name: string,\n value: Partial<RateLimitPolicy>,\n base?: RateLimitPolicy,\n logger: Pick<Console, \"warn\"> = console,\n): RateLimitPolicy | undefined {\n const limit = value.limit ?? base?.limit;\n const windowMs = value.windowMs ?? base?.windowMs;\n const dimensions = value.dimensions ?? base?.dimensions;\n if (\n typeof limit !== \"number\" ||\n !Number.isFinite(limit) ||\n limit <= 0 ||\n typeof windowMs !== \"number\" ||\n !Number.isFinite(windowMs) ||\n windowMs <= 0 ||\n !Array.isArray(dimensions) ||\n dimensions.length === 0 ||\n dimensions.some((dimension) => !DIMENSIONS.has(dimension))\n ) {\n logger.warn(JSON.stringify({\n event: \"rate-limit:invalid-policy\",\n policy: name,\n }));\n return base ? { ...base, dimensions: [...base.dimensions] } : undefined;\n }\n const dimensionLimits: Partial<Record<RateLimitDimension, number>> = {\n ...(base?.dimensionLimits ?? {}),\n };\n const rawDimensionLimits = (value as { dimensionLimits?: unknown }).dimensionLimits;\n if (rawDimensionLimits !== undefined) {\n if (\n typeof rawDimensionLimits !== \"object\" ||\n rawDimensionLimits === null ||\n Array.isArray(rawDimensionLimits)\n ) {\n logger.warn(JSON.stringify({\n event: \"rate-limit:invalid-dimension-limits\",\n policy: name,\n }));\n } else {\n for (const [dimension, raw] of Object.entries(rawDimensionLimits)) {\n if (\n !DIMENSIONS.has(dimension as RateLimitDimension) ||\n typeof raw !== \"number\" ||\n !Number.isFinite(raw) ||\n raw <= 0\n ) {\n logger.warn(JSON.stringify({\n event: \"rate-limit:invalid-dimension-limit\",\n policy: name,\n dimension,\n }));\n continue;\n }\n dimensionLimits[dimension as RateLimitDimension] = Math.floor(raw);\n }\n }\n }\n return {\n limit: Math.floor(limit),\n windowMs: Math.floor(windowMs),\n dimensions: [...dimensions],\n ...(Object.keys(dimensionLimits).length > 0 ? { dimensionLimits } : {}),\n ...(value.enabled !== undefined\n ? { enabled: value.enabled }\n : base?.enabled !== undefined\n ? { enabled: base.enabled }\n : {}),\n };\n}\n\n/** Parse `AXIOM_RATE_LIMIT_POLICIES` as a partial policy map. */\nexport function parseRateLimitPoliciesEnv(\n raw: string | undefined,\n logger: Pick<Console, \"warn\"> = console,\n): Record<string, Partial<RateLimitPolicy>> {\n if (!raw || raw.trim() === \"\") return {};\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(\"expected an object\");\n }\n return parsed as Record<string, Partial<RateLimitPolicy>>;\n } catch (error) {\n logger.warn(JSON.stringify({\n event: \"rate-limit:invalid-env-policies\",\n error: error instanceof Error ? error.message : String(error),\n }));\n return {};\n }\n}\n\n/** Merge defaults, env, and host overrides into a validated policy map. */\nexport function mergeRateLimitPolicies(\n config: RateLimitConfig = {},\n logger: Pick<Console, \"warn\"> = console,\n): Record<string, RateLimitPolicy> {\n const merged: Record<string, RateLimitPolicy> = {};\n for (const [name, policy] of Object.entries(DEFAULT_RATE_LIMIT_POLICIES)) {\n merged[name] = { ...policy, dimensions: [...policy.dimensions] };\n }\n const overrides = [\n legacyOpenRateLimitPolicies(process.env),\n parseRateLimitPoliciesEnv(process.env.AXIOM_RATE_LIMIT_POLICIES, logger),\n config.policies ?? {},\n ];\n for (const layer of overrides) {\n for (const [name, value] of Object.entries(layer)) {\n const policy = normalizePolicy(name, value, merged[name], logger);\n if (policy) merged[name] = policy;\n }\n }\n return merged;\n}\n\n/**\n * Map the platform rate-limit settings onto policy overrides.\n *\n * Only leaves actually sourced from the persisted platform document are\n * emitted. Effective env/default values must not become platform overrides:\n * doing so would silently replace operator-configured host or environment\n * limits during composition.\n */\nexport function platformRateLimitOverrides(\n settings: Pick<PlatformSettingsProvider, \"getSnapshot\" | \"getRateLimits\">,\n): Record<string, Partial<RateLimitPolicy>> {\n const snapshot = settings.getSnapshot();\n const sources = snapshot.sources;\n const rateLimits = snapshot.effective.rateLimits;\n const overrides: Record<string, Partial<RateLimitPolicy>> = {};\n\n if (sources.openApiPerMinute === \"platform\") {\n overrides[\"open.api\"] = { limit: rateLimits.openApiPerMinute };\n }\n if (sources.openAgentPerMinute === \"platform\") {\n overrides[\"open.agent\"] = { limit: rateLimits.openAgentPerMinute };\n }\n\n const a2a: Partial<RateLimitPolicy> = {};\n if (sources.a2aJsonRpcPerMinute === \"platform\") {\n a2a.limit = rateLimits.a2aJsonRpcPerMinute;\n }\n if (sources.a2aJsonRpcIpPerMinute === \"platform\") {\n a2a.dimensionLimits = { ip: rateLimits.a2aJsonRpcIpPerMinute };\n }\n if (Object.keys(a2a).length > 0) overrides[\"a2a.jsonrpc\"] = a2a;\n\n return overrides;\n}\n\n/**\n * Merge two partial policy maps at policy-key granularity.\n *\n * Host base overrides are preserved unless the platform layer sets the same\n * field. `dimensionLimits` objects are merged per dimension instead of being\n * replaced, so a platform override of one dimension keeps host dimensions.\n */\nfunction mergePolicyOverrides(\n base: Record<string, Partial<RateLimitPolicy>>,\n overrides: Record<string, Partial<RateLimitPolicy>>,\n): Record<string, Partial<RateLimitPolicy>> {\n const merged: Record<string, Partial<RateLimitPolicy>> = { ...base };\n for (const [name, value] of Object.entries(overrides)) {\n const existing = merged[name];\n if (!existing) {\n merged[name] = { ...value };\n continue;\n }\n const dimensionLimits = {\n ...(existing.dimensionLimits ?? {}),\n ...(value.dimensionLimits ?? {}),\n };\n merged[name] = {\n ...existing,\n ...value,\n ...(Object.keys(dimensionLimits).length > 0 ? { dimensionLimits } : {}),\n };\n }\n return merged;\n}\n\n/** Dependencies for {@link RateLimitService}. */\nexport interface RateLimitServiceOptions {\n store: RateLimitStore;\n policies: Record<string, RateLimitPolicy>;\n enabled: boolean;\n logger?: Pick<Console, \"warn\">;\n /**\n * Host-supplied policy overrides (the gateway `rateLimits.policies` config).\n *\n * Kept so platform settings updates can rebuild the effective policy map\n * without dropping these host overrides.\n */\n basePolicyOverrides?: Record<string, Partial<RateLimitPolicy>>;\n}\n\n/**\n * Reusable fixed-window rate limiter.\n *\n * Each policy declares dimensions; a request is allowed only when every\n * supplied dimension stays under the policy limit. Missing dimension values are\n * skipped, so routes should provide at least one meaningful dimension.\n */\nexport class RateLimitService {\n private readonly warnedPolicies = new Set<string>();\n\n constructor(private options: RateLimitServiceOptions) {}\n\n /** Replace the active policy map, e.g. after platform settings change. */\n updatePolicies(policies: Record<string, RateLimitPolicy>): void {\n this.options.policies = policies;\n }\n\n /**\n * Rebuild the active policy map on top of the host config overrides.\n *\n * Use this for platform settings changes so host-supplied\n * `rateLimits.policies` are preserved instead of being lost by a full-map\n * replacement.\n */\n updatePlatformPolicies(\n overrides: Record<string, Partial<RateLimitPolicy>>,\n ): void {\n this.options.policies = mergeRateLimitPolicies(\n {\n policies: mergePolicyOverrides(\n this.options.basePolicyOverrides ?? {},\n overrides,\n ),\n },\n this.options.logger,\n );\n }\n\n /**\n * Consume one request against a named policy.\n *\n * @param policyName - Policy key (for example `mcp-app-share.rpc`).\n * @param values - Dimension values for this request.\n * @returns The decision; `allowed:false` means the caller should return 429.\n */\n async consume(\n policyName: string,\n values: Partial<Record<RateLimitDimension, string | undefined>>,\n ): Promise<RateLimitDecision> {\n if (!this.options.enabled) {\n return {\n allowed: true,\n policy: policyName,\n limit: 0,\n remaining: 0,\n retryAfterSeconds: 0,\n };\n }\n const policy = this.options.policies[policyName];\n if (!policy && !this.warnedPolicies.has(policyName)) {\n this.warnedPolicies.add(policyName);\n this.options.logger?.warn(JSON.stringify({\n event: \"rate-limit:unknown-policy\",\n policy: policyName,\n }));\n }\n if (!policy || policy.enabled === false) {\n return {\n allowed: true,\n policy: policyName,\n limit: policy?.limit ?? 0,\n remaining: policy?.limit ?? 0,\n retryAfterSeconds: 0,\n };\n }\n\n let remaining = policy.limit;\n for (const dimension of policy.dimensions) {\n const raw = values[dimension];\n if (typeof raw !== \"string\" || raw === \"\") continue;\n const key = `axiom:rate-limit:${policyName}:${dimension}:${hashDimension(raw)}`;\n const result = await this.options.store.increment(key, policy.windowMs);\n if (!result) continue;\n const dimensionLimit = policy.dimensionLimits?.[dimension] ?? policy.limit;\n remaining = Math.min(remaining, dimensionLimit - result.count);\n if (result.count > dimensionLimit) {\n return {\n allowed: false,\n policy: policyName,\n limit: dimensionLimit,\n remaining: 0,\n retryAfterSeconds: Math.max(1, Math.ceil(result.ttlMs / 1000)),\n exceededDimension: dimension,\n };\n }\n }\n\n return {\n allowed: true,\n policy: policyName,\n limit: policy.limit,\n remaining: Math.max(0, remaining),\n retryAfterSeconds: 0,\n };\n }\n}\n\n/**\n * Build the gateway rate-limit service from config + environment.\n */\nexport function createRateLimitService(\n config: RateLimitConfig = {},\n logger: Pick<Console, \"warn\"> = console,\n): RateLimitService {\n const redisUrl = config.redisUrl ?? process.env.REDIS_URL?.trim();\n const redis = redisUrl\n ? new RedisRateLimitStore({\n url: redisUrl,\n onError: (error) => logger.warn(JSON.stringify({\n event: \"rate-limit:redis-unavailable\",\n error: error instanceof Error ? error.message : String(error),\n })),\n })\n : undefined;\n return new RateLimitService({\n store: new HybridRateLimitStore(redis, {\n maxEntries: config.maxInMemoryEntries ?? DEFAULT_MAX_ENTRIES,\n }),\n policies: mergeRateLimitPolicies(config, logger),\n basePolicyOverrides: config.policies ?? {},\n enabled:\n config.enabled\n ?? process.env.AXIOM_RATE_LIMIT_DISABLED !== \"true\",\n logger,\n });\n}\n\nconst disabledService = new RateLimitService({\n store: new HybridRateLimitStore(undefined),\n policies: {},\n enabled: false,\n});\nlet defaultService: RateLimitService | undefined;\n\n/** Install the gateway-wide rate-limit service (called by gateway startup). */\nexport function setRateLimitService(service: RateLimitService): void {\n defaultService = service;\n}\n\n/** Reset to the disabled default; intended for tests. */\nexport function resetRateLimitService(): void {\n defaultService = undefined;\n}\n\n/**\n * Read the gateway-wide rate-limit service.\n *\n * Direct route unit tests (which do not start the gateway) get a disabled\n * service; production startup installs the configured one via\n * {@link setRateLimitService}.\n */\nexport function getRateLimitService(): RateLimitService {\n return defaultService ?? disabledService;\n}\n","import type { RateLimitPolicy } from \"./types\";\n\n/**\n * Gateway default rate-limit policies.\n *\n * Routes opt in explicitly; this table is not a blanket limit for every API.\n * Deployment overrides merge over these values (see `RateLimitConfig`).\n */\nexport const DEFAULT_RATE_LIMIT_POLICIES: Record<string, RateLimitPolicy> = {\n \"mcp-app-share.rpc\": {\n limit: 600,\n windowMs: 60_000,\n dimensions: [\"token\", \"ip\"],\n dimensionLimits: { ip: 60 },\n },\n \"mcp-app-share.unlock\": {\n limit: 5,\n windowMs: 60_000,\n dimensions: [\"token\", \"ip\"],\n },\n \"mcp-app-share.bootstrap\": {\n limit: 300,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"auth.login\": {\n limit: 10,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"auth.oidc.begin\": {\n limit: 60,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"auth.oidc.callback\": {\n limit: 30,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"auth.oidc.exchange\": {\n limit: 60,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"invite.link.resolve\": {\n limit: 60,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"invite.link.claim\": {\n limit: 30,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"branding.read\": {\n limit: 300,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"auth.oidc.email_completion\": {\n limit: 30,\n windowMs: 60_000,\n dimensions: [\"ip\"],\n },\n \"oidc-provider.test\": {\n limit: 20,\n windowMs: 60_000,\n dimensions: [\"user\", \"ip\"],\n },\n \"open.api\": {\n limit: 60_000,\n windowMs: 60_000,\n dimensions: [\"credential\"],\n },\n \"open.agent\": {\n limit: 2_000,\n windowMs: 60_000,\n dimensions: [\"credential\"],\n },\n \"a2a.jsonrpc\": {\n limit: 600,\n windowMs: 60_000,\n dimensions: [\"key\", \"ip\"],\n dimensionLimits: { ip: 300 },\n },\n};\n\n/**\n * Legacy Open rate-limit env as a fallback layer.\n *\n * `OPEN_RATE_LIMIT_PER_MINUTE` / `OPEN_AGENT_RATE_LIMIT_PER_MINUTE` predate the\n * gateway policy table. They sit below `AXIOM_RATE_LIMIT_POLICIES` and host\n * overrides so new configuration can supersede them.\n */\nexport function legacyOpenRateLimitPolicies(\n env: NodeJS.ProcessEnv = process.env,\n): Record<string, Partial<RateLimitPolicy>> {\n const open = Number(env.OPEN_RATE_LIMIT_PER_MINUTE);\n const agent = Number(env.OPEN_AGENT_RATE_LIMIT_PER_MINUTE);\n return {\n ...(Number.isFinite(open) && open > 0 ? { \"open.api\": { limit: open } } : {}),\n ...(Number.isFinite(agent) && agent > 0\n ? { \"open.agent\": { limit: agent } }\n : {}),\n };\n}\n","import { createClient } from \"redis\";\n\nexport interface SharedRedis {\n readonly isOpen: boolean;\n connect(): Promise<void>;\n on(event: \"error\", listener: (error: unknown) => void): unknown;\n set(key: string, value: string, options: { NX: true; PX: number }): Promise<string | null>;\n incr(key: string): Promise<number>;\n pExpire(key: string, ms: number): Promise<boolean>;\n eval(script: string, options: { keys: string[]; arguments: string[] }): Promise<unknown>;\n}\n\nconst clientPromises = new Map<string, Promise<SharedRedis>>();\n\n/** Lazily connect a shared Redis client; defaults to `REDIS_URL`. */\nexport async function getSharedRedisClient(\n url: string | undefined = process.env.REDIS_URL?.trim(),\n): Promise<SharedRedis | undefined> {\n if (!url) return undefined;\n let clientPromise = clientPromises.get(url);\n if (!clientPromise) {\n clientPromise = (async () => {\n const client = createClient({ url }) as unknown as SharedRedis;\n client.on(\"error\", () => undefined);\n await client.connect();\n return client;\n })();\n clientPromises.set(url, clientPromise);\n }\n try {\n const client = await clientPromise;\n return client.isOpen ? client : undefined;\n } catch {\n if (clientPromises.get(url) === clientPromise) clientPromises.delete(url);\n return undefined;\n }\n}\n\n/** Atomically claim a dedup key. `undefined` means Redis is unavailable. */\nexport async function claimDedupKey(key: string, ttlMs: number): Promise<boolean | undefined> {\n try {\n const client = await getSharedRedisClient();\n if (!client) return undefined;\n const result = await client.set(key, \"1\", { NX: true, PX: ttlMs });\n return result === \"OK\";\n } catch {\n return undefined;\n }\n}\n\n/** Atomically increment a rate counter. `undefined` means Redis is unavailable. */\nexport async function incrementRateCounter(key: string, windowMs: number): Promise<number | undefined> {\n try {\n const client = await getSharedRedisClient();\n if (!client) return undefined;\n const count = await client.incr(key);\n if (count === 1) await client.pExpire(key, windowMs);\n return count;\n } catch {\n return undefined;\n }\n}\n","import { getSharedRedisClient } from \"../../router/middlewares/channelSharedState\";\nimport type { RateLimitStore } from \"./types\";\n\n/** Fixed-window counter incremented atomically in Redis. */\nconst INCREMENT_SCRIPT = `\nlocal count = redis.call(\"INCR\", KEYS[1])\nlocal ttl = redis.call(\"PTTL\", KEYS[1])\nif count == 1 or ttl < 0 then\n redis.call(\"PEXPIRE\", KEYS[1], ARGV[1])\n ttl = tonumber(ARGV[1])\nend\nreturn {count, ttl}\n`;\n\n/**\n * Process-local fixed-window counters.\n *\n * Used directly in single-process deployments and as the fallback when Redis\n * is unavailable. Entries expire lazily and the map is bounded.\n */\nexport class InMemoryRateLimitStore implements RateLimitStore {\n private readonly counters = new Map<string, { count: number; resetAt: number }>();\n\n constructor(\n private readonly options: {\n maxEntries?: number;\n now?: () => number;\n } = {},\n ) {}\n\n async increment(\n key: string,\n windowMs: number,\n ): Promise<{ count: number; ttlMs: number }> {\n const now = this.options.now?.() ?? Date.now();\n const current = this.counters.get(key);\n const next =\n !current || now >= current.resetAt\n ? { count: 1, resetAt: now + windowMs }\n : { count: current.count + 1, resetAt: current.resetAt };\n this.counters.set(key, next);\n this.evict(now);\n return {\n count: next.count,\n ttlMs: Math.max(0, next.resetAt - now),\n };\n }\n\n /** Drop expired entries, then the oldest entries if still over the cap. */\n private evict(now: number): void {\n const max = this.options.maxEntries ?? 10_000;\n if (this.counters.size <= max) return;\n for (const [key, counter] of this.counters) {\n if (counter.resetAt <= now) this.counters.delete(key);\n }\n if (this.counters.size <= max) return;\n // Insertion order approximates reset order for fixed windows; avoid an\n // O(n log n) sort on a hot path under attack.\n let excess = this.counters.size - max;\n for (const key of this.counters.keys()) {\n if (excess <= 0) break;\n this.counters.delete(key);\n excess -= 1;\n }\n }\n}\n\n/** Redis-backed fixed-window counters. Returns `undefined` on any failure. */\nexport class RedisRateLimitStore implements RateLimitStore {\n constructor(\n private readonly options: {\n url?: string;\n onError?: (error: unknown) => void;\n } = {},\n ) {}\n\n async increment(\n key: string,\n windowMs: number,\n ): Promise<{ count: number; ttlMs: number } | undefined> {\n try {\n const client = await getSharedRedisClient(this.options.url);\n if (!client) return undefined;\n const result = await client.eval(INCREMENT_SCRIPT, {\n keys: [key],\n arguments: [String(windowMs)],\n });\n if (!Array.isArray(result) || result.length < 2) return undefined;\n const count = Number(result[0]);\n const ttlMs = Number(result[1]);\n if (!Number.isFinite(count) || count < 1) return undefined;\n return {\n count,\n ttlMs: Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : windowMs,\n };\n } catch (error) {\n this.options.onError?.(error);\n return undefined;\n }\n }\n}\n\n/**\n * Use Redis when available, otherwise fall back to the process-local store.\n *\n * A Redis failure is treated as \"unavailable\", not \"unlimited\": the request is\n * still counted in memory.\n */\nexport class HybridRateLimitStore implements RateLimitStore {\n private readonly memory: InMemoryRateLimitStore;\n\n constructor(\n private readonly redis: RateLimitStore | undefined,\n options: { maxEntries?: number; now?: () => number } = {},\n ) {\n this.memory = new InMemoryRateLimitStore(options);\n }\n\n async increment(\n key: string,\n windowMs: number,\n ): Promise<{ count: number; ttlMs: number }> {\n if (this.redis) {\n const shared = await this.redis.increment(key, windowMs);\n if (shared) return shared;\n }\n return this.memory.increment(key, windowMs);\n }\n}\n"],"mappings":"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","names":[]}
@@ -8,7 +8,10 @@ function getRunCancelRouter() {
8
8
  }
9
9
 
10
10
  // src/services/run-task/runTaskQuota.ts
11
- function resolveRunTaskMaxActivePerTenant(env = process.env) {
11
+ function resolveRunTaskMaxActivePerTenant(env = process.env, settings) {
12
+ const platform = settings?.getRateLimits().maxActiveRunTasksPerTenant;
13
+ if (platform === null) return void 0;
14
+ if (platform !== void 0) return platform;
12
15
  return resolvePositiveInteger(env.RUN_TASK_MAX_ACTIVE_PER_TENANT);
13
16
  }
14
17
  function resolveRunTaskMaxRunningPerTenant(env = process.env) {
@@ -52,6 +55,15 @@ async function wakeRunTaskQueue() {
52
55
  }
53
56
  }
54
57
 
58
+ // src/services/settings/settingsRuntime.ts
59
+ var current2;
60
+ function setPlatformSettingsProvider(provider) {
61
+ current2 = provider;
62
+ }
63
+ function getPlatformSettingsProvider() {
64
+ return current2;
65
+ }
66
+
55
67
  // src/services/workspace-file-service.ts
56
68
  import * as fs2 from "fs/promises";
57
69
  import * as path2 from "path";
@@ -239,6 +251,8 @@ export {
239
251
  setRunTaskWakeStatsSource,
240
252
  getRunTaskWakeStatsSource,
241
253
  wakeRunTaskQueue,
254
+ setPlatformSettingsProvider,
255
+ getPlatformSettingsProvider,
242
256
  PathOutsideRootError,
243
257
  isPathWithinRoot,
244
258
  resolvePathWithinRoot,
@@ -248,4 +262,4 @@ export {
248
262
  saveProjectFile,
249
263
  formatFileRefsSection
250
264
  };
251
- //# sourceMappingURL=chunk-PHWGCRW2.mjs.map
265
+ //# sourceMappingURL=chunk-67CDUQM3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/run-cancel/runCancelRuntime.ts","../src/services/run-task/runTaskQuota.ts","../src/services/run-task/runTaskRuntimeRegistry.ts","../src/services/settings/settingsRuntime.ts","../src/services/workspace-file-service.ts","../src/utils/path-containment.ts","../src/services/files/task-file-refs.ts"],"sourcesContent":["import type { RunCancelRouter } from \"./RunCancelRouter\";\n\nlet runCancelRouter: RunCancelRouter | undefined;\n\n/**\n * Register the process-wide run cancellation router.\n *\n * @param router - Router available to HTTP controllers, or `undefined` to\n * clear it during shutdown.\n *\n * @remarks\n * The router is only constructed when the gateway uses the Redis chunk source,\n * a Redis URL, and a durable run ledger. Controllers treat a missing router as\n * cancellation being unavailable rather than aborting locally.\n */\nexport function setRunCancelRouter(router?: RunCancelRouter): void {\n runCancelRouter = router;\n}\n\n/**\n * Return the registered run cancellation router, if any.\n *\n * @returns The process-wide router, or `undefined` when cancellation routing\n * is not configured for this replica.\n */\nexport function getRunCancelRouter(): RunCancelRouter | undefined {\n return runCancelRouter;\n}\n\n/**\n * Clear the process-wide run cancellation router between tests.\n *\n * @remarks Test-only helper; production code clears the router through\n * {@link setRunCancelRouter} during shutdown.\n */\nexport function resetRunCancelRouterForTests(): void {\n runCancelRouter = undefined;\n}\n","import type { PlatformSettingsProvider } from \"@axiom-lattice/protocols\";\n\n/**\n * Resolves the optional per-tenant run-queue admission limit.\n *\n * @param env Environment source; defaults to `process.env`.\n * @param settings Optional platform settings provider; its cached snapshot\n * takes precedence over the environment.\n * @returns The configured positive safe integer, or `undefined` when unlimited.\n * @remarks Unset, blank, zero, negative, fractional and unsafe values all mean\n * unlimited. An explicit platform `null` means unlimited and overrides the\n * environment. This is admission control (pending + running `run_api` rows per\n * tenant); it is not fair scheduling or per-tenant execution concurrency.\n */\nexport function resolveRunTaskMaxActivePerTenant(\n env: NodeJS.ProcessEnv = process.env,\n settings?: Pick<PlatformSettingsProvider, \"getRateLimits\">,\n): number | undefined {\n const platform = settings?.getRateLimits().maxActiveRunTasksPerTenant;\n if (platform === null) return undefined;\n if (platform !== undefined) return platform;\n return resolvePositiveInteger(env.RUN_TASK_MAX_ACTIVE_PER_TENANT);\n}\n\n/**\n * Resolves the optional per-tenant execution concurrency cap.\n *\n * @param env Environment source; defaults to `process.env`.\n * @returns The configured positive safe integer, or `undefined` when unlimited.\n * @remarks Unset, blank, zero, negative, fractional and unsafe values all mean\n * unlimited. This caps `running` `run_api` rows per tenant; it does not admit\n * or reject new work (that is {@link resolveRunTaskMaxActivePerTenant}) and it\n * is not strict round-robin fairness across tenants.\n */\nexport function resolveRunTaskMaxRunningPerTenant(\n env: NodeJS.ProcessEnv = process.env,\n): number | undefined {\n return resolvePositiveInteger(env.RUN_TASK_MAX_RUNNING_PER_TENANT);\n}\n\nfunction resolvePositiveInteger(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined;\n const parsed = Number(raw);\n return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n","import type {\n RunTaskWakePublisher,\n RunTaskWakeStats,\n} from \"./RedisRunTaskWake\";\nimport type { RunTaskRuntime } from \"./RunTaskRuntime\";\n\n/** Structural source of wake transport posture for process metrics. */\nexport interface RunTaskWakeStatsSource {\n /** Returns a point-in-time wake posture snapshot. */\n stats(): RunTaskWakeStats;\n}\n\nlet current: RunTaskRuntime | undefined;\nlet publisher: RunTaskWakePublisher | undefined;\nlet wakeStatsSource: RunTaskWakeStatsSource | undefined;\n\n/**\n * Registers the process-local run task runtime.\n *\n * @param runtime The running runtime, or `undefined` on shutdown.\n * @remarks The registry is intentionally process-local: it can only wake the\n * worker in this process. Other replicas still discover pending runs through DB\n * polling, which remains authoritative.\n */\nexport function setRunTaskRuntime(runtime?: RunTaskRuntime): void {\n current = runtime;\n}\n\n/**\n * Returns the process-local run task runtime, when this replica runs one.\n *\n * @returns The registered runtime, or `undefined`.\n */\nexport function getRunTaskRuntime(): RunTaskRuntime | undefined {\n return current;\n}\n\n/**\n * Registers the best-effort cross-replica wake publisher.\n *\n * @param next The Redis-backed publisher, or `undefined` on shutdown.\n * @remarks API-only replicas may register a publisher without subscribing. A\n * missing publisher leaves the process-local wake (and DB polling) intact.\n */\nexport function setRunTaskWakePublisher(\n next?: RunTaskWakePublisher,\n): void {\n publisher = next;\n}\n\n/**\n * Registers the process-local wake transport used for metrics.\n *\n * @param next The wake transport, or `undefined` on shutdown.\n * @remarks Kept separate from the publish registry so API-only replicas can\n * expose publisher connection posture even without a local worker.\n */\nexport function setRunTaskWakeStatsSource(\n next?: RunTaskWakeStatsSource,\n): void {\n wakeStatsSource = next;\n}\n\n/**\n * Returns the process-local wake transport registered for metrics.\n *\n * @returns The wake stats source, or `undefined` when no Redis wake exists.\n */\nexport function getRunTaskWakeStatsSource(): RunTaskWakeStatsSource | undefined {\n return wakeStatsSource;\n}\n\n/**\n * Best-effort wake for newly created pending runs.\n *\n * @returns A promise that resolves after the local wake attempt; the Redis\n * publish continues in the background.\n * @remarks Never authoritative and never throws. The local runtime is woken\n * first so a hung or failed publisher cannot suppress it. The cross-replica\n * publish is fire-and-forget and wakes other worker replicas; this replica does\n * not depend on round-tripping its own frame. A missing runtime, missing\n * publisher, or transport failure leaves the durable run to polling.\n */\nexport async function wakeRunTaskQueue(): Promise<void> {\n try {\n await current?.wake();\n } catch {\n // Polling is the durable fallback; wake failures are best-effort.\n }\n const next = publisher;\n if (!next) return;\n try {\n void next.publish().catch(() => undefined);\n } catch {\n // Polling is the durable fallback; wake failures are best-effort.\n }\n}\n","import type { PlatformSettingsProvider } from \"@axiom-lattice/protocols\";\n\n/**\n * Composition-root provider used by legacy gateway route modules.\n *\n * New code should accept an injected {@link PlatformSettingsProvider}; this\n * process-local accessor only bridges modules that are not yet wired through\n * dependency injection.\n */\nlet current: PlatformSettingsProvider | undefined;\n\n/** Installs (or clears) the process-local platform settings provider. */\nexport function setPlatformSettingsProvider(\n provider: PlatformSettingsProvider | undefined,\n): void {\n current = provider;\n}\n\n/** Returns the process-local provider, or undefined before composition. */\nexport function getPlatformSettingsProvider():\n | PlatformSettingsProvider\n | undefined {\n return current;\n}\n","import * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport { getStoreLattice, getSandBoxManager } from \"@axiom-lattice/core\";\nimport { isPathWithinRoot } from \"../utils/path-containment\";\n\n/** Binary file extensions handled as raw bytes rather than UTF-8 text. */\nconst BINARY_EXTENSIONS = new Set([\n \"pdf\", \"xlsx\", \"xls\", \"docx\", \"doc\", \"pptx\", \"ppt\",\n \"png\", \"jpg\", \"jpeg\", \"gif\", \"bmp\", \"ico\", \"webp\",\n \"mp3\", \"mp4\", \"webm\", \"ogg\", \"wav\", \"avi\", \"mov\",\n \"m4v\", \"mkv\", \"3gp\", \"m4a\", \"aac\", \"flac\", \"opus\", \"oga\", \"wma\", \"aiff\",\n \"zip\", \"tar\", \"gz\", \"rar\", \"7z\",\n]);\n\n/**\n * Whether a filename denotes a binary (non-UTF-8) payload, used to choose the\n * raw-bytes write path in sandbox volume backends.\n */\nexport function isBinaryContentType(filename: string): boolean {\n const ext = filename.split(\".\").pop()?.toLowerCase() || \"\";\n return BINARY_EXTENSIONS.has(ext);\n}\n\n/**\n * Filesystem root directory for a project, mirroring the layout used by the\n * workspace file backend (`/lattice_store/tenants/:tenant/workspaces/:ws/:project`).\n */\nexport function filesystemRootDirectory(\n tenantId: string,\n workspaceId: string,\n projectId: string,\n): string {\n return `/lattice_store/tenants/${tenantId}/workspaces/${workspaceId}/${projectId}`;\n}\n\n/** Raised when a file write request is rejected for path/name safety reasons. */\nexport class ProjectFileValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ProjectFileValidationError\";\n }\n}\n\n/** Parameters for writing a file into a project's workspace storage. */\nexport interface SaveProjectFileParams {\n tenantId: string;\n projectId: string;\n /**\n * Pre-resolved workspace id. When omitted it is derived from the project via\n * the project store (needed by callers that only know the project, e.g. A2A).\n */\n workspaceId?: string;\n name: string;\n bytes: Uint8Array;\n mimeType?: string;\n /** Relative subdirectory under the project root; sandbox defaults to `/project/uploads`. */\n path?: string;\n assistantId?: string;\n}\n\n/** Result of a successful project-file write. */\nexport interface SavedProjectFile {\n /** Storage path usable as a `TaskFileRef.uri` (e.g. `/project/uploads/a.pdf`). */\n uri: string;\n name: string;\n size: number;\n}\n\n/**\n * Persist file bytes into a project's workspace storage and return a URI to it.\n *\n * Supports both sandbox-backed workspaces (delegating to the sandbox/volume\n * upload API) and filesystem-backed workspaces (writing under the project root).\n * The returned URI is the storage path agents reference via {@link TaskFileRef}.\n */\nexport async function saveProjectFile(\n params: SaveProjectFileParams,\n): Promise<SavedProjectFile> {\n const workspaceStore = getStoreLattice(\"default\", \"workspace\").store;\n const projectStore = getStoreLattice(\"default\", \"project\").store;\n\n let workspaceId = params.workspaceId;\n if (!workspaceId) {\n const project = await projectStore.getProjectById(\n params.tenantId,\n params.projectId,\n );\n if (!project) {\n throw new Error(\"Project not found\");\n }\n workspaceId = project.workspaceId;\n }\n if (!workspaceId) throw new Error(\"Workspace not found\");\n\n const workspace = await workspaceStore.getWorkspaceById(\n params.tenantId,\n workspaceId,\n );\n if (!workspace) {\n throw new Error(\"Workspace not found\");\n }\n\n const buffer = Buffer.from(params.bytes);\n const filename = params.name || \"file\";\n const pathValue = params.path;\n\n // Validate the untrusted filename before any path join or disk I/O, equally\n // for sandbox and filesystem branches: reject anything that is not a bare\n // basename on either path flavor (blocks \"../../etc/passwd\", \"a\\\\b\", \"..\").\n if (\n path.basename(filename) !== filename ||\n path.posix.basename(filename) !== filename ||\n filename === \"..\"\n ) {\n throw new ProjectFileValidationError(\"Invalid filename\");\n }\n\n if (workspace.storageType === \"sandbox\") {\n const sandboxManager = getSandBoxManager();\n const volumeConfig = {\n assistant_id: params.assistantId || \"\",\n thread_id: \"\",\n tenantId: params.tenantId,\n workspaceId,\n projectId: params.projectId,\n };\n\n const relativePath = pathValue\n ? path.posix.join(pathValue, filename)\n : path.posix.join(\"/project/uploads\", filename);\n\n const isBinary = isBinaryContentType(filename);\n const volumeBackend = await sandboxManager.getVolumeBackendForPath(\n volumeConfig,\n relativePath,\n );\n if (volumeBackend && !isBinary) {\n await volumeBackend.write(relativePath, buffer.toString(\"utf-8\"));\n } else if (volumeBackend && volumeBackend.writeBinary) {\n await volumeBackend.writeBinary(relativePath, buffer);\n } else {\n const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);\n await sandbox.file.uploadFile({ file: relativePath, data: buffer });\n }\n\n return { uri: relativePath, name: filename, size: buffer.length };\n }\n\n const rootDir = filesystemRootDirectory(\n params.tenantId,\n workspaceId,\n params.projectId,\n );\n await fs.mkdir(rootDir, { recursive: true });\n const relativeDirectory = pathValue?.replace(/^[/\\\\]+/, \"\") || \".\";\n const targetDir = path.resolve(rootDir, relativeDirectory);\n if (!isPathWithinRoot(path.resolve(rootDir), targetDir)) {\n throw new ProjectFileValidationError(\n \"Upload path must stay within the project directory\",\n );\n }\n await fs.mkdir(targetDir, { recursive: true });\n\n const [resolvedRoot, resolvedTargetDir] = await Promise.all([\n fs.realpath(rootDir),\n fs.realpath(targetDir),\n ]);\n if (!isPathWithinRoot(resolvedRoot, resolvedTargetDir)) {\n throw new ProjectFileValidationError(\n \"Upload path must stay within the project directory\",\n );\n }\n\n const targetPath = path.join(resolvedTargetDir, filename);\n await fs.writeFile(targetPath, buffer);\n const stat = await fs.stat(targetPath);\n\n const uri = path.posix.join(\"/\", relativeDirectory, filename);\n return { uri, name: filename, size: stat.size };\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nexport class PathOutsideRootError extends Error {\n constructor(\n public readonly rootPath: string,\n public readonly targetPath: string,\n ) {\n super(`Path is outside the allowed root: ${targetPath}`);\n this.name = \"PathOutsideRootError\";\n }\n}\n\nexport function isPathWithinRoot(rootPath: string, targetPath: string): boolean {\n const relative = path.relative(rootPath, targetPath);\n return (\n relative === \"\" ||\n (relative !== \"..\" &&\n !relative.startsWith(`..${path.sep}`) &&\n !path.isAbsolute(relative))\n );\n}\n\nexport async function resolvePathWithinRoot(\n rootPath: string,\n targetPath: string,\n): Promise<{ rootPath: string; targetPath: string }> {\n const [resolvedRoot, resolvedTarget] = await Promise.all([\n fs.realpath(rootPath),\n fs.realpath(targetPath),\n ]);\n\n if (!isPathWithinRoot(resolvedRoot, resolvedTarget)) {\n throw new PathOutsideRootError(resolvedRoot, resolvedTarget);\n }\n\n return { rootPath: resolvedRoot, targetPath: resolvedTarget };\n}\n","import type { TaskFileRef } from \"@axiom-lattice/protocols\";\n\n/** Render task file references for Agent-visible message text. */\nexport function formatFileRefsSection(refs: TaskFileRef[]): string {\n const lines = refs.map((ref) => {\n const label = ref.name ?? ref.uri;\n const mime = ref.mimeType ? ` (${ref.mimeType})` : \"\";\n return `- ${label}${mime}: ${ref.uri}`;\n });\n return [\"[附件]\", ...lines].join(\"\\n\");\n}\n"],"mappings":"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","names":["current","fs","path","relative","stat"]}
@@ -577,4 +577,4 @@ export {
577
577
  resolveMcpAppDeps,
578
578
  registerMcpAppRoutes
579
579
  };
580
- //# sourceMappingURL=chunk-T2HIWZGP.mjs.map
580
+ //# sourceMappingURL=chunk-BIMO2ILH.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/routes/mcp-apps.ts","../src/services/mcp-ui/UiResourceResolver.ts","../src/services/mcp-ui/McpUiResourceCacheStore.ts","../src/services/mcp-ui/buildCsp.ts","../src/services/mcp-ui/sandboxPage.ts","../src/services/plugins/tenantPluginResolver.ts"],"sourcesContent":["/**\n * MCP App routes.\n *\n * Exposes the two thin HTTP seams the sandboxed MCP App frame needs: resolving a\n * UI resource and proxying a whitelisted JSON-RPC envelope to the owning MCP\n * server. The pure handler factory keeps the security-relevant decisions\n * (ref parsing, method allow-list, plugin tool scoping) framework-agnostic and\n * unit-testable; {@link registerMcpAppRoutes} only translates HTTP to those\n * handlers.\n */\n\nimport type { FastifyInstance, FastifyRequest } from \"fastify\";\nimport {\n parseMcpUiRef,\n type McpUiRef,\n type McpUiResourcePayload,\n type Plugin,\n type SandboxPluginManifest,\n} from \"@axiom-lattice/protocols\";\nimport {\n getSandboxPluginCatalog,\n mcpManager,\n resolvePluginConnections,\n verifyMcpUiScope,\n type SandboxPluginInvokeArgs,\n} from \"@axiom-lattice/core\";\nimport { UiResourceResolver } from \"../services/mcp-ui/UiResourceResolver\";\nimport { InMemoryMcpUiResourceCacheStore } from \"../services/mcp-ui/McpUiResourceCacheStore\";\nimport { buildCsp } from \"../services/mcp-ui/buildCsp\";\nimport { SANDBOX_HTML } from \"../services/mcp-ui/sandboxPage\";\nimport { resolveTenantPluginByType } from \"../services/plugins/tenantPluginResolver\";\nimport {\n invokePluginTool,\n PluginToolScopeError,\n} from \"../services/plugins/pluginToolExecution\";\n\nconst SANDBOX_HTML_CONTENT_TYPE = \"text/html; charset=utf-8\";\n\n// Re-exported so existing importers keep resolving the error from this module;\n// the class itself now lives in the shared plugin tool execution helper.\nexport { PluginToolScopeError };\n\n/**\n * Thrown by the plugin backend for MCP methods the plugin surface does not\n * support (Plan 2 serves UI resources and the plugin's own tool calls only).\n * {@link makeMcpAppHandlers} maps it to 403.\n */\nexport class PluginMethodNotSupportedError extends Error {\n /**\n * @param method - Unsupported JSON-RPC method.\n */\n constructor(method: string) {\n super(`plugin backend does not support ${method}`);\n this.name = \"PluginMethodNotSupportedError\";\n }\n}\n\n/**\n * Client-facing scope denial from the plugin tool path.\n *\n * `status`/`code` are stable so the MCP App host can surface a precise error\n * instead of a generic 502.\n */\nexport class McpAppScopeError extends Error {\n /**\n * @param status - HTTP status to return.\n * @param code - Stable machine-readable denial code.\n */\n constructor(\n readonly status: 403 | 409,\n readonly code:\n | \"SCOPE_INVALID\"\n | \"SCOPE_MISMATCH\"\n | \"CONNECTION_REQUIRED\"\n | \"CONNECTION_NOT_ALLOWED\",\n ) {\n super(code);\n this.name = \"McpAppScopeError\";\n }\n}\n\n/** Result envelope returned by the pure handlers. */\nexport interface McpAppHandlerResult<T = unknown> {\n status: number;\n body: T;\n}\n\n/**\n * Request-scoped run context forwarded from the console request to plugin\n * execution.\n *\n * Plugin handlers run in a project-isolated sandbox whose path layout is\n * `/project/<plugin>/...`; without the workspace/project the executor resolves a\n * tenant-level sandbox and tool calls read the wrong (empty) files.\n */\nexport interface McpAppRunContext {\n tenantId: string;\n workspaceId?: string;\n projectId?: string;\n}\n\n/** Injectable seams for scope verification/connection resolution in tests. */\nexport interface McpAppScopeDeps {\n verifyScope?: typeof verifyMcpUiScope;\n resolveConnections?: typeof resolvePluginConnections;\n}\n\n/** Log and build a scope denial. */\nfunction denyScope(\n status: 403 | 409,\n code: McpAppScopeError[\"code\"],\n detail: Record<string, unknown>,\n): McpAppScopeError {\n console.warn(JSON.stringify({ event: \"mcp-apps:scope-denied\", ...detail, code }));\n return new McpAppScopeError(status, code);\n}\n\n/** Dependencies injected into the route handlers. */\nexport interface McpAppRouteDeps {\n resolveUiResource: (tenantId: string, ref: McpUiRef) => Promise<McpUiResourcePayload>;\n callTool: (\n tenantId: string,\n ref: McpUiRef,\n name: string,\n args: Record<string, unknown>,\n context?: McpAppRunContext,\n ) => Promise<unknown>;\n readResource: (tenantId: string, ref: McpUiRef, uri: string) => Promise<unknown>;\n listTools: (tenantId: string, ref: McpUiRef) => Promise<unknown>;\n}\n\n/**\n * Arguments the plugin backend forwards to the sandbox executor.\n *\n * Aliased to core's executor contract so the shared {@link invokePluginTool}\n * seam cannot drift from the real executor.\n */\nexport type McpAppPluginInvokeArgs = SandboxPluginInvokeArgs;\n\n/** Normalized sandbox handler outcome consumed by the plugin backend. */\nexport interface McpAppPluginInvokeResult {\n ok: boolean;\n result?: unknown;\n error?: string;\n}\n\n/** Minimal tenant plugin catalog surface needed by the plugin backend. */\nexport interface McpAppPluginCatalog {\n readPluginFile: (tenantId: string, pluginType: string, relPath: string) => Promise<string>;\n /**\n * Validated (normalized) manifest for the plugin.\n *\n * This is the authoritative source for handler paths and tool caps: it is the\n * output of the manifest validator, not a fresh parse of the raw\n * `plugin.json`, so execution cannot diverge from what validation accepted.\n */\n getValidatedManifest: (tenantId: string, pluginType: string) => Promise<SandboxPluginManifest | undefined>;\n getExecutor: () => {\n invoke: (args: McpAppPluginInvokeArgs) => Promise<McpAppPluginInvokeResult>;\n };\n}\n\n/**\n * Plugin seam injected into {@link resolveMcpAppDeps}: the tenant plugin\n * resolver plus the sandbox catalog/executor. Extracted so tests can supply a\n * fake and exercise the real plugin backend without touching the module\n * singleton.\n */\nexport interface McpAppPluginBackend {\n resolvePlugin: (tenantId: string, type: string) => Promise<Plugin | undefined>;\n catalog: McpAppPluginCatalog;\n}\n\nconst ALLOWED_METHODS = new Set([\"tools/call\", \"tools/list\", \"resources/read\"]);\n\n/** Narrow an unknown value to a plain data record, otherwise `undefined`. */\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\n/**\n * Build pure, framework-agnostic handlers for the MCP app routes.\n *\n * @param deps Backend proxies.\n * @returns `resource` and `rpc` handlers.\n */\nexport function makeMcpAppHandlers(deps: McpAppRouteDeps) {\n return {\n /**\n * Resolve the `mcp_app` UI resource for a parsed ref.\n *\n * @param tenantId Tenant scope.\n * @param rawRef Untrusted ref payload.\n * @returns The resource payload plus a ready-to-use `cspPolicy` string\n * ({@link buildCsp}, always non-empty), or a 400/502 error envelope.\n */\n async resource(tenantId: string, rawRef: unknown): Promise<McpAppHandlerResult> {\n const ref = parseMcpUiRef(rawRef);\n if (!ref) return { status: 400, body: { error: \"INVALID_REF\" } };\n if (ref.kind === \"plugin\" && ref.pluginType === \"\") {\n return { status: 400, body: { error: \"INVALID_REF\" } };\n }\n try {\n const payload = await deps.resolveUiResource(tenantId, ref);\n return { status: 200, body: { ...payload, cspPolicy: buildCsp(payload.csp) } };\n } catch (error) {\n console.warn(JSON.stringify({\n event: \"mcp-apps:resource-failed\",\n tenantId,\n kind: ref.kind,\n error: error instanceof Error ? error.message : String(error),\n }));\n return { status: 502, body: { error: \"RESOURCE_UNAVAILABLE\" } };\n }\n },\n\n /**\n * Proxy an allow-listed JSON-RPC method for a parsed ref.\n *\n * @param tenantId Tenant scope.\n * @param rawRef Untrusted ref payload.\n * @param method JSON-RPC method name.\n * @param params JSON-RPC params.\n * @param context Request-scoped run context forwarded to `tools/call`.\n * @returns The backend result, or a 400/403/502 error envelope.\n */\n async rpc(\n tenantId: string,\n rawRef: unknown,\n method: string,\n params: Record<string, unknown>,\n context?: McpAppRunContext,\n ): Promise<McpAppHandlerResult> {\n const ref = parseMcpUiRef(rawRef);\n if (!ref) return { status: 400, body: { error: \"INVALID_REF\" } };\n if (!ALLOWED_METHODS.has(method)) return { status: 403, body: { error: \"METHOD_NOT_ALLOWED\" } };\n try {\n if (method === \"tools/call\") {\n const name = typeof params.name === \"string\" ? params.name : \"\";\n if (name === \"\") return { status: 400, body: { error: \"INVALID_PARAMS\" } };\n if (\"pluginType\" in ref && ref.pluginType === \"\") {\n return { status: 400, body: { error: \"INVALID_REF\" } };\n }\n return {\n status: 200,\n body: await deps.callTool(tenantId, ref, name, asRecord(params.arguments) ?? {}, context),\n };\n }\n if (method === \"resources/read\") {\n const uri = typeof params.uri === \"string\" ? params.uri : \"\";\n if (uri === \"\") return { status: 400, body: { error: \"INVALID_PARAMS\" } };\n return { status: 200, body: await deps.readResource(tenantId, ref, uri) };\n }\n return { status: 200, body: { tools: await deps.listTools(tenantId, ref) } };\n } catch (error) {\n if (error instanceof McpAppScopeError) {\n return { status: error.status, body: { error: error.code } };\n }\n if (error instanceof PluginToolScopeError) {\n return { status: 403, body: { error: \"PLUGIN_TOOL_SCOPE\" } };\n }\n if (error instanceof PluginMethodNotSupportedError) {\n return { status: 403, body: { error: \"PLUGIN_METHOD_UNSUPPORTED\" } };\n }\n const context: Record<string, unknown> = { tenantId, kind: ref.kind, method };\n if (method === \"resources/read\") context.uri = typeof params.uri === \"string\" ? params.uri : \"\";\n if (method === \"tools/call\") context.name = typeof params.name === \"string\" ? params.name : \"\";\n console.warn(JSON.stringify({\n event: \"mcp-apps:rpc-failed\",\n ...context,\n error: error instanceof Error ? error.message : String(error),\n }));\n return { status: 502, body: { error: \"RPC_FAILED\" } };\n }\n },\n };\n}\n\n/**\n * Read a plugin UI resource and enrich it with the declaring tool's\n * CSP/permissions/border preferences.\n *\n * Two backends serve plugin UIs:\n * - **Global plugins** declare inline `meta.uiResources[resource]` HTML, keyed\n * by the `ui://` resource itself (the same key a tool's `ui.resource` points\n * at). That HTML is returned directly with no sandbox read and defaults to\n * `text/html;profile=mcp-app`.\n * - **Tenant sandbox plugins** declare a relative `ui/<tool>.html` path; the\n * file is read from the tenant volume and served as `text/html`.\n *\n * In both cases the resource must be declared by a tool of the plugin, so an\n * arbitrary `uiResources` key or volume path cannot be fetched.\n *\n * @param backend Plugin resolver + sandbox catalog seam.\n * @param tenantId Tenant scope.\n * @param pluginType Plugin type addressed by the ref.\n * @param resource Resource key: an inline `ui://` key for global plugins, or a\n * safe relative `.html` path for tenant plugins.\n * @returns The UI resource payload.\n * @throws When the plugin, declaring tool, or file is missing. A resource not\n * declared by any tool is rejected before the catalog is asked to read it.\n *\n * @remarks\n * Tenant plugin files live under the tenant-level configured agents root/plugins, so\n * unlike tool execution this path needs no workspace/project context.\n */\nexport async function fetchPluginUiResource(\n backend: McpAppPluginBackend,\n tenantId: string,\n pluginType: string,\n resource: string,\n): Promise<McpUiResourcePayload> {\n const plugin = await backend.resolvePlugin(tenantId, pluginType);\n if (!plugin) throw new Error(`plugin \"${pluginType}\" not found`);\n const tool = (plugin.meta.tools ?? []).find((t) => t.ui?.resource === resource);\n if (!tool?.ui) throw new Error(`plugin \"${pluginType}\" has no tool declaring ui resource \"${resource}\"`);\n\n const inline = plugin.meta.uiResources?.[resource];\n if (inline) {\n return {\n mimeType: inline.mimeType ?? \"text/html;profile=mcp-app\",\n html: inline.html,\n csp: inline.csp ?? tool.ui.csp,\n permissions: tool.ui.permissions,\n prefersBorder: tool.ui.prefersBorder,\n };\n }\n\n const html = await backend.catalog.readPluginFile(tenantId, pluginType, resource);\n return {\n mimeType: \"text/html\",\n html,\n csp: tool.ui.csp,\n permissions: tool.ui.permissions,\n prefersBorder: tool.ui.prefersBorder,\n };\n}\n\n/**\n * Execute one of a plugin's own tools through the injected sandbox executor.\n *\n * A scope-bearing ref is verified, its connection key is resolved against the\n * tenant Connection Store, and the connection is injected. A scope-less legacy\n * ref keeps the request-context path; a connection-declaring plugin without a\n * scope fails with `CONNECTION_REQUIRED`.\n *\n * @param backend - Plugin resolver + sandbox catalog seam.\n * @param tenantId - Tenant scope.\n * @param ref - Parsed plugin ref.\n * @param name - Tool name requested by the app.\n * @param args - Tool arguments.\n * @param context - Legacy request context for scope-less refs.\n * @param deps - Injectable scope/connection seams for tests.\n * @returns The handler result.\n * @throws McpAppScopeError when the scope is invalid/mismatched or its\n * connection key is disallowed/missing.\n */\nexport async function callPluginTool(\n backend: McpAppPluginBackend,\n tenantId: string,\n ref: Extract<McpUiRef, { kind: \"plugin\" }>,\n name: string,\n args: Record<string, unknown>,\n context?: McpAppRunContext,\n deps?: McpAppScopeDeps,\n): Promise<unknown> {\n const scope = ref.scope ? (deps?.verifyScope ?? verifyMcpUiScope)(ref.scope) : null;\n if (ref.scope && !scope) {\n throw denyScope(403, \"SCOPE_INVALID\", {\n tenantId,\n pluginType: ref.pluginType,\n tool: name,\n });\n }\n if (scope) {\n const mismatched =\n scope.tid !== tenantId ||\n scope.plugin !== ref.pluginType ||\n scope.resource !== ref.resource;\n if (mismatched) {\n throw denyScope(403, \"SCOPE_MISMATCH\", {\n tenantId,\n pluginType: ref.pluginType,\n tool: name,\n jti: scope.jti,\n threadId: scope.thid,\n });\n }\n }\n\n const manifest = await backend.catalog.getValidatedManifest(tenantId, ref.pluginType);\n const rawConnectionKey = args.connectionKey;\n const explicit =\n typeof rawConnectionKey === \"string\" ? rawConnectionKey : undefined;\n const scoped = scope?.ck;\n const connectionRelevant = Boolean(manifest?.connection) || scoped !== undefined;\n if (\n connectionRelevant &&\n (explicit !== undefined\n ? explicit !== scoped\n : rawConnectionKey !== undefined)\n ) {\n throw denyScope(403, \"CONNECTION_NOT_ALLOWED\", {\n tenantId,\n pluginType: ref.pluginType,\n tool: name,\n jti: scope?.jti,\n threadId: scope?.thid,\n });\n }\n\n let connection: Record<string, unknown> | undefined;\n let connectionKey: string | undefined;\n if (scoped !== undefined) {\n const resolveConnections = deps?.resolveConnections ?? resolvePluginConnections;\n const resolved = await resolveConnections(\n ref.pluginType,\n { connections: [scoped] },\n { tenantId },\n );\n if (resolved.length === 0) {\n throw denyScope(403, \"CONNECTION_NOT_ALLOWED\", {\n tenantId,\n pluginType: ref.pluginType,\n tool: name,\n jti: scope?.jti,\n threadId: scope?.thid,\n });\n }\n connection = resolved[0]!.config;\n connectionKey = resolved[0]!.key;\n }\n\n if (manifest?.connection && !connectionKey) {\n throw denyScope(409, \"CONNECTION_REQUIRED\", {\n tenantId,\n pluginType: ref.pluginType,\n tool: name,\n jti: scope?.jti,\n threadId: scope?.thid,\n });\n }\n\n const identity = scope\n ? {\n workspaceId: scope.wid,\n projectId: scope.pid,\n threadId: scope.thid,\n assistantId: scope.aid,\n }\n : {\n workspaceId: context?.workspaceId,\n projectId: context?.projectId,\n };\n\n return invokePluginTool(backend, tenantId, ref.pluginType, name, args, {\n ...identity,\n ...(connection === undefined ? {} : { connection }),\n });\n}\n\n/**\n * Default plugin backend: the shared tenant plugin resolver plus the\n * process-wide sandbox plugin catalog and executor.\n *\n * @returns The live plugin backend.\n */\nfunction createDefaultPluginBackend(): McpAppPluginBackend {\n return {\n resolvePlugin: (tenantId, type) => resolveTenantPluginByType(tenantId, type),\n catalog: {\n readPluginFile: (tenantId, pluginType, relPath) =>\n getSandboxPluginCatalog().readPluginFile(tenantId, pluginType, relPath),\n getValidatedManifest: (tenantId, pluginType) =>\n getSandboxPluginCatalog().getValidatedManifest(tenantId, pluginType),\n getExecutor: () => getSandboxPluginCatalog().getExecutor(),\n },\n };\n}\n\n/**\n * Process-wide UI resource resolver installed by the default route wiring.\n *\n * @remarks The plugin-catalog-change subscriber invalidates a tenant's plugin\n * UI resources through this so an edited `ui/*.html` re-resolves immediately\n * instead of being served from the 5-minute TTL cache.\n */\nlet uiResourceResolverSingleton: UiResourceResolver | undefined;\n\n/**\n * The UI resource resolver used by the live MCP app routes.\n *\n * @returns The resolver installed by {@link resolveMcpAppDeps}, or `undefined`\n * before the routes are registered.\n */\nexport function getMcpUiResourceResolver(): UiResourceResolver | undefined {\n return uiResourceResolverSingleton;\n}\n\n/**\n * Wire the handler deps to the live MCP runtime and tenant plugin backend.\n *\n * @param pluginBackend Plugin seam; defaults to the live catalog/executor.\n * @returns The concrete route dependencies.\n */\nexport function resolveMcpAppDeps(\n pluginBackend: McpAppPluginBackend = createDefaultPluginBackend(),\n): McpAppRouteDeps {\n const uiResourceResolver = new UiResourceResolver({\n store: new InMemoryMcpUiResourceCacheStore(),\n fetchMcp: (tenantId, serverKey, resourceUri) => mcpManager.readUiResource(tenantId, serverKey, resourceUri),\n fetchPlugin: (tenantId, pluginType, resource) =>\n fetchPluginUiResource(pluginBackend, tenantId, pluginType, resource),\n });\n uiResourceResolverSingleton = uiResourceResolver;\n return {\n resolveUiResource: (tenantId, ref) => uiResourceResolver.resolve(tenantId, ref),\n callTool: (tenantId, ref, name, args, context) =>\n ref.kind === \"mcp\"\n ? mcpManager.callServerTool(tenantId, ref.serverKey, name, args)\n : callPluginTool(pluginBackend, tenantId, ref, name, args, context),\n // Plugin refs reach these only after `tools/call`; the handler treats the\n // rejection as an unsupported method (403). The branches are live because\n // the pure handler deliberately delegates method policy to the deps.\n readResource: (tenantId, ref, uri) =>\n ref.kind === \"mcp\"\n ? mcpManager.readServerResource(tenantId, ref.serverKey, uri)\n : Promise.reject(new PluginMethodNotSupportedError(\"resources/read\")),\n listTools: (tenantId, ref) =>\n ref.kind === \"mcp\"\n ? mcpManager.getAvailableTools(tenantId, ref.serverKey)\n : Promise.reject(new PluginMethodNotSupportedError(\"tools/list\")),\n };\n}\n\ntype RequestWithUser = FastifyRequest & { user?: { tenantId?: string } };\n\n/** Read a header as a single value, using the first entry when repeated. */\nfunction firstHeader(request: FastifyRequest, name: string): string | undefined {\n const header = request.headers[name];\n return Array.isArray(header) ? header[0] : header;\n}\n\n/** Resolve the tenant from the console session, then the legacy header. */\nfunction tenantOf(request: FastifyRequest): string {\n const user = (request as RequestWithUser).user;\n return user?.tenantId ?? firstHeader(request, \"x-tenant-id\") ?? \"default\";\n}\n\n/**\n * Build the request-scoped run context from the console request.\n *\n * The console sends the active workspace/project on every API call\n * (`x-workspace-id`/`x-project-id`); forwarding them lets a UI-initiated\n * `tools/call` resolve the same project-isolated sandbox the agent run used.\n */\nfunction runContextOf(request: FastifyRequest): McpAppRunContext {\n return {\n tenantId: tenantOf(request),\n workspaceId: firstHeader(request, \"x-workspace-id\"),\n projectId: firstHeader(request, \"x-project-id\"),\n };\n}\n\n/**\n * Register MCP app routes on the Fastify instance.\n *\n * @param fastify Gateway Fastify instance.\n * @param deps Route dependencies; defaults to live runtime wiring.\n * @remarks Resource/rpc derive `tenantId` from the console session. The sandbox\n * bootstrap page is loaded as an iframe navigation (no `Authorization` header),\n * so `GET /mcp-apps/sandbox.html` is added to the console-auth bypass list; it\n * is a static document carrying no secrets and no identity, while the\n * `/api/mcp-apps/*` seams stay authenticated. The MCP Apps spec prefers the\n * sandbox proxy origin to differ from the console SPA origin (defense-in-depth\n * containment); single-origin deployments are supported, where the outer proxy\n * frame shares the console origin.\n */\nexport function registerMcpAppRoutes(\n fastify: FastifyInstance,\n deps: McpAppRouteDeps = resolveMcpAppDeps(),\n): void {\n const handlers = makeMcpAppHandlers(deps);\n\n fastify.get(\"/mcp-apps/sandbox.html\", async (_request, reply) => {\n return reply\n .header(\"content-type\", SANDBOX_HTML_CONTENT_TYPE)\n .header(\"x-content-type-options\", \"nosniff\")\n .header(\"cache-control\", \"no-store\")\n .send(SANDBOX_HTML);\n });\n\n fastify.post(\"/api/mcp-apps/resource\", async (request, reply) => {\n const body = asRecord(request.body) ?? {};\n const result = await handlers.resource(tenantOf(request), body.ref);\n return reply.code(result.status).send(result.body);\n });\n\n fastify.post(\"/api/mcp-apps/rpc\", async (request, reply) => {\n const body = asRecord(request.body) ?? {};\n const params = asRecord(body.params) ?? {};\n const context = runContextOf(request);\n const result = await handlers.rpc(\n context.tenantId,\n body.ref,\n String(body.method ?? \"\"),\n params,\n context,\n );\n return reply.code(result.status).send(result.body);\n });\n}\n","import { createHash } from \"node:crypto\";\nimport type { McpUiRef, McpUiResourcePayload } from \"@axiom-lattice/protocols\";\nimport type { McpUiCacheEntry, McpUiResourceCacheStore } from \"./McpUiResourceCacheStore\";\n\n/** Dependencies for {@link UiResourceResolver}. */\nexport interface UiResourceResolverDeps {\n store: McpUiResourceCacheStore;\n fetchMcp: (tenantId: string, serverKey: string, resourceUri: string) => Promise<McpUiResourcePayload>;\n fetchPlugin: (tenantId: string, pluginType: string, resource: string) => Promise<McpUiResourcePayload>;\n ttlMs?: number;\n}\n\nconst DEFAULT_TTL_MS = 5 * 60 * 1000;\n\n/** Public payload fields, excluding internal cache metadata (`hash`/`fetchedAt`/`stale`). */\ntype McpUiPublicFields = Omit<McpUiResourcePayload, \"stale\">;\n\nfunction publicFieldsOf(payload: McpUiResourcePayload): McpUiPublicFields {\n return {\n mimeType: payload.mimeType,\n html: payload.html,\n ...(payload.csp !== undefined ? { csp: payload.csp } : {}),\n ...(payload.permissions !== undefined ? { permissions: payload.permissions } : {}),\n ...(payload.prefersBorder !== undefined ? { prefersBorder: payload.prefersBorder } : {}),\n };\n}\n\n/**\n * Resolve `mcp_app` refs from either the MCP or plugin backend, with a\n * stale-while-error cache.\n *\n * @remarks Concurrent resolves for the same tenant/ref share a single backend\n * fetch (single-flight). On backend failure, a cached entry (even expired) is\n * returned with `stale: true` instead of throwing.\n */\nexport class UiResourceResolver {\n private readonly inFlight = new Map<string, Promise<McpUiResourcePayload>>();\n\n constructor(private deps: UiResourceResolverDeps) {}\n\n private keyOf(ref: McpUiRef): string {\n return ref.kind === \"mcp\"\n ? `mcp\\u0000${ref.serverKey}\\u0000${ref.resourceUri}`\n : `plugin\\u0000${ref.pluginType}\\u0000${ref.resource}`;\n }\n\n private fullKey(tenantId: string, ref: McpUiRef): string {\n return `${tenantId}\\u0000${this.keyOf(ref)}`;\n }\n\n /**\n * Resolve a UI resource for a tenant.\n *\n * @param tenantId Tenant scope.\n * @param ref The fence ref.\n * @returns Public UI resource payload (no cache metadata).\n */\n async resolve(tenantId: string, ref: McpUiRef): Promise<McpUiResourcePayload> {\n const key = this.fullKey(tenantId, ref);\n const ttl = this.deps.ttlMs ?? DEFAULT_TTL_MS;\n const cached = await this.deps.store.get(key);\n if (cached && Date.now() - cached.fetchedAt < ttl) return publicFieldsOf(cached);\n\n const existing = this.inFlight.get(key);\n if (existing) return existing;\n\n const promise = this.fetchAndCache(tenantId, ref, key, cached);\n this.inFlight.set(key, promise);\n try {\n return await promise;\n } finally {\n this.inFlight.delete(key);\n }\n }\n\n /**\n * Evict the cached entry for a tenant/ref.\n *\n * @param tenantId Tenant scope.\n * @param ref The fence ref.\n */\n async invalidate(tenantId: string, ref: McpUiRef): Promise<void> {\n await this.deps.store.delete(this.fullKey(tenantId, ref));\n }\n\n /**\n * Evict every cached plugin UI resource for a tenant.\n *\n * @param tenantId Tenant whose plugin UI resources to drop.\n * @remarks Called when a tenant's plugin catalog changes so an edited\n * `ui/*.html` is re-fetched instead of being served from the TTL cache\n * (which is keyed by ref identity, not content).\n */\n async invalidateTenantPlugins(tenantId: string): Promise<void> {\n await this.deps.store.deleteByPrefix(`${tenantId}\\u0000plugin\\u0000`);\n }\n\n private async fetchAndCache(\n tenantId: string,\n ref: McpUiRef,\n key: string,\n cached: McpUiCacheEntry | undefined,\n ): Promise<McpUiResourcePayload> {\n try {\n const fresh =\n ref.kind === \"mcp\"\n ? await this.deps.fetchMcp(tenantId, ref.serverKey, ref.resourceUri)\n : await this.deps.fetchPlugin(tenantId, ref.pluginType, ref.resource);\n const entry: McpUiCacheEntry = {\n ...publicFieldsOf(fresh),\n hash: createHash(\"sha256\").update(fresh.html).digest(\"hex\"),\n fetchedAt: Date.now(),\n };\n await this.deps.store.set(key, entry);\n return publicFieldsOf(fresh);\n } catch (error) {\n if (cached) return { ...publicFieldsOf(cached), stale: true };\n throw error;\n }\n }\n}\n","import type { McpUiResourcePayload } from \"@axiom-lattice/protocols\";\n\n/** Cache entry with provenance. */\nexport interface McpUiCacheEntry extends McpUiResourcePayload {\n hash: string;\n fetchedAt: number;\n}\n\n/** Persistence seam for resolved UI resources. */\nexport interface McpUiResourceCacheStore {\n get(key: string): Promise<McpUiCacheEntry | undefined>;\n set(key: string, entry: McpUiCacheEntry): Promise<void>;\n delete(key: string): Promise<void>;\n deleteByPrefix(prefix: string): Promise<void>;\n}\n\n/** Process-local cache implementation. */\nexport class InMemoryMcpUiResourceCacheStore implements McpUiResourceCacheStore {\n private map = new Map<string, McpUiCacheEntry>();\n\n async get(key: string): Promise<McpUiCacheEntry | undefined> {\n return this.map.get(key);\n }\n\n async set(key: string, entry: McpUiCacheEntry): Promise<void> {\n this.map.set(key, entry);\n }\n\n async delete(key: string): Promise<void> {\n this.map.delete(key);\n }\n\n async deleteByPrefix(prefix: string): Promise<void> {\n for (const key of this.map.keys()) {\n if (key.startsWith(prefix)) this.map.delete(key);\n }\n }\n}\n","import type { McpUiCsp } from \"@axiom-lattice/protocols\";\n\n// `'wasm-unsafe-eval'` permits WebAssembly compilation (e.g. CesiumJS) without\n// the broader `'unsafe-eval'`; MCP Apps' declared CSP has no wasm field, so the\n// host always grants it. Browsers without support simply ignore the token.\nconst DEFAULT_CSP = [\n \"default-src 'none'\",\n \"script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'\",\n \"style-src 'self' 'unsafe-inline'\",\n \"img-src 'self' data:\",\n \"media-src 'self' data:\",\n // Fonts are restricted to the document origin; declared `resourceDomains`\n // are the only origins ever added (no `data:` — a base64-embedded font must\n // be served from a declared origin).\n \"font-src 'self'\",\n \"object-src 'none'\",\n \"connect-src 'none'\",\n \"frame-src 'none'\",\n \"base-uri 'self'\",\n].join(\"; \");\n\n/**\n * A single safe CSP source expression: an explicit `http(s)://` / `ws(s)://`\n * origin (optionally with a leading `*.` wildcard subdomain and optionally with\n * a path). The negated class forbids whitespace and the `;`, `,`, `'`, `\"`\n * characters, and — because `*` is only allowed as the leading label via the\n * `(\\*\\.)?` group — a bare `*` or a non-leading wildcard is rejected too, so a\n * declared value cannot terminate the token or smuggle an extra directive.\n */\nconst ORIGIN_PATTERN = /^(https?|wss?):\\/\\/(\\*\\.)?[^\\s;,'\"*]+$/;\n\n/** Keep only well-formed origin strings, silently dropping junk or injections. */\nfunction declaredOrigins(domains: string[] | undefined): string[] {\n if (!Array.isArray(domains)) return [];\n return domains.filter((domain) => typeof domain === \"string\" && ORIGIN_PATTERN.test(domain));\n}\n\n/**\n * Build a CSP header value from a UI resource's declared origins.\n *\n * @param csp Declared CSP metadata (may be undefined).\n * @returns A CSP string. Never loosens beyond declared origins.\n * @remarks Omitted `csp` yields the restrictive MCP Apps default.\n */\nexport function buildCsp(csp: McpUiCsp | undefined): string {\n if (!csp) return DEFAULT_CSP;\n const resourceOrigins = declaredOrigins(csp.resourceDomains);\n const resource = resourceOrigins.join(\" \");\n // Per MCP Apps, `resourceDomains` covers scripts/styles/fonts as well as\n // images and media, so the same validated origins feed every resource\n // directive. Only declared origins are ever appended.\n const withResourceOrigins = (base: string): string => (resource ? `${base} ${resource}` : base);\n const img = [\"'self'\", \"data:\", ...resourceOrigins].join(\" \");\n const connect = declaredOrigins(csp.connectDomains);\n const frame = declaredOrigins(csp.frameDomains);\n const baseUri = declaredOrigins(csp.baseUriDomains);\n const parts = [\n \"default-src 'none'\",\n withResourceOrigins(\"script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'\"),\n withResourceOrigins(\"style-src 'self' 'unsafe-inline'\"),\n `img-src ${img}`,\n `media-src ${[\"'self'\", \"data:\", ...resourceOrigins].join(\" \")}`,\n withResourceOrigins(\"font-src 'self'\"),\n \"object-src 'none'\",\n `connect-src ${connect.length ? connect.join(\" \") : \"'none'\"}`,\n `frame-src ${frame.length ? frame.join(\" \") : \"'none'\"}`,\n `base-uri ${baseUri.length ? baseUri.join(\" \") : \"'self'\"}`,\n ];\n return parts.join(\"; \");\n}\n","/**\n * Static bootstrap document for the MCP App double-iframe sandbox proxy.\n *\n * The MCP Apps spec prefers the host and the sandbox proxy to be cross-origin\n * (defense-in-depth), so this page is served from the gateway origin; the\n * console SPA typically runs on its own origin, but single-origin deployments\n * are supported (the outer proxy then shares the console origin). It owns\n * exactly two responsibilities:\n *\n * 1. On load, announce `ui/notifications/sandbox-proxy-ready` to its parent.\n * The host then sends `ui/notifications/sandbox-resource-ready` with the\n * resource `{ html, sandbox?, csp? }`, and this page builds the nested\n * View iframe (opaque origin, `allow-scripts` by default).\n * 2. Relay JSON-RPC between host and View, skipping the control channel:\n * messages whose method starts with `ui/notifications/sandbox-` are\n * consumed here and never forwarded. Relaying is direction-aware so a View\n * response is never echoed back into the View.\n *\n * CSP delivery: the View document is loaded through `srcdoc`, which has no\n * HTTP response of its own, so a response header is impossible and a\n * parent-document `<meta>` (processed only during that document's parse) cannot\n * apply a per-resource policy. The only faithful mechanism is to splice a\n * `<meta http-equiv=\"Content-Security-Policy\">` into the resource HTML before\n * assigning `srcdoc`; the browser then enforces it against the View document.\n *\n * The host sends `params.csp` as a ready-to-use CSP header string (the gateway\n * resource route returns `cspPolicy`). If it is missing or not a string, the\n * page injects the restrictive {@link buildCsp} default rather than\n * `String()`-coercing an object into `[object Object]`.\n *\n * CONTRACT: the host MUST forward the resource's `cspPolicy` string as\n * `params.csp`; it must NOT forward the raw `csp` object from the resource\n * payload (that object is only the declared-origin input to `buildCsp`).\n *\n * Kept as a string constant (rather than a `public/` file plus a build copy\n * step) because the page is a single small document with no runtime imports.\n */\nimport { buildCsp } from \"./buildCsp\";\n\nconst DEFAULT_CSP_LITERAL = JSON.stringify(buildCsp(undefined));\n\nexport const SANDBOX_HTML = `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>MCP App Sandbox</title>\n <style>html, body { margin: 0; height: 100%; }</style>\n </head>\n <body>\n <script>\n (function () {\n var HOST_READY = \"ui/notifications/sandbox-proxy-ready\";\n var RESOURCE_READY = \"ui/notifications/sandbox-resource-ready\";\n var CONTROL_PREFIX = \"ui/notifications/sandbox-\";\n var DEFAULT_CSP = ${DEFAULT_CSP_LITERAL};\n\n function withCsp(html, csp) {\n var policy = typeof csp === \"string\" && csp.length > 0 ? csp : DEFAULT_CSP;\n var escaped = policy.replace(/&/g, \"&amp;\").replace(/\"/g, \"&quot;\");\n var meta = '<meta http-equiv=\"Content-Security-Policy\" content=\"' + escaped + '\">';\n // Insert at the very start (after an optional doctype) so the parser\n // places the meta in the implicit head. Never search for a literal\n // <head>: a resource could hide a decoy inside a comment/string and\n // slip its real head past the splice.\n var doctype = /^\\\\s*<!doctype[^>]*>/i.exec(html);\n if (doctype) {\n return html.slice(0, doctype[0].length) + meta + html.slice(doctype[0].length);\n }\n return meta + html;\n }\n\n // Size reporter injected into every View document. The host renders the\n // View inside an opaque-origin iframe it cannot measure, so auto-height\n // only happens when the View posts ui/notifications/size-changed. Most\n // hand-written plugin apps never do; this script makes every app report\n // its own content size. It only measures and postMessages — it never\n // mutates the app's DOM.\n var SIZE_REPORTER = [\n \"(function(){\",\n \"function send(){\",\n \"try{\",\n \"var d=document.documentElement,b=document.body;\",\n \"var w=Math.ceil(Math.max(d?d.scrollWidth:0,b?b.scrollWidth:0));\",\n \"var h=Math.ceil(Math.max(d?d.scrollHeight:0,b?b.scrollHeight:0));\",\n \"window.parent.postMessage({jsonrpc:'2.0',method:'ui/notifications/size-changed',params:{width:w,height:h}},'*');\",\n \"}catch(e){}\",\n \"}\",\n \"send();\",\n \"window.addEventListener('load',send);\",\n \"if(typeof ResizeObserver!=='undefined'){\",\n \"var ro=new ResizeObserver(send);\",\n \"ro.observe(document.documentElement);\",\n \"if(document.body)ro.observe(document.body);\",\n \"}else{window.addEventListener('resize',send);}\",\n \"})();\"\n ].join(\"\");\n\n function withSizeReporter(html) {\n // Split the closing tag so this template literal never contains a\n // literal script terminator that would end the sandbox page's own\n // script block during HTML parsing.\n var tag = \"<scr\" + \"ipt>\" + SIZE_REPORTER + \"<\" + \"/scr\" + \"ipt>\";\n var lower = html.toLowerCase();\n var bodyEnd = lower.lastIndexOf(\"</body>\");\n if (bodyEnd >= 0) return html.slice(0, bodyEnd) + tag + html.slice(bodyEnd);\n return html + tag;\n }\n\n var inner = null;\n\n window.parent.postMessage({ jsonrpc: \"2.0\", method: HOST_READY, params: {} }, \"*\");\n\n window.addEventListener(\"message\", function (event) {\n if (event.source !== window.parent) return;\n var data = event.data;\n if (!data || typeof data !== \"object\") return;\n\n if (data.method === RESOURCE_READY) {\n var params = data.params || {};\n if (inner && inner.parentNode) inner.parentNode.removeChild(inner);\n inner = document.createElement(\"iframe\");\n inner.setAttribute(\"sandbox\", params.sandbox || \"allow-scripts\");\n inner.setAttribute(\"srcdoc\", withCsp(withSizeReporter(String(params.html || \"\")), params.csp));\n inner.style.cssText = \"width:100%;height:100%;border:0;display:block\";\n document.body.appendChild(inner);\n return;\n }\n\n if (typeof data.method === \"string\" && data.method.indexOf(CONTROL_PREFIX) === 0) return;\n if (inner && inner.contentWindow) inner.contentWindow.postMessage(data, \"*\");\n });\n\n window.addEventListener(\"message\", function (event) {\n if (inner && event.source === inner.contentWindow) {\n var data = event.data;\n if (data && typeof data === \"object\" && typeof data.method === \"string\"\n && data.method.indexOf(CONTROL_PREFIX) === 0) return;\n window.parent.postMessage(event.data, \"*\");\n }\n });\n })();\n </script>\n </body>\n</html>\n`;\n","import { listTenantPlugins } from \"@axiom-lattice/core\";\nimport type { Plugin } from \"@axiom-lattice/protocols\";\n\n/**\n * Load and merge the global ∪ tenant plugin set for a tenant.\n *\n * Thin gateway re-export of the core {@link listTenantPlugins} primitive — the\n * single source of truth for tenant plugin resolution, shared with tenant\n * plugin agents, the architect's `list_middleware_types`, and the connection\n * routes. Kept as a module so gateway services keep one import site.\n */\nexport { listTenantPlugins };\n\n/**\n * Resolve one merged plugin by `meta.type` for a tenant.\n *\n * @param tenantId - Tenant whose plugins to search.\n * @param type - Plugin type to resolve.\n * @returns The merged plugin, or `undefined` when no source declares the type.\n */\nexport async function resolveTenantPluginByType(\n tenantId: string,\n type: string,\n): Promise<Plugin | undefined> {\n return (await listTenantPlugins(tenantId)).find((p) => p.meta.type === type);\n}\n"],"mappings":"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","names":["context"]}
@@ -1 +1 @@
1
- import{k as R}from"./embed-page-chunk-FF2HNPMW.js";import"./embed-page-chunk-5HWN5HSA.js";import"./embed-page-chunk-DI5UAV2I.js";import"./embed-page-chunk-4BPMGDDV.js";import"./embed-page-chunk-HQLSH3FC.js";import"./embed-page-chunk-5AWYMAAX.js";import"./embed-page-chunk-D7YTSCUO.js";import"./embed-page-chunk-S4XZPBRM.js";import"./embed-page-chunk-BBIV2YA2.js";import{U as L,h as E}from"./embed-page-chunk-MVT6CPWE.js";import"./embed-page-chunk-54M4265G.js";import{a as P}from"./embed-page-chunk-3XPHW2C7.js";import"./embed-page-chunk-5QZRLAW2.js";import"./embed-page-chunk-F7ZUYG42.js";import{l as C}from"./embed-page-chunk-3FNSGFP2.js";import{a as F}from"./embed-page-chunk-QV7QAJ4N.js";import"./embed-page-chunk-VW3NF5NT.js";import"./embed-page-chunk-Z4Z6VCUZ.js";import"./embed-page-chunk-L7GPZSYD.js";import{Ja as T,W as p,_ as m,aa as b,za as I}from"./embed-page-chunk-BUFWD6SE.js";import{a as O}from"./embed-page-chunk-R5QL57JJ.js";import"./embed-page-chunk-QOWM3LMZ.js";import"./embed-page-chunk-EUOOO2LS.js";import"./embed-page-chunk-5JDQ456X.js";import{f as u}from"./embed-page-chunk-QBTAJ2YI.js";var n=u(O());var t=u(F()),$={component_key:"workspace_assistants",data:{}},J=d=>{let i=d;if(typeof d=="string")try{i=JSON.parse(d)}catch{return{}}return i!==null&&typeof i=="object"&&!Array.isArray(i)?i:{}},X=({data:d})=>{let{message:i}=b.useApp(),{assistantId:l,initialTab:B}=d??{},{get:f,put:A,del:y}=P(),e=C(),v=E(),[g,w]=(0,n.useState)(null),[_,h]=(0,n.useState)([]),[j,S]=(0,n.useState)(!1),[x,k]=(0,n.useState)(null),c=(0,n.useCallback)(()=>{v({...$,message:e("governance.detail.listCardMessage")})},[v,e]),o=(0,n.useCallback)(async()=>{S(!0),k(null);try{let s=(await f("/api/assistants")).data?.records??[];h(s),w(s.find(r=>r.id===l)??null)}catch{k(e("governance.detail.loadFailed"))}finally{S(!1)}},[f,l,e]);(0,n.useEffect)(()=>{l&&o()},[o,l]);let z=(0,n.useCallback)(async(a,s)=>{let r=J(a.graphDefinition),D;try{D=await A(`/api/assistants/${a.id}`,{name:s.name!==void 0?s.name:a.name,description:s.description!==void 0?s.description:a.description,graphDefinition:{...r,...s}})}catch(N){throw i.error(e("governance.detail.saveFailed")),N}if(!D.success)throw i.error(e("governance.detail.saveFailed")),new Error("Failed to save agent");i.success(e("common.saved")),await o()},[A,o,e]),U=(0,n.useCallback)(a=>{w(s=>s&&s.id===a.id?a:s),h(s=>s.map(r=>r.id===a.id?a:r))},[]),G=(0,n.useCallback)(async a=>{if(!(await y(`/api/assistants/${a}`)).success)throw new Error("Failed to delete agent");i.success(e("governance.detail.deleted")),c()},[y,c,e]);return l?g?(0,t.jsx)("div",{style:{height:"100%",overflow:"auto"},children:(0,t.jsx)(L,{children:(0,t.jsx)(R,{assistant:g,allAssistants:_,onBack:c,onDeleted:G,onSave:z,onAssistantUpdated:U,onAgentListInvalidated:()=>void o(),initialTab:B})},g.id)}):j?(0,t.jsx)("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",height:"100%"},children:(0,t.jsx)(T,{})}):x?(0,t.jsx)("div",{style:{padding:24},children:(0,t.jsx)(p,{type:"error",showIcon:!0,message:e("governance.detail.loadFailed"),"data-testid":"detail-error",action:(0,t.jsxs)(I,{children:[(0,t.jsx)(m,{size:"small",onClick:()=>void o(),children:e("common.retry")}),(0,t.jsx)(m,{size:"small",type:"primary",onClick:c,children:e("governance.detail.backToList")})]})})}):(0,t.jsx)("div",{style:{padding:24},children:(0,t.jsx)(p,{type:"warning",showIcon:!0,message:e("governance.detail.notFound"),"data-testid":"detail-not-found",action:(0,t.jsx)(m,{size:"small",type:"primary",onClick:c,children:e("governance.detail.backToList")})})}):(0,t.jsx)("div",{style:{padding:24},children:(0,t.jsx)(p,{type:"warning",showIcon:!0,message:e("governance.detail.missingId"),"data-testid":"detail-missing-id"})})};export{X as AgentDetailSideApp};
1
+ import{k as R}from"./embed-page-chunk-QRFTGCB2.js";import"./embed-page-chunk-D7YTSCUO.js";import"./embed-page-chunk-ML6AVDQQ.js";import"./embed-page-chunk-ZVTX7OVB.js";import"./embed-page-chunk-RU45VIPM.js";import"./embed-page-chunk-HQLSH3FC.js";import"./embed-page-chunk-5AWYMAAX.js";import"./embed-page-chunk-S4XZPBRM.js";import"./embed-page-chunk-G6UTU7IC.js";import{U as L,h as E}from"./embed-page-chunk-4GCFDWE7.js";import{a as P}from"./embed-page-chunk-5O5IKZOC.js";import"./embed-page-chunk-AFYXBC77.js";import"./embed-page-chunk-TZPX2O54.js";import"./embed-page-chunk-F7ZUYG42.js";import{l as C}from"./embed-page-chunk-5EENS2J7.js";import{a as F}from"./embed-page-chunk-QV7QAJ4N.js";import"./embed-page-chunk-VW3NF5NT.js";import"./embed-page-chunk-Z4Z6VCUZ.js";import"./embed-page-chunk-L7GPZSYD.js";import{Ja as T,W as p,_ as m,aa as b,za as I}from"./embed-page-chunk-BUFWD6SE.js";import{a as O}from"./embed-page-chunk-R5QL57JJ.js";import"./embed-page-chunk-QOWM3LMZ.js";import"./embed-page-chunk-EUOOO2LS.js";import"./embed-page-chunk-5JDQ456X.js";import{f as u}from"./embed-page-chunk-QBTAJ2YI.js";var n=u(O());var t=u(F()),$={component_key:"workspace_assistants",data:{}},J=d=>{let i=d;if(typeof d=="string")try{i=JSON.parse(d)}catch{return{}}return i!==null&&typeof i=="object"&&!Array.isArray(i)?i:{}},X=({data:d})=>{let{message:i}=b.useApp(),{assistantId:l,initialTab:B}=d??{},{get:f,put:A,del:y}=P(),e=C(),v=E(),[g,w]=(0,n.useState)(null),[_,h]=(0,n.useState)([]),[j,S]=(0,n.useState)(!1),[x,k]=(0,n.useState)(null),c=(0,n.useCallback)(()=>{v({...$,message:e("governance.detail.listCardMessage")})},[v,e]),o=(0,n.useCallback)(async()=>{S(!0),k(null);try{let s=(await f("/api/assistants")).data?.records??[];h(s),w(s.find(r=>r.id===l)??null)}catch{k(e("governance.detail.loadFailed"))}finally{S(!1)}},[f,l,e]);(0,n.useEffect)(()=>{l&&o()},[o,l]);let z=(0,n.useCallback)(async(a,s)=>{let r=J(a.graphDefinition),D;try{D=await A(`/api/assistants/${a.id}`,{name:s.name!==void 0?s.name:a.name,description:s.description!==void 0?s.description:a.description,graphDefinition:{...r,...s}})}catch(N){throw i.error(e("governance.detail.saveFailed")),N}if(!D.success)throw i.error(e("governance.detail.saveFailed")),new Error("Failed to save agent");i.success(e("common.saved")),await o()},[A,o,e]),U=(0,n.useCallback)(a=>{w(s=>s&&s.id===a.id?a:s),h(s=>s.map(r=>r.id===a.id?a:r))},[]),G=(0,n.useCallback)(async a=>{if(!(await y(`/api/assistants/${a}`)).success)throw new Error("Failed to delete agent");i.success(e("governance.detail.deleted")),c()},[y,c,e]);return l?g?(0,t.jsx)("div",{style:{height:"100%",overflow:"auto"},children:(0,t.jsx)(L,{children:(0,t.jsx)(R,{assistant:g,allAssistants:_,onBack:c,onDeleted:G,onSave:z,onAssistantUpdated:U,onAgentListInvalidated:()=>void o(),initialTab:B})},g.id)}):j?(0,t.jsx)("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",height:"100%"},children:(0,t.jsx)(T,{})}):x?(0,t.jsx)("div",{style:{padding:24},children:(0,t.jsx)(p,{type:"error",showIcon:!0,message:e("governance.detail.loadFailed"),"data-testid":"detail-error",action:(0,t.jsxs)(I,{children:[(0,t.jsx)(m,{size:"small",onClick:()=>void o(),children:e("common.retry")}),(0,t.jsx)(m,{size:"small",type:"primary",onClick:c,children:e("governance.detail.backToList")})]})})}):(0,t.jsx)("div",{style:{padding:24},children:(0,t.jsx)(p,{type:"warning",showIcon:!0,message:e("governance.detail.notFound"),"data-testid":"detail-not-found",action:(0,t.jsx)(m,{size:"small",type:"primary",onClick:c,children:e("governance.detail.backToList")})})}):(0,t.jsx)("div",{style:{padding:24},children:(0,t.jsx)(p,{type:"warning",showIcon:!0,message:e("governance.detail.missingId"),"data-testid":"detail-missing-id"})})};export{X as AgentDetailSideApp};