@tangle-network/agent-app 0.60.197 → 0.60.199

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 (45) hide show
  1. package/dist/chat-routes/index.js +6 -4
  2. package/dist/chat-routes/index.js.map +1 -1
  3. package/dist/chat-routes/turn-routes.d.ts +28 -0
  4. package/dist/{chunk-XJEIGVMQ.js → chunk-2OWT3TNV.js} +2 -2
  5. package/dist/{chunk-XP77DJEF.js → chunk-6XXKFYIM.js} +72 -30
  6. package/dist/chunk-6XXKFYIM.js.map +1 -0
  7. package/dist/chunk-7XQIY2LU.js +151 -0
  8. package/dist/chunk-7XQIY2LU.js.map +1 -0
  9. package/dist/chunk-BKPJFES3.js +229 -0
  10. package/dist/chunk-BKPJFES3.js.map +1 -0
  11. package/dist/{chunk-7WEPGGOW.js → chunk-EGBNNTAL.js} +2 -2
  12. package/dist/{chunk-7WEPGGOW.js.map → chunk-EGBNNTAL.js.map} +1 -1
  13. package/dist/{chunk-SUIH5B55.js → chunk-RJJRRR5Z.js} +184 -16
  14. package/dist/chunk-RJJRRR5Z.js.map +1 -0
  15. package/dist/{chunk-FPOOBGRG.js → chunk-T4BLISYH.js} +40 -5
  16. package/dist/chunk-T4BLISYH.js.map +1 -0
  17. package/dist/public-consultation/index.d.ts +1 -1
  18. package/dist/public-consultation/index.js +2 -1
  19. package/dist/public-consultation/index.js.map +1 -1
  20. package/dist/runtime/index.js +4 -2
  21. package/dist/runtime/index.js.map +1 -1
  22. package/dist/runtime/router-turn.d.ts +14 -1
  23. package/dist/runtime/router-turn.js +3 -1
  24. package/dist/sandbox/index.d.ts +19 -0
  25. package/dist/sandbox/index.js +2 -1
  26. package/dist/sandbox/prompt-trace.d.ts +20 -0
  27. package/dist/sandbox/sandbox-run-tool.d.ts +8 -0
  28. package/dist/trace/index.d.ts +0 -1
  29. package/dist/trace/index.js +0 -240
  30. package/dist/trace/index.js.map +1 -1
  31. package/dist/turn-trace/index.d.ts +11 -0
  32. package/dist/turn-trace/index.js +121 -0
  33. package/dist/turn-trace/index.js.map +1 -0
  34. package/dist/turn-trace/otel.d.ts +36 -0
  35. package/dist/turn-trace/recorder.d.ts +122 -0
  36. package/dist/turn-trace/server-timing.d.ts +15 -0
  37. package/dist/turn-trace/traced-fetch.d.ts +31 -0
  38. package/dist/workspace-apps/index.js +1 -1
  39. package/dist/workspace-apps/react.js +1 -1
  40. package/package.json +6 -1
  41. package/dist/chunk-FPOOBGRG.js.map +0 -1
  42. package/dist/chunk-SUIH5B55.js.map +0 -1
  43. package/dist/chunk-XP77DJEF.js.map +0 -1
  44. package/dist/trace/stage-timing.d.ts +0 -61
  45. /package/dist/{chunk-XJEIGVMQ.js.map → chunk-2OWT3TNV.js.map} +0 -0
