@axiom-lattice/gateway 10.3.1 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/{A2AAuthService-UKN53ZGP.mjs → A2AAuthService-UYWSDE5L.mjs} +2 -2
  2. package/dist/{a2a-YLKZE5JG.mjs → a2a-OCCSVNWB.mjs} +3 -3
  3. package/dist/{a2a-standard-ELY7MTJB.mjs → a2a-standard-2IJP4JG5.mjs} +2 -2
  4. package/dist/{chunk-BIMO2ILH.mjs → chunk-5EXTBV2Z.mjs} +6 -3
  5. package/dist/chunk-5EXTBV2Z.mjs.map +1 -0
  6. package/dist/{chunk-JGQURU44.mjs → chunk-6DTR3CPW.mjs} +2 -2
  7. package/dist/chunk-6DTR3CPW.mjs.map +1 -0
  8. package/dist/{chunk-BOPNXTMO.mjs → chunk-7XTRBDCF.mjs} +2 -2
  9. package/dist/{chunk-MFWTGNQR.mjs → chunk-HBDRSIPK.mjs} +1 -1
  10. package/dist/chunk-HBDRSIPK.mjs.map +1 -0
  11. package/dist/{chunk-YR32MWEJ.mjs → chunk-JQWIJDZN.mjs} +2 -2
  12. package/dist/{chunk-LCM4OQTI.mjs → chunk-NQS3U4KP.mjs} +23 -1
  13. package/dist/chunk-NQS3U4KP.mjs.map +1 -0
  14. package/dist/{chunk-NYYSBNDG.mjs → chunk-Q5ADRJ3T.mjs} +2 -2
  15. package/dist/{chunk-7CG57LEF.mjs → chunk-WKHTSTB6.mjs} +2 -2
  16. package/dist/chunk-WKHTSTB6.mjs.map +1 -0
  17. package/dist/embed/agent-chat-loader.js +1 -1
  18. package/dist/embed/{embed-page-AgentDetailSideApp-HQMDIT7S.js → embed-page-AgentDetailSideApp-CQEMTLCM.js} +1 -1
  19. package/dist/embed/{embed-page-AgentGovernanceWorkbench-T26AS6KD.js → embed-page-AgentGovernanceWorkbench-MFE7A3HX.js} +1 -1
  20. package/dist/embed/{embed-page-LangfuseDashboard-WU5WJUBH.js → embed-page-LangfuseDashboard-FXNWREGX.js} +1 -1
  21. package/dist/embed/{embed-page-MessageGraph-AB5JL57B.js → embed-page-MessageGraph-2KRSGTMR.js} +1 -1
  22. package/dist/embed/{embed-page-ReactChart-OFCUVHIA.js → embed-page-ReactChart-BFMFGXXT.js} +1 -1
  23. package/dist/embed/{embed-page-SkillFlow-3Z4KXWBR.js → embed-page-SkillFlow-F24RIIPR.js} +1 -1
  24. package/dist/embed/{embed-page-TasksSideApp-5J5KPJGJ.js → embed-page-TasksSideApp-EKOC6GR5.js} +1 -1
  25. package/dist/embed/{embed-page-ThreadMaintenancePanel-TOPARLQN.js → embed-page-ThreadMaintenancePanel-25HFF6LC.js} +1 -1
  26. package/dist/embed/embed-page-TopologyRuntimeView-AZ56RJ3H.js +6 -0
  27. package/dist/embed/{embed-page-WorkflowAutomationView-3CGPGS7D.js → embed-page-WorkflowAutomationView-TZR47MHE.js} +1 -1
  28. package/dist/embed/{embed-page-chunk-GMLPPJZP.js → embed-page-chunk-43ZJCYNA.js} +1 -1
  29. package/dist/embed/{embed-page-chunk-N2Y3D6YG.js → embed-page-chunk-5VJZ7DJM.js} +1 -1
  30. package/dist/embed/{embed-page-chunk-T2GL7G2Z.js → embed-page-chunk-FUNZXEPN.js} +1 -1
  31. package/dist/embed/{embed-page-chunk-JRGR5W3C.js → embed-page-chunk-GWB56NNU.js} +1 -1
  32. package/dist/embed/{embed-page-chunk-GXHZDCWF.js → embed-page-chunk-JAZH5PLN.js} +1 -1
  33. package/dist/embed/{embed-page-chunk-PNJ7DZ5V.js → embed-page-chunk-QQZUP2A4.js} +1 -1
  34. package/dist/embed/embed-page-chunk-S2G53SSD.js +217 -0
  35. package/dist/embed/{embed-page-chunk-KJTJI54Q.js → embed-page-chunk-TJMEWCV7.js} +141 -141
  36. package/dist/embed/{embed-page-chunk-U3XF7MHJ.js → embed-page-chunk-XP37R2JT.js} +5 -5
  37. package/dist/embed/{embed-page-chunk-4T3AWQQQ.js → embed-page-chunk-YI3NKQSA.js} +1 -1
  38. package/dist/embed/{embed-page-en-PPUKY32O.js → embed-page-en-BRZZWMIP.js} +1 -1
  39. package/dist/embed/{embed-page-en-3DNM36A2.js → embed-page-en-RKPA6NPQ.js} +3 -1
  40. package/dist/embed/{embed-page-zh-4SFTLL5M.js → embed-page-zh-D4S5EDEI.js} +1 -1
  41. package/dist/embed/{embed-page-zh-OA54TF5G.js → embed-page-zh-LQCDXDUF.js} +3 -1
  42. package/dist/embed/embed-page.js +3 -3
  43. package/dist/embed/mcp-app-page.js +19 -19
  44. package/dist/index.js +2083 -1398
  45. package/dist/index.js.map +1 -1
  46. package/dist/index.mjs +1148 -271
  47. package/dist/index.mjs.map +1 -1
  48. package/dist/{mcp-apps-MZECSNN2.mjs → mcp-apps-MKHHUJE4.mjs} +2 -2
  49. package/dist/{open-Y7PAA2VJ.mjs → open-TJLQARLV.mjs} +8 -8
  50. package/dist/open-TJLQARLV.mjs.map +1 -0
  51. package/dist/{open-catalog-NVGP6SQP.mjs → open-catalog-LLTMSZGH.mjs} +2 -2
  52. package/dist/{open-exposure-HGYT5VFC.mjs → open-exposure-2CSXJTM3.mjs} +3 -3
  53. package/dist/{open-files-WK4SQHE3.mjs → open-files-QIMOTHSN.mjs} +2 -2
  54. package/dist/{open-session-CLZT3HVG.mjs → open-session-F5H6PFSN.mjs} +3 -3
  55. package/dist/open-session-F5H6PFSN.mjs.map +1 -0
  56. package/dist/{openLattice-D5YTSZBY.mjs → openLattice-KFUREFWM.mjs} +2 -2
  57. package/dist/{registerExposedPlugins-2JGXSMAL.mjs → registerExposedPlugins-N6ENBBKN.mjs} +3 -3
  58. package/dist/{resources-YD5DOPGD.mjs → resources-53PXMFQI.mjs} +37 -12
  59. package/dist/resources-53PXMFQI.mjs.map +1 -0
  60. package/dist/{sandboxOpenCredentialProvider-RTYOYRCT.mjs → sandboxOpenCredentialProvider-3CUISBEL.mjs} +4 -4
  61. package/dist/sandboxOpenCredentialProvider-3CUISBEL.mjs.map +1 -0
  62. package/package.json +8 -8
  63. package/dist/chunk-7CG57LEF.mjs.map +0 -1
  64. package/dist/chunk-BIMO2ILH.mjs.map +0 -1
  65. package/dist/chunk-JGQURU44.mjs.map +0 -1
  66. package/dist/chunk-LCM4OQTI.mjs.map +0 -1
  67. package/dist/chunk-MFWTGNQR.mjs.map +0 -1
  68. package/dist/chunk-PWHUWFXL.mjs +0 -173
  69. package/dist/chunk-PWHUWFXL.mjs.map +0 -1
  70. package/dist/collection_service-RTRJEQFL.mjs +0 -8
  71. package/dist/embed/embed-page-TopologyRuntimeView-DNYSW2GN.js +0 -6
  72. package/dist/embed/embed-page-chunk-5UOSSN26.js +0 -217
  73. package/dist/kbProvider-2D6DZ34N.mjs +0 -50
  74. package/dist/kbProvider-2D6DZ34N.mjs.map +0 -1
  75. package/dist/open-Y7PAA2VJ.mjs.map +0 -1
  76. package/dist/open-session-CLZT3HVG.mjs.map +0 -1
  77. package/dist/registerExposedPlugins-2JGXSMAL.mjs.map +0 -1
  78. package/dist/resources-YD5DOPGD.mjs.map +0 -1
  79. package/dist/sandboxOpenCredentialProvider-RTYOYRCT.mjs.map +0 -1
  80. /package/dist/{A2AAuthService-UKN53ZGP.mjs.map → A2AAuthService-UYWSDE5L.mjs.map} +0 -0
  81. /package/dist/{a2a-YLKZE5JG.mjs.map → a2a-OCCSVNWB.mjs.map} +0 -0
  82. /package/dist/{a2a-standard-ELY7MTJB.mjs.map → a2a-standard-2IJP4JG5.mjs.map} +0 -0
  83. /package/dist/{chunk-BOPNXTMO.mjs.map → chunk-7XTRBDCF.mjs.map} +0 -0
  84. /package/dist/{chunk-YR32MWEJ.mjs.map → chunk-JQWIJDZN.mjs.map} +0 -0
  85. /package/dist/{chunk-NYYSBNDG.mjs.map → chunk-Q5ADRJ3T.mjs.map} +0 -0
  86. /package/dist/{collection_service-RTRJEQFL.mjs.map → mcp-apps-MKHHUJE4.mjs.map} +0 -0
  87. /package/dist/{open-catalog-NVGP6SQP.mjs.map → open-catalog-LLTMSZGH.mjs.map} +0 -0
  88. /package/dist/{open-exposure-HGYT5VFC.mjs.map → open-exposure-2CSXJTM3.mjs.map} +0 -0
  89. /package/dist/{open-files-WK4SQHE3.mjs.map → open-files-QIMOTHSN.mjs.map} +0 -0
  90. /package/dist/{mcp-apps-MZECSNN2.mjs.map → openLattice-KFUREFWM.mjs.map} +0 -0
  91. /package/dist/{openLattice-D5YTSZBY.mjs.map → registerExposedPlugins-N6ENBBKN.mjs.map} +0 -0
@@ -2,11 +2,11 @@ import {
2
2
  A2AAuthError,
3
3
  A2AAuthService,
4
4
  parseEnvKeys
5
- } from "./chunk-LCM4OQTI.mjs";
5
+ } from "./chunk-NQS3U4KP.mjs";
6
6
  import "./chunk-NFEGQTCC.mjs";
7
7
  export {
8
8
  A2AAuthError,
9
9
  A2AAuthService,
10
10
  parseEnvKeys
11
11
  };
