@tangle-network/agent-app 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/DesignCanvas-3JEEIT6Y.js +10 -0
  2. package/dist/DesignCanvas-3JEEIT6Y.js.map +1 -0
  3. package/dist/DesignCanvasEditor-37LPJIIR.js +9 -0
  4. package/dist/DesignCanvasEditor-37LPJIIR.js.map +1 -0
  5. package/dist/chunk-2Q73HGDI.js +1743 -0
  6. package/dist/chunk-2Q73HGDI.js.map +1 -0
  7. package/dist/{chunk-3WAJWYKD.js → chunk-6UOE5CTA.js} +9 -92
  8. package/dist/{chunk-3WAJWYKD.js.map → chunk-6UOE5CTA.js.map} +1 -1
  9. package/dist/chunk-7QCIYDGC.js +1119 -0
  10. package/dist/chunk-7QCIYDGC.js.map +1 -0
  11. package/dist/chunk-A76ZHWNF.js +194 -0
  12. package/dist/chunk-A76ZHWNF.js.map +1 -0
  13. package/dist/chunk-F5KTWRO7.js +2276 -0
  14. package/dist/chunk-F5KTWRO7.js.map +1 -0
  15. package/dist/chunk-JZAJE3JL.js +990 -0
  16. package/dist/chunk-JZAJE3JL.js.map +1 -0
  17. package/dist/{chunk-CF5DZELC.js → chunk-SSX2A6XX.js} +39 -2
  18. package/dist/chunk-SSX2A6XX.js.map +1 -0
  19. package/dist/design-canvas/drizzle.d.ts +569 -0
  20. package/dist/design-canvas/drizzle.js +183 -0
  21. package/dist/design-canvas/drizzle.js.map +1 -0
  22. package/dist/design-canvas/index.d.ts +261 -0
  23. package/dist/design-canvas/index.js +96 -0
  24. package/dist/design-canvas/index.js.map +1 -0
  25. package/dist/design-canvas-react/index.d.ts +916 -0
  26. package/dist/design-canvas-react/index.js +423 -0
  27. package/dist/design-canvas-react/index.js.map +1 -0
  28. package/dist/export-presets-Dl5Aa5xj.d.ts +284 -0
  29. package/dist/index.d.ts +6 -1
  30. package/dist/index.js +103 -3
  31. package/dist/mcp-rpc-DLw_r9PQ.d.ts +55 -0
  32. package/dist/model-BHLN208Z.d.ts +183 -0
  33. package/dist/sequences/index.d.ts +4 -0
  34. package/dist/sequences/index.js +2 -2
  35. package/dist/store-CUStmtdH.d.ts +64 -0
  36. package/dist/tools/index.d.ts +64 -2
  37. package/dist/tools/index.js +10 -2
  38. package/package.json +28 -3
  39. package/dist/chunk-CF5DZELC.js.map +0 -1
  40. package/dist/chunk-IJZJWKUK.js +0 -77
  41. package/dist/chunk-IJZJWKUK.js.map +0 -1
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Design-canvas scene model — the product-agnostic document behind the visual
3
+ * asset editor. A document is an ordered list of pages; a page is an ordered
4
+ * list of elements (index order IS z-order, bottom→top); every element is a
5
+ * typed node with a closed attribute vocabulary. The whole document
6
+ * serializes as one JSON value and persists atomically with a revision
7
+ * counter (see ./store) — every canvas action is a durable operation against
8
+ * this schema, which is what makes the canvas automatable: agents and
9
+ * external data sources mutate the same document the editor renders.
10
+ *
11
+ * Units are CSS pixels throughout; `settings.dpi` carries the print
12
+ * conversion factor for bleed/trim-aware exports. Angles are degrees.
13
+ * Nothing here touches Konva, React, or a database.
14
+ */
15
+ declare const SCENE_SCHEMA_VERSION = 1;
16
+ interface SceneDocument {
17
+ schemaVersion: typeof SCENE_SCHEMA_VERSION;
18
+ title: string;
19
+ pages: ScenePage[];
20
+ settings: SceneSettings;
21
+ metadata: Record<string, unknown>;
22
+ }
23
+ interface SceneSettings {
24
+ /** Print conversion factor for mm↔px math at export; 96 = CSS default. */
25
+ dpi: number;
26
+ }
27
+ /** Per-side bleed extents in px, drawn OUTSIDE the page bounds. Trim = the
28
+ * page rect itself; exports may include or exclude the bleed area. */
29
+ interface PageBleed {
30
+ top: number;
31
+ right: number;
32
+ bottom: number;
33
+ left: number;
34
+ }
35
+ /** Saved ruler guides, in page coordinates. */
36
+ interface PageGuides {
37
+ vertical: number[];
38
+ horizontal: number[];
39
+ }
40
+ interface ScenePage {
41
+ id: string;
42
+ name: string;
43
+ width: number;
44
+ height: number;
45
+ /** Page background fill (color string); elements paint over it. */
46
+ background: string;
47
+ bleed: PageBleed | null;
48
+ guides: PageGuides;
49
+ elements: SceneElement[];
50
+ }
51
+ /** Attributes every element carries. `x`/`y` are the element's top-left in
52
+ * page coordinates (for `group`, children are relative to the group origin).
53
+ * `slot` names a template binding point — `apply_data` targets it. */
54
+ interface SceneElementBase {
55
+ id: string;
56
+ name: string;
57
+ x: number;
58
+ y: number;
59
+ /** Degrees, clockwise, about the element's top-left origin (Konva default). */
60
+ rotation: number;
61
+ /** 0..1 */
62
+ opacity: number;
63
+ locked: boolean;
64
+ visible: boolean;
65
+ slot?: string;
66
+ }
67
+ interface RectElement extends SceneElementBase {
68
+ kind: 'rect';
69
+ width: number;
70
+ height: number;
71
+ fill: string;
72
+ stroke?: string;
73
+ strokeWidth?: number;
74
+ cornerRadius?: number;
75
+ }
76
+ interface EllipseElement extends SceneElementBase {
77
+ kind: 'ellipse';
78
+ width: number;
79
+ height: number;
80
+ fill: string;
81
+ stroke?: string;
82
+ strokeWidth?: number;
83
+ }
84
+ interface LineElement extends SceneElementBase {
85
+ kind: 'line';
86
+ /** Flat [x0, y0, x1, y1, ...] relative to (x, y); ≥ 2 points. */
87
+ points: number[];
88
+ stroke: string;
89
+ strokeWidth: number;
90
+ dash?: number[];
91
+ }
92
+ interface TextElement extends SceneElementBase {
93
+ kind: 'text';
94
+ text: string;
95
+ /** Wrap width; height derives from content. */
96
+ width: number;
97
+ fontFamily: string;
98
+ fontSize: number;
99
+ fontStyle: 'normal' | 'bold' | 'italic' | 'bold italic';
100
+ fill: string;
101
+ align: 'left' | 'center' | 'right';
102
+ lineHeight: number;
103
+ letterSpacing: number;
104
+ }
105
+ interface ImageElement extends SceneElementBase {
106
+ kind: 'image';
107
+ width: number;
108
+ height: number;
109
+ /** http(s) or rooted /api/ path — same boundary rule as sequences media. */
110
+ src: string;
111
+ /** How the source maps into the frame; 'fill' stretches, 'cover' crops. */
112
+ fit: 'fill' | 'cover' | 'contain';
113
+ }
114
+ /** Video placed on a canvas renders and exports as its poster frame — motion
115
+ * belongs to the sequences surface; this keeps video assets placeable in
116
+ * static layouts (e.g. a thumbnail mock) without a playback engine. */
117
+ interface VideoElement extends SceneElementBase {
118
+ kind: 'video';
119
+ width: number;
120
+ height: number;
121
+ src: string;
122
+ posterSrc?: string;
123
+ }
124
+ interface GroupElement extends SceneElementBase {
125
+ kind: 'group';
126
+ children: SceneElement[];
127
+ }
128
+ type SceneElement = RectElement | EllipseElement | LineElement | TextElement | ImageElement | VideoElement | GroupElement;
129
+ type SceneElementKind = SceneElement['kind'];
130
+ declare const SCENE_ELEMENT_KINDS: readonly SceneElementKind[];
131
+ interface Bounds {
132
+ x: number;
133
+ y: number;
134
+ width: number;
135
+ height: number;
136
+ }
137
+ /** Unrotated local extent of an element (line/group derive from content). */
138
+ declare function elementExtent(element: SceneElement): {
139
+ width: number;
140
+ height: number;
141
+ };
142
+ declare function estimateTextHeight(element: Pick<TextElement, 'text' | 'fontSize' | 'lineHeight'>): number;
143
+ /** Axis-aligned bounding box in the parent's coordinate space, accounting for
144
+ * rotation about the element's top-left origin. */
145
+ declare function elementAabb(element: SceneElement): Bounds;
146
+ declare function boundsIntersect(a: Bounds, b: Bounds): boolean;
147
+ declare function requirePage(document: SceneDocument, pageId: string): ScenePage;
148
+ /** Depth-first search across a page including group children. Returns the
149
+ * element and the array that owns it (page.elements or a group's children),
150
+ * so callers can splice in place. */
151
+ declare function findElement(page: ScenePage, elementId: string): {
152
+ element: SceneElement;
153
+ owner: SceneElement[];
154
+ index: number;
155
+ } | null;
156
+ declare function requireElement(page: ScenePage, elementId: string): {
157
+ element: SceneElement;
158
+ owner: SceneElement[];
159
+ index: number;
160
+ };
161
+ /** All slot names declared across the document — the template's fillable
162
+ * surface. Duplicate slot names are a validation error (see ./validate). */
163
+ declare function collectSlots(document: SceneDocument): Map<string, {
164
+ pageId: string;
165
+ elementId: string;
166
+ kind: SceneElementKind;
167
+ }>;
168
+ interface NewPageOptions {
169
+ name?: string;
170
+ width?: number;
171
+ height?: number;
172
+ background?: string;
173
+ }
174
+ declare function createEmptyDocument(title: string, page?: NewPageOptions): SceneDocument;
175
+ declare function createPage(options: NewPageOptions, id: string): ScenePage;
176
+ declare function assertPositiveFinite(value: number, label: string): void;
177
+ declare function assertFinite(value: number, label: string): void;
178
+ declare function assertColor(value: string, label: string): void;
179
+ /** Media boundary rule shared with sequences: remote http(s) or a rooted
180
+ * /api/ path — never sandbox-local files or data: blobs. */
181
+ declare function assertSceneMediaSrc(value: string, label: string): void;
182
+
183
+ export { type Bounds as B, type EllipseElement as E, type GroupElement as G, type ImageElement as I, type LineElement as L, type NewPageOptions as N, type PageBleed as P, type RectElement as R, SCENE_ELEMENT_KINDS as S, type TextElement as T, type VideoElement as V, type PageGuides as a, SCENE_SCHEMA_VERSION as b, type SceneDocument as c, type SceneElement as d, type SceneElementBase as e, type SceneElementKind as f, type ScenePage as g, type SceneSettings as h, assertColor as i, assertFinite as j, assertPositiveFinite as k, assertSceneMediaSrc as l, boundsIntersect as m, collectSlots as n, createEmptyDocument as o, createPage as p, elementAabb as q, elementExtent as r, estimateTextHeight as s, findElement as t, requireElement as u, requirePage as v };
@@ -276,6 +276,10 @@ declare function findSequenceMcpTool(name: string): SequenceMcpToolDefinition |
276
276
  * execution failures (argument shape, validation, store throws) become