@@ -0,0 +1,229 @@
1
+ // src/turn-trace/recorder.ts
2
+ var TURN_SPAN_KINDS = ["admission", "router", "model", "network", "tool", "sandbox", "persist", "other"];
3
+ var TRACE_ID_PATTERN = /^[\w-]{8,200}$/;
4
+ var MAX_ATTRS = 48;
5
+ var MAX_ATTR_STRING = 200;
6
+ function isTraceId(value) {
7
+ return TRACE_ID_PATTERN.test(value);
8
+ }
9
+ function turnTraceId(executionId) {
10
+ if (TRACE_ID_PATTERN.test(executionId)) return executionId;
11
+ const id = executionId.replace(/[^\w-]/g, "_").slice(0, 200);
12
+ return id.length >= 8 ? id : id.padEnd(8, "_");
13
+ }
14
+ function randomHex(bytes) {
15
+ const out = new Uint8Array(bytes);
16
+ if (typeof globalThis.crypto?.getRandomValues === "function") globalThis.crypto.getRandomValues(out);
17
+ else for (let i = 0; i < bytes; i++) out[i] = Math.floor(Math.random() * 256);
18
+ return Array.from(out, (b) => b.toString(16).padStart(2, "0")).join("");
19
+ }
20
+ function boundedAttrs(attrs) {
21
+ const out = {};
22
+ let count = 0;
23
+ for (const [key, value] of Object.entries(attrs)) {
24
+ if (count >= MAX_ATTRS) break;
25
+ if (typeof value === "number" && !Number.isFinite(value)) continue;
26
+ out[key.slice(0, 80)] = typeof value === "string" ? value.slice(0, MAX_ATTR_STRING) : value;
27
+ count++;
28
+ }
29
+ return out;
30
+ }
31
+ function errorText(error) {
32
+ if (error instanceof Error) return (error.name === "Error" ? error.message : `${error.name}: ${error.message}`).slice(0, MAX_ATTR_STRING);
33
+ return String(error).slice(0, MAX_ATTR_STRING);
34
+ }
35
+ var zeroKinds = () => ({
36
+ admission: 0,
37
+ router: 0,
38
+ model: 0,
39
+ network: 0,
40
+ tool: 0,
41
+ sandbox: 0,
42
+ persist: 0,
43
+ other: 0
44
+ });
45
+ var round1 = (value) => Math.round(value * 10) / 10;
46
+ function summarizeTurnSpans(spans, rootSpanId, startMs, endMs, traceId, dropped = 0) {
47
+ const wallMs = Math.max(0, endMs - startMs);
48
+ const parent = new Map(spans.map((span) => [span.spanId, span.parentSpanId]));
49
+ const depthOf = (id) => {
50
+ let depth = 0;
51
+ for (let at = parent.get(id); at !== void 0 && at !== rootSpanId; at = parent.get(at)) {
52
+ depth++;
53
+ if (depth > 64) break;
54
+ }
55
+ return depth;
56
+ };
57
+ const clipped = spans.map((span) => ({ span, start: Math.max(startMs, span.startMs), end: Math.min(endMs, span.endMs), depth: depthOf(span.spanId) })).filter((entry) => entry.end > entry.start);
58
+ const points = [.../* @__PURE__ */ new Set([startMs, endMs, ...clipped.flatMap((entry) => [entry.start, entry.end])])].sort((a, b) => a - b);
59
+ const byKind = zeroKinds();
60
+ let coveredMs = 0;
61
+ let largestGapMs = 0;
62
+ let gap = 0;
63
+ for (let i = 0; i + 1 < points.length; i++) {
64
+ const from = points[i];
65
+ const to = points[i + 1];
66
+ let deepest;
67
+ for (const entry of clipped) {
68
+ if (entry.start <= from && entry.end >= to && (!deepest || entry.depth > deepest.depth || entry.depth === deepest.depth && entry.start > deepest.start)) deepest = entry;
69
+ }
70
+ if (deepest) {
71
+ byKind[deepest.span.kind] += to - from;
72
+ coveredMs += to - from;
73
+ gap = 0;
74
+ } else {
75
+ gap += to - from;
76
+ largestGapMs = Math.max(largestGapMs, gap);
77
+ }
78
+ }
79
+ for (const kind of TURN_SPAN_KINDS) byKind[kind] = round1(byKind[kind]);
80
+ return {
81
+ traceId,
82
+ wallMs: round1(wallMs),
83
+ coveredMs: round1(coveredMs),
84
+ coveragePct: wallMs > 0 ? round1(coveredMs / wallMs * 100) : 0,
85
+ uncoveredMs: round1(wallMs - coveredMs),
86
+ largestGapMs: round1(largestGapMs),
87
+ byKind,
88
+ spans: spans.length,
89
+ modelCalls: spans.filter((span) => span.name === "model.call").length,
90
+ toolCalls: spans.filter((span) => span.name === "tool.call").length,
91
+ dropped
92
+ };
93
+ }
94
+ function createTurnTrace(options = {}) {
95
+ const now = options.now ?? Date.now;
96
+ const startedAt = options.startedAt ?? now();
97
+ const maxSpans = options.maxSpans ?? 1e3;
98
+ const rootId = randomHex(8);
99
+ let executionId = options.executionId ?? `pending_${randomHex(6)}`;
100
+ let traceId = turnTraceId(executionId);
101
+ const records = /* @__PURE__ */ new Map();
102
+ const open = /* @__PURE__ */ new Set();
103
+ let dropped = 0;
104
+ let finished;
105
+ const inert = (id) => {
106
+ const handle = {
107
+ id,
108
+ set: () => handle,
109
+ end: () => {
110
+ },
111
+ child: () => inert(rootId),
112
+ record: () => inert(rootId)
113
+ };
114
+ return handle;
115
+ };
116
+ const handleFor = (id) => {
117
+ const handle = {
118
+ id,
119
+ set(attrs) {
120
+ const record = records.get(id);
121
+ if (record && !finished) record.attrs = boundedAttrs({ ...record.attrs, ...attrs });
122
+ return handle;
123
+ },
124
+ end(attrs, error) {
125
+ const record = records.get(id);
126
+ if (!record || !open.has(id) || finished) return;
127
+ open.delete(id);
128
+ record.endMs = Math.max(record.startMs, now());
129
+ if (attrs) record.attrs = boundedAttrs({ ...record.attrs, ...attrs });
130
+ if (error !== void 0) record.error = errorText(error);
131
+ },
132
+ child: (name, kind, attrs) => trace.start(name, kind, attrs, handle),
133
+ record: (name, kind, start, end, attrs) => trace.record(name, kind, start, end, attrs, handle)
134
+ };
135
+ return handle;
136
+ };
137
+ const add = (name, kind, startMs, endMs, attrs, parent) => {
138
+ if (finished) return inert(rootId);
139
+ if (records.size >= maxSpans) {
140
+ dropped++;
141
+ return inert(rootId);
142
+ }
143
+ const spanId = randomHex(8);
144
+ records.set(spanId, {
145
+ spanId,
146
+ parentSpanId: parent && parent.id !== spanId ? parent.id : rootId,
147
+ name: name.slice(0, 80),
148
+ kind,
149
+ startMs,
150
+ endMs: endMs ?? startMs,
151
+ attrs: attrs ? boundedAttrs(attrs) : {}
152
+ });
153
+ if (endMs === void 0) open.add(spanId);
154
+ return handleFor(spanId);
155
+ };
156
+ const rootHandle = {
157
+ id: rootId,
158
+ set: () => rootHandle,
159
+ end: () => {
160
+ },
161
+ child: (name, kind, attrs) => trace.start(name, kind, attrs),
162
+ record: (name, kind, start, end, attrs) => trace.record(name, kind, start, end, attrs)
163
+ };
164
+ const trace = {
165
+ get traceId() {
166
+ return traceId;
167
+ },
168
+ get executionId() {
169
+ return executionId;
170
+ },
171
+ startedAt,
172
+ root: rootHandle,
173
+ bind(id) {
174
+ executionId = id;
175
+ traceId = turnTraceId(id);
176
+ },
177
+ start: (name, kind, attrs, parent) => add(name, kind, now(), void 0, attrs, parent),
178
+ record: (name, kind, startMs, endMs, attrs, parent) => add(name, kind, startMs, Math.max(startMs, endMs), attrs, parent),
179
+ async measure(name, kind, work, attrs, parent) {
180
+ const span = trace.start(name, kind, attrs, parent);
181
+ try {
182
+ const result = await work(span);
183
+ span.end();
184
+ return result;
185
+ } catch (error) {
186
+ span.end(void 0, error);
187
+ throw error;
188
+ }
189
+ },
190
+ summary(at) {
191
+ const end = at ?? finished?.endedAt ?? now();
192
+ const spans = [...records.values()].map((record) => open.has(record.spanId) ? { ...record, endMs: end } : record);
193
+ return summarizeTurnSpans(spans, rootId, startedAt, end, traceId, dropped);
194
+ },
195
+ finish(outcome) {
196
+ if (finished) return finished;
197
+ const endedAt = Math.max(startedAt, outcome?.endedAt ?? now());
198
+ for (const id of open) {
199
+ const record = records.get(id);
200
+ record.endMs = Math.max(record.startMs, endedAt);
201
+ record.unfinished = true;
202
+ }
203
+ open.clear();
204
+ const spans = [...records.values()].sort((a, b) => a.startMs - b.startMs);
205
+ finished = {
206
+ traceId,
207
+ rootSpanId: rootId,
208
+ executionId,
209
+ startedAt,
210
+ endedAt,
211
+ spans,
212
+ summary: summarizeTurnSpans(spans, rootId, startedAt, endedAt, traceId, dropped),
213
+ status: outcome?.error === void 0 ? "ok" : "error",
214
+ ...outcome?.error === void 0 ? {} : { error: errorText(outcome.error) }
215
+ };
216
+ return finished;
217
+ }
218
+ };
219
+ return trace;
220
+ }
221
+
222
+ export {
223
+ TURN_SPAN_KINDS,
224
+ isTraceId,
225
+ turnTraceId,
226
+ summarizeTurnSpans,
227
+ createTurnTrace
228
+ };
229
+ //# sourceMappingURL=chunk-BKPJFES3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/turn-trace/recorder.ts"],"sourcesContent":["/**\n * One trace per agent turn: a root span for the turn's wall time and named\n * child spans for where that time went (admission, each model call, each tool\n * call, the box claim and prompt, persistence).\n *\n * This module only records. It has no imports, so the Router engine, the\n * sandbox helpers and the chat route share it from any runtime, a Worker\n * included. `./otel` turns a finished trace into OTLP spans for Tangle\n * Intelligence.\n *\n * The turn's trace id is its execution id in the charset every hop accepts\n * ({@link turnTraceId}), sent as `x-tangle-trace-id` on every Router and\n * Sandbox call, so a Router dispatch row and a sandbox record join the turn's\n * spans by one id.\n */\n\n/** What a span's time is spent on. Each instant of the turn is credited to the deepest open span's kind. */\nexport type TurnSpanKind =\n | 'admission'\n | 'router'\n | 'model'\n | 'network'\n | 'tool'\n | 'sandbox'\n | 'persist'\n | 'other'\n\nexport const TURN_SPAN_KINDS: readonly TurnSpanKind[] = ['admission', 'router', 'model', 'network', 'tool', 'sandbox', 'persist', 'other']\n\n/** Bounded primitive metadata. Never prompts, answers, tool arguments or credentials. */\nexport type TurnSpanAttrs = Record<string, string | number | boolean>\n\nexport interface TurnSpanRecord {\n spanId: string\n /** The root span's id for a top-level child. */\n parentSpanId: string\n name: string\n kind: TurnSpanKind\n startMs: number\n endMs: number\n attrs: TurnSpanAttrs\n /** Set when the span ended in failure; a short code or message, never content. */\n error?: string\n /** True when the turn finished while the span was still open. */\n unfinished?: boolean\n}\n\nexport interface TurnSpan {\n readonly id: string\n /** Merge attributes. A later value replaces an earlier one. */\n set(attrs: TurnSpanAttrs): TurnSpan\n /** End now. A second call changes nothing. `error` marks the span failed. */\n end(attrs?: TurnSpanAttrs, error?: unknown): void\n /** A child that starts now. */\n child(name: string, kind: TurnSpanKind, attrs?: TurnSpanAttrs): TurnSpan\n /** A child whose times are already known. */\n record(name: string, kind: TurnSpanKind, startMs: number, endMs: number, attrs?: TurnSpanAttrs): TurnSpan\n}\n\n/** How much of the turn's wall time is inside named spans, and what the time was spent on. */\nexport interface TurnTraceSummary {\n traceId: string\n wallMs: number\n /** Wall time inside at least one child span. */\n coveredMs: number\n /** `coveredMs / wallMs` as a percentage, one decimal. */\n coveragePct: number\n uncoveredMs: number\n /** The longest stretch with no span open: where an unnamed wait hides. */\n largestGapMs: number\n /** Wall time credited to the deepest open span's kind. Sums to `coveredMs`. */\n byKind: Record<TurnSpanKind, number>\n spans: number\n modelCalls: number\n toolCalls: number\n /** Spans refused after the per-turn cap. */\n dropped: number\n}\n\nexport interface TurnTraceSnapshot {\n traceId: string\n /** The id the root span is exported under. */\n rootSpanId: string\n executionId: string\n startedAt: number\n endedAt: number\n spans: TurnSpanRecord[]\n summary: TurnTraceSummary\n /** `ok`, or the failure the turn ended with. */\n status: 'ok' | 'error'\n error?: string\n}\n\n/** Where a finished turn's trace goes. `labels` are the host's metadata for the root span (session, user, workspace). */\nexport type TurnTraceSink = (snapshot: TurnTraceSnapshot, labels: TurnSpanAttrs) => void | Promise<void>\n\nexport interface TurnTrace {\n /** The turn's execution id in the charset the Router and Sandbox accept. Send it as `x-tangle-trace-id`. */\n readonly traceId: string\n readonly executionId: string\n readonly startedAt: number\n /** The span a top-level child hangs from. */\n readonly root: TurnSpan\n /** Name the turn's execution id once it is known. Spans recorded earlier keep their times. */\n bind(executionId: string): void\n start(name: string, kind: TurnSpanKind, attrs?: TurnSpanAttrs, parent?: TurnSpan): TurnSpan\n record(name: string, kind: TurnSpanKind, startMs: number, endMs: number, attrs?: TurnSpanAttrs, parent?: TurnSpan): TurnSpan\n /** Run `work` inside a span; the span ends when it settles, failed if it throws. */\n measure<T>(name: string, kind: TurnSpanKind, work: (span: TurnSpan) => Promise<T> | T, attrs?: TurnSpanAttrs, parent?: TurnSpan): Promise<T>\n /** Read the current numbers without ending the trace. */\n summary(now?: number): TurnTraceSummary\n /** End the trace: open spans close at `endedAt`, and later spans are refused. A second call returns the first result. */\n finish(outcome?: { error?: unknown; endedAt?: number }): TurnTraceSnapshot\n}\n\nexport interface CreateTurnTraceOptions {\n /** Epoch ms the turn began (default now). A route passes its request time so admission is inside the trace. */\n startedAt?: number\n executionId?: string\n now?: () => number\n /** Most spans one turn keeps (default 1000); the rest are counted as dropped. */\n maxSpans?: number\n}\n\nconst TRACE_ID_PATTERN = /^[\\w-]{8,200}$/\nconst MAX_ATTRS = 48\nconst MAX_ATTR_STRING = 200\n\n/** True when the Router and the Sandbox API keep this id as a `x-tangle-trace-id`. */\nexport function isTraceId(value: string): boolean {\n return TRACE_ID_PATTERN.test(value)\n}\n\n/**\n * The turn's execution id as a trace id. Execution ids carry `:` (`app:session:turn`),\n * which the Router drops from `x-tangle-trace-id`, so every character outside\n * `[A-Za-z0-9_-]` becomes `_`. An id already in the charset is returned as it is.\n */\nexport function turnTraceId(executionId: string): string {\n if (TRACE_ID_PATTERN.test(executionId)) return executionId\n const id = executionId.replace(/[^\\w-]/g, '_').slice(0, 200)\n return id.length >= 8 ? id : id.padEnd(8, '_')\n}\n\nfunction randomHex(bytes: number): string {\n const out = new Uint8Array(bytes)\n if (typeof globalThis.crypto?.getRandomValues === 'function') globalThis.crypto.getRandomValues(out)\n else for (let i = 0; i < bytes; i++) out[i] = Math.floor(Math.random() * 256)\n return Array.from(out, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\nfunction boundedAttrs(attrs: TurnSpanAttrs): TurnSpanAttrs {\n const out: TurnSpanAttrs = {}\n let count = 0\n for (const [key, value] of Object.entries(attrs)) {\n if (count >= MAX_ATTRS) break\n if (typeof value === 'number' && !Number.isFinite(value)) continue\n out[key.slice(0, 80)] = typeof value === 'string' ? value.slice(0, MAX_ATTR_STRING) : value\n count++\n }\n return out\n}\n\nfunction errorText(error: unknown): string {\n if (error instanceof Error) return (error.name === 'Error' ? error.message : `${error.name}: ${error.message}`).slice(0, MAX_ATTR_STRING)\n return String(error).slice(0, MAX_ATTR_STRING)\n}\n\nconst zeroKinds = (): Record<TurnSpanKind, number> => ({\n admission: 0, router: 0, model: 0, network: 0, tool: 0, sandbox: 0, persist: 0, other: 0,\n})\n\nconst round1 = (value: number) => Math.round(value * 10) / 10\n\n/**\n * Coverage and the per-kind split for a set of spans over `[startMs, endMs]`.\n * Exported for a reader that holds the spans of a finished trace.\n */\nexport function summarizeTurnSpans(\n spans: readonly Pick<TurnSpanRecord, 'spanId' | 'parentSpanId' | 'kind' | 'name' | 'startMs' | 'endMs'>[],\n rootSpanId: string,\n startMs: number,\n endMs: number,\n traceId: string,\n dropped = 0,\n): TurnTraceSummary {\n const wallMs = Math.max(0, endMs - startMs)\n const parent = new Map(spans.map((span) => [span.spanId, span.parentSpanId]))\n const depthOf = (id: string): number => {\n let depth = 0\n for (let at = parent.get(id); at !== undefined && at !== rootSpanId; at = parent.get(at)) {\n depth++\n if (depth > 64) break\n }\n return depth\n }\n const clipped = spans\n .map((span) => ({ span, start: Math.max(startMs, span.startMs), end: Math.min(endMs, span.endMs), depth: depthOf(span.spanId) }))\n .filter((entry) => entry.end > entry.start)\n const points = [...new Set([startMs, endMs, ...clipped.flatMap((entry) => [entry.start, entry.end])])].sort((a, b) => a - b)\n const byKind = zeroKinds()\n let coveredMs = 0\n let largestGapMs = 0\n let gap = 0\n for (let i = 0; i + 1 < points.length; i++) {\n const from = points[i]!\n const to = points[i + 1]!\n let deepest: (typeof clipped)[number] | undefined\n for (const entry of clipped) {\n if (entry.start <= from && entry.end >= to && (!deepest || entry.depth > deepest.depth || (entry.depth === deepest.depth && entry.start > deepest.start))) deepest = entry\n }\n if (deepest) {\n byKind[deepest.span.kind] += to - from\n coveredMs += to - from\n gap = 0\n } else {\n gap += to - from\n largestGapMs = Math.max(largestGapMs, gap)\n }\n }\n for (const kind of TURN_SPAN_KINDS) byKind[kind] = round1(byKind[kind])\n return {\n traceId,\n wallMs: round1(wallMs),\n coveredMs: round1(coveredMs),\n coveragePct: wallMs > 0 ? round1((coveredMs / wallMs) * 100) : 0,\n uncoveredMs: round1(wallMs - coveredMs),\n largestGapMs: round1(largestGapMs),\n byKind,\n spans: spans.length,\n modelCalls: spans.filter((span) => span.name === 'model.call').length,\n toolCalls: spans.filter((span) => span.name === 'tool.call').length,\n dropped,\n }\n}\n\nexport function createTurnTrace(options: CreateTurnTraceOptions = {}): TurnTrace {\n const now = options.now ?? Date.now\n const startedAt = options.startedAt ?? now()\n const maxSpans = options.maxSpans ?? 1000\n const rootId = randomHex(8)\n let executionId = options.executionId ?? `pending_${randomHex(6)}`\n let traceId = turnTraceId(executionId)\n const records = new Map<string, TurnSpanRecord>()\n const open = new Set<string>()\n let dropped = 0\n let finished: TurnTraceSnapshot | undefined\n\n // A refused or late span still returns a handle, so instrumentation never branches on the trace's state.\n const inert = (id: string): TurnSpan => {\n const handle: TurnSpan = {\n id,\n set: () => handle,\n end: () => {},\n child: () => inert(rootId),\n record: () => inert(rootId),\n }\n return handle\n }\n\n const handleFor = (id: string): TurnSpan => {\n const handle: TurnSpan = {\n id,\n set(attrs) {\n const record = records.get(id)\n if (record && !finished) record.attrs = boundedAttrs({ ...record.attrs, ...attrs })\n return handle\n },\n end(attrs, error) {\n const record = records.get(id)\n if (!record || !open.has(id) || finished) return\n open.delete(id)\n record.endMs = Math.max(record.startMs, now())\n if (attrs) record.attrs = boundedAttrs({ ...record.attrs, ...attrs })\n if (error !== undefined) record.error = errorText(error)\n },\n child: (name, kind, attrs) => trace.start(name, kind, attrs, handle),\n record: (name, kind, start, end, attrs) => trace.record(name, kind, start, end, attrs, handle),\n }\n return handle\n }\n\n const add = (name: string, kind: TurnSpanKind, startMs: number, endMs: number | undefined, attrs: TurnSpanAttrs | undefined, parent: TurnSpan | undefined): TurnSpan => {\n if (finished) return inert(rootId)\n if (records.size >= maxSpans) {\n dropped++\n return inert(rootId)\n }\n const spanId = randomHex(8)\n records.set(spanId, {\n spanId,\n parentSpanId: parent && parent.id !== spanId ? parent.id : rootId,\n name: name.slice(0, 80),\n kind,\n startMs,\n endMs: endMs ?? startMs,\n attrs: attrs ? boundedAttrs(attrs) : {},\n })\n if (endMs === undefined) open.add(spanId)\n return handleFor(spanId)\n }\n\n const rootHandle: TurnSpan = {\n id: rootId,\n set: () => rootHandle,\n end: () => {},\n child: (name, kind, attrs) => trace.start(name, kind, attrs),\n record: (name, kind, start, end, attrs) => trace.record(name, kind, start, end, attrs),\n }\n\n const trace: TurnTrace = {\n get traceId() { return traceId },\n get executionId() { return executionId },\n startedAt,\n root: rootHandle,\n bind(id) {\n executionId = id\n traceId = turnTraceId(id)\n },\n start: (name, kind, attrs, parent) => add(name, kind, now(), undefined, attrs, parent),\n record: (name, kind, startMs, endMs, attrs, parent) => add(name, kind, startMs, Math.max(startMs, endMs), attrs, parent),\n async measure(name, kind, work, attrs, parent) {\n const span = trace.start(name, kind, attrs, parent)\n try {\n const result = await work(span)\n span.end()\n return result\n } catch (error) {\n span.end(undefined, error)\n throw error\n }\n },\n summary(at) {\n const end = at ?? finished?.endedAt ?? now()\n const spans = [...records.values()].map((record) => open.has(record.spanId) ? { ...record, endMs: end } : record)\n return summarizeTurnSpans(spans, rootId, startedAt, end, traceId, dropped)\n },\n finish(outcome) {\n if (finished) return finished\n const endedAt = Math.max(startedAt, outcome?.endedAt ?? now())\n for (const id of open) {\n const record = records.get(id)!\n record.endMs = Math.max(record.startMs, endedAt)\n record.unfinished = true\n }\n open.clear()\n const spans = [...records.values()].sort((a, b) => a.startMs - b.startMs)\n finished = {\n traceId,\n rootSpanId: rootId,\n executionId,\n startedAt,\n endedAt,\n spans,\n summary: summarizeTurnSpans(spans, rootId, startedAt, endedAt, traceId, dropped),\n status: outcome?.error === undefined ? 'ok' : 'error',\n ...(outcome?.error === undefined ? {} : { error: errorText(outcome.error) }),\n }\n return finished\n },\n }\n return trace\n}\n"],"mappings":"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","names":[]}
@@ -430,7 +430,7 @@ function createCloudArtifactOrigin(options) {
430
430
  const headers = baseHeaders();
431
431
  headers.set("Content-Type", file.contentType);
432
432
  headers.set("ETag", `"${file.sha256}"`);
433
- headers.set("Cache-Control", "private, max-age=60");
433
+ headers.set("Cache-Control", "no-store");
434
434
  if (request.headers.get("If-None-Match") === `"${file.sha256}"`) {
435
435
  await object.stream().cancel();
436
436
  return new Response(null, { status: 304, headers });
@@ -471,4 +471,4 @@ export {
471
471
  cloudArtifactContentSecurityPolicy,
472
472
  createCloudArtifactOrigin
473
473
  };
474
- //# sourceMappingURL=chunk-7WEPGGOW.js.map
474
+ //# sourceMappingURL=chunk-EGBNNTAL.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/workspace-apps/cloud-artifacts.ts","../src/workspace-apps/cloud-artifact-origin.ts"],"sourcesContent":["/**\n * Cloud artifacts: a page, dashboard or small app an agent made, copied out of\n * the box into object storage when it is produced, so it opens from a link\n * while the box is asleep and its owner can share it.\n *\n * Three kinds, the smallest that covers each case:\n * - `static`: HTML, CSS, JS and media with no server code, served from storage.\n * - `worker`: a static bundle plus `_worker.js`. A request that matches no\n * file runs in a Worker Loader isolate with no network and only the\n * bindings the product grants (`./cloud-artifact-origin`).\n * - `openui`: a declarative OpenUI view that the product renders itself.\n * A live dev server on a box port stays a {@link WorkspaceAppRecord} preview.\n *\n * The product owns records, authorization and the routes that mint links.\n * This module owns the bundle contract, the storage layout and the link tokens.\n * Agent HTML is served only from a separate origin (`./cloud-artifact-origin`),\n * never from the product's origin, where it could act with a session.\n */\nimport { constantTimeEqual, hmacSha256Base64Url } from '../crypto/web-token'\nimport type { ObjectStore } from '../object-store'\nimport { createExpiringCapabilityToken, verifyExpiringCapabilityToken } from '../tools/capability'\nimport type { WorkspaceRole } from '../teams/roles'\n\nexport type CloudArtifactKind = 'static' | 'worker' | 'openui'\n\n/** One stored file of a version. Bytes live at {@link cloudArtifactObjectKey}. */\nexport interface CloudArtifactFile {\n /** Relative to the bundle root, such as `index.html` or `images/hero.png`. */\n path: string\n contentType: string\n size: number\n /** Hex SHA-256 of the bytes; identical files share one stored object. */\n sha256: string\n}\n\n/** An immutable snapshot. Its id changes when any path, type or byte changes. */\nexport interface CloudArtifactVersion {\n id: string\n kind: CloudArtifactKind\n /** The file a link opens: HTML for `static` and `worker`, JSON for `openui`. */\n entry: string\n files: CloudArtifactFile[]\n createdAt: string\n}\n\n/** What the origin needs from the product's record. Products persist more. */\nexport interface CloudArtifactRecord {\n id: string\n workspaceId: string\n title: string\n version: CloudArtifactVersion\n}\n\n/** Who a view link was minted for: a workspace member, or a public share. */\nexport type CloudArtifactGrant = { type: 'member' } | { type: 'share'; shareId: string }\n\n/** Opening an artifact needs `viewer`; creating or revoking a public link needs this role. */\nexport const CLOUD_ARTIFACT_SHARE_ROLE: WorkspaceRole = 'editor'\n\n/** The module a `worker` bundle runs, at the bundle root (the Cloudflare Pages name). */\nexport const CLOUD_ARTIFACT_WORKER_MODULE = '_worker.js'\nexport const CLOUD_ARTIFACT_MAX_FILES = 200\nexport const CLOUD_ARTIFACT_MAX_FILE_BYTES = 10 * 1024 * 1024\nexport const CLOUD_ARTIFACT_MAX_BYTES = 50 * 1024 * 1024\n/** Member view links are re-minted on every page load, so they can be short. */\nexport const CLOUD_ARTIFACT_VIEW_TTL_MS = 30 * 60 * 1000\n\nexport type CloudArtifactErrorCode =\n | 'invalid_path'\n | 'duplicate_path'\n | 'unsupported_type'\n | 'too_large'\n | 'too_many_files'\n | 'missing_entry'\n | 'invalid_entry'\n\nexport class CloudArtifactError extends Error {\n constructor(readonly code: CloudArtifactErrorCode, message: string) {\n super(message)\n this.name = 'CloudArtifactError'\n }\n}\n\nconst TYPES: Record<string, string> = {\n html: 'text/html; charset=utf-8',\n htm: 'text/html; charset=utf-8',\n css: 'text/css; charset=utf-8',\n js: 'text/javascript; charset=utf-8',\n mjs: 'text/javascript; charset=utf-8',\n json: 'application/json; charset=utf-8',\n map: 'application/json; charset=utf-8',\n txt: 'text/plain; charset=utf-8',\n md: 'text/markdown; charset=utf-8',\n csv: 'text/csv; charset=utf-8',\n xml: 'application/xml; charset=utf-8',\n svg: 'image/svg+xml',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n avif: 'image/avif',\n ico: 'image/x-icon',\n woff: 'font/woff',\n woff2: 'font/woff2',\n ttf: 'font/ttf',\n otf: 'font/otf',\n mp4: 'video/mp4',\n webm: 'video/webm',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n pdf: 'application/pdf',\n wasm: 'application/wasm',\n}\n\n/** The served type for a path, or null when cloud artifacts do not serve it. */\nexport function cloudArtifactContentType(path: string): string | null {\n const dot = path.lastIndexOf('.')\n if (dot < 0 || dot < path.lastIndexOf('/')) return null\n return TYPES[path.slice(dot + 1).toLowerCase()] ?? null\n}\n\nconst SEGMENT = /^[^\\u0000-\\u001f\\u007f/\\\\?#%]{1,120}$/\n\n/** A bundle path: relative, no `.` or `..` segments, no control or URL-special characters. */\nexport function isCloudArtifactPath(path: string): boolean {\n if (typeof path !== 'string' || path.length > 240) return false\n return path.split('/').every((segment) => SEGMENT.test(segment) && segment !== '.' && segment !== '..'\n && segment.trim() === segment)\n}\n\nfunction mediaType(contentType: string): string {\n return contentType.split(';')[0]!.trim().toLowerCase()\n}\n\nfunction hex(bytes: ArrayBuffer): string {\n return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nasync function sha256(bytes: Uint8Array): Promise<string> {\n return hex(await crypto.subtle.digest('SHA-256', bytes as Uint8Array<ArrayBuffer>))\n}\n\nexport interface CloudArtifactInputFile {\n path: string\n body: Uint8Array | string\n}\n\nexport interface PreparedCloudArtifact {\n version: CloudArtifactVersion\n /** Bytes to store, by SHA-256. Carried files are already stored and absent here. */\n bodies: ReadonlyMap<string, Uint8Array>\n}\n\n/**\n * Validate and hash a bundle. `carry` keeps files of an earlier version that\n * the new source could not supply, such as images that existed only on a box\n * that has since been recycled; their bytes are already stored.\n */\nexport async function prepareCloudArtifact(input: {\n files: readonly CloudArtifactInputFile[]\n entry: string\n /** Set for a declarative OpenUI view; otherwise `_worker.js` makes it `worker`. */\n openui?: boolean\n carry?: readonly CloudArtifactFile[]\n createdAt?: string\n}): Promise<PreparedCloudArtifact> {\n const files = new Map<string, CloudArtifactFile>()\n const bodies = new Map<string, Uint8Array>()\n let total = 0\n for (const file of input.files) {\n if (!isCloudArtifactPath(file.path)) throw new CloudArtifactError('invalid_path', `Invalid artifact path: ${file.path}`)\n if (files.has(file.path)) throw new CloudArtifactError('duplicate_path', `Duplicate artifact path: ${file.path}`)\n const contentType = cloudArtifactContentType(file.path)\n if (!contentType) throw new CloudArtifactError('unsupported_type', `Cloud artifacts do not serve ${file.path}`)\n const bytes = typeof file.body === 'string' ? new TextEncoder().encode(file.body) : file.body\n if (bytes.byteLength > CLOUD_ARTIFACT_MAX_FILE_BYTES) {\n throw new CloudArtifactError('too_large', `${file.path} is larger than ${CLOUD_ARTIFACT_MAX_FILE_BYTES} bytes`)\n }\n total += bytes.byteLength\n const digest = await sha256(bytes)\n bodies.set(digest, bytes)\n files.set(file.path, { path: file.path, contentType, size: bytes.byteLength, sha256: digest })\n }\n for (const file of input.carry ?? []) {\n if (files.has(file.path) || !isCloudArtifactPath(file.path) || !/^[0-9a-f]{64}$/.test(file.sha256)) continue\n total += file.size\n files.set(file.path, file)\n }\n if (files.size > CLOUD_ARTIFACT_MAX_FILES) {\n throw new CloudArtifactError('too_many_files', `An artifact holds at most ${CLOUD_ARTIFACT_MAX_FILES} files`)\n }\n if (total > CLOUD_ARTIFACT_MAX_BYTES) {\n throw new CloudArtifactError('too_large', `An artifact holds at most ${CLOUD_ARTIFACT_MAX_BYTES} bytes`)\n }\n const entry = files.get(input.entry)\n if (!entry) throw new CloudArtifactError('missing_entry', `The bundle has no ${input.entry}`)\n const kind: CloudArtifactKind = input.openui ? 'openui' : files.has(CLOUD_ARTIFACT_WORKER_MODULE) ? 'worker' : 'static'\n if (mediaType(entry.contentType) !== (kind === 'openui' ? 'application/json' : 'text/html')) {\n throw new CloudArtifactError('invalid_entry', `A ${kind} artifact cannot open ${input.entry}`)\n }\n const sorted = [...files.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))\n const manifest = JSON.stringify([kind, input.entry, sorted.map((file) => [file.path, file.contentType, file.sha256])])\n const id = (await sha256(new TextEncoder().encode(manifest))).slice(0, 32)\n return {\n version: { id, kind, entry: input.entry, files: sorted, createdAt: input.createdAt ?? new Date().toISOString() },\n bodies,\n }\n}\n\n/** Content-addressed per workspace, so erasing a workspace deletes one prefix. */\nexport function cloudArtifactObjectKey(workspaceId: string, sha256Hex: string): string {\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(workspaceId) || !/^[0-9a-f]{64}$/.test(sha256Hex)) {\n throw new Error('Invalid cloud artifact object key')\n }\n return `cloud-artifacts/${workspaceId}/${sha256Hex}`\n}\n\n/** The prefix holding every stored byte of a workspace's artifacts. */\nexport function cloudArtifactWorkspacePrefix(workspaceId: string): string {\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(workspaceId)) throw new Error('Invalid workspace id')\n return `cloud-artifacts/${workspaceId}/`\n}\n\n/** Store a prepared version's new bytes; objects already present are not rewritten. */\nexport async function storeCloudArtifact(\n store: ObjectStore,\n workspaceId: string,\n prepared: PreparedCloudArtifact,\n): Promise<CloudArtifactVersion> {\n for (const file of prepared.version.files) {\n const bytes = prepared.bodies.get(file.sha256)\n if (!bytes) continue\n const key = cloudArtifactObjectKey(workspaceId, file.sha256)\n if (await store.head(key)) continue\n await store.put(key, bytes, { contentType: file.contentType, contentLength: bytes.byteLength })\n }\n return prepared.version\n}\n\n// ── Collecting a bundle from a source ───────────────────────────────────────\n\nconst ATTRIBUTE_REFERENCE = /\\s(?:src|href|poster|data-src|data)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/gi\nconst SRCSET = /\\ssrcset\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/gi\nconst CSS_URL = /url\\(\\s*(?:\"([^\"]*)\"|'([^']*)'|([^)'\"\\s]+))\\s*\\)/gi\nconst CSS_IMPORT = /@import\\s+(?:\"([^\"]*)\"|'([^']*)')/gi\nconst JS_IMPORT = /(?:\\bimport\\s*(?:[\\w*{}\\s,$]+\\s*from\\s*)?|\\bexport\\s*[\\w*{}\\s,$]+\\s*from\\s*|\\bimport\\s*\\(\\s*)[\"']([^\"']+)[\"']/g\nconst JS_FETCH = /\\bfetch\\s*\\(\\s*[\"']([^\"']+)[\"']/g\n\nfunction dirname(path: string): string {\n const slash = path.lastIndexOf('/')\n return slash < 0 ? '' : path.slice(0, slash)\n}\n\n/** Resolve a document-relative reference to a bundle path, or null when it is not a local file. */\nexport function resolveCloudArtifactReference(reference: string, documentPath: string): string | null {\n const raw = reference.trim()\n if (!raw || raw.startsWith('#') || raw.startsWith('/') || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(raw)) return null\n let path = raw.split(/[?#]/)[0]!\n try {\n path = decodeURIComponent(path)\n } catch {\n return null\n }\n const parts = dirname(documentPath) ? dirname(documentPath).split('/') : []\n for (const segment of path.split('/')) {\n if (segment === '' || segment === '.') continue\n if (segment === '..') {\n if (parts.length === 0) return null\n parts.pop()\n } else parts.push(segment)\n }\n const resolved = parts.join('/')\n return resolved && isCloudArtifactPath(resolved) && cloudArtifactContentType(resolved) ? resolved : null\n}\n\nfunction matches(pattern: RegExp, text: string): string[] {\n const found: string[] = []\n for (const match of text.matchAll(pattern)) {\n const value = match.slice(1).find((group) => group !== undefined)\n if (value) found.push(value)\n }\n return found\n}\n\n/** Local files an HTML, CSS or JavaScript document refers to, as bundle paths. */\nexport function cloudArtifactReferences(text: string, documentPath: string): string[] {\n const type = mediaType(cloudArtifactContentType(documentPath) ?? '')\n const references: string[] = []\n if (type === 'text/html') {\n references.push(...matches(ATTRIBUTE_REFERENCE, text))\n for (const set of matches(SRCSET, text)) {\n references.push(...set.split(',').map((candidate) => candidate.trim().split(/\\s+/)[0] ?? ''))\n }\n references.push(...matches(CSS_URL, text), ...matches(CSS_IMPORT, text))\n for (const script of text.matchAll(/<script\\b[^>]*>([\\s\\S]*?)<\\/script>/gi)) {\n references.push(...matches(JS_IMPORT, script[1] ?? ''), ...matches(JS_FETCH, script[1] ?? ''))\n }\n } else if (type === 'text/css') {\n references.push(...matches(CSS_URL, text), ...matches(CSS_IMPORT, text))\n } else if (type === 'text/javascript') {\n references.push(...matches(JS_IMPORT, text), ...matches(JS_FETCH, text))\n }\n const resolved = new Set<string>()\n for (const reference of references) {\n const path = resolveCloudArtifactReference(reference, documentPath)\n if (path && path !== documentPath) resolved.add(path)\n }\n return [...resolved]\n}\n\n/**\n * Gather an entry and every local file it refers to, transitively, plus\n * `_worker.js` and its imports when the root has one. `readMany` returns the\n * bytes of the paths it found (paths are bundle-relative); missing files are\n * reported, not fatal, so a page with one broken image still publishes.\n */\nexport async function collectCloudArtifactBundle(input: {\n entry: string\n readMany(paths: readonly string[]): Promise<ReadonlyMap<string, Uint8Array>>\n maxFiles?: number\n maxBytes?: number\n}): Promise<{ files: CloudArtifactInputFile[]; missing: string[] }> {\n const maxFiles = input.maxFiles ?? CLOUD_ARTIFACT_MAX_FILES\n const maxBytes = input.maxBytes ?? CLOUD_ARTIFACT_MAX_BYTES\n const files = new Map<string, Uint8Array>()\n const missing: string[] = []\n const seen = new Set<string>()\n let total = 0\n let frontier = [input.entry, CLOUD_ARTIFACT_WORKER_MODULE].filter((path) => isCloudArtifactPath(path))\n const decoder = new TextDecoder()\n while (frontier.length > 0 && files.size < maxFiles) {\n const batch = frontier.filter((path) => !seen.has(path)).slice(0, maxFiles - files.size)\n batch.forEach((path) => seen.add(path))\n if (batch.length === 0) break\n const read = await input.readMany(batch)\n const next: string[] = []\n for (const path of batch) {\n const bytes = read.get(path)\n if (!bytes) {\n if (path !== CLOUD_ARTIFACT_WORKER_MODULE) missing.push(path)\n continue\n }\n if (bytes.byteLength > CLOUD_ARTIFACT_MAX_FILE_BYTES || total + bytes.byteLength > maxBytes) {\n missing.push(path)\n continue\n }\n total += bytes.byteLength\n files.set(path, bytes)\n const type = mediaType(cloudArtifactContentType(path) ?? '')\n if (type === 'text/html' || type === 'text/css' || type === 'text/javascript') {\n next.push(...cloudArtifactReferences(decoder.decode(bytes), path))\n }\n }\n frontier = next\n }\n return { files: [...files].map(([path, body]) => ({ path, body })), missing }\n}\n\n// ── Links ───────────────────────────────────────────────────────────────────\n\nconst ID = /^[0-9a-f]{32}$/\nconst VIEW_PREFIX = 'cav_'\n\nfunction grantSegment(grant: CloudArtifactGrant): string {\n if (grant.type === 'member') return 'm'\n if (!ID.test(grant.shareId)) throw new Error('Invalid cloud artifact share id')\n return grant.shareId\n}\n\nfunction viewSubject(artifactId: string, segment: string): string {\n return `cloud-artifact-view:${artifactId}:${segment}`\n}\n\n/** A random 32-hex id for an artifact or a share. */\nexport function createCloudArtifactId(): string {\n return hex(crypto.getRandomValues(new Uint8Array(16)).buffer as ArrayBuffer)\n}\n\nfunction encodePath(path: string): string {\n return path.split('/').map(encodeURIComponent).join('/')\n}\n\n/**\n * A short-lived URL on the artifact origin for one artifact and one grant.\n * Every file of the bundle sits under the same prefix, so relative links work.\n * The product mints it only after it has authorized the viewer.\n */\nexport async function createCloudArtifactViewUrl(input: {\n origin: string\n secret: string\n artifactId: string\n grant: CloudArtifactGrant\n path: string\n ttlMs?: number\n now?: () => number\n}): Promise<string> {\n if (!ID.test(input.artifactId)) throw new Error('Invalid cloud artifact id')\n if (!isCloudArtifactPath(input.path)) throw new Error('Invalid cloud artifact path')\n const segment = grantSegment(input.grant)\n const token = await createExpiringCapabilityToken(viewSubject(input.artifactId, segment), {\n secret: input.secret,\n prefix: VIEW_PREFIX,\n expiresInMs: input.ttlMs ?? CLOUD_ARTIFACT_VIEW_TTL_MS,\n now: input.now,\n })\n if (!token) throw new Error('Cloud artifact links need a signing secret')\n return `${new URL(input.origin).origin}/${input.artifactId}/${segment}/${token}/${encodePath(input.path)}`\n}\n\nexport interface CloudArtifactView {\n artifactId: string\n grant: CloudArtifactGrant\n /** Decoded bundle path; empty when the URL names the root. */\n path: string\n}\n\n/** Read and verify a view URL on the artifact origin; null for anything forged, expired or malformed. */\nexport async function readCloudArtifactViewUrl(\n url: URL,\n options: { secret: string; now?: () => number },\n): Promise<CloudArtifactView | null> {\n const [, artifactId, segment, token, ...rest] = url.pathname.split('/')\n if (!artifactId || !ID.test(artifactId) || !segment || !token) return null\n if (segment !== 'm' && !ID.test(segment)) return null\n const valid = await verifyExpiringCapabilityToken(viewSubject(artifactId, segment), token, {\n secret: options.secret,\n prefix: VIEW_PREFIX,\n now: options.now,\n })\n if (!valid) return null\n let path: string\n try {\n path = rest.map(decodeURIComponent).join('/')\n } catch {\n return null\n }\n if (path !== '' && !isCloudArtifactPath(path)) return null\n return { artifactId, grant: segment === 'm' ? { type: 'member' } : { type: 'share', shareId: segment }, path }\n}\n\nfunction shareSubject(shareId: string): string {\n return `cloud-artifact-share:${shareId}`\n}\n\n/**\n * The secret part of a public link: `<shareId>.<HMAC>`. It is derived, so the\n * product stores only the share id and can show the link again; revoking the\n * share row turns the link off at once.\n */\nexport async function createCloudArtifactShareToken(shareId: string, secret: string): Promise<string> {\n if (!ID.test(shareId)) throw new Error('Invalid cloud artifact share id')\n if (!secret.trim()) throw new Error('Cloud artifact links need a signing secret')\n return `${shareId}.${await hmacSha256Base64Url(shareSubject(shareId), secret.trim())}`\n}\n\n/** The share id a public link names, or null when its signature does not verify. */\nexport async function readCloudArtifactShareToken(token: string, secret: string): Promise<string | null> {\n const [shareId, signature, extra] = token.split('.')\n if (extra !== undefined || !shareId || !signature || !ID.test(shareId) || !secret.trim()) return null\n const expected = await hmacSha256Base64Url(shareSubject(shareId), secret.trim())\n return constantTimeEqual(signature, expected) ? shareId : null\n}\n","/**\n * The artifact origin: the only place cloud artifact HTML and scripts run.\n *\n * Mount it on a host of its own on a different site from the product (for\n * GTM, `gtm-artifacts.tangle.sh` against `gtm.tangle.tools`), so a page an\n * agent wrote never shares cookies, storage or same-site requests with a\n * signed-in product session. Every response also carries a CSP `sandbox`\n * without `allow-same-origin`, so each document runs in an opaque origin and\n * one artifact cannot read another's storage.\n *\n * A request names `/<artifactId>/<grant>/<token>/<path>`. The token is a\n * short-lived capability minted by the product after it authorized the viewer;\n * `resolve` returns the record only while that grant is still live, so a\n * revoked share stops serving on its next request.\n *\n * Files are streamed from the object store. For a `worker` artifact, a request\n * that matches no file runs `_worker.js` in a Worker Loader isolate with\n * `globalOutbound: null` (no network), CPU and subrequest limits, and only the\n * bindings `workerEnv` grants. Its response headers are replaced with this\n * origin's, so the isolate cannot loosen them.\n */\nimport type { ObjectStore } from '../object-store'\nimport {\n CLOUD_ARTIFACT_WORKER_MODULE,\n cloudArtifactObjectKey,\n readCloudArtifactViewUrl,\n type CloudArtifactGrant,\n type CloudArtifactRecord,\n} from './cloud-artifacts'\n\n/** Script and style CDNs a generated page may load. Everything else is local or inline. */\nexport const CLOUD_ARTIFACT_SCRIPT_HOSTS = [\n 'https://cdn.jsdelivr.net',\n 'https://cdnjs.cloudflare.com',\n 'https://unpkg.com',\n] as const\n\n/** The iframe sandbox for an artifact frame; never add `allow-same-origin`. */\nexport const CLOUD_ARTIFACT_FRAME_SANDBOX =\n 'allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads'\n\n/** The Content-Security-Policy every artifact response carries. */\nexport function cloudArtifactContentSecurityPolicy(input: { origin: string; frameAncestors: readonly string[] }): string {\n const self = new URL(input.origin).origin\n const cdns = CLOUD_ARTIFACT_SCRIPT_HOSTS.join(' ')\n return [\n \"default-src 'none'\",\n `script-src ${self} 'unsafe-inline' 'unsafe-eval' blob: ${cdns}`,\n `style-src ${self} 'unsafe-inline' https://fonts.googleapis.com ${cdns}`,\n `img-src ${self} data: blob: https:`,\n `font-src ${self} data: https:`,\n `media-src ${self} data: blob: https:`,\n `connect-src ${self}`,\n 'frame-src https:',\n 'worker-src blob:',\n `form-action ${self}`,\n \"base-uri 'none'\",\n `frame-ancestors ${input.frameAncestors.length > 0 ? input.frameAncestors.join(' ') : \"'none'\"}`,\n `sandbox ${CLOUD_ARTIFACT_FRAME_SANDBOX}`,\n ].join('; ')\n}\n\n/** The code shape Worker Loader's `get` callback returns (Cloudflare `WorkerCode`). */\nexport interface CloudArtifactWorkerCode {\n compatibilityDate: string\n compatibilityFlags?: string[]\n mainModule: string\n modules: Record<string, string | { js: string } | { json: unknown } | { text: string }>\n env?: Record<string, unknown>\n globalOutbound: null\n limits?: { cpuMs?: number; subRequests?: number }\n}\n\n/** Structural `env.LOADER` (a `worker_loaders` binding), so workers-types stays out of the public types. */\nexport interface CloudArtifactWorkerLoader {\n get(id: string, load: () => Promise<CloudArtifactWorkerCode>): {\n getEntrypoint(name?: string | null, options?: { limits?: { cpuMs?: number; subRequests?: number } }): {\n fetch(request: Request): Promise<Response>\n }\n }\n}\n\nexport interface CloudArtifactOriginOptions {\n /** This origin, such as `https://gtm-artifacts.tangle.sh`. Requests for another host get 404. */\n origin: string\n /** Product origins allowed to frame an artifact (CSP `frame-ancestors`). */\n frameAncestors: readonly string[]\n /** The secret the product signs view links with. */\n secret: string\n store: ObjectStore\n /**\n * The artifact's current record for a verified link, or null when it no\n * longer exists or the grant is no longer live (a revoked share).\n */\n resolve(view: { artifactId: string; grant: CloudArtifactGrant }): Promise<CloudArtifactRecord | null>\n /** The `worker_loaders` binding. Without it a `worker` artifact serves only its files. */\n loader?: CloudArtifactWorkerLoader\n /** Bindings granted to a `worker` artifact's isolate; nothing else reaches it. */\n workerEnv?(record: CloudArtifactRecord, grant: CloudArtifactGrant): Record<string, unknown>\n workerLimits?: { cpuMs: number; subRequests: number }\n /** Compatibility date for isolates. */\n workerCompatibilityDate?: string\n now?: () => number\n}\n\nconst DEFAULT_WORKER_LIMITS = { cpuMs: 50, subRequests: 20 }\nconst MAX_WORKER_SOURCE_BYTES = 4 * 1024 * 1024\nconst FORWARDED_REQUEST_HEADERS = ['accept', 'accept-language', 'content-type', 'if-none-match', 'range']\nconst ROBOTS = 'User-agent: *\\nDisallow: /\\n'\n\nfunction plain(status: number, text: string, headers: Headers): Response {\n headers.set('Content-Type', 'text/plain; charset=utf-8')\n headers.set('Cache-Control', 'no-store')\n return new Response(text, { status, headers })\n}\n\n/** Serve cloud artifacts on their own origin. */\nexport function createCloudArtifactOrigin(options: CloudArtifactOriginOptions): (request: Request) => Promise<Response> {\n const origin = new URL(options.origin).origin\n if (!/^https:\\/\\//.test(origin) && !/^http:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$/.test(origin)) {\n throw new Error('The cloud artifact origin must be HTTPS')\n }\n const ancestors = options.frameAncestors.map((value) => new URL(value).origin)\n if (ancestors.includes(origin)) {\n throw new Error('The cloud artifact origin must not be a product origin')\n }\n if (!options.secret.trim()) throw new Error('The cloud artifact origin needs the link signing secret')\n const csp = cloudArtifactContentSecurityPolicy({ origin, frameAncestors: ancestors })\n const limits = options.workerLimits ?? DEFAULT_WORKER_LIMITS\n\n const baseHeaders = (): Headers => new Headers({\n 'Content-Security-Policy': csp,\n 'X-Content-Type-Options': 'nosniff',\n // The view token is in the URL; never send it to a CDN or a linked site.\n 'Referrer-Policy': 'no-referrer',\n 'Permissions-Policy': 'camera=(), microphone=(), geolocation=(), payment=(), usb=(), serial=(), bluetooth=()',\n 'Cross-Origin-Resource-Policy': 'cross-origin',\n // Opaque-origin documents fetch their own files cross-origin; the URL token is the capability.\n 'Access-Control-Allow-Origin': '*',\n 'X-Robots-Tag': 'noindex, nofollow',\n })\n\n async function runWorker(\n request: Request,\n record: CloudArtifactRecord,\n grant: CloudArtifactGrant,\n path: string,\n ): Promise<Response> {\n const loader = options.loader\n if (!loader) return plain(404, 'Not found', baseHeaders())\n const version = record.version\n const worker = loader.get(`${record.id}:${version.id}`, async () => {\n const modules: CloudArtifactWorkerCode['modules'] = {}\n let bytes = 0\n for (const file of version.files) {\n const type = file.contentType.split(';')[0]\n if (type !== 'text/javascript' && type !== 'application/json') continue\n bytes += file.size\n if (bytes > MAX_WORKER_SOURCE_BYTES) throw new Error('Artifact worker source is too large')\n const object = await options.store.get(cloudArtifactObjectKey(record.workspaceId, file.sha256))\n if (!object) throw new Error(`Artifact file ${file.path} is missing from storage`)\n const text = await new Response(object.stream()).text()\n modules[file.path] = type === 'application/json' ? { json: JSON.parse(text) } : { js: text }\n }\n return {\n compatibilityDate: options.workerCompatibilityDate ?? '2026-10-01',\n mainModule: CLOUD_ARTIFACT_WORKER_MODULE,\n modules,\n env: options.workerEnv?.(record, grant) ?? {},\n globalOutbound: null,\n limits,\n }\n })\n const url = new URL(request.url)\n const headers = new Headers()\n for (const name of FORWARDED_REQUEST_HEADERS) {\n const value = request.headers.get(name)\n if (value !== null) headers.set(name, value)\n }\n const hasBody = request.method !== 'GET' && request.method !== 'HEAD'\n const inner = new Request(`https://artifact.invalid/${path}${url.search}`, {\n method: request.method,\n headers,\n body: hasBody ? request.body : undefined,\n redirect: 'manual',\n })\n let response: Response\n try {\n response = await worker.getEntrypoint(null, { limits }).fetch(inner)\n } catch {\n return plain(502, \"This artifact's server code failed.\", baseHeaders())\n }\n const out = baseHeaders()\n const type = response.headers.get('Content-Type')\n if (type) out.set('Content-Type', type)\n out.set('Cache-Control', 'no-store')\n return new Response(request.method === 'HEAD' ? null : response.body, { status: response.status, headers: out })\n }\n\n return async function serve(request: Request): Promise<Response> {\n const url = new URL(request.url)\n if (url.origin !== origin) return plain(404, 'Not found', baseHeaders())\n if (url.pathname === '/robots.txt') return plain(200, ROBOTS, baseHeaders())\n if (request.method === 'OPTIONS') {\n const headers = baseHeaders()\n headers.set('Access-Control-Allow-Methods', 'GET, HEAD, POST, PUT, PATCH, DELETE')\n headers.set('Access-Control-Allow-Headers', 'Content-Type')\n headers.set('Access-Control-Max-Age', '600')\n return new Response(null, { status: 204, headers })\n }\n const view = await readCloudArtifactViewUrl(url, { secret: options.secret, now: options.now })\n if (!view) return plain(404, 'This link is invalid or has expired. Open the artifact again from its page.', baseHeaders())\n const record = await options.resolve({ artifactId: view.artifactId, grant: view.grant })\n if (!record || record.id !== view.artifactId) return plain(404, 'This artifact is no longer shared.', baseHeaders())\n const path = view.path || record.version.entry\n const file = record.version.kind === 'openui' ? undefined : record.version.files.find((item) => item.path === path)\n const readable = request.method === 'GET' || request.method === 'HEAD'\n if (file && readable && path !== CLOUD_ARTIFACT_WORKER_MODULE) {\n const object = await options.store.get(cloudArtifactObjectKey(record.workspaceId, file.sha256))\n if (!object) return plain(404, 'Not found', baseHeaders())\n const headers = baseHeaders()\n headers.set('Content-Type', file.contentType)\n headers.set('ETag', `\"${file.sha256}\"`)\n headers.set('Cache-Control', 'private, max-age=60')\n if (request.headers.get('If-None-Match') === `\"${file.sha256}\"`) {\n await object.stream().cancel()\n return new Response(null, { status: 304, headers })\n }\n headers.set('Content-Length', String(object.size))\n if (request.method === 'HEAD') await object.stream().cancel()\n return new Response(request.method === 'HEAD' ? null : object.stream(), { status: 200, headers })\n }\n if (record.version.kind === 'worker') return runWorker(request, record, view.grant, path)\n return plain(404, 'Not found', baseHeaders())\n }\n}\n"],"mappings":"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","names":["type"]}
1
+ {"version":3,"sources":["../src/workspace-apps/cloud-artifacts.ts","../src/workspace-apps/cloud-artifact-origin.ts"],"sourcesContent":["/**\n * Cloud artifacts: a page, dashboard or small app an agent made, copied out of\n * the box into object storage when it is produced, so it opens from a link\n * while the box is asleep and its owner can share it.\n *\n * Three kinds, the smallest that covers each case:\n * - `static`: HTML, CSS, JS and media with no server code, served from storage.\n * - `worker`: a static bundle plus `_worker.js`. A request that matches no\n * file runs in a Worker Loader isolate with no network and only the\n * bindings the product grants (`./cloud-artifact-origin`).\n * - `openui`: a declarative OpenUI view that the product renders itself.\n * A live dev server on a box port stays a {@link WorkspaceAppRecord} preview.\n *\n * The product owns records, authorization and the routes that mint links.\n * This module owns the bundle contract, the storage layout and the link tokens.\n * Agent HTML is served only from a separate origin (`./cloud-artifact-origin`),\n * never from the product's origin, where it could act with a session.\n */\nimport { constantTimeEqual, hmacSha256Base64Url } from '../crypto/web-token'\nimport type { ObjectStore } from '../object-store'\nimport { createExpiringCapabilityToken, verifyExpiringCapabilityToken } from '../tools/capability'\nimport type { WorkspaceRole } from '../teams/roles'\n\nexport type CloudArtifactKind = 'static' | 'worker' | 'openui'\n\n/** One stored file of a version. Bytes live at {@link cloudArtifactObjectKey}. */\nexport interface CloudArtifactFile {\n /** Relative to the bundle root, such as `index.html` or `images/hero.png`. */\n path: string\n contentType: string\n size: number\n /** Hex SHA-256 of the bytes; identical files share one stored object. */\n sha256: string\n}\n\n/** An immutable snapshot. Its id changes when any path, type or byte changes. */\nexport interface CloudArtifactVersion {\n id: string\n kind: CloudArtifactKind\n /** The file a link opens: HTML for `static` and `worker`, JSON for `openui`. */\n entry: string\n files: CloudArtifactFile[]\n createdAt: string\n}\n\n/** What the origin needs from the product's record. Products persist more. */\nexport interface CloudArtifactRecord {\n id: string\n workspaceId: string\n title: string\n version: CloudArtifactVersion\n}\n\n/** Who a view link was minted for: a workspace member, or a public share. */\nexport type CloudArtifactGrant = { type: 'member' } | { type: 'share'; shareId: string }\n\n/** Opening an artifact needs `viewer`; creating or revoking a public link needs this role. */\nexport const CLOUD_ARTIFACT_SHARE_ROLE: WorkspaceRole = 'editor'\n\n/** The module a `worker` bundle runs, at the bundle root (the Cloudflare Pages name). */\nexport const CLOUD_ARTIFACT_WORKER_MODULE = '_worker.js'\nexport const CLOUD_ARTIFACT_MAX_FILES = 200\nexport const CLOUD_ARTIFACT_MAX_FILE_BYTES = 10 * 1024 * 1024\nexport const CLOUD_ARTIFACT_MAX_BYTES = 50 * 1024 * 1024\n/** Member view links are re-minted on every page load, so they can be short. */\nexport const CLOUD_ARTIFACT_VIEW_TTL_MS = 30 * 60 * 1000\n\nexport type CloudArtifactErrorCode =\n | 'invalid_path'\n | 'duplicate_path'\n | 'unsupported_type'\n | 'too_large'\n | 'too_many_files'\n | 'missing_entry'\n | 'invalid_entry'\n\nexport class CloudArtifactError extends Error {\n constructor(readonly code: CloudArtifactErrorCode, message: string) {\n super(message)\n this.name = 'CloudArtifactError'\n }\n}\n\nconst TYPES: Record<string, string> = {\n html: 'text/html; charset=utf-8',\n htm: 'text/html; charset=utf-8',\n css: 'text/css; charset=utf-8',\n js: 'text/javascript; charset=utf-8',\n mjs: 'text/javascript; charset=utf-8',\n json: 'application/json; charset=utf-8',\n map: 'application/json; charset=utf-8',\n txt: 'text/plain; charset=utf-8',\n md: 'text/markdown; charset=utf-8',\n csv: 'text/csv; charset=utf-8',\n xml: 'application/xml; charset=utf-8',\n svg: 'image/svg+xml',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n avif: 'image/avif',\n ico: 'image/x-icon',\n woff: 'font/woff',\n woff2: 'font/woff2',\n ttf: 'font/ttf',\n otf: 'font/otf',\n mp4: 'video/mp4',\n webm: 'video/webm',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n pdf: 'application/pdf',\n wasm: 'application/wasm',\n}\n\n/** The served type for a path, or null when cloud artifacts do not serve it. */\nexport function cloudArtifactContentType(path: string): string | null {\n const dot = path.lastIndexOf('.')\n if (dot < 0 || dot < path.lastIndexOf('/')) return null\n return TYPES[path.slice(dot + 1).toLowerCase()] ?? null\n}\n\nconst SEGMENT = /^[^\\u0000-\\u001f\\u007f/\\\\?#%]{1,120}$/\n\n/** A bundle path: relative, no `.` or `..` segments, no control or URL-special characters. */\nexport function isCloudArtifactPath(path: string): boolean {\n if (typeof path !== 'string' || path.length > 240) return false\n return path.split('/').every((segment) => SEGMENT.test(segment) && segment !== '.' && segment !== '..'\n && segment.trim() === segment)\n}\n\nfunction mediaType(contentType: string): string {\n return contentType.split(';')[0]!.trim().toLowerCase()\n}\n\nfunction hex(bytes: ArrayBuffer): string {\n return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nasync function sha256(bytes: Uint8Array): Promise<string> {\n return hex(await crypto.subtle.digest('SHA-256', bytes as Uint8Array<ArrayBuffer>))\n}\n\nexport interface CloudArtifactInputFile {\n path: string\n body: Uint8Array | string\n}\n\nexport interface PreparedCloudArtifact {\n version: CloudArtifactVersion\n /** Bytes to store, by SHA-256. Carried files are already stored and absent here. */\n bodies: ReadonlyMap<string, Uint8Array>\n}\n\n/**\n * Validate and hash a bundle. `carry` keeps files of an earlier version that\n * the new source could not supply, such as images that existed only on a box\n * that has since been recycled; their bytes are already stored.\n */\nexport async function prepareCloudArtifact(input: {\n files: readonly CloudArtifactInputFile[]\n entry: string\n /** Set for a declarative OpenUI view; otherwise `_worker.js` makes it `worker`. */\n openui?: boolean\n carry?: readonly CloudArtifactFile[]\n createdAt?: string\n}): Promise<PreparedCloudArtifact> {\n const files = new Map<string, CloudArtifactFile>()\n const bodies = new Map<string, Uint8Array>()\n let total = 0\n for (const file of input.files) {\n if (!isCloudArtifactPath(file.path)) throw new CloudArtifactError('invalid_path', `Invalid artifact path: ${file.path}`)\n if (files.has(file.path)) throw new CloudArtifactError('duplicate_path', `Duplicate artifact path: ${file.path}`)\n const contentType = cloudArtifactContentType(file.path)\n if (!contentType) throw new CloudArtifactError('unsupported_type', `Cloud artifacts do not serve ${file.path}`)\n const bytes = typeof file.body === 'string' ? new TextEncoder().encode(file.body) : file.body\n if (bytes.byteLength > CLOUD_ARTIFACT_MAX_FILE_BYTES) {\n throw new CloudArtifactError('too_large', `${file.path} is larger than ${CLOUD_ARTIFACT_MAX_FILE_BYTES} bytes`)\n }\n total += bytes.byteLength\n const digest = await sha256(bytes)\n bodies.set(digest, bytes)\n files.set(file.path, { path: file.path, contentType, size: bytes.byteLength, sha256: digest })\n }\n for (const file of input.carry ?? []) {\n if (files.has(file.path) || !isCloudArtifactPath(file.path) || !/^[0-9a-f]{64}$/.test(file.sha256)) continue\n total += file.size\n files.set(file.path, file)\n }\n if (files.size > CLOUD_ARTIFACT_MAX_FILES) {\n throw new CloudArtifactError('too_many_files', `An artifact holds at most ${CLOUD_ARTIFACT_MAX_FILES} files`)\n }\n if (total > CLOUD_ARTIFACT_MAX_BYTES) {\n throw new CloudArtifactError('too_large', `An artifact holds at most ${CLOUD_ARTIFACT_MAX_BYTES} bytes`)\n }\n const entry = files.get(input.entry)\n if (!entry) throw new CloudArtifactError('missing_entry', `The bundle has no ${input.entry}`)\n const kind: CloudArtifactKind = input.openui ? 'openui' : files.has(CLOUD_ARTIFACT_WORKER_MODULE) ? 'worker' : 'static'\n if (mediaType(entry.contentType) !== (kind === 'openui' ? 'application/json' : 'text/html')) {\n throw new CloudArtifactError('invalid_entry', `A ${kind} artifact cannot open ${input.entry}`)\n }\n const sorted = [...files.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))\n const manifest = JSON.stringify([kind, input.entry, sorted.map((file) => [file.path, file.contentType, file.sha256])])\n const id = (await sha256(new TextEncoder().encode(manifest))).slice(0, 32)\n return {\n version: { id, kind, entry: input.entry, files: sorted, createdAt: input.createdAt ?? new Date().toISOString() },\n bodies,\n }\n}\n\n/** Content-addressed per workspace, so erasing a workspace deletes one prefix. */\nexport function cloudArtifactObjectKey(workspaceId: string, sha256Hex: string): string {\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(workspaceId) || !/^[0-9a-f]{64}$/.test(sha256Hex)) {\n throw new Error('Invalid cloud artifact object key')\n }\n return `cloud-artifacts/${workspaceId}/${sha256Hex}`\n}\n\n/** The prefix holding every stored byte of a workspace's artifacts. */\nexport function cloudArtifactWorkspacePrefix(workspaceId: string): string {\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(workspaceId)) throw new Error('Invalid workspace id')\n return `cloud-artifacts/${workspaceId}/`\n}\n\n/** Store a prepared version's new bytes; objects already present are not rewritten. */\nexport async function storeCloudArtifact(\n store: ObjectStore,\n workspaceId: string,\n prepared: PreparedCloudArtifact,\n): Promise<CloudArtifactVersion> {\n for (const file of prepared.version.files) {\n const bytes = prepared.bodies.get(file.sha256)\n if (!bytes) continue\n const key = cloudArtifactObjectKey(workspaceId, file.sha256)\n if (await store.head(key)) continue\n await store.put(key, bytes, { contentType: file.contentType, contentLength: bytes.byteLength })\n }\n return prepared.version\n}\n\n// ── Collecting a bundle from a source ───────────────────────────────────────\n\nconst ATTRIBUTE_REFERENCE = /\\s(?:src|href|poster|data-src|data)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/gi\nconst SRCSET = /\\ssrcset\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/gi\nconst CSS_URL = /url\\(\\s*(?:\"([^\"]*)\"|'([^']*)'|([^)'\"\\s]+))\\s*\\)/gi\nconst CSS_IMPORT = /@import\\s+(?:\"([^\"]*)\"|'([^']*)')/gi\nconst JS_IMPORT = /(?:\\bimport\\s*(?:[\\w*{}\\s,$]+\\s*from\\s*)?|\\bexport\\s*[\\w*{}\\s,$]+\\s*from\\s*|\\bimport\\s*\\(\\s*)[\"']([^\"']+)[\"']/g\nconst JS_FETCH = /\\bfetch\\s*\\(\\s*[\"']([^\"']+)[\"']/g\n\nfunction dirname(path: string): string {\n const slash = path.lastIndexOf('/')\n return slash < 0 ? '' : path.slice(0, slash)\n}\n\n/** Resolve a document-relative reference to a bundle path, or null when it is not a local file. */\nexport function resolveCloudArtifactReference(reference: string, documentPath: string): string | null {\n const raw = reference.trim()\n if (!raw || raw.startsWith('#') || raw.startsWith('/') || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(raw)) return null\n let path = raw.split(/[?#]/)[0]!\n try {\n path = decodeURIComponent(path)\n } catch {\n return null\n }\n const parts = dirname(documentPath) ? dirname(documentPath).split('/') : []\n for (const segment of path.split('/')) {\n if (segment === '' || segment === '.') continue\n if (segment === '..') {\n if (parts.length === 0) return null\n parts.pop()\n } else parts.push(segment)\n }\n const resolved = parts.join('/')\n return resolved && isCloudArtifactPath(resolved) && cloudArtifactContentType(resolved) ? resolved : null\n}\n\nfunction matches(pattern: RegExp, text: string): string[] {\n const found: string[] = []\n for (const match of text.matchAll(pattern)) {\n const value = match.slice(1).find((group) => group !== undefined)\n if (value) found.push(value)\n }\n return found\n}\n\n/** Local files an HTML, CSS or JavaScript document refers to, as bundle paths. */\nexport function cloudArtifactReferences(text: string, documentPath: string): string[] {\n const type = mediaType(cloudArtifactContentType(documentPath) ?? '')\n const references: string[] = []\n if (type === 'text/html') {\n references.push(...matches(ATTRIBUTE_REFERENCE, text))\n for (const set of matches(SRCSET, text)) {\n references.push(...set.split(',').map((candidate) => candidate.trim().split(/\\s+/)[0] ?? ''))\n }\n references.push(...matches(CSS_URL, text), ...matches(CSS_IMPORT, text))\n for (const script of text.matchAll(/<script\\b[^>]*>([\\s\\S]*?)<\\/script>/gi)) {\n references.push(...matches(JS_IMPORT, script[1] ?? ''), ...matches(JS_FETCH, script[1] ?? ''))\n }\n } else if (type === 'text/css') {\n references.push(...matches(CSS_URL, text), ...matches(CSS_IMPORT, text))\n } else if (type === 'text/javascript') {\n references.push(...matches(JS_IMPORT, text), ...matches(JS_FETCH, text))\n }\n const resolved = new Set<string>()\n for (const reference of references) {\n const path = resolveCloudArtifactReference(reference, documentPath)\n if (path && path !== documentPath) resolved.add(path)\n }\n return [...resolved]\n}\n\n/**\n * Gather an entry and every local file it refers to, transitively, plus\n * `_worker.js` and its imports when the root has one. `readMany` returns the\n * bytes of the paths it found (paths are bundle-relative); missing files are\n * reported, not fatal, so a page with one broken image still publishes.\n */\nexport async function collectCloudArtifactBundle(input: {\n entry: string\n readMany(paths: readonly string[]): Promise<ReadonlyMap<string, Uint8Array>>\n maxFiles?: number\n maxBytes?: number\n}): Promise<{ files: CloudArtifactInputFile[]; missing: string[] }> {\n const maxFiles = input.maxFiles ?? CLOUD_ARTIFACT_MAX_FILES\n const maxBytes = input.maxBytes ?? CLOUD_ARTIFACT_MAX_BYTES\n const files = new Map<string, Uint8Array>()\n const missing: string[] = []\n const seen = new Set<string>()\n let total = 0\n let frontier = [input.entry, CLOUD_ARTIFACT_WORKER_MODULE].filter((path) => isCloudArtifactPath(path))\n const decoder = new TextDecoder()\n while (frontier.length > 0 && files.size < maxFiles) {\n const batch = frontier.filter((path) => !seen.has(path)).slice(0, maxFiles - files.size)\n batch.forEach((path) => seen.add(path))\n if (batch.length === 0) break\n const read = await input.readMany(batch)\n const next: string[] = []\n for (const path of batch) {\n const bytes = read.get(path)\n if (!bytes) {\n if (path !== CLOUD_ARTIFACT_WORKER_MODULE) missing.push(path)\n continue\n }\n if (bytes.byteLength > CLOUD_ARTIFACT_MAX_FILE_BYTES || total + bytes.byteLength > maxBytes) {\n missing.push(path)\n continue\n }\n total += bytes.byteLength\n files.set(path, bytes)\n const type = mediaType(cloudArtifactContentType(path) ?? '')\n if (type === 'text/html' || type === 'text/css' || type === 'text/javascript') {\n next.push(...cloudArtifactReferences(decoder.decode(bytes), path))\n }\n }\n frontier = next\n }\n return { files: [...files].map(([path, body]) => ({ path, body })), missing }\n}\n\n// ── Links ───────────────────────────────────────────────────────────────────\n\nconst ID = /^[0-9a-f]{32}$/\nconst VIEW_PREFIX = 'cav_'\n\nfunction grantSegment(grant: CloudArtifactGrant): string {\n if (grant.type === 'member') return 'm'\n if (!ID.test(grant.shareId)) throw new Error('Invalid cloud artifact share id')\n return grant.shareId\n}\n\nfunction viewSubject(artifactId: string, segment: string): string {\n return `cloud-artifact-view:${artifactId}:${segment}`\n}\n\n/** A random 32-hex id for an artifact or a share. */\nexport function createCloudArtifactId(): string {\n return hex(crypto.getRandomValues(new Uint8Array(16)).buffer as ArrayBuffer)\n}\n\nfunction encodePath(path: string): string {\n return path.split('/').map(encodeURIComponent).join('/')\n}\n\n/**\n * A short-lived URL on the artifact origin for one artifact and one grant.\n * Every file of the bundle sits under the same prefix, so relative links work.\n * The product mints it only after it has authorized the viewer.\n */\nexport async function createCloudArtifactViewUrl(input: {\n origin: string\n secret: string\n artifactId: string\n grant: CloudArtifactGrant\n path: string\n ttlMs?: number\n now?: () => number\n}): Promise<string> {\n if (!ID.test(input.artifactId)) throw new Error('Invalid cloud artifact id')\n if (!isCloudArtifactPath(input.path)) throw new Error('Invalid cloud artifact path')\n const segment = grantSegment(input.grant)\n const token = await createExpiringCapabilityToken(viewSubject(input.artifactId, segment), {\n secret: input.secret,\n prefix: VIEW_PREFIX,\n expiresInMs: input.ttlMs ?? CLOUD_ARTIFACT_VIEW_TTL_MS,\n now: input.now,\n })\n if (!token) throw new Error('Cloud artifact links need a signing secret')\n return `${new URL(input.origin).origin}/${input.artifactId}/${segment}/${token}/${encodePath(input.path)}`\n}\n\nexport interface CloudArtifactView {\n artifactId: string\n grant: CloudArtifactGrant\n /** Decoded bundle path; empty when the URL names the root. */\n path: string\n}\n\n/** Read and verify a view URL on the artifact origin; null for anything forged, expired or malformed. */\nexport async function readCloudArtifactViewUrl(\n url: URL,\n options: { secret: string; now?: () => number },\n): Promise<CloudArtifactView | null> {\n const [, artifactId, segment, token, ...rest] = url.pathname.split('/')\n if (!artifactId || !ID.test(artifactId) || !segment || !token) return null\n if (segment !== 'm' && !ID.test(segment)) return null\n const valid = await verifyExpiringCapabilityToken(viewSubject(artifactId, segment), token, {\n secret: options.secret,\n prefix: VIEW_PREFIX,\n now: options.now,\n })\n if (!valid) return null\n let path: string\n try {\n path = rest.map(decodeURIComponent).join('/')\n } catch {\n return null\n }\n if (path !== '' && !isCloudArtifactPath(path)) return null\n return { artifactId, grant: segment === 'm' ? { type: 'member' } : { type: 'share', shareId: segment }, path }\n}\n\nfunction shareSubject(shareId: string): string {\n return `cloud-artifact-share:${shareId}`\n}\n\n/**\n * The secret part of a public link: `<shareId>.<HMAC>`. It is derived, so the\n * product stores only the share id and can show the link again; revoking the\n * share row turns the link off at once.\n */\nexport async function createCloudArtifactShareToken(shareId: string, secret: string): Promise<string> {\n if (!ID.test(shareId)) throw new Error('Invalid cloud artifact share id')\n if (!secret.trim()) throw new Error('Cloud artifact links need a signing secret')\n return `${shareId}.${await hmacSha256Base64Url(shareSubject(shareId), secret.trim())}`\n}\n\n/** The share id a public link names, or null when its signature does not verify. */\nexport async function readCloudArtifactShareToken(token: string, secret: string): Promise<string | null> {\n const [shareId, signature, extra] = token.split('.')\n if (extra !== undefined || !shareId || !signature || !ID.test(shareId) || !secret.trim()) return null\n const expected = await hmacSha256Base64Url(shareSubject(shareId), secret.trim())\n return constantTimeEqual(signature, expected) ? shareId : null\n}\n","/**\n * The artifact origin: the only place cloud artifact HTML and scripts run.\n *\n * Mount it on a host of its own on a different site from the product (for\n * GTM, `gtm-artifacts.tangle.sh` against `gtm.tangle.tools`), so a page an\n * agent wrote never shares cookies, storage or same-site requests with a\n * signed-in product session. Every response also carries a CSP `sandbox`\n * without `allow-same-origin`, so each document runs in an opaque origin and\n * one artifact cannot read another's storage.\n *\n * A request names `/<artifactId>/<grant>/<token>/<path>`. The token is a\n * short-lived capability minted by the product after it authorized the viewer;\n * `resolve` returns the record only while that grant is still live, so a\n * revoked share stops serving on its next request.\n *\n * Files are streamed from the object store. For a `worker` artifact, a request\n * that matches no file runs `_worker.js` in a Worker Loader isolate with\n * `globalOutbound: null` (no network), CPU and subrequest limits, and only the\n * bindings `workerEnv` grants. Its response headers are replaced with this\n * origin's, so the isolate cannot loosen them.\n */\nimport type { ObjectStore } from '../object-store'\nimport {\n CLOUD_ARTIFACT_WORKER_MODULE,\n cloudArtifactObjectKey,\n readCloudArtifactViewUrl,\n type CloudArtifactGrant,\n type CloudArtifactRecord,\n} from './cloud-artifacts'\n\n/** Script and style CDNs a generated page may load. Everything else is local or inline. */\nexport const CLOUD_ARTIFACT_SCRIPT_HOSTS = [\n 'https://cdn.jsdelivr.net',\n 'https://cdnjs.cloudflare.com',\n 'https://unpkg.com',\n] as const\n\n/** The iframe sandbox for an artifact frame; never add `allow-same-origin`. */\nexport const CLOUD_ARTIFACT_FRAME_SANDBOX =\n 'allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads'\n\n/** The Content-Security-Policy every artifact response carries. */\nexport function cloudArtifactContentSecurityPolicy(input: { origin: string; frameAncestors: readonly string[] }): string {\n const self = new URL(input.origin).origin\n const cdns = CLOUD_ARTIFACT_SCRIPT_HOSTS.join(' ')\n return [\n \"default-src 'none'\",\n `script-src ${self} 'unsafe-inline' 'unsafe-eval' blob: ${cdns}`,\n `style-src ${self} 'unsafe-inline' https://fonts.googleapis.com ${cdns}`,\n `img-src ${self} data: blob: https:`,\n `font-src ${self} data: https:`,\n `media-src ${self} data: blob: https:`,\n `connect-src ${self}`,\n 'frame-src https:',\n 'worker-src blob:',\n `form-action ${self}`,\n \"base-uri 'none'\",\n `frame-ancestors ${input.frameAncestors.length > 0 ? input.frameAncestors.join(' ') : \"'none'\"}`,\n `sandbox ${CLOUD_ARTIFACT_FRAME_SANDBOX}`,\n ].join('; ')\n}\n\n/** The code shape Worker Loader's `get` callback returns (Cloudflare `WorkerCode`). */\nexport interface CloudArtifactWorkerCode {\n compatibilityDate: string\n compatibilityFlags?: string[]\n mainModule: string\n modules: Record<string, string | { js: string } | { json: unknown } | { text: string }>\n env?: Record<string, unknown>\n globalOutbound: null\n limits?: { cpuMs?: number; subRequests?: number }\n}\n\n/** Structural `env.LOADER` (a `worker_loaders` binding), so workers-types stays out of the public types. */\nexport interface CloudArtifactWorkerLoader {\n get(id: string, load: () => Promise<CloudArtifactWorkerCode>): {\n getEntrypoint(name?: string | null, options?: { limits?: { cpuMs?: number; subRequests?: number } }): {\n fetch(request: Request): Promise<Response>\n }\n }\n}\n\nexport interface CloudArtifactOriginOptions {\n /** This origin, such as `https://gtm-artifacts.tangle.sh`. Requests for another host get 404. */\n origin: string\n /** Product origins allowed to frame an artifact (CSP `frame-ancestors`). */\n frameAncestors: readonly string[]\n /** The secret the product signs view links with. */\n secret: string\n store: ObjectStore\n /**\n * The artifact's current record for a verified link, or null when it no\n * longer exists or the grant is no longer live (a revoked share).\n */\n resolve(view: { artifactId: string; grant: CloudArtifactGrant }): Promise<CloudArtifactRecord | null>\n /** The `worker_loaders` binding. Without it a `worker` artifact serves only its files. */\n loader?: CloudArtifactWorkerLoader\n /** Bindings granted to a `worker` artifact's isolate; nothing else reaches it. */\n workerEnv?(record: CloudArtifactRecord, grant: CloudArtifactGrant): Record<string, unknown>\n workerLimits?: { cpuMs: number; subRequests: number }\n /** Compatibility date for isolates. */\n workerCompatibilityDate?: string\n now?: () => number\n}\n\nconst DEFAULT_WORKER_LIMITS = { cpuMs: 50, subRequests: 20 }\nconst MAX_WORKER_SOURCE_BYTES = 4 * 1024 * 1024\nconst FORWARDED_REQUEST_HEADERS = ['accept', 'accept-language', 'content-type', 'if-none-match', 'range']\nconst ROBOTS = 'User-agent: *\\nDisallow: /\\n'\n\nfunction plain(status: number, text: string, headers: Headers): Response {\n headers.set('Content-Type', 'text/plain; charset=utf-8')\n headers.set('Cache-Control', 'no-store')\n return new Response(text, { status, headers })\n}\n\n/** Serve cloud artifacts on their own origin. */\nexport function createCloudArtifactOrigin(options: CloudArtifactOriginOptions): (request: Request) => Promise<Response> {\n const origin = new URL(options.origin).origin\n if (!/^https:\\/\\//.test(origin) && !/^http:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$/.test(origin)) {\n throw new Error('The cloud artifact origin must be HTTPS')\n }\n const ancestors = options.frameAncestors.map((value) => new URL(value).origin)\n if (ancestors.includes(origin)) {\n throw new Error('The cloud artifact origin must not be a product origin')\n }\n if (!options.secret.trim()) throw new Error('The cloud artifact origin needs the link signing secret')\n const csp = cloudArtifactContentSecurityPolicy({ origin, frameAncestors: ancestors })\n const limits = options.workerLimits ?? DEFAULT_WORKER_LIMITS\n\n const baseHeaders = (): Headers => new Headers({\n 'Content-Security-Policy': csp,\n 'X-Content-Type-Options': 'nosniff',\n // The view token is in the URL; never send it to a CDN or a linked site.\n 'Referrer-Policy': 'no-referrer',\n 'Permissions-Policy': 'camera=(), microphone=(), geolocation=(), payment=(), usb=(), serial=(), bluetooth=()',\n 'Cross-Origin-Resource-Policy': 'cross-origin',\n // Opaque-origin documents fetch their own files cross-origin; the URL token is the capability.\n 'Access-Control-Allow-Origin': '*',\n 'X-Robots-Tag': 'noindex, nofollow',\n })\n\n async function runWorker(\n request: Request,\n record: CloudArtifactRecord,\n grant: CloudArtifactGrant,\n path: string,\n ): Promise<Response> {\n const loader = options.loader\n if (!loader) return plain(404, 'Not found', baseHeaders())\n const version = record.version\n const worker = loader.get(`${record.id}:${version.id}`, async () => {\n const modules: CloudArtifactWorkerCode['modules'] = {}\n let bytes = 0\n for (const file of version.files) {\n const type = file.contentType.split(';')[0]\n if (type !== 'text/javascript' && type !== 'application/json') continue\n bytes += file.size\n if (bytes > MAX_WORKER_SOURCE_BYTES) throw new Error('Artifact worker source is too large')\n const object = await options.store.get(cloudArtifactObjectKey(record.workspaceId, file.sha256))\n if (!object) throw new Error(`Artifact file ${file.path} is missing from storage`)\n const text = await new Response(object.stream()).text()\n modules[file.path] = type === 'application/json' ? { json: JSON.parse(text) } : { js: text }\n }\n return {\n compatibilityDate: options.workerCompatibilityDate ?? '2026-10-01',\n mainModule: CLOUD_ARTIFACT_WORKER_MODULE,\n modules,\n env: options.workerEnv?.(record, grant) ?? {},\n globalOutbound: null,\n limits,\n }\n })\n const url = new URL(request.url)\n const headers = new Headers()\n for (const name of FORWARDED_REQUEST_HEADERS) {\n const value = request.headers.get(name)\n if (value !== null) headers.set(name, value)\n }\n const hasBody = request.method !== 'GET' && request.method !== 'HEAD'\n const inner = new Request(`https://artifact.invalid/${path}${url.search}`, {\n method: request.method,\n headers,\n body: hasBody ? request.body : undefined,\n redirect: 'manual',\n })\n let response: Response\n try {\n response = await worker.getEntrypoint(null, { limits }).fetch(inner)\n } catch {\n return plain(502, \"This artifact's server code failed.\", baseHeaders())\n }\n const out = baseHeaders()\n const type = response.headers.get('Content-Type')\n if (type) out.set('Content-Type', type)\n out.set('Cache-Control', 'no-store')\n return new Response(request.method === 'HEAD' ? null : response.body, { status: response.status, headers: out })\n }\n\n return async function serve(request: Request): Promise<Response> {\n const url = new URL(request.url)\n if (url.origin !== origin) return plain(404, 'Not found', baseHeaders())\n if (url.pathname === '/robots.txt') return plain(200, ROBOTS, baseHeaders())\n if (request.method === 'OPTIONS') {\n const headers = baseHeaders()\n headers.set('Access-Control-Allow-Methods', 'GET, HEAD, POST, PUT, PATCH, DELETE')\n headers.set('Access-Control-Allow-Headers', 'Content-Type')\n headers.set('Access-Control-Max-Age', '600')\n return new Response(null, { status: 204, headers })\n }\n const view = await readCloudArtifactViewUrl(url, { secret: options.secret, now: options.now })\n if (!view) return plain(404, 'This link is invalid or has expired. Open the artifact again from its page.', baseHeaders())\n const record = await options.resolve({ artifactId: view.artifactId, grant: view.grant })\n if (!record || record.id !== view.artifactId) return plain(404, 'This artifact is no longer shared.', baseHeaders())\n const path = view.path || record.version.entry\n const file = record.version.kind === 'openui' ? undefined : record.version.files.find((item) => item.path === path)\n const readable = request.method === 'GET' || request.method === 'HEAD'\n if (file && readable && path !== CLOUD_ARTIFACT_WORKER_MODULE) {\n const object = await options.store.get(cloudArtifactObjectKey(record.workspaceId, file.sha256))\n if (!object) return plain(404, 'Not found', baseHeaders())\n const headers = baseHeaders()\n headers.set('Content-Type', file.contentType)\n headers.set('ETag', `\"${file.sha256}\"`)\n headers.set('Cache-Control', 'no-store')\n if (request.headers.get('If-None-Match') === `\"${file.sha256}\"`) {\n await object.stream().cancel()\n return new Response(null, { status: 304, headers })\n }\n headers.set('Content-Length', String(object.size))\n if (request.method === 'HEAD') await object.stream().cancel()\n return new Response(request.method === 'HEAD' ? null : object.stream(), { status: 200, headers })\n }\n if (record.version.kind === 'worker') return runWorker(request, record, view.grant, path)\n return plain(404, 'Not found', baseHeaders())\n }\n}\n"],"mappings":";;;;;;;;;;AAyDO,IAAM,4BAA2C;AAGjD,IAAM,+BAA+B;AACrC,IAAM,2BAA2B;AACjC,IAAM,gCAAgC,KAAK,OAAO;AAClD,IAAM,2BAA2B,KAAK,OAAO;AAE7C,IAAM,6BAA6B,KAAK,KAAK;AAW7C,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5C,YAAqB,MAA8B,SAAiB;AAClE,UAAM,OAAO;AADM;AAEnB,SAAK,OAAO;AAAA,EACd;AAAA,EAHqB;AAIvB;AAEA,IAAM,QAAgC;AAAA,EACpC,MAAM;AAAA,EACN,KAAK;AAAA,EACL,KAAK;AAAA,EACL,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,KAAK;AAAA,EACL,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,MAAM;AAAA,EACN,MAAM;AAAA,EACN,KAAK;AAAA,EACL,MAAM;AAAA,EACN,OAAO;AAAA,EACP,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AACR;AAGO,SAAS,yBAAyB,MAA6B;AACpE,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,MAAI,MAAM,KAAK,MAAM,KAAK,YAAY,GAAG,EAAG,QAAO;AACnD,SAAO,MAAM,KAAK,MAAM,MAAM,CAAC,EAAE,YAAY,CAAC,KAAK;AACrD;AAEA,IAAM,UAAU;AAGT,SAAS,oBAAoB,MAAuB;AACzD,MAAI,OAAO,SAAS,YAAY,KAAK,SAAS,IAAK,QAAO;AAC1D,SAAO,KAAK,MAAM,GAAG,EAAE,MAAM,CAAC,YAAY,QAAQ,KAAK,OAAO,KAAK,YAAY,OAAO,YAAY,QAC7F,QAAQ,KAAK,MAAM,OAAO;AACjC;AAEA,SAAS,UAAU,aAA6B;AAC9C,SAAO,YAAY,MAAM,GAAG,EAAE,CAAC,EAAG,KAAK,EAAE,YAAY;AACvD;AAEA,SAAS,IAAI,OAA4B;AACvC,SAAO,CAAC,GAAG,IAAI,WAAW,KAAK,CAAC,EAAE,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC7F;AAEA,eAAe,OAAO,OAAoC;AACxD,SAAO,IAAI,MAAM,OAAO,OAAO,OAAO,WAAW,KAAgC,CAAC;AACpF;AAkBA,eAAsB,qBAAqB,OAOR;AACjC,QAAM,QAAQ,oBAAI,IAA+B;AACjD,QAAM,SAAS,oBAAI,IAAwB;AAC3C,MAAI,QAAQ;AACZ,aAAW,QAAQ,MAAM,OAAO;AAC9B,QAAI,CAAC,oBAAoB,KAAK,IAAI,EAAG,OAAM,IAAI,mBAAmB,gBAAgB,0BAA0B,KAAK,IAAI,EAAE;AACvH,QAAI,MAAM,IAAI,KAAK,IAAI,EAAG,OAAM,IAAI,mBAAmB,kBAAkB,4BAA4B,KAAK,IAAI,EAAE;AAChH,UAAM,cAAc,yBAAyB,KAAK,IAAI;AACtD,QAAI,CAAC,YAAa,OAAM,IAAI,mBAAmB,oBAAoB,gCAAgC,KAAK,IAAI,EAAE;AAC9G,UAAM,QAAQ,OAAO,KAAK,SAAS,WAAW,IAAI,YAAY,EAAE,OAAO,KAAK,IAAI,IAAI,KAAK;AACzF,QAAI,MAAM,aAAa,+BAA+B;AACpD,YAAM,IAAI,mBAAmB,aAAa,GAAG,KAAK,IAAI,mBAAmB,6BAA6B,QAAQ;AAAA,IAChH;AACA,aAAS,MAAM;AACf,UAAM,SAAS,MAAM,OAAO,KAAK;AACjC,WAAO,IAAI,QAAQ,KAAK;AACxB,UAAM,IAAI,KAAK,MAAM,EAAE,MAAM,KAAK,MAAM,aAAa,MAAM,MAAM,YAAY,QAAQ,OAAO,CAAC;AAAA,EAC/F;AACA,aAAW,QAAQ,MAAM,SAAS,CAAC,GAAG;AACpC,QAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,oBAAoB,KAAK,IAAI,KAAK,CAAC,iBAAiB,KAAK,KAAK,MAAM,EAAG;AACpG,aAAS,KAAK;AACd,UAAM,IAAI,KAAK,MAAM,IAAI;AAAA,EAC3B;AACA,MAAI,MAAM,OAAO,0BAA0B;AACzC,UAAM,IAAI,mBAAmB,kBAAkB,6BAA6B,wBAAwB,QAAQ;AAAA,EAC9G;AACA,MAAI,QAAQ,0BAA0B;AACpC,UAAM,IAAI,mBAAmB,aAAa,6BAA6B,wBAAwB,QAAQ;AAAA,EACzG;AACA,QAAM,QAAQ,MAAM,IAAI,MAAM,KAAK;AACnC,MAAI,CAAC,MAAO,OAAM,IAAI,mBAAmB,iBAAiB,qBAAqB,MAAM,KAAK,EAAE;AAC5F,QAAM,OAA0B,MAAM,SAAS,WAAW,MAAM,IAAI,4BAA4B,IAAI,WAAW;AAC/G,MAAI,UAAU,MAAM,WAAW,OAAO,SAAS,WAAW,qBAAqB,cAAc;AAC3F,UAAM,IAAI,mBAAmB,iBAAiB,KAAK,IAAI,yBAAyB,MAAM,KAAK,EAAE;AAAA,EAC/F;AACA,QAAM,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAO,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI,CAAE;AAClG,QAAM,WAAW,KAAK,UAAU,CAAC,MAAM,MAAM,OAAO,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,KAAK,aAAa,KAAK,MAAM,CAAC,CAAC,CAAC;AACrH,QAAM,MAAM,MAAM,OAAO,IAAI,YAAY,EAAE,OAAO,QAAQ,CAAC,GAAG,MAAM,GAAG,EAAE;AACzE,SAAO;AAAA,IACL,SAAS,EAAE,IAAI,MAAM,OAAO,MAAM,OAAO,OAAO,QAAQ,WAAW,MAAM,cAAa,oBAAI,KAAK,GAAE,YAAY,EAAE;AAAA,IAC/G;AAAA,EACF;AACF;AAGO,SAAS,uBAAuB,aAAqB,WAA2B;AACrF,MAAI,CAAC,yBAAyB,KAAK,WAAW,KAAK,CAAC,iBAAiB,KAAK,SAAS,GAAG;AACpF,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AACA,SAAO,mBAAmB,WAAW,IAAI,SAAS;AACpD;AAGO,SAAS,6BAA6B,aAA6B;AACxE,MAAI,CAAC,yBAAyB,KAAK,WAAW,EAAG,OAAM,IAAI,MAAM,sBAAsB;AACvF,SAAO,mBAAmB,WAAW;AACvC;AAGA,eAAsB,mBACpB,OACA,aACA,UAC+B;AAC/B,aAAW,QAAQ,SAAS,QAAQ,OAAO;AACzC,UAAM,QAAQ,SAAS,OAAO,IAAI,KAAK,MAAM;AAC7C,QAAI,CAAC,MAAO;AACZ,UAAM,MAAM,uBAAuB,aAAa,KAAK,MAAM;AAC3D,QAAI,MAAM,MAAM,KAAK,GAAG,EAAG;AAC3B,UAAM,MAAM,IAAI,KAAK,OAAO,EAAE,aAAa,KAAK,aAAa,eAAe,MAAM,WAAW,CAAC;AAAA,EAChG;AACA,SAAO,SAAS;AAClB;AAIA,IAAM,sBAAsB;AAC5B,IAAM,SAAS;AACf,IAAM,UAAU;AAChB,IAAM,aAAa;AACnB,IAAM,YAAY;AAClB,IAAM,WAAW;AAEjB,SAAS,QAAQ,MAAsB;AACrC,QAAM,QAAQ,KAAK,YAAY,GAAG;AAClC,SAAO,QAAQ,IAAI,KAAK,KAAK,MAAM,GAAG,KAAK;AAC7C;AAGO,SAAS,8BAA8B,WAAmB,cAAqC;AACpG,QAAM,MAAM,UAAU,KAAK;AAC3B,MAAI,CAAC,OAAO,IAAI,WAAW,GAAG,KAAK,IAAI,WAAW,GAAG,KAAK,4BAA4B,KAAK,GAAG,EAAG,QAAO;AACxG,MAAI,OAAO,IAAI,MAAM,MAAM,EAAE,CAAC;AAC9B,MAAI;AACF,WAAO,mBAAmB,IAAI;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,QAAQ,YAAY,IAAI,QAAQ,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;AAC1E,aAAW,WAAW,KAAK,MAAM,GAAG,GAAG;AACrC,QAAI,YAAY,MAAM,YAAY,IAAK;AACvC,QAAI,YAAY,MAAM;AACpB,UAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,YAAM,IAAI;AAAA,IACZ,MAAO,OAAM,KAAK,OAAO;AAAA,EAC3B;AACA,QAAM,WAAW,MAAM,KAAK,GAAG;AAC/B,SAAO,YAAY,oBAAoB,QAAQ,KAAK,yBAAyB,QAAQ,IAAI,WAAW;AACtG;AAEA,SAAS,QAAQ,SAAiB,MAAwB;AACxD,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,KAAK,SAAS,OAAO,GAAG;AAC1C,UAAM,QAAQ,MAAM,MAAM,CAAC,EAAE,KAAK,CAAC,UAAU,UAAU,MAAS;AAChE,QAAI,MAAO,OAAM,KAAK,KAAK;AAAA,EAC7B;AACA,SAAO;AACT;AAGO,SAAS,wBAAwB,MAAc,cAAgC;AACpF,QAAM,OAAO,UAAU,yBAAyB,YAAY,KAAK,EAAE;AACnE,QAAM,aAAuB,CAAC;AAC9B,MAAI,SAAS,aAAa;AACxB,eAAW,KAAK,GAAG,QAAQ,qBAAqB,IAAI,CAAC;AACrD,eAAW,OAAO,QAAQ,QAAQ,IAAI,GAAG;AACvC,iBAAW,KAAK,GAAG,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,cAAc,UAAU,KAAK,EAAE,MAAM,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC;AAAA,IAC9F;AACA,eAAW,KAAK,GAAG,QAAQ,SAAS,IAAI,GAAG,GAAG,QAAQ,YAAY,IAAI,CAAC;AACvE,eAAW,UAAU,KAAK,SAAS,uCAAuC,GAAG;AAC3E,iBAAW,KAAK,GAAG,QAAQ,WAAW,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,QAAQ,UAAU,OAAO,CAAC,KAAK,EAAE,CAAC;AAAA,IAC/F;AAAA,EACF,WAAW,SAAS,YAAY;AAC9B,eAAW,KAAK,GAAG,QAAQ,SAAS,IAAI,GAAG,GAAG,QAAQ,YAAY,IAAI,CAAC;AAAA,EACzE,WAAW,SAAS,mBAAmB;AACrC,eAAW,KAAK,GAAG,QAAQ,WAAW,IAAI,GAAG,GAAG,QAAQ,UAAU,IAAI,CAAC;AAAA,EACzE;AACA,QAAM,WAAW,oBAAI,IAAY;AACjC,aAAW,aAAa,YAAY;AAClC,UAAM,OAAO,8BAA8B,WAAW,YAAY;AAClE,QAAI,QAAQ,SAAS,aAAc,UAAS,IAAI,IAAI;AAAA,EACtD;AACA,SAAO,CAAC,GAAG,QAAQ;AACrB;AAQA,eAAsB,2BAA2B,OAKmB;AAClE,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,QAAQ,oBAAI,IAAwB;AAC1C,QAAM,UAAoB,CAAC;AAC3B,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,QAAQ;AACZ,MAAI,WAAW,CAAC,MAAM,OAAO,4BAA4B,EAAE,OAAO,CAAC,SAAS,oBAAoB,IAAI,CAAC;AACrG,QAAM,UAAU,IAAI,YAAY;AAChC,SAAO,SAAS,SAAS,KAAK,MAAM,OAAO,UAAU;AACnD,UAAM,QAAQ,SAAS,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,MAAM,IAAI;AACvF,UAAM,QAAQ,CAAC,SAAS,KAAK,IAAI,IAAI,CAAC;AACtC,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,OAAO,MAAM,MAAM,SAAS,KAAK;AACvC,UAAM,OAAiB,CAAC;AACxB,eAAW,QAAQ,OAAO;AACxB,YAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B,UAAI,CAAC,OAAO;AACV,YAAI,SAAS,6BAA8B,SAAQ,KAAK,IAAI;AAC5D;AAAA,MACF;AACA,UAAI,MAAM,aAAa,iCAAiC,QAAQ,MAAM,aAAa,UAAU;AAC3F,gBAAQ,KAAK,IAAI;AACjB;AAAA,MACF;AACA,eAAS,MAAM;AACf,YAAM,IAAI,MAAM,KAAK;AACrB,YAAM,OAAO,UAAU,yBAAyB,IAAI,KAAK,EAAE;AAC3D,UAAI,SAAS,eAAe,SAAS,cAAc,SAAS,mBAAmB;AAC7E,aAAK,KAAK,GAAG,wBAAwB,QAAQ,OAAO,KAAK,GAAG,IAAI,CAAC;AAAA,MACnE;AAAA,IACF;AACA,eAAW;AAAA,EACb;AACA,SAAO,EAAE,OAAO,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,OAAO,EAAE,MAAM,KAAK,EAAE,GAAG,QAAQ;AAC9E;AAIA,IAAM,KAAK;AACX,IAAM,cAAc;AAEpB,SAAS,aAAa,OAAmC;AACvD,MAAI,MAAM,SAAS,SAAU,QAAO;AACpC,MAAI,CAAC,GAAG,KAAK,MAAM,OAAO,EAAG,OAAM,IAAI,MAAM,iCAAiC;AAC9E,SAAO,MAAM;AACf;AAEA,SAAS,YAAY,YAAoB,SAAyB;AAChE,SAAO,uBAAuB,UAAU,IAAI,OAAO;AACrD;AAGO,SAAS,wBAAgC;AAC9C,SAAO,IAAI,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC,EAAE,MAAqB;AAC7E;AAEA,SAAS,WAAW,MAAsB;AACxC,SAAO,KAAK,MAAM,GAAG,EAAE,IAAI,kBAAkB,EAAE,KAAK,GAAG;AACzD;AAOA,eAAsB,2BAA2B,OAQ7B;AAClB,MAAI,CAAC,GAAG,KAAK,MAAM,UAAU,EAAG,OAAM,IAAI,MAAM,2BAA2B;AAC3E,MAAI,CAAC,oBAAoB,MAAM,IAAI,EAAG,OAAM,IAAI,MAAM,6BAA6B;AACnF,QAAM,UAAU,aAAa,MAAM,KAAK;AACxC,QAAM,QAAQ,MAAM,8BAA8B,YAAY,MAAM,YAAY,OAAO,GAAG;AAAA,IACxF,QAAQ,MAAM;AAAA,IACd,QAAQ;AAAA,IACR,aAAa,MAAM,SAAS;AAAA,IAC5B,KAAK,MAAM;AAAA,EACb,CAAC;AACD,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,4CAA4C;AACxE,SAAO,GAAG,IAAI,IAAI,MAAM,MAAM,EAAE,MAAM,IAAI,MAAM,UAAU,IAAI,OAAO,IAAI,KAAK,IAAI,WAAW,MAAM,IAAI,CAAC;AAC1G;AAUA,eAAsB,yBACpB,KACA,SACmC;AACnC,QAAM,CAAC,EAAE,YAAY,SAAS,OAAO,GAAG,IAAI,IAAI,IAAI,SAAS,MAAM,GAAG;AACtE,MAAI,CAAC,cAAc,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC,WAAW,CAAC,MAAO,QAAO;AACtE,MAAI,YAAY,OAAO,CAAC,GAAG,KAAK,OAAO,EAAG,QAAO;AACjD,QAAM,QAAQ,MAAM,8BAA8B,YAAY,YAAY,OAAO,GAAG,OAAO;AAAA,IACzF,QAAQ,QAAQ;AAAA,IAChB,QAAQ;AAAA,IACR,KAAK,QAAQ;AAAA,EACf,CAAC;AACD,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,IAAI,kBAAkB,EAAE,KAAK,GAAG;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,SAAS,MAAM,CAAC,oBAAoB,IAAI,EAAG,QAAO;AACtD,SAAO,EAAE,YAAY,OAAO,YAAY,MAAM,EAAE,MAAM,SAAS,IAAI,EAAE,MAAM,SAAS,SAAS,QAAQ,GAAG,KAAK;AAC/G;AAEA,SAAS,aAAa,SAAyB;AAC7C,SAAO,wBAAwB,OAAO;AACxC;AAOA,eAAsB,8BAA8B,SAAiB,QAAiC;AACpG,MAAI,CAAC,GAAG,KAAK,OAAO,EAAG,OAAM,IAAI,MAAM,iCAAiC;AACxE,MAAI,CAAC,OAAO,KAAK,EAAG,OAAM,IAAI,MAAM,4CAA4C;AAChF,SAAO,GAAG,OAAO,IAAI,MAAM,oBAAoB,aAAa,OAAO,GAAG,OAAO,KAAK,CAAC,CAAC;AACtF;AAGA,eAAsB,4BAA4B,OAAe,QAAwC;AACvG,QAAM,CAAC,SAAS,WAAW,KAAK,IAAI,MAAM,MAAM,GAAG;AACnD,MAAI,UAAU,UAAa,CAAC,WAAW,CAAC,aAAa,CAAC,GAAG,KAAK,OAAO,KAAK,CAAC,OAAO,KAAK,EAAG,QAAO;AACjG,QAAM,WAAW,MAAM,oBAAoB,aAAa,OAAO,GAAG,OAAO,KAAK,CAAC;AAC/E,SAAO,kBAAkB,WAAW,QAAQ,IAAI,UAAU;AAC5D;;;AChbO,IAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,+BACX;AAGK,SAAS,mCAAmC,OAAsE;AACvH,QAAM,OAAO,IAAI,IAAI,MAAM,MAAM,EAAE;AACnC,QAAM,OAAO,4BAA4B,KAAK,GAAG;AACjD,SAAO;AAAA,IACL;AAAA,IACA,cAAc,IAAI,wCAAwC,IAAI;AAAA,IAC9D,aAAa,IAAI,iDAAiD,IAAI;AAAA,IACtE,WAAW,IAAI;AAAA,IACf,YAAY,IAAI;AAAA,IAChB,aAAa,IAAI;AAAA,IACjB,eAAe,IAAI;AAAA,IACnB;AAAA,IACA;AAAA,IACA,eAAe,IAAI;AAAA,IACnB;AAAA,IACA,mBAAmB,MAAM,eAAe,SAAS,IAAI,MAAM,eAAe,KAAK,GAAG,IAAI,QAAQ;AAAA,IAC9F,WAAW,4BAA4B;AAAA,EACzC,EAAE,KAAK,IAAI;AACb;AA6CA,IAAM,wBAAwB,EAAE,OAAO,IAAI,aAAa,GAAG;AAC3D,IAAM,0BAA0B,IAAI,OAAO;AAC3C,IAAM,4BAA4B,CAAC,UAAU,mBAAmB,gBAAgB,iBAAiB,OAAO;AACxG,IAAM,SAAS;AAEf,SAAS,MAAM,QAAgB,MAAc,SAA4B;AACvE,UAAQ,IAAI,gBAAgB,2BAA2B;AACvD,UAAQ,IAAI,iBAAiB,UAAU;AACvC,SAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,QAAQ,CAAC;AAC/C;AAGO,SAAS,0BAA0B,SAA8E;AACtH,QAAM,SAAS,IAAI,IAAI,QAAQ,MAAM,EAAE;AACvC,MAAI,CAAC,cAAc,KAAK,MAAM,KAAK,CAAC,6CAA6C,KAAK,MAAM,GAAG;AAC7F,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AACA,QAAM,YAAY,QAAQ,eAAe,IAAI,CAAC,UAAU,IAAI,IAAI,KAAK,EAAE,MAAM;AAC7E,MAAI,UAAU,SAAS,MAAM,GAAG;AAC9B,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AACA,MAAI,CAAC,QAAQ,OAAO,KAAK,EAAG,OAAM,IAAI,MAAM,yDAAyD;AACrG,QAAM,MAAM,mCAAmC,EAAE,QAAQ,gBAAgB,UAAU,CAAC;AACpF,QAAM,SAAS,QAAQ,gBAAgB;AAEvC,QAAM,cAAc,MAAe,IAAI,QAAQ;AAAA,IAC7C,2BAA2B;AAAA,IAC3B,0BAA0B;AAAA;AAAA,IAE1B,mBAAmB;AAAA,IACnB,sBAAsB;AAAA,IACtB,gCAAgC;AAAA;AAAA,IAEhC,+BAA+B;AAAA,IAC/B,gBAAgB;AAAA,EAClB,CAAC;AAED,iBAAe,UACb,SACA,QACA,OACA,MACmB;AACnB,UAAM,SAAS,QAAQ;AACvB,QAAI,CAAC,OAAQ,QAAO,MAAM,KAAK,aAAa,YAAY,CAAC;AACzD,UAAM,UAAU,OAAO;AACvB,UAAM,SAAS,OAAO,IAAI,GAAG,OAAO,EAAE,IAAI,QAAQ,EAAE,IAAI,YAAY;AAClE,YAAM,UAA8C,CAAC;AACrD,UAAI,QAAQ;AACZ,iBAAW,QAAQ,QAAQ,OAAO;AAChC,cAAMA,QAAO,KAAK,YAAY,MAAM,GAAG,EAAE,CAAC;AAC1C,YAAIA,UAAS,qBAAqBA,UAAS,mBAAoB;AAC/D,iBAAS,KAAK;AACd,YAAI,QAAQ,wBAAyB,OAAM,IAAI,MAAM,qCAAqC;AAC1F,cAAM,SAAS,MAAM,QAAQ,MAAM,IAAI,uBAAuB,OAAO,aAAa,KAAK,MAAM,CAAC;AAC9F,YAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,iBAAiB,KAAK,IAAI,0BAA0B;AACjF,cAAM,OAAO,MAAM,IAAI,SAAS,OAAO,OAAO,CAAC,EAAE,KAAK;AACtD,gBAAQ,KAAK,IAAI,IAAIA,UAAS,qBAAqB,EAAE,MAAM,KAAK,MAAM,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK;AAAA,MAC7F;AACA,aAAO;AAAA,QACL,mBAAmB,QAAQ,2BAA2B;AAAA,QACtD,YAAY;AAAA,QACZ;AAAA,QACA,KAAK,QAAQ,YAAY,QAAQ,KAAK,KAAK,CAAC;AAAA,QAC5C,gBAAgB;AAAA,QAChB;AAAA,MACF;AAAA,IACF,CAAC;AACD,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,UAAU,IAAI,QAAQ;AAC5B,eAAW,QAAQ,2BAA2B;AAC5C,YAAM,QAAQ,QAAQ,QAAQ,IAAI,IAAI;AACtC,UAAI,UAAU,KAAM,SAAQ,IAAI,MAAM,KAAK;AAAA,IAC7C;AACA,UAAM,UAAU,QAAQ,WAAW,SAAS,QAAQ,WAAW;AAC/D,UAAM,QAAQ,IAAI,QAAQ,4BAA4B,IAAI,GAAG,IAAI,MAAM,IAAI;AAAA,MACzE,QAAQ,QAAQ;AAAA,MAChB;AAAA,MACA,MAAM,UAAU,QAAQ,OAAO;AAAA,MAC/B,UAAU;AAAA,IACZ,CAAC;AACD,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,OAAO,cAAc,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK;AAAA,IACrE,QAAQ;AACN,aAAO,MAAM,KAAK,uCAAuC,YAAY,CAAC;AAAA,IACxE;AACA,UAAM,MAAM,YAAY;AACxB,UAAM,OAAO,SAAS,QAAQ,IAAI,cAAc;AAChD,QAAI,KAAM,KAAI,IAAI,gBAAgB,IAAI;AACtC,QAAI,IAAI,iBAAiB,UAAU;AACnC,WAAO,IAAI,SAAS,QAAQ,WAAW,SAAS,OAAO,SAAS,MAAM,EAAE,QAAQ,SAAS,QAAQ,SAAS,IAAI,CAAC;AAAA,EACjH;AAEA,SAAO,eAAe,MAAM,SAAqC;AAC/D,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAI,IAAI,WAAW,OAAQ,QAAO,MAAM,KAAK,aAAa,YAAY,CAAC;AACvE,QAAI,IAAI,aAAa,cAAe,QAAO,MAAM,KAAK,QAAQ,YAAY,CAAC;AAC3E,QAAI,QAAQ,WAAW,WAAW;AAChC,YAAM,UAAU,YAAY;AAC5B,cAAQ,IAAI,gCAAgC,qCAAqC;AACjF,cAAQ,IAAI,gCAAgC,cAAc;AAC1D,cAAQ,IAAI,0BAA0B,KAAK;AAC3C,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAAA,IACpD;AACA,UAAM,OAAO,MAAM,yBAAyB,KAAK,EAAE,QAAQ,QAAQ,QAAQ,KAAK,QAAQ,IAAI,CAAC;AAC7F,QAAI,CAAC,KAAM,QAAO,MAAM,KAAK,+EAA+E,YAAY,CAAC;AACzH,UAAM,SAAS,MAAM,QAAQ,QAAQ,EAAE,YAAY,KAAK,YAAY,OAAO,KAAK,MAAM,CAAC;AACvF,QAAI,CAAC,UAAU,OAAO,OAAO,KAAK,WAAY,QAAO,MAAM,KAAK,sCAAsC,YAAY,CAAC;AACnH,UAAM,OAAO,KAAK,QAAQ,OAAO,QAAQ;AACzC,UAAM,OAAO,OAAO,QAAQ,SAAS,WAAW,SAAY,OAAO,QAAQ,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI;AAClH,UAAM,WAAW,QAAQ,WAAW,SAAS,QAAQ,WAAW;AAChE,QAAI,QAAQ,YAAY,SAAS,8BAA8B;AAC7D,YAAM,SAAS,MAAM,QAAQ,MAAM,IAAI,uBAAuB,OAAO,aAAa,KAAK,MAAM,CAAC;AAC9F,UAAI,CAAC,OAAQ,QAAO,MAAM,KAAK,aAAa,YAAY,CAAC;AACzD,YAAM,UAAU,YAAY;AAC5B,cAAQ,IAAI,gBAAgB,KAAK,WAAW;AAC5C,cAAQ,IAAI,QAAQ,IAAI,KAAK,MAAM,GAAG;AACtC,cAAQ,IAAI,iBAAiB,UAAU;AACvC,UAAI,QAAQ,QAAQ,IAAI,eAAe,MAAM,IAAI,KAAK,MAAM,KAAK;AAC/D,cAAM,OAAO,OAAO,EAAE,OAAO;AAC7B,eAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAAA,MACpD;AACA,cAAQ,IAAI,kBAAkB,OAAO,OAAO,IAAI,CAAC;AACjD,UAAI,QAAQ,WAAW,OAAQ,OAAM,OAAO,OAAO,EAAE,OAAO;AAC5D,aAAO,IAAI,SAAS,QAAQ,WAAW,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAAA,IAClG;AACA,QAAI,OAAO,QAAQ,SAAS,SAAU,QAAO,UAAU,SAAS,QAAQ,KAAK,OAAO,IAAI;AACxF,WAAO,MAAM,KAAK,aAAa,YAAY,CAAC;AAAA,EAC9C;AACF;","names":["type"]}