@ggui-ai/mcp-server 0.25.0 → 0.26.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 (59) hide show
  1. package/README.md +2 -2
  2. package/dist/api-renders-routes.d.ts +46 -5
  3. package/dist/api-renders-routes.d.ts.map +1 -1
  4. package/dist/api-renders-routes.js +98 -39
  5. package/dist/api-renders-stream-route.d.ts +2 -1
  6. package/dist/api-renders-stream-route.d.ts.map +1 -1
  7. package/dist/api-renders-stream-route.js +14 -2
  8. package/dist/build-mcp.d.ts +16 -0
  9. package/dist/build-mcp.d.ts.map +1 -1
  10. package/dist/build-mcp.js +48 -20
  11. package/dist/console-auth.d.ts +10 -10
  12. package/dist/console-auth.d.ts.map +1 -1
  13. package/dist/first-seen.d.ts +14 -0
  14. package/dist/first-seen.d.ts.map +1 -0
  15. package/dist/first-seen.js +19 -0
  16. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -1
  17. package/dist/ggui-session-channel/action-ingress.js +96 -8
  18. package/dist/ggui-session-channel/channel-subscriptions.d.ts +0 -1
  19. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -1
  20. package/dist/ggui-session-channel/channel-subscriptions.js +4 -1
  21. package/dist/ggui-session-channel/internal-types.d.ts +20 -12
  22. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -1
  23. package/dist/ggui-session-channel/outbound.d.ts.map +1 -1
  24. package/dist/ggui-session-channel/outbound.js +18 -6
  25. package/dist/ggui-session-channel/stream-epoch.d.ts +61 -0
  26. package/dist/ggui-session-channel/stream-epoch.d.ts.map +1 -0
  27. package/dist/ggui-session-channel/stream-epoch.js +47 -0
  28. package/dist/ggui-session-channel/subscribe.d.ts +29 -17
  29. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -1
  30. package/dist/ggui-session-channel/subscribe.js +247 -124
  31. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -1
  32. package/dist/ggui-session-channel/subscriber-lifecycle.js +17 -7
  33. package/dist/ggui-session-channel.d.ts +13 -5
  34. package/dist/ggui-session-channel.d.ts.map +1 -1
  35. package/dist/ggui-session-channel.js +2 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/instructions-presets.d.ts +22 -21
  40. package/dist/instructions-presets.d.ts.map +1 -1
  41. package/dist/instructions-presets.js +38 -38
  42. package/dist/llm-backed-negotiator.d.ts +1 -36
  43. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  44. package/dist/llm-backed-negotiator.js +0 -196
  45. package/dist/mcp-apps-outbound.d.ts +39 -90
  46. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  47. package/dist/mcp-apps-outbound.js +45 -3
  48. package/dist/mcp-endpoint-routes.d.ts.map +1 -1
  49. package/dist/mcp-endpoint-routes.js +4 -1
  50. package/dist/server.d.ts +108 -33
  51. package/dist/server.d.ts.map +1 -1
  52. package/dist/server.js +181 -48
  53. package/dist/storage.d.ts +9 -1
  54. package/dist/storage.d.ts.map +1 -1
  55. package/dist/storage.js +4 -3
  56. package/dist/view-proof-gate.d.ts +96 -0
  57. package/dist/view-proof-gate.d.ts.map +1 -0
  58. package/dist/view-proof-gate.js +167 -0
  59. package/package.json +14 -15
package/dist/server.js CHANGED
@@ -39,7 +39,7 @@
39
39
  */
40
40
  import { CONSOLE_DIST_DIR } from "@ggui-ai/console/server";
41
41
  import { RUNTIME_BUNDLE_FILE, RUNTIME_BUNDLE_URL_PATH, RUNTIME_SHIMS_DIR, RUNTIME_SHIMS_URL_PREFIX, } from "@ggui-ai/iframe-runtime/server";
42
- import { createDeterministicBlueprintSelector, isTokenRegisteringAuthAdapter, mintSessionToken, mintWsToken, verifyWsTokenSignature, verifyToken, } from "@ggui-ai/mcp-server-core";
42
+ import { isTokenRegisteringAuthAdapter, DEFAULT_WS_TOKEN_REFRESH_WINDOW_MULTIPLIER, DEFAULT_WS_TOKEN_TTL_SEC, mintViewRoot, mintWsToken, verifyWsTokenSignature, verifyToken, } from "@ggui-ai/mcp-server-core";
43
43
  import { createInMemoryBlueprintSearch, createInMemoryGeneratorRegistry, FixedWindowRateLimiter, InMemoryActiveConsumerRegistry, InMemoryAppMetadataStore, InMemoryAuthAdapter, InMemoryBlueprintIndex, InMemoryBlueprintStore, InMemoryKeyValueStore, InMemoryPairingService, InMemoryPendingEventConsumer, InMemoryQuotaStore, InMemoryGguiSessionStore, InMemoryGguiSessionStreamBuffer, InMemoryVectorStore, MockEmbeddingProvider, NoopAuditSink, NoopRateLimiter, NoopTelemetrySink, } from "@ggui-ai/mcp-server-core/in-memory";
44
44
  import { createGguiListGadgetsHandler, createGguiListThemesHandler, } from "@ggui-ai/mcp-server-handlers/app-discovery";
45
45
  import { createDescribeBlueprintFormatHandler, createDescribeDataContractFormatHandler, createGetBlueprintBoilerplateHandler, createGetExampleBlueprintsHandler, createListAvailablePrimitivesHandler, createListFeaturedBlueprintsHandler, createRenderBlueprintHandler, createSearchBlueprintsHandler, createValidateBlueprintHandler, } from "@ggui-ai/mcp-server-handlers/blueprints";
