@happyvertical/smrt-web 0.43.10 → 0.44.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.
- package/AGENTS.md +93 -1
- package/dist/chunks/intents-BUTyN7YQ.js +390 -0
- package/dist/chunks/intents-BUTyN7YQ.js.map +1 -0
- package/dist/chunks/{src-n14q6RHC.js → src-D1ZtD6Bt.js} +54 -24
- package/dist/chunks/{src-n14q6RHC.js.map → src-D1ZtD6Bt.js.map} +1 -1
- package/dist/index.d.ts +320 -0
- package/dist/index.js +3 -2
- package/dist/intents.d.ts +300 -0
- package/dist/intents.js +2 -0
- package/dist/webmcp.d.ts +244 -0
- package/dist/webmcp.js +2 -2
- package/package.json +7 -3
package/AGENTS.md
CHANGED
|
@@ -38,8 +38,9 @@ module you are editing. This file keeps what holds in every module.
|
|
|
38
38
|
| `index.ts` hooks + `durable-store.ts` | the six capability hook points, hook error isolation, the no-op guarantee, and the shared durable-store namespacing/wipe registry | [agents/capability-seam.md](agents/capability-seam.md) |
|
|
39
39
|
| `offline/` | durable offline writes — config, sync-apply-only replay, idempotency, the shared namespace-keyed engine, and Web Locks leader election | [agents/offline-outbox.md](agents/offline-outbox.md) |
|
|
40
40
|
| `sse-client.ts` | the client half of live cache invalidation — the app-wide subscriber, the wire contract it consumes, and SSE-vs-polling behaviour | [agents/live-invalidation.md](agents/live-invalidation.md) |
|
|
41
|
-
| `webmcp.ts` | framework-agnostic WebMCP registrar; validates an optionally bounded/namespaced, effect-filtered prospective set atomically, keeps legacy list-backed tools on collection state, and executes canonical tool-only definitions directly through REST fetchers | — |
|
|
41
|
+
| `webmcp.ts` | framework-agnostic WebMCP registrar; validates an optionally bounded/namespaced, effect-filtered prospective set atomically, keeps legacy list-backed tools on collection state, and executes canonical tool-only definitions directly through REST fetchers. `registerWebMcpBespokeTool` is the single-tool sibling a UI layer's hand-written component tool routes through (`useWebMcpTool` in smrt-svelte, #2586): same fail-closed effect classification and `effects` policy, no `namespace`/`maxTools` | — |
|
|
42
42
|
| `persistence/` + `update-state.ts` | the read-cache rehydrate capability and the framework-free `updateAvailable` primitive (bundle + contract signals) | [agents/version-persistence.md](agents/version-persistence.md) |
|
|
43
|
+
| `intents.ts` + `capability-classification.ts` | the framework-agnostic declarative view-intent contract, its declaration registry, and the one capability classification rule both it and `webmcp.ts` apply (#2587, #2588). Ships as the dependency-free `@happyvertical/smrt-web/intents` entry — importing it pulls no client-data engine | — |
|
|
43
44
|
| `data-query.ts` | dependency-free browser mirror and defensive response normalizer for the canonical bounded data-query envelope (#2444) | — |
|
|
44
45
|
| `remote-query.ts` | query-shaped remote pages over a `SmrtWebCollection`, with keyed stale cache, execution modes, cancellation/latest-query-wins, and optional query-scoped live subscriptions (#2445) | — |
|
|
45
46
|
|
|
@@ -70,6 +71,16 @@ module you are editing. This file keeps what holds in every module.
|
|
|
70
71
|
Duplicates fail atomically before registration.
|
|
71
72
|
With no exposure policy, only `read` effects are selected; broader effects
|
|
72
73
|
require explicit opt-in, and undeclared custom actions are destructive.
|
|
74
|
+
**Classification (#2587)**: a canonical definition's `effect` / `idempotent`
|
|
75
|
+
/ `openWorld` are TRUSTED verbatim — core's `tool-schema.ts` is the sole
|
|
76
|
+
classifier, and the registrar never recomputes them by action name. The CRUD
|
|
77
|
+
switch in `webmcp.ts` (`actionSemantics()`) survives only as the fallback for
|
|
78
|
+
legacy `SmrtWebCollectionDefinition` tool descriptors that carry no metadata.
|
|
79
|
+
Both paths share one fail-closed default for an undeclared capability:
|
|
80
|
+
`{ effect: 'destructive', idempotent: false, openWorld: true }` — see
|
|
81
|
+
`CapabilityClassification` in `@happyvertical/smrt-types`, which this package
|
|
82
|
+
mirrors structurally rather than importing (its dependency-DAG guardrails
|
|
83
|
+
below keep it free of every `@happyvertical/*` dependency).
|
|
73
84
|
Direct mutations invalidate their
|
|
74
85
|
own and relationship-derived collection names through the public
|
|
75
86
|
`invalidateSmrtWebCollections()` seam when the host supplies its shared
|
|
@@ -103,6 +114,87 @@ different request id — before they reach a UI surface.
|
|
|
103
114
|
- To swap the engine, reimplement the SMRT-owned public types over a different
|
|
104
115
|
backend; the boundary guard keeps consumers insulated from the change.
|
|
105
116
|
|
|
117
|
+
## Declarative view intents — the static form (#2588)
|
|
118
|
+
|
|
119
|
+
A view intent is a component-owned interaction with no model projection. It is
|
|
120
|
+
declared as DATA and compiles into the mounted `ControlInteractionRegistry` /
|
|
121
|
+
`DataSurfaceRegistry` commands, so `StagedControlReview` stays on the path and
|
|
122
|
+
an agent-staged value stays a proposal. Registration goes through
|
|
123
|
+
`registerWebMcpBespokeTool`, so an intent inherits the same fail-closed
|
|
124
|
+
`effects` exposure policy as a generated model tool.
|
|
125
|
+
|
|
126
|
+
### The contract #2591's scanner matcher implements against
|
|
127
|
+
|
|
128
|
+
The scanner walks `**/*.ts` and `**/*.tsx` only and matches decorators on
|
|
129
|
+
classes, not standalone calls, so an emittable intent must satisfy ALL of:
|
|
130
|
+
|
|
131
|
+
1. **A `.ts`/`.tsx` module**, never inline in a `.svelte` file. Convention: a
|
|
132
|
+
sidecar named for what it belongs to — `OrderTable.intents.ts` — or one
|
|
133
|
+
intents module per feature. The binding imports from it.
|
|
134
|
+
2. **Imported from `@happyvertical/smrt-web/intents`.** The package root
|
|
135
|
+
re-exports the runtime half only; `defineIntent` ships solely from the
|
|
136
|
+
`/intents` entry, so the matcher has exactly one import specifier to
|
|
137
|
+
recognize and a sidecar never drags in the client-data engine.
|
|
138
|
+
3. **A module-scope `defineIntent(...)` call** — not inside a function, a
|
|
139
|
+
class, a conditional, or a loop.
|
|
140
|
+
4. **Exactly one argument, an object literal** whose values are literals,
|
|
141
|
+
literal objects, or literal arrays. No spreads, no identifiers, no template
|
|
142
|
+
interpolation, no computed keys. The shape `definePrompt` already uses.
|
|
143
|
+
|
|
144
|
+
Anything computed or conditional is NOT static and keeps using
|
|
145
|
+
`useWebMcpTool` — that escape hatch exists precisely because a tool set
|
|
146
|
+
derived from fetched data cannot be read without evaluation.
|
|
147
|
+
|
|
148
|
+
Worked example, complete and matchable:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// OrderTable.intents.ts
|
|
152
|
+
import { defineIntent } from '@happyvertical/smrt-web/intents';
|
|
153
|
+
|
|
154
|
+
export const nextPageIntent = defineIntent({
|
|
155
|
+
id: 'orders.next_page',
|
|
156
|
+
description: 'Advance the orders table by one page',
|
|
157
|
+
capability: { effect: 'read', idempotent: false, openWorld: false },
|
|
158
|
+
target: { registry: 'dataSurface', controlId: 'next-page', kind: 'table' },
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`id` is lowercase and dot-namespaced with at least two segments. It is the
|
|
163
|
+
identity a playbook step names (`{ kind: 'intent', id }`, #2589) and the
|
|
164
|
+
manifest will carry, so it must not be derived from a namespace, a generated
|
|
165
|
+
tool name, or a route. The WebMCP tool name is `id` with `.`/`-` replaced by
|
|
166
|
+
`_`; an id resolving into the reserved `smrt_ui_` prefix is rejected, because
|
|
167
|
+
the six fixed UI tools are unchanged by this contract and intents sit above
|
|
168
|
+
them rather than duplicating them. That flattening is not injective —
|
|
169
|
+
`orders.foo_bar` and `orders.foo.bar` both derive `orders_foo_bar` — so
|
|
170
|
+
`defineIntent` also rejects a second id that derives a tool name an already
|
|
171
|
+
declared intent derives, at the one place both are visible. Two such intents
|
|
172
|
+
would otherwise fight over a single WebMCP tool name at mount, where the
|
|
173
|
+
failure is a shadowed or rejected registration rather than a clear error.
|
|
174
|
+
|
|
175
|
+
**Gotcha — that check covers intents only.** A derived name can still collide
|
|
176
|
+
with a GENERATED model tool (`${model}_${action}`) or, under a custom
|
|
177
|
+
`webmcp.ui.prefix`, with one of the six fixed UI tools. Neither is knowable at
|
|
178
|
+
declaration time, because both depend on a runtime `namespace`/`prefix` the
|
|
179
|
+
declaration never sees, and there is no document-global tool-name lock for
|
|
180
|
+
bespoke registrations to participate in. Nothing escalates — an intent stays
|
|
181
|
+
browser-only and fail-closed either way — but one registration silently
|
|
182
|
+
shadows or loses to the other. Keep an intent's first id segment out of your
|
|
183
|
+
model names, or give `registerWebMcpTools` a `namespace`.
|
|
184
|
+
|
|
185
|
+
### The no-REST invariant
|
|
186
|
+
|
|
187
|
+
`ViewIntentDeclaration` has no `execute`, `fetch`, `url`, `route`, `endpoint`,
|
|
188
|
+
or `method` field and no field of function type; `target` is a closed
|
|
189
|
+
two-member union naming a browser registry. `defineIntent` additionally
|
|
190
|
+
rejects, at runtime, any key outside its allowlist and any non-JSON value
|
|
191
|
+
anywhere in the object, so an `as any` cast smuggles nothing either. The
|
|
192
|
+
tool's `execute` is then CONSTRUCTED by `compileViewIntentToolSpec` from
|
|
193
|
+
`intent.target` — no author-supplied callable ever runs. Proven by
|
|
194
|
+
`src/intents.test.ts` ("the no-REST invariant"). If you add a field to the
|
|
195
|
+
declaration, it must be data, and it must be reachable by the scanner without
|
|
196
|
+
evaluation.
|
|
197
|
+
|
|
106
198
|
## Reference consumer
|
|
107
199
|
|
|
108
200
|
`packages/products` consumes the runtime as its reference store across npm,
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
//#region src/capability-classification.ts
|
|
2
|
+
var WEBMCP_TOOL_EFFECTS = [
|
|
3
|
+
"read",
|
|
4
|
+
"write",
|
|
5
|
+
"destructive"
|
|
6
|
+
];
|
|
7
|
+
function resolveDeclaredCapability(declared) {
|
|
8
|
+
const effect = WEBMCP_TOOL_EFFECTS.includes(declared.effect) ? declared.effect : "destructive";
|
|
9
|
+
return {
|
|
10
|
+
effect,
|
|
11
|
+
destructive: effect !== "read",
|
|
12
|
+
idempotent: declared.idempotent ?? false,
|
|
13
|
+
openWorld: declared.openWorld ?? true
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function capabilityAnnotations(classification) {
|
|
17
|
+
return {
|
|
18
|
+
readOnlyHint: classification.effect === "read",
|
|
19
|
+
destructiveHint: classification.destructive,
|
|
20
|
+
idempotentHint: classification.idempotent,
|
|
21
|
+
openWorldHint: classification.openWorld,
|
|
22
|
+
untrustedContentHint: true
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function capabilityDeclarationHints(classification) {
|
|
26
|
+
return {
|
|
27
|
+
readOnlyHint: classification.effect === "read",
|
|
28
|
+
destructiveHint: classification.effect === "destructive",
|
|
29
|
+
idempotentHint: classification.idempotent,
|
|
30
|
+
openWorldHint: classification.openWorld,
|
|
31
|
+
untrustedContentHint: true
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/intents.ts
|
|
36
|
+
var VIEW_INTENT_CONTROL_ACTIONS = [
|
|
37
|
+
"focus",
|
|
38
|
+
"reveal",
|
|
39
|
+
"highlight",
|
|
40
|
+
"explain",
|
|
41
|
+
"validate",
|
|
42
|
+
"stage",
|
|
43
|
+
"apply",
|
|
44
|
+
"discard",
|
|
45
|
+
"clear",
|
|
46
|
+
"undo"
|
|
47
|
+
];
|
|
48
|
+
var VIEW_INTENT_DATA_SURFACE_KINDS = [
|
|
49
|
+
"table",
|
|
50
|
+
"list",
|
|
51
|
+
"report",
|
|
52
|
+
"custom"
|
|
53
|
+
];
|
|
54
|
+
var DECLARATION_KEYS = /* @__PURE__ */ new Set([
|
|
55
|
+
"id",
|
|
56
|
+
"description",
|
|
57
|
+
"inputSchema",
|
|
58
|
+
"capability",
|
|
59
|
+
"target"
|
|
60
|
+
]);
|
|
61
|
+
var CAPABILITY_KEYS = /* @__PURE__ */ new Set([
|
|
62
|
+
"effect",
|
|
63
|
+
"idempotent",
|
|
64
|
+
"openWorld"
|
|
65
|
+
]);
|
|
66
|
+
var CONTROL_TARGET_KEYS = /* @__PURE__ */ new Set([
|
|
67
|
+
"registry",
|
|
68
|
+
"action",
|
|
69
|
+
"formId",
|
|
70
|
+
"controlId"
|
|
71
|
+
]);
|
|
72
|
+
var DATA_SURFACE_TARGET_KEYS = /* @__PURE__ */ new Set([
|
|
73
|
+
"registry",
|
|
74
|
+
"controlId",
|
|
75
|
+
"surfaceId",
|
|
76
|
+
"kind"
|
|
77
|
+
]);
|
|
78
|
+
var INTENT_ID_PATTERN = /^[a-z][a-z0-9]*(?:\.[a-z0-9][a-z0-9_]*)+$/;
|
|
79
|
+
var IDENTIFIER_MAX_LENGTH = 256;
|
|
80
|
+
var INTENT_ID_MAX_LENGTH = 128;
|
|
81
|
+
var DESCRIPTION_MAX_LENGTH = 1024;
|
|
82
|
+
var RESERVED_TOOL_NAME_PREFIX = "smrt_ui_";
|
|
83
|
+
function fail(message) {
|
|
84
|
+
throw new Error(`[smrt-web] invalid view intent declaration: ${message}`);
|
|
85
|
+
}
|
|
86
|
+
function isPlainObject(value) {
|
|
87
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
88
|
+
const proto = Object.getPrototypeOf(value);
|
|
89
|
+
return proto === Object.prototype || proto === null;
|
|
90
|
+
}
|
|
91
|
+
function assertNoUnknownKeys(value, allowed, path) {
|
|
92
|
+
for (const key of Object.keys(value)) if (!allowed.has(key)) fail(`unknown key '${key}' at ${path}. A declaration is data only \u2014 there is no field for an execute function, a URL, a route, or a fetch.`);
|
|
93
|
+
}
|
|
94
|
+
function cloneJsonValue(value, path, depth = 0) {
|
|
95
|
+
if (depth > 32) fail(`${path} nests deeper than 32 levels`);
|
|
96
|
+
if (value === null) return null;
|
|
97
|
+
if (typeof value === "function") fail(`${path} is a function. A view intent declaration carries no code: its execute is constructed by the registry from 'target', so an intent has no path to REST.`);
|
|
98
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
99
|
+
if (typeof value === "number") {
|
|
100
|
+
if (!Number.isFinite(value)) fail(`${path} must be a finite number`);
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
if (Array.isArray(value)) return value.map((entry, index) => cloneJsonValue(entry, `${path}[${index}]`, depth + 1));
|
|
104
|
+
if (isPlainObject(value)) {
|
|
105
|
+
const copy = {};
|
|
106
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
107
|
+
const cloned = cloneJsonValue(entry, `${path}.${key}`, depth + 1);
|
|
108
|
+
Object.defineProperty(copy, key, {
|
|
109
|
+
value: cloned,
|
|
110
|
+
enumerable: true,
|
|
111
|
+
writable: true,
|
|
112
|
+
configurable: true
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return copy;
|
|
116
|
+
}
|
|
117
|
+
fail(`${path} must be JSON data (got ${Object.prototype.toString.call(value)})`);
|
|
118
|
+
}
|
|
119
|
+
function assertIdentifier(value, path, maxLength = IDENTIFIER_MAX_LENGTH) {
|
|
120
|
+
if (typeof value !== "string" || value.length === 0) fail(`${path} must be a non-empty string`);
|
|
121
|
+
if (value.length > maxLength) fail(`${path} must be at most ${maxLength} characters`);
|
|
122
|
+
for (const character of value) {
|
|
123
|
+
const code = character.charCodeAt(0);
|
|
124
|
+
if (code < 32 || code === 127) fail(`${path} contains a control character`);
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
function normalizeCapability(value) {
|
|
129
|
+
if (value === void 0) return void 0;
|
|
130
|
+
if (!isPlainObject(value)) fail("capability must be an object literal");
|
|
131
|
+
assertNoUnknownKeys(value, CAPABILITY_KEYS, "capability");
|
|
132
|
+
const { effect, idempotent, openWorld } = value;
|
|
133
|
+
if (effect !== void 0 && !WEBMCP_TOOL_EFFECTS.includes(effect)) fail(`capability.effect must be one of ${WEBMCP_TOOL_EFFECTS.join(", ")}`);
|
|
134
|
+
if (idempotent !== void 0 && typeof idempotent !== "boolean") fail("capability.idempotent must be a boolean");
|
|
135
|
+
if (openWorld !== void 0 && typeof openWorld !== "boolean") fail("capability.openWorld must be a boolean");
|
|
136
|
+
return {
|
|
137
|
+
...effect === void 0 ? {} : { effect },
|
|
138
|
+
...idempotent === void 0 ? {} : { idempotent },
|
|
139
|
+
...openWorld === void 0 ? {} : { openWorld }
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function normalizeTarget(value) {
|
|
143
|
+
if (!isPlainObject(value)) fail("target must be an object literal");
|
|
144
|
+
const registry = value.registry;
|
|
145
|
+
if (registry === "control") {
|
|
146
|
+
assertNoUnknownKeys(value, CONTROL_TARGET_KEYS, "target");
|
|
147
|
+
const action = value.action;
|
|
148
|
+
if (!VIEW_INTENT_CONTROL_ACTIONS.includes(action)) fail(`target.action must be one of ${VIEW_INTENT_CONTROL_ACTIONS.join(", ")}`);
|
|
149
|
+
return {
|
|
150
|
+
registry: "control",
|
|
151
|
+
action,
|
|
152
|
+
...value.formId === void 0 ? {} : { formId: assertIdentifier(value.formId, "target.formId") },
|
|
153
|
+
...value.controlId === void 0 ? {} : { controlId: assertIdentifier(value.controlId, "target.controlId") }
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (registry === "dataSurface") {
|
|
157
|
+
assertNoUnknownKeys(value, DATA_SURFACE_TARGET_KEYS, "target");
|
|
158
|
+
if (value.kind !== void 0 && !VIEW_INTENT_DATA_SURFACE_KINDS.includes(value.kind)) fail(`target.kind must be one of ${VIEW_INTENT_DATA_SURFACE_KINDS.join(", ")}`);
|
|
159
|
+
return {
|
|
160
|
+
registry: "dataSurface",
|
|
161
|
+
controlId: assertIdentifier(value.controlId, "target.controlId"),
|
|
162
|
+
...value.surfaceId === void 0 ? {} : { surfaceId: assertIdentifier(value.surfaceId, "target.surfaceId") },
|
|
163
|
+
...value.kind === void 0 ? {} : { kind: value.kind }
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
fail("target.registry must be 'control' or 'dataSurface'");
|
|
167
|
+
}
|
|
168
|
+
function viewIntentToolName(id) {
|
|
169
|
+
return id.replace(/[.-]/g, "_");
|
|
170
|
+
}
|
|
171
|
+
var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("@happyvertical/smrt-web:view-intents");
|
|
172
|
+
function registryState() {
|
|
173
|
+
const host = globalThis;
|
|
174
|
+
const existing = host[REGISTRY_KEY];
|
|
175
|
+
if (existing) return existing;
|
|
176
|
+
const created = { intents: /* @__PURE__ */ new Map() };
|
|
177
|
+
host[REGISTRY_KEY] = created;
|
|
178
|
+
return created;
|
|
179
|
+
}
|
|
180
|
+
function defineIntent(declaration) {
|
|
181
|
+
if (!isPlainObject(declaration)) fail("defineIntent expects an object literal");
|
|
182
|
+
assertNoUnknownKeys(declaration, DECLARATION_KEYS, "declaration");
|
|
183
|
+
const source = cloneJsonValue(declaration, "declaration");
|
|
184
|
+
assertNoUnknownKeys(source, DECLARATION_KEYS, "declaration");
|
|
185
|
+
const id = assertIdentifier(source.id, "id", INTENT_ID_MAX_LENGTH);
|
|
186
|
+
if (!INTENT_ID_PATTERN.test(id)) fail(`id '${id}' must be lowercase and namespaced with at least one dot, e.g. 'orders.filter_by_status'`);
|
|
187
|
+
if (viewIntentToolName(id).startsWith(RESERVED_TOOL_NAME_PREFIX)) fail(`id '${id}' resolves into the reserved '${RESERVED_TOOL_NAME_PREFIX}' namespace of the six fixed UI tools`);
|
|
188
|
+
const description = assertIdentifier(source.description, "description", DESCRIPTION_MAX_LENGTH);
|
|
189
|
+
if (source.inputSchema !== void 0 && !isPlainObject(source.inputSchema)) fail("inputSchema must be an object literal");
|
|
190
|
+
const intent = deepFreeze({
|
|
191
|
+
kind: "intent",
|
|
192
|
+
id,
|
|
193
|
+
description,
|
|
194
|
+
inputSchema: source.inputSchema ?? {
|
|
195
|
+
type: "object",
|
|
196
|
+
properties: {}
|
|
197
|
+
},
|
|
198
|
+
classification: resolveDeclaredCapability(normalizeCapability(source.capability) ?? {}),
|
|
199
|
+
target: normalizeTarget(source.target),
|
|
200
|
+
planes: ["browser"]
|
|
201
|
+
});
|
|
202
|
+
const state = registryState();
|
|
203
|
+
const toolName = viewIntentToolName(id);
|
|
204
|
+
for (const other of state.intents.values()) if (other.id !== id && viewIntentToolName(other.id) === toolName) fail(`id '${id}' derives the WebMCP tool name '${toolName}', which intent '${other.id}' already derives`);
|
|
205
|
+
const existing = state.intents.get(id);
|
|
206
|
+
if (existing) {
|
|
207
|
+
if (JSON.stringify(existing) === JSON.stringify(intent)) return existing;
|
|
208
|
+
console.warn(`[smrt-web] view intent '${id}' was re-declared with a different definition; replacing it. In development this is an HMR update. In a built application it means two declarations share one id.`);
|
|
209
|
+
}
|
|
210
|
+
state.intents.set(id, intent);
|
|
211
|
+
return intent;
|
|
212
|
+
}
|
|
213
|
+
function deepFreeze(value) {
|
|
214
|
+
if (value && typeof value === "object") {
|
|
215
|
+
for (const entry of Object.values(value)) deepFreeze(entry);
|
|
216
|
+
Object.freeze(value);
|
|
217
|
+
}
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
function listViewIntents() {
|
|
221
|
+
return [...registryState().intents.values()];
|
|
222
|
+
}
|
|
223
|
+
function resolveViewIntent(id) {
|
|
224
|
+
return registryState().intents.get(id);
|
|
225
|
+
}
|
|
226
|
+
function clearViewIntentRegistry() {
|
|
227
|
+
registryState().intents.clear();
|
|
228
|
+
}
|
|
229
|
+
var PUBLIC_REASONS = /* @__PURE__ */ new Set([
|
|
230
|
+
"not_found",
|
|
231
|
+
"unsupported",
|
|
232
|
+
"consent_required",
|
|
233
|
+
"human_confirmation_required",
|
|
234
|
+
"sensitive_control",
|
|
235
|
+
"control_not_writable",
|
|
236
|
+
"control_not_editable",
|
|
237
|
+
"nothing_to_undo",
|
|
238
|
+
"stale_revision",
|
|
239
|
+
"idempotency_conflict",
|
|
240
|
+
"non_monotonic_revision",
|
|
241
|
+
"invalid_request",
|
|
242
|
+
"denied"
|
|
243
|
+
]);
|
|
244
|
+
function publicReason(reason) {
|
|
245
|
+
return typeof reason === "string" && PUBLIC_REASONS.has(reason) ? reason : "denied";
|
|
246
|
+
}
|
|
247
|
+
function ok(result) {
|
|
248
|
+
return JSON.stringify({
|
|
249
|
+
ok: true,
|
|
250
|
+
...result
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function denied(reason) {
|
|
254
|
+
return JSON.stringify({
|
|
255
|
+
ok: false,
|
|
256
|
+
reason
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function toolArguments(args) {
|
|
260
|
+
return isPlainObject(args) ? args : {};
|
|
261
|
+
}
|
|
262
|
+
var commandCounter = 0;
|
|
263
|
+
function newCommandId() {
|
|
264
|
+
const cryptoLike = globalThis.crypto;
|
|
265
|
+
if (typeof cryptoLike?.randomUUID === "function") return cryptoLike.randomUUID();
|
|
266
|
+
commandCounter += 1;
|
|
267
|
+
return `intent-${Date.now().toString(36)}-${commandCounter}`;
|
|
268
|
+
}
|
|
269
|
+
var IntentArgumentError = class extends Error {};
|
|
270
|
+
function jsonArg(args, key) {
|
|
271
|
+
try {
|
|
272
|
+
return cloneJsonValue(args[key], key);
|
|
273
|
+
} catch {
|
|
274
|
+
throw new IntentArgumentError(key);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function optionalRevision(args) {
|
|
278
|
+
const revision = args.revision;
|
|
279
|
+
return typeof revision === "number" && Number.isFinite(revision) ? { revision } : {};
|
|
280
|
+
}
|
|
281
|
+
function intentErrorReason(error) {
|
|
282
|
+
return error instanceof IntentArgumentError ? "invalid_request" : "denied";
|
|
283
|
+
}
|
|
284
|
+
function buildControlCommand(target, identity, args) {
|
|
285
|
+
const base = {
|
|
286
|
+
action: target.action,
|
|
287
|
+
identity
|
|
288
|
+
};
|
|
289
|
+
switch (target.action) {
|
|
290
|
+
case "highlight": {
|
|
291
|
+
const durationMs = args.durationMs;
|
|
292
|
+
if (durationMs !== void 0) {
|
|
293
|
+
if (typeof durationMs !== "number" || !Number.isFinite(durationMs)) throw new IntentArgumentError("durationMs");
|
|
294
|
+
return {
|
|
295
|
+
...base,
|
|
296
|
+
durationMs
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return base;
|
|
300
|
+
}
|
|
301
|
+
case "stage":
|
|
302
|
+
if (!("value" in args)) throw new IntentArgumentError("value");
|
|
303
|
+
return {
|
|
304
|
+
...base,
|
|
305
|
+
value: jsonArg(args, "value")
|
|
306
|
+
};
|
|
307
|
+
case "apply": return {
|
|
308
|
+
...base,
|
|
309
|
+
..."value" in args ? { value: jsonArg(args, "value") } : {},
|
|
310
|
+
...optionalRevision(args)
|
|
311
|
+
};
|
|
312
|
+
case "discard": return {
|
|
313
|
+
...base,
|
|
314
|
+
...optionalRevision(args)
|
|
315
|
+
};
|
|
316
|
+
default: return base;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function compileViewIntentToolSpec(intent, binding) {
|
|
320
|
+
if (binding.registry !== intent.target.registry) throw new Error(`[smrt-web] intent '${intent.id}' targets the ${intent.target.registry} registry but was bound to ${binding.registry}`);
|
|
321
|
+
const spec = {
|
|
322
|
+
name: viewIntentToolName(intent.id),
|
|
323
|
+
description: intent.description,
|
|
324
|
+
inputSchema: intent.inputSchema,
|
|
325
|
+
annotations: capabilityDeclarationHints(intent.classification)
|
|
326
|
+
};
|
|
327
|
+
if (binding.registry === "control" && intent.target.registry === "control") {
|
|
328
|
+
const target = intent.target;
|
|
329
|
+
const identity = binding.identity;
|
|
330
|
+
assertBoundIdentity(intent.id, "formId", target.formId, identity.formId);
|
|
331
|
+
assertBoundIdentity(intent.id, "controlId", target.controlId, identity.controlId);
|
|
332
|
+
const registry = binding.registryPort;
|
|
333
|
+
return {
|
|
334
|
+
...spec,
|
|
335
|
+
execute: async (args) => {
|
|
336
|
+
try {
|
|
337
|
+
const command = buildControlCommand(target, identity, toolArguments(args));
|
|
338
|
+
const result = await registry.execute(command, { source: "agent" });
|
|
339
|
+
return result.ok ? ok({
|
|
340
|
+
action: target.action,
|
|
341
|
+
identity
|
|
342
|
+
}) : denied(publicReason(result.reason));
|
|
343
|
+
} catch (error) {
|
|
344
|
+
return denied(intentErrorReason(error));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (binding.registry === "dataSurface" && intent.target.registry === "dataSurface") {
|
|
350
|
+
const target = intent.target;
|
|
351
|
+
const identity = binding.identity;
|
|
352
|
+
assertBoundIdentity(intent.id, "surfaceId", target.surfaceId, identity.surfaceId);
|
|
353
|
+
assertBoundIdentity(intent.id, "kind", target.kind, identity.kind);
|
|
354
|
+
const registry = binding.registryPort;
|
|
355
|
+
return {
|
|
356
|
+
...spec,
|
|
357
|
+
execute: async (args) => {
|
|
358
|
+
try {
|
|
359
|
+
const input = toolArguments(args);
|
|
360
|
+
const payload = "payload" in input ? jsonArg(input, "payload") : void 0;
|
|
361
|
+
const snapshot = registry.inspect(identity);
|
|
362
|
+
if (!snapshot) return denied("not_found");
|
|
363
|
+
const result = await registry.execute({
|
|
364
|
+
version: 1,
|
|
365
|
+
commandId: newCommandId(),
|
|
366
|
+
identity,
|
|
367
|
+
expectedRevision: snapshot.revision,
|
|
368
|
+
controlId: target.controlId,
|
|
369
|
+
...payload === void 0 ? {} : { payload }
|
|
370
|
+
});
|
|
371
|
+
return result.ok ? ok({
|
|
372
|
+
controlId: target.controlId,
|
|
373
|
+
identity,
|
|
374
|
+
...result.revision === void 0 ? {} : { revision: result.revision }
|
|
375
|
+
}) : denied(publicReason(result.reason));
|
|
376
|
+
} catch (error) {
|
|
377
|
+
return denied(intentErrorReason(error));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
throw new Error(`[smrt-web] intent '${intent.id}' could not be compiled for its binding`);
|
|
383
|
+
}
|
|
384
|
+
function assertBoundIdentity(id, field, declared, bound) {
|
|
385
|
+
if (declared !== void 0 && declared !== bound) throw new Error(`[smrt-web] intent '${id}' declares ${field} '${declared}' but was bound to '${bound}'`);
|
|
386
|
+
}
|
|
387
|
+
//#endregion
|
|
388
|
+
export { resolveViewIntent as a, capabilityAnnotations as c, listViewIntents as i, resolveDeclaredCapability as l, compileViewIntentToolSpec as n, viewIntentToolName as o, defineIntent as r, WEBMCP_TOOL_EFFECTS as s, clearViewIntentRegistry as t };
|
|
389
|
+
|
|
390
|
+
//# sourceMappingURL=intents-BUTyN7YQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intents-BUTyN7YQ.js","names":[],"sources":["../../src/capability-classification.ts","../../src/intents.ts"],"sourcesContent":["/**\n * The one capability classification rule this package applies, extracted so\n * every declaration site in smrt-web shares a single implementation (#2587,\n * #2588).\n *\n * Two sites consume it today: `webmcp.ts` (canonical definitions trusted\n * through it directly, and its legacy CRUD switch's fail-closed default\n * branch) and `intents.ts` (a declared view intent, which is never a CRUD\n * verb and so resolves through the declaration rule alone). Keeping the rule\n * here rather than private to `webmcp.ts` is what lets the intent path be a\n * dependency-free module that never pulls the client-data engine.\n *\n * This module structurally mirrors `CapabilityEffect` / `CapabilityDeclaration`\n * / `CapabilityClassification` in `@happyvertical/smrt-types` rather than\n * importing them — this package's dependency-DAG guardrails keep it free of\n * every `@happyvertical/*` dependency (see AGENTS.md \"No inter-smrt\n * dependencies\"), the same reason `data-query.ts` mirrors that package's\n * bounded query envelope structurally instead of importing it.\n */\n\n/**\n * Browser/agent-visible effect classification for a capability. `'read'`\n * never mutates; `'write'` mutates within the application; `'destructive'`\n * may remove or irreversibly change data.\n */\nexport type WebMcpToolEffect = 'read' | 'write' | 'destructive';\n\n/** Every valid {@link WebMcpToolEffect}, in exposure-breadth order. */\nexport const WEBMCP_TOOL_EFFECTS: readonly WebMcpToolEffect[] = [\n 'read',\n 'write',\n 'destructive',\n];\n\n/**\n * An author-supplied, partial classification. Any omitted field resolves\n * through the fail-closed rule on {@link resolveDeclaredCapability}, never\n * through a CRUD- or name-based guess.\n */\nexport interface WebMcpCapabilityDeclaration {\n effect?: WebMcpToolEffect;\n idempotent?: boolean;\n openWorld?: boolean;\n}\n\n/** A fully resolved classification. */\nexport interface WebMcpCapabilityClassification {\n effect: WebMcpToolEffect;\n /**\n * Derived, never declared: every non-read effect is annotated destructive\n * to the browser, so a `write` capability cannot claim the MCP\n * additive-only guarantee.\n */\n destructive: boolean;\n idempotent: boolean;\n openWorld: boolean;\n}\n\n/** The MCP-shaped annotation set a resolved classification emits. */\nexport interface WebMcpCapabilityAnnotations {\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n untrustedContentHint: boolean;\n}\n\n/**\n * Resolve a declared capability with no CRUD/action-based guess — the shared\n * fail-closed default documented on `CapabilityClassification` in\n * `@happyvertical/smrt-types` (#2587): an omitted or invalid `effect`\n * resolves to `'destructive'`, an omitted `idempotent` to `false`, an\n * omitted `openWorld` to `true`. Each field defaults independently: a\n * declared `effect: 'read'` does NOT imply `idempotent: true`.\n */\nexport function resolveDeclaredCapability(\n declared: WebMcpCapabilityDeclaration,\n): WebMcpCapabilityClassification {\n const effect = WEBMCP_TOOL_EFFECTS.includes(\n declared.effect as WebMcpToolEffect,\n )\n ? (declared.effect as WebMcpToolEffect)\n : 'destructive';\n return {\n effect,\n destructive: effect !== 'read',\n idempotent: declared.idempotent ?? false,\n openWorld: declared.openWorld ?? true,\n };\n}\n\n/**\n * The browser annotations a resolved classification is finally registered\n * with. `destructiveHint` carries the DERIVED `destructive` flag, so a\n * `write` capability is annotated destructive exactly like a generated\n * custom model action declared `effect: 'write'`.\n */\nexport function capabilityAnnotations(\n classification: WebMcpCapabilityClassification,\n): WebMcpCapabilityAnnotations {\n return {\n readOnlyHint: classification.effect === 'read',\n destructiveHint: classification.destructive,\n idempotentHint: classification.idempotent,\n openWorldHint: classification.openWorld,\n untrustedContentHint: true,\n };\n}\n\n/**\n * Encode a resolved classification as the INPUT hints\n * `registerWebMcpBespokeTool` decodes (`bespokeDeclaredSemantics`), so a\n * capability resolved here survives that registrar's own re-resolution\n * unchanged.\n *\n * This is deliberately NOT {@link capabilityAnnotations}. That function emits\n * the derived `destructive` flag, and `destructiveHint: true` is the first\n * check the registrar makes — round-tripping it would silently upgrade every\n * `write` capability to `destructive`. On the input side `destructiveHint`\n * means \"the author declared the destructive EFFECT\", and `false` selects the\n * `write` bucket; the registrar re-emits it as `true` on its way to the\n * browser.\n */\nexport function capabilityDeclarationHints(\n classification: WebMcpCapabilityClassification,\n): WebMcpCapabilityAnnotations {\n return {\n readOnlyHint: classification.effect === 'read',\n destructiveHint: classification.effect === 'destructive',\n idempotentHint: classification.idempotent,\n openWorldHint: classification.openWorld,\n untrustedContentHint: true,\n };\n}\n","/**\n * Framework-agnostic declarative view intents (#2588).\n *\n * A view intent is an interaction a component owns that has no model\n * projection and never will — filter this list, advance the wizard, open the\n * archived tab. It is declared as a LITERAL object in a `.ts` module and\n * compiles into the mounted `ControlInteractionRegistry` /\n * `DataSurfaceRegistry` commands that `@happyvertical/smrt-ui` already\n * exposes, never around them.\n *\n * ## The hard invariant: an intent can never reach REST\n *\n * Enforced twice, structurally and at runtime.\n *\n * **Structurally**, {@link ViewIntentDeclaration} has no `execute`, `fetch`,\n * `url`, `route`, `endpoint`, or `method` field, and no field of function\n * type anywhere. Its `target` is a closed two-member union naming a browser\n * registry. There is no way to spell a network call in the type.\n *\n * **At runtime**, {@link defineIntent} rejects any key outside the declared\n * allowlist and any non-JSON value (functions included) anywhere in the\n * declaration, so a declaration cast through `as any` cannot smuggle one in\n * either. It reads every property exactly once and keeps a JSON COPY, so a\n * getter or Proxy cannot show the checker plain data and hand something else\n * to whoever reads the property next. The tool's `execute` is then CONSTRUCTED\n * by\n * {@link compileViewIntentToolSpec} from `intent.target` alone. An author\n * never supplies a callable, so there is no author-controlled code on the\n * execution path to place a `fetch` in.\n *\n * The decorator remains the sole authority on model-operation exposure\n * (epic #2585 invariant 1): a component may reference an operation\n * `@smrt({ api })` already exposes, through the generated model tools, but an\n * intent can only move browser state. And because intents dispatch registry\n * commands, `StagedControlReview` stays on the path unconditionally — an\n * agent-sourced `stage` is a proposal, and `apply` still requires the\n * registry's trusted local human gesture.\n *\n * ## Static form (the #2591 scanner contract)\n *\n * Declarations must be module-scope `defineIntent({ ... })` calls with a\n * single object literal argument, in a `.ts`/`.tsx` module — see\n * `packages/smrt-web/AGENTS.md` \"Declarative view intents\" for the contract\n * a scanner matcher implements against. Computed or conditional tool sets\n * keep using `useWebMcpTool`.\n *\n * This module imports nothing at runtime — not the client-data engine, not\n * `./index.js`, not `./webmcp.js` — so a `Foo.intents.ts` sidecar costs a\n * page nothing but its own declarations. Registration lives in `webmcp.ts`\n * (`registerViewIntent`), which is loaded lazily by every binding.\n */\n\nimport {\n capabilityDeclarationHints,\n resolveDeclaredCapability,\n WEBMCP_TOOL_EFFECTS,\n type WebMcpCapabilityAnnotations,\n type WebMcpCapabilityClassification,\n type WebMcpCapabilityDeclaration,\n} from './capability-classification.js';\n\n// ---------------------------------------------------------------------------\n// Declaration contract\n// ---------------------------------------------------------------------------\n\n/**\n * Control commands a view intent may dispatch. Structurally mirrors\n * `ControlCommandAction` in `@happyvertical/smrt-ui/forms`; this package\n * cannot import that one (see AGENTS.md \"No inter-smrt dependencies\").\n */\nexport type ViewIntentControlAction =\n | 'focus'\n | 'reveal'\n | 'highlight'\n | 'explain'\n | 'validate'\n | 'stage'\n | 'apply'\n | 'discard'\n | 'clear'\n | 'undo';\n\nconst VIEW_INTENT_CONTROL_ACTIONS: readonly ViewIntentControlAction[] = [\n 'focus',\n 'reveal',\n 'highlight',\n 'explain',\n 'validate',\n 'stage',\n 'apply',\n 'discard',\n 'clear',\n 'undo',\n];\n\n/** Mirrors `DataSurfaceIdentity['kind']` in `@happyvertical/smrt-ui/data`. */\nexport type ViewIntentDataSurfaceKind = 'table' | 'list' | 'report' | 'custom';\n\nconst VIEW_INTENT_DATA_SURFACE_KINDS: readonly ViewIntentDataSurfaceKind[] = [\n 'table',\n 'list',\n 'report',\n 'custom',\n];\n\n/**\n * The optional record a registry identity is qualified by. Rich forms use it\n * to tell apart controls that share a `formId`/`controlId` across records.\n * Structurally mirrors `ControlIdentity['subject']` /\n * `DataSurfaceIdentity['subject']` in `@happyvertical/smrt-ui`.\n */\nexport interface ViewIntentSubject {\n type: string;\n id: string;\n label?: string;\n}\n\n/**\n * A control-registry target: the intent dispatches exactly one\n * `ControlInteractionRegistry` command against a mounted control.\n *\n * `formId`/`controlId` are the statically declared half of the identity. A\n * binding supplies the mounted identity and must MATCH anything declared\n * here — a declaration is authority over a binding, never the other way\n * round.\n */\nexport interface ViewIntentControlTarget {\n registry: 'control';\n action: ViewIntentControlAction;\n formId?: string;\n controlId?: string;\n}\n\n/**\n * A data-surface target: the intent dispatches one\n * `DataSurfaceVisibleCommand` — a browser-visible state transition, never a\n * server-side query or mutation.\n */\nexport interface ViewIntentDataSurfaceTarget {\n registry: 'dataSurface';\n /** The visible-command `controlId` the mounted surface implements. */\n controlId: string;\n surfaceId?: string;\n kind?: ViewIntentDataSurfaceKind;\n}\n\nexport type ViewIntentTarget =\n | ViewIntentControlTarget\n | ViewIntentDataSurfaceTarget;\n\n/**\n * The literal object passed to {@link defineIntent}. Every field is data;\n * none is a function, a URL, or a route.\n */\nexport interface ViewIntentDeclaration {\n /**\n * Stable, namespaced identity — at least one dot, lowercase, e.g.\n * `orders.filter_by_status`. It is the intent's name in the manifest\n * (#2591) and in a playbook step (`{ kind: 'intent', id }`, #2589), so it\n * must not be derived from anything that can change (a namespace, a\n * generated tool name, a route).\n */\n id: string;\n /** Human/agent-readable description. Becomes the tool description. */\n description: string;\n /** JSON Schema for the tool's arguments. Must be plain JSON. */\n inputSchema?: Record<string, unknown>;\n /**\n * Partial capability declaration resolved by the shared fail-closed rule\n * (#2587). Omitted entirely, an intent classifies destructive,\n * non-idempotent, open-world.\n */\n capability?: WebMcpCapabilityDeclaration;\n /** Which registry this compiles into, and what it addresses there. */\n target: ViewIntentTarget;\n}\n\n/**\n * A validated, frozen intent.\n *\n * `kind: 'intent'` and `id` are the exported identity a `smrt-playbooks`\n * step reference (`{ kind: 'intent', id }`) names, and this object is\n * directly assignable to that package's `PlaybookIntentRecord` seam —\n * `{ id, classification, planes }` — so {@link resolveViewIntent} can be\n * passed as its `intents` resolver unchanged.\n */\nexport interface ViewIntent {\n readonly kind: 'intent';\n readonly id: string;\n readonly description: string;\n readonly inputSchema: Record<string, unknown>;\n /** Resolved through the shared fail-closed rule at declaration time. */\n readonly classification: WebMcpCapabilityClassification;\n readonly target: ViewIntentTarget;\n /**\n * An intent moves mounted browser state, so it is browser-valid only. A\n * server-side agent reaches one through the #2446 command/ack bridge, which\n * a playbook must declare explicitly.\n */\n readonly planes: readonly ['browser'];\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nconst DECLARATION_KEYS = new Set([\n 'id',\n 'description',\n 'inputSchema',\n 'capability',\n 'target',\n]);\nconst CAPABILITY_KEYS = new Set(['effect', 'idempotent', 'openWorld']);\nconst CONTROL_TARGET_KEYS = new Set([\n 'registry',\n 'action',\n 'formId',\n 'controlId',\n]);\nconst DATA_SURFACE_TARGET_KEYS = new Set([\n 'registry',\n 'controlId',\n 'surfaceId',\n 'kind',\n]);\n\n/** Lowercase, dot-namespaced, at least two segments. */\nconst INTENT_ID_PATTERN = /^[a-z][a-z0-9]*(?:\\.[a-z0-9][a-z0-9_]*)+$/;\nconst IDENTIFIER_MAX_LENGTH = 256;\nconst INTENT_ID_MAX_LENGTH = 128;\nconst DESCRIPTION_MAX_LENGTH = 1024;\n\n/**\n * The six fixed `smrt_ui_*` tools are explicitly unchanged by this contract,\n * and intents sit above them rather than duplicating them. An intent whose\n * derived tool name would land in their namespace is rejected.\n */\nconst RESERVED_TOOL_NAME_PREFIX = 'smrt_ui_';\n\nfunction fail(message: string): never {\n throw new Error(`[smrt-web] invalid view intent declaration: ${message}`);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nfunction assertNoUnknownKeys(\n value: Record<string, unknown>,\n allowed: ReadonlySet<string>,\n path: string,\n): void {\n for (const key of Object.keys(value)) {\n if (!allowed.has(key)) {\n fail(\n `unknown key '${key}' at ${path}. A declaration is data only — there is no field for an execute function, a URL, a route, or a fetch.`,\n );\n }\n }\n}\n\n/**\n * Validate that a value is JSON data and return a fresh deep COPY of it. This\n * is the runtime half of the no-REST invariant: a function value is the only\n * way author code could reach the execution path, and there is no legitimate\n * reason for one to appear in a static declaration.\n *\n * It copies rather than merely asserting because validate-then-store-by-\n * reference is a time-of-check/time-of-use gap: a getter (or a Proxy) can\n * return plain JSON to the checker and a function to whoever reads the\n * property next. Every property is read exactly once, here, and everything\n * downstream sees only this copy — so what was validated is precisely what is\n * kept, frozen, and handed to the browser.\n */\nfunction cloneJsonValue(value: unknown, path: string, depth = 0): unknown {\n if (depth > 32) fail(`${path} nests deeper than 32 levels`);\n if (value === null) return null;\n if (typeof value === 'function') {\n fail(\n `${path} is a function. A view intent declaration carries no code: its execute is constructed by the registry from 'target', so an intent has no path to REST.`,\n );\n }\n if (typeof value === 'string' || typeof value === 'boolean') return value;\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) fail(`${path} must be a finite number`);\n return value;\n }\n if (Array.isArray(value)) {\n return value.map((entry, index) =>\n cloneJsonValue(entry, `${path}[${index}]`, depth + 1),\n );\n }\n if (isPlainObject(value)) {\n const copy: Record<string, unknown> = {};\n // `Object.entries` reads each own enumerable property once. Symbol-keyed\n // properties are deliberately dropped: they are not JSON, they cannot be\n // named by a scanner, and nothing downstream reads them.\n for (const [key, entry] of Object.entries(value)) {\n const cloned = cloneJsonValue(entry, `${path}.${key}`, depth + 1);\n // `defineProperty`, not assignment: an own `__proto__` key (which\n // `JSON.parse` produces, so an agent argument can carry one) would\n // otherwise hit `Object.prototype`'s setter — silently dropping the key\n // AND swapping the copy's prototype for cloned input. This keeps the\n // copy exactly what was validated.\n Object.defineProperty(copy, key, {\n value: cloned,\n enumerable: true,\n writable: true,\n configurable: true,\n });\n }\n return copy;\n }\n fail(\n `${path} must be JSON data (got ${Object.prototype.toString.call(value)})`,\n );\n}\n\nfunction assertIdentifier(\n value: unknown,\n path: string,\n maxLength = IDENTIFIER_MAX_LENGTH,\n): string {\n if (typeof value !== 'string' || value.length === 0) {\n fail(`${path} must be a non-empty string`);\n }\n if (value.length > maxLength) {\n fail(`${path} must be at most ${maxLength} characters`);\n }\n for (const character of value) {\n const code = character.charCodeAt(0);\n if (code < 32 || code === 127) fail(`${path} contains a control character`);\n }\n return value;\n}\n\nfunction normalizeCapability(\n value: unknown,\n): WebMcpCapabilityDeclaration | undefined {\n if (value === undefined) return undefined;\n if (!isPlainObject(value)) fail('capability must be an object literal');\n assertNoUnknownKeys(value, CAPABILITY_KEYS, 'capability');\n const { effect, idempotent, openWorld } = value;\n if (effect !== undefined && !WEBMCP_TOOL_EFFECTS.includes(effect as never)) {\n fail(`capability.effect must be one of ${WEBMCP_TOOL_EFFECTS.join(', ')}`);\n }\n if (idempotent !== undefined && typeof idempotent !== 'boolean') {\n fail('capability.idempotent must be a boolean');\n }\n if (openWorld !== undefined && typeof openWorld !== 'boolean') {\n fail('capability.openWorld must be a boolean');\n }\n return {\n ...(effect === undefined ? {} : { effect: effect as never }),\n ...(idempotent === undefined ? {} : { idempotent }),\n ...(openWorld === undefined ? {} : { openWorld }),\n };\n}\n\nfunction normalizeTarget(value: unknown): ViewIntentTarget {\n if (!isPlainObject(value)) fail('target must be an object literal');\n const registry = value.registry;\n if (registry === 'control') {\n assertNoUnknownKeys(value, CONTROL_TARGET_KEYS, 'target');\n const action = value.action;\n if (!VIEW_INTENT_CONTROL_ACTIONS.includes(action as never)) {\n fail(\n `target.action must be one of ${VIEW_INTENT_CONTROL_ACTIONS.join(', ')}`,\n );\n }\n return {\n registry: 'control',\n action: action as ViewIntentControlAction,\n ...(value.formId === undefined\n ? {}\n : { formId: assertIdentifier(value.formId, 'target.formId') }),\n ...(value.controlId === undefined\n ? {}\n : { controlId: assertIdentifier(value.controlId, 'target.controlId') }),\n };\n }\n if (registry === 'dataSurface') {\n assertNoUnknownKeys(value, DATA_SURFACE_TARGET_KEYS, 'target');\n if (\n value.kind !== undefined &&\n !VIEW_INTENT_DATA_SURFACE_KINDS.includes(value.kind as never)\n ) {\n fail(\n `target.kind must be one of ${VIEW_INTENT_DATA_SURFACE_KINDS.join(', ')}`,\n );\n }\n return {\n registry: 'dataSurface',\n controlId: assertIdentifier(value.controlId, 'target.controlId'),\n ...(value.surfaceId === undefined\n ? {}\n : { surfaceId: assertIdentifier(value.surfaceId, 'target.surfaceId') }),\n ...(value.kind === undefined\n ? {}\n : { kind: value.kind as ViewIntentDataSurfaceKind }),\n };\n }\n fail(\"target.registry must be 'control' or 'dataSurface'\");\n}\n\n/** Derive the WebMCP tool name for an intent id. */\nexport function viewIntentToolName(id: string): string {\n return id.replace(/[.-]/g, '_');\n}\n\n// ---------------------------------------------------------------------------\n// The declaration registry\n// ---------------------------------------------------------------------------\n\nconst REGISTRY_KEY = Symbol.for('@happyvertical/smrt-web:view-intents');\n\ninterface ViewIntentRegistryState {\n intents: Map<string, ViewIntent>;\n}\n\nfunction registryState(): ViewIntentRegistryState {\n // A `globalThis` singleton, matching `ObjectRegistry` in smrt-core, so the\n // registry survives HMR and duplicate module instances in a dev bundle.\n const host = globalThis as Record<symbol, unknown>;\n const existing = host[REGISTRY_KEY] as ViewIntentRegistryState | undefined;\n if (existing) return existing;\n const created: ViewIntentRegistryState = { intents: new Map() };\n host[REGISTRY_KEY] = created;\n return created;\n}\n\n/**\n * Declare a view intent.\n *\n * Call this at MODULE SCOPE with a single object literal — no spreads, no\n * conditionals, no computed values — in a `.ts` module, conventionally a\n * `Foo.intents.ts` sidecar beside the component that binds it. That form is\n * the contract #2591's scanner matcher reads without evaluating the module.\n *\n * The returned intent is deeply frozen and registered under its `id`. A\n * byte-identical re-declaration returns the SAME frozen object. A\n * re-declaration that differs REPLACES the previous one and warns: editing a\n * sidecar is what an HMR update is for, and throwing there would break the\n * single most common dev action on these files. Two genuinely different\n * intents sharing an id is an authoring mistake the warning surfaces, and one\n * #2591's scanner catches statically, where both declarations are visible at\n * once.\n *\n * @throws if the declaration is not static JSON data, carries an unknown\n * key, carries a function anywhere, names a reserved tool namespace, or\n * derives a WebMCP tool name another intent already derives.\n */\nexport function defineIntent(declaration: ViewIntentDeclaration): ViewIntent {\n if (!isPlainObject(declaration)) {\n fail('defineIntent expects an object literal');\n }\n // `Object.keys` invokes no getter, so the allowlist check is safe to run on\n // the caller's object and gives the clearest message for a smuggled key.\n assertNoUnknownKeys(declaration, DECLARATION_KEYS, 'declaration');\n // Validate AND copy before any field-specific check, so a smuggled callable\n // is rejected wherever it sits — including inside `inputSchema` or `target`\n // — and every check below reads the copy rather than re-reading a property\n // that could answer differently the second time.\n const source = cloneJsonValue(declaration, 'declaration') as Record<\n string,\n unknown\n >;\n assertNoUnknownKeys(source, DECLARATION_KEYS, 'declaration');\n\n const id = assertIdentifier(source.id, 'id', INTENT_ID_MAX_LENGTH);\n if (!INTENT_ID_PATTERN.test(id)) {\n fail(\n `id '${id}' must be lowercase and namespaced with at least one dot, e.g. 'orders.filter_by_status'`,\n );\n }\n if (viewIntentToolName(id).startsWith(RESERVED_TOOL_NAME_PREFIX)) {\n fail(\n `id '${id}' resolves into the reserved '${RESERVED_TOOL_NAME_PREFIX}' namespace of the six fixed UI tools`,\n );\n }\n const description = assertIdentifier(\n source.description,\n 'description',\n DESCRIPTION_MAX_LENGTH,\n );\n if (source.inputSchema !== undefined && !isPlainObject(source.inputSchema)) {\n fail('inputSchema must be an object literal');\n }\n\n const intent: ViewIntent = deepFreeze({\n kind: 'intent',\n id,\n description,\n inputSchema: (source.inputSchema ?? {\n type: 'object',\n properties: {},\n }) as Record<string, unknown>,\n classification: resolveDeclaredCapability(\n normalizeCapability(source.capability) ?? {},\n ),\n target: normalizeTarget(source.target),\n planes: ['browser'] as const,\n });\n\n const state = registryState();\n // `viewIntentToolName` is not injective — `orders.foo_bar` and\n // `orders.foo.bar` both flatten to `orders_foo_bar` — and two intents that\n // collide there would fight over one WebMCP tool name at mount, where the\n // failure is a shadowed or rejected registration rather than a clear error.\n // Reject the second declaration instead, at the only place both are visible.\n const toolName = viewIntentToolName(id);\n for (const other of state.intents.values()) {\n if (other.id !== id && viewIntentToolName(other.id) === toolName) {\n fail(\n `id '${id}' derives the WebMCP tool name '${toolName}', which intent '${other.id}' already derives`,\n );\n }\n }\n const existing = state.intents.get(id);\n if (existing) {\n if (JSON.stringify(existing) === JSON.stringify(intent)) return existing;\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); a replaced intent declaration surfaces via console.warn by design (#2588)\n console.warn(\n `[smrt-web] view intent '${id}' was re-declared with a different definition; replacing it. In development this is an HMR update. In a built application it means two declarations share one id.`,\n );\n }\n state.intents.set(id, intent);\n return intent;\n}\n\nfunction deepFreeze<T>(value: T): T {\n if (value && typeof value === 'object') {\n for (const entry of Object.values(value)) deepFreeze(entry);\n Object.freeze(value);\n }\n return value;\n}\n\n/** Every intent declared in modules loaded so far, in declaration order. */\nexport function listViewIntents(): readonly ViewIntent[] {\n return [...registryState().intents.values()];\n}\n\n/**\n * Look up a declared intent by id.\n *\n * Directly usable as `smrt-playbooks`' `PlaybookIntentResolver` seam (#2589):\n * a {@link ViewIntent} satisfies its `PlaybookIntentRecord` shape, so an\n * intent step inherits this intent's classification and browser-only plane\n * validity rather than classifying itself.\n */\nexport function resolveViewIntent(id: string): ViewIntent | undefined {\n return registryState().intents.get(id);\n}\n\n/**\n * Drop every declared intent. For tests and HMR teardown; production code\n * has no reason to call it.\n */\nexport function clearViewIntentRegistry(): void {\n registryState().intents.clear();\n}\n\n// ---------------------------------------------------------------------------\n// Compilation to a bespoke tool spec\n// ---------------------------------------------------------------------------\n\n/**\n * The `ControlInteractionRegistry` surface an intent uses. Declared\n * structurally so this package takes no dependency on\n * `@happyvertical/smrt-ui`; the real registry satisfies it.\n */\nexport interface ViewIntentControlRegistryPort {\n execute(\n command: {\n action: ViewIntentControlAction;\n identity: ViewIntentControlIdentity;\n value?: unknown;\n durationMs?: number;\n revision?: number;\n },\n context?: { source: 'agent' },\n ): Promise<{ ok: boolean; reason?: string }>;\n}\n\n/** A mounted control's full registry key, subject included. */\nexport interface ViewIntentControlIdentity {\n formId: string;\n controlId: string;\n subject?: ViewIntentSubject;\n}\n\n/** A mounted data surface's full registry key, subject included. */\nexport interface ViewIntentDataSurfaceIdentity {\n surfaceId: string;\n kind: ViewIntentDataSurfaceKind;\n subject?: ViewIntentSubject;\n}\n\n/** The `DataSurfaceRegistry` surface an intent uses. */\nexport interface ViewIntentDataSurfaceRegistryPort {\n inspect(\n identity: ViewIntentDataSurfaceIdentity,\n ): { revision: number } | undefined;\n execute(command: {\n version: 1;\n commandId: string;\n identity: ViewIntentDataSurfaceIdentity;\n expectedRevision: number;\n controlId: string;\n payload?: unknown;\n }): Promise<{ ok: boolean; revision?: number; reason?: string }>;\n}\n\n/** The mounted identity a binding supplies for an intent's lifetime. */\nexport type ViewIntentBinding =\n | {\n registry: 'control';\n registryPort: ViewIntentControlRegistryPort;\n identity: ViewIntentControlIdentity;\n }\n | {\n registry: 'dataSurface';\n registryPort: ViewIntentDataSurfaceRegistryPort;\n identity: ViewIntentDataSurfaceIdentity;\n };\n\n/**\n * A bespoke tool spec, structurally identical to\n * `WebMcpBespokeToolSpec` in `./webmcp.js`. Declared here rather than\n * imported so this module stays free of even a type edge to the registrar\n * entry.\n */\nexport interface ViewIntentToolSpec {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n annotations: WebMcpCapabilityAnnotations;\n execute: (args: Record<string, unknown>) => Promise<string>;\n}\n\n/**\n * Reasons an intent may report back to an agent. Anything else collapses to\n * `denied`, so a registry's internal reason never leaks through a tool\n * result. Mirrors the allowlist the fixed `smrt_ui_*` tools apply.\n */\nconst PUBLIC_REASONS = new Set([\n 'not_found',\n 'unsupported',\n 'consent_required',\n 'human_confirmation_required',\n 'sensitive_control',\n 'control_not_writable',\n 'control_not_editable',\n 'nothing_to_undo',\n 'stale_revision',\n 'idempotency_conflict',\n 'non_monotonic_revision',\n 'invalid_request',\n 'denied',\n]);\n\nfunction publicReason(reason: unknown): string {\n return typeof reason === 'string' && PUBLIC_REASONS.has(reason)\n ? reason\n : 'denied';\n}\n\nfunction ok(result: Record<string, unknown>): string {\n return JSON.stringify({ ok: true, ...result });\n}\n\nfunction denied(reason: string): string {\n return JSON.stringify({ ok: false, reason });\n}\n\n/**\n * WebMCP hands `execute` whatever the agent sent. Anything that is not a plain\n * object becomes an empty argument set, so a string or array argument fails\n * closed through the normal `invalid_request` path instead of throwing a\n * TypeError out of an `in` check.\n */\nfunction toolArguments(args: unknown): Record<string, unknown> {\n return isPlainObject(args) ? args : {};\n}\n\nlet commandCounter = 0;\n\nfunction newCommandId(): string {\n const cryptoLike = (globalThis as { crypto?: { randomUUID?: () => string } })\n .crypto;\n if (typeof cryptoLike?.randomUUID === 'function') {\n return cryptoLike.randomUUID();\n }\n commandCounter += 1;\n return `intent-${Date.now().toString(36)}-${commandCounter}`;\n}\n\nclass IntentArgumentError extends Error {}\n\n/**\n * Validate one agent-supplied argument and return a JSON copy of it. Copying\n * closes the same time-of-check gap `cloneJsonValue` closes for declarations:\n * the value handed to a registry command is the one that was validated, not\n * whatever a getter answers on the next read.\n */\nfunction jsonArg(args: Record<string, unknown>, key: string): unknown {\n try {\n return cloneJsonValue(args[key], key);\n } catch {\n throw new IntentArgumentError(key);\n }\n}\n\n/**\n * Read `revision` ONCE and keep the value that was type-checked, so a\n * two-faced getter cannot pass the `number` check and then land something\n * else on the command.\n */\nfunction optionalRevision(args: Record<string, unknown>): {\n revision?: number;\n} {\n const revision = args.revision;\n return typeof revision === 'number' && Number.isFinite(revision)\n ? { revision }\n : {};\n}\n\n/**\n * Collapse anything thrown on the execution path to an allowlisted reason.\n *\n * A registry does not only RETURN a rejection — `DataSurfaceRegistry` throws\n * for a payload that is too deep, too large, or carries a forbidden or\n * prototype-pollution key, and any registry can throw from a host handler. An\n * uncaught throw would escape both the `{ ok, reason }` contract and the\n * {@link publicReason} allowlist, handing the agent a raw internal message.\n * The six fixed `smrt_ui_*` tools guard the same way (`executeSafely` in\n * smrt-svelte's `webmcp-ui.ts`), collapsing anything unrecognized rather than\n * letting it out.\n */\nfunction intentErrorReason(error: unknown): string {\n return error instanceof IntentArgumentError ? 'invalid_request' : 'denied';\n}\n\nfunction buildControlCommand(\n target: ViewIntentControlTarget,\n identity: ViewIntentControlIdentity,\n args: Record<string, unknown>,\n): Parameters<ViewIntentControlRegistryPort['execute']>[0] {\n const base = { action: target.action, identity };\n switch (target.action) {\n case 'highlight': {\n const durationMs = args.durationMs;\n if (durationMs !== undefined) {\n if (typeof durationMs !== 'number' || !Number.isFinite(durationMs)) {\n throw new IntentArgumentError('durationMs');\n }\n return { ...base, durationMs };\n }\n return base;\n }\n case 'stage':\n if (!('value' in args)) throw new IntentArgumentError('value');\n return { ...base, value: jsonArg(args, 'value') };\n case 'apply':\n // `reviewedValueIsCanonical` is deliberately never set from intent\n // arguments: it is the staged-review surface's own marker, and an\n // agent-driven intent must not be able to claim a human review.\n return {\n ...base,\n ...('value' in args ? { value: jsonArg(args, 'value') } : {}),\n ...optionalRevision(args),\n };\n case 'discard':\n return { ...base, ...optionalRevision(args) };\n default:\n return base;\n }\n}\n\n/**\n * Compile a declared intent plus a mounted binding into a bespoke tool spec.\n *\n * The `execute` this returns is built here from `intent.target`; nothing an\n * author wrote is called. It dispatches exactly one registry command with\n * `source: 'agent'`, so every policy the registry enforces — staged review,\n * local-gesture proof, sensitivity, writability — applies unchanged.\n *\n * @throws if the binding does not match the intent's declared target.\n */\nexport function compileViewIntentToolSpec(\n intent: ViewIntent,\n binding: ViewIntentBinding,\n): ViewIntentToolSpec {\n if (binding.registry !== intent.target.registry) {\n throw new Error(\n `[smrt-web] intent '${intent.id}' targets the ${intent.target.registry} registry but was bound to ${binding.registry}`,\n );\n }\n\n const spec = {\n name: viewIntentToolName(intent.id),\n description: intent.description,\n inputSchema: intent.inputSchema,\n annotations: capabilityDeclarationHints(intent.classification),\n };\n\n if (binding.registry === 'control' && intent.target.registry === 'control') {\n const target = intent.target;\n const identity = binding.identity;\n assertBoundIdentity(intent.id, 'formId', target.formId, identity.formId);\n assertBoundIdentity(\n intent.id,\n 'controlId',\n target.controlId,\n identity.controlId,\n );\n const registry = binding.registryPort;\n return {\n ...spec,\n execute: async (args) => {\n try {\n const command = buildControlCommand(\n target,\n identity,\n toolArguments(args),\n );\n const result = await registry.execute(command, { source: 'agent' });\n return result.ok\n ? ok({ action: target.action, identity })\n : denied(publicReason(result.reason));\n } catch (error) {\n return denied(intentErrorReason(error));\n }\n },\n };\n }\n\n if (\n binding.registry === 'dataSurface' &&\n intent.target.registry === 'dataSurface'\n ) {\n const target = intent.target;\n const identity = binding.identity;\n assertBoundIdentity(\n intent.id,\n 'surfaceId',\n target.surfaceId,\n identity.surfaceId,\n );\n assertBoundIdentity(intent.id, 'kind', target.kind, identity.kind);\n const registry = binding.registryPort;\n return {\n ...spec,\n execute: async (args) => {\n try {\n const input = toolArguments(args);\n const payload =\n 'payload' in input ? jsonArg(input, 'payload') : undefined;\n const snapshot = registry.inspect(identity);\n if (!snapshot) return denied('not_found');\n const result = await registry.execute({\n version: 1,\n commandId: newCommandId(),\n identity,\n expectedRevision: snapshot.revision,\n controlId: target.controlId,\n ...(payload === undefined ? {} : { payload }),\n });\n return result.ok\n ? ok({\n controlId: target.controlId,\n identity,\n ...(result.revision === undefined\n ? {}\n : { revision: result.revision }),\n })\n : denied(publicReason(result.reason));\n } catch (error) {\n return denied(intentErrorReason(error));\n }\n },\n };\n }\n\n /* c8 ignore next 3 -- unreachable: registry equality is checked above */\n throw new Error(\n `[smrt-web] intent '${intent.id}' could not be compiled for its binding`,\n );\n}\n\nfunction assertBoundIdentity(\n id: string,\n field: string,\n declared: string | undefined,\n bound: string,\n): void {\n if (declared !== undefined && declared !== bound) {\n throw new Error(\n `[smrt-web] intent '${id}' declares ${field} '${declared}' but was bound to '${bound}'`,\n );\n }\n}\n"],"mappings":";AA4BO,IAAM,sBAAmD;CAC9D;CACA;CACA;AACF;AA2CO,SAAS,0BACd,UACgC;CAChC,MAAM,SAAS,oBAAoB,SACjC,SAAS,MACX,IACK,SAAS,SACV;CACJ,OAAO;EACL;EACA,aAAa,WAAW;EACxB,YAAY,SAAS,cAAc;EACnC,WAAW,SAAS,aAAa;CACnC;AACF;AAQO,SAAS,sBACd,gBAC6B;CAC7B,OAAO;EACL,cAAc,eAAe,WAAW;EACxC,iBAAiB,eAAe;EAChC,gBAAgB,eAAe;EAC/B,eAAe,eAAe;EAC9B,sBAAsB;CACxB;AACF;AAgBO,SAAS,2BACd,gBAC6B;CAC7B,OAAO;EACL,cAAc,eAAe,WAAW;EACxC,iBAAiB,eAAe,WAAW;EAC3C,gBAAgB,eAAe;EAC/B,eAAe,eAAe;EAC9B,sBAAsB;CACxB;AACF;;;ACnDA,IAAM,8BAAkE;CACtE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAKA,IAAM,iCAAuE;CAC3E;CACA;CACA;CACA;AACF;AAuGA,IAAM,mCAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,IAAM,kCAAkB,IAAI,IAAI;CAAC;CAAU;CAAc;AAAW,CAAC;AACrE,IAAM,sCAAsB,IAAI,IAAI;CAClC;CACA;CACA;CACA;AACF,CAAC;AACD,IAAM,2CAA2B,IAAI,IAAI;CACvC;CACA;CACA;CACA;AACF,CAAC;AAGD,IAAM,oBAAoB;AAC1B,IAAM,wBAAwB;AAC9B,IAAM,uBAAuB;AAC7B,IAAM,yBAAyB;AAO/B,IAAM,4BAA4B;AAElC,SAAS,KAAK,SAAwB;CACpC,MAAM,IAAI,MAAM,+CAA+C,SAAS;AAC1E;AAEA,SAAS,cAAc,OAAkD;CACvE,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO;CACxE,MAAM,QAAQ,OAAO,eAAe,KAAK;CACzC,OAAO,UAAU,OAAO,aAAa,UAAU;AACjD;AAEA,SAAS,oBACP,OACA,SACA,MACM;CACN,KAAA,MAAW,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,CAAC,QAAQ,IAAI,GAAG,GAClB,KACE,gBAAgB,IAAG,OAAQ,KAAI,2GACjC;AAGN;AAeA,SAAS,eAAe,OAAgB,MAAc,QAAQ,GAAY;CACxE,IAAI,QAAQ,IAAI,KAAK,GAAG,KAAI,6BAA8B;CAC1D,IAAI,UAAU,MAAM,OAAO;CAC3B,IAAI,OAAO,UAAU,YACnB,KACE,GAAG,KAAI,uJACT;CAEF,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW,OAAO;CACpE,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,KAAK,GAAG,KAAI,yBAA0B;EACnE,OAAO;CACT;CACA,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,OAAO,UACvB,eAAe,OAAO,GAAG,KAAI,GAAI,MAAK,IAAK,QAAQ,CAAC,CACtD;CAEF,IAAI,cAAc,KAAK,GAAG;EACxB,MAAM,OAAgC,CAAC;EAIvC,KAAA,MAAW,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;GAChD,MAAM,SAAS,eAAe,OAAO,GAAG,KAAI,GAAI,OAAO,QAAQ,CAAC;GAMhE,OAAO,eAAe,MAAM,KAAK;IAC/B,OAAO;IACP,YAAY;IACZ,UAAU;IACV,cAAc;GAChB,CAAC;EACH;EACA,OAAO;CACT;CACA,KACE,GAAG,KAAI,0BAA2B,OAAO,UAAU,SAAS,KAAK,KAAK,EAAC,EACzE;AACF;AAEA,SAAS,iBACP,OACA,MACA,YAAY,uBACJ;CACR,IAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GAChD,KAAK,GAAG,KAAI,4BAA6B;CAE3C,IAAI,MAAM,SAAS,WACjB,KAAK,GAAG,KAAI,mBAAoB,UAAS,YAAa;CAExD,KAAA,MAAW,aAAa,OAAO;EAC7B,MAAM,OAAO,UAAU,WAAW,CAAC;EACnC,IAAI,OAAO,MAAM,SAAS,KAAK,KAAK,GAAG,KAAI,8BAA+B;CAC5E;CACA,OAAO;AACT;AAEA,SAAS,oBACP,OACyC;CACzC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,IAAI,CAAC,cAAc,KAAK,GAAG,KAAK,sCAAsC;CACtE,oBAAoB,OAAO,iBAAiB,YAAY;CACxD,MAAM,EAAE,QAAQ,YAAY,cAAc;CAC1C,IAAI,WAAW,KAAA,KAAa,CAAC,oBAAoB,SAAS,MAAe,GACvE,KAAK,oCAAoC,oBAAoB,KAAK,IAAI,GAAG;CAE3E,IAAI,eAAe,KAAA,KAAa,OAAO,eAAe,WACpD,KAAK,yCAAyC;CAEhD,IAAI,cAAc,KAAA,KAAa,OAAO,cAAc,WAClD,KAAK,wCAAwC;CAE/C,OAAO;EACL,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,OAAwB;EAC1D,GAAI,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW;EACjD,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU;CACjD;AACF;AAEA,SAAS,gBAAgB,OAAkC;CACzD,IAAI,CAAC,cAAc,KAAK,GAAG,KAAK,kCAAkC;CAClE,MAAM,WAAW,MAAM;CACvB,IAAI,aAAa,WAAW;EAC1B,oBAAoB,OAAO,qBAAqB,QAAQ;EACxD,MAAM,SAAS,MAAM;EACrB,IAAI,CAAC,4BAA4B,SAAS,MAAe,GACvD,KACE,gCAAgC,4BAA4B,KAAK,IAAI,GACvE;EAEF,OAAO;GACL,UAAU;GACV;GACA,GAAI,MAAM,WAAW,KAAA,IACjB,CAAC,IACD,EAAE,QAAQ,iBAAiB,MAAM,QAAQ,eAAe,EAAE;GAC9D,GAAI,MAAM,cAAc,KAAA,IACpB,CAAC,IACD,EAAE,WAAW,iBAAiB,MAAM,WAAW,kBAAkB,EAAE;EACzE;CACF;CACA,IAAI,aAAa,eAAe;EAC9B,oBAAoB,OAAO,0BAA0B,QAAQ;EAC7D,IACE,MAAM,SAAS,KAAA,KACf,CAAC,+BAA+B,SAAS,MAAM,IAAa,GAE5D,KACE,8BAA8B,+BAA+B,KAAK,IAAI,GACxE;EAEF,OAAO;GACL,UAAU;GACV,WAAW,iBAAiB,MAAM,WAAW,kBAAkB;GAC/D,GAAI,MAAM,cAAc,KAAA,IACpB,CAAC,IACD,EAAE,WAAW,iBAAiB,MAAM,WAAW,kBAAkB,EAAE;GACvE,GAAI,MAAM,SAAS,KAAA,IACf,CAAC,IACD,EAAE,MAAM,MAAM,KAAkC;EACtD;CACF;CACA,KAAK,oDAAoD;AAC3D;AAGO,SAAS,mBAAmB,IAAoB;CACrD,OAAO,GAAG,QAAQ,SAAS,GAAG;AAChC;AAMA,IAAM,eAAe,uBAAO,IAAI,sCAAsC;AAMtE,SAAS,gBAAyC;CAGhD,MAAM,OAAO;CACb,MAAM,WAAW,KAAK;CACtB,IAAI,UAAU,OAAO;CACrB,MAAM,UAAmC,EAAE,yBAAS,IAAI,IAAI,EAAE;CAC9D,KAAK,gBAAgB;CACrB,OAAO;AACT;AAuBO,SAAS,aAAa,aAAgD;CAC3E,IAAI,CAAC,cAAc,WAAW,GAC5B,KAAK,wCAAwC;CAI/C,oBAAoB,aAAa,kBAAkB,aAAa;CAKhE,MAAM,SAAS,eAAe,aAAa,aAAa;CAIxD,oBAAoB,QAAQ,kBAAkB,aAAa;CAE3D,MAAM,KAAK,iBAAiB,OAAO,IAAI,MAAM,oBAAoB;CACjE,IAAI,CAAC,kBAAkB,KAAK,EAAE,GAC5B,KACE,OAAO,GAAE,yFACX;CAEF,IAAI,mBAAmB,EAAE,CAAA,CAAE,WAAW,yBAAyB,GAC7D,KACE,OAAO,GAAE,gCAAiC,0BAAyB,sCACrE;CAEF,MAAM,cAAc,iBAClB,OAAO,aACP,eACA,sBACF;CACA,IAAI,OAAO,gBAAgB,KAAA,KAAa,CAAC,cAAc,OAAO,WAAW,GACvE,KAAK,uCAAuC;CAG9C,MAAM,SAAqB,WAAW;EACpC,MAAM;EACN;EACA;EACA,aAAc,OAAO,eAAe;GAClC,MAAM;GACN,YAAY,CAAC;EACf;EACA,gBAAgB,0BACd,oBAAoB,OAAO,UAAU,KAAK,CAAC,CAC7C;EACA,QAAQ,gBAAgB,OAAO,MAAM;EACrC,QAAQ,CAAC,SAAS;CACpB,CAAC;CAED,MAAM,QAAQ,cAAc;CAM5B,MAAM,WAAW,mBAAmB,EAAE;CACtC,KAAA,MAAW,SAAS,MAAM,QAAQ,OAAO,GACvC,IAAI,MAAM,OAAO,MAAM,mBAAmB,MAAM,EAAE,MAAM,UACtD,KACE,OAAO,GAAE,kCAAmC,SAAQ,mBAAoB,MAAM,GAAE,kBAClF;CAGJ,MAAM,WAAW,MAAM,QAAQ,IAAI,EAAE;CACrC,IAAI,UAAU;EACZ,IAAI,KAAK,UAAU,QAAQ,MAAM,KAAK,UAAU,MAAM,GAAG,OAAO;EAEhE,QAAQ,KACN,2BAA2B,GAAE,kKAC/B;CACF;CACA,MAAM,QAAQ,IAAI,IAAI,MAAM;CAC5B,OAAO;AACT;AAEA,SAAS,WAAc,OAAa;CAClC,IAAI,SAAS,OAAO,UAAU,UAAU;EACtC,KAAA,MAAW,SAAS,OAAO,OAAO,KAAK,GAAG,WAAW,KAAK;EAC1D,OAAO,OAAO,KAAK;CACrB;CACA,OAAO;AACT;AAGO,SAAS,kBAAyC;CACvD,OAAO,CAAC,GAAG,cAAc,CAAA,CAAE,QAAQ,OAAO,CAAC;AAC7C;AAUO,SAAS,kBAAkB,IAAoC;CACpE,OAAO,cAAc,CAAA,CAAE,QAAQ,IAAI,EAAE;AACvC;AAMO,SAAS,0BAAgC;CAC9C,cAAc,CAAA,CAAE,QAAQ,MAAM;AAChC;AAqFA,IAAM,iCAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,aAAa,QAAyB;CAC7C,OAAO,OAAO,WAAW,YAAY,eAAe,IAAI,MAAM,IAC1D,SACA;AACN;AAEA,SAAS,GAAG,QAAyC;CACnD,OAAO,KAAK,UAAU;EAAE,IAAI;EAAM,GAAG;CAAO,CAAC;AAC/C;AAEA,SAAS,OAAO,QAAwB;CACtC,OAAO,KAAK,UAAU;EAAE,IAAI;EAAO;CAAO,CAAC;AAC7C;AAQA,SAAS,cAAc,MAAwC;CAC7D,OAAO,cAAc,IAAI,IAAI,OAAO,CAAC;AACvC;AAEA,IAAI,iBAAiB;AAErB,SAAS,eAAuB;CAC9B,MAAM,aAAc,WACjB;CACH,IAAI,OAAO,YAAY,eAAe,YACpC,OAAO,WAAW,WAAW;CAE/B,kBAAkB;CAClB,OAAO,UAAU,KAAK,IAAI,CAAA,CAAE,SAAS,EAAE,EAAC,GAAI;AAC9C;AAEA,IAAM,sBAAN,cAAkC,MAAM,CAAC;AAQzC,SAAS,QAAQ,MAA+B,KAAsB;CACpE,IAAI;EACF,OAAO,eAAe,KAAK,MAAM,GAAG;CACtC,QAAQ;EACN,MAAM,IAAI,oBAAoB,GAAG;CACnC;AACF;AAOA,SAAS,iBAAiB,MAExB;CACA,MAAM,WAAW,KAAK;CACtB,OAAO,OAAO,aAAa,YAAY,OAAO,SAAS,QAAQ,IAC3D,EAAE,SAAS,IACX,CAAC;AACP;AAcA,SAAS,kBAAkB,OAAwB;CACjD,OAAO,iBAAiB,sBAAsB,oBAAoB;AACpE;AAEA,SAAS,oBACP,QACA,UACA,MACyD;CACzD,MAAM,OAAO;EAAE,QAAQ,OAAO;EAAQ;CAAS;CAC/C,QAAQ,OAAO,QAAf;EACE,KAAK,aAAa;GAChB,MAAM,aAAa,KAAK;GACxB,IAAI,eAAe,KAAA,GAAW;IAC5B,IAAI,OAAO,eAAe,YAAY,CAAC,OAAO,SAAS,UAAU,GAC/D,MAAM,IAAI,oBAAoB,YAAY;IAE5C,OAAO;KAAE,GAAG;KAAM;IAAW;GAC/B;GACA,OAAO;EACT;EACA,KAAK;GACH,IAAI,EAAE,WAAW,OAAO,MAAM,IAAI,oBAAoB,OAAO;GAC7D,OAAO;IAAE,GAAG;IAAM,OAAO,QAAQ,MAAM,OAAO;GAAE;EAClD,KAAK,SAIH,OAAO;GACL,GAAG;GACH,GAAI,WAAW,OAAO,EAAE,OAAO,QAAQ,MAAM,OAAO,EAAE,IAAI,CAAC;GAC3D,GAAG,iBAAiB,IAAI;EAC1B;EACF,KAAK,WACH,OAAO;GAAE,GAAG;GAAM,GAAG,iBAAiB,IAAI;EAAE;EAC9C,SACE,OAAO;CACX;AACF;AAYO,SAAS,0BACd,QACA,SACoB;CACpB,IAAI,QAAQ,aAAa,OAAO,OAAO,UACrC,MAAM,IAAI,MACR,sBAAsB,OAAO,GAAE,gBAAiB,OAAO,OAAO,SAAQ,6BAA8B,QAAQ,UAC9G;CAGF,MAAM,OAAO;EACX,MAAM,mBAAmB,OAAO,EAAE;EAClC,aAAa,OAAO;EACpB,aAAa,OAAO;EACpB,aAAa,2BAA2B,OAAO,cAAc;CAC/D;CAEA,IAAI,QAAQ,aAAa,aAAa,OAAO,OAAO,aAAa,WAAW;EAC1E,MAAM,SAAS,OAAO;EACtB,MAAM,WAAW,QAAQ;EACzB,oBAAoB,OAAO,IAAI,UAAU,OAAO,QAAQ,SAAS,MAAM;EACvE,oBACE,OAAO,IACP,aACA,OAAO,WACP,SAAS,SACX;EACA,MAAM,WAAW,QAAQ;EACzB,OAAO;GACL,GAAG;GACH,SAAS,OAAO,SAAS;IACvB,IAAI;KACF,MAAM,UAAU,oBACd,QACA,UACA,cAAc,IAAI,CACpB;KACA,MAAM,SAAS,MAAM,SAAS,QAAQ,SAAS,EAAE,QAAQ,QAAQ,CAAC;KAClE,OAAO,OAAO,KACV,GAAG;MAAE,QAAQ,OAAO;MAAQ;KAAS,CAAC,IACtC,OAAO,aAAa,OAAO,MAAM,CAAC;IACxC,SAAS,OAAO;KACd,OAAO,OAAO,kBAAkB,KAAK,CAAC;IACxC;GACF;EACF;CACF;CAEA,IACE,QAAQ,aAAa,iBACrB,OAAO,OAAO,aAAa,eAC3B;EACA,MAAM,SAAS,OAAO;EACtB,MAAM,WAAW,QAAQ;EACzB,oBACE,OAAO,IACP,aACA,OAAO,WACP,SAAS,SACX;EACA,oBAAoB,OAAO,IAAI,QAAQ,OAAO,MAAM,SAAS,IAAI;EACjE,MAAM,WAAW,QAAQ;EACzB,OAAO;GACL,GAAG;GACH,SAAS,OAAO,SAAS;IACvB,IAAI;KACF,MAAM,QAAQ,cAAc,IAAI;KAChC,MAAM,UACJ,aAAa,QAAQ,QAAQ,OAAO,SAAS,IAAI,KAAA;KACnD,MAAM,WAAW,SAAS,QAAQ,QAAQ;KAC1C,IAAI,CAAC,UAAU,OAAO,OAAO,WAAW;KACxC,MAAM,SAAS,MAAM,SAAS,QAAQ;MACpC,SAAS;MACT,WAAW,aAAa;MACxB;MACA,kBAAkB,SAAS;MAC3B,WAAW,OAAO;MAClB,GAAI,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ;KAC7C,CAAC;KACD,OAAO,OAAO,KACV,GAAG;MACD,WAAW,OAAO;MAClB;MACA,GAAI,OAAO,aAAa,KAAA,IACpB,CAAC,IACD,EAAE,UAAU,OAAO,SAAS;KAClC,CAAC,IACD,OAAO,aAAa,OAAO,MAAM,CAAC;IACxC,SAAS,OAAO;KACd,OAAO,OAAO,kBAAkB,KAAK,CAAC;IACxC;GACF;EACF;CACF;CAGA,MAAM,IAAI,MACR,sBAAsB,OAAO,GAAE,wCACjC;AACF;AAEA,SAAS,oBACP,IACA,OACA,UACA,OACM;CACN,IAAI,aAAa,KAAA,KAAa,aAAa,OACzC,MAAM,IAAI,MACR,sBAAsB,GAAE,aAAc,MAAK,IAAK,SAAQ,sBAAuB,MAAK,EACtF;AAEJ"}
|