@ggui-ai/protocol 0.1.0-rc.1

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 (222) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +46 -0
  3. package/dist/bridge/invoke-agent.d.ts +65 -0
  4. package/dist/bridge/invoke-agent.d.ts.map +1 -0
  5. package/dist/bridge/invoke-agent.js +113 -0
  6. package/dist/envelope-adapters.d.ts +24 -0
  7. package/dist/envelope-adapters.d.ts.map +1 -0
  8. package/dist/envelope-adapters.js +14 -0
  9. package/dist/envelopes/builders.d.ts +145 -0
  10. package/dist/envelopes/builders.d.ts.map +1 -0
  11. package/dist/envelopes/builders.js +113 -0
  12. package/dist/errors/unknown-permission-name.d.ts +12 -0
  13. package/dist/errors/unknown-permission-name.d.ts.map +1 -0
  14. package/dist/errors/unknown-permission-name.js +29 -0
  15. package/dist/errors/version-mismatch.d.ts +55 -0
  16. package/dist/errors/version-mismatch.d.ts.map +1 -0
  17. package/dist/errors/version-mismatch.js +52 -0
  18. package/dist/gadgets/resolve-contract-gadgets.d.ts +93 -0
  19. package/dist/gadgets/resolve-contract-gadgets.d.ts.map +1 -0
  20. package/dist/gadgets/resolve-contract-gadgets.js +119 -0
  21. package/dist/gadgets/stdlib-gadgets.d.ts +43 -0
  22. package/dist/gadgets/stdlib-gadgets.d.ts.map +1 -0
  23. package/dist/gadgets/stdlib-gadgets.js +161 -0
  24. package/dist/iframe-bridge.d.ts +63 -0
  25. package/dist/iframe-bridge.d.ts.map +1 -0
  26. package/dist/iframe-bridge.js +166 -0
  27. package/dist/index.d.ts +62 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +79 -0
  30. package/dist/integrations/mcp-apps.d.ts +1218 -0
  31. package/dist/integrations/mcp-apps.d.ts.map +1 -0
  32. package/dist/integrations/mcp-apps.js +427 -0
  33. package/dist/navigation/index.d.ts +3 -0
  34. package/dist/navigation/index.d.ts.map +1 -0
  35. package/dist/navigation/index.js +1 -0
  36. package/dist/navigation/stack-navigation.d.ts +55 -0
  37. package/dist/navigation/stack-navigation.d.ts.map +1 -0
  38. package/dist/navigation/stack-navigation.js +80 -0
  39. package/dist/recommended-prompts.d.ts +56 -0
  40. package/dist/recommended-prompts.d.ts.map +1 -0
  41. package/dist/recommended-prompts.js +55 -0
  42. package/dist/registry/blueprint-key.d.ts +9 -0
  43. package/dist/registry/blueprint-key.d.ts.map +1 -0
  44. package/dist/registry/blueprint-key.js +28 -0
  45. package/dist/registry/canonicalize-contract.d.ts +35 -0
  46. package/dist/registry/canonicalize-contract.d.ts.map +1 -0
  47. package/dist/registry/canonicalize-contract.js +166 -0
  48. package/dist/registry/summarize-contract.d.ts +46 -0
  49. package/dist/registry/summarize-contract.d.ts.map +1 -0
  50. package/dist/registry/summarize-contract.js +63 -0
  51. package/dist/schema-learning/derive-contract.d.ts +67 -0
  52. package/dist/schema-learning/derive-contract.d.ts.map +1 -0
  53. package/dist/schema-learning/derive-contract.js +117 -0
  54. package/dist/schema-learning/merge.d.ts +32 -0
  55. package/dist/schema-learning/merge.d.ts.map +1 -0
  56. package/dist/schema-learning/merge.js +146 -0
  57. package/dist/schemas/blueprint.d.ts +32 -0
  58. package/dist/schemas/blueprint.d.ts.map +1 -0
  59. package/dist/schemas/blueprint.js +92 -0
  60. package/dist/schemas/data-contract.d.ts +750 -0
  61. package/dist/schemas/data-contract.d.ts.map +1 -0
  62. package/dist/schemas/data-contract.js +663 -0
  63. package/dist/schemas/gadget-name-grammar.d.ts +29 -0
  64. package/dist/schemas/gadget-name-grammar.d.ts.map +1 -0
  65. package/dist/schemas/gadget-name-grammar.js +28 -0
  66. package/dist/schemas/handshake-suggestion.d.ts +46 -0
  67. package/dist/schemas/handshake-suggestion.d.ts.map +1 -0
  68. package/dist/schemas/handshake-suggestion.js +107 -0
  69. package/dist/schemas/invoke.d.ts +337 -0
  70. package/dist/schemas/invoke.d.ts.map +1 -0
  71. package/dist/schemas/invoke.js +169 -0
  72. package/dist/schemas/mcp.d.ts +301 -0
  73. package/dist/schemas/mcp.d.ts.map +1 -0
  74. package/dist/schemas/mcp.js +373 -0
  75. package/dist/schemas/ops-blueprint.d.ts +176 -0
  76. package/dist/schemas/ops-blueprint.d.ts.map +1 -0
  77. package/dist/schemas/ops-blueprint.js +259 -0
  78. package/dist/schemas/sync-check.d.ts +11 -0
  79. package/dist/schemas/sync-check.d.ts.map +1 -0
  80. package/dist/schemas/sync-check.js +60 -0
  81. package/dist/screen-blueprints/define.d.ts +22 -0
  82. package/dist/screen-blueprints/define.d.ts.map +1 -0
  83. package/dist/screen-blueprints/define.js +3 -0
  84. package/dist/screen-blueprints/index.d.ts +4 -0
  85. package/dist/screen-blueprints/index.d.ts.map +1 -0
  86. package/dist/screen-blueprints/index.js +3 -0
  87. package/dist/screen-blueprints/match.d.ts +35 -0
  88. package/dist/screen-blueprints/match.d.ts.map +1 -0
  89. package/dist/screen-blueprints/match.js +51 -0
  90. package/dist/screen-blueprints/types.d.ts +164 -0
  91. package/dist/screen-blueprints/types.d.ts.map +1 -0
  92. package/dist/screen-blueprints/types.js +1 -0
  93. package/dist/stream/stream-parser.d.ts +62 -0
  94. package/dist/stream/stream-parser.d.ts.map +1 -0
  95. package/dist/stream/stream-parser.js +199 -0
  96. package/dist/transport/websocket.d.ts +178 -0
  97. package/dist/transport/websocket.d.ts.map +1 -0
  98. package/dist/transport/websocket.js +1 -0
  99. package/dist/types/app-config.d.ts +61 -0
  100. package/dist/types/app-config.d.ts.map +1 -0
  101. package/dist/types/app-config.js +1 -0
  102. package/dist/types/auth.d.ts +61 -0
  103. package/dist/types/auth.d.ts.map +1 -0
  104. package/dist/types/auth.js +1 -0
  105. package/dist/types/blueprint.d.ts +206 -0
  106. package/dist/types/blueprint.d.ts.map +1 -0
  107. package/dist/types/blueprint.js +1 -0
  108. package/dist/types/canvas-lifecycle.d.ts +105 -0
  109. package/dist/types/canvas-lifecycle.d.ts.map +1 -0
  110. package/dist/types/canvas-lifecycle.js +38 -0
  111. package/dist/types/capabilities.d.ts +40 -0
  112. package/dist/types/capabilities.d.ts.map +1 -0
  113. package/dist/types/capabilities.js +19 -0
  114. package/dist/types/contract-inference.d.ts +401 -0
  115. package/dist/types/contract-inference.d.ts.map +1 -0
  116. package/dist/types/contract-inference.js +44 -0
  117. package/dist/types/credential.d.ts +41 -0
  118. package/dist/types/credential.d.ts.map +1 -0
  119. package/dist/types/credential.js +32 -0
  120. package/dist/types/data-bindings.d.ts +322 -0
  121. package/dist/types/data-bindings.d.ts.map +1 -0
  122. package/dist/types/data-bindings.js +29 -0
  123. package/dist/types/data-contract.d.ts +1296 -0
  124. package/dist/types/data-contract.d.ts.map +1 -0
  125. package/dist/types/data-contract.js +111 -0
  126. package/dist/types/events.d.ts +182 -0
  127. package/dist/types/events.d.ts.map +1 -0
  128. package/dist/types/events.js +8 -0
  129. package/dist/types/feedback.d.ts +24 -0
  130. package/dist/types/feedback.d.ts.map +1 -0
  131. package/dist/types/feedback.js +7 -0
  132. package/dist/types/gadget.d.ts +121 -0
  133. package/dist/types/gadget.d.ts.map +1 -0
  134. package/dist/types/gadget.js +24 -0
  135. package/dist/types/handshake-suggestion.d.ts +264 -0
  136. package/dist/types/handshake-suggestion.d.ts.map +1 -0
  137. package/dist/types/handshake-suggestion.js +70 -0
  138. package/dist/types/host-context.d.ts +163 -0
  139. package/dist/types/host-context.d.ts.map +1 -0
  140. package/dist/types/host-context.js +142 -0
  141. package/dist/types/interface-context.d.ts +105 -0
  142. package/dist/types/interface-context.d.ts.map +1 -0
  143. package/dist/types/interface-context.js +115 -0
  144. package/dist/types/invoke.d.ts +28 -0
  145. package/dist/types/invoke.d.ts.map +1 -0
  146. package/dist/types/invoke.js +7 -0
  147. package/dist/types/live-channel.d.ts +613 -0
  148. package/dist/types/live-channel.d.ts.map +1 -0
  149. package/dist/types/live-channel.js +1 -0
  150. package/dist/types/llm.d.ts +61 -0
  151. package/dist/types/llm.d.ts.map +1 -0
  152. package/dist/types/llm.js +186 -0
  153. package/dist/types/mcp-proxy.d.ts +67 -0
  154. package/dist/types/mcp-proxy.d.ts.map +1 -0
  155. package/dist/types/mcp-proxy.js +46 -0
  156. package/dist/types/mcp.d.ts +637 -0
  157. package/dist/types/mcp.d.ts.map +1 -0
  158. package/dist/types/mcp.js +30 -0
  159. package/dist/types/openrouter-models.d.ts +22 -0
  160. package/dist/types/openrouter-models.d.ts.map +1 -0
  161. package/dist/types/openrouter-models.js +4843 -0
  162. package/dist/types/region.d.ts +26 -0
  163. package/dist/types/region.d.ts.map +1 -0
  164. package/dist/types/region.js +36 -0
  165. package/dist/types/session.d.ts +419 -0
  166. package/dist/types/session.d.ts.map +1 -0
  167. package/dist/types/session.js +1 -0
  168. package/dist/types/thread.d.ts +207 -0
  169. package/dist/types/thread.d.ts.map +1 -0
  170. package/dist/types/thread.js +57 -0
  171. package/dist/types/ui-generator.d.ts +100 -0
  172. package/dist/types/ui-generator.d.ts.map +1 -0
  173. package/dist/types/ui-generator.js +53 -0
  174. package/dist/validation/ajv-runtime.d.ts +140 -0
  175. package/dist/validation/ajv-runtime.d.ts.map +1 -0
  176. package/dist/validation/ajv-runtime.js +452 -0
  177. package/dist/validation/content-hash.d.ts +3 -0
  178. package/dist/validation/content-hash.d.ts.map +1 -0
  179. package/dist/validation/content-hash.js +21 -0
  180. package/dist/validation/contract-validator.d.ts +244 -0
  181. package/dist/validation/contract-validator.d.ts.map +1 -0
  182. package/dist/validation/contract-validator.js +711 -0
  183. package/dist/validation/cross-references.d.ts +105 -0
  184. package/dist/validation/cross-references.d.ts.map +1 -0
  185. package/dist/validation/cross-references.js +164 -0
  186. package/dist/validation/hygiene-rules.d.ts +250 -0
  187. package/dist/validation/hygiene-rules.d.ts.map +1 -0
  188. package/dist/validation/hygiene-rules.js +564 -0
  189. package/dist/validation/lint-contract.d.ts +130 -0
  190. package/dist/validation/lint-contract.d.ts.map +1 -0
  191. package/dist/validation/lint-contract.js +225 -0
  192. package/dist/validation/name-invariants.d.ts +117 -0
  193. package/dist/validation/name-invariants.d.ts.map +1 -0
  194. package/dist/validation/name-invariants.js +172 -0
  195. package/dist/validation/reserved-channels.d.ts +156 -0
  196. package/dist/validation/reserved-channels.d.ts.map +1 -0
  197. package/dist/validation/reserved-channels.js +356 -0
  198. package/dist/validation/resolve-stream-channel.d.ts +78 -0
  199. package/dist/validation/resolve-stream-channel.d.ts.map +1 -0
  200. package/dist/validation/resolve-stream-channel.js +64 -0
  201. package/dist/validation/sanitize-error.d.ts +46 -0
  202. package/dist/validation/sanitize-error.d.ts.map +1 -0
  203. package/dist/validation/sanitize-error.js +88 -0
  204. package/dist/validation/schema-compat-invariants.d.ts +140 -0
  205. package/dist/validation/schema-compat-invariants.d.ts.map +1 -0
  206. package/dist/validation/schema-compat-invariants.js +220 -0
  207. package/dist/validation/schema-meta-validation.d.ts +60 -0
  208. package/dist/validation/schema-meta-validation.d.ts.map +1 -0
  209. package/dist/validation/schema-meta-validation.js +131 -0
  210. package/dist/validation/schema-subset.d.ts +165 -0
  211. package/dist/validation/schema-subset.d.ts.map +1 -0
  212. package/dist/validation/schema-subset.js +295 -0
  213. package/dist/validation/ui-security.d.ts +54 -0
  214. package/dist/validation/ui-security.d.ts.map +1 -0
  215. package/dist/validation/ui-security.js +138 -0
  216. package/dist/validation/zod-to-json-schema.d.ts +63 -0
  217. package/dist/validation/zod-to-json-schema.d.ts.map +1 -0
  218. package/dist/validation/zod-to-json-schema.js +126 -0
  219. package/dist/version.d.ts +1458 -0
  220. package/dist/version.d.ts.map +1 -0
  221. package/dist/version.js +1459 -0
  222. package/package.json +113 -0
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Handshake-suggestion shapes (2026-05-12).
3
+ *
4
+ * The three-step handshake protocol replaces the older `match` + `plan`
5
+ * framing on the handshake output.
6
+ *
7
+ * Step 1 — the agent posts a `BlueprintDraft` (its idea: contract +
8
+ * optional variance + optional generator hint).
9
+ *
10
+ * Step 2 — the server runs `BlueprintSearch` + contract validation in
11
+ * parallel and returns a {@link HandshakeSuggestion}. The suggestion's
12
+ * `origin` enum routes the agent's next decision:
13
+ *
14
+ * - `cache` — search-score crossed the per-app threshold; cached
15
+ * code wins. `blueprintMeta.codeHash` is present.
16
+ * - `agent` — search missed but validation passed; gen pending
17
+ * against the agent's draft. Provisional blueprintId.
18
+ * - `synth` — search missed AND validation failed; synth amended
19
+ * the contract. Provisional blueprintId; `amendments`
20
+ * carries the diff vs the agent's draft.
21
+ *
22
+ * Step 3 — the agent posts a `PushDecision` (accept the suggestion or
23
+ * override with a fresh draft). Accept reuses the provisional
24
+ * `blueprintId`; override mints a fresh one.
25
+ *
26
+ * Locked decisions:
27
+ *
28
+ * - `blueprintMeta` is ALWAYS present on a successful handshake
29
+ * (Option B from §D5). `codeHash` is the only field that's absent
30
+ * on non-cache origins.
31
+ * - `amendments` is populated only on `origin: 'synth'`. On `cache`
32
+ * and `agent` origins it MUST be omitted.
33
+ * - `validationFindings` is populated only when validators ran AND
34
+ * produced findings — on cache hits these surface as a soft
35
+ * warning ("your draft would've had X issue — using cached
36
+ * blueprint instead"); on agent/synth they're carried for
37
+ * telemetry only (synth's amendment already addressed them).
38
+ */
39
+ import type { Blueprint, BlueprintVariance } from './blueprint.js';
40
+ import type { DataContract, JsonValue } from './data-contract.js';
41
+ /**
42
+ * Where the handshake's `blueprintMeta` came from. Routes the agent's
43
+ * cognitive model:
44
+ *
45
+ * - `cache` — an existing blueprint matched at or above the per-app
46
+ * threshold. `blueprintMeta.codeHash` is present; the
47
+ * paired `ggui_push({decision: {kind: 'accept'}})`
48
+ * short-circuits to cache delivery.
49
+ * - `agent` — no cache hit, but the agent's draft validated cleanly.
50
+ * `codeHash` absent; gen runs on push against the
51
+ * agent's draft contract verbatim.
52
+ * - `synth` — no cache hit AND validation failed. The synth
53
+ * amender produced a new contract; the diff vs the
54
+ * agent's draft is in `amendments.contractDiff`.
55
+ */
56
+ export type SuggestionOrigin = 'cache' | 'agent' | 'synth';
57
+ /**
58
+ * Agent's draft on the handshake input — what the agent wants to
59
+ * build. The contract is required; variance + generator are optional
60
+ * hints. The server combines this with its own session/app context
61
+ * (cached blueprints, validator outcomes, operator pins) to produce
62
+ * a {@link HandshakeSuggestion}.
63
+ */
64
+ export interface BlueprintDraft {
65
+ /**
66
+ * Agent-authored DataContract. Drives both the blueprint-search
67
+ * embed/structural axes and the contract validators. The agent is
68
+ * the contract authority; synth amends only when validation fails.
69
+ */
70
+ readonly contract: DataContract;
71
+ /**
72
+ * Optional variance tags. Carried through to the suggestion's
73
+ * `blueprintMeta.variance` field; if `decision: 'accept'` lands on a
74
+ * fresh-gen path (origin === 'agent' or 'synth'), the persisted
75
+ * Blueprint row inherits these tags.
76
+ */
77
+ readonly variance?: {
78
+ /** Free-form persona tag (e.g. 'minimalist', 'data-dense'). */
79
+ readonly persona?: string;
80
+ /** Aesthetic tag — promoted to first-class in a future slice. */
81
+ readonly aesthetic?: string;
82
+ /** Small structured signal — JSON-safe. */
83
+ readonly context?: {
84
+ readonly [key: string]: JsonValue | undefined;
85
+ };
86
+ /** Raw style hint / seed prompt. */
87
+ readonly seedPrompt?: string;
88
+ };
89
+ /**
90
+ * Generator slug hint (e.g. `'ui-gen-advanced-opus-4-7'`). The
91
+ * server resolves the effective generator as:
92
+ *
93
+ * 1. Operator app-pin (`App.pinnedGenerator`) — wins if set.
94
+ * 2. This hint — if registered in the GeneratorRegistry.
95
+ * 3. Registry default (`ui-gen-default-haiku-4-5`).
96
+ *
97
+ * Hint-only; unknown slugs fall through to the registry default.
98
+ */
99
+ readonly generator?: string;
100
+ }
101
+ /**
102
+ * Blueprint metadata projected onto the handshake response. The agent
103
+ * uses this to decide whether to accept (reuse the provisional id) or
104
+ * override (mint a fresh id with its own new draft).
105
+ *
106
+ * `blueprintId` is PROVISIONAL — it becomes durable iff the paired
107
+ * push sends `decision: 'accept'`. An override discards it.
108
+ */
109
+ export interface BlueprintMeta {
110
+ /**
111
+ * Provisional blueprint id. Server-minted at handshake-time.
112
+ * Becomes durable when push accepts; discarded on push override.
113
+ */
114
+ readonly blueprintId: string;
115
+ /** Canonical RFC 8785 (JCS) hash of the suggestion's contract. */
116
+ readonly contractHash: string;
117
+ /**
118
+ * Content hash of the cached code body. Present iff `origin ===
119
+ * 'cache'`. Absent for `agent` / `synth` (gen pending).
120
+ */
121
+ readonly codeHash?: string;
122
+ /** Slug of the generator that produced (or will produce) the code. */
123
+ readonly generator: string;
124
+ /** Variance tags carried through from the suggestion. */
125
+ readonly variance: BlueprintVariance;
126
+ /**
127
+ * Optional matcher telemetry — why this blueprint was selected.
128
+ * Operator-readable; LLM-readable. E.g. `'contract-hash, persona →
129
+ * score 0.92'`.
130
+ */
131
+ readonly selectedReason?: string;
132
+ }
133
+ /**
134
+ * Validator finding surfaced on the suggestion. Mirrors
135
+ * `@ggui-ai/protocol/validation/lint-contract`'s `ContractIssue` shape
136
+ * loosely — kept structural here so the suggestion contract doesn't
137
+ * import from the linter module and create a tight cycle.
138
+ *
139
+ * Each finding has a stable `code`, a severity, the dotted-path
140
+ * location, and a human-readable `message`.
141
+ */
142
+ export interface SuggestionFinding {
143
+ /** Stable error code (e.g. `'CTR_REF_NEXT_STEP'`, `'CTR_DUP_NAME'`). */
144
+ readonly code: string;
145
+ readonly severity: 'error' | 'warn';
146
+ /** Dotted JS-style path into the contract. */
147
+ readonly path: string;
148
+ /** Human-readable violation prose. */
149
+ readonly message: string;
150
+ }
151
+ /**
152
+ * Synth's amendment — the diff vs the agent's draft. Populated only on
153
+ * `origin: 'synth'`.
154
+ *
155
+ * `contractDiff` is an RFC 6902 JSON-Patch-style array; the diff
156
+ * applied to the agent's draft yields the suggestion's contract.
157
+ * Helpers in `@ggui-ai/protocol/validation/contract-diff` produce and
158
+ * apply the diff.
159
+ *
160
+ * `reasoning` is the synth model's natural-language explanation —
161
+ * "added required `submit` action so the form completion is
162
+ * observable", etc.
163
+ */
164
+ export interface SuggestionAmendments {
165
+ readonly contractDiff: JsonPatch;
166
+ readonly reasoning: string;
167
+ }
168
+ /**
169
+ * Minimal RFC 6902 JSON-Patch shape carried in handshake-suggestion
170
+ * amendments. The protocol re-exports this so consumers can apply /
171
+ * inspect patches without an external dependency.
172
+ *
173
+ * Subset support — every emitter MUST honor `add` / `remove` /
174
+ * `replace`; `move` / `copy` / `test` are reserved for future use
175
+ * (consumers MAY reject unrecognized ops).
176
+ */
177
+ export type JsonPatch = readonly JsonPatchOp[];
178
+ export type JsonPatchOp = {
179
+ readonly op: 'add';
180
+ readonly path: string;
181
+ readonly value: JsonValue;
182
+ } | {
183
+ readonly op: 'remove';
184
+ readonly path: string;
185
+ } | {
186
+ readonly op: 'replace';
187
+ readonly path: string;
188
+ readonly value: JsonValue;
189
+ };
190
+ /**
191
+ * The full handshake suggestion. Produced by the server in step-2 of
192
+ * the three-step handshake; the agent reads this in the response and
193
+ * branches its push decision on `origin` (accept vs override).
194
+ */
195
+ export interface HandshakeSuggestion {
196
+ /** Routing discriminator — see {@link SuggestionOrigin}. */
197
+ readonly origin: SuggestionOrigin;
198
+ /** Operator-readable + LLM-readable rationale ("contract-hash → score 0.92"). */
199
+ readonly rationale: string;
200
+ /** Provisional blueprint metadata — see {@link BlueprintMeta}. */
201
+ readonly blueprintMeta: BlueprintMeta;
202
+ /**
203
+ * Populated iff `origin === 'synth'`. Carries the JSON-Patch diff
204
+ * vs the agent's draft and the synth model's reasoning.
205
+ */
206
+ readonly amendments?: SuggestionAmendments;
207
+ /**
208
+ * Populated iff validators ran AND produced findings. On `origin:
209
+ * 'cache'` these surface as a soft warning (the agent's draft
210
+ * WOULD have had issues, but the cached blueprint is being served);
211
+ * on `agent` / `synth` they're absent (agent path's validators
212
+ * passed; synth path's amendments already addressed them).
213
+ */
214
+ readonly validationFindings?: readonly SuggestionFinding[];
215
+ }
216
+ /**
217
+ * Decision discriminator on the push input. Replaces the old
218
+ * `{contract? | contractHash?}` triad with a clearer accept-vs-
219
+ * override branch.
220
+ *
221
+ * - `accept` — use the handshake's `blueprintMeta` verbatim.
222
+ * If `codeHash` is present (origin === 'cache'),
223
+ * delivery is a fast cache fetch. Otherwise gen
224
+ * runs against the suggestion's stored contract.
225
+ * - `override` — mint a fresh blueprintId and run gen against the
226
+ * agent's NEW draft. The provisional id from the
227
+ * handshake is discarded.
228
+ */
229
+ export type PushDecision = {
230
+ readonly kind: 'accept';
231
+ } | {
232
+ readonly kind: 'override';
233
+ readonly blueprintDraft: BlueprintDraft;
234
+ };
235
+ /**
236
+ * Build a minimal JSON-Patch RFC 6902 diff between two contracts.
237
+ *
238
+ * Algorithm: shallow walk over the union of top-level keys; for each
239
+ * key, recurse into nested objects, otherwise emit `add` / `remove` /
240
+ * `replace` at the appropriate path. Arrays are diffed as whole values
241
+ * (no LCS) — sufficient for the synth-amendment use case where the
242
+ * synth model rewrites slot/action maps wholesale rather than
243
+ * splicing single array elements.
244
+ *
245
+ * Output is a {@link JsonPatch}; applying it to `before` produces
246
+ * `after` (modulo array-element identity).
247
+ *
248
+ * Pure / deterministic. Exposed so synth implementations don't need
249
+ * to ship their own diff helper.
250
+ */
251
+ export declare function jsonPatch(before: unknown, after: unknown): JsonPatch;
252
+ /**
253
+ * Top-N alternative blueprints surfaced on the handshake response.
254
+ * Agents can override into one of these (push with `decision:
255
+ * 'override'`) — the alternatives are full {@link Blueprint} rows so
256
+ * the agent inspects everything it needs to decide.
257
+ *
258
+ * Sorted by descending match score; the suggestion's primary
259
+ * `blueprintMeta` is NOT duplicated here (the alternatives are
260
+ * what the search returned EXCLUDING the top result that became the
261
+ * primary).
262
+ */
263
+ export type SuggestionAlternatives = readonly Blueprint[];
264
+ //# sourceMappingURL=handshake-suggestion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake-suggestion.d.ts","sourceRoot":"","sources":["../../src/types/handshake-suggestion.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Build a minimal JSON-Patch RFC 6902 diff between two contracts.
3
+ *
4
+ * Algorithm: shallow walk over the union of top-level keys; for each
5
+ * key, recurse into nested objects, otherwise emit `add` / `remove` /
6
+ * `replace` at the appropriate path. Arrays are diffed as whole values
7
+ * (no LCS) — sufficient for the synth-amendment use case where the
8
+ * synth model rewrites slot/action maps wholesale rather than
9
+ * splicing single array elements.
10
+ *
11
+ * Output is a {@link JsonPatch}; applying it to `before` produces
12
+ * `after` (modulo array-element identity).
13
+ *
14
+ * Pure / deterministic. Exposed so synth implementations don't need
15
+ * to ship their own diff helper.
16
+ */
17
+ export function jsonPatch(before, after) {
18
+ const ops = [];
19
+ buildPatchOps(before, after, '', ops);
20
+ return Object.freeze(ops);
21
+ }
22
+ function buildPatchOps(before, after, path, ops) {
23
+ if (before === after)
24
+ return;
25
+ // null / primitive replacements
26
+ if (before === null ||
27
+ after === null ||
28
+ typeof before !== 'object' ||
29
+ typeof after !== 'object') {
30
+ ops.push({ op: 'replace', path, value: after });
31
+ return;
32
+ }
33
+ const beforeArr = Array.isArray(before);
34
+ const afterArr = Array.isArray(after);
35
+ if (beforeArr !== afterArr) {
36
+ // Whole-value replace when the kind flips (object ↔ array).
37
+ ops.push({ op: 'replace', path, value: after });
38
+ return;
39
+ }
40
+ if (beforeArr && afterArr) {
41
+ // Whole-array replace — sufficient for amendment diffs.
42
+ ops.push({ op: 'replace', path, value: after });
43
+ return;
44
+ }
45
+ // Both are plain objects.
46
+ const beforeObj = before;
47
+ const afterObj = after;
48
+ const keys = new Set([...Object.keys(beforeObj), ...Object.keys(afterObj)]);
49
+ for (const key of keys) {
50
+ const childPath = `${path}/${encodeJsonPointerSegment(key)}`;
51
+ const inBefore = Object.prototype.hasOwnProperty.call(beforeObj, key);
52
+ const inAfter = Object.prototype.hasOwnProperty.call(afterObj, key);
53
+ if (!inAfter) {
54
+ ops.push({ op: 'remove', path: childPath });
55
+ continue;
56
+ }
57
+ if (!inBefore) {
58
+ ops.push({ op: 'add', path: childPath, value: afterObj[key] });
59
+ continue;
60
+ }
61
+ buildPatchOps(beforeObj[key], afterObj[key], childPath, ops);
62
+ }
63
+ }
64
+ /**
65
+ * Encode a single JSON-Pointer segment per RFC 6901 §4: `~` → `~0`,
66
+ * `/` → `~1`. Other characters pass through unchanged.
67
+ */
68
+ function encodeJsonPointerSegment(seg) {
69
+ return seg.replace(/~/g, '~0').replace(/\//g, '~1');
70
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Host Context — projected subset of `McpUiHostContext` ggui captures from
3
+ * the MCP Apps `ui/initialize` response and echoes back to session state so
4
+ * the agent can reason about device/host capabilities on subsequent turns.
5
+ *
6
+ * The MCP Apps spec (`@modelcontextprotocol/ext-apps`) defines a rich
7
+ * `McpUiHostContext` (theme, styles, displayMode, availableDisplayModes,
8
+ * containerDimensions, locale, timeZone, userAgent, platform,
9
+ * deviceCapabilities). ggui captures it iframe-side at `ui/initialize` and
10
+ * echoes a TRIMMED projection back over the live channel (via the
11
+ * `host_context_observed` outbound message) so the server can persist it on
12
+ * `SessionRecord.hostContext` and surface it on `ggui_handshake` /
13
+ * `ggui_consume` output for the agent.
14
+ *
15
+ * Why a projection rather than passthrough:
16
+ *
17
+ * - `theme` / `styles` already flow through ggui's separate theming
18
+ * pipeline (`InterfaceContext` + the theme registry). Duplicating
19
+ * creates two sources of truth.
20
+ * - `toolInfo` is host-loop-internal — the agent has its own toolInfo via
21
+ * MCP framing.
22
+ * - `userAgent` is rarely actionable; skip until a concrete use case
23
+ * appears. Easy to add later (additive optional field).
24
+ *
25
+ * What the projection KEEPS:
26
+ *
27
+ * - `availableDisplayModes` / `currentDisplayMode` — drives canvas-mode
28
+ * display-mode escalation policy (see canvas-mode-detail-displaymode.md).
29
+ * - `containerDimensions` — lets the agent reason about layout density
30
+ * and lets the canvas reflow on resize.
31
+ * - `platform` / `deviceCapabilities` — feeds the generator's
32
+ * responsive-UI prompts.
33
+ * - `locale` / `timeZone` — useful for the agent's date/number rendering.
34
+ *
35
+ * Compatibility posture: every field is optional. Hosts that emit a
36
+ * minimal `McpUiHostContext` (spec-permissible) project to an empty
37
+ * object; consumers MUST handle every field as possibly absent.
38
+ *
39
+ * Versioning: the wire wrapper carries `schemaVersion` so future
40
+ * projection widenings can be detected; this module is the canonical
41
+ * shape for the current schema major.
42
+ */
43
+ import type { JsonValue } from './data-contract';
44
+ /**
45
+ * The three display modes the MCP Apps spec defines. Mirror of
46
+ * `McpUiDisplayMode` from `@modelcontextprotocol/ext-apps` — re-declared
47
+ * here so the protocol package doesn't take a runtime dependency on the
48
+ * SDK (the SDK is consumed in iframe-runtime + system-card; the protocol
49
+ * package stays SDK-free per the layering boundary).
50
+ *
51
+ * Stay in sync with the SDK literal: `'inline' | 'fullscreen' | 'pip'`.
52
+ * If the spec adds a fourth mode, widen here and in
53
+ * `iframe-runtime`'s capability-resolution helpers in lockstep.
54
+ */
55
+ export type McpUiDisplayMode = 'inline' | 'fullscreen' | 'pip';
56
+ /**
57
+ * Width specification — either fixed `width` or `maxWidth`, never both.
58
+ * Matches the spec's discriminated container-dimension shape.
59
+ */
60
+ export interface HostContextWidth {
61
+ readonly width?: number;
62
+ readonly maxWidth?: number;
63
+ }
64
+ /**
65
+ * Height specification — either fixed `height` or `maxHeight`, never both.
66
+ */
67
+ export interface HostContextHeight {
68
+ readonly height?: number;
69
+ readonly maxHeight?: number;
70
+ }
71
+ /**
72
+ * Iframe / container dimensions reported by the host. Width and height
73
+ * are independently spec'd (one may be fixed, the other max-bounded).
74
+ */
75
+ export type HostContextContainerDimensions = HostContextWidth & HostContextHeight;
76
+ /**
77
+ * Input capabilities reported by the host. Both `touch` and `hover` may
78
+ * be true (hybrid devices); both may be false (rare, e.g., voice-only
79
+ * hosts).
80
+ */
81
+ export interface HostContextDeviceCapabilities {
82
+ readonly touch?: boolean;
83
+ readonly hover?: boolean;
84
+ }
85
+ /**
86
+ * Trimmed projection of `McpUiHostContext` that ggui captures iframe-side
87
+ * and echoes to session state for agent visibility.
88
+ *
89
+ * Every field is optional. Hosts that emit minimal context project to
90
+ * mostly-empty objects; consumers MUST treat every field as possibly
91
+ * absent and degrade gracefully.
92
+ *
93
+ * Theme + styles intentionally EXCLUDED — they flow through ggui's own
94
+ * theming pipeline (`InterfaceContext`, theme registry). Duplicating
95
+ * here would create two sources of truth.
96
+ *
97
+ * `userAgent` + `toolInfo` intentionally EXCLUDED for v1 — easy to add
98
+ * later if a concrete use case appears.
99
+ */
100
+ export interface HostContextProjection {
101
+ /** Display modes the host can render this view in. Absent ⇒ assume `['inline']`. */
102
+ readonly availableDisplayModes?: readonly McpUiDisplayMode[];
103
+ /** Current display mode the host is rendering. Absent ⇒ assume `'inline'`. */
104
+ readonly currentDisplayMode?: McpUiDisplayMode;
105
+ /** Iframe container dimensions. Absent ⇒ unknown; use a reasonable default. */
106
+ readonly containerDimensions?: HostContextContainerDimensions;
107
+ /** Host platform classification. */
108
+ readonly platform?: 'web' | 'desktop' | 'mobile';
109
+ /** Touch / hover input capability. */
110
+ readonly deviceCapabilities?: HostContextDeviceCapabilities;
111
+ /** User's BCP-47 locale (e.g., `'en-US'`). */
112
+ readonly locale?: string;
113
+ /** User's IANA timezone (e.g., `'America/Los_Angeles'`). */
114
+ readonly timeZone?: string;
115
+ }
116
+ /**
117
+ * Live-channel inbound (client → server) payload that delivers the
118
+ * iframe-captured `HostContextProjection` to the server. Server-side
119
+ * handler writes to `SessionRecord.hostContext`; subsequent
120
+ * `ggui_handshake` / `ggui_consume` responses surface the value to the
121
+ * agent via the optional `client.hostContext` field.
122
+ *
123
+ * Emission cadence:
124
+ * - Once after the iframe-runtime's `ui/initialize` resolves (initial
125
+ * capture).
126
+ * - Once per `ui/notifications/host-context-changed` notification
127
+ * received from the host.
128
+ *
129
+ * Idempotent — re-delivery (e.g., after a reconnect) overwrites the
130
+ * stored value; no merge logic.
131
+ */
132
+ export interface HostContextObservedPayload {
133
+ readonly sessionId: string;
134
+ readonly hostContext: HostContextProjection;
135
+ }
136
+ /**
137
+ * Project a raw `McpUiHostContext` (from the spec SDK or any equivalent
138
+ * shape — accepts `unknown` so callers don't need to drag in the SDK
139
+ * just to call this) into `HostContextProjection`.
140
+ *
141
+ * Defensive: every field crosses a trust boundary. Malformed inputs
142
+ * (wrong types, weird shapes) drop silently to undefined for that
143
+ * field rather than failing the whole projection. The whole capture
144
+ * path is best-effort — never blocks the bootstrap.
145
+ *
146
+ * Returns `undefined` when the input is not an object at all (caller
147
+ * received null / array / primitive from the host). Returns an empty
148
+ * object when the input is an object but no recognized fields are
149
+ * present — the distinction lets callers tell "host emitted context
150
+ * with no recognized fields" from "host emitted no context."
151
+ */
152
+ export declare function projectHostContext(raw: unknown): HostContextProjection | undefined;
153
+ /**
154
+ * Deep equality check for two projections. Used by the iframe-runtime
155
+ * to suppress no-op re-emissions when a `host-context-changed`
156
+ * notification arrives but no projection-visible field actually changed.
157
+ *
158
+ * JSON-stringify is sufficient because the projection contains only
159
+ * primitives, arrays of primitives, and plain objects of primitives.
160
+ */
161
+ export declare function hostContextProjectionsEqual(a: HostContextProjection | undefined, b: HostContextProjection | undefined): boolean;
162
+ export type { JsonValue };
163
+ //# sourceMappingURL=host-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-context.d.ts","sourceRoot":"","sources":["../../src/types/host-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAMjD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,YAAY,GAAG,KAAK,CAAC;AAM/D;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAMlF;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAMD;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,qBAAqB;IACpC,oFAAoF;IACpF,QAAQ,CAAC,qBAAqB,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC7D,8EAA8E;IAC9E,QAAQ,CAAC,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IAC/C,+EAA+E;IAC/E,QAAQ,CAAC,mBAAmB,CAAC,EAAE,8BAA8B,CAAC;IAC9D,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,QAAQ,CAAC;IACjD,sCAAsC;IACtC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;IAC5D,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAMD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC;CAC7C;AAoBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,qBAAqB,GAAG,SAAS,CAsDlF;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,CAAC,EAAE,qBAAqB,GAAG,SAAS,EACpC,CAAC,EAAE,qBAAqB,GAAG,SAAS,GACnC,OAAO,CAIT;AAGD,YAAY,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Host Context — projected subset of `McpUiHostContext` ggui captures from
3
+ * the MCP Apps `ui/initialize` response and echoes back to session state so
4
+ * the agent can reason about device/host capabilities on subsequent turns.
5
+ *
6
+ * The MCP Apps spec (`@modelcontextprotocol/ext-apps`) defines a rich
7
+ * `McpUiHostContext` (theme, styles, displayMode, availableDisplayModes,
8
+ * containerDimensions, locale, timeZone, userAgent, platform,
9
+ * deviceCapabilities). ggui captures it iframe-side at `ui/initialize` and
10
+ * echoes a TRIMMED projection back over the live channel (via the
11
+ * `host_context_observed` outbound message) so the server can persist it on
12
+ * `SessionRecord.hostContext` and surface it on `ggui_handshake` /
13
+ * `ggui_consume` output for the agent.
14
+ *
15
+ * Why a projection rather than passthrough:
16
+ *
17
+ * - `theme` / `styles` already flow through ggui's separate theming
18
+ * pipeline (`InterfaceContext` + the theme registry). Duplicating
19
+ * creates two sources of truth.
20
+ * - `toolInfo` is host-loop-internal — the agent has its own toolInfo via
21
+ * MCP framing.
22
+ * - `userAgent` is rarely actionable; skip until a concrete use case
23
+ * appears. Easy to add later (additive optional field).
24
+ *
25
+ * What the projection KEEPS:
26
+ *
27
+ * - `availableDisplayModes` / `currentDisplayMode` — drives canvas-mode
28
+ * display-mode escalation policy (see canvas-mode-detail-displaymode.md).
29
+ * - `containerDimensions` — lets the agent reason about layout density
30
+ * and lets the canvas reflow on resize.
31
+ * - `platform` / `deviceCapabilities` — feeds the generator's
32
+ * responsive-UI prompts.
33
+ * - `locale` / `timeZone` — useful for the agent's date/number rendering.
34
+ *
35
+ * Compatibility posture: every field is optional. Hosts that emit a
36
+ * minimal `McpUiHostContext` (spec-permissible) project to an empty
37
+ * object; consumers MUST handle every field as possibly absent.
38
+ *
39
+ * Versioning: the wire wrapper carries `schemaVersion` so future
40
+ * projection widenings can be detected; this module is the canonical
41
+ * shape for the current schema major.
42
+ */
43
+ // =============================================================================
44
+ // Projection helper
45
+ // =============================================================================
46
+ /**
47
+ * Type guard — `value` is a non-null, non-array plain object.
48
+ */
49
+ function isPlainObject(value) {
50
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
51
+ }
52
+ /**
53
+ * True iff `value` is a valid `McpUiDisplayMode` literal.
54
+ */
55
+ function isDisplayMode(value) {
56
+ return value === 'inline' || value === 'fullscreen' || value === 'pip';
57
+ }
58
+ /**
59
+ * Project a raw `McpUiHostContext` (from the spec SDK or any equivalent
60
+ * shape — accepts `unknown` so callers don't need to drag in the SDK
61
+ * just to call this) into `HostContextProjection`.
62
+ *
63
+ * Defensive: every field crosses a trust boundary. Malformed inputs
64
+ * (wrong types, weird shapes) drop silently to undefined for that
65
+ * field rather than failing the whole projection. The whole capture
66
+ * path is best-effort — never blocks the bootstrap.
67
+ *
68
+ * Returns `undefined` when the input is not an object at all (caller
69
+ * received null / array / primitive from the host). Returns an empty
70
+ * object when the input is an object but no recognized fields are
71
+ * present — the distinction lets callers tell "host emitted context
72
+ * with no recognized fields" from "host emitted no context."
73
+ */
74
+ export function projectHostContext(raw) {
75
+ if (!isPlainObject(raw))
76
+ return undefined;
77
+ const out = {};
78
+ // displayMode
79
+ if (isDisplayMode(raw.displayMode)) {
80
+ out.currentDisplayMode = raw.displayMode;
81
+ }
82
+ // availableDisplayModes
83
+ if (Array.isArray(raw.availableDisplayModes)) {
84
+ const filtered = raw.availableDisplayModes.filter(isDisplayMode);
85
+ if (filtered.length > 0)
86
+ out.availableDisplayModes = filtered;
87
+ }
88
+ // containerDimensions
89
+ if (isPlainObject(raw.containerDimensions)) {
90
+ const dims = {};
91
+ const cd = raw.containerDimensions;
92
+ if (typeof cd.width === 'number')
93
+ dims.width = cd.width;
94
+ if (typeof cd.maxWidth === 'number')
95
+ dims.maxWidth = cd.maxWidth;
96
+ if (typeof cd.height === 'number')
97
+ dims.height = cd.height;
98
+ if (typeof cd.maxHeight === 'number')
99
+ dims.maxHeight = cd.maxHeight;
100
+ if (Object.keys(dims).length > 0)
101
+ out.containerDimensions = dims;
102
+ }
103
+ // platform
104
+ if (raw.platform === 'web' || raw.platform === 'desktop' || raw.platform === 'mobile') {
105
+ out.platform = raw.platform;
106
+ }
107
+ // deviceCapabilities
108
+ if (isPlainObject(raw.deviceCapabilities)) {
109
+ const dc = {};
110
+ const src = raw.deviceCapabilities;
111
+ if (typeof src.touch === 'boolean')
112
+ dc.touch = src.touch;
113
+ if (typeof src.hover === 'boolean')
114
+ dc.hover = src.hover;
115
+ if (Object.keys(dc).length > 0)
116
+ out.deviceCapabilities = dc;
117
+ }
118
+ // locale
119
+ if (typeof raw.locale === 'string' && raw.locale.length > 0) {
120
+ out.locale = raw.locale;
121
+ }
122
+ // timeZone
123
+ if (typeof raw.timeZone === 'string' && raw.timeZone.length > 0) {
124
+ out.timeZone = raw.timeZone;
125
+ }
126
+ return out;
127
+ }
128
+ /**
129
+ * Deep equality check for two projections. Used by the iframe-runtime
130
+ * to suppress no-op re-emissions when a `host-context-changed`
131
+ * notification arrives but no projection-visible field actually changed.
132
+ *
133
+ * JSON-stringify is sufficient because the projection contains only
134
+ * primitives, arrays of primitives, and plain objects of primitives.
135
+ */
136
+ export function hostContextProjectionsEqual(a, b) {
137
+ if (a === b)
138
+ return true;
139
+ if (a === undefined || b === undefined)
140
+ return false;
141
+ return JSON.stringify(a) === JSON.stringify(b);
142
+ }