12
- //# sourceMappingURL=A2AAuthService-UKN53ZGP.mjs.map
12
+ //# sourceMappingURL=A2AAuthService-UYWSDE5L.mjs.map
@@ -5,8 +5,8 @@ import {
5
5
  setA2AAuthService,
6
6
  setA2AKeyStore,
7
7
  setA2AProjectAccess
8
- } from "./chunk-NYYSBNDG.mjs";
9
- import "./chunk-LCM4OQTI.mjs";
8
+ } from "./chunk-Q5ADRJ3T.mjs";
9
+ import "./chunk-NQS3U4KP.mjs";
10
10
  import "./chunk-NFEGQTCC.mjs";
11
11
  export {
12
12
  broadcastA2AKeyChanged,
@@ -16,4 +16,4 @@ export {
16
16
  setA2AKeyStore,
17
17
  setA2AProjectAccess
18
18
  };
19
- //# sourceMappingURL=a2a-YLKZE5JG.mjs.map
19
+ //# sourceMappingURL=a2a-OCCSVNWB.mjs.map
@@ -16,7 +16,7 @@ import {
16
16
  } from "./chunk-P4JEGK74.mjs";
17
17
  import {
18
18
  A2AAuthError
19
- } from "./chunk-LCM4OQTI.mjs";
19
+ } from "./chunk-NQS3U4KP.mjs";
20
20
  import "./chunk-NFEGQTCC.mjs";
21
21
 
22
22
  // src/routes/a2a-standard.ts
@@ -4141,4 +4141,4 @@ data: ${JSON.stringify(errorResponse)}
4141
4141
  export {
4142
4142
  registerA2AStandardRoutes
4143
4143
  };
4144
- //# sourceMappingURL=a2a-standard-ELY7MTJB.mjs.map
4144
+ //# sourceMappingURL=a2a-standard-2IJP4JG5.mjs.map
@@ -23,7 +23,8 @@ function publicFieldsOf(payload) {
23
23
  html: payload.html,
24
24
  ...payload.csp !== void 0 ? { csp: payload.csp } : {},
25
25
  ...payload.permissions !== void 0 ? { permissions: payload.permissions } : {},
26
- ...payload.prefersBorder !== void 0 ? { prefersBorder: payload.prefersBorder } : {}
26
+ ...payload.prefersBorder !== void 0 ? { prefersBorder: payload.prefersBorder } : {},
27
+ ...payload.trustedSameOrigin === true ? { trustedSameOrigin: true } : {}
27
28
  };
28
29
  }
29
30
  var UiResourceResolver = class {
@@ -396,6 +397,7 @@ async function fetchPluginUiResource(backend, tenantId, pluginType, resource) {
396
397
  if (!plugin) throw new Error(`plugin "${pluginType}" not found`);
397
398
  const tool = (plugin.meta.tools ?? []).find((t) => t.ui?.resource === resource);
398
399
  if (!tool?.ui) throw new Error(`plugin "${pluginType}" has no tool declaring ui resource "${resource}"`);
400
+ const trustedSameOrigin = tool.ui.trustedSameOrigin === true && plugin.meta.source?.startsWith("builtin:") === true;
399
401
  const inline = plugin.meta.uiResources?.[resource];
400
402
  if (inline) {
401
403
  return {
@@ -403,7 +405,8 @@ async function fetchPluginUiResource(backend, tenantId, pluginType, resource) {
403
405
  html: inline.html,
404
406
  csp: inline.csp ?? tool.ui.csp,
405
407
  permissions: tool.ui.permissions,
406
- prefersBorder: tool.ui.prefersBorder
408
+ prefersBorder: tool.ui.prefersBorder,
409
+ ...trustedSameOrigin ? { trustedSameOrigin: true } : {}
407
410
  };
408
411
  }
409
412
  const html = await backend.catalog.readPluginFile(tenantId, pluginType, resource);
@@ -577,4 +580,4 @@ export {
577
580
  resolveMcpAppDeps,
578
581
  registerMcpAppRoutes
579
582
  };
580
- //# sourceMappingURL=chunk-BIMO2ILH.mjs.map
583
+ //# sourceMappingURL=chunk-5EXTBV2Z.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 // Only built-in plugins may opt into same-origin execution. Tenant sandbox\n // plugins are authored in the tenant volume; external MCP servers are third\n // party. Neither can use this flag even if their metadata declares it.\n const trustedSameOrigin =\n tool.ui.trustedSameOrigin === true &&\n plugin.meta.source?.startsWith(\"builtin:\") === true;\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 ...(trustedSameOrigin ? { trustedSameOrigin: true } : {}),\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 ...(payload.trustedSameOrigin === true ? { trustedSameOrigin: true } : {}),\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"]}
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  ConnectionNotAllowedError,
7
7
  grantCovers
8
- } from "./chunk-MFWTGNQR.mjs";
8
+ } from "./chunk-HBDRSIPK.mjs";
9
9
 
10
10
  // src/services/open/tenantOpenPlugins.ts
11
11
  import {
@@ -172,4 +172,4 @@ export {
172
172
  mergeOpenCatalog,
173
173
  buildTenantOpenPlugins
174
174
  };
175
- //# sourceMappingURL=chunk-JGQURU44.mjs.map
175
+ //# sourceMappingURL=chunk-6DTR3CPW.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/open/tenantOpenPlugins.ts"],"sourcesContent":["/**\n * Tenant sandbox plugin exposure for the Open MCP surface.\n *\n * Global plugins are registered into `openLattice` by `registerExposedPlugins`;\n * tenant sandbox plugins live in per-tenant volumes and are not part of that\n * registry. This module derives the credential's grant-filtered view of those\n * tenant plugins (tools + `ui://` resources) and routes execution back into the\n * tenant sandbox executor through the shared {@link invokePluginTool} seam.\n *\n * Scope is strictly the credential's `tenantId`; globals are merged by the\n * caller (`routes/open.ts`).\n */\n\nimport {\n getSandboxPluginCatalog,\n jsonSchemaToZod,\n resolveSandboxPluginConnections,\n} from \"@axiom-lattice/core\";\nimport type {\n OpenExecutionResult,\n OpenGrant,\n Plugin,\n SandboxPluginDiagnostic,\n} from \"@axiom-lattice/protocols\";\nimport {\n ConnectionNotAllowedError,\n grantCovers,\n type OpenToolDescriptor,\n} from \"./openLattice\";\nimport type { OpenUiResource } from \"./openMcpServer\";\nimport {\n invokePluginTool,\n PluginToolScopeError,\n type PluginToolCatalog,\n} from \"../plugins/pluginToolExecution\";\n\n/** Catalog surface needed to read tenant plugin files and execute tools. */\nexport interface TenantOpenPluginCatalog extends PluginToolCatalog {\n /** Read a file inside a tenant plugin directory (e.g. a `ui/*.html`). */\n readPluginFile(tenantId: string, pluginType: string, relPath: string): Promise<string>;\n}\n\n/**\n * Result of listing a tenant's sandbox plugins.\n *\n * The live catalog's `ensureLoaded` reports read failures as error-level\n * diagnostics instead of throwing, so the richer object shape is preferred.\n * A bare `Plugin[]` remains accepted for test/back-compat seams and is treated\n * as a healthy listing (`degraded: false`).\n */\nexport type TenantPluginListing =\n | Plugin[]\n | { plugins: Plugin[]; diagnostics?: SandboxPluginDiagnostic[] };\n\n/** Test seams for {@link buildTenantOpenPlugins}; both default to the live catalog. */\nexport interface TenantOpenPluginDeps {\n catalog?: TenantOpenPluginCatalog;\n listPlugins?: (tenantId: string) => Promise<TenantPluginListing>;\n /**\n * Resolve the invoked plugin's connection credentials.\n *\n * Defaults to core's {@link resolveSandboxPluginConnections}, scoped to the\n * credential tenant: grant-pinned keys when present, otherwise\n * `connectAll: true`. Injected by tests and by callers that have already\n * resolved connections.\n */\n resolveConnections?: (\n pluginType: string,\n ctx: TenantOpenPluginInvokeContext,\n ) => Promise<TenantConnectionResolution>;\n /**\n * Tool names already owned by the global/built-in Open catalog.\n *\n * Defense in depth for the `mergeOpenCatalog` guarantee: a tenant tool whose\n * name collides is skipped at build time, so the session never registers a\n * duplicate (the SDK throws on `registerTool`) **and** the bundle's\n * `hasTool` never claims a tool that must route to the global provider.\n */\n reservedToolNames?: ReadonlySet<string>;\n}\n\n/** Credentials resolved for one plugin domain, forwarded to the sandbox executor. */\nexport interface TenantConnectionResolution {\n connection?: Record<string, unknown>;\n connections?: Array<{ key: string; config: Record<string, unknown> }>;\n}\n\n/** Request-scoped identity forwarded when invoking a tenant plugin tool. */\nexport interface TenantOpenPluginInvokeContext {\n tenantId: string;\n workspaceId?: string;\n projectId?: string;\n /** Connection keys pinned by the covering grant (plugin domains only). */\n connectionKeys?: string[];\n /** Synthesized run identity forwarded alongside the invocation. */\n runConfig?: Record<string, unknown>;\n}\n\n/** The credential's grant-filtered tenant plugin bundle for one MCP session. */\nexport interface TenantOpenPlugins {\n tools: OpenToolDescriptor[];\n uiResources: Map<string, OpenUiResource>;\n hasTool(toolName: string): boolean;\n invoke(\n toolName: string,\n args: unknown,\n ctx: TenantOpenPluginInvokeContext,\n ): Promise<OpenExecutionResult>;\n /**\n * True when the tenant catalog could not be read reliably: it reported at\n * least one error-level diagnostic (e.g. `SANDBOX_UNAVAILABLE`,\n * `PLUGINS_LIST_FAILED`). Callers should log this and serve global tools only;\n * the bundle is still safe to use (empty tools, `hasTool` returns false).\n */\n degraded: boolean;\n}\n\n/** Narrow an unknown value to a plain data record, otherwise an empty record. */\nfunction asArgs(value: unknown): Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\n/** Serialize a handler result the way the Open surface renders tool output. */\nfunction toText(result: unknown): string {\n return typeof result === \"string\" ? result : JSON.stringify(result ?? null);\n}\n\n/** Adapter over the process-wide sandbox plugin catalog. */\nfunction defaultCatalog(): TenantOpenPluginCatalog {\n return {\n getValidatedManifest: (tenantId, type) =>\n getSandboxPluginCatalog().getValidatedManifest(tenantId, type),\n getExecutor: () => getSandboxPluginCatalog().getExecutor(),\n readPluginFile: (tenantId, type, relPath) =>\n getSandboxPluginCatalog().readPluginFile(tenantId, type, relPath),\n };\n}\n\n/**\n * Build the resolver config for one invocation.\n *\n * Pinned keys replace `connectAll`; an absent/empty list keeps the legacy\n * connect-all behavior used by the Open surface before selectors existed.\n */\nexport function connectionResolutionConfig(\n connectionKeys?: string[],\n): { connections: string[] } | { connectAll: true } {\n return connectionKeys && connectionKeys.length > 0\n ? { connections: connectionKeys }\n : { connectAll: true };\n}\n\n/**\n * Default connection resolution: every connection of the invoked plugin type,\n * scoped to the credential tenant (or only the grant-pinned keys).\n *\n * A missing connection store degrades to no credentials, matching the Agent\n * path's tolerance, so an unconfigured deployment fails at the plugin boundary\n * rather than throwing from the Open session.\n */\nasync function defaultResolveConnections(\n pluginType: string,\n ctx: TenantOpenPluginInvokeContext,\n): Promise<TenantConnectionResolution> {\n try {\n return await resolveSandboxPluginConnections(\n pluginType,\n connectionResolutionConfig(ctx.connectionKeys),\n {\n tenantId: ctx.tenantId,\n workspaceId: ctx.workspaceId,\n projectId: ctx.projectId,\n },\n );\n } catch (err) {\n if (err instanceof Error && err.message === \"ConnectionStore not configured\") {\n return {};\n }\n throw err;\n }\n}\n\n/**\n * Merge global/built-in Open tools with tenant sandbox plugin tools.\n *\n * The MCP SDK throws when `registerTool` sees a duplicate tool name, and the\n * built-in Open domains (`collection`/`sandbox`/`agent`) are not sandbox plugin types,\n * so a tenant plugin of the same type could otherwise shadow/crash the session.\n * **Global wins**: a tenant tool whose `toolName` already exists in the global\n * catalog is dropped. Tenant-internal duplicates are also collapsed\n * (first-wins) as a defensive measure.\n *\n * @param globalCatalog - Grant-filtered OpenLattice descriptors.\n * @param tenantTools - Tenant sandbox plugin descriptors.\n * @returns A duplicate-free catalog with global entries taking precedence.\n */\nexport function mergeOpenCatalog(\n globalCatalog: OpenToolDescriptor[],\n tenantTools: OpenToolDescriptor[],\n): OpenToolDescriptor[] {\n const seen = new Set(globalCatalog.map((t) => t.toolName));\n const merged = [...globalCatalog];\n for (const tool of tenantTools) {\n if (seen.has(tool.toolName)) continue;\n seen.add(tool.toolName);\n merged.push(tool);\n }\n return merged;\n}\n\n/**\n * Build the credential-scoped tenant plugin tool catalog and UI resources.\n *\n * Only tools whose manifest marks `expose: true` and whose `(domain, action)` is\n * covered by the credential's grants are included. A tool's declared\n * `ui.resource` becomes a deterministic `ui://${domain}/${resource}` URI whose\n * reader pulls the HTML from the tenant volume lazily.\n *\n * @param tenantId - Credential tenant; never caller-supplied.\n * @param grants - Credential grants (domain/action allow-list).\n * @param deps - Optional catalog/list seams for tests.\n * @returns The merged tool descriptors, UI resources, and invoke router.\n *\n * @remarks\n * Sandbox tools carry a restricted JSON Schema object, which is converted with\n * core's {@link jsonSchemaToZod} (the same conversion the manifest validator\n * used), so the registered MCP input schema matches the sandbox contract.\n */\nexport async function buildTenantOpenPlugins(\n tenantId: string,\n grants: OpenGrant[],\n deps: TenantOpenPluginDeps = {},\n): Promise<TenantOpenPlugins> {\n const catalog = deps.catalog ?? defaultCatalog();\n const listPlugins =\n deps.listPlugins ??\n (async (tenant: string) => {\n // `ensureLoaded` reports read failures as diagnostics (it does not throw),\n // so capture both halves of the snapshot to detect degradation.\n const snapshot = await getSandboxPluginCatalog().ensureLoaded(tenant);\n return { plugins: snapshot.plugins, diagnostics: snapshot.diagnostics };\n });\n const resolveConnections = deps.resolveConnections ?? defaultResolveConnections;\n const listed = await listPlugins(tenantId);\n const plugins = Array.isArray(listed) ? listed : listed.plugins;\n const diagnostics = Array.isArray(listed) ? [] : listed.diagnostics ?? [];\n // A throw from `listPlugins` still propagates: callers keep their own catch\n // (which degrades to an empty bundle). Any error-level diagnostic marks the\n // bundle degraded so callers can surface the real, non-throwing failure mode.\n const degraded = diagnostics.some((d) => d.level === \"error\");\n\n const tools: OpenToolDescriptor[] = [];\n const uiResources = new Map<string, OpenUiResource>();\n const toolIndex = new Map<string, { pluginType: string; action: string }>();\n\n for (const plugin of plugins) {\n const domain = plugin.meta.type;\n const manifest = await catalog.getValidatedManifest(tenantId, domain);\n if (!manifest) continue;\n for (const tool of manifest.tools ?? []) {\n if (tool.expose !== true) continue;\n if (!grantCovers(grants, domain, tool.name)) continue;\n const toolName = `${domain}_${tool.name}`;\n if (deps.reservedToolNames?.has(toolName)) continue;\n const ui = tool.ui?.resource\n ? {\n resourceUri: `ui://${domain}/${tool.ui.resource}`,\n csp: tool.ui.csp,\n displayMode: tool.ui.displayMode,\n }\n : undefined;\n if (tool.ui?.resource) {\n const resource = tool.ui.resource;\n const csp = tool.ui.csp;\n uiResources.set(ui!.resourceUri, {\n mimeType: \"text/html;profile=mcp-app\",\n csp,\n read: async () => ({\n html: await catalog.readPluginFile(tenantId, domain, resource),\n csp,\n }),\n });\n }\n tools.push({\n toolName,\n domain,\n action: tool.name,\n description: tool.description,\n schema: jsonSchemaToZod(tool.schema),\n annotations: {\n readOnlyHint: tool.readOnly ?? false,\n destructiveHint: tool.destructive ?? false,\n },\n ui,\n });\n toolIndex.set(toolName, { pluginType: domain, action: tool.name });\n }\n }\n\n return {\n tools,\n uiResources,\n hasTool: (toolName) => toolIndex.has(toolName),\n degraded,\n invoke: async (toolName, args, ctx) => {\n const entry = toolIndex.get(toolName);\n if (!entry) {\n return {\n content: [{ type: \"text\", text: `Unknown action: ${toolName}` }],\n isError: true,\n };\n }\n try {\n // Resolve the plugin's own connections from the credential tenant. The\n // Open surface has no agent run to select connection keys, so exposure\n // uses the Agent path's `connectAll` semantics unless the grant pins\n // specific connections.\n const resolution = await resolveConnections(entry.pluginType, ctx);\n const requested = ctx.connectionKeys;\n let connection = resolution.connection;\n let connections = resolution.connections;\n if (requested?.length) {\n const found = new Set((connections ?? []).map((item) => item.key));\n const missing = requested.filter((key) => !found.has(key));\n if (missing.length > 0) {\n throw new ConnectionNotAllowedError(\n `${entry.pluginType}: ${missing.join(\", \")}`,\n );\n }\n connections = (connections ?? []).filter((item) => requested.includes(item.key));\n connection = connections[0]?.config;\n }\n const result = await invokePluginTool(\n { catalog },\n ctx.tenantId,\n entry.pluginType,\n entry.action,\n asArgs(args),\n {\n workspaceId: ctx.workspaceId,\n projectId: ctx.projectId,\n ...(connection === undefined ? {} : { connection }),\n ...(connections === undefined ? {} : { connections }),\n },\n );\n return { content: [{ type: \"text\", text: toText(result) }] };\n } catch (error) {\n if (error instanceof PluginToolScopeError) {\n return {\n content: [{ type: \"text\", text: error.message }],\n isError: true,\n };\n }\n throw error;\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -5,7 +5,7 @@ import {
5
5
  ConnectionNotAllowedError,
6
6
  grantCovers,
7
7
  openLattice
8
- } from "./chunk-MFWTGNQR.mjs";
8
+ } from "./chunk-HBDRSIPK.mjs";
9
9
 