277
277
  * `isError` tool results carrying the thrown message verbatim so the model can
278
278
  * read WHY and retry; only protocol-level misuse becomes a JSON-RPC error.
279
+ *
280
+ * The envelope (JSON-RPC framing, initialize/ping/tools/list/tools/call,
281
+ * notification 202, -32601) lives in {@link createMcpToolHandler}; this module
282
+ * is a thin adapter wiring the sequences tool list + playhead env.
279
283
  */
280
284
 
281
285
  /** Newest first. The handler echoes the client's requested version when
@@ -41,7 +41,7 @@ import {
41
41
  validateSetClipText,
42
42
  validateSplitClip,
43
43
  validateTrimClip
44
- } from "../chunk-3WAJWYKD.js";
44
+ } from "../chunk-6UOE5CTA.js";
45
45
  import {
46
46
  MIN_SEQUENCE_CLIP_FRAMES,
47
47
  assertClipFitsSequence,
@@ -55,7 +55,7 @@ import {
55
55
  snapshotFrame,
56
56
  trackIntervals
57
57
  } from "../chunk-ZYBWGSAZ.js";
58
- import "../chunk-IJZJWKUK.js";
58
+ import "../chunk-A76ZHWNF.js";
59
59
  export {
60
60
  DEFAULT_SEQUENCES_MCP_DESCRIPTION,
61
61
  MAX_CAPTION_BATCH,
@@ -0,0 +1,64 @@
1
+ import { c as SceneDocument } from './model-BHLN208Z.js';
2
+
3
+ /**
4
+ * Storage contract for design-canvas documents. Unlike sequences (row per
5
+ * clip), a scene document persists as ONE JSON value with a monotonic
6
+ * revision counter — saves are atomic and optimistic: a save carrying a
7
+ * stale `expectedRev` throws, the caller refetches and replays. That keeps
8
+ * concurrent editors (human + agent in the same document) from silently
9
+ * clobbering each other without needing row-level merge machinery.
10
+ *
11
+ * The product constructs one store per (workspace, document, actor) request
12
+ * scope — RBAC runs BEFORE construction; the store never re-checks identity.
13
+ * Every method throws on failure; the MCP dispatcher converts throws into
14
+ * structured tool errors.
15
+ */
16
+
17
+ interface SceneDocumentRecord {
18
+ document: SceneDocument;
19
+ /** Monotonic revision; increments on every successful save. */
20
+ rev: number;
21
+ }
22
+ interface SceneDecision {
23
+ id: string;
24
+ kind: 'human_edit' | 'agent_edit' | 'agent_proposal' | 'export' | 'note';
25
+ instruction: string;
26
+ reasoningSummary: string | null;
27
+ metadata: Record<string, unknown>;
28
+ createdAt: Date;
29
+ }
30
+ interface NewSceneDecision {
31
+ kind: SceneDecision['kind'];
32
+ instruction: string;
33
+ reasoningSummary?: string | null;
34
+ metadata?: Record<string, unknown>;
35
+ }
36
+ type SceneExportFormat = 'png' | 'jpeg' | 'json';
37
+ interface SceneExportRecord {
38
+ id: string;
39
+ format: SceneExportFormat;
40
+ status: 'queued' | 'processing' | 'completed' | 'failed';
41
+ resultUrl: string | null;
42
+ metadata: Record<string, unknown>;
43
+ createdAt: Date;
44
+ }
45
+ interface SceneStore {
46
+ /** Current document + revision. */
47
+ getDocument(): Promise<SceneDocumentRecord>;
48
+ /** Atomic full-document save. Throws when `expectedRev` is stale — the
49
+ * caller must refetch, reapply, and retry; never merge silently. */
50
+ saveDocument(document: SceneDocument, expectedRev: number): Promise<SceneDocumentRecord>;
51
+ recordDecision(input: NewSceneDecision): Promise<SceneDecision>;
52
+ createExport(format: SceneExportFormat, metadata?: Record<string, unknown>): Promise<SceneExportRecord>;
53
+ listDecisions(limit?: number): Promise<SceneDecision[]>;
54
+ listExports(limit?: number): Promise<SceneExportRecord[]>;
55
+ }
56
+ /** Per-request scope a product binds at store construction; decision and
57
+ * export rows attribute to the acting user — never trusted from tool args. */
58
+ interface SceneStoreScope {
59
+ workspaceId: string;
60
+ documentId: string;
61
+ userId: string;
62
+ }
63
+
64
+ export type { NewSceneDecision as N, SceneDecision as S, SceneDocumentRecord as a, SceneExportFormat as b, SceneExportRecord as c, SceneStore as d, SceneStoreScope as e };
@@ -1,7 +1,8 @@
1
1
  import { b as AppToolName, f as ToolHeaderNames } from '../mcp-CIupfjxV.js';