@@ -68,10 +68,11 @@ import { BoundedValidatorTraceSink, mountConsoleValidatorRoutes } from "./consol
68
68
  // `ggui_ops_get_credit_balance`'s pattern).
69
69
  import { createCreateAppHandler, createDeleteAppHandler, createListAppsHandler, createSetAppThemeHandler, createSetDefaultAppHandler, createUpdateAppHandler, } from "@ggui-ai/mcp-server-handlers/ops-apps";
70
70
  import { fontFaceRules, validateOverlayCoverage } from "@ggui-ai/design/themes";
71
- import { createGguiConsumeHandler, createGguiDeclareToolCatalogHandler, createGguiEmitHandler, createGguiGetRenderSourceHandler, createGguiGetSessionHandler, createGguiHandshakeHandler, createGguiListSessionsHandler, createGguiRefreshWsTokenHandler, createGguiRenderHandler, createGguiRuntimePullHandler, createGguiRuntimeTelemetryHandler, createGguiSubmitActionHandler, createGguiSyncContextHandler, createGguiUpdateHandler, createGguiAmendHandler, InMemoryToolIdentityCatalogStore, createInMemoryProvisionalPreviewRegistry, } from "@ggui-ai/mcp-server-handlers/renders";
71
+ import { createGguiConsumeHandler, createGguiDeclareToolCatalogHandler, createGguiEmitHandler, createGguiGetRenderSourceHandler, createGguiGetSessionHandler, createGguiHandshakeHandler, createGguiListSessionsHandler, createGguiRefreshWsTokenHandler, createGguiRenderHandler, createGguiReportRenderFailureHandler, createGguiRuntimePullHandler, createGguiRuntimeTelemetryHandler, createGguiSubmitActionHandler, createGguiSyncContextHandler, createGguiUpdateHandler, createGguiAmendHandler, InMemoryToolIdentityCatalogStore, createInMemoryProvisionalPreviewRegistry, } from "@ggui-ai/mcp-server-handlers/renders";
72
72
  import { DEFAULT_ADMIN_BLUEPRINTS_PATH, mountAdminBlueprintsTransport, } from "./admin-blueprints-transport.js";
73
73
  import { mountAdminOAuthProvidersTransport } from "./admin-oauth-providers-transport.js";
74
74
  import { DEFAULT_BUILDER_APP_ID, defaultAppIdFromIdentity } from "./auth.js";
75
+ import { assertViewProofDeclarations, createViewProofGate, unverifiableViewProofTools, } from "./view-proof-gate.js";
75
76
  import { mountMcpEndpoints } from "./mcp-endpoint-routes.js";
76
77
  import { mountApiRendersRoutes } from "./api-renders-routes.js";
77
78
  import { mountApiRendersStreamRoute } from "./api-renders-stream-route.js";
@@ -96,7 +97,7 @@ import { computeRuntimeBundleHash, insertRuntimeBundleHash, } from "./runtime-bu
96
97
  import { createCsrfMiddleware, mountCsrfTokenRoute } from "./csrf-middleware.js";
97
98
  import { mountEmailLoginRoutes } from "./email-login.js";
98
99
  import { resolveMcpInstructions } from "./instructions-presets.js";
99
- import { buildLlmCaller, createLlmBackedHandshakeNegotiator } from "./llm-backed-negotiator.js";
100
+ import { createLlmBackedHandshakeNegotiator } from "./llm-backed-negotiator.js";
100
101
  import { createConsoleLogger } from "./logger.js";
101
102
  import { buildControlService, CONTROL_PATH } from "./control-service.js";
102
103
  import { createBrowserCorsMiddleware, createPreflightFallback } from "./browser-cors.js";
@@ -230,6 +231,9 @@ export function buildOpsBlueprintHandlers(input) {
230
231
  }));
231
232
  handlers.push(createGguiOpsDeleteBlueprintHandler({
232
233
  blueprintStore: bundle.blueprintStore,
234
+ // ggui#1541 — the registry generate and register mirror into, so a
235
+ // delete takes the blueprint out of the handshake's reuse too.
236
+ ...(bundle.cacheRegistry ? { cacheRegistry: bundle.cacheRegistry } : {}),
233
237
  authorizeAppAccess: bundle.authorizeAppAccess,
234
238
  }));
235
239
  return handlers;