10
10
  // src/services/open/registerExposedPlugins.ts
11
11
  import { PluginRegistry } from "@axiom-lattice/core";
@@ -201,4 +201,4 @@ export {
201
201
  registerExposedPluginTools,
202
202
  collectExposedPluginUiResources
203
203
  };
204
- //# sourceMappingURL=chunk-BOPNXTMO.mjs.map
204
+ //# sourceMappingURL=chunk-7XTRBDCF.mjs.map
@@ -233,4 +233,4 @@ export {
233
233
  OpenLattice,
234
234
  openLattice
235
235
  };
236
- //# sourceMappingURL=chunk-MFWTGNQR.mjs.map
236
+ //# sourceMappingURL=chunk-HBDRSIPK.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/open/openLattice.ts"],"sourcesContent":["/**\n * OpenLattice — the registry + invocation kernel for the Open API surface.\n *\n * Every credential-driven execution flows through exactly one path:\n * catalogFor(grants) → what the credential may see\n * invoke(grants, ...) → authorize → validate → execute → result\n *\n * Built-in providers (sandbox, agent) and plugin-exposed tools all\n * register the same OpenProviderDeclaration contract.\n */\n\nimport { z } from \"zod\";\nimport type {\n McpUiCsp,\n McpUiDisplayMode,\n OpenCapabilitySource,\n OpenCatalogDomain,\n OpenCatalogItem,\n OpenExecutionContext,\n OpenExecutionResult,\n OpenGrant,\n} from \"@axiom-lattice/protocols\";\n\n/**\n * UI metadata exposed to MCP App hosts: `resourceUri` is advertised in a\n * tool's `_meta.ui`, while `csp`/`displayMode` are consumer hints.\n */\nexport type OpenUiMeta = {\n resourceUri: string;\n csp?: McpUiCsp;\n displayMode?: McpUiDisplayMode;\n};\n\nexport interface OpenActionDeclaration {\n name: string;\n /** Explicit MCP tool name override (defaults to `{domain}_{name}`). */\n toolName?: string;\n /** Friendly display label (falls back to the action name). */\n label?: string;\n description: string;\n schema: z.ZodRawShape | z.ZodTypeAny;\n annotations: { readOnlyHint: boolean; destructiveHint: boolean };\n /** MCP App UI metadata; advertised in the tool's `_meta.ui`. */\n ui?: OpenUiMeta;\n}\n\n/** Request-scoped context available while deriving the catalog. */\nexport interface OpenCatalogContext {\n tenantId?: string;\n}\n\nexport interface OpenProviderDeclaration {\n domain: string;\n /** Human label for the management catalog (defaults to domain). */\n label?: string;\n /** Catalog provenance (defaults to \"builtin\"). */\n source?: OpenCapabilitySource;\n /** \"action\" (plugins) or \"instance\" (agent); defaults to \"action\". */\n itemKind?: \"action\" | \"instance\";\n /** Display-only scope hint (defaults to \"tenant\"). */\n scopeKind?: \"tenant\" | \"workspace\" | \"project\";\n /** Whether this domain's tools resolve tenant connections. */\n connectionBacked?: boolean;\n actions: OpenActionDeclaration[];\n /**\n * Dynamic actions derived from the credential's grants — used by instance\n * domains (e.g. agent: one tool per granted assistant).\n */\n instanceActions?: (grants: OpenGrant[], ctx?: OpenCatalogContext) => OpenActionDeclaration[] | Promise<OpenActionDeclaration[]>;\n execute: (\n action: string,\n args: unknown,\n ctx: OpenExecutionContext,\n ) => Promise<OpenExecutionResult>;\n}\n\nexport interface OpenToolDescriptor {\n /** MCP tool name, \"{domain}_{action}\". */\n toolName: string;\n domain: string;\n action: string;\n description: string;\n schema: z.ZodRawShape | z.ZodTypeAny;\n annotations: { readOnlyHint: boolean; destructiveHint: boolean };\n /** MCP App UI metadata; advertised in the tool's `_meta.ui`. */\n ui?: OpenUiMeta;\n}\n\n/** Thrown when a credential has no grant covering the requested action. */\nexport class GrantDeniedError extends Error {\n readonly code = \"GRANT_DENIED\";\n constructor(toolName: string) {\n super(`GRANT_DENIED: no grant covers ${toolName}`);\n this.name = \"GrantDeniedError\";\n }\n}\n\n/** Thrown when a grant pins a connection that is missing or foreign. */\nexport class ConnectionNotAllowedError extends Error {\n readonly code = \"CONNECTION_NOT_ALLOWED\";\n constructor(detail: string) {\n super(`CONNECTION_NOT_ALLOWED: ${detail}`);\n this.name = \"ConnectionNotAllowedError\";\n }\n}\n\n/** Thrown when args fail the action's zod schema. */\nexport class ValidationError extends Error {\n readonly code = \"VALIDATION_FAILED\";\n constructor(detail: string) {\n super(`VALIDATION_FAILED: ${detail}`);\n this.name = \"ValidationError\";\n }\n}\n\n/** Thrown for a toolName with no registered provider/action. */\nexport class ActionNotFoundError extends Error {\n readonly code = \"ACTION_NOT_FOUND\";\n constructor(toolName: string) {\n super(`ACTION_NOT_FOUND: ${toolName}`);\n this.name = \"ActionNotFoundError\";\n }\n}\n\ninterface RegisteredProvider {\n declaration: OpenProviderDeclaration;\n actions: Map<string, OpenActionDeclaration>;\n}\n\n/** Effective MCP tool name: explicit override, else `{domain}_{name}`. */\nfunction effectiveToolName(domain: string, action: OpenActionDeclaration): string {\n return action.toolName ?? `${domain}_${action.name}`;\n}\n\n/** The first grant covering one action of a domain (authorization predicate). */\nexport function findCoveringGrant(\n grants: OpenGrant[],\n domain: string,\n action: string,\n): OpenGrant | undefined {\n return grants.find(\n (g) =>\n g.domain === domain\n && (!g.items || g.items.length === 0 || g.items.includes(action)),\n );\n}\n\n/**\n * Normalize an untrusted selector connection list; throws on invalid\n * containers/entries.\n */\nexport function normalizeConnectionKeys(value: unknown): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) {\n throw new ValidationError(\"selector.connections must be an array\");\n }\n if (value.length === 0) return undefined;\n const keys = new Set<string>();\n for (const item of value) {\n if (typeof item !== \"string\" || item.trim().length === 0) {\n throw new ValidationError(\n \"selector.connections must contain non-empty strings\",\n );\n }\n keys.add(item);\n }\n return [...keys];\n}\n\n/**\n * Normalize the selector of one grant, validating its container shape.\n * A malformed selector fails closed instead of becoming connect-all.\n */\nexport function normalizeGrantConnectionKeys(grant: OpenGrant): string[] | undefined {\n const selector: unknown = grant.selector;\n if (selector === undefined) return undefined;\n if (typeof selector !== \"object\" || selector === null || Array.isArray(selector)) {\n throw new ValidationError(\"grant.selector must be an object\");\n }\n return normalizeConnectionKeys((selector as { connections?: unknown }).connections);\n}\n\n/**\n * Resolve the effective connection selector across every covering grant.\n *\n * Authorization is the union of grants, so the selector must not depend on\n * grant order. A covering grant without `selector.connections` means\n * unrestricted (legacy connect-all); otherwise the pinned keys are unioned.\n */\nexport function resolveCoveringConnectionKeys(\n grants: OpenGrant[],\n domain: string,\n action: string,\n): string[] | undefined {\n const covering = grants.filter(\n (g) =>\n g.domain === domain\n && (!g.items || g.items.length === 0 || g.items.includes(action)),\n );\n if (covering.length === 0) return undefined;\n const normalized = covering.map((grant) => normalizeGrantConnectionKeys(grant));\n if (normalized.some((keys) => !keys)) return undefined;\n const keys = new Set<string>();\n for (const selected of normalized) {\n for (const key of selected ?? []) keys.add(key);\n }\n return keys.size > 0 ? [...keys] : undefined;\n}\n\n/**\n * Whether the given grants cover one action of a domain (default-deny;\n * empty/absent `items` covers every action). Shared by tool and UI-resource\n * filtering so item-level grants apply identically to both.\n */\nexport function grantCovers(\n grants: OpenGrant[],\n domain: string,\n action: string,\n): boolean {\n return findCoveringGrant(grants, domain, action) !== undefined;\n}\n\nexport class OpenLattice {\n private providers = new Map<string, RegisteredProvider>();\n\n /** Register a provider; domain+action collisions are rejected. */\n register(declaration: OpenProviderDeclaration): void {\n const existing = this.providers.get(declaration.domain);\n if (existing) {\n throw new Error(\n `Open provider domain already registered: ${declaration.domain}`,\n );\n }\n const actions = new Map<string, OpenActionDeclaration>();\n for (const action of declaration.actions) {\n actions.set(action.name, action);\n }\n this.providers.set(declaration.domain, { declaration, actions });\n }\n\n /** Tool descriptors visible under the given grants (default-deny). */\n async catalogFor(grants: OpenGrant[], ctx?: OpenCatalogContext): Promise<OpenToolDescriptor[]> {\n const catalog: OpenToolDescriptor[] = [];\n for (const [domain, entry] of this.providers) {\n // Static actions.\n for (const [actionName, action] of entry.actions) {\n if (!grantCovers(grants, domain, actionName)) continue;\n catalog.push({\n toolName: effectiveToolName(domain, action),\n domain,\n action: actionName,\n description: action.description,\n schema: action.schema,\n annotations: action.annotations,\n ui: action.ui,\n });\n }\n // Instance-derived actions (e.g. agent: one tool per granted assistant).\n if (entry.declaration.instanceActions) {\n const instances = await entry.declaration.instanceActions(grants, ctx);\n for (const action of instances) {\n catalog.push({\n toolName: effectiveToolName(domain, action),\n domain,\n action: action.name,\n description: action.description,\n schema: action.schema,\n annotations: action.annotations,\n ui: action.ui,\n });\n }\n }\n }\n return catalog;\n }\n\n /**\n * Full grantable catalog (unfiltered by grants) for the management picker.\n * Instance domains (agent) return an empty item list — the catalog service\n * fills their per-tenant instances.\n */\n describeCatalog(): OpenCatalogDomain[] {\n const domains: OpenCatalogDomain[] = [];\n for (const [domain, entry] of this.providers) {\n const declaration = entry.declaration;\n const items: OpenCatalogItem[] = [];\n if ((declaration.itemKind ?? \"action\") === \"action\") {\n for (const [actionName, action] of entry.actions) {\n items.push({\n id: actionName,\n toolName: `${domain}_${actionName}`,\n label: action.label ?? actionName,\n description: action.description,\n annotations: action.annotations,\n });\n }\n }\n domains.push({\n domain,\n label: declaration.label ?? domain,\n source: declaration.source ?? \"builtin\",\n scopeKind: declaration.scopeKind ?? \"tenant\",\n connectionBacked: declaration.connectionBacked === true,\n itemKind: declaration.itemKind ?? \"action\",\n items,\n });\n }\n return domains;\n }\n\n private async resolveAction(\n grants: OpenGrant[],\n toolName: string,\n ctx?: OpenCatalogContext,\n ): Promise<{\n domain: string;\n entry: RegisteredProvider;\n action: OpenActionDeclaration;\n /** Grant-matching id: the action name (assistant id for agent instances). */\n actionName: string;\n } | null> {\n for (const [domain, entry] of this.providers) {\n for (const [actionName, action] of entry.actions) {\n if (effectiveToolName(domain, action) === toolName) {\n return { domain, entry, action, actionName };\n }\n }\n if (entry.declaration.instanceActions) {\n const instances = await entry.declaration.instanceActions(grants, ctx);\n for (const action of instances) {\n if (effectiveToolName(domain, action) === toolName) {\n return { domain, entry, action, actionName: action.name };\n }\n }\n }\n }\n return null;\n }\n\n /** Whether the given grants cover a tool (`{domain}_{action}`). */\n isAllowed(grants: OpenGrant[], toolName: string): boolean {\n const sep = toolName.indexOf(\"_\");\n const domain = sep > 0 ? toolName.slice(0, sep) : toolName;\n const action = sep > 0 ? toolName.slice(sep + 1) : \"\";\n return grantCovers(grants, domain, action);\n }\n\n /** The single credential-driven execution path. */\n async invoke(\n grants: OpenGrant[],\n ctx: OpenExecutionContext,\n toolName: string,\n args: unknown,\n ): Promise<OpenExecutionResult> {\n const resolved = await this.resolveAction(grants, toolName, { tenantId: ctx.tenantId });\n if (!resolved) {\n throw new ActionNotFoundError(toolName);\n }\n const grant = findCoveringGrant(grants, resolved.domain, resolved.actionName);\n if (!grant) {\n throw new GrantDeniedError(toolName);\n }\n const connectionKeys = resolveCoveringConnectionKeys(\n grants,\n resolved.domain,\n resolved.actionName,\n );\n if (connectionKeys && resolved.entry.declaration.connectionBacked !== true) {\n throw new ValidationError(\n `selector.connections is not supported for domain \"${resolved.domain}\"`,\n );\n }\n const resolvedSchema = resolved.action.schema;\n const parsed = (\n \"safeParse\" in resolvedSchema\n ? (resolvedSchema as z.ZodTypeAny).safeParse(args)\n : z.object(resolvedSchema as z.ZodRawShape).safeParse(args)\n );\n if (!parsed.success) {\n throw new ValidationError(parsed.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\"; \"));\n }\n return resolved.entry.declaration.execute(\n resolved.actionName,\n parsed.data,\n connectionKeys ? { ...ctx, connectionKeys } : ctx,\n );\n }\n}\n\n/** Process-wide singleton for the gateway's Open surface. */\nexport const openLattice = new OpenLattice();\n"],"mappings":"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","names":["keys"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  normalizeGrantConnectionKeys
3
- } from "./chunk-MFWTGNQR.mjs";
3
+ } from "./chunk-HBDRSIPK.mjs";
4
4
 
