buckyos 0.7.159 → 0.7.162

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.
@@ -0,0 +1,1248 @@
1
+ const AIWS_COMPONENT_FILE = "component.json";
2
+ const AIWS_COMPOSITION_FILE = "composition.json";
3
+ const AIWS_MANIFEST_FILE = "aiws-manifest.json";
4
+ const AIWS_BUILD_STATE_FILE = ".aiws-build.json";
5
+ const AIWS_MANIFEST_VERSION = 3;
6
+ const AIWS_HTML_API_VERSION = 3;
7
+ const AIWS_DECLARATIVE_API_VERSION = 1;
8
+ const AIWS_TEMPLATE_FORMAT = "aiws-template/1";
9
+ const AIWS_MAX_SOURCE_FILES = 64;
10
+ class AiwsSourceError extends Error {
11
+ constructor(diagnostics) {
12
+ super(diagnostics.filter((d) => d.severity === "error").map((d) => `${d.file ? `${d.file}: ` : ""}${d.message}`).join("; ") || "invalid control source");
13
+ this.name = "AiwsSourceError";
14
+ this.diagnostics = diagnostics;
15
+ }
16
+ }
17
+ const encoder = new TextEncoder();
18
+ const decoder = new TextDecoder();
19
+ function joinPath(dir, name) {
20
+ return `${dir.replace(/\/+$/, "")}/${name}`;
21
+ }
22
+ function folderName(title) {
23
+ const cleaned = title.replace(/[/\\\u0000-\u001f]/g, " ").replace(/^\.+/, "").trim();
24
+ return cleaned.slice(0, 80) || "Workspace";
25
+ }
26
+ function homeControlDir(owner, workspaceTitle, name) {
27
+ return `/home/${owner}/AI Workspace/${folderName(workspaceTitle)}/controls/${name}`;
28
+ }
29
+ function homeExportDir(owner, pkg) {
30
+ return `/home/${owner}/AI Workspace/exports/${pkg}`;
31
+ }
32
+ function sourceStateKey(defEntityId) {
33
+ return `aiws:src:${defEntityId}`;
34
+ }
35
+ async function sha256Hex(bytes) {
36
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
37
+ return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
38
+ }
39
+ async function aiwsContentDigest(files) {
40
+ const pairs = [];
41
+ for (const [name, bytes] of files)
42
+ pairs.push([name, await sha256Hex(bytes)]);
43
+ pairs.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
44
+ return `sha256:${await sha256Hex(encoder.encode(JSON.stringify(pairs)))}`;
45
+ }
46
+ function sourcesKey(files) {
47
+ return JSON.stringify([...files].sort((a, b) => a.name.localeCompare(b.name)).map((f) => [f.name, f.obj_id ?? f.etag ?? ""]));
48
+ }
49
+ const AIWS_MAX_INLINE_BYTES = 4 * 1024 * 1024;
50
+ function base64(bytes) {
51
+ let binary = "";
52
+ for (let i = 0; i < bytes.length; i += 32768)
53
+ binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
54
+ return btoa(binary);
55
+ }
56
+ async function packBuildFiles(files, upload, byRef = []) {
57
+ let total = files.reduce((n, f) => n + f.bytes.length, 0);
58
+ const uploaded = /* @__PURE__ */ new Map();
59
+ if (total > AIWS_MAX_INLINE_BYTES) {
60
+ if (!upload)
61
+ throw new AiwsSourceError([{ file: "", severity: "error", message: `a control's inline files are limited to ${AIWS_MAX_INLINE_BYTES} bytes per build` }]);
62
+ for (const f of [...files].sort((a, b) => b.bytes.length - a.bytes.length)) {
63
+ if (total <= AIWS_MAX_INLINE_BYTES)
64
+ break;
65
+ uploaded.set(f.name, await upload(f.name, f.bytes));
66
+ total -= f.bytes.length;
67
+ }
68
+ }
69
+ const out = [...byRef];
70
+ for (const f of files) {
71
+ const obj = uploaded.get(f.name);
72
+ out.push(obj ? { name: f.name, obj_id: obj } : { name: f.name, base64: base64(f.bytes) });
73
+ }
74
+ return out.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
75
+ }
76
+ async function buildFiles(nfsp, files, upload) {
77
+ var _a;
78
+ const byRef = [];
79
+ const inline = [];
80
+ for (const f of files) {
81
+ if ((_a = f.obj_id) == null ? void 0 : _a.startsWith("cyfile:")) {
82
+ byRef.push({ name: f.name, obj_id: f.obj_id });
83
+ continue;
84
+ }
85
+ if (!f.node_id)
86
+ throw new AiwsSourceError([{ file: f.name, severity: "error", message: "the file service did not name this file" }]);
87
+ inline.push({ name: f.name, bytes: new Uint8Array(await (await nfsp.readFile(f.node_id)).arrayBuffer()) });
88
+ }
89
+ return packBuildFiles(inline, upload, byRef);
90
+ }
91
+ const COMPONENT_KEYS = ["manifest_version", "ident", "kind", "title", "description", "host_api", "accepts", "allow_no_source", "default_size", "config_schema", "bindings", "entry", "assets", "catalog", "agent", "deps", "events"];
92
+ const SCHEMA_KEYWORDS = ["type", "enum", "default", "required", "title", "description", "minimum", "maximum", "minLength", "maxLength", "properties", "items"];
93
+ const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
94
+ const DID = /^did:[a-z0-9]+:[^/#\s]+$/;
95
+ const BINDING = /^[a-z][a-z0-9_]{0,31}$/;
96
+ const IDENT = /^did:[a-z0-9]+:[^/#\s]+#[a-z0-9][a-z0-9._-]{0,63}$/;
97
+ const V2_HINT = "needs manifest_version 2 (raise it in component.json)";
98
+ const V3_HINT = "needs manifest_version 3 (raise it in component.json)";
99
+ const EVENT_PARAM_TYPES = ["string", "number", "boolean", "object", "entity"];
100
+ const ITEM_KINDS = ["text", "value", "metric", "bar", "list", "badge"];
101
+ const ITEM_KEYS = ["kind", "text", "label", "bind", "by", "format", "limit"];
102
+ const ACTION_COMMANDS = ["open_source", "activate_editor", "open_definition"];
103
+ const INSPECTOR_KINDS = ["text", "number", "select", "boolean", "color"];
104
+ function isRecord(v) {
105
+ return !!v && typeof v === "object" && !Array.isArray(v);
106
+ }
107
+ function isSourcePath(name) {
108
+ return name.length > 0 && encoder.encode(name).length <= 255 && !name.includes("\0") && name.split("/").every((seg) => seg.length > 0 && !seg.startsWith("."));
109
+ }
110
+ function configProps(configSchema) {
111
+ const props = isRecord(configSchema) ? configSchema.properties : void 0;
112
+ return isRecord(props) ? props : {};
113
+ }
114
+ function configPlaceholders(text, at, props, error) {
115
+ for (const m of text.matchAll(/\{\{([^}]*)\}\}/g)) {
116
+ const inner = m[1].trim();
117
+ if (!inner.startsWith("config."))
118
+ error(`${at}: only {{config.<key>}} may be used here`);
119
+ else if (!(inner.slice(7) in props))
120
+ error(`${at}: {{${inner}}} names no property of config_schema`);
121
+ }
122
+ }
123
+ function eventProblems(v, out) {
124
+ if (!isRecord(v)) {
125
+ out.push("events must be an object { <name>: { title?, description?, params? } }");
126
+ return;
127
+ }
128
+ if (Object.keys(v).length > 16)
129
+ out.push("events: at most 16");
130
+ for (const [name, raw] of Object.entries(v)) {
131
+ const at = `events.${name}`;
132
+ if (!BINDING.test(name))
133
+ out.push(`${at}: an event name is [a-z][a-z0-9_]{0,31}`);
134
+ if (!isRecord(raw)) {
135
+ out.push(`${at} must be an object { title?, description?, params? }`);
136
+ continue;
137
+ }
138
+ for (const [k, x] of Object.entries(raw)) {
139
+ if (k === "title") {
140
+ if (typeof x !== "string" || x.length > 256)
141
+ out.push(`${at}.title: at most 256 characters`);
142
+ } else if (k === "description") {
143
+ if (typeof x !== "string" || x.length > 4e3)
144
+ out.push(`${at}.description: at most 4000 characters`);
145
+ } else if (k !== "params")
146
+ out.push(`${at}: unknown key ${k} (title, description, params)`);
147
+ }
148
+ if (raw.params === void 0)
149
+ continue;
150
+ if (!isRecord(raw.params)) {
151
+ out.push(`${at}.params must be an object { <param>: { type, required?, description? } }`);
152
+ continue;
153
+ }
154
+ if (Object.keys(raw.params).length > 16)
155
+ out.push(`${at}.params: at most 16`);
156
+ for (const [p, spec] of Object.entries(raw.params)) {
157
+ const pat = `${at}.params.${p}`;
158
+ if (!BINDING.test(p))
159
+ out.push(`${pat}: a param name is [a-z][a-z0-9_]{0,31}`);
160
+ if (!isRecord(spec)) {
161
+ out.push(`${pat} must be an object { type, required?, description? }`);
162
+ continue;
163
+ }
164
+ for (const [k, x] of Object.entries(spec)) {
165
+ if (k === "type") {
166
+ if (typeof x !== "string" || !EVENT_PARAM_TYPES.includes(x))
167
+ out.push(`${pat}.type: ${EVENT_PARAM_TYPES.join(", ")}`);
168
+ } else if (k === "required") {
169
+ if (typeof x !== "boolean")
170
+ out.push(`${pat}.required must be a boolean`);
171
+ } else if (k === "description") {
172
+ if (typeof x !== "string" || x.length > 1e3)
173
+ out.push(`${pat}.description: at most 1000 characters`);
174
+ } else
175
+ out.push(`${pat}: unknown key ${k} (type, required, description)`);
176
+ }
177
+ if (spec.type === void 0)
178
+ out.push(`${pat}.type required`);
179
+ }
180
+ }
181
+ }
182
+ function schemaProblems(v, path, out) {
183
+ if (!v || typeof v !== "object" || Array.isArray(v)) {
184
+ out.push(`${path}: a schema is an object`);
185
+ return;
186
+ }
187
+ for (const [k, x] of Object.entries(v)) {
188
+ if (!SCHEMA_KEYWORDS.includes(k)) {
189
+ out.push(`${path}/${k}: keyword ${k} is outside the supported subset`);
190
+ continue;
191
+ }
192
+ if (k === "properties" && x && typeof x === "object")
193
+ for (const [n, s] of Object.entries(x))
194
+ schemaProblems(s, `${path}/properties/${n}`, out);
195
+ if (k === "items")
196
+ schemaProblems(x, `${path}/items`, out);
197
+ }
198
+ }
199
+ function checkDeclarative(text, context = {}, file = "declarative.json") {
200
+ const out = [];
201
+ const error = (message, code) => out.push({ file, severity: "error", message, ...code ? { code } : {} });
202
+ const warn = (message) => out.push({ file, severity: "warning", message });
203
+ let o;
204
+ try {
205
+ const parsed = JSON.parse(text);
206
+ if (!isRecord(parsed)) {
207
+ error("a declarative definition is a JSON object");
208
+ return out;
209
+ }
210
+ o = parsed;
211
+ } catch (e) {
212
+ error(e instanceof Error ? e.message : String(e));
213
+ return out;
214
+ }
215
+ const bindings = isRecord(context.bindings) ? context.bindings : {};
216
+ const props = configProps(context.configSchema);
217
+ const strings = (v, at) => {
218
+ if (typeof v === "string")
219
+ configPlaceholders(v, at, props, error);
220
+ else if (Array.isArray(v))
221
+ v.forEach((x, i) => strings(x, `${at}[${i}]`));
222
+ else if (isRecord(v))
223
+ for (const [k, x] of Object.entries(v))
224
+ strings(x, `${at}.${k}`);
225
+ };
226
+ const button = (it, at) => {
227
+ if (context.manifestVersion !== void 0 && context.manifestVersion < 3)
228
+ error(`${at}: a button ${V3_HINT}`);
229
+ for (const k of Object.keys(it))
230
+ if (!["kind", "label", "emit", "params"].includes(k))
231
+ warn(`${at}: key ${k} is ignored by the interpreter`);
232
+ if (typeof it.label !== "string" || !it.label)
233
+ error(`${at}.label: the button's text (required)`);
234
+ if (typeof it.emit !== "string" || !it.emit)
235
+ error(`${at}.emit: the event the button raises (required)`);
236
+ else if (context.events && !context.events.includes(it.emit))
237
+ error(`${at}.emit ${it.emit}: not an event of component.json`, "EVENT_UNDECLARED");
238
+ if (it.params !== void 0) {
239
+ if (isRecord(it.params))
240
+ strings(it.params, `${at}.params`);
241
+ else
242
+ error(`${at}.params must be an object`);
243
+ }
244
+ };
245
+ const items = (list, at) => {
246
+ if (list === void 0)
247
+ return;
248
+ if (!Array.isArray(list)) {
249
+ error(`${at} must be an array`);
250
+ return;
251
+ }
252
+ list.forEach((it, i) => {
253
+ const iat = `${at}[${i}]`;
254
+ if (!isRecord(it)) {
255
+ error(`${iat} must be an object`);
256
+ return;
257
+ }
258
+ if (it.kind === "button") {
259
+ button(it, iat);
260
+ return;
261
+ }
262
+ if (typeof it.kind !== "string" || !ITEM_KINDS.includes(it.kind))
263
+ error(`${iat}.kind ${JSON.stringify(it.kind)}: one of ${[...ITEM_KINDS, "button"].join(", ")}`);
264
+ for (const k of Object.keys(it))
265
+ if (!ITEM_KEYS.includes(k))
266
+ warn(`${iat}: key ${k} is ignored by the interpreter`);
267
+ if (it.format !== void 0 && !["text", "number", "percent", "currency"].includes(it.format))
268
+ error(`${iat}.format ${JSON.stringify(it.format)}: text, number, percent or currency`);
269
+ if (typeof it.bind === "string" && it.bind.includes(".")) {
270
+ const prefix = it.bind.slice(0, it.bind.indexOf("."));
271
+ if (prefix !== "props" && !(prefix in bindings))
272
+ warn(`${iat}.bind ${JSON.stringify(it.bind)}: ${prefix} is not a declared binding, so it reads the field ${JSON.stringify(it.bind)} of the main source`);
273
+ }
274
+ });
275
+ };
276
+ for (const [k, v] of Object.entries(o)) {
277
+ if (k === "layout") {
278
+ if (v !== "stack" && v !== "row")
279
+ error("layout: stack or row");
280
+ } else if (k === "items")
281
+ items(v, "items");
282
+ else if (k === "view")
283
+ items(isRecord(v) ? v.items : void 0, "view.items");
284
+ else if (k === "accent") {
285
+ if (typeof v !== "string")
286
+ error("accent must be a colour string");
287
+ } else if (k === "actions") {
288
+ (Array.isArray(v) ? v : []).forEach((a, i) => {
289
+ const r = isRecord(a) ? a : {};
290
+ if (typeof r.command !== "string" || !ACTION_COMMANDS.includes(r.command))
291
+ error(`actions[${i}].command: one of ${ACTION_COMMANDS.join(", ")}`);
292
+ if (typeof r.id !== "string" || typeof r.label !== "string")
293
+ error(`actions[${i}] needs id and label`);
294
+ });
295
+ } else if (k === "inspector") {
296
+ (Array.isArray(v) ? v : []).forEach((f, i) => {
297
+ const r = isRecord(f) ? f : {};
298
+ if (typeof r.kind !== "string" || !INSPECTOR_KINDS.includes(r.kind))
299
+ error(`inspector[${i}].kind: one of ${INSPECTOR_KINDS.join(", ")}`);
300
+ if (typeof r.key !== "string" || typeof r.label !== "string")
301
+ error(`inspector[${i}] needs key and label`);
302
+ });
303
+ } else
304
+ warn(`key ${k} is not part of the declarative vocabulary`);
305
+ }
306
+ if (o.items === void 0 && o.view === void 0)
307
+ warn("no items: the control draws nothing");
308
+ return out;
309
+ }
310
+ function checkComposition(text, configSchema, file = AIWS_COMPOSITION_FILE, context = {}) {
311
+ const out = [];
312
+ const error = (message, code) => out.push({ file, severity: "error", message, ...code ? { code } : {} });
313
+ let o;
314
+ try {
315
+ const parsed = JSON.parse(text);
316
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
317
+ error("a composition is a JSON object { layout, slots, chrome?, events? }");
318
+ return out;
319
+ }
320
+ o = parsed;
321
+ } catch (e) {
322
+ error(e instanceof Error ? e.message : String(e));
323
+ return out;
324
+ }
325
+ const props = configProps(configSchema);
326
+ const placeholders = (t, at) => configPlaceholders(t, at, props, error);
327
+ const dimension = (v, at, max) => {
328
+ if (typeof v === "number" && v >= 0 && v <= max)
329
+ return;
330
+ if (typeof v === "string" && /^\s*\{\{.*\}\}\s*$/.test(v)) {
331
+ placeholders(v, at);
332
+ return;
333
+ }
334
+ error(`${at}: a number in [0, ${max}] or {{config.<key>}}`);
335
+ };
336
+ for (const k of Object.keys(o)) {
337
+ if (!["layout", "slots", "chrome", "events"].includes(k))
338
+ error(`unknown key ${k} (a composition says layout, slots, chrome and events)`);
339
+ }
340
+ const layout = o.layout;
341
+ let algorithm = "stack";
342
+ if (!layout || typeof layout !== "object")
343
+ error("layout { algorithm: stack | fixed | grid } required");
344
+ else {
345
+ if (layout.algorithm === "stack" || layout.algorithm === "fixed" || layout.algorithm === "grid")
346
+ algorithm = layout.algorithm;
347
+ else
348
+ error(`layout.algorithm ${JSON.stringify(layout.algorithm)}: stack, fixed or grid (the algorithms the host ships)`);
349
+ for (const [k, v] of Object.entries(layout)) {
350
+ if (k === "algorithm")
351
+ continue;
352
+ if ((k === "direction" || k === "spacing") && algorithm !== "stack") {
353
+ error(`layout.${k} belongs to the stack layout`);
354
+ continue;
355
+ }
356
+ if ((k === "columns" || k === "gap") && algorithm !== "grid") {
357
+ error(`layout.${k} belongs to the grid layout`);
358
+ continue;
359
+ }
360
+ if (k === "direction") {
361
+ if (v !== "column" && v !== "row")
362
+ error("layout.direction: column or row");
363
+ } else if (k === "spacing")
364
+ dimension(v, "layout.spacing", 2e3);
365
+ else if (k === "columns") {
366
+ if (!Number.isInteger(v) || v < 1 || v > 12)
367
+ error("layout.columns: an integer in [1, 12]");
368
+ } else if (k === "gap")
369
+ dimension(v, "layout.gap", 2e3);
370
+ else if (k === "padding") {
371
+ if (v && typeof v === "object" && !Array.isArray(v))
372
+ for (const [side, n] of Object.entries(v)) {
373
+ if (!["t", "r", "b", "l"].includes(side))
374
+ error(`layout.padding.${side}: t, r, b or l`);
375
+ dimension(n, `layout.padding.${side}`, 2e3);
376
+ }
377
+ else
378
+ dimension(v, "layout.padding", 2e3);
379
+ } else
380
+ error(`layout: unknown key ${k}`);
381
+ }
382
+ if (algorithm === "grid" && layout.columns === void 0)
383
+ error("layout.columns (1…12) required: the cells of a grid row");
384
+ }
385
+ const slots = o.slots && typeof o.slots === "object" && !Array.isArray(o.slots) ? o.slots : null;
386
+ if (!slots)
387
+ error("slots { <name>: { accepts?, min?, max?, placement? } } required");
388
+ else {
389
+ if (Object.keys(slots).length > 16)
390
+ error("at most 16 slots");
391
+ for (const [name, raw] of Object.entries(slots)) {
392
+ const at = `slots.${name}`;
393
+ if (!BINDING.test(name))
394
+ error(`${at}: a slot name is [a-z][a-z0-9_]{0,31}`);
395
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
396
+ error(`${at} must be an object`);
397
+ continue;
398
+ }
399
+ const slot = raw;
400
+ for (const [k, v] of Object.entries(slot)) {
401
+ if (k === "accepts") {
402
+ if (!Array.isArray(v) || v.length === 0 || v.length > 32) {
403
+ error(`${at}.accepts: 1…32 entries (cell:<ident>, a view type, container)`);
404
+ continue;
405
+ }
406
+ for (const a of v) {
407
+ if (typeof a === "string" && a.startsWith("cell:")) {
408
+ if (!IDENT.test(a.slice(5)) || encoder.encode(a.slice(5)).length > 128)
409
+ error(`${at}.accepts ${JSON.stringify(a)}: cell:<author_did>#<name>`);
410
+ } else if (a !== "container" && !(typeof a === "string" && NAME.test(a)))
411
+ error(`${at}.accepts ${JSON.stringify(a)}: cell:<ident>, a view type or container`);
412
+ }
413
+ } else if (k === "min") {
414
+ if (!Number.isInteger(v) || v < 0 || v > 64)
415
+ error(`${at}.min: an integer in [0, 64]`);
416
+ } else if (k === "max") {
417
+ if (!Number.isInteger(v) || v < 1 || v > 1e3)
418
+ error(`${at}.max: an integer in [1, 1000]`);
419
+ } else if (k === "placement") {
420
+ if (v !== "stack" && v !== "top" && v !== "bottom" && v !== "free")
421
+ error(`${at}.placement: stack, top, bottom or free`);
422
+ else if (algorithm === "fixed" && v !== "free")
423
+ error(`${at}.placement: a fixed layout keeps every child where it is (free)`);
424
+ else if (algorithm === "grid" && v !== "stack")
425
+ error(`${at}.placement: a grid fills its cells with the slots' children in slot order (stack)`);
426
+ } else if (k === "title") {
427
+ if (typeof v !== "string" || v.length > 256)
428
+ error(`${at}.title: at most 256 characters`);
429
+ } else
430
+ error(`${at}: unknown key ${k}`);
431
+ }
432
+ if (Number.isInteger(slot.min) && Number.isInteger(slot.max) && slot.min > slot.max)
433
+ error(`${at}: min exceeds max`);
434
+ }
435
+ }
436
+ const chrome = o.chrome;
437
+ if (chrome !== void 0) {
438
+ if (!chrome || typeof chrome !== "object" || Array.isArray(chrome))
439
+ error("chrome must be an object");
440
+ else
441
+ for (const [k, v] of Object.entries(chrome)) {
442
+ const at = `chrome.${k}`;
443
+ if (k === "background" || k === "border") {
444
+ if (typeof v === "string" && v.length <= 64)
445
+ placeholders(v, at);
446
+ else
447
+ error(`${at}: a colour (≤ 64 characters) or {{config.<key>}}`);
448
+ } else if (k === "radius")
449
+ dimension(v, at, 200);
450
+ else if (k === "empty") {
451
+ if (typeof v === "string" && v.length <= 1e3)
452
+ placeholders(v, at);
453
+ else
454
+ error(`${at}: a text of at most 1000 characters`);
455
+ } else if (k === "title") {
456
+ const t = v;
457
+ if (!t || typeof t !== "object" || Array.isArray(t)) {
458
+ error(`${at}: { text, slot? }`);
459
+ continue;
460
+ }
461
+ for (const [tk, tv] of Object.entries(t)) {
462
+ if (tk === "text") {
463
+ if (typeof tv === "string" && tv.length <= 256)
464
+ placeholders(tv, "chrome.title.text");
465
+ else
466
+ error("chrome.title.text: at most 256 characters");
467
+ } else if (tk === "slot") {
468
+ if (typeof tv !== "string" || !slots || !(tv in slots))
469
+ error(`chrome.title.slot ${JSON.stringify(tv)}: a slot of this composition`);
470
+ } else
471
+ error(`chrome.title: unknown key ${tk}`);
472
+ }
473
+ if (typeof t.text !== "string")
474
+ error("chrome.title.text required");
475
+ } else
476
+ error(`chrome: unknown key ${k} (background, radius, border, title, empty)`);
477
+ }
478
+ }
479
+ if (o.events !== void 0) {
480
+ if (context.manifestVersion !== void 0 && context.manifestVersion < 3)
481
+ error(`events ${V3_HINT}`);
482
+ if (!isRecord(o.events))
483
+ error("events must be an object { <name>: { from: { slot, event }, title?, description? } }");
484
+ else {
485
+ if (Object.keys(o.events).length > 16)
486
+ error("events: at most 16");
487
+ for (const [name, raw] of Object.entries(o.events)) {
488
+ const at = `events.${name}`;
489
+ if (!BINDING.test(name))
490
+ error(`${at}: an event name is [a-z][a-z0-9_]{0,31}`);
491
+ if (!isRecord(raw)) {
492
+ error(`${at} must be an object { from: { slot, event }, title?, description? }`);
493
+ continue;
494
+ }
495
+ for (const [k, v] of Object.entries(raw)) {
496
+ if (k === "title") {
497
+ if (typeof v !== "string" || v.length > 256)
498
+ error(`${at}.title: at most 256 characters`);
499
+ } else if (k === "description") {
500
+ if (typeof v !== "string" || v.length > 4e3)
501
+ error(`${at}.description: at most 4000 characters`);
502
+ } else if (k !== "from")
503
+ error(`${at}: unknown key ${k} (from, title, description)`);
504
+ }
505
+ const from = raw.from;
506
+ if (!isRecord(from)) {
507
+ error(`${at}.from { slot, event } required: the child event it re-raises`);
508
+ continue;
509
+ }
510
+ for (const k of Object.keys(from))
511
+ if (k !== "slot" && k !== "event")
512
+ error(`${at}.from: unknown key ${k} (slot, event)`);
513
+ const event = typeof from.event === "string" && BINDING.test(from.event) ? from.event : void 0;
514
+ if (!event)
515
+ error(`${at}.from.event: an event name [a-z][a-z0-9_]{0,31}`);
516
+ const slot = typeof from.slot === "string" && slots ? slots[from.slot] : void 0;
517
+ if (!isRecord(slot)) {
518
+ error(`${at}.from.slot ${JSON.stringify(from.slot)}: a slot of this composition`);
519
+ continue;
520
+ }
521
+ if (!event)
522
+ continue;
523
+ const cells = (Array.isArray(slot.accepts) ? slot.accepts : []).filter((a) => typeof a === "string" && a.startsWith("cell:")).map((a) => a.slice(5));
524
+ if (cells.length === 0) {
525
+ error(`${at}: slot ${from.slot} accepts no cell:<ident>, so no control there declares ${event}`, "EVENT_UNDECLARED");
526
+ continue;
527
+ }
528
+ const declared = cells.map((ident) => {
529
+ var _a;
530
+ return (_a = context.cellEvents) == null ? void 0 : _a.call(context, ident);
531
+ });
532
+ if (declared.every((names) => names !== void 0 && !names.includes(event)))
533
+ error(`${at}: no control slot ${from.slot} accepts (${cells.join(", ")}) declares the event ${event}`, "EVENT_UNDECLARED");
534
+ }
535
+ }
536
+ }
537
+ return out;
538
+ }
539
+ function checkComponent(text, fileNames, readText, cellEvents) {
540
+ const out = [];
541
+ const f = AIWS_COMPONENT_FILE;
542
+ const error = (message) => out.push({ file: f, severity: "error", message });
543
+ let c;
544
+ try {
545
+ const parsed = JSON.parse(text);
546
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
547
+ error("must be a JSON object");
548
+ return out;
549
+ }
550
+ c = parsed;
551
+ } catch (e) {
552
+ error(e instanceof Error ? e.message : String(e));
553
+ return out;
554
+ }
555
+ for (const k of Object.keys(c))
556
+ if (!COMPONENT_KEYS.includes(k))
557
+ error(`unknown key ${k}`);
558
+ const mv = c.manifest_version;
559
+ const version = typeof mv === "number" && Number.isInteger(mv) && mv >= 1 ? mv : 0;
560
+ if (!version)
561
+ error(`manifest_version (1…${AIWS_MANIFEST_VERSION}) required`);
562
+ else if (version > AIWS_MANIFEST_VERSION)
563
+ error(`manifest_version ${mv} is newer than this client reads (${AIWS_MANIFEST_VERSION})`);
564
+ const before3 = version > 0 && version < 3;
565
+ const ident = c.ident;
566
+ let self = "";
567
+ if (!ident || typeof ident !== "object")
568
+ error("ident { author_did, name } required");
569
+ else {
570
+ if (typeof ident.author_did !== "string" || !DID.test(ident.author_did))
571
+ error("ident.author_did must be a DID (did:<method>:<id>)");
572
+ if (typeof ident.name !== "string" || !NAME.test(ident.name))
573
+ error("ident.name must be [a-z0-9][a-z0-9._-]{0,63}");
574
+ if (typeof ident.author_did === "string" && typeof ident.name === "string") {
575
+ self = `${ident.author_did}#${ident.name}`;
576
+ if (encoder.encode(self).length > 128)
577
+ error("an identity is at most 128 bytes");
578
+ }
579
+ }
580
+ const composite = c.kind === "composite";
581
+ if (c.kind !== "declarative" && c.kind !== "html" && !composite)
582
+ error("kind must be declarative, html or composite");
583
+ if (composite && version === 1)
584
+ error(`kind composite ${V2_HINT}`);
585
+ if (composite) {
586
+ for (const k of ["accepts", "allow_no_source"])
587
+ if (c[k] !== void 0)
588
+ error(`${k}: a composite is a container of Blocks, not a Cell showing data`);
589
+ }
590
+ if (typeof c.title !== "string" || !c.title || c.title.length > 256)
591
+ error("title: 1…256 characters");
592
+ if (c.config_schema !== void 0) {
593
+ const p = [];
594
+ schemaProblems(c.config_schema, "config_schema", p);
595
+ p.forEach(error);
596
+ }
597
+ if (c.bindings !== void 0) {
598
+ if (!c.bindings || typeof c.bindings !== "object")
599
+ error("bindings must be an object");
600
+ else
601
+ for (const n of Object.keys(c.bindings))
602
+ if (!BINDING.test(n) || n === "props")
603
+ error(`binding name ${n}: [a-z][a-z0-9_]{0,31}, not props`);
604
+ }
605
+ if (c.deps !== void 0) {
606
+ if (!Array.isArray(c.deps) || c.deps.length > 32)
607
+ error("deps: at most 32 identities <author_did>#<name>");
608
+ else if (c.deps.length > 0 && version === 1)
609
+ error(`deps ${V2_HINT}`);
610
+ else {
611
+ const seen = /* @__PURE__ */ new Set();
612
+ for (const d of c.deps) {
613
+ if (typeof d !== "string" || !IDENT.test(d))
614
+ error(`deps: ${JSON.stringify(d)} is not an identity <author_did>#<name>`);
615
+ else if (d === self)
616
+ error("deps: a control cannot depend on itself");
617
+ else if (seen.has(d))
618
+ error(`deps lists ${d} twice`);
619
+ seen.add(String(d));
620
+ }
621
+ }
622
+ }
623
+ let events = [];
624
+ if (c.events !== void 0) {
625
+ if (composite)
626
+ error("events: a composite declares its events in composition.json (events.<name>.from re-raises an event of a child)");
627
+ else if (before3)
628
+ error(`events ${V3_HINT}`);
629
+ else {
630
+ const p = [];
631
+ eventProblems(c.events, p);
632
+ p.forEach(error);
633
+ }
634
+ if (!composite && isRecord(c.events))
635
+ events = Object.keys(c.events);
636
+ }
637
+ const has = (n) => typeof n === "string" && fileNames.includes(n);
638
+ const named = /* @__PURE__ */ new Set([f]);
639
+ if (c.kind === "html") {
640
+ const e = c.entry;
641
+ if (!e || typeof e !== "object" || typeof e.html !== "string")
642
+ error("entry must be { html, css?, js? }");
643
+ else
644
+ for (const [k, n] of Object.entries(e)) {
645
+ if (!has(n))
646
+ error(`entry.${k} ${JSON.stringify(n)} is not in the directory`);
647
+ else
648
+ named.add(String(n));
649
+ }
650
+ if (c.host_api !== void 0 && c.host_api !== 2 && c.host_api !== 3)
651
+ error("host_api: the HTML Block host speaks API 2 and 3");
652
+ else if (c.host_api === 3 && before3)
653
+ error(`host_api 3 ${V3_HINT}`);
654
+ if (events.length > 0 && c.host_api !== 3)
655
+ out.push({ file: f, severity: "warning", message: "events: an HTML control raises them with aiws.emit, which is host API 3 (set host_api to 3)" });
656
+ } else if (c.kind === "declarative" || composite) {
657
+ if (!has(c.entry))
658
+ error(`entry ${JSON.stringify(c.entry)} is not in the directory`);
659
+ else {
660
+ named.add(String(c.entry));
661
+ const entryText = readText == null ? void 0 : readText(String(c.entry));
662
+ const manifestVersion = version || void 0;
663
+ if (entryText !== void 0 && composite)
664
+ out.push(...checkComposition(entryText, c.config_schema, String(c.entry), { manifestVersion, cellEvents }));
665
+ else if (entryText !== void 0)
666
+ out.push(...checkDeclarative(entryText, { bindings: c.bindings, events, configSchema: c.config_schema, manifestVersion }, String(c.entry)));
667
+ }
668
+ if (composite && c.host_api !== void 0)
669
+ error("host_api: a composite runs no code of its own (its layout and chrome are the host's)");
670
+ if (!composite && c.host_api !== void 0 && c.host_api !== AIWS_DECLARATIVE_API_VERSION)
671
+ error(`host_api: the declarative interpreter is version ${AIWS_DECLARATIVE_API_VERSION}`);
672
+ }
673
+ for (const a of Array.isArray(c.assets) ? c.assets : []) {
674
+ if (!has(a))
675
+ error(`asset ${JSON.stringify(a)} is not in the directory`);
676
+ else
677
+ named.add(String(a));
678
+ }
679
+ if (c.agent !== void 0) {
680
+ if (!has(c.agent))
681
+ error(`agent ${JSON.stringify(c.agent)} is not in the directory`);
682
+ else
683
+ named.add(String(c.agent));
684
+ }
685
+ for (const n of fileNames) {
686
+ if (!isSourcePath(n))
687
+ out.push({ file: n, severity: "error", message: "a file of a source directory: a relative path whose parts do not start with '.'" });
688
+ else if (!named.has(n))
689
+ out.push({ file: n, severity: "warning", message: "not named by component.json: kept in the source snapshot only" });
690
+ }
691
+ return out;
692
+ }
693
+ function slotCells(composition) {
694
+ const out = /* @__PURE__ */ new Set();
695
+ for (const slot of Object.values(composition.slots ?? {}))
696
+ for (const a of slot.accepts ?? [])
697
+ if (a.startsWith("cell:"))
698
+ out.add(a.slice(5));
699
+ return [...out].sort();
700
+ }
701
+ function compatManifest(value) {
702
+ if (!value || typeof value !== "object")
703
+ throw new AiwsSourceError([{ file: AIWS_MANIFEST_FILE, severity: "error", message: "must be a JSON object" }]);
704
+ const m = value;
705
+ const version = m.manifest_version;
706
+ if (typeof version !== "number" || version < 1)
707
+ throw new AiwsSourceError([{ file: AIWS_MANIFEST_FILE, severity: "error", message: "manifest_version required" }]);
708
+ if (version > AIWS_MANIFEST_VERSION)
709
+ throw new AiwsSourceError([{ file: AIWS_MANIFEST_FILE, severity: "error", message: `manifest_version ${version} needs a newer client (this one reads ${AIWS_MANIFEST_VERSION})` }]);
710
+ if (m.purpose !== "aiws")
711
+ throw new AiwsSourceError([{ file: AIWS_MANIFEST_FILE, severity: "error", message: "purpose must be aiws" }]);
712
+ if (!Array.isArray(m.controls))
713
+ throw new AiwsSourceError([{ file: AIWS_MANIFEST_FILE, severity: "error", message: "controls must be an array" }]);
714
+ return { manifest_version: version, purpose: "aiws", host_api: m.host_api ?? {}, controls: m.controls, workspace_templates: Array.isArray(m.workspace_templates) ? m.workspace_templates : [] };
715
+ }
716
+ const TEMPLATE_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
717
+ function templateDir(id) {
718
+ return `templates/${id}/`;
719
+ }
720
+ async function aiwsTemplateDigest(files, id) {
721
+ const dir = templateDir(id);
722
+ const own = [];
723
+ for (const [path, bytes] of files)
724
+ if (path.startsWith(dir))
725
+ own.push([path.slice(dir.length), bytes]);
726
+ return aiwsContentDigest(own);
727
+ }
728
+ function templateProblems(t, controls) {
729
+ if (!isRecord(t))
730
+ return ["a template is an object { id, title, snapshot, digest, requires, description?, params?, reset? }"];
731
+ const out = [];
732
+ for (const k of Object.keys(t))
733
+ if (!["id", "title", "description", "snapshot", "digest", "requires", "params", "reset"].includes(k))
734
+ out.push(`unknown key ${k}`);
735
+ if (typeof t.id !== "string" || !TEMPLATE_ID.test(t.id))
736
+ out.push("id: [a-z0-9][a-z0-9-]{0,63}");
737
+ else if (t.snapshot !== `${templateDir(t.id)}snapshot.json`)
738
+ out.push(`snapshot must be ${templateDir(t.id)}snapshot.json`);
739
+ if (typeof t.title !== "string" || !t.title || t.title.length > 256)
740
+ out.push("title: 1…256 characters");
741
+ if (t.description !== void 0 && (typeof t.description !== "string" || t.description.length > 4e3))
742
+ out.push("description: at most 4000 characters");
743
+ if (typeof t.digest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(t.digest))
744
+ out.push("digest: sha256:<64 hex digits>");
745
+ const requires = t.requires;
746
+ if (!isRecord(requires))
747
+ out.push("requires { host_api?, controls } required");
748
+ else {
749
+ for (const k of Object.keys(requires))
750
+ if (k !== "host_api" && k !== "controls")
751
+ out.push(`requires: unknown key ${k}`);
752
+ if (requires.host_api !== void 0) {
753
+ if (!isRecord(requires.host_api))
754
+ out.push("requires.host_api must be an object { html?, declarative? }");
755
+ else
756
+ for (const [k, v] of Object.entries(requires.host_api))
757
+ if (k !== "html" && k !== "declarative" || !Number.isInteger(v) || v < 1)
758
+ out.push(`requires.host_api.${k}: html or declarative, a positive integer`);
759
+ }
760
+ if (!Array.isArray(requires.controls) || requires.controls.length > 256)
761
+ out.push("requires.controls: at most 256 identities");
762
+ else {
763
+ const seen = /* @__PURE__ */ new Set();
764
+ for (const ident of requires.controls) {
765
+ if (typeof ident !== "string" || !controls.has(ident))
766
+ out.push(`requires.controls: ${JSON.stringify(ident)} is not a control of this manifest`);
767
+ else if (seen.has(ident))
768
+ out.push(`requires.controls lists ${ident} twice`);
769
+ seen.add(ident);
770
+ }
771
+ }
772
+ }
773
+ if (t.params !== void 0) {
774
+ if (!isRecord(t.params) || Object.keys(t.params).length > 32)
775
+ out.push("params: an object of at most 32 { type, title?, default? }");
776
+ else
777
+ for (const [name, p] of Object.entries(t.params)) {
778
+ const at = `params.${name}`;
779
+ if (!BINDING.test(name))
780
+ out.push(`${at}: a param name is [a-z][a-z0-9_]{0,31}`);
781
+ if (!isRecord(p)) {
782
+ out.push(`${at} must be an object { type, title?, default? }`);
783
+ continue;
784
+ }
785
+ for (const [k, v] of Object.entries(p)) {
786
+ if (k === "type") {
787
+ if (v !== "string" && v !== "number" && v !== "boolean")
788
+ out.push(`${at}.type: string, number or boolean`);
789
+ } else if (k === "title") {
790
+ if (typeof v !== "string" || v.length > 256)
791
+ out.push(`${at}.title: at most 256 characters`);
792
+ } else if (k === "default") {
793
+ if (!["string", "number", "boolean"].includes(typeof v))
794
+ out.push(`${at}.default: a string, number or boolean`);
795
+ } else
796
+ out.push(`${at}: unknown key ${k} (type, title, default)`);
797
+ }
798
+ if (p.type === void 0)
799
+ out.push(`${at}.type required`);
800
+ }
801
+ }
802
+ if (t.reset !== void 0) {
803
+ const tables = isRecord(t.reset) ? t.reset.tables : void 0;
804
+ if (!isRecord(t.reset) || Object.keys(t.reset).some((k) => k !== "tables"))
805
+ out.push("reset: { tables }");
806
+ else if (tables !== void 0 && (!Array.isArray(tables) || tables.length > 64 || new Set(tables).size !== tables.length || tables.some((x) => typeof x !== "string" || !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(x))))
807
+ out.push("reset.tables: at most 64 distinct table ids");
808
+ }
809
+ return out;
810
+ }
811
+ async function verifyBundle(files) {
812
+ var _a, _b;
813
+ const raw = files.get(AIWS_MANIFEST_FILE);
814
+ if (!raw)
815
+ throw new AiwsSourceError([{ file: AIWS_MANIFEST_FILE, severity: "error", message: "missing" }]);
816
+ let parsed;
817
+ try {
818
+ parsed = JSON.parse(decoder.decode(raw));
819
+ } catch (e) {
820
+ throw new AiwsSourceError([{ file: AIWS_MANIFEST_FILE, severity: "error", message: String(e) }]);
821
+ }
822
+ const manifest = compatManifest(parsed);
823
+ const diagnostics = [];
824
+ const templates = manifest.workspace_templates;
825
+ if (templates.length > 0 && manifest.manifest_version < 3)
826
+ diagnostics.push({ file: AIWS_MANIFEST_FILE, severity: "error", message: `Workspace templates need manifest_version 3 (raise it in ${AIWS_MANIFEST_FILE})` });
827
+ if (templates.length > 32)
828
+ diagnostics.push({ file: AIWS_MANIFEST_FILE, severity: "error", message: "at most 32 Workspace templates" });
829
+ if (manifest.controls.length === 0 && templates.length === 0)
830
+ diagnostics.push({ file: AIWS_MANIFEST_FILE, severity: "error", message: "lists no control" });
831
+ const declared = /* @__PURE__ */ new Map();
832
+ for (const c of manifest.controls) {
833
+ const component = typeof c.dir === "string" ? files.get(`${c.dir}${AIWS_COMPONENT_FILE}`) : void 0;
834
+ try {
835
+ const comp = component ? JSON.parse(decoder.decode(component)) : void 0;
836
+ if (isRecord(comp))
837
+ declared.set(c.ident, isRecord(comp.events) ? Object.keys(comp.events) : []);
838
+ } catch {
839
+ }
840
+ }
841
+ const dirs = /* @__PURE__ */ new Set();
842
+ for (const c of manifest.controls) {
843
+ if (typeof c.dir !== "string" || !/^controls\/[a-z0-9][a-z0-9._-]{0,70}\/$/.test(c.dir) || dirs.has(c.dir)) {
844
+ diagnostics.push({ file: AIWS_MANIFEST_FILE, severity: "error", message: `control ${c.ident}: bad or repeated dir` });
845
+ continue;
846
+ }
847
+ dirs.add(c.dir);
848
+ const own = [...files].filter(([p]) => p.startsWith(c.dir)).map(([p, b]) => [p.slice(c.dir.length), b]);
849
+ if (await aiwsContentDigest(own) !== c.digest)
850
+ diagnostics.push({ file: c.dir, severity: "error", message: "the files do not match the manifest digest" });
851
+ const component = own.find(([n]) => n === AIWS_COMPONENT_FILE);
852
+ if (!component) {
853
+ diagnostics.push({ file: `${c.dir}${AIWS_COMPONENT_FILE}`, severity: "error", message: "missing" });
854
+ continue;
855
+ }
856
+ const text = (name) => {
857
+ const hit = own.find(([n]) => n === name);
858
+ return hit ? decoder.decode(hit[1]) : void 0;
859
+ };
860
+ for (const d of checkComponent(decoder.decode(component[1]), own.map(([n]) => n), text, (ident) => declared.get(ident)))
861
+ diagnostics.push({ ...d, file: `${c.dir}${d.file}` });
862
+ try {
863
+ const comp = JSON.parse(decoder.decode(component[1]));
864
+ if (`${(_a = comp.ident) == null ? void 0 : _a.author_did}#${(_b = comp.ident) == null ? void 0 : _b.name}` !== c.ident || comp.kind !== c.kind)
865
+ diagnostics.push({ file: c.dir, severity: "error", message: "ident and kind must match its component.json" });
866
+ if (JSON.stringify([...comp.deps ?? []].sort()) !== JSON.stringify([...c.deps ?? []].sort()))
867
+ diagnostics.push({ file: c.dir, severity: "error", message: "deps must match its component.json" });
868
+ } catch {
869
+ }
870
+ }
871
+ const idents = new Set(manifest.controls.map((c) => c.ident));
872
+ for (const c of manifest.controls)
873
+ for (const d of c.deps ?? [])
874
+ if (!idents.has(d))
875
+ diagnostics.push({ file: AIWS_MANIFEST_FILE, severity: "error", message: `${c.ident} depends on ${d}, which the bundle does not carry` });
876
+ for (const [i, t] of templates.entries()) {
877
+ const label = isRecord(t) && typeof t.id === "string" ? `template ${t.id}` : `workspace_templates[${i}]`;
878
+ for (const p of templateProblems(t, idents))
879
+ diagnostics.push({ file: AIWS_MANIFEST_FILE, severity: "error", message: `${label}: ${p}` });
880
+ if (!isRecord(t) || typeof t.id !== "string" || !TEMPLATE_ID.test(t.id))
881
+ continue;
882
+ const dir = templateDir(t.id);
883
+ if (dirs.has(dir)) {
884
+ diagnostics.push({ file: AIWS_MANIFEST_FILE, severity: "error", message: `${label} is listed twice` });
885
+ continue;
886
+ }
887
+ dirs.add(dir);
888
+ const snapshot = files.get(`${dir}snapshot.json`);
889
+ let format;
890
+ try {
891
+ format = snapshot ? JSON.parse(decoder.decode(snapshot)).format : void 0;
892
+ } catch {
893
+ format = void 0;
894
+ }
895
+ if (!snapshot)
896
+ diagnostics.push({ file: `${dir}snapshot.json`, severity: "error", message: "missing" });
897
+ else if (format !== AIWS_TEMPLATE_FORMAT)
898
+ diagnostics.push({ file: `${dir}snapshot.json`, severity: "error", message: `not a template snapshot (format ${AIWS_TEMPLATE_FORMAT})` });
899
+ if (await aiwsTemplateDigest(files, t.id) !== t.digest)
900
+ diagnostics.push({ file: dir, severity: "error", message: "the files do not match the template digest" });
901
+ }
902
+ for (const p of files.keys())
903
+ if (p !== AIWS_MANIFEST_FILE && ![...dirs].some((d) => p.startsWith(d)))
904
+ diagnostics.push({ file: p, severity: "error", message: "belongs to no control or template of the manifest" });
905
+ return { manifest, diagnostics };
906
+ }
907
+ async function exportBundle(client, workspaceId, entityIds, fetchAsset) {
908
+ const r = await client.extExport(workspaceId, entityIds, "package");
909
+ const files = /* @__PURE__ */ new Map();
910
+ files.set(AIWS_MANIFEST_FILE, encoder.encode(`${JSON.stringify(r.manifest, null, 2)}
911
+ `));
912
+ for (const f of r.files)
913
+ files.set(f.path, f.text !== void 0 ? encoder.encode(f.text) : await fetchAsset(workspaceId, String(f.obj_id)));
914
+ return { manifest: compatManifest(r.manifest), files, controls: r.controls };
915
+ }
916
+ async function child(nfsp, folder, name) {
917
+ const glob = /[*?[\]\\]/.test(name) ? void 0 : name;
918
+ const page = await nfsp.list(typeof folder === "string" ? folder : { ref: folder }, { limit: glob ? 10 : 1e3, ...glob ? { filter: { name_glob: glob } } : {} }, ["base", "ident"]);
919
+ return page.entries.find((e) => e.name === name);
920
+ }
921
+ async function ensureFolder(nfsp, path) {
922
+ const parts = path.split("/").filter(Boolean);
923
+ if (parts.length === 0)
924
+ throw new Error(`not a folder path: ${path}`);
925
+ let ref = (await nfsp.stat(`/${parts[0]}`)).ref;
926
+ for (const name of parts.slice(1)) {
927
+ const hit = await child(nfsp, ref, name);
928
+ if (hit && hit.target.kind !== "dir")
929
+ throw new AiwsSourceError([{ file: name, severity: "error", message: `${name} in ${path} is not a folder` }]);
930
+ ref = hit ? hit.target.ref : (await nfsp.mkdir({ ref }, name)).ref;
931
+ }
932
+ return ref;
933
+ }
934
+ async function findFolder(nfsp, path) {
935
+ const parts = path.split("/").filter(Boolean);
936
+ if (parts.length === 0)
937
+ return null;
938
+ let ref = (await nfsp.stat(`/${parts[0]}`)).ref;
939
+ for (const name of parts.slice(1)) {
940
+ const hit = await child(nfsp, ref, name);
941
+ if (!hit || hit.target.kind !== "dir")
942
+ return null;
943
+ ref = hit.target.ref;
944
+ }
945
+ return ref;
946
+ }
947
+ async function hasControlSource(nfsp, dir) {
948
+ const ref = await findFolder(nfsp, dir);
949
+ return ref !== null && await child(nfsp, ref, AIWS_COMPONENT_FILE) !== void 0;
950
+ }
951
+ async function writeHomeFile(nfsp, dir, name, bytes) {
952
+ const ref = typeof dir === "string" ? await ensureFolder(nfsp, dir) : dir;
953
+ await nfsp.uploadFile(ref, name, bytes, { overwrite: true });
954
+ }
955
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
956
+ async function collectSources(nfsp, dir) {
957
+ const out = [];
958
+ const problems = [];
959
+ const walk = async (folder, prefix, depth) => {
960
+ var _a, _b, _c;
961
+ let cursor;
962
+ do {
963
+ const page = await nfsp.list(typeof folder === "string" ? folder : { ref: folder }, { limit: 500, ...cursor ? { cursor } : {} }, ["base", "ident"]);
964
+ for (const e of page.entries) {
965
+ if (e.name.startsWith("."))
966
+ continue;
967
+ const name = `${prefix}${e.name}`;
968
+ if (e.target.kind === "dir") {
969
+ if (depth >= 8)
970
+ problems.push({ file: name, severity: "error", message: "sub-folders nest at most 8 levels" });
971
+ else
972
+ await walk(e.target.ref, `${name}/`, depth + 1);
973
+ continue;
974
+ }
975
+ if (e.target.kind !== "file") {
976
+ problems.push({ file: name, severity: "error", message: "only files and folders belong to a control source" });
977
+ continue;
978
+ }
979
+ const ref = e.target.ref;
980
+ const objId = ref.type === "object" ? ref.obj_id : typeof ((_a = e.target.attrs) == null ? void 0 : _a.obj_id) === "string" ? e.target.attrs.obj_id : void 0;
981
+ out.push({
982
+ name,
983
+ ...objId ? { obj_id: objId } : {},
984
+ ...ref.type === "live" ? { node_id: ref.node_id } : {},
985
+ ...typeof ((_b = e.target.attrs) == null ? void 0 : _b.etag) === "string" ? { etag: e.target.attrs.etag } : {},
986
+ ...typeof ((_c = e.target.attrs) == null ? void 0 : _c.size) === "number" ? { size: e.target.attrs.size } : {}
987
+ });
988
+ }
989
+ cursor = page.truncated ? page.next_cursor : void 0;
990
+ } while (cursor);
991
+ };
992
+ await walk(dir, "", 0);
993
+ if (out.length === 0)
994
+ problems.push({ file: "", severity: "error", message: `${dir} holds no files` });
995
+ if (out.length > AIWS_MAX_SOURCE_FILES)
996
+ problems.push({ file: "", severity: "error", message: `a control holds at most ${AIWS_MAX_SOURCE_FILES} files` });
997
+ if (problems.some((p) => p.severity === "error"))
998
+ throw new AiwsSourceError(problems);
999
+ return out.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
1000
+ }
1001
+ async function writeSourceFile(nfsp, dir, name, bytes) {
1002
+ const cut = name.lastIndexOf("/");
1003
+ await writeHomeFile(nfsp, cut < 0 ? dir : joinPath(dir, name.slice(0, cut)), cut < 0 ? name : name.slice(cut + 1), bytes);
1004
+ }
1005
+ async function sourcesDigest(nfsp, files) {
1006
+ const pairs = [];
1007
+ for (const f of files) {
1008
+ if (!f.node_id)
1009
+ throw new AiwsSourceError([{ file: f.name, severity: "error", message: "the file service did not name this file" }]);
1010
+ pairs.push([f.name, new Uint8Array(await (await nfsp.readFile(f.node_id)).arrayBuffer())]);
1011
+ }
1012
+ return aiwsContentDigest(pairs);
1013
+ }
1014
+ async function unbuiltChanges(client, nfsp, workspaceId, defEntityId, dir) {
1015
+ var _a;
1016
+ if (!await hasControlSource(nfsp, dir))
1017
+ return null;
1018
+ const home = await sourcesDigest(nfsp, await collectSources(nfsp, dir));
1019
+ const r = await client.extExport(workspaceId, [defEntityId], "pull");
1020
+ const built = String(((_a = r.controls[0]) == null ? void 0 : _a.digest) ?? "");
1021
+ return { changed: home !== built, home, built };
1022
+ }
1023
+ async function readBuildState(nfsp, dir) {
1024
+ try {
1025
+ const entry = await child(nfsp, dir, AIWS_BUILD_STATE_FILE);
1026
+ const nodeId = (entry == null ? void 0 : entry.target.ref.type) === "live" ? entry.target.ref.node_id : void 0;
1027
+ if (!nodeId)
1028
+ return null;
1029
+ const response = await nfsp.readFile(nodeId);
1030
+ return await response.json();
1031
+ } catch {
1032
+ return null;
1033
+ }
1034
+ }
1035
+ async function writeBuildState(nfsp, dir, state) {
1036
+ await writeHomeFile(nfsp, dir, AIWS_BUILD_STATE_FILE, encoder.encode(`${JSON.stringify(state, null, 2)}
1037
+ `));
1038
+ }
1039
+ async function buildControl(client, nfsp, workspaceId, dir, opts = {}) {
1040
+ const files = await collectSources(nfsp, dir);
1041
+ const state = await readBuildState(nfsp, dir);
1042
+ const base = state && state.workspace_id === workspaceId ? { def_rev: state.def_rev, dir_rev: state.dir_rev } : void 0;
1043
+ const result = await client.extBuild(workspaceId, {
1044
+ files: await buildFiles(nfsp, files, opts.upload),
1045
+ ...opts.folderId ? { folder_id: opts.folderId } : {},
1046
+ ...base ? { expect: base } : {},
1047
+ ...opts.force ? { force: true } : {},
1048
+ ...opts.dryRun ? { dry_run: true } : {},
1049
+ ...opts.migration ? { migration: opts.migration } : {}
1050
+ });
1051
+ if (!opts.dryRun && typeof result.def_entity_id === "string" && typeof result.def_rev === "number") {
1052
+ await writeBuildState(nfsp, dir, { workspace_id: workspaceId, def_entity_id: result.def_entity_id, def_rev: result.def_rev, dir_rev: Number(result.dir_rev ?? 0), ident: String(result.ident ?? ""), at: (/* @__PURE__ */ new Date()).toISOString() });
1053
+ }
1054
+ return { ...result, files };
1055
+ }
1056
+ async function pullControl(client, nfsp, workspaceId, defEntityId, outDir, fetchAsset) {
1057
+ const r = await client.extExport(workspaceId, [defEntityId], "pull");
1058
+ const control = r.controls[0];
1059
+ await ensureFolder(nfsp, outDir);
1060
+ for (const f of control.files)
1061
+ await writeSourceFile(nfsp, outDir, f.name, f.text !== void 0 ? encoder.encode(f.text) : await fetchAsset(workspaceId, String(f.obj_id)));
1062
+ const state = { workspace_id: workspaceId, def_entity_id: control.entity_id, def_rev: control.def_rev, dir_rev: control.dir_rev, ident: control.ident, at: (/* @__PURE__ */ new Date()).toISOString() };
1063
+ await writeBuildState(nfsp, outDir, state);
1064
+ return { files: control.files.map((f) => f.name), state };
1065
+ }
1066
+ async function watchControl(nfsp, dir, onChange, opts = {}) {
1067
+ var _a, _b;
1068
+ let last = "";
1069
+ let timer;
1070
+ let running = Promise.resolve();
1071
+ const check = () => {
1072
+ running = running.then(async () => {
1073
+ let files;
1074
+ try {
1075
+ files = await collectSources(nfsp, dir);
1076
+ } catch {
1077
+ return;
1078
+ }
1079
+ const key = sourcesKey(files);
1080
+ if (key === last)
1081
+ return;
1082
+ last = key;
1083
+ await onChange(files);
1084
+ }).catch(() => void 0);
1085
+ };
1086
+ check();
1087
+ while (!((_a = opts.signal) == null ? void 0 : _a.aborted)) {
1088
+ const listing = await nfsp.list(dir, { limit: 1 }, ["base"]);
1089
+ const stream = nfsp.watch({ tokens: listing.watch_token ? [listing.watch_token] : [], signal: opts.signal });
1090
+ try {
1091
+ for await (const _ of stream) {
1092
+ if (timer)
1093
+ clearTimeout(timer);
1094
+ timer = setTimeout(check, opts.debounceMs ?? 500);
1095
+ }
1096
+ } catch {
1097
+ if ((_b = opts.signal) == null ? void 0 : _b.aborted)
1098
+ break;
1099
+ await sleep(2e3);
1100
+ } finally {
1101
+ stream.close();
1102
+ }
1103
+ }
1104
+ if (timer)
1105
+ clearTimeout(timer);
1106
+ await running;
1107
+ }
1108
+ function controlSkeleton(opts) {
1109
+ const title = opts.title ?? opts.name;
1110
+ if (opts.kind === "composite") {
1111
+ const fixed = opts.algorithm === "fixed";
1112
+ const slots = opts.slots ?? { items: { placement: fixed ? "free" : "stack", title: "Items" } };
1113
+ const composition = {
1114
+ layout: fixed ? { algorithm: "fixed", padding: 12 } : { algorithm: "stack", direction: "column", spacing: "{{config.gap}}", padding: 12 },
1115
+ slots,
1116
+ chrome: { background: "{{config.background}}", radius: 12, title: { text: "{{config.title}}" }, empty: "Drop Blocks here" }
1117
+ };
1118
+ const component = {
1119
+ manifest_version: AIWS_MANIFEST_VERSION,
1120
+ ident: { author_did: opts.authorDid, name: opts.name },
1121
+ kind: "composite",
1122
+ title,
1123
+ description: `${title}: a panel of Blocks`,
1124
+ default_size: { w: 320, h: 360 },
1125
+ config_schema: { type: "object", properties: {
1126
+ title: { type: "string", title: "Title", default: title },
1127
+ background: { type: "string", title: "Background", default: "#f6f7fb" },
1128
+ gap: { type: "integer", title: "Gap (px)", default: 8, minimum: 0, maximum: 64 }
1129
+ } },
1130
+ entry: AIWS_COMPOSITION_FILE,
1131
+ deps: opts.deps ?? [],
1132
+ catalog: { group: "custom", description: `${title}` },
1133
+ agent: "AGENT.md"
1134
+ };
1135
+ const names = Object.keys(slots).map((n) => `\`${n}\``).join(", ");
1136
+ return {
1137
+ [AIWS_COMPONENT_FILE]: `${JSON.stringify(component, null, 2)}
1138
+ `,
1139
+ [AIWS_COMPOSITION_FILE]: `${JSON.stringify(composition, null, 2)}
1140
+ `,
1141
+ "AGENT.md": `# ${title}
1142
+
1143
+ A composite: an instance is a \`buckyos.container { kind: "composite", def_ref }\`; put Blocks under it with \`slot\` set to one of ${names}. Config: \`title\`, \`background\`, \`gap\`.
1144
+ `
1145
+ };
1146
+ }
1147
+ const base = {
1148
+ manifest_version: AIWS_MANIFEST_VERSION,
1149
+ ident: { author_did: opts.authorDid, name: opts.name },
1150
+ kind: opts.kind ?? "declarative",
1151
+ title,
1152
+ description: `${title}: a control of this Workspace`,
1153
+ accepts: ["buckyos.table-source", "buckyos.record"],
1154
+ allow_no_source: true,
1155
+ default_size: { w: 260, h: 140 },
1156
+ config_schema: { type: "object", properties: { label: { type: "string", title: "Label", default: title } } },
1157
+ bindings: { data: { accepts: ["buckyos.table-source"], title: "Data", description: "The table this control summarizes" } },
1158
+ catalog: { group: "custom", description: `${title}` },
1159
+ agent: "AGENT.md"
1160
+ };
1161
+ const agent = `# ${title}
1162
+
1163
+ Bind \`data\` to a table. Config: \`label\` (text).
1164
+ `;
1165
+ if (opts.kind === "html") {
1166
+ const events = { clicked: { title: "Clicked", description: "Raised when someone clicks the button (in run mode)", params: { count: { type: "number", description: "Rows of the bound table" } } } };
1167
+ return {
1168
+ [AIWS_COMPONENT_FILE]: `${JSON.stringify({ ...base, host_api: AIWS_HTML_API_VERSION, events, entry: { html: "view.html", css: "style.css", js: "behavior.js" } }, null, 2)}
1169
+ `,
1170
+ "view.html": '<div class="card"><div class="label" id="label"></div><div class="value" id="value">—</div><button id="go" type="button">Go</button></div>\n',
1171
+ "style.css": ".card{font:14px system-ui;padding:12px}.label{opacity:.7}.value{font-size:28px;font-weight:600}\n",
1172
+ "behavior.js": [
1173
+ "document.getElementById('label').textContent = (aiws.context.config || {}).label || ''",
1174
+ "var count = 0",
1175
+ "if (aiws.bindings.data) aiws.input('data').rows().then(function (rows) { count = rows.length; document.getElementById('value').textContent = String(count) })",
1176
+ "// the public event of component.json: an instance binds handlers to it in its `on` (host API 3)",
1177
+ "document.getElementById('go').addEventListener('click', function () { aiws.emit('clicked', { count: count }) })",
1178
+ ""
1179
+ ].join("\n"),
1180
+ "AGENT.md": `${agent}Event: \`clicked\` (\`count\`), raised by its button; bind handlers to it in the instance's \`on\`.
1181
+ `
1182
+ };
1183
+ }
1184
+ return {
1185
+ [AIWS_COMPONENT_FILE]: `${JSON.stringify({ ...base, entry: "declarative.json" }, null, 2)}
1186
+ `,
1187
+ "declarative.json": `${JSON.stringify({ layout: "stack", items: [{ kind: "metric", label: title, bind: "data.count", format: "number" }, { kind: "text", text: "Rows of the bound table" }] }, null, 2)}
1188
+ `,
1189
+ "AGENT.md": agent
1190
+ };
1191
+ }
1192
+ async function createControlSource(nfsp, dir, files) {
1193
+ const ref = await ensureFolder(nfsp, dir);
1194
+ const existing = await nfsp.list({ ref }, { limit: 500 }, ["base"]).catch(() => null);
1195
+ if (existing == null ? void 0 : existing.entries.some((e) => e.name === AIWS_COMPONENT_FILE))
1196
+ throw new AiwsSourceError([{ file: AIWS_COMPONENT_FILE, severity: "error", message: `${dir} already holds a control` }]);
1197
+ for (const [name, text] of Object.entries(files))
1198
+ await writeHomeFile(nfsp, ref, name, encoder.encode(text));
1199
+ return ref;
1200
+ }
1201
+ export {
1202
+ AIWS_BUILD_STATE_FILE,
1203
+ AIWS_COMPONENT_FILE,
1204
+ AIWS_COMPOSITION_FILE,
1205
+ AIWS_DECLARATIVE_API_VERSION,
1206
+ AIWS_HTML_API_VERSION,
1207
+ AIWS_MANIFEST_FILE,
1208
+ AIWS_MANIFEST_VERSION,
1209
+ AIWS_MAX_INLINE_BYTES,
1210
+ AIWS_MAX_SOURCE_FILES,
1211
+ AIWS_TEMPLATE_FORMAT,
1212
+ AiwsSourceError,
1213
+ aiwsContentDigest,
1214
+ aiwsTemplateDigest,
1215
+ buildControl,
1216
+ buildFiles,
1217
+ checkComponent,
1218
+ checkComposition,
1219
+ checkDeclarative,
1220
+ collectSources,
1221
+ compatManifest,
1222
+ controlSkeleton,
1223
+ createControlSource,
1224
+ ensureFolder,
1225
+ exportBundle,
1226
+ findFolder,
1227
+ folderName,
1228
+ hasControlSource,
1229
+ homeControlDir,
1230
+ homeExportDir,
1231
+ isSourcePath,
1232
+ joinPath,
1233
+ packBuildFiles,
1234
+ pullControl,
1235
+ readBuildState,
1236
+ slotCells,
1237
+ sourceStateKey,
1238
+ sourcesDigest,
1239
+ sourcesKey,
1240
+ templateDir,
1241
+ unbuiltChanges,
1242
+ verifyBundle,
1243
+ watchControl,
1244
+ writeBuildState,
1245
+ writeHomeFile,
1246
+ writeSourceFile
1247
+ };
1248
+ //# sourceMappingURL=aiws_ext.mjs.map