@@ -362,6 +366,15 @@ export function defaultHandlers(deps) {
362
366
  handlers.push(createGguiRuntimeTelemetryHandler({
363
367
  ...(deps.logger ? { logger: deps.logger } : {}),
364
368
  }));
369
+ // `ggui_runtime_report_render_failure` (ggui#1609) — a card whose render
370
+ // failed says so, for the session its view proof binds. Unlike
371
+ // telemetry it is recorded, so its session is proven, never claimed;
372
+ // this server names each proven report (`render_failed`) and stores
373
+ // nothing — a deployment that keeps a per-session mark supplies
374
+ // `recordRenderFailure` when it composes its own handler set.
375
+ handlers.push(createGguiReportRenderFailureHandler({
376
+ renderStore: deps.render.renderStore,
377
+ }));
365
378
  // `ggui_runtime_refresh_ws_token` — the authorized re-mint of a
366
379
  // view's live credential (ggui#1496 part B). Registered only when the
367
380
  // deployment can both verify an envelope and mint a credential;
@@ -450,7 +463,13 @@ export function defaultHandlers(deps) {
450
463
  // Bootstrap-emission deps mirror render so MCP Apps hosts that
451
464
  // forward `ui/notifications/tool-result` via postMessage can
452
465
  // re-apply patched props on the live mount without a WS round-trip.
453
- ...(deps.update.mintBootstrap ? { mintWsToken: deps.update.mintBootstrap } : {}),
466
+ // The result door issues a view key when a key-issuing minter is
467
+ // wired (ggui#1415); otherwise it mints the live credential alone.
468
+ ...(deps.update.mintViewRoot
469
+ ? { mintWsToken: deps.update.mintViewRoot }
470
+ : deps.update.mintBootstrap
471
+ ? { mintWsToken: deps.update.mintBootstrap }
472
+ : {}),
454
473
  ...(deps.update.runtimeUrl !== undefined ? { runtimeUrl: deps.update.runtimeUrl } : {}),
455
474
  ...(deps.update.sessionApiBaseUrl !== undefined
456
475
  ? { sessionApiBaseUrl: deps.update.sessionApiBaseUrl }
@@ -634,7 +653,13 @@ export function defaultHandlers(deps) {
634
653
  ...(deps.render.streamWebSocketLocalTools !== undefined
635
654
  ? { streamWebSocketLocalTools: deps.render.streamWebSocketLocalTools }
636
655
  : {}),
637
- ...(deps.render.mintBootstrap ? { mintWsToken: deps.render.mintBootstrap } : {}),
656
+ // The result door issues a view key when a key-issuing minter is
657
+ // wired (ggui#1415); otherwise it mints the live credential alone.
658
+ ...(deps.render.mintViewRoot
659
+ ? { mintWsToken: deps.render.mintViewRoot }
660
+ : deps.render.mintBootstrap
661
+ ? { mintWsToken: deps.render.mintBootstrap }
662
+ : {}),
638
663
  ...(deps.render.runtimeUrl !== undefined ? { runtimeUrl: deps.render.runtimeUrl } : {}),
639
664
  ...(deps.render.sessionApiBaseUrl !== undefined
640
665
  ? { sessionApiBaseUrl: deps.render.sessionApiBaseUrl }
@@ -750,6 +775,35 @@ export function buildOpsBundleHandlers(deps) {
750
775
  }
751
776
  return handlers;
752
777
  }
778
+ /**
779
+ * How long {@link GguiServer.close} lets active connections finish before
780
+ * it ends them (ggui#1631), in milliseconds.
781
+ */
782
+ export const DEFAULT_CLOSE_GRACE_MS = 5000;
783
+ /**
784
+ * The ws-token TTL and refresh window a server mints with (ggui#1496 part
785
+ * B). Both are positive whole seconds, and the window is at least the TTL:
786
+ * then every token this server mints, root and renewal alike, is past its
787
+ * `exp` by `rootIat + window`, so every gate refuses it in the same second.
788
+ * (A token minted while the window was longer, or by a server sharing the
789
+ * secret with a longer window, can outlive that second on the other gates;
790
+ * only `/state` refuses it.)
791
+ */
792
+ function resolveWsTokenLifetimes(opts) {
793
+ const wholePositive = (v) => Number.isInteger(v) && v > 0;
794
+ const ttlSec = opts.wsTokenTtlSec ?? DEFAULT_WS_TOKEN_TTL_SEC;
795
+ if (!wholePositive(ttlSec)) {
796
+ throw new Error(`createGguiServer: wsTokenTtlSec must be a positive whole number of seconds (got ${String(ttlSec)}).`);
797
+ }
798
+ const windowSec = opts.wsTokenRefreshWindowSec ?? DEFAULT_WS_TOKEN_REFRESH_WINDOW_MULTIPLIER * ttlSec;
799
+ if (!wholePositive(windowSec)) {
800
+ throw new Error(`createGguiServer: wsTokenRefreshWindowSec must be a positive whole number of seconds (got ${String(windowSec)}).`);
801
+ }
802
+ if (windowSec < ttlSec) {
803
+ throw new Error(`createGguiServer: wsTokenRefreshWindowSec (${windowSec}) must be at least wsTokenTtlSec (${ttlSec}); a shorter window would let a root token outlive its own renewal chain.`);
804
+ }
805
+ return { ttlSec, windowSec };
806
+ }
753
807
  /**
754
808
  * Build a runnable OSS MCP server. Every option has a sensible default
755
809
  * so `createGguiServer()` with no arguments boots a working in-memory
@@ -759,6 +813,11 @@ export function createGguiServer(opts = {}) {
759
813
  if ((opts.perAppRouting?.perAppOnlySources?.length ?? 0) > 0 && !opts.perAppRouting?.authorize) {
760
814
  throw new Error("createGguiServer: perAppRouting.perAppOnlySources lists credential sources that are valid only at their app's endpoint, but perAppRouting.authorize is not wired");
761
815
  }
816
+ const { ttlSec: wsTokenTtlSec, windowSec: wsTokenRefreshWindowSec } = resolveWsTokenLifetimes(opts);
817
+ const keyedSince = opts.viewProof?.keyedSince;
818
+ if (keyedSince !== undefined && (!Number.isFinite(keyedSince) || keyedSince < 0)) {
819
+ throw new Error(`createGguiServer: viewProof.keyedSince must be a finite, non-negative epoch in milliseconds (got ${String(keyedSince)}).`);
820
+ }
762
821
  const info = { ...DEFAULT_INFO, ...opts.info };
763
822
  const logger = opts.logger ?? createConsoleLogger({ server: info.name });
764
823
  const bodyLimit = opts.bodyLimit ?? "4mb";
@@ -973,12 +1032,23 @@ export function createGguiServer(opts = {}) {
973
1032
  // (which always call this on the live-mode path) get clean strings
974
1033
  // without per-callsite narrowing.
975
1034
  let mintBootstrap;
1035
+ // The key-issuing doors' minters (ggui#1415): a root ws token stamped
1036
+ // with the secret's key id and the door, and the view key rooted in it.
1037
+ // Only the render/update results (`result`) and the data-plane read door
1038
+ // (`read`), on a read that issues the key, use them; every bearer door,
1039
+ // and every read that issues no key, mints through `mintBootstrap`.
1040
+ let mintResultViewRoot;
1041
+ let mintReadViewRoot;
1042
+ // `/state`'s one minter (ggui#1496 part B, slice 2): a possession renewal
1043
+ // that carries its chain's root forward. Every other door mints a root
1044
+ // through `mintBootstrap`.
1045
+ let mintChainedBootstrap;
976
1046
  let channelBootstrap;
977
1047
  // The authorized refresh's envelope check (ggui#1496 part B): signature,
978
1048
  // shape and kind at ANY age, over the same secret the minter signs with.
979
1049
  let wsTokenVerify;
980
- // Shared HMAC secret for server-minted creds (bootstrap tokens,
981
- // session tokens, console cookies). Distinct `kind` claims
1050
+ // Shared HMAC secret for server-minted creds (ws bootstrap tokens,
1051
+ // console cookies). Distinct `kind` claims
982
1052
  // prevent cross-kind confusion; sharing the secret keeps the
983
1053
  // config surface small and means operators rotate ONE value.
984
1054
  // Resolved here so both the MCP Apps block and the console
@@ -999,7 +1069,7 @@ export function createGguiServer(opts = {}) {
999
1069
  // from `@ggui-ai/mcp-server-core` for callers that need explicit
1000
1070
  // single-use semantics (one-time-link share, etc.).
1001
1071
  const syncMinter = (sessionId, appId) => {
1002
- const { token, claims } = mintWsToken({ sessionId, appId }, secret);
1072
+ const { token, claims } = mintWsToken({ sessionId, appId, ttlSec: wsTokenTtlSec }, secret);
1003
1073
  return {
1004
1074
  wsUrl,
1005
1075
  token,
@@ -1007,6 +1077,36 @@ export function createGguiServer(opts = {}) {
1007
1077
  };
1008
1078
  };
1009
1079
  mintBootstrap = syncMinter;
1080
+ const viewRootMinter = (src) => (sessionId, appId) => {
1081
+ const minted = mintViewRoot({ sessionId, appId, ttlSec: wsTokenTtlSec, src }, secret);
1082
+ if (minted.viewKeyNotIssued !== undefined) {
1083
+ logger.info("view_key_not_issued", { sessionId, src, reason: minted.viewKeyNotIssued });
1084
+ }
1085
+ return {
1086
+ wsUrl,
1087
+ token: minted.token,
1088
+ expiresAt: new Date(minted.claims.exp * 1000).toISOString(),
1089
+ viewKey: minted.viewKey,
1090
+ };
1091
+ };
1092
+ mintResultViewRoot = viewRootMinter("result");
1093
+ mintReadViewRoot = viewRootMinter("read");
1094
+ // The renewal's `exp` is capped at `rootIat + window` by the mint
1095
+ // itself (`notAfter`), from the same clock read that stamps its `iat`.
1096
+ // No second left means nothing to renew (`undefined`, answered as the
1097
+ // ordinary expiry). That happens when the bound's second arrives between
1098
+ // the route's expiry check and this mint (the route awaits the store in
1099
+ // between), or for a token minted while the window was longer.
1100
+ mintChainedBootstrap = (sessionId, appId, rootIat) => {
1101
+ const { token, claims } = mintWsToken({ sessionId, appId, ttlSec: wsTokenTtlSec, rootIat, notAfter: rootIat + wsTokenRefreshWindowSec }, secret);
1102
+ if (claims.exp <= claims.iat)
1103
+ return undefined;
1104
+ return {
1105
+ wsUrl,
1106
+ token,
1107
+ expiresAt: new Date(claims.exp * 1000).toISOString(),
1108
+ };
1109
+ };
1010
1110
  channelBootstrap = {
1011
1111
  verify: (token) => {
1012
1112
  const result = verifyToken(token, secret, "ws");
@@ -1025,15 +1125,16 @@ export function createGguiServer(opts = {}) {
1025
1125
  }
1026
1126
  return { ok: false, reason: "invalid" };
1027
1127
  },
1028
- issueSessionToken: (sessionId, appId) => {
1029
- const { token } = mintSessionToken({ sessionId, appId }, secret);
1030
- return token;
1031
- },
1032
1128
  };
1033
1129
  wsTokenVerify = (envelope) => {
1034
1130
  const result = verifyWsTokenSignature(envelope, secret);
1035
1131
  return result.ok
1036
- ? { ok: true, sessionId: result.claims.sessionId, appId: result.claims.appId, iat: result.claims.iat }
1132
+ ? {
1133
+ ok: true,
1134
+ sessionId: result.claims.sessionId,
1135
+ appId: result.claims.appId,
1136
+ rootIat: result.claims.rootIat ?? result.claims.iat,
1137
+ }
1037
1138
  : { ok: false };
1038
1139
  };
1039
1140
  }
@@ -1140,7 +1241,6 @@ export function createGguiServer(opts = {}) {
1140
1241
  new InMemoryBlueprintStore({
1141
1242
  embeddingProvider: opts.embedding,
1142
1243
  });
1143
- const blueprintSelector = opts.blueprintSelector ?? createDeterministicBlueprintSelector();
1144
1244
  // Auto-seed the multi-axis search against the resolved store.
1145
1245
  // Type-narrowed at the call site: when the caller passed
1146
1246
  // their own store we can't assume it implements `listAllForApp`,
@@ -1423,6 +1523,7 @@ export function createGguiServer(opts = {}) {
1423
1523
  ? { postFailureHook: opts.renderPostFailureHook }
1424
1524
  : {}),
1425
1525
  ...(mintBootstrap ? { mintBootstrap } : {}),
1526
+ ...(mintResultViewRoot ? { mintViewRoot: mintResultViewRoot } : {}),
1426
1527
  // The authorized refresh's envelope check (ggui#1496 part B),
1427
1528
  // over the same secret as `mintBootstrap`. Absent when MCP
1428
1529
  // Apps isn't enabled; the tool isn't registered then.
@@ -1499,25 +1600,7 @@ export function createGguiServer(opts = {}) {
1499
1600
  // every story-path render + emits `cache.hit /
1500
1601
  // similarity / cachedBlueprintId / llmCallsAvoided` on
1501
1602
  // structuredContent.
1502
- ...(generationWithCache
1503
- ? {
1504
- generation: {
1505
- ...generationWithCache,
1506
- // Thread an LLMCaller resolver into
1507
- // render so the registry-based three-tier matcher
1508
- // can fire its rerank step. Per-call resolution
1509
- // because BYOK creds depend on ctx.
1510
- resolveLlmCaller: async (ctx) => {
1511
- if (!generationWithCache.resolveLlm)
1512
- return null;
1513
- const creds = await generationWithCache.resolveLlm(ctx);
1514
- if (!creds)
1515
- return null;
1516
- return buildLlmCaller(creds.selection, creds.providerKey);
1517
- },
1518
- },
1519
- }
1520
- : {}),
1603
+ ...(generationWithCache ? { generation: generationWithCache } : {}),
1521
1604
  // Live-subscriber notifier. Late-binds to the
1522
1605
  // GguiSessionChannelServer created further down (`channel`
1523
1606
  // / `channelForHealth`) — the channel doesn't exist yet
@@ -1651,6 +1734,7 @@ export function createGguiServer(opts = {}) {
1651
1734
  // resolvers keeps the two transports byte-identical at the
1652
1735
  // bootstrap-projection boundary.
1653
1736
  ...(mintBootstrap ? { mintBootstrap } : {}),
1737
+ ...(mintResultViewRoot ? { mintViewRoot: mintResultViewRoot } : {}),
1654
1738
  // Function form: matches render so ggui_update emits the
1655
1739
  // same absolute URL when the server sits behind a tunnel
1656
1740
  // / reverse proxy.
@@ -1762,6 +1846,30 @@ export function createGguiServer(opts = {}) {
1762
1846
  // construction instead of at first `tools/call`. The actual route
1763
1847
  // mounting happens below alongside the canonical routes.
1764
1848
  const mcpServices = validateMcpServices(opts.mcpServices);
1849
+ // ggui#1415 — the view-origin proof's measuring gate, over every handler
1850
+ // this server serves (the data and control planes, mounts, and isolated
1851
+ // services). Every declared proof must be one a view can sign (refused at
1852
+ // boot otherwise). The gate runs where views are keyed (MCP Apps on);
1853
+ // without it, a declared call's line says `viewProofUnverifiable`, named
1854
+ // once here.
1855
+ const servedHandlers = [...handlers, ...mcpServices.flatMap((service) => service.handlers)];
1856
+ assertViewProofDeclarations(servedHandlers);
1857
+ const viewProofGate = mcpAppsEnabled && sharedTokenSecret !== undefined
1858
+ ? createViewProofGate({
1859
+ secret: sharedTokenSecret,
1860
+ sessionStore: renderStore,
1861
+ ...(keyedSince !== undefined ? { keyedSince } : {}),
1862
+ })
1863
+ : undefined;
1864
+ if (viewProofGate === undefined) {
1865
+ const unverifiable = unverifiableViewProofTools(servedHandlers);
1866
+ if (unverifiable.length > 0) {
1867
+ logger.warn("view_proof_unverifiable", {
1868
+ tools: unverifiable,
1869
+ hint: "These tools declare a view proof. MCP Apps is off, so this server keys no view and does not verify proofs.",
1870
+ });
1871
+ }
1872
+ }
1765
1873
  // The control plane — every `protocol`- and `ops`-tagged handler,
1766
1874
  // projected onto one anonymous-capable route with per-tool auth and
1767
1875
  // confirmation gates. Built from the SAME composed `handlers` array
@@ -1860,10 +1968,12 @@ export function createGguiServer(opts = {}) {
1860
1968
  // pass a stable `csrfSecret` so tokens survive a restart.
1861
1969
  app.use(createSecurityHeadersMiddleware());
1862
1970
  // Resolved once at boot — every per-request `buildMcpServer` call
1863
- // receives the same string, so `tools/list` + `initialize` stay
1864
- // consistent across the lifetime of the process. Operators tune
1865
- // via `mcpInstructions: 'default' | 'aggressive' | 'minimal' | 'off'`
1866
- // or a custom string. See `instructions-presets.ts` for full copy.
1971
+ // receives the same string, so every `initialize` result carries the
1972
+ // same instructions for the life of the process. Operators opt in via
1973
+ // `mcpInstructions: 'default' | 'aggressive' | 'always' | 'minimal'` or
1974
+ // a custom string; unset or `'off'` sends none (ggui#1579). The boot
1975
+ // line says what is SENT, so it can never again name text no host got.
1976
+ // See `instructions-presets.ts` for the copy.
1867
1977
  const resolvedInstructions = resolveMcpInstructions(opts.mcpInstructions);
1868
1978
  if (resolvedInstructions) {
1869
1979
  logger.info("mcp_instructions_set", {
@@ -1874,15 +1984,19 @@ export function createGguiServer(opts = {}) {
1874
1984
  opts.mcpInstructions === "minimal" ||
1875
1985
  opts.mcpInstructions === "off")
1876
1986
  ? opts.mcpInstructions
1877
- : opts.mcpInstructions === undefined
1878
- ? "default (no-preset fallback)"
1879
- : "custom",
1987
+ : "custom",
1880
1988
  length: resolvedInstructions.length,
1989
+ sentInInitialize: true,
1881
1990
  });
1882
1991
  }
1883
1992
  else {
1884
1993
  logger.info("mcp_instructions_off", {
1885
- reason: opts.mcpInstructions === "off" ? "preset=off" : "empty custom string",
1994
+ reason: opts.mcpInstructions === undefined
1995
+ ? "unset (the default is off)"
1996
+ : opts.mcpInstructions === "off"
1997
+ ? "preset=off"
1998
+ : "empty custom string",
1999
+ sentInInitialize: false,
1886
2000
  });
1887
2001
  }
1888
2002
  const csrfSecret = opts.csrfSecret && opts.csrfSecret.length >= 32
@@ -2026,6 +2140,7 @@ export function createGguiServer(opts = {}) {
2026
2140
  // endpoint family spreads a fresh copy per request.
2027
2141
  const buildMcpOptions = {
2028
2142
  mcpAppsOutbound: mcpAppsEnabled,
2143
+ ...(viewProofGate !== undefined ? { viewProofGate } : {}),
2029
2144
  // Caller-provided `shellHtml` overrides the default (and the
2030
2145
  // inline-runtime shell); `installMcpAppsOutbound` falls back to
2031
2146
  // its baked `GGUI_RENDER_SHELL_HTML` constant when both are
@@ -2198,6 +2313,10 @@ export function createGguiServer(opts = {}) {
2198
2313
  // Mirrors the per-tool `mintBootstrap` plumbed into
2199
2314
  // the handler factory above.
2200
2315
  ...(mintBootstrap ? { mintWsToken: mintBootstrap } : {}),
2316
+ // The data-plane read door issues a view key (ggui#1415); every
2317
+ // other mount, and an anonymous caller, mints through
2318
+ // `mintBootstrap` alone.
2319
+ ...(mintReadViewRoot ? { mintViewRoot: mintReadViewRoot } : {}),
2201
2320
  },
2202
2321
  }
2203
2322
  : {}),
@@ -2267,9 +2386,10 @@ export function createGguiServer(opts = {}) {
2267
2386
  // R6 /state snapshot + R7 /events cursor-replay reads — see
2268
2387
  // `./api-renders-routes.ts` for the wsToken auth posture, binding +
2269
2388
  // app-scope gates, and response taxonomy. Mounted only when MCP Apps is on,
2270
- // a render store is resolved, and a token secret exists (the same
2271
- // preconditions the credential minter needs).
2272
- if (mcpAppsEnabled && renderStore && sharedTokenSecret !== undefined) {
2389
+ // a render store is resolved, and a token secret exists. MCP Apps being on
2390
+ // is also what assigns the two minters, so the last conjunct only narrows
2391
+ // the type: it never keeps a deployment from mounting.
2392
+ if (mcpAppsEnabled && renderStore && sharedTokenSecret !== undefined && mintChainedBootstrap) {
2273
2393
  mountApiRendersRoutes({
2274
2394
  app,
2275
2395
  renderStore,
@@ -2287,7 +2407,7 @@ export function createGguiServer(opts = {}) {
2287
2407
  // composes (ggui#522) — session-API URLs keep the public origin.
2288
2408
  ...(opts.codeBaseUrl !== undefined ? { codeBaseUrl: opts.codeBaseUrl } : {}),
2289
2409
  ...(mintCodeModuleUrl !== undefined ? { mintCodeModuleUrl } : {}),
2290
- ...(mintBootstrap ? { mintBootstrap } : {}),
2410
+ mintChainedBootstrap,
2291
2411
  resolveRuntimeUrl: resolveRuntimeUrlForResultMeta,
2292
2412
  logger,
2293
2413
  });
@@ -3138,7 +3258,6 @@ export function createGguiServer(opts = {}) {
3138
3258
  adminToken: resolvedAdminToken,
3139
3259
  generators: generators ?? null,
3140
3260
  blueprintStore,
3141
- blueprintSelector,
3142
3261
  blueprintSearch,
3143
3262
  async listen(port = 0, host = opts.host ?? "127.0.0.1") {
3144
3263
  // The Origin/Host validation policy (ggui#438a) was built above
@@ -3222,14 +3341,28 @@ export function createGguiServer(opts = {}) {
3222
3341
  }
3223
3342
  });
3224
3343
  },
3225
- async close() {
3344
+ async close(options = {}) {
3226
3345
  if (channel)
3227
3346
  await channel.close();
3228
3347
  const server = httpServer;
3229
3348
  if (!server)
3230
3349
  return;
3350
+ const graceMs = options.graceMs ?? DEFAULT_CLOSE_GRACE_MS;
3231
3351
  await new Promise((resolve, reject) => {
3232
- server.close((err) => (err ? reject(err) : resolve()));
3352
+ // ggui#1631: after the grace, end what is still open. The timer is
3353
+ // unref'd so it never keeps the process alive on its own.
3354
+ const deadline = Number.isFinite(graceMs)
3355
+ ? setTimeout(() => server.closeAllConnections(), Math.max(0, graceMs))
3356
+ : undefined;
3357
+ deadline?.unref();
3358
+ server.close((err) => {
3359
+ if (deadline !== undefined)
3360
+ clearTimeout(deadline);
3361
+ if (err)
3362
+ reject(err);
3363
+ else
3364
+ resolve();
3365
+ });
3233
3366
  });
3234
3367
  httpServer = null;
3235
3368
  },
package/dist/storage.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { GguiSessionStore, ThreadStore, VectorStore } from '@ggui-ai/mcp-server-core';
1
+ import type { GguiSessionStore, GguiSessionStreamBuffer, ThreadStore, VectorStore } from '@ggui-ai/mcp-server-core';
2
2
  import type { StorageConfig } from '@ggui-ai/project-config';
3
3
  export interface ResolveStorageFromConfigOptions {
4
4
  /**
@@ -49,6 +49,14 @@ export interface ResolvedStorageStores {
49
49
  * `/ggui/health` advertisement matches the active store.
50
50
  */
51
51
  readonly threadDurability?: 'durable' | 'ephemeral';
52
+ /**
53
+ * The live channel's stream buffer, present iff `renders` declares
54
+ * sqlite: a `SqliteGguiSessionStreamBuffer` on the same database file,
55
+ * so a session that survives a restart also keeps its stream `seq`, its
56
+ * epoch and its retained envelopes (ggui#1534). Absent otherwise, and
57
+ * `createGguiServer` keeps its in-memory buffer.
58
+ */
59
+ readonly streamBuffer?: GguiSessionStreamBuffer;
52
60
  }
53
61
  /**
54
62
  * Instantiate the concrete storage adapters declared in a parsed
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAqDA,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAQlC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,+BAA+B;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC;oEACgE;IAChE,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IACxC;oEACgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B;;;;;;;;;;;;;;;;;yCAiBqC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;CACrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,IAAI,GAAE,+BAAoC,GACzC,OAAO,CAAC,qBAAqB,CAAC,CAgDhC"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAqDA,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACvB,WAAW,EACX,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAQlC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,+BAA+B;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC;oEACgE;IAChE,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IACxC;oEACgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B;;;;;;;;;;;;;;;;;yCAiBqC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;IACpD;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC;CACjD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,IAAI,GAAE,+BAAoC,GACzC,OAAO,CAAC,qBAAqB,CAAC,CAkDhC"}
package/dist/storage.js CHANGED
@@ -41,9 +41,9 @@
41
41
  * who want storage from config write:
42
42
  *
43
43
  * ```ts
44
- * const { renderStore, vectors } =
44
+ * const { renderStore, vectors, streamBuffer } =
45
45
  * await resolveStorageFromConfig(manifest.storage, { baseDir: projectRoot });
46
- * const server = createGguiServer({ renderStore, vectors });
46
+ * const server = createGguiServer({ renderStore, vectors, streamBuffer });
47
47
  * ```
48
48
  *
49
49
  * Explicit instances passed to `createGguiServer` still win — this
@@ -113,11 +113,12 @@ export async function resolveStorageFromConfig(config, opts = {}) {
113
113
  // Single dynamic import serves every sqlite adapter — better-sqlite3's
114
114
  // N-API binding loads once even if the import lands twice; the
115
115
  // subpath barrel is cached by Node's module loader.
116
- const { SqliteGguiSessionStore, SqliteVectorStore, SqliteThreadStore } = await import('@ggui-ai/mcp-server-core/sqlite');
116
+ const { SqliteGguiSessionStore, SqliteGguiSessionStreamBuffer, SqliteVectorStore, SqliteThreadStore } = await import('@ggui-ai/mcp-server-core/sqlite');
117
117
  if (config.renders && config.renders.driver === 'sqlite') {
118
118
  const filename = resolveStoragePath(config.renders.path, opts.baseDir);
119
119
  ensureParentDir(filename);
120
120
  result.renderStore = new SqliteGguiSessionStore({ filename });
121
+ result.streamBuffer = new SqliteGguiSessionStreamBuffer({ filename });
121
122
  }
122
123
  if (config.vectors && config.vectors.driver === 'sqlite') {
123
124
  const filename = resolveStoragePath(config.vectors.path, opts.baseDir);
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The view-origin proof's measuring gate (ggui#1415, release N): for a tool
3
+ * that declares a view proof, verify the call's proof before the handler
4
+ * runs, put the verdict on the handler context, and name it on the
5
+ * `tool_invoked` line. It never refuses a call and never fails one: at this
6
+ * release the proof is measured, not enforced.
7
+ *
8
+ * Built once per server (`createViewProofGate`, holding the secret the ws
9
+ * envelopes are signed with) and run per call (`runViewProofGate`) from
10
+ * `buildMcpServer`'s per-tool callback.
11
+ */
12
+ import { ViewProofRepeatCache, type GguiSessionStore, type ViewProofVerdict, type VerifyViewProofInput } from '@ggui-ai/mcp-server-core';
13
+ import { type HandlerContext, type SharedHandler } from '@ggui-ai/mcp-server-handlers';
14
+ import { type ViewRootSrc } from '@ggui-ai/protocol/integrations/mcp-apps';
15
+ import type { ZodRawShape } from 'zod';
16
+ /**
17
+ * Which era a call's session belongs to, for a `required` call whose proof
18
+ * is not valid; the first match wins. `row_absent`: no store, no row, or
19
+ * the read failed. `foreign`: another app owns the row. `unkeyed`: the
20
+ * session's ids are too long for a view key. `legacy`: created before
21
+ * `keyedSince`, or `keyedSince` is unset. `current`: everything else.
22
+ */
23
+ export type ViewProofEra = 'current' | 'legacy' | 'unkeyed' | 'foreign' | 'row_absent';
24
+ /** How old the proof's root is on the server's clock (`now − rootIat`). */
25
+ export type ViewProofRootAge = 'lt_3m' | 'lt_1h' | 'lt_1d' | 'lt_7d' | 'lt_30d' | 'ge_30d';
26
+ /**
27
+ * The view clock against the server's (`vtime − now`), bucketed at 30 s,
28
+ * 5 min and 1 h on each side. Relay latency makes it negative (`behind`),
29
+ * so `ahead` can only be the view's clock running ahead.
30
+ */
31
+ export type ViewProofSkew = 'behind_ge_1h' | 'behind_lt_1h' | 'behind_lt_5m' | 'within_30s' | 'ahead_lt_5m' | 'ahead_lt_1h' | 'ahead_ge_1h';
32
+ /**
33
+ * The proof's fields on a `tool_invoked` line: ids, flags and buckets only.
34
+ * Never the proof, its root, its nonce, its tags, the view key or any
35
+ * argument.
36
+ */
37
+ export interface ViewProofLineFields {
38
+ readonly viewProof?: ViewProofVerdict['verdict'];
39
+ readonly viewProofReason?: string;
40
+ readonly viewProofErrorClass?: string;
41
+ /** Valid only: the session the proof's root names (it equals the call's). */
42
+ readonly viewProofSessionId?: string;
43
+ /** Not valid: the call's session as the caller claims it, capped. */
44
+ readonly claimedSessionId?: string;
45
+ readonly claimedSessionIdTruncated?: true;
46
+ readonly viewProofRootAge?: ViewProofRootAge;
47
+ readonly viewProofRootSrc?: ViewRootSrc;
48
+ readonly viewProofSkew?: ViewProofSkew;
49
+ /** Dispatch only: the gesture's user-activation flag, self-asserted by any key holder. */
50
+ readonly viewProofActivation?: boolean;
51
+ /** This replica saw the same `(session, nonce)` within the repeat window. */
52
+ readonly viewProofRepeat?: true;
53
+ readonly viewProofEra?: ViewProofEra;
54
+ /** The tool declares a proof and the server has no verifier. */
55
+ readonly viewProofUnverifiable?: true;
56
+ }
57
+ /** A server's gate: its verifier, and what it reads to classify an era. */
58
+ export interface ViewProofGate {
59
+ verify(input: VerifyViewProofInput): ViewProofVerdict;
60
+ /** Whether a session's ids fit a view key's root. */
61
+ keyable(sessionId: string, appId: string): boolean;
62
+ readonly sessionStore: GguiSessionStore | undefined;
63
+ /** Epoch ms from which every replica of this deployment issues view keys. */
64
+ readonly keyedSince: number | undefined;
65
+ readonly repeatCache: ViewProofRepeatCache;
66
+ now(): number;
67
+ }
68
+ export interface CreateViewProofGateOptions {
69
+ /** The secret the server's ws envelopes are signed with. */
70
+ readonly secret: string;
71
+ readonly sessionStore: GguiSessionStore | undefined;
72
+ readonly keyedSince?: number;
73
+ readonly now?: () => number;
74
+ }
75
+ /** Build a server's gate once; the secret stays in its closures. */
76
+ export declare function createViewProofGate(opts: CreateViewProofGateOptions): ViewProofGate;
77
+ /**
78
+ * Refuse, at boot, a handler that declares a view proof on a tool the
79
+ * proof's bound-argument table does not name: no view can sign a call to
80
+ * it, so every call would read `missing` for a reason no one could find.
81
+ */
82
+ export declare function assertViewProofDeclarations(handlers: ReadonlyArray<Pick<SharedHandler<ZodRawShape, ZodRawShape>, 'name' | 'viewProof'>>): void;
83
+ /** The tools whose declared proof a server without a verifier cannot check. */
84
+ export declare function unverifiableViewProofTools(handlers: ReadonlyArray<Pick<SharedHandler<ZodRawShape, ZodRawShape>, 'name' | 'viewProof'>>): string[];
85
+ /** What the gate hands back: the context the handler runs with, and the line fields. */
86
+ export interface ViewProofGateResult {
87
+ readonly ctx: HandlerContext;
88
+ readonly fields: ViewProofLineFields;
89
+ }
90
+ /**
91
+ * Run the gate for one call. `gate` is undefined on a server with no
92
+ * verifier. Total: it never throws, and never changes what the handler
93
+ * answers; a declaration it cannot read leaves the call as it came.
94
+ */
95
+ export declare function runViewProofGate(gate: ViewProofGate | undefined, handler: Pick<SharedHandler<ZodRawShape, ZodRawShape>, 'name' | 'viewProof'>, input: Record<string, unknown>, ctx: HandlerContext): Promise<ViewProofGateResult>;
96
+ //# sourceMappingURL=view-proof-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-proof-gate.d.ts","sourceRoot":"","sources":["../src/view-proof-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,oBAAoB,EAGpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,aAAa,EAEnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AAEvF,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3F;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACrB,cAAc,GACd,cAAc,GACd,cAAc,GACd,YAAY,GACZ,aAAa,GACb,aAAa,GACb,aAAa,CAAC;AAElB;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,qEAAqE;IACrE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,yBAAyB,CAAC,EAAE,IAAI,CAAC;IAC1C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC;IACxC,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,0FAA0F;IAC1F,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,6EAA6E;IAC7E,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,CAAC;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC,gEAAgE;IAChE,QAAQ,CAAC,qBAAqB,CAAC,EAAE,IAAI,CAAC;CACvC;AAED,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,gBAAgB,CAAC;IACtD,qDAAqD;IACrD,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IACnD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpD,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,GAAG,IAAI,MAAM,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,0BAA0B,GAAG,aAAa,CAWnF;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,GAC3F,IAAI,CAON;AAED,+EAA+E;AAC/E,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,GAC3F,MAAM,EAAE,CAEV;AAwDD,wFAAwF;AACxF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,GAAG,SAAS,EAC/B,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,EAC5E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,mBAAmB,CAAC,CA+C9B"}