5
5
  // src/services/open/sandboxOpenReflection.ts
6
6
  import {
@@ -205,4 +205,4 @@ export {
205
205
  createDefaultAgentReflectionDeps,
206
206
  mergeGrantsByDomain
207
207
  };
208
- //# sourceMappingURL=chunk-YR32MWEJ.mjs.map
208
+ //# sourceMappingURL=chunk-JQWIJDZN.mjs.map
@@ -1,4 +1,5 @@
1
1
  // src/services/a2a/A2AAuthService.ts
2
+ import { PluginRegistry } from "@axiom-lattice/core";
2
3
  import { effectiveGrants } from "@axiom-lattice/protocols";
3
4
  var A2AAuthError = class extends Error {
4
5
  constructor(message, statusCode) {
@@ -31,6 +32,12 @@ function devModeDefaults() {
31
32
  const projectId = process.env[DEFAULT_PROJECT_ENV] || void 0;
32
33
  return { tenantId, projectId };
33
34
  }
35
+ function requiresExplicitGrantItems(domain) {
36
+ const plugin = PluginRegistry.get(domain);
37
+ return (plugin?.meta.openExpose ?? []).some(
38
+ (entry) => typeof entry !== "string" && entry.destructive === true
39
+ );
40
+ }
34
41
  function validateGrantSelectors(grants) {
35
42
  if (grants === void 0) return;
36
43
  if (!Array.isArray(grants)) {
@@ -41,6 +48,21 @@ function validateGrantSelectors(grants) {
41
48
  if (typeof grant !== "object" || grant === null || Array.isArray(grant)) {
42
49
  throw new A2AAuthError("grant must be an object", 400);
43
50
  }
51
+ const record = grant;
52
+ if (typeof record.domain !== "string" || record.domain.length === 0) {
53
+ throw new A2AAuthError("grant.domain must be a non-empty string", 400);
54
+ }
55
+ if (record.items !== void 0 && (!Array.isArray(record.items) || record.items.some(
56
+ (item) => typeof item !== "string" || item.trim().length === 0
57
+ ))) {
58
+ throw new A2AAuthError("grant.items must be an array of non-empty strings", 400);
59
+ }
60
+ if (requiresExplicitGrantItems(record.domain) && (!Array.isArray(record.items) || record.items.length === 0)) {
61
+ throw new A2AAuthError(
62
+ `grant for destructive domain "${record.domain}" requires explicit items`,
63
+ 400
64
+ );
65
+ }
44
66
  const selector = grant.selector;
45
67
  if (selector === void 0) continue;
46
68
  if (typeof selector !== "object" || selector === null || Array.isArray(selector)) {
@@ -191,4 +213,4 @@ export {
191
213
  parseEnvKeys,
192
214
  A2AAuthService
193
215
  };
194
- //# sourceMappingURL=chunk-LCM4OQTI.mjs.map
216
+ //# sourceMappingURL=chunk-NQS3U4KP.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/a2a/A2AAuthService.ts"],"sourcesContent":["import { PluginRegistry } from \"@axiom-lattice/core\";\nimport { effectiveGrants } from \"@axiom-lattice/protocols\";\nimport type {\n A2AApiKeyEntry,\n A2AApiKeyRecord,\n A2AApiKeyStore,\n A2AAuthContext,\n Assistant,\n AssistantStore,\n ProjectStore,\n OpenGrant,\n} from \"@axiom-lattice/protocols\";\n\n/**\n * Error thrown by {@link A2AAuthService} for authz/authn failures.\n * Carries an HTTP status code for the route layer to surface.\n */\nexport class A2AAuthError extends Error {\n public readonly statusCode: number;\n\n constructor(message: string, statusCode: number) {\n super(message);\n this.name = \"A2AAuthError\";\n this.statusCode = statusCode;\n }\n}\n\n/**\n * Dependencies injected into {@link A2AAuthService} so stores can be mocked in tests.\n */\nexport interface A2AAuthServiceDeps {\n keyStore: A2AApiKeyStore;\n assistantStore: AssistantStore;\n projectStore: ProjectStore;\n envKeyMap?: Map<string, A2AApiKeyEntry>;\n /**\n * When true, requests with no key are allowed through when no keys are\n * configured (dev mode). Defaults to false — JSON-RPC stays locked until a\n * key is configured or this opt-in is explicitly enabled.\n */\n allowUnauthenticated?: boolean;\n}\n\nexport interface UpdateKeyScopedInput {\n label?: string;\n projectId?: string;\n assistantIds?: string[];\n grants?: OpenGrant[];\n}\n\n/** Input for creating an API key scoped to a tenant + project. */\nexport interface CreateKeyScopedInput {\n tenantId: string;\n projectId?: string;\n assistantIds?: string[];\n /** Open-door grants (validated minimally; unknown domains simply never match). */\n grants?: OpenGrant[];\n label?: string;\n}\n\nconst DEFAULT_TENANT_ENV = \"A2A_DEFAULT_TENANT_ID\";\nconst DEFAULT_TENANT_ID = \"a2a-default-tenant\";\nconst DEFAULT_PROJECT_ENV = \"A2A_DEFAULT_PROJECT_ID\";\n\n/** Matches an HTTP `Authorization` Bearer scheme, case-insensitively (`bearer`/`Bearer`). */\nconst BEARER_SCHEME_RE = /^Bearer\\s+/i;\n\n/**\n * Parse an `A2A_API_KEYS=\"key:tenant:project,...\"` env string into a lookup map.\n * Entries missing a key, tenant, or project segment are skipped.\n */\nexport function parseEnvKeys(env: string): Map<string, A2AApiKeyEntry> {\n const map = new Map<string, A2AApiKeyEntry>();\n\n if (!env) return map;\n\n for (const entry of env.split(\",\")) {\n const trimmed = entry.trim();\n if (!trimmed) continue;\n\n const parts = trimmed.split(\":\");\n const key = parts[0]?.trim();\n const tenantId = parts[1]?.trim();\n const projectId = parts[2]?.trim();\n if (!key || !tenantId || !projectId) continue;\n\n map.set(key, { key, tenantId, projectId });\n }\n\n return map;\n}\n\n/**\n * Resolve the dev-mode default tenant/project when no keys are configured.\n */\nfunction devModeDefaults(): { tenantId: string; projectId?: string } {\n const tenantId = process.env[DEFAULT_TENANT_ENV] || DEFAULT_TENANT_ID;\n const projectId = process.env[DEFAULT_PROJECT_ENV] || undefined;\n return { tenantId, projectId };\n}\n\n/** Domains whose exposed tools can destroy data require an explicit item list. */\nfunction requiresExplicitGrantItems(domain: string): boolean {\n const plugin = PluginRegistry.get(domain);\n return (plugin?.meta.openExpose ?? []).some(\n (entry) => typeof entry !== \"string\" && entry.destructive === true,\n );\n}\n\n/** Reject malformed or over-broad grants before they are persisted. */\nfunction validateGrantSelectors(grants: OpenGrant[] | undefined): void {\n if (grants === undefined) return;\n if (!Array.isArray(grants)) {\n throw new A2AAuthError(\"grants must be an array\", 400);\n }\n for (const entry of grants as unknown[]) {\n const grant: unknown = entry;\n if (typeof grant !== \"object\" || grant === null || Array.isArray(grant)) {\n throw new A2AAuthError(\"grant must be an object\", 400);\n }\n const record = grant as { domain?: unknown; items?: unknown };\n if (typeof record.domain !== \"string\" || record.domain.length === 0) {\n throw new A2AAuthError(\"grant.domain must be a non-empty string\", 400);\n }\n if (\n record.items !== undefined\n && (\n !Array.isArray(record.items)\n || record.items.some(\n (item) => typeof item !== \"string\" || item.trim().length === 0,\n )\n )\n ) {\n throw new A2AAuthError(\"grant.items must be an array of non-empty strings\", 400);\n }\n if (\n requiresExplicitGrantItems(record.domain)\n && (!Array.isArray(record.items) || record.items.length === 0)\n ) {\n throw new A2AAuthError(\n `grant for destructive domain \"${record.domain}\" requires explicit items`,\n 400,\n );\n }\n const selector: unknown = (grant as { selector?: unknown }).selector;\n if (selector === undefined) continue;\n if (typeof selector !== \"object\" || selector === null || Array.isArray(selector)) {\n throw new A2AAuthError(\"grant.selector must be an object\", 400);\n }\n const connections = (selector as { connections?: unknown }).connections;\n if (connections === undefined) continue;\n if (\n !Array.isArray(connections)\n || connections.some(\n (key) => typeof key !== \"string\" || key.trim().length === 0,\n )\n ) {\n throw new A2AAuthError(\n \"selector.connections must be an array of non-empty connection keys\",\n 400,\n );\n }\n }\n}\n\n/**\n * Authenticates A2A requests against the key store + env key map,\n * enforces assistant whitelist / exposure, and validates key scope on creation.\n */\nexport class A2AAuthService {\n private readonly keyStore: A2AApiKeyStore;\n private readonly assistantStore: AssistantStore;\n private readonly projectStore: ProjectStore;\n private readonly envKeyMap: Map<string, A2AApiKeyEntry>;\n /** Lazily-populated store key map; reloaded via {@link refresh} after keys CRUD. */\n private storeKeyMapCache: Map<string, A2AApiKeyEntry> | null = null;\n private readonly allowUnauthenticated: boolean;\n\n constructor(deps: A2AAuthServiceDeps) {\n this.keyStore = deps.keyStore;\n this.assistantStore = deps.assistantStore;\n this.projectStore = deps.projectStore;\n this.envKeyMap = deps.envKeyMap ?? new Map();\n this.allowUnauthenticated = deps.allowUnauthenticated ?? false;\n }\n\n /**\n * Reload the cached store key map. Call once at startup and after any keys\n * CRUD mutation so {@link authenticate} does not hit the store on every request.\n */\n async refresh(): Promise<void> {\n this.storeKeyMapCache = await this.keyStore.loadIntoMap();\n }\n\n /** Return the cached store key map, loading it on first use. */\n private async getStoreKeyMap(): Promise<Map<string, A2AApiKeyEntry>> {\n if (this.storeKeyMapCache === null) {\n await this.refresh();\n }\n return this.storeKeyMapCache ?? new Map<string, A2AApiKeyEntry>();\n }\n\n /**\n * Authenticate an incoming request header (authorization or x-api-key value).\n * Returns an auth context; `authenticated: false` means the caller must reject.\n */\n async authenticate(header: string | undefined): Promise<A2AAuthContext> {\n const storeMap = await this.getStoreKeyMap();\n const noAuthRequired = storeMap.size === 0 && this.envKeyMap.size === 0;\n\n const token = this.extractToken(header);\n\n if (!token) {\n if (noAuthRequired && this.allowUnauthenticated) {\n const { tenantId, projectId } = devModeDefaults();\n return { authenticated: true, tenantId, projectId };\n }\n return { authenticated: false };\n }\n\n // Store keys take priority; env keys serve as fallback.\n const merged = new Map<string, A2AApiKeyEntry>([...this.envKeyMap, ...storeMap]);\n const entry = merged.get(token);\n\n if (!entry) return { authenticated: false };\n\n // Unified key management: the agent whitelist comes from the key's grants\n // when present, else the legacy assistantIds.\n //\n // - explicit grants: agent access only if an agent-domain grant exists\n // (missing agent grant => empty whitelist => no agents);\n // - bare agent grant (no items) => undefined => legacy \"all agents\";\n // - no grants: fall back to legacy assistantIds (undefined => all agents).\n const hasExplicitGrants = Array.isArray(entry.grants) && entry.grants.length > 0;\n const agentGrant = effectiveGrants(entry).find((g) => g.domain === \"agent\");\n const assistantIds = hasExplicitGrants\n ? (agentGrant?.items ?? [])\n : entry.assistantIds;\n\n return {\n authenticated: true,\n apiKey: token,\n tenantId: entry.tenantId,\n projectId: entry.projectId,\n assistantIds,\n source: header && BEARER_SCHEME_RE.test(header) ? \"bearer\" : \"x-api-key\",\n };\n }\n\n /**\n * Assert that the given assistant is reachable under the provided auth\n * context: authenticated, same tenant, and within the key's whitelist.\n *\n * Authorization is unified on the API key — the agent no longer carries an\n * exposure flag; the key's assistant whitelist / grants is the single source.\n */\n async assertAgentAccess(auth: A2AAuthContext, assistantId: string): Promise<Assistant> {\n if (!auth.authenticated || !auth.tenantId) {\n throw new A2AAuthError(\"Unauthorized\", 401);\n }\n\n const assistant = await this.assistantStore.getAssistantById(auth.tenantId, assistantId);\n if (!assistant || assistant.tenantId !== auth.tenantId) {\n throw new A2AAuthError(\"Agent not found\", 404);\n }\n\n // undefined = legacy \"all agents\"; [] = explicitly none; list = whitelist.\n const whitelist = auth.assistantIds;\n if (whitelist !== undefined && !whitelist.includes(assistantId)) {\n throw new A2AAuthError(\"Agent not allowed for this key\", 403);\n }\n\n return assistant;\n }\n\n /**\n * Create an API key scoped to a tenant + project, validating the project exists\n * in the tenant and every assistant belongs to the same tenant.\n */\n async createKeyScoped(input: CreateKeyScopedInput): Promise<A2AApiKeyRecord> {\n const { tenantId, projectId, assistantIds, grants, label } = input;\n\n if (!projectId) {\n throw new A2AAuthError(\"projectId is required\", 400);\n }\n\n const project = await this.projectStore.getProjectById(tenantId, projectId);\n if (!project || project.tenantId !== tenantId) {\n throw new A2AAuthError(\"Project not found\", 400);\n }\n\n validateGrantSelectors(grants);\n\n if (assistantIds && assistantIds.length > 0) {\n for (const id of assistantIds) {\n const assistant = await this.assistantStore.getAssistantById(tenantId, id);\n if (!assistant || assistant.tenantId !== tenantId) {\n throw new A2AAuthError(\"Assistant not found\", 400);\n }\n }\n }\n\n return this.keyStore.create({ tenantId, projectId, assistantIds, grants, label });\n }\n\n async updateKeyScoped(\n id: string,\n tenantId: string,\n input: UpdateKeyScopedInput,\n ): Promise<A2AApiKeyRecord> {\n const { projectId, assistantIds, grants, label } = input;\n if (projectId) {\n const project = await this.projectStore.getProjectById(tenantId, projectId);\n if (!project || project.tenantId !== tenantId) {\n throw new A2AAuthError(\"Project not found\", 400);\n }\n }\n validateGrantSelectors(grants);\n if (assistantIds && assistantIds.length > 0) {\n for (const aId of assistantIds) {\n const assistant = await this.assistantStore.getAssistantById(tenantId, aId);\n if (!assistant || assistant.tenantId !== tenantId) {\n throw new A2AAuthError(\"Assistant not found\", 400);\n }\n }\n }\n return this.keyStore.update(id, { label, projectId, assistantIds, grants });\n }\n\n private extractToken(header: string | undefined): string | undefined {\n if (!header) return undefined;\n const match = BEARER_SCHEME_RE.exec(header);\n if (match) {\n return header.substring(match[0].length);\n }\n return header;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  A2AAuthError
3
- } from "./chunk-LCM4OQTI.mjs";
3
+ } from "./chunk-NQS3U4KP.mjs";
4
4
 
5
5
  // src/routes/a2a.ts
6
6
  import { getStoreLattice, getReplicaId } from "@axiom-lattice/core";
@@ -572,4 +572,4 @@ export {
572
572
  broadcastA2AKeyChanged,
573
573
  registerA2ARoutes
574
574
  };
575
- //# sourceMappingURL=chunk-NYYSBNDG.mjs.map
575
+ //# sourceMappingURL=chunk-Q5ADRJ3T.mjs.map
@@ -5,7 +5,7 @@ import {
5
5
 
6
6
  // src/services/open/previewToken.ts
7
7
  init_openToken();
8
- var BROWSER_GRANTS = [{ domain: "kb", items: ["search"] }];
8
+ var BROWSER_GRANTS = [{ domain: "collection", items: ["search_collection"] }];
9
9
  function buildPreviewToken(ids) {
10
10
  return signOpenToken({
11
11
  kind: "browser_token",
@@ -24,4 +24,4 @@ export {
24
24
  BROWSER_GRANTS,
25
25
  buildPreviewTokenScript
26
26
  };
27
- //# sourceMappingURL=chunk-7CG57LEF.mjs.map
27
+ //# sourceMappingURL=chunk-WKHTSTB6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/open/previewToken.ts"],"sourcesContent":["/**\n * Preview token: the internal-preview variant of a browser token. Injected\n * into HTML served by the authenticated/bypassed file routes (viewfile,\n * /f/*) so a pre-publish app can call the Open API with the same page code\n * it will use after publishing.\n *\n * Grants are capped by the platform browser whitelist (V1: collection search),\n * exactly like /open/session — the preview credential is strictly weaker\n * than the file-read access the URL already confers.\n */\n\nimport { signOpenToken } from \"./openToken\";\n\n/** V1 browser whitelist: only read-only collection search may reach a browser. */\nexport const BROWSER_GRANTS = [{ domain: \"collection\", items: [\"search_collection\"] }];\n\nexport interface PreviewTokenIds {\n tenantId: string;\n workspaceId?: string;\n projectId: string;\n}\n\nexport function buildPreviewToken(ids: PreviewTokenIds): string {\n return signOpenToken({\n kind: \"browser_token\",\n tenantId: ids.tenantId,\n workspaceId: ids.workspaceId,\n projectId: ids.projectId,\n preview: true,\n grants: BROWSER_GRANTS,\n });\n}\n\n/** `<script>` snippet that exposes the preview token to page JS. */\nexport function buildPreviewTokenScript(ids: PreviewTokenIds): string {\n return `<script>window.__OPEN_API_TOKEN__=${JSON.stringify(buildPreviewToken(ids))};</script>`;\n}\n"],"mappings":";;;;;;AAWA;AAGO,IAAM,iBAAiB,CAAC,EAAE,QAAQ,cAAc,OAAO,CAAC,mBAAmB,EAAE,CAAC;AAQ9E,SAAS,kBAAkB,KAA8B;AAC9D,SAAO,cAAc;AAAA,IACnB,MAAM;AAAA,IACN,UAAU,IAAI;AAAA,IACd,aAAa,IAAI;AAAA,IACjB,WAAW,IAAI;AAAA,IACf,SAAS;AAAA,IACT,QAAQ;AAAA,EACV,CAAC;AACH;AAGO,SAAS,wBAAwB,KAA8B;AACpE,SAAO,qCAAqC,KAAK,UAAU,kBAAkB,GAAG,CAAC,CAAC;AACpF;","names":[]}
@@ -1 +1 @@
1
- "use strict";var __axiomEmbedInternal=(()=>{var _="axiom-web-app-embed";var T=["axiom:ready","axiom:unread","axiom:error","axiom:message"];function R(e,n){return{source:_,version:1,type:e,payload:n}}var ee=6,ne=40,te=500;function oe(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let n=e,t=Object.keys(n);return t.length!==2||!t.includes("label")||!t.includes("text")?!1:typeof n.label=="string"&&n.label.length>=1&&n.label.length<=ne&&typeof n.text=="string"&&n.text.length>=1&&n.text.length<=te}function O(e){if(!Array.isArray(e))return[];let n=[];for(let t of e){if(n.length>=ee)break;oe(t)&&n.push({label:t.label,text:t.text})}return n}function L(e,n){if(typeof e!="object"||e===null)return null;let t=e;return t.source!==_||t.version!==1||typeof t.type!="string"||!n.includes(t.type)?null:{type:t.type,payload:t.payload}}var ie=/^[A-Za-z0-9._:@-]{1,128}$/,re=640,C=56,ae=12,h="default";function m(e,n,t){let o=e[n];return typeof o=="number"&&Number.isFinite(o)?o:t}function u(e,n){let t=e[n];return typeof t=="string"&&t.length>0?t:null}function q(e,n,t){let o=e.offset;return typeof o!="object"||o===null?t:m(o,n,t)}function se(e){if(e===void 0)return;if(!Array.isArray(e)){console.warn("[AxiomChat] settings.quicks must be an array; ignoring it.");return}let n=O(e);return n.length!==e.length&&console.warn(`[AxiomChat] settings.quicks: kept ${n.length} of ${e.length} entries (max 6; label 1-40; text 1-500).`),n}function Q(e,n,t,o){let l=u(e,"webAppId")??n("web-app-id");if(l===null)return null;let d=u(e,"gatewayUrl")??n("gateway-url"),f=(d!==null?d:t).replace(/\/+$/,"");return{id:o,base:f,webAppId:l,mode:e.mode==="inline"||n("mode")==="inline"?"inline":"bubble",containerSelector:u(e,"containerSelector")??n("container-selector"),bubble:!(e.bubble===!1||n("bubble")==="false"),width:m(e,"width",380),height:m(e,"height",640),position:e.position==="bottom-left"||n("position")==="bottom-left"?"bottom-left":"bottom-right",offsetX:m(e,"offsetX",q(e,"x",20)),offsetY:m(e,"offsetY",q(e,"y",20)),zIndex:m(e,"zIndex",9999),userId:u(e,"userId")??n("user-id"),welcome:u(e,"welcome"),quicks:se(e.quicks)}}function H(){return window.innerWidth<=re}function z(e){let n=null,t=null,o=!1,l=!1,d=[],f=[],M=[];function g(i,r){let a=n;if(a===null)return;let p=a.contentWindow;p!==null&&p.postMessage(R(i,r),new URL(a.src).origin)}function j(){return e.bubble!==!1&&e.mode==="bubble"}function E(i,r){let a=e.offsetY+(j()?C+ae:0),p=Math.min(e.width,Math.max(window.innerWidth-e.offsetX*2,240)),J=Math.min(e.height,Math.max(window.innerHeight-a-e.offsetY,240));Object.assign(i.style,H()?{position:"fixed",inset:"0px",width:"100%",height:"100%",border:"none",display:r?"block":"none",zIndex:String(e.zIndex)}:{position:"fixed",bottom:`${a}px`,left:e.position==="bottom-left"?`${e.offsetX}px`:"auto",right:e.position==="bottom-right"?`${e.offsetX}px`:"auto",width:`${p}px`,height:`${J}px`,border:"none",borderRadius:"16px",boxShadow:"0 12px 40px rgba(23, 32, 51, 0.24)",background:"#ffffff",display:r?"block":"none",zIndex:String(e.zIndex)})}function X(){let i=t;if(i===null)return;let r=e.position==="bottom-right"?{right:`${e.offsetX}px`,left:"auto"}:{left:`${e.offsetX}px`,right:"auto"};if(H()&&o){Object.assign(i.style,{...r,top:`${e.offsetY}px`,bottom:"auto"});return}Object.assign(i.style,{...r,top:"auto",bottom:`${e.offsetY}px`})}function b(){if(n!==null)return n;let i=document.createElement("iframe");if(i.src=`${e.base}/api/web-apps/${encodeURIComponent(e.webAppId)}/chat?mode=embed`,i.title="Agent chat",i.setAttribute("aria-label","Agent chat"),e.mode==="inline"){let r=e.containerSelector!==null?document.querySelector(e.containerSelector):null;r===null&&console.warn(`[AxiomChat] inline container not found: ${e.containerSelector??"(none)"}`),Object.assign(i.style,{width:"100%",height:"100%",border:"none",display:"block"}),(r??document.body).appendChild(i)}else E(i,o),document.body.appendChild(i);return i.addEventListener("load",()=>{g("axiom:hello",{webAppId:e.webAppId})}),n=i,i}function x(i){for(let r of f)r(i)}function y(){t!==null&&(t.setAttribute("aria-expanded",o?"true":"false"),X())}function w(){if(e.mode==="bubble"){let i=b();o=!0,E(i,!0)}else b();y()}function A(){e.mode!=="bubble"||n===null||(o=!1,E(n,!1),y())}function k(){o?A():w()}function V(i){if(typeof i!="object"||i===null)return!1;let r=i;return(r.role==="human"||r.role==="ai")&&typeof r.content=="string"}function S(i){if(typeof i!="string")return;let r=i.trim();if(r.length===0){x({code:"INVALID_REQUEST",message:"AxiomChat.sendMessage requires non-empty text."});return}n===null&&b(),g("axiom:send",{text:r})}function I(i){l?i():d.push(i)}function v(i){f.push(i)}function P(i){M.push(i)}function W(){if(e.bubble===!1||e.mode!=="bubble")return;let i=document.createElement("button");i.type="button",i.setAttribute("aria-label","Agent chat"),i.setAttribute("aria-expanded","false"),Object.assign(i.style,{position:"fixed",width:`${C}px`,height:`${C}px`,border:"none",borderRadius:"9999px",background:"#4f46e5",color:"#ffffff",cursor:"pointer",boxShadow:"0 8px 24px rgba(23, 32, 51, 0.28)",zIndex:String(e.zIndex+1),display:"flex",alignItems:"center",justifyContent:"center"}),i.innerHTML='<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H9l-4.05 3.24A1 1 0 0 1 3.2 18.4V5.5Z" fill="currentColor"/></svg>',i.addEventListener("click",()=>{k()}),t=i,document.body.appendChild(i)}function F(i){let r=L(i,T);if(r!==null){if(r.type==="axiom:ready"){if(e.userId!==null&&ie.test(e.userId)){if(g("axiom:identity",{userId:e.userId}),l=!0,e.welcome!==null||e.quicks!==void 0){let a={};e.welcome!==null&&(a.welcome=e.welcome),e.quicks!==void 0&&(a.quicks=e.quicks),g("axiom:config",a)}for(let a of d)a()}else x({code:"USER_ID_REQUIRED",message:"A stable userId must be configured for the embed."});return}if(r.type==="axiom:error"){let a=r.payload;x({code:typeof a.code=="string"?a.code:"UNKNOWN",message:typeof a.message=="string"?a.message:"Unknown error"});return}if(r.type==="axiom:message"){if(!V(r.payload))return;for(let a of M)a(r.payload)}}}function K(){W(),y(),e.mode==="inline"&&b()}let Z={id:e.id,open:w,hide:A,toggle:k,sendMessage:S,onReady:I,onMessage:P,onError:v};return{settings:e,handle:Z,getPanel:()=>n,activate:K,open:w,hide:A,toggle:k,sendMessage:S,registerReady:I,registerError:v,registerMessage:P,handleIncoming:F}}var c=new Map;function le(e){let n=e,t=2;for(;c.has(n);)n=`${e}-${t}`,t+=1;return n}function B(e){if(typeof e!="object"||e===null)throw new Error("[AxiomChat] mount(settings) requires a settings object with a webAppId.");let n=e;if(u(n,"webAppId")===null)throw new Error("[AxiomChat] mount(settings) requires a webAppId.");let t=u(n,"id");if(t!==null){let f=c.get(t);if(f!==void 0)return console.warn(`[AxiomChat] mount: instance id "${t}" is already in use; returning the existing instance.`),f.handle}let o=t??le(u(n,"webAppId")??""),l=Q(n,()=>null,N,o);if(l===null)throw new Error("[AxiomChat] mount(settings) requires a webAppId.");let d=z(l);return c.set(o,d),d.activate(),d.handle}function de(e){return c.get(e)?.handle}function s(e){let n=c.get(h);if(n===void 0){console.warn("[AxiomChat] not configured");return}e(n)}function $(e){if(Array.isArray(e)===!1)return;let[n,t]=e;if(n==="mount"){try{B(t)}catch(o){console.warn("[AxiomChat] mount failed:",o)}return}if(n==="onReady"&&typeof t=="function"){s(o=>o.registerReady(t));return}if(n==="onError"&&typeof t=="function"){s(o=>o.registerError(t));return}if(n==="onMessage"&&typeof t=="function"){s(o=>o.registerMessage(t));return}if(n==="sendMessage"&&typeof t=="string"){s(o=>o.sendMessage(t));return}n==="open"?s(o=>o.open()):n==="hide"?s(o=>o.hide()):n==="toggle"&&s(o=>o.toggle())}function U(e){for(let n of c.values()){let t=n.getPanel();if(t!==null&&e.origin===new URL(t.src).origin&&!(e.source===null||e.source!==t.contentWindow)){n.handleIncoming(e.data);return}}}var G="__axiomChatMessageListener";function ue(){let e=window,n=e[G];typeof n=="function"&&window.removeEventListener("message",n),e[G]=U,window.addEventListener("message",U)}var Y=document.currentScript,D=Y?.src||null,N=D!==null?new URL(D).origin:window.location.origin;function ce(){let e=t=>{let o=Y?.getAttribute(`data-${t}`);return typeof o=="string"&&o.length>0?o:null},n=window.AxiomChatSettings??{};return Q(n,e,N,h)}function fe(){let e=ce();e!==null?c.set(h,z(e)):console.warn("[AxiomChat] Missing webAppId (set window.AxiomChatSettings.webAppId or data-web-app-id)."),ue();let n=(o=>{o!==void 0&&$(o)});n.open=()=>s(o=>o.open()),n.hide=()=>s(o=>o.hide()),n.toggle=()=>s(o=>o.toggle()),n.sendMessage=o=>s(l=>l.sendMessage(o)),n.mount=B,n.get=de;let t=window.AxiomChat;if(Array.isArray(t))for(let o of t)$(o);window.AxiomChat=n,c.get(h)?.activate()}fe();})();
1
+ "use strict";var __axiomEmbedInternal=(()=>{var _="axiom-web-app-embed";var T=["axiom:ready","axiom:unread","axiom:error","axiom:message"];function R(e,n){return{source:_,version:1,type:e,payload:n}}var J=6,ee=40,ne=500;function te(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let n=e,t=Object.keys(n);return t.length!==2||!t.includes("label")||!t.includes("text")?!1:typeof n.label=="string"&&n.label.length>=1&&n.label.length<=ee&&typeof n.text=="string"&&n.text.length>=1&&n.text.length<=ne}function O(e){if(!Array.isArray(e))return[];let n=[];for(let t of e){if(n.length>=J)break;te(t)&&n.push({label:t.label,text:t.text})}return n}function L(e,n){if(typeof e!="object"||e===null)return null;let t=e;return t.source!==_||t.version!==1||typeof t.type!="string"||!n.includes(t.type)?null:{type:t.type,payload:t.payload}}var oe=/^[A-Za-z0-9._:@-]{1,128}$/,ie=640,C=56,re=12,h="default";function m(e,n,t){let o=e[n];return typeof o=="number"&&Number.isFinite(o)?o:t}function u(e,n){let t=e[n];return typeof t=="string"&&t.length>0?t:null}function $(e,n,t){let o=e.offset;return typeof o!="object"||o===null?t:m(o,n,t)}function ae(e){if(e===void 0)return;if(!Array.isArray(e)){console.warn("[AxiomChat] settings.quicks must be an array; ignoring it.");return}let n=O(e);return n.length!==e.length&&console.warn(`[AxiomChat] settings.quicks: kept ${n.length} of ${e.length} entries (max 6; label 1-40; text 1-500).`),n}function D(e,n,t,o){let s=u(e,"webAppId")??n("web-app-id");if(s===null)return null;let d=u(e,"gatewayUrl")??n("gateway-url"),f=(d!==null?d:t).replace(/\/+$/,"");return{id:o,base:f,webAppId:s,mode:e.mode==="inline"||n("mode")==="inline"?"inline":"bubble",containerSelector:u(e,"containerSelector")??n("container-selector"),bubble:!(e.bubble===!1||n("bubble")==="false"),width:m(e,"width",380),height:m(e,"height",640),position:e.position==="bottom-left"||n("position")==="bottom-left"?"bottom-left":"bottom-right",offsetX:m(e,"offsetX",$(e,"x",20)),offsetY:m(e,"offsetY",$(e,"y",20)),zIndex:m(e,"zIndex",9999),userId:u(e,"userId")??n("user-id"),welcome:u(e,"welcome"),quicks:ae(e.quicks)}}function q(){return window.innerWidth<=ie}function B(e){let n=null,t=null,o=!1,s=!1,d=[],f=[],M=[];function g(i,r){let a=n;if(a===null)return;let p=a.contentWindow;p!==null&&p.postMessage(R(i,r),new URL(a.src).origin)}function N(){return e.bubble!==!1&&e.mode==="bubble"}function E(i,r){let a=e.offsetY+(N()?C+re:0),p=Math.min(e.width,Math.max(window.innerWidth-e.offsetX*2,240)),Z=Math.min(e.height,Math.max(window.innerHeight-a-e.offsetY,240));Object.assign(i.style,q()?{position:"fixed",inset:"0px",width:"100%",height:"100%",border:"none",display:r?"block":"none",zIndex:String(e.zIndex)}:{position:"fixed",bottom:`${a}px`,left:e.position==="bottom-left"?`${e.offsetX}px`:"auto",right:e.position==="bottom-right"?`${e.offsetX}px`:"auto",width:`${p}px`,height:`${Z}px`,border:"none",borderRadius:"16px",boxShadow:"0 12px 40px rgba(23, 32, 51, 0.24)",background:"#ffffff",display:r?"block":"none",zIndex:String(e.zIndex)})}function j(){let i=t;if(i===null)return;let r=e.position==="bottom-right"?{right:`${e.offsetX}px`,left:"auto"}:{left:`${e.offsetX}px`,right:"auto"};if(q()&&o){Object.assign(i.style,{...r,top:`${e.offsetY}px`,bottom:"auto"});return}Object.assign(i.style,{...r,top:"auto",bottom:`${e.offsetY}px`})}function b(){if(n!==null)return n;let i=document.createElement("iframe");if(i.src=`${e.base}/api/web-apps/${encodeURIComponent(e.webAppId)}/chat?mode=embed`,i.title="Agent chat",i.setAttribute("aria-label","Agent chat"),e.mode==="inline"){let r=e.containerSelector!==null?document.querySelector(e.containerSelector):null;r===null&&console.warn(`[AxiomChat] inline container not found: ${e.containerSelector??"(none)"}`),Object.assign(i.style,{width:"100%",height:"100%",border:"none",display:"block"}),(r??document.body).appendChild(i)}else E(i,o),document.body.appendChild(i);return i.addEventListener("load",()=>{g("axiom:hello",{webAppId:e.webAppId})}),n=i,i}function x(i){for(let r of f)r(i)}function y(){t!==null&&(t.setAttribute("aria-expanded",o?"true":"false"),j())}function w(){if(e.mode==="bubble"){let i=b();o=!0,E(i,!0)}else b();y()}function A(){e.mode!=="bubble"||n===null||(o=!1,E(n,!1),y())}function k(){o?A():w()}function X(i){if(typeof i!="object"||i===null)return!1;let r=i;return(r.role==="human"||r.role==="ai")&&typeof r.content=="string"}function S(i){if(typeof i!="string")return;let r=i.trim();if(r.length===0){x({code:"INVALID_REQUEST",message:"AxiomChat.sendMessage requires non-empty text."});return}n===null&&b(),g("axiom:send",{text:r})}function I(i){s?i():d.push(i)}function v(i){f.push(i)}function P(i){M.push(i)}function V(){if(e.bubble===!1||e.mode!=="bubble")return;let i=document.createElement("button");i.type="button",i.setAttribute("aria-label","Agent chat"),i.setAttribute("aria-expanded","false"),Object.assign(i.style,{position:"fixed",width:`${C}px`,height:`${C}px`,border:"none",borderRadius:"9999px",background:"#4f46e5",color:"#ffffff",cursor:"pointer",boxShadow:"0 8px 24px rgba(23, 32, 51, 0.28)",zIndex:String(e.zIndex+1),display:"flex",alignItems:"center",justifyContent:"center"}),i.innerHTML='<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H9l-4.05 3.24A1 1 0 0 1 3.2 18.4V5.5Z" fill="currentColor"/></svg>',i.addEventListener("click",()=>{k()}),t=i,document.body.appendChild(i)}function W(i){let r=L(i,T);if(r!==null){if(r.type==="axiom:ready"){if(e.userId!==null&&oe.test(e.userId)){if(g("axiom:identity",{userId:e.userId}),s=!0,e.welcome!==null||e.quicks!==void 0){let a={};e.welcome!==null&&(a.welcome=e.welcome),e.quicks!==void 0&&(a.quicks=e.quicks),g("axiom:config",a)}for(let a of d)a()}else x({code:"USER_ID_REQUIRED",message:"A stable userId must be configured for the embed."});return}if(r.type==="axiom:error"){let a=r.payload;x({code:typeof a.code=="string"?a.code:"UNKNOWN",message:typeof a.message=="string"?a.message:"Unknown error"});return}if(r.type==="axiom:message"){if(!X(r.payload))return;for(let a of M)a(r.payload)}}}function F(){V(),y(),e.mode==="inline"&&b()}let K={id:e.id,open:w,hide:A,toggle:k,sendMessage:S,onReady:I,onMessage:P,onError:v};return{settings:e,handle:K,getPanel:()=>n,activate:F,open:w,hide:A,toggle:k,sendMessage:S,registerReady:I,registerError:v,registerMessage:P,handleIncoming:W}}var c=new Map;function se(e){let n=e,t=2;for(;c.has(n);)n=`${e}-${t}`,t+=1;return n}function Q(e){if(typeof e!="object"||e===null)throw new Error("[AxiomChat] mount(settings) requires a settings object with a webAppId.");let n=e;if(u(n,"webAppId")===null)throw new Error("[AxiomChat] mount(settings) requires a webAppId.");let t=u(n,"id");if(t!==null){let f=c.get(t);if(f!==void 0)return console.warn(`[AxiomChat] mount: instance id "${t}" is already in use; returning the existing instance.`),f.handle}let o=t??se(u(n,"webAppId")??""),s=D(n,()=>null,Y,o);if(s===null)throw new Error("[AxiomChat] mount(settings) requires a webAppId.");let d=B(s);return c.set(o,d),d.activate(),d.handle}function le(e){return c.get(e)?.handle}function l(e){let n=c.get(h);if(n===void 0){console.warn("[AxiomChat] not configured");return}e(n)}function H(e){if(Array.isArray(e)===!1)return;let[n,t]=e;if(n==="mount"){try{Q(t)}catch(o){console.warn("[AxiomChat] mount failed:",o)}return}if(n==="onReady"&&typeof t=="function"){l(o=>o.registerReady(t));return}if(n==="onError"&&typeof t=="function"){l(o=>o.registerError(t));return}if(n==="onMessage"&&typeof t=="function"){l(o=>o.registerMessage(t));return}if(n==="sendMessage"&&typeof t=="string"){l(o=>o.sendMessage(t));return}n==="open"?l(o=>o.open()):n==="hide"?l(o=>o.hide()):n==="toggle"&&l(o=>o.toggle())}function U(e){for(let n of c.values()){let t=n.getPanel();if(t!==null&&e.origin===new URL(t.src).origin&&!(e.source===null||e.source!==t.contentWindow)){n.handleIncoming(e.data);return}}}var G="__axiomChatMessageListener";function de(){let e=window,n=e[G];typeof n=="function"&&window.removeEventListener("message",n),e[G]=U,window.addEventListener("message",U)}function ue(e){if(e===null||e.length===0)return window.location.origin;try{let n=new URL(e),o=n.pathname.lastIndexOf("/embed/"),s=o>=0?n.pathname.slice(0,o):"";return`${n.origin}${s}`}catch{return window.location.origin}}var z=document.currentScript,ce=z?.src||null,Y=ue(ce);function fe(){let e=t=>{let o=z?.getAttribute(`data-${t}`);return typeof o=="string"&&o.length>0?o:null},n=window.AxiomChatSettings??{};return D(n,e,Y,h)}function me(){let e=fe();e!==null?c.set(h,B(e)):console.warn("[AxiomChat] Missing webAppId (set window.AxiomChatSettings.webAppId or data-web-app-id)."),de();let n=(o=>{o!==void 0&&H(o)});n.open=()=>l(o=>o.open()),n.hide=()=>l(o=>o.hide()),n.toggle=()=>l(o=>o.toggle()),n.sendMessage=o=>l(s=>s.sendMessage(o)),n.mount=Q,n.get=le;let t=window.AxiomChat;if(Array.isArray(t))for(let o of t)H(o);window.AxiomChat=n,c.get(h)?.activate()}me();})();
@@ -1 +1 @@
1
- import{k as R}from"./embed-page-chunk-N2Y3D6YG.js";import"./embed-page-chunk-GXHZDCWF.js";import"./embed-page-chunk-GMLPPJZP.js";import"./embed-page-chunk-4T3AWQQQ.js";import"./embed-page-chunk-BJESMNTG.js";import"./embed-page-chunk-QPWSDWJ2.js";import"./embed-page-chunk-U6PURUXV.js";import"./embed-page-chunk-S4XZPBRM.js";import"./embed-page-chunk-JRGR5W3C.js";import{U as L,h as E}from"./embed-page-chunk-KJTJI54Q.js";import"./embed-page-chunk-5UOSSN26.js";import{a as P}from"./embed-page-chunk-T2GL7G2Z.js";import"./embed-page-chunk-U3XF7MHJ.js";import"./embed-page-chunk-BNA3NXXX.js";import{l as C}from"./embed-page-chunk-PNJ7DZ5V.js";import{a as F}from"./embed-page-chunk-L2PNY33P.js";import"./embed-page-chunk-DLSQ2OWQ.js";import"./embed-page-chunk-V5NE4FIR.js";import"./embed-page-chunk-7TINC3YA.js";import{Ga as I,Qa as T,ba as p,fa as m,ha as b}from"./embed-page-chunk-TWJOR2LG.js";import{a as O}from"./embed-page-chunk-MPQBYKDM.js";import"./embed-page-chunk-3KBY77YU.js";import"./embed-page-chunk-U2CG7PH5.js";import{e as u}from"./embed-page-chunk-45A47GH2.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-5VJZ7DJM.js";import"./embed-page-chunk-JAZH5PLN.js";import"./embed-page-chunk-43ZJCYNA.js";import"./embed-page-chunk-YI3NKQSA.js";import"./embed-page-chunk-BJESMNTG.js";import"./embed-page-chunk-QPWSDWJ2.js";import"./embed-page-chunk-U6PURUXV.js";import"./embed-page-chunk-S4XZPBRM.js";import"./embed-page-chunk-GWB56NNU.js";import{U as L,h as E}from"./embed-page-chunk-TJMEWCV7.js";import"./embed-page-chunk-S2G53SSD.js";import{a as P}from"./embed-page-chunk-FUNZXEPN.js";import"./embed-page-chunk-XP37R2JT.js";import"./embed-page-chunk-BNA3NXXX.js";import{l as C}from"./embed-page-chunk-QQZUP2A4.js";import{a as F}from"./embed-page-chunk-L2PNY33P.js";import"./embed-page-chunk-DLSQ2OWQ.js";import"./embed-page-chunk-V5NE4FIR.js";import"./embed-page-chunk-7TINC3YA.js";import{Ga as I,Qa as T,ba as p,fa as m,ha as b}from"./embed-page-chunk-TWJOR2LG.js";import{a as O}from"./embed-page-chunk-MPQBYKDM.js";import"./embed-page-chunk-3KBY77YU.js";import"./embed-page-chunk-U2CG7PH5.js";import{e as u}from"./embed-page-chunk-45A47GH2.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};