2
2
  export { A as APP_TOOL_NAMES, a as AppToolMcpServer, c as AuthenticateOptions, B as BuildHttpMcpServerOptions, d as BuildMcpServerOptions, D as DEFAULT_APP_TOOL_PATHS, e as DEFAULT_HEADER_NAMES, O as OpenAIFunctionTool, T as ToolAuthResult, g as authenticateToolRequest, h as buildAppToolMcpServer, i as buildAppToolOpenAITools, j as buildHttpMcpServer, k as isAppToolName, r as readToolArgs } from '../mcp-CIupfjxV.js';
3
- import { c as AppToolHandlers, f as AppToolTaxonomy, b as AppToolContext, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-By4B3K37.js';
3
+ import { f as AppToolTaxonomy, c as AppToolHandlers, b as AppToolContext, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-By4B3K37.js';
4
4
  export { A as AddCitationArgs, a as AddCitationResult, R as RenderUiArgs, g as RenderUiResult, S as ScheduleFollowupArgs, h as ScheduleFollowupResult, i as SubmitProposalArgs, j as SubmitProposalResult } from '../types-By4B3K37.js';
5
+ export { C as CreateMcpToolHandlerOptions, M as MCP_PROTOCOL_VERSIONS, a as McpProtocolVersion, b as McpServerInfo, c as McpToolDefinition, d as createMcpToolHandler } from '../mcp-rpc-DLw_r9PQ.js';
5
6
 
6
7
  /** A correctable bad-input error a tool handler throws; the HTTP layer maps it
7
8
  * to a 4xx with the code, the runtime layer to a failed tool_result. So the
@@ -65,6 +66,67 @@ declare function verifyExpiringCapabilityToken(subject: string, token: string, o
65
66
  now?: () => number;
66
67
  }): Promise<boolean>;
67
68
 
69
+ /**
70
+ * Capability gating — compose an agent session's tool surface from a
71
+ * product-defined capability registry.
72
+ *
73
+ * Products that let users pick what an agent can do (a "Studio" agent with the
74
+ * full build toolset vs a clean "Assistant" with none) all need the same
75
+ * mechanics: a registry of named capabilities, each unlocking proposal types
76
+ * and/or named tool groups, resolved against the product's
77
+ * {@link AppToolTaxonomy} into the concrete tools to expose. The mechanics are
78
+ * generic and live here; the capability VOCABULARY (ids, labels, which
79
+ * proposal types, which tool groups) is the product's.
80
+ */
81
+
82
+ /** One toggleable tool group in a product's capability registry. */
83
+ interface ToolCapability {
84
+ id: string;
85
+ label: string;
86
+ description: string;
87
+ /** Proposal types this capability unlocks (intersected with the taxonomy,
88
+ * so a capability can never widen the product's proposal surface). */
89
+ proposalTypes?: readonly string[];
90
+ /** Unlocks every taxonomy proposal type beyond the base set — the domain's
91
+ * specialized long tail (risk_assessment, vuln_report, …). */
92
+ domainActions?: boolean;
93
+ /** Named product tool groups (e.g. 'sandbox', 'integrations') this
94
+ * capability unlocks. The vocabulary is the product's; the resolver only
95
+ * unions them. */
96
+ toolGroups?: readonly string[];
97
+ }
98
+ interface ResolveToolCapabilitiesOptions {
99
+ taxonomy: AppToolTaxonomy;
100
+ /** The product's full capability registry. */
101
+ capabilities: readonly ToolCapability[];
102
+ /** Enabled capability ids. `undefined` means full access (legacy callers
103
+ * that don't send a capability set); an explicit `[]` means a pure chat
104
+ * agent with no tools. Unknown ids are ignored. */
105
+ enabled: readonly string[] | undefined;
106
+ /** The shared base proposal types `domainActions` excludes. Defaults to
107
+ * every type some capability names explicitly via `proposalTypes` — i.e.
108
+ * "domain actions" are the taxonomy types no capability claims. */
109
+ baseProposalTypes?: readonly string[];
110
+ }
111
+ interface ResolvedToolCapabilities {
112
+ /** Proposal types to keep — feed to {@link restrictTaxonomy}. */
113
+ proposalTypes: string[];
114
+ /** Product tool groups to expose (deduped union across enabled caps). */
115
+ toolGroups: string[];
116
+ }
117
+ /**
118
+ * Resolve an enabled capability-id set against a taxonomy into the concrete
119
+ * tool surface. Fail-closed: only types present in the taxonomy survive, and
120
+ * an empty `enabled` set yields no tools at all.
121
+ */
122
+ declare function resolveToolCapabilities(opts: ResolveToolCapabilitiesOptions): ResolvedToolCapabilities;
123
+ /**
124
+ * Restrict a taxonomy to a subset of proposal types, intersecting the
125
+ * regulated subset too — the regulated label survives restriction, so a
126
+ * narrowed agent can never launder a regulated type into an unregulated one.
127
+ */
128
+ declare function restrictTaxonomy(taxonomy: AppToolTaxonomy, allowed: readonly string[]): AppToolTaxonomy;
129
+
68
130
  interface DispatchOptions {
69
131
  handlers: AppToolHandlers;
70
132
  taxonomy: AppToolTaxonomy;
@@ -134,4 +196,4 @@ interface HandleToolRequestOptions extends DispatchOptions {
134
196
  */
135
197
  declare function handleAppToolRequest(request: Request, opts: HandleToolRequestOptions): Promise<Response>;
136
198
 
137
- export { AppToolContext, AppToolHandlers, AppToolName, AppToolOutcome, AppToolProducedEvent, type AppToolRuntimeExecutor, AppToolTaxonomy, type CapabilityTokenOptions, type DispatchOptions, type ExpiringCapabilityTokenOptions, type HandleToolRequestOptions, type RuntimeExecutorOptions, ToolHeaderNames, ToolInputError, createAppToolRuntimeExecutor, createCapabilityToken, createExpiringCapabilityToken, dispatchAppTool, handleAppToolRequest, outcomeStatus, verifyCapabilityToken, verifyExpiringCapabilityToken };
199
+ export { AppToolContext, AppToolHandlers, AppToolName, AppToolOutcome, AppToolProducedEvent, type AppToolRuntimeExecutor, AppToolTaxonomy, type CapabilityTokenOptions, type DispatchOptions, type ExpiringCapabilityTokenOptions, type HandleToolRequestOptions, type ResolveToolCapabilitiesOptions, type ResolvedToolCapabilities, type RuntimeExecutorOptions, type ToolCapability, ToolHeaderNames, ToolInputError, createAppToolRuntimeExecutor, createCapabilityToken, createExpiringCapabilityToken, dispatchAppTool, handleAppToolRequest, outcomeStatus, resolveToolCapabilities, restrictTaxonomy, verifyCapabilityToken, verifyExpiringCapabilityToken };
@@ -2,17 +2,21 @@ import {
2
2
  createCapabilityToken,
3
3
  createExpiringCapabilityToken,
4
4
  handleAppToolRequest,
5
+ resolveToolCapabilities,
6
+ restrictTaxonomy,
5
7
  verifyCapabilityToken,
6
8
  verifyExpiringCapabilityToken
7
- } from "../chunk-CF5DZELC.js";
9
+ } from "../chunk-SSX2A6XX.js";
8
10
  import {
9
11
  DEFAULT_APP_TOOL_PATHS,
10
12
  DEFAULT_HEADER_NAMES,
13
+ MCP_PROTOCOL_VERSIONS,
11
14
  authenticateToolRequest,
12
15
  buildAppToolMcpServer,
13
16
  buildHttpMcpServer,
17
+ createMcpToolHandler,
14
18
  readToolArgs
15
- } from "../chunk-IJZJWKUK.js";
19
+ } from "../chunk-A76ZHWNF.js";
16
20
  import {
17
21
  APP_TOOL_NAMES,
18
22
  ToolInputError,
@@ -26,6 +30,7 @@ export {
26
30
  APP_TOOL_NAMES,
27
31
  DEFAULT_APP_TOOL_PATHS,
28
32
  DEFAULT_HEADER_NAMES,
33
+ MCP_PROTOCOL_VERSIONS,
29
34
  ToolInputError,
30
35
  authenticateToolRequest,
31
36
  buildAppToolMcpServer,
@@ -34,11 +39,14 @@ export {
34
39
  createAppToolRuntimeExecutor,
35
40
  createCapabilityToken,
36
41
  createExpiringCapabilityToken,
42
+ createMcpToolHandler,
37
43
  dispatchAppTool,
38
44
  handleAppToolRequest,
39
45
  isAppToolName,
40
46
  outcomeStatus,
41
47
  readToolArgs,
48
+ resolveToolCapabilities,
49
+ restrictTaxonomy,
42
50
  verifyCapabilityToken,
43
51
  verifyExpiringCapabilityToken
44
52
  };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "packageManager": "pnpm@10.33.4",
5
- "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto composed over the Tangle agent substrate through typed seams.",
5
+ "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent\u2192app tool side channel, integration-hub client, per-workspace billing, and crypto \u2014 composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
7
7
  "tangle",
8
8
  "ai-agent",
@@ -166,6 +166,21 @@
166
166
  "types": "./dist/sequences-react/index.d.ts",
167
167
  "import": "./dist/sequences-react/index.js",
168
168
  "default": "./dist/sequences-react/index.js"
169
+ },
170
+ "./design-canvas": {
171
+ "types": "./dist/design-canvas/index.d.ts",
172
+ "import": "./dist/design-canvas/index.js",
173
+ "default": "./dist/design-canvas/index.js"
174
+ },
175
+ "./design-canvas/drizzle": {
176
+ "types": "./dist/design-canvas/drizzle.d.ts",
177
+ "import": "./dist/design-canvas/drizzle.js",
178
+ "default": "./dist/design-canvas/drizzle.js"
179
+ },
180
+ "./design-canvas-react": {
181
+ "types": "./dist/design-canvas-react/index.d.ts",
182
+ "import": "./dist/design-canvas-react/index.js",
183
+ "default": "./dist/design-canvas-react/index.js"
169
184
  }
170
185
  },
171
186
  "scripts": {
@@ -189,8 +204,10 @@
189
204
  "better-sqlite3": "^12.10.0",
190
205
  "drizzle-orm": "^0.45.2",
191
206
  "jsdom": "^29.1.1",
207
+ "konva": "^10.3.0",
192
208
  "react": "^19.0.0",
193
209
  "react-dom": "^19.2.7",
210
+ "react-konva": "^19.2.5",
194
211
  "tsup": "^8.0.0",
195
212
  "typescript": "^5.7.0",
196
213
  "vitest": "^3.0.0"
@@ -202,7 +219,9 @@
202
219
  "@tangle-network/agent-knowledge": ">=1.5.0",
203
220
  "@tangle-network/agent-runtime": ">=0.21.0",
204
221
  "drizzle-orm": ">=0.36",
205
- "react": ">=18"
222
+ "react": ">=18",
223
+ "konva": ">=9",
224
+ "react-konva": ">=18"
206
225
  },
207
226
  "peerDependenciesMeta": {
208
227
  "@huggingface/transformers": {
@@ -219,6 +238,12 @@
219
238
  },
220
239
  "react": {
221
240
  "optional": true
241
+ },
242
+ "konva": {
243
+ "optional": true
244
+ },
245
+ "react-konva": {
246
+ "optional": true
222
247
  }
223
248
  },
224
249
  "dependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/capability.ts","../src/tools/http.ts"],"sourcesContent":["/**\n * Per-user capability token — the sandbox→app auth primitive behind the\n * `verifyToken` seam in {@link authenticateToolRequest}.\n *\n * An app-agent runs inside the sandbox and reaches the host app back over HTTP\n * (the app tools, the integration-invoke bridge). The route must act AS the\n * connecting user without trusting any model-supplied identity, so the turn\n * mints a short HMAC token bound to the user id and bakes it into the per-turn\n * MCP server header; the route verifies it to recover the user.\n *\n * `HMAC-SHA256(secret, \"user:<userId>\")`, base64url, with an app-chosen prefix.\n * The token encodes no scopes — the hub's policy engine authorizes per action.\n * Fail-closed: with no secret, no token is minted (the caller MUST omit the MCP\n * server rather than fake an authorized call). WebCrypto only — runs on\n * Workers, Node, and the browser with no Node `crypto` dependency.\n */\n\nexport interface CapabilityTokenOptions {\n /** Shared HMAC secret. When absent, mint returns undefined / verify returns false. */\n secret?: string\n /** Token prefix (namespaces the credential; lets verify reject foreign tokens\n * cheaply). Default `cap_`. */\n prefix?: string\n}\n\n/** Mint a capability token for `userId`, or `undefined` when no secret is\n * configured (fail-closed — the caller omits the MCP server rather than fake it). */\nexport async function createCapabilityToken(userId: string, opts: CapabilityTokenOptions): Promise<string | undefined> {\n const secret = opts.secret?.trim()\n if (!secret) return undefined\n const prefix = opts.prefix ?? 'cap_'\n return `${prefix}${await sign(userId, secret)}`\n}\n\n/** Verify a capability token against `userId`. Returns false (never throws) for\n * an unconfigured secret, a wrong prefix, a malformed token, or a mismatch. */\nexport async function verifyCapabilityToken(userId: string, token: string, opts: CapabilityTokenOptions): Promise<boolean> {\n const secret = opts.secret?.trim()\n const prefix = opts.prefix ?? 'cap_'\n if (!secret || !token.startsWith(prefix)) return false\n const expected = `${prefix}${await sign(userId, secret)}`\n return timingSafeEqual(token, expected)\n}\n\nexport interface ExpiringCapabilityTokenOptions extends CapabilityTokenOptions {\n /** Token lifetime. Expired tokens verify false regardless of signature. */\n expiresInMs: number\n /** Clock injection for tests; defaults to Date.now. */\n now?: () => number\n}\n\n/**\n * Mint an EXPIRING capability token: `<prefix><base64url(payload)>.<sig>` where\n * the payload carries `{ sub, exp, n }` (subject, epoch-ms expiry, random\n * nonce) and the signature is HMAC-SHA256 over the encoded payload. Use this\n * for user-initiated scoped channels (e.g. a per-sequence MCP endpoint) where\n * a captured token must not stay valid past its window; the bare\n * {@link createCapabilityToken} remains for turn-scoped tool bridges whose\n * mint+verify happen inside one request cycle. Fail-closed like the bare\n * variant: no secret → no token.\n */\nexport async function createExpiringCapabilityToken(subject: string, opts: ExpiringCapabilityTokenOptions): Promise<string | undefined> {\n const secret = opts.secret?.trim()\n if (!secret) return undefined\n if (!Number.isFinite(opts.expiresInMs) || opts.expiresInMs <= 0) throw new Error('expiresInMs must be a positive number')\n const prefix = opts.prefix ?? 'cap_'\n const now = opts.now ?? Date.now\n const payload = base64urlText(JSON.stringify({ sub: subject, exp: now() + opts.expiresInMs, n: crypto.randomUUID() }))\n return `${prefix}${payload}.${await signText(payload, secret)}`\n}\n\n/** Verify an expiring token against `subject`: prefix, payload integrity,\n * subject match, and expiry all checked; returns false (never throws) on any\n * failure including a malformed payload. */\nexport async function verifyExpiringCapabilityToken(subject: string, token: string, opts: CapabilityTokenOptions & { now?: () => number }): Promise<boolean> {\n const secret = opts.secret?.trim()\n const prefix = opts.prefix ?? 'cap_'\n if (!secret || !token.startsWith(prefix)) return false\n const body = token.slice(prefix.length)\n const dot = body.lastIndexOf('.')\n if (dot <= 0 || dot === body.length - 1) return false\n const payload = body.slice(0, dot)\n const sig = body.slice(dot + 1)\n if (!timingSafeEqual(sig, await signText(payload, secret))) return false\n let parsed: { sub?: unknown; exp?: unknown }\n try {\n parsed = JSON.parse(textFromBase64url(payload)) as { sub?: unknown; exp?: unknown }\n } catch {\n return false\n }\n if (parsed.sub !== subject) return false\n if (typeof parsed.exp !== 'number') return false\n const now = opts.now ?? Date.now\n return parsed.exp > now()\n}\n\nasync function sign(userId: string, secret: string): Promise<string> {\n return signText(`user:${userId}`, secret)\n}\n\nasync function signText(message: string, secret: string): Promise<string> {\n const enc = new TextEncoder()\n const key = await crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])\n const sig = await crypto.subtle.sign('HMAC', key, enc.encode(message))\n return base64url(new Uint8Array(sig))\n}\n\nfunction base64urlText(text: string): string {\n return base64url(new TextEncoder().encode(text))\n}\n\nfunction textFromBase64url(value: string): string {\n const b64 = value.replace(/-/g, '+').replace(/_/g, '/')\n const bin = atob(b64)\n const bytes = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)\n return new TextDecoder().decode(bytes)\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let s = ''\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]!)\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/** Length-independent-leak-free compare for two same-charset strings. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n","import { authenticateToolRequest, type ToolHeaderNames } from './auth'\nimport { dispatchAppTool, outcomeStatus, type DispatchOptions } from './dispatch'\nimport type { AppToolName } from './openai'\n\nexport interface HandleToolRequestOptions extends DispatchOptions {\n /** Which app tool this route serves. */\n tool: AppToolName\n /** Verify the bearer capability token belongs to the header user. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n /** Optional success-message builder for a friendlier tool result. */\n message?: (result: unknown) => string\n}\n\n/**\n * Handle one app-tool HTTP request end to end — the sandbox MCP path. The\n * agent's per-turn HTTP MCP server POSTs here; this authenticates (header user\n * + capability token), reads the args (MCP-alias tolerant), dispatches to the\n * product handler, and returns a JSON Response. A product's route file becomes\n * a one-liner: `export const action = ({ request }) => handleAppToolRequest(request, cfg)`.\n */\nexport async function handleAppToolRequest(request: Request, opts: HandleToolRequestOptions): Promise<Response> {\n if (request.method !== 'POST') return Response.json({ error: 'Method not allowed' }, { status: 405 })\n\n const auth = await authenticateToolRequest(request, { verifyToken: opts.verifyToken, headerNames: opts.headerNames })\n if (!auth.ok) return auth.response\n\n let body: { args?: Record<string, unknown>; arguments?: Record<string, unknown> } & Record<string, unknown>\n try {\n body = (await request.json()) as typeof body\n } catch {\n return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n const args = (body.args ?? body.arguments ?? body) as Record<string, unknown>\n\n const outcome = await dispatchAppTool(opts.tool, args, auth.ctx, opts)\n if (!outcome.ok) {\n return Response.json({ error: outcome.code, message: outcome.message }, { status: outcomeStatus(outcome) })\n }\n const payload = outcome.result as Record<string, unknown>\n return Response.json({ ok: true, ...payload, ...(opts.message ? { message: opts.message(outcome.result) } : {}) })\n}\n"],"mappings":"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","names":[]}
