@ggui-ai/mcp-server-handlers 0.6.2 → 0.7.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.
- package/dist/ops-apps/delete-app.d.ts +26 -9
- package/dist/ops-apps/delete-app.d.ts.map +1 -1
- package/dist/ops-apps/delete-app.js +34 -11
- package/dist/ops-apps/index.d.ts +1 -1
- package/dist/ops-apps/index.d.ts.map +1 -1
- package/dist/ops-apps/index.js +1 -1
- package/dist/ops-apps/types.d.ts +32 -1
- package/dist/ops-apps/types.d.ts.map +1 -1
- package/dist/ops-apps/types.js +16 -0
- package/dist/ops-blueprint/generate.d.ts.map +1 -1
- package/dist/ops-blueprint/generate.js +6 -2
- package/dist/ops-blueprint/register.d.ts.map +1 -1
- package/dist/ops-blueprint/register.js +7 -2
- package/dist/ops-connector-keys/types.d.ts +7 -0
- package/dist/ops-connector-keys/types.d.ts.map +1 -1
- package/dist/renders/blueprint-durability.d.ts +111 -0
- package/dist/renders/blueprint-durability.d.ts.map +1 -0
- package/dist/renders/blueprint-durability.js +85 -0
- package/dist/renders/blueprint-registry.d.ts +24 -0
- package/dist/renders/blueprint-registry.d.ts.map +1 -1
- package/dist/renders/blueprint-registry.js +9 -0
- package/dist/renders/code-delivery-events.d.ts +76 -0
- package/dist/renders/code-delivery-events.d.ts.map +1 -0
- package/dist/renders/code-delivery-events.js +60 -0
- package/dist/renders/fetch-gadget-types.d.ts +7 -3
- package/dist/renders/fetch-gadget-types.d.ts.map +1 -1
- package/dist/renders/fetch-gadget-types.js +7 -3
- package/dist/renders/generation-cache.d.ts +9 -0
- package/dist/renders/generation-cache.d.ts.map +1 -1
- package/dist/renders/handshake.d.ts +5 -3
- package/dist/renders/handshake.d.ts.map +1 -1
- package/dist/renders/handshake.js +9 -4
- package/dist/renders/index.d.ts +3 -0
- package/dist/renders/index.d.ts.map +1 -1
- package/dist/renders/index.js +13 -0
- package/dist/renders/render-identity.d.ts +178 -0
- package/dist/renders/render-identity.d.ts.map +1 -0
- package/dist/renders/render-identity.js +136 -0
- package/dist/renders/render.d.ts +39 -5
- package/dist/renders/render.d.ts.map +1 -1
- package/dist/renders/render.js +198 -83
- package/dist/renders/sync-context.d.ts +13 -1
- package/dist/renders/sync-context.d.ts.map +1 -1
- package/dist/renders/sync-context.js +6 -1
- package/dist/renders/update.d.ts +15 -1
- package/dist/renders/update.d.ts.map +1 -1
- package/dist/renders/update.js +6 -1
- package/package.json +7 -7
package/dist/renders/render.js
CHANGED
|
@@ -53,6 +53,8 @@ import { handlerFailure, isHandlerFailure, } from '../types.js';
|
|
|
53
53
|
import { consumeHandshakeRecord, peekHandshakeRecord, HandshakeNotFoundError, } from './handshake.js';
|
|
54
54
|
import { evaluateProvisionalPreviewGate, finalizeProvisionalPreview, kickoffProvisionalPreview, } from './provisional-preview.js';
|
|
55
55
|
import { assertNoDuplicateGadgetHooks } from './assert-no-duplicate-gadget-hooks.js';
|
|
56
|
+
import { reportRenderCodeWriteFailed } from './code-delivery-events.js';
|
|
57
|
+
import { writeRenderIdentity } from './render-identity.js';
|
|
56
58
|
import { findBlueprintExact, readBlueprintById, registerBlueprint, } from './blueprint-registry.js';
|
|
57
59
|
import { assertGadgetsRegistered, filterDescriptorsToContract, } from './assert-gadgets.js';
|
|
58
60
|
import { fetchGadgetTypes } from './fetch-gadget-types.js';
|
|
@@ -316,6 +318,24 @@ export function createGguiRenderHandler(deps) {
|
|
|
316
318
|
// the §6 re-resolution and the cold-gen registration below both
|
|
317
319
|
// key on it, so reuse / registration stay on the same identity.
|
|
318
320
|
const effectiveVariantKey = variantKey(effectiveVariance);
|
|
321
|
+
// Contract axis of that same reuse key. Pure over the effective
|
|
322
|
+
// contract, so it is computed ONCE here and read everywhere:
|
|
323
|
+
// the §6 exact re-resolution, the wire `contractHash` field, the
|
|
324
|
+
// resource-URI segment, and the durable identity record every
|
|
325
|
+
// commit site writes. Hoisted to handler scope because the
|
|
326
|
+
// identity write-through happens at commit time — well before
|
|
327
|
+
// the wire output is assembled.
|
|
328
|
+
const effectiveContractKey = blueprintKey(effectiveContract);
|
|
329
|
+
// Bind the identity slice a commit will record. The blueprint id
|
|
330
|
+
// is per-path (a reuse knows it up front; a cold gen resolves it
|
|
331
|
+
// via registration immediately BEFORE its success commit — #460),
|
|
332
|
+
// which is why it is a parameter and the contract/variant axes
|
|
333
|
+
// are captured.
|
|
334
|
+
const identityWriterFor = (blueprintId) => (committed) => writeRenderIdentity(deps.renderIdentityStore, committed, {
|
|
335
|
+
blueprintId,
|
|
336
|
+
contractKey: effectiveContractKey,
|
|
337
|
+
variantKey: effectiveVariantKey,
|
|
338
|
+
});
|
|
319
339
|
// Resolved gadget catalog, lifted to handler scope. When
|
|
320
340
|
// `appMetadataStore` is bound, the registry-membership block
|
|
321
341
|
// below captures the catalog (App record's `gadgets`, or
|
|
@@ -606,16 +626,20 @@ export function createGguiRenderHandler(deps) {
|
|
|
606
626
|
contentType: 'application/javascript+react',
|
|
607
627
|
createdAt: nowEpochMs,
|
|
608
628
|
lastActivityAt: nowEpochMs,
|
|
609
|
-
expiresAt: nowEpochMs + DEFAULT_RENDER_TTL_MS,
|
|
629
|
+
expiresAt: nowEpochMs + (deps.renderTtlMs ?? DEFAULT_RENDER_TTL_MS),
|
|
610
630
|
eventSequence: 0,
|
|
611
631
|
};
|
|
612
632
|
try {
|
|
613
|
-
await deps.renderStore.commit({
|
|
633
|
+
const committed = await deps.renderStore.commit({
|
|
614
634
|
render: placeholder,
|
|
615
635
|
appId: ctx.appId,
|
|
616
636
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
617
637
|
});
|
|
618
638
|
placeholderCommitted = true;
|
|
639
|
+
// The placeholder is a real row a locator can address, so it
|
|
640
|
+
// gets a record too — the later in-place replacement
|
|
641
|
+
// overwrites it with the settled identity.
|
|
642
|
+
await identityWriterFor(null)(committed);
|
|
619
643
|
}
|
|
620
644
|
catch {
|
|
621
645
|
// Defensive — a placeholder-commit failure is not fatal to
|
|
@@ -671,18 +695,19 @@ export function createGguiRenderHandler(deps) {
|
|
|
671
695
|
kind: 'mcp-apps-probe',
|
|
672
696
|
createdAt: nowEpochMs,
|
|
673
697
|
lastActivityAt: nowEpochMs,
|
|
674
|
-
expiresAt: nowEpochMs + DEFAULT_RENDER_TTL_MS,
|
|
698
|
+
expiresAt: nowEpochMs + (deps.renderTtlMs ?? DEFAULT_RENDER_TTL_MS),
|
|
675
699
|
eventSequence: 0,
|
|
676
700
|
props: { intent: story.intent },
|
|
677
701
|
};
|
|
678
702
|
try {
|
|
679
|
-
await deps.renderStore.commit({
|
|
703
|
+
const committed = await deps.renderStore.commit({
|
|
680
704
|
render: probeRender,
|
|
681
705
|
appId: ctx.appId,
|
|
682
706
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
683
707
|
});
|
|
684
708
|
safelyNotifyGguiSessionCommit(deps.channelNotifier, sessionId, probeRender);
|
|
685
709
|
generatedCodeReady = true;
|
|
710
|
+
await identityWriterFor(null)(committed);
|
|
686
711
|
}
|
|
687
712
|
catch {
|
|
688
713
|
// Commit failure leaves codeReady=false; downstream synth
|
|
@@ -796,7 +821,7 @@ export function createGguiRenderHandler(deps) {
|
|
|
796
821
|
// `(contractKey, effectiveVariantKey)` and reuse a stored
|
|
797
822
|
// component for that exact variant if one exists (per-app first,
|
|
798
823
|
// then seed pools — see fan-out comment above).
|
|
799
|
-
const bp = await findExactAcrossPools(
|
|
824
|
+
const bp = await findExactAcrossPools(effectiveContractKey, effectiveVariantKey);
|
|
800
825
|
if (bp) {
|
|
801
826
|
blueprintHit = {
|
|
802
827
|
id: bp.id,
|
|
@@ -808,11 +833,18 @@ export function createGguiRenderHandler(deps) {
|
|
|
808
833
|
}
|
|
809
834
|
}
|
|
810
835
|
if (blueprintHit) {
|
|
811
|
-
|
|
836
|
+
// Reuse → the stored UUID is the materialised component id.
|
|
837
|
+
// `cache.cachedBlueprintId === blueprintId` on a hit (§9.3).
|
|
838
|
+
// Resolved BEFORE the commit so the identity record written
|
|
839
|
+
// at that commit already carries the id (no backfill needed
|
|
840
|
+
// on this path).
|
|
841
|
+
resolvedBlueprintId = blueprintHit.id;
|
|
842
|
+
generatedCodeReady = await commitCachedGguiSession(deps.renderStore, deps.provisionalPreview, deps.channelNotifier, deps.checkRenderContracts, deps.renderTtlMs, {
|
|
812
843
|
sessionId,
|
|
813
844
|
appId: ctx.appId,
|
|
814
845
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
815
846
|
story,
|
|
847
|
+
writeIdentity: identityWriterFor(blueprintHit.id),
|
|
816
848
|
cacheHit: {
|
|
817
849
|
cachedBlueprintId: blueprintHit.id,
|
|
818
850
|
similarity: blueprintHit.cosine,
|
|
@@ -874,9 +906,6 @@ export function createGguiRenderHandler(deps) {
|
|
|
874
906
|
kind: 'full-template',
|
|
875
907
|
reason: `full-template: reused stored blueprint ${blueprintHit.id}; 1 generation call avoided`,
|
|
876
908
|
};
|
|
877
|
-
// Reuse → the stored UUID is the materialised component id.
|
|
878
|
-
// `cache.cachedBlueprintId === blueprintId` on a hit (§9.3).
|
|
879
|
-
resolvedBlueprintId = blueprintHit.id;
|
|
880
909
|
}
|
|
881
910
|
else {
|
|
882
911
|
// The `.d.ts` fetch is deferred to HERE — the cold-gen
|
|
@@ -890,10 +919,57 @@ export function createGguiRenderHandler(deps) {
|
|
|
890
919
|
const resolvedGadgetTypes = resolvedAppLibraries !== undefined
|
|
891
920
|
? await fetchGadgetTypes(resolvedAppLibraries)
|
|
892
921
|
: undefined;
|
|
893
|
-
const
|
|
922
|
+
const generationCache = deps.generation.cache;
|
|
923
|
+
const outcome = await runGenerationIntoGguiSession(deps.generation, deps.renderStore, deps.provisionalPreview, deps.channelNotifier, deps.checkRenderContracts, deps.generator, deps.renderTtlMs, {
|
|
894
924
|
ctx,
|
|
895
925
|
sessionId,
|
|
896
926
|
story,
|
|
927
|
+
// #460 — the callee binds each commit's id itself:
|
|
928
|
+
// failure commits bind null; the success commit binds
|
|
929
|
+
// whatever `resolveBlueprintId` returned, resolved
|
|
930
|
+
// BEFORE that commit (no backfill exists anymore).
|
|
931
|
+
writeIdentityFor: identityWriterFor,
|
|
932
|
+
// Registration is the resolve hook, passed only when a
|
|
933
|
+
// cache is bound (unbound deployments keep null ids —
|
|
934
|
+
// #445). Failures swallow to undefined inside
|
|
935
|
+
// `safelyRegisterBlueprint`, incl. fail-closed
|
|
936
|
+
// rejections. The cache handle is captured as a const so
|
|
937
|
+
// the async closure keeps the narrowing.
|
|
938
|
+
...(generationCache
|
|
939
|
+
? {
|
|
940
|
+
resolveBlueprintId: async (produced) => safelyRegisterBlueprint({
|
|
941
|
+
embedding: generationCache.embedding,
|
|
942
|
+
vectorStore: generationCache.vectorStore,
|
|
943
|
+
index: generationCache.index,
|
|
944
|
+
// Forwarded so a fresh mint is written
|
|
945
|
+
// through to durable storage. Undefined on
|
|
946
|
+
// deployments that bound none, which
|
|
947
|
+
// `registerBlueprint` treats as a no-op.
|
|
948
|
+
...(generationCache.durability !== undefined
|
|
949
|
+
? { durability: generationCache.durability }
|
|
950
|
+
: {}),
|
|
951
|
+
}, ctx.appId, {
|
|
952
|
+
kind: 'template',
|
|
953
|
+
contract: story.contract,
|
|
954
|
+
intent,
|
|
955
|
+
componentCode: produced.componentCode,
|
|
956
|
+
// Engine provenance from the generator's own
|
|
957
|
+
// metadata stamp — a fresh generation mints
|
|
958
|
+
// an `llm`-sourced row.
|
|
959
|
+
source: produced.source,
|
|
960
|
+
// Register under the EFFECTIVE variance
|
|
961
|
+
// (proposed on accept, re-aimed on
|
|
962
|
+
// `override.variance`) so the row's
|
|
963
|
+
// `variantKey` equals `effectiveVariantKey`
|
|
964
|
+
// — the same `(contractKey, variantKey)`
|
|
965
|
+
// identity the §6 re-resolution and the wire
|
|
966
|
+
// output key on.
|
|
967
|
+
...(effectiveVariance !== undefined
|
|
968
|
+
? { variance: effectiveVariance }
|
|
969
|
+
: {}),
|
|
970
|
+
}),
|
|
971
|
+
}
|
|
972
|
+
: {}),
|
|
897
973
|
...(runtimeProps !== undefined
|
|
898
974
|
? { runtimeProps: runtimeProps }
|
|
899
975
|
: {}),
|
|
@@ -925,36 +1001,12 @@ export function createGguiRenderHandler(deps) {
|
|
|
925
1001
|
? 'cold: generated fresh — no stored component was reused for this render'
|
|
926
1002
|
: 'cold: generation failed — no stored component was produced or reused',
|
|
927
1003
|
};
|
|
928
|
-
//
|
|
929
|
-
//
|
|
930
|
-
//
|
|
931
|
-
//
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
// equals `effectiveVariantKey` — the same
|
|
935
|
-
// `(contractKey, variantKey)` identity the §6 re-resolution
|
|
936
|
-
// and the wire output key on. Never the default sentinel when
|
|
937
|
-
// an override re-aimed the variant.
|
|
938
|
-
if (outcome.ok && outcome.componentCode && outcome.source) {
|
|
939
|
-
const registered = await safelyRegisterBlueprint({
|
|
940
|
-
embedding: deps.generation.cache.embedding,
|
|
941
|
-
vectorStore: deps.generation.cache.vectorStore,
|
|
942
|
-
index: deps.generation.cache.index,
|
|
943
|
-
}, ctx.appId, {
|
|
944
|
-
kind: 'template',
|
|
945
|
-
contract: story.contract,
|
|
946
|
-
intent,
|
|
947
|
-
componentCode: outcome.componentCode,
|
|
948
|
-
// Engine provenance travels on the outcome — the
|
|
949
|
-
// generator stamped its own slug + the resolved
|
|
950
|
-
// model id into metadata, and a fresh generation
|
|
951
|
-
// mints an `llm`-sourced row.
|
|
952
|
-
source: outcome.source,
|
|
953
|
-
...(effectiveVariance !== undefined
|
|
954
|
-
? { variance: effectiveVariance }
|
|
955
|
-
: {}),
|
|
956
|
-
});
|
|
957
|
-
resolvedBlueprintId = registered;
|
|
1004
|
+
// #460 — registration already ran INSIDE the generation
|
|
1005
|
+
// call, before the success commit (the resolve hook
|
|
1006
|
+
// above); the outcome carries the id the committed
|
|
1007
|
+
// identity record was written with. No backfill exists.
|
|
1008
|
+
if (outcome.ok) {
|
|
1009
|
+
resolvedBlueprintId = outcome.blueprintId;
|
|
958
1010
|
}
|
|
959
1011
|
}
|
|
960
1012
|
}
|
|
@@ -990,15 +1042,16 @@ export function createGguiRenderHandler(deps) {
|
|
|
990
1042
|
contentType: 'application/javascript+react',
|
|
991
1043
|
createdAt: nowEpochMs,
|
|
992
1044
|
lastActivityAt: nowEpochMs,
|
|
993
|
-
expiresAt: nowEpochMs + DEFAULT_RENDER_TTL_MS,
|
|
1045
|
+
expiresAt: nowEpochMs + (deps.renderTtlMs ?? DEFAULT_RENDER_TTL_MS),
|
|
994
1046
|
eventSequence: 0,
|
|
995
1047
|
};
|
|
996
1048
|
try {
|
|
997
|
-
await deps.renderStore.commit({
|
|
1049
|
+
const committed = await deps.renderStore.commit({
|
|
998
1050
|
render: placeholder,
|
|
999
1051
|
appId: ctx.appId,
|
|
1000
1052
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
1001
1053
|
});
|
|
1054
|
+
await identityWriterFor(null)(committed);
|
|
1002
1055
|
}
|
|
1003
1056
|
catch {
|
|
1004
1057
|
// Defensive, matching the provisional-preview placeholder
|
|
@@ -1015,10 +1068,30 @@ export function createGguiRenderHandler(deps) {
|
|
|
1015
1068
|
// several branches above (cache-hit, fresh generation, MCP Apps
|
|
1016
1069
|
// inbound) — re-reading is simpler than threading a reference
|
|
1017
1070
|
// through every branch and matches resultMeta's own pattern.
|
|
1018
|
-
//
|
|
1019
|
-
//
|
|
1020
|
-
//
|
|
1021
|
-
//
|
|
1071
|
+
//
|
|
1072
|
+
// A store failure does NOT fail the render: the code is already
|
|
1073
|
+
// generated and the row already committed. But nothing catches
|
|
1074
|
+
// the envelope either — the guard above narrowed this to a
|
|
1075
|
+
// compiled-component render, and `codeUrl` is the only STATIC
|
|
1076
|
+
// delivery surface such a render has (the other static mode,
|
|
1077
|
+
// system-card `kind`, is excluded by that same guard). What
|
|
1078
|
+
// survives is deployment-shaped, and no arm of it is visible on
|
|
1079
|
+
// the wire:
|
|
1080
|
+
//
|
|
1081
|
+
// - `mintWsToken` wired ⇒ the slice still carries the live
|
|
1082
|
+
// trio, the iframe subscribes, and the WS delivers the
|
|
1083
|
+
// render body. Cost: the zero-round-trip first paint.
|
|
1084
|
+
// - A host that resolves `resourceUri` re-mints `codeUrl`
|
|
1085
|
+
// against this same store at READ time, so a transient
|
|
1086
|
+
// fault costs it nothing.
|
|
1087
|
+
// - Neither ⇒ this envelope's slice keeps `runtimeUrl` and no
|
|
1088
|
+
// mode discriminator, which hosts read as "not a mountable
|
|
1089
|
+
// ggui render".
|
|
1090
|
+
//
|
|
1091
|
+
// Hence the named event rather than a swallow — see
|
|
1092
|
+
// `code-delivery-events.ts`. A render with no `componentCode`
|
|
1093
|
+
// (the placeholder / system arms) is not a failure and emits
|
|
1094
|
+
// nothing: there was never a body to deliver.
|
|
1022
1095
|
let codeUrl;
|
|
1023
1096
|
let codeHash;
|
|
1024
1097
|
if (deps.codeStore && deps.codeBaseUrl) {
|
|
@@ -1037,8 +1110,17 @@ export function createGguiRenderHandler(deps) {
|
|
|
1037
1110
|
codeUrl = `${base}/code/${hash}.js`;
|
|
1038
1111
|
}
|
|
1039
1112
|
}
|
|
1040
|
-
catch {
|
|
1041
|
-
//
|
|
1113
|
+
catch (err) {
|
|
1114
|
+
// Named for the OUTCOME, not the call: a throwing
|
|
1115
|
+
// `renderStore.get` or `hashOf` reaches here too, and the
|
|
1116
|
+
// consequence is identical — this envelope has no codeUrl.
|
|
1117
|
+
// `error` carries which one it was.
|
|
1118
|
+
reportRenderCodeWriteFailed({
|
|
1119
|
+
sessionId,
|
|
1120
|
+
appId: ctx.appId,
|
|
1121
|
+
liveChannelWired: deps.mintWsToken !== undefined,
|
|
1122
|
+
cause: err,
|
|
1123
|
+
});
|
|
1042
1124
|
}
|
|
1043
1125
|
}
|
|
1044
1126
|
// Per-render theme overlay. The commit paths above
|
|
@@ -1064,11 +1146,16 @@ export function createGguiRenderHandler(deps) {
|
|
|
1064
1146
|
top.type !== 'mcpApps' &&
|
|
1065
1147
|
top.type !== 'system') {
|
|
1066
1148
|
const overlaid = { ...top, themeId: parsed.themeId };
|
|
1067
|
-
await deps.renderStore.commit({
|
|
1149
|
+
const committed = await deps.renderStore.commit({
|
|
1068
1150
|
render: overlaid,
|
|
1069
1151
|
appId: ctx.appId,
|
|
1070
1152
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
1071
1153
|
});
|
|
1154
|
+
// Last commit of the render when a theme override is in
|
|
1155
|
+
// play, and every reuse / cold-gen path has settled by
|
|
1156
|
+
// here — so this write carries the final blueprint id
|
|
1157
|
+
// (already resolved before the success commit, #460).
|
|
1158
|
+
await identityWriterFor(resolvedBlueprintId ?? null)(committed);
|
|
1072
1159
|
}
|
|
1073
1160
|
}
|
|
1074
1161
|
catch (err) {
|
|
@@ -1076,14 +1163,6 @@ export function createGguiRenderHandler(deps) {
|
|
|
1076
1163
|
console.warn('[ggui_render.theme_overlay_failed]', err instanceof Error ? err.message : String(err));
|
|
1077
1164
|
}
|
|
1078
1165
|
}
|
|
1079
|
-
// Canonical-key of the resolved contract. Surface on output so
|
|
1080
|
-
// downstream consumers (resultMeta builder, cache trace,
|
|
1081
|
-
// resource URI minter) read from a single computed value
|
|
1082
|
-
// instead of recomputing against the same canonicalization.
|
|
1083
|
-
// This is the same hash the handshake returned as
|
|
1084
|
-
// `contractHash`. The variant axis pairs with this on the wire
|
|
1085
|
-
// output via `effectiveVariantKey` (computed once up top).
|
|
1086
|
-
const resolvedContractHash = blueprintKey(effectiveContract);
|
|
1087
1166
|
// ── Failure envelope (SPEC §7.1) ──────────────────────────────
|
|
1088
1167
|
//
|
|
1089
1168
|
// A failed/rejected generation returns the in-result failure
|
|
@@ -1106,7 +1185,7 @@ export function createGguiRenderHandler(deps) {
|
|
|
1106
1185
|
shortCode,
|
|
1107
1186
|
codeReady: false,
|
|
1108
1187
|
handshakeId: handshakeRecord.handshakeId,
|
|
1109
|
-
contractHash:
|
|
1188
|
+
contractHash: effectiveContractKey,
|
|
1110
1189
|
// Present-on-materialisation (§9.1): no component
|
|
1111
1190
|
// materialised, so the id is the empty sentinel.
|
|
1112
1191
|
blueprintId: '',
|
|
@@ -1128,7 +1207,7 @@ export function createGguiRenderHandler(deps) {
|
|
|
1128
1207
|
ctx,
|
|
1129
1208
|
sessionId,
|
|
1130
1209
|
contract: effectiveContract,
|
|
1131
|
-
contractHash:
|
|
1210
|
+
contractHash: effectiveContractKey,
|
|
1132
1211
|
intent: story.intent,
|
|
1133
1212
|
action,
|
|
1134
1213
|
codeReady: false,
|
|
@@ -1163,8 +1242,8 @@ export function createGguiRenderHandler(deps) {
|
|
|
1163
1242
|
// structuredContent too lets agent SDKs that strip `_meta` from
|
|
1164
1243
|
// tool_results (OpenAI Agents SDK, Google ADK) still hand a
|
|
1165
1244
|
// mount handle to their frontend without the side-channel.
|
|
1166
|
-
const blueprintSegmentForOutput =
|
|
1167
|
-
? `/${
|
|
1245
|
+
const blueprintSegmentForOutput = effectiveContractKey
|
|
1246
|
+
? `/${effectiveContractKey}`
|
|
1168
1247
|
: '';
|
|
1169
1248
|
const resourceUriForOutput = `${GGUI_RENDER_UI_META.resourceUri}/${sessionId}${blueprintSegmentForOutput}`;
|
|
1170
1249
|
const result = {
|
|
@@ -1174,7 +1253,7 @@ export function createGguiRenderHandler(deps) {
|
|
|
1174
1253
|
shortCode,
|
|
1175
1254
|
codeReady: generatedCodeReady,
|
|
1176
1255
|
handshakeId: handshakeRecord.handshakeId,
|
|
1177
|
-
contractHash:
|
|
1256
|
+
contractHash: effectiveContractKey,
|
|
1178
1257
|
// Reuse → stored UUID (§6 point-read); cold-gen → minted UUID
|
|
1179
1258
|
// (`safelyRegisterBlueprint`). Empty only on the
|
|
1180
1259
|
// genuinely-no-component branches (probe-card / generation-off),
|
|
@@ -1200,7 +1279,7 @@ export function createGguiRenderHandler(deps) {
|
|
|
1200
1279
|
ctx,
|
|
1201
1280
|
sessionId,
|
|
1202
1281
|
contract: effectiveContract,
|
|
1203
|
-
contractHash:
|
|
1282
|
+
contractHash: effectiveContractKey,
|
|
1204
1283
|
intent: story.intent,
|
|
1205
1284
|
action,
|
|
1206
1285
|
codeReady: generatedCodeReady,
|
|
@@ -1416,7 +1495,13 @@ export function createGguiRenderHandler(deps) {
|
|
|
1416
1495
|
* semantics; anything shorter would close active renders mid-lifecycle.
|
|
1417
1496
|
*/
|
|
1418
1497
|
const DEFAULT_RENDER_TTL_MS = 60 * 60 * 1000;
|
|
1419
|
-
async function runGenerationIntoGguiSession(generation, renderStore, previewDeps, channelNotifier, checkRenderContracts, generatorOverride,
|
|
1498
|
+
async function runGenerationIntoGguiSession(generation, renderStore, previewDeps, channelNotifier, checkRenderContracts, generatorOverride,
|
|
1499
|
+
/**
|
|
1500
|
+
* Render-row retention window in ms — forwarded from
|
|
1501
|
+
* `GguiRenderHandlerDeps.renderTtlMs`. `undefined` falls back to
|
|
1502
|
+
* `DEFAULT_RENDER_TTL_MS` at each commit site below.
|
|
1503
|
+
*/
|
|
1504
|
+
renderTtlMs, args) {
|
|
1420
1505
|
const { ctx, sessionId, story } = args;
|
|
1421
1506
|
const nowIso = new Date().toISOString();
|
|
1422
1507
|
const nowEpochMs = Date.now();
|
|
@@ -1449,13 +1534,14 @@ async function runGenerationIntoGguiSession(generation, renderStore, previewDeps
|
|
|
1449
1534
|
result = await generatorOverride(generateInputBase, ctx);
|
|
1450
1535
|
}
|
|
1451
1536
|
catch (err) {
|
|
1452
|
-
return commitErrorGguiSession(renderStore, previewDeps, channelNotifier, {
|
|
1537
|
+
return commitErrorGguiSession(renderStore, previewDeps, channelNotifier, renderTtlMs, {
|
|
1453
1538
|
sessionId,
|
|
1454
1539
|
appId: ctx.appId,
|
|
1455
1540
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
1456
1541
|
story,
|
|
1457
1542
|
nowIso,
|
|
1458
1543
|
nowEpochMs,
|
|
1544
|
+
writeIdentity: args.writeIdentityFor(null),
|
|
1459
1545
|
message: err instanceof Error
|
|
1460
1546
|
? `generator threw: ${err.message}`
|
|
1461
1547
|
: 'generator threw',
|
|
@@ -1472,13 +1558,14 @@ async function runGenerationIntoGguiSession(generation, renderStore, previewDeps
|
|
|
1472
1558
|
creds = await generation.resolveLlm(ctx);
|
|
1473
1559
|
}
|
|
1474
1560
|
catch (err) {
|
|
1475
|
-
return commitErrorGguiSession(renderStore, previewDeps, channelNotifier, {
|
|
1561
|
+
return commitErrorGguiSession(renderStore, previewDeps, channelNotifier, renderTtlMs, {
|
|
1476
1562
|
sessionId,
|
|
1477
1563
|
appId: ctx.appId,
|
|
1478
1564
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
1479
1565
|
story,
|
|
1480
1566
|
nowIso,
|
|
1481
1567
|
nowEpochMs,
|
|
1568
|
+
writeIdentity: args.writeIdentityFor(null),
|
|
1482
1569
|
message: err instanceof Error
|
|
1483
1570
|
? `credential resolution failed: ${err.message}`
|
|
1484
1571
|
: 'credential resolution failed',
|
|
@@ -1508,16 +1595,18 @@ async function runGenerationIntoGguiSession(generation, renderStore, previewDeps
|
|
|
1508
1595
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
1509
1596
|
nowIso,
|
|
1510
1597
|
render: fallback,
|
|
1598
|
+
writeIdentity: args.writeIdentityFor(null),
|
|
1511
1599
|
});
|
|
1512
1600
|
}
|
|
1513
1601
|
}
|
|
1514
|
-
return commitErrorGguiSession(renderStore, previewDeps, channelNotifier, {
|
|
1602
|
+
return commitErrorGguiSession(renderStore, previewDeps, channelNotifier, renderTtlMs, {
|
|
1515
1603
|
sessionId,
|
|
1516
1604
|
appId: ctx.appId,
|
|
1517
1605
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
1518
1606
|
story,
|
|
1519
1607
|
nowIso,
|
|
1520
1608
|
nowEpochMs,
|
|
1609
|
+
writeIdentity: args.writeIdentityFor(null),
|
|
1521
1610
|
message: 'no credentials available for the configured generation provider (expected env var or ~/.ggui/credentials.json entry)',
|
|
1522
1611
|
code: 'NO_CREDENTIALS',
|
|
1523
1612
|
reason: 'no-credentials',
|
|
@@ -1532,13 +1621,14 @@ async function runGenerationIntoGguiSession(generation, renderStore, previewDeps
|
|
|
1532
1621
|
});
|
|
1533
1622
|
}
|
|
1534
1623
|
catch (err) {
|
|
1535
|
-
return commitErrorGguiSession(renderStore, previewDeps, channelNotifier, {
|
|
1624
|
+
return commitErrorGguiSession(renderStore, previewDeps, channelNotifier, renderTtlMs, {
|
|
1536
1625
|
sessionId,
|
|
1537
1626
|
appId: ctx.appId,
|
|
1538
1627
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
1539
1628
|
story,
|
|
1540
1629
|
nowIso,
|
|
1541
1630
|
nowEpochMs,
|
|
1631
|
+
writeIdentity: args.writeIdentityFor(null),
|
|
1542
1632
|
message: err instanceof Error
|
|
1543
1633
|
? `generator threw: ${err.message}`
|
|
1544
1634
|
: 'generator threw',
|
|
@@ -1549,13 +1639,14 @@ async function runGenerationIntoGguiSession(generation, renderStore, previewDeps
|
|
|
1549
1639
|
}
|
|
1550
1640
|
}
|
|
1551
1641
|
if (!result.ok) {
|
|
1552
|
-
return commitErrorGguiSession(renderStore, previewDeps, channelNotifier, {
|
|
1642
|
+
return commitErrorGguiSession(renderStore, previewDeps, channelNotifier, renderTtlMs, {
|
|
1553
1643
|
sessionId,
|
|
1554
1644
|
appId: ctx.appId,
|
|
1555
1645
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
1556
1646
|
story,
|
|
1557
1647
|
nowIso,
|
|
1558
1648
|
nowEpochMs,
|
|
1649
|
+
writeIdentity: args.writeIdentityFor(null),
|
|
1559
1650
|
message: result.error.message,
|
|
1560
1651
|
// Canonical projection — cloud seam codes (VALIDATION_ERROR /
|
|
1561
1652
|
// NO_PLATFORM_KEY / PRODUCTION_FAILED) map through 1:1;
|
|
@@ -1576,7 +1667,7 @@ async function runGenerationIntoGguiSession(generation, renderStore, previewDeps
|
|
|
1576
1667
|
contentType: 'application/javascript+react',
|
|
1577
1668
|
createdAt: nowEpochMs,
|
|
1578
1669
|
lastActivityAt: nowEpochMs,
|
|
1579
|
-
expiresAt: nowEpochMs + DEFAULT_RENDER_TTL_MS,
|
|
1670
|
+
expiresAt: nowEpochMs + (renderTtlMs ?? DEFAULT_RENDER_TTL_MS),
|
|
1580
1671
|
eventSequence: 0,
|
|
1581
1672
|
...(result.response.warnings && result.response.warnings.length > 0
|
|
1582
1673
|
? { description: result.response.warnings[0] }
|
|
@@ -1617,12 +1708,30 @@ async function runGenerationIntoGguiSession(generation, renderStore, previewDeps
|
|
|
1617
1708
|
throw err;
|
|
1618
1709
|
}
|
|
1619
1710
|
}
|
|
1711
|
+
// #460 — resolve the blueprint id BEFORE the commit. Registration
|
|
1712
|
+
// was already awaited on this path (it just ran after the commit),
|
|
1713
|
+
// so the reorder adds no latency and deletes the get+put backfill —
|
|
1714
|
+
// and with it the backfill's stale-write race against the pod's
|
|
1715
|
+
// field-targeted seq advance.
|
|
1716
|
+
const producedSource = {
|
|
1717
|
+
kind: 'llm',
|
|
1718
|
+
generator: result.metadata.generator,
|
|
1719
|
+
model: result.metadata.model,
|
|
1720
|
+
};
|
|
1721
|
+
let resolvedBlueprintId;
|
|
1722
|
+
if (args.resolveBlueprintId) {
|
|
1723
|
+
resolvedBlueprintId = await args.resolveBlueprintId({
|
|
1724
|
+
componentCode: result.response.componentCode,
|
|
1725
|
+
source: producedSource,
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1620
1728
|
try {
|
|
1621
|
-
await renderStore.commit({
|
|
1729
|
+
const committed = await renderStore.commit({
|
|
1622
1730
|
render: componentRender,
|
|
1623
1731
|
appId: ctx.appId,
|
|
1624
1732
|
userId: ctx.userId, // per-user isolation (undefined for non-federated single-user)
|
|
1625
1733
|
});
|
|
1734
|
+
await args.writeIdentityFor(resolvedBlueprintId ?? null)(committed);
|
|
1626
1735
|
}
|
|
1627
1736
|
catch {
|
|
1628
1737
|
await safelyFinalizePreview(previewDeps, sessionId, 'commit-failed');
|
|
@@ -1645,11 +1754,10 @@ async function runGenerationIntoGguiSession(generation, renderStore, previewDeps
|
|
|
1645
1754
|
ok: true,
|
|
1646
1755
|
componentCode: result.response.componentCode,
|
|
1647
1756
|
createdAt: nowIso,
|
|
1648
|
-
source:
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
},
|
|
1757
|
+
source: producedSource,
|
|
1758
|
+
...(resolvedBlueprintId !== undefined
|
|
1759
|
+
? { blueprintId: resolvedBlueprintId }
|
|
1760
|
+
: {}),
|
|
1653
1761
|
};
|
|
1654
1762
|
}
|
|
1655
1763
|
/**
|
|
@@ -1670,12 +1778,13 @@ async function commitNoCredentialsCardGguiSession(renderStore, previewDeps, chan
|
|
|
1670
1778
|
const render = { ...args.render, id: args.sessionId };
|
|
1671
1779
|
let committed = false;
|
|
1672
1780
|
try {
|
|
1673
|
-
await renderStore.commit({
|
|
1781
|
+
const stored = await renderStore.commit({
|
|
1674
1782
|
render,
|
|
1675
1783
|
appId: args.appId,
|
|
1676
1784
|
userId: args.userId,
|
|
1677
1785
|
});
|
|
1678
1786
|
committed = true;
|
|
1787
|
+
await args.writeIdentity(stored);
|
|
1679
1788
|
}
|
|
1680
1789
|
catch {
|
|
1681
1790
|
// Commit rejected — preview teardown is the only honest recovery;
|
|
@@ -1709,7 +1818,9 @@ async function commitNoCredentialsCardGguiSession(renderStore, previewDeps, chan
|
|
|
1709
1818
|
* `componentCode === ''` by showing a fallback UI; the extra `error`
|
|
1710
1819
|
* field carries the operator-facing reason.
|
|
1711
1820
|
*/
|
|
1712
|
-
async function commitErrorGguiSession(renderStore, previewDeps, channelNotifier,
|
|
1821
|
+
async function commitErrorGguiSession(renderStore, previewDeps, channelNotifier,
|
|
1822
|
+
/** Forwarded from `GguiRenderHandlerDeps.renderTtlMs` (see there). */
|
|
1823
|
+
renderTtlMs, args) {
|
|
1713
1824
|
const errorRender = {
|
|
1714
1825
|
id: args.sessionId,
|
|
1715
1826
|
appId: args.appId,
|
|
@@ -1720,18 +1831,19 @@ async function commitErrorGguiSession(renderStore, previewDeps, channelNotifier,
|
|
|
1720
1831
|
contentType: 'application/javascript+react',
|
|
1721
1832
|
createdAt: args.nowEpochMs,
|
|
1722
1833
|
lastActivityAt: args.nowEpochMs,
|
|
1723
|
-
expiresAt: args.nowEpochMs + DEFAULT_RENDER_TTL_MS,
|
|
1834
|
+
expiresAt: args.nowEpochMs + (renderTtlMs ?? DEFAULT_RENDER_TTL_MS),
|
|
1724
1835
|
eventSequence: 0,
|
|
1725
1836
|
...(args.appTheme !== undefined ? { theme: args.appTheme } : {}),
|
|
1726
1837
|
};
|
|
1727
1838
|
let committed = false;
|
|
1728
1839
|
try {
|
|
1729
|
-
await renderStore.commit({
|
|
1840
|
+
const stored = await renderStore.commit({
|
|
1730
1841
|
render: errorRender,
|
|
1731
1842
|
appId: args.appId,
|
|
1732
1843
|
userId: args.userId,
|
|
1733
1844
|
});
|
|
1734
1845
|
committed = true;
|
|
1846
|
+
await args.writeIdentity(stored);
|
|
1735
1847
|
}
|
|
1736
1848
|
catch {
|
|
1737
1849
|
// Secondary failure — render store rejected the error record.
|
|
@@ -1810,7 +1922,9 @@ function isJsonObject(v) {
|
|
|
1810
1922
|
* returns `codeReady: false` so the agent observes the degraded
|
|
1811
1923
|
* state through the channel instead of a synthetic "ready" signal).
|
|
1812
1924
|
*/
|
|
1813
|
-
async function commitCachedGguiSession(renderStore, previewDeps, channelNotifier, checkRenderContracts,
|
|
1925
|
+
async function commitCachedGguiSession(renderStore, previewDeps, channelNotifier, checkRenderContracts,
|
|
1926
|
+
/** Forwarded from `GguiRenderHandlerDeps.renderTtlMs` (see there). */
|
|
1927
|
+
renderTtlMs, args) {
|
|
1814
1928
|
const nowEpochMs = Date.now();
|
|
1815
1929
|
// Cached path — project optional contract fields onto the
|
|
1816
1930
|
// ComponentGguiSession so the bootstrap-meta derivation in `resultMeta`
|
|
@@ -1824,7 +1938,7 @@ async function commitCachedGguiSession(renderStore, previewDeps, channelNotifier
|
|
|
1824
1938
|
contentType: 'application/javascript+react',
|
|
1825
1939
|
createdAt: nowEpochMs,
|
|
1826
1940
|
lastActivityAt: nowEpochMs,
|
|
1827
|
-
expiresAt: nowEpochMs + DEFAULT_RENDER_TTL_MS,
|
|
1941
|
+
expiresAt: nowEpochMs + (renderTtlMs ?? DEFAULT_RENDER_TTL_MS),
|
|
1828
1942
|
eventSequence: 0,
|
|
1829
1943
|
...(args.runtimeProps !== undefined
|
|
1830
1944
|
? { props: args.runtimeProps }
|
|
@@ -1862,11 +1976,12 @@ async function commitCachedGguiSession(renderStore, previewDeps, channelNotifier
|
|
|
1862
1976
|
}
|
|
1863
1977
|
}
|
|
1864
1978
|
try {
|
|
1865
|
-
await renderStore.commit({
|
|
1979
|
+
const committed = await renderStore.commit({
|
|
1866
1980
|
render: componentRender,
|
|
1867
1981
|
appId: args.appId,
|
|
1868
1982
|
userId: args.userId,
|
|
1869
1983
|
});
|
|
1984
|
+
await args.writeIdentity(committed);
|
|
1870
1985
|
}
|
|
1871
1986
|
catch {
|
|
1872
1987
|
await safelyFinalizePreview(previewDeps, args.sessionId, 'commit-failed');
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
* IS the addressable scope.
|
|
53
53
|
*/
|
|
54
54
|
import { z } from 'zod';
|
|
55
|
-
import type { GguiSessionStore } from '@ggui-ai/mcp-server-core';
|
|
55
|
+
import type { GguiSessionStore, RenderIdentityStore } from '@ggui-ai/mcp-server-core';
|
|
56
56
|
import type { SharedHandler } from '../types.js';
|
|
57
57
|
declare const inputSchema: {
|
|
58
58
|
readonly sessionId: z.ZodString;
|
|
@@ -80,6 +80,18 @@ interface SyncContextRejected {
|
|
|
80
80
|
type SyncContextOutput = SyncContextAccepted | SyncContextRejected;
|
|
81
81
|
export interface CreateGguiSyncContextHandlerDeps {
|
|
82
82
|
readonly renderStore: GguiSessionStore;
|
|
83
|
+
/**
|
|
84
|
+
* Durable render-identity side store. A snapshot sync re-commits the
|
|
85
|
+
* render row, so when a record exists its freshness stamp is
|
|
86
|
+
* refreshed off the committed row.
|
|
87
|
+
*
|
|
88
|
+
* Note what this does NOT persist: the record carries no
|
|
89
|
+
* `contextSnapshot`, so the snapshot itself stays on the render row
|
|
90
|
+
* alone. Identity fields are never recomputed here, and a render
|
|
91
|
+
* with no record yet is skipped rather than synthesized. Writes are
|
|
92
|
+
* best-effort and can never fail the sync.
|
|
93
|
+
*/
|
|
94
|
+
readonly renderIdentityStore?: RenderIdentityStore;
|
|
83
95
|
}
|
|
84
96
|
/**
|
|
85
97
|
* Build the `ggui_runtime_sync_context` handler. Registers as app-visible so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-context.d.ts","sourceRoot":"","sources":["../../src/renders/sync-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"sync-context.d.ts","sourceRoot":"","sources":["../../src/renders/sync-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,QAAA,MAAM,WAAW;;;;CAyBP,CAAC;AAEX,QAAA,MAAM,YAAY;;;;;;;;;CAWR,CAAC;AAEX,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;CACnB;AACD,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,IAAI,EACT,mBAAmB,GACnB,iBAAiB,GACjB,0BAA0B,GAC1B,mBAAmB,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AACD,KAAK,iBAAiB,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AAEnE,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CACpD;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,gCAAgC,GACrC,aAAa,CAAC,OAAO,WAAW,EAAE,OAAO,YAAY,EAAE,iBAAiB,CAAC,CAwH3E"}
|