@@ -1,77 +0,0 @@
1
- // src/tools/auth.ts
2
- var DEFAULT_HEADER_NAMES = {
3
- userId: "X-Agent-App-User-Id",
4
- workspaceId: "X-Agent-App-Workspace-Id",
5
- threadId: "X-Agent-App-Thread-Id"
6
- };
7
- async function authenticateToolRequest(request, opts) {
8
- const h = opts.headerNames ?? DEFAULT_HEADER_NAMES;
9
- const userId = request.headers.get(h.userId)?.trim();
10
- const workspaceId = request.headers.get(h.workspaceId)?.trim();
11
- const threadId = request.headers.get(h.threadId)?.trim() || null;
12
- const bearer = request.headers.get("authorization")?.match(/^Bearer\s+(.+)$/i)?.[1];
13
- if (!userId || !bearer) {
14
- return { ok: false, response: Response.json({ error: "Missing capability credentials" }, { status: 401 }) };
15
- }
16
- if (!await opts.verifyToken(userId, bearer)) {
17
- return { ok: false, response: Response.json({ error: "Invalid capability token" }, { status: 401 }) };
18
- }
19
- if (!workspaceId) {
20
- return { ok: false, response: Response.json({ error: "Missing workspace context" }, { status: 400 }) };
21
- }
22
- return { ok: true, ctx: { userId, workspaceId, threadId } };
23
- }
24
- async function readToolArgs(request) {
25
- let body;
26
- try {
27
- body = await request.json();
28
- } catch {
29
- return null;
30
- }
31
- return body.args ?? body.arguments ?? body;
32
- }
33
-
34
- // src/tools/mcp.ts
35
- var DEFAULT_APP_TOOL_PATHS = {
36
- submit_proposal: "/api/tools/propose",
37
- schedule_followup: "/api/tools/followup",
38
- render_ui: "/api/tools/render-ui",
39
- add_citation: "/api/tools/citation"
40
- };
41
- function buildHttpMcpServer(opts) {
42
- const base = opts.baseUrl.replace(/\/+$/, "");
43
- const h = opts.headerNames ?? DEFAULT_HEADER_NAMES;
44
- return {
45
- transport: "http",
46
- url: `${base}${opts.path}`,
47
- headers: {
48
- Authorization: `Bearer ${opts.token}`,
49
- [h.userId]: opts.ctx.userId,
50
- ...opts.ctx.workspaceId ? { [h.workspaceId]: opts.ctx.workspaceId } : {},
51
- ...opts.ctx.threadId ? { [h.threadId]: opts.ctx.threadId } : {},
52
- "Content-Type": "application/json"
53
- },
54
- enabled: true,
55
- metadata: { description: opts.description }
56
- };
57
- }
58
- function buildAppToolMcpServer(opts) {
59
- return buildHttpMcpServer({
60
- path: opts.paths?.[opts.tool] ?? DEFAULT_APP_TOOL_PATHS[opts.tool],
61
- baseUrl: opts.baseUrl,
62
- token: opts.token,
63
- ctx: opts.ctx,
64
- description: opts.description,
65
- headerNames: opts.headerNames
66
- });
67
- }
68
-
69
- export {
70
- DEFAULT_HEADER_NAMES,
71
- authenticateToolRequest,
72
- readToolArgs,
73
- DEFAULT_APP_TOOL_PATHS,
74
- buildHttpMcpServer,
75
- buildAppToolMcpServer
76
- };
77
- //# sourceMappingURL=chunk-IJZJWKUK.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/auth.ts","../src/tools/mcp.ts"],"sourcesContent":["import type { AppToolContext } from './types'\n\n/**\n * Header names carrying the server-set per-turn context + the capability token.\n * Defaults are product-neutral (`X-Agent-App-*`); a product that already ships\n * a header convention (e.g. `X-Acme-User-Id`) passes its own.\n */\nexport interface ToolHeaderNames {\n userId: string\n workspaceId: string\n threadId: string\n}\n\nexport const DEFAULT_HEADER_NAMES: ToolHeaderNames = {\n userId: 'X-Agent-App-User-Id',\n workspaceId: 'X-Agent-App-Workspace-Id',\n threadId: 'X-Agent-App-Thread-Id',\n}\n\nexport interface AuthenticateOptions {\n /** Verify the bearer capability token belongs to `userId`. The product's\n * HMAC/JWT impl — the seam that keeps token crypto out of this package. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n}\n\nexport type ToolAuthResult =\n | { ok: true; ctx: AppToolContext }\n | { ok: false; response: Response }\n\n/**\n * Recover + verify the trusted context for a tool request. The user comes from\n * a server-set header and the bearer token MUST verify against THAT user; the\n * workspace comes from a header too — never from tool args — so the model can\n * neither forge identity nor target another workspace. Fail-closed: any missing\n * credential or a token minted for another user yields a 401/400 Response.\n */\nexport async function authenticateToolRequest(request: Request, opts: AuthenticateOptions): Promise<ToolAuthResult> {\n const h = opts.headerNames ?? DEFAULT_HEADER_NAMES\n const userId = request.headers.get(h.userId)?.trim()\n const workspaceId = request.headers.get(h.workspaceId)?.trim()\n const threadId = request.headers.get(h.threadId)?.trim() || null\n const bearer = request.headers.get('authorization')?.match(/^Bearer\\s+(.+)$/i)?.[1]\n\n if (!userId || !bearer) {\n return { ok: false, response: Response.json({ error: 'Missing capability credentials' }, { status: 401 }) }\n }\n if (!(await opts.verifyToken(userId, bearer))) {\n return { ok: false, response: Response.json({ error: 'Invalid capability token' }, { status: 401 }) }\n }\n if (!workspaceId) {\n return { ok: false, response: Response.json({ error: 'Missing workspace context' }, { status: 400 }) }\n }\n return { ok: true, ctx: { userId, workspaceId, threadId } }\n}\n\n/** Read a tool's argument object from the request body, tolerant of MCP host\n * aliases (`args` / `arguments`) or a bare body. Returns null on non-JSON. */\nexport async function readToolArgs<T>(request: Request): Promise<T | null> {\n let body: { args?: T; arguments?: T }\n try {\n body = (await request.json()) as typeof body\n } catch {\n return null\n }\n return (body.args ?? body.arguments ?? (body as T)) as T\n}\n","import type { AppToolContext } from './types'\nimport type { AppToolName } from './openai'\nimport type { ToolHeaderNames } from './auth'\nimport { DEFAULT_HEADER_NAMES } from './auth'\n\n/** Default route path each app tool is served at. A product mounts its routes\n * at these paths (or supplies its own via {@link BuildMcpServerOptions.paths}). */\nexport const DEFAULT_APP_TOOL_PATHS: Record<AppToolName, string> = {\n submit_proposal: '/api/tools/propose',\n schedule_followup: '/api/tools/followup',\n render_ui: '/api/tools/render-ui',\n add_citation: '/api/tools/citation',\n}\n\n/** The portable MCP server entry the sandbox SDK accepts (transport + url +\n * headers). Matches `AgentProfileMcpServer` structurally without importing the\n * sandbox SDK — products spread it into their profile's `mcp` map. */\nexport interface AppToolMcpServer {\n transport: 'http'\n url: string\n headers: Record<string, string>\n enabled: true\n metadata: { description: string }\n}\n\nexport interface BuildHttpMcpServerOptions {\n /** Route path on the app the sandbox POSTs to (e.g. `/api/tools/propose`). */\n path: string\n /** App base URL the sandbox reaches back to (no trailing slash required). */\n baseUrl: string\n /** Per-user capability token, baked into the Authorization header. */\n token: string\n ctx: AppToolContext\n /** Tool description the model sees. */\n description: string\n headerNames?: ToolHeaderNames\n}\n\n/**\n * Build ONE HTTP MCP server entry — the generic agent→app bridge. The\n * capability token + the user/workspace/thread ids ride in server-set headers\n * (never tool args), so the model can't forge identity or target another\n * workspace. Workspace/thread headers are omitted when their `ctx` value is\n * empty/null (e.g. an integration-invoke bridge that's user-scoped only). Used\n * directly for non-app-tool bridges (integration_invoke) and via\n * {@link buildAppToolMcpServer} for the four app tools.\n */\nexport function buildHttpMcpServer(opts: BuildHttpMcpServerOptions): AppToolMcpServer {\n const base = opts.baseUrl.replace(/\\/+$/, '')\n const h = opts.headerNames ?? DEFAULT_HEADER_NAMES\n return {\n transport: 'http',\n url: `${base}${opts.path}`,\n headers: {\n Authorization: `Bearer ${opts.token}`,\n [h.userId]: opts.ctx.userId,\n ...(opts.ctx.workspaceId ? { [h.workspaceId]: opts.ctx.workspaceId } : {}),\n ...(opts.ctx.threadId ? { [h.threadId]: opts.ctx.threadId } : {}),\n 'Content-Type': 'application/json',\n },\n enabled: true,\n metadata: { description: opts.description },\n }\n}\n\nexport interface BuildMcpServerOptions {\n tool: AppToolName\n baseUrl: string\n token: string\n ctx: AppToolContext\n description: string\n headerNames?: ToolHeaderNames\n paths?: Partial<Record<AppToolName, string>>\n}\n\n/** Build one of the four app-tool MCP servers — a thin wrapper over\n * {@link buildHttpMcpServer} that maps the tool name to its route path. */\nexport function buildAppToolMcpServer(opts: BuildMcpServerOptions): AppToolMcpServer {\n return buildHttpMcpServer({\n path: opts.paths?.[opts.tool] ?? DEFAULT_APP_TOOL_PATHS[opts.tool],\n baseUrl: opts.baseUrl,\n token: opts.token,\n ctx: opts.ctx,\n description: opts.description,\n headerNames: opts.headerNames,\n })\n}\n"],"mappings":";AAaO,IAAM,uBAAwC;AAAA,EACnD,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,UAAU;AACZ;AAoBA,eAAsB,wBAAwB,SAAkB,MAAoD;AAClH,QAAM,IAAI,KAAK,eAAe;AAC9B,QAAM,SAAS,QAAQ,QAAQ,IAAI,EAAE,MAAM,GAAG,KAAK;AACnD,QAAM,cAAc,QAAQ,QAAQ,IAAI,EAAE,WAAW,GAAG,KAAK;AAC7D,QAAM,WAAW,QAAQ,QAAQ,IAAI,EAAE,QAAQ,GAAG,KAAK,KAAK;AAC5D,QAAM,SAAS,QAAQ,QAAQ,IAAI,eAAe,GAAG,MAAM,kBAAkB,IAAI,CAAC;AAElF,MAAI,CAAC,UAAU,CAAC,QAAQ;AACtB,WAAO,EAAE,IAAI,OAAO,UAAU,SAAS,KAAK,EAAE,OAAO,iCAAiC,GAAG,EAAE,QAAQ,IAAI,CAAC,EAAE;AAAA,EAC5G;AACA,MAAI,CAAE,MAAM,KAAK,YAAY,QAAQ,MAAM,GAAI;AAC7C,WAAO,EAAE,IAAI,OAAO,UAAU,SAAS,KAAK,EAAE,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC,EAAE;AAAA,EACtG;AACA,MAAI,CAAC,aAAa;AAChB,WAAO,EAAE,IAAI,OAAO,UAAU,SAAS,KAAK,EAAE,OAAO,4BAA4B,GAAG,EAAE,QAAQ,IAAI,CAAC,EAAE;AAAA,EACvG;AACA,SAAO,EAAE,IAAI,MAAM,KAAK,EAAE,QAAQ,aAAa,SAAS,EAAE;AAC5D;AAIA,eAAsB,aAAgB,SAAqC;AACzE,MAAI;AACJ,MAAI;AACF,WAAQ,MAAM,QAAQ,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAQ,KAAK,QAAQ,KAAK,aAAc;AAC1C;;;AC3DO,IAAM,yBAAsD;AAAA,EACjE,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,WAAW;AAAA,EACX,cAAc;AAChB;AAmCO,SAAS,mBAAmB,MAAmD;AACpF,QAAM,OAAO,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AAC5C,QAAM,IAAI,KAAK,eAAe;AAC9B,SAAO;AAAA,IACL,WAAW;AAAA,IACX,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI;AAAA,IACxB,SAAS;AAAA,MACP,eAAe,UAAU,KAAK,KAAK;AAAA,MACnC,CAAC,EAAE,MAAM,GAAG,KAAK,IAAI;AAAA,MACrB,GAAI,KAAK,IAAI,cAAc,EAAE,CAAC,EAAE,WAAW,GAAG,KAAK,IAAI,YAAY,IAAI,CAAC;AAAA,MACxE,GAAI,KAAK,IAAI,WAAW,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,IAAI,SAAS,IAAI,CAAC;AAAA,MAC/D,gBAAgB;AAAA,IAClB;AAAA,IACA,SAAS;AAAA,IACT,UAAU,EAAE,aAAa,KAAK,YAAY;AAAA,EAC5C;AACF;AAcO,SAAS,sBAAsB,MAA+C;AACnF,SAAO,mBAAmB;AAAA,IACxB,MAAM,KAAK,QAAQ,KAAK,IAAI,KAAK,uBAAuB,KAAK,IAAI;AAAA,IACjE,SAAS,KAAK;AAAA,IACd,OAAO,KAAK;AAAA,IACZ,KAAK,KAAK;AAAA,IACV,aAAa,KAAK;AAAA,IAClB,aAAa,KAAK;AAAA,EACpB,CAAC;AACH;","names":[]}