@lazyingart/agent-web 0.1.40

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 (42) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +438 -0
  3. package/docs/architecture.md +503 -0
  4. package/package.json +43 -0
  5. package/src/aginti-adapter.js +602 -0
  6. package/src/chat-context.js +1020 -0
  7. package/src/chat-migrations.js +947 -0
  8. package/src/chat-store.js +3308 -0
  9. package/src/cli.js +134 -0
  10. package/src/cloud-server.js +2043 -0
  11. package/src/contracts.js +103 -0
  12. package/src/deterministic-context-summarizer.js +254 -0
  13. package/src/direct-chat-capability-limits.js +66 -0
  14. package/src/direct-chat-contract.js +3 -0
  15. package/src/errors.js +50 -0
  16. package/src/http-contract.js +592 -0
  17. package/src/index.js +88 -0
  18. package/src/localllm-connector.js +667 -0
  19. package/src/migrations.js +231 -0
  20. package/src/operator-health.js +184 -0
  21. package/src/password-verifier.js +131 -0
  22. package/src/service-config.js +547 -0
  23. package/src/service.js +408 -0
  24. package/src/sqlite-health.js +83 -0
  25. package/src/storage-path.js +130 -0
  26. package/src/store.js +914 -0
  27. package/src/validation.js +181 -0
  28. package/src/vision-attachment.js +404 -0
  29. package/src/web/aginti-client.js +552 -0
  30. package/src/web/aginti-protocol.js +1146 -0
  31. package/src/web/asset-map.js +462 -0
  32. package/src/web/browser-app.js +6491 -0
  33. package/src/web/cloud-session-client.js +427 -0
  34. package/src/web/direct-chat-client.js +1482 -0
  35. package/src/web/index.js +10 -0
  36. package/src/web/presentation-state.js +107 -0
  37. package/src/web/pwa-assets.js +854 -0
  38. package/src/web/pwa-update-handoff-store.js +179 -0
  39. package/src/web/safe-rendering.js +836 -0
  40. package/src/web/vision-image-client.js +546 -0
  41. package/src/web/vision-image-sanitizer.js +168 -0
  42. package/src/web/web-release.js +28 -0
@@ -0,0 +1,462 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+
4
+ import { init as initializeEsmLexer, parse as parseEsm } from "es-module-lexer";
5
+
6
+ import {
7
+ AGENT_WEB_GENERATOR_VERSION,
8
+ AGENT_WEB_KATEX_VERSION,
9
+ AGENT_WEB_MODULE_ROUTES,
10
+ AGENT_WEB_RELEASE_ROOT,
11
+ BRIGHT_APP_CSS,
12
+ agentWebBuildQuery,
13
+ agentWebCacheName,
14
+ agentWebScopeIdentity,
15
+ createAppShellHtml,
16
+ createPwaIcon,
17
+ createPwaManifest,
18
+ createServiceWorkerSource,
19
+ normalizeAgentWebBasePath,
20
+ versionedAgentWebAsset,
21
+ } from "./pwa-assets.js";
22
+
23
+ export const AGENT_WEB_ESM_LEXER_VERSION = "2.3.1";
24
+ export const STANDALONE_ROOT_CONTENT_SECURITY_POLICY = "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' blob:; connect-src 'self'; font-src 'none'; manifest-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'; worker-src 'self'";
25
+ export const STANDALONE_SHELL_SECURITY_HEADERS = Object.freeze({
26
+ "x-content-type-options": "nosniff",
27
+ "referrer-policy": "same-origin",
28
+ "x-frame-options": "DENY",
29
+ "permissions-policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=()",
30
+ "strict-transport-security": "max-age=31536000; includeSubDomains",
31
+ "cross-origin-resource-policy": "same-origin",
32
+ "cross-origin-opener-policy": "same-origin",
33
+ });
34
+
35
+ const PLACEHOLDER_DIGEST = "0".repeat(64);
36
+ const PLACEHOLDER_RELEASE = `r-${PLACEHOLDER_DIGEST}`;
37
+ const KATEX_MODULE_URL = new URL(import.meta.resolve("katex/dist/katex.mjs"));
38
+ const KATEX_PACKAGE_URL = new URL(import.meta.resolve("katex/package.json"));
39
+ const ESM_LEXER_MODULE_URL = new URL(import.meta.resolve("es-module-lexer"));
40
+ const ESM_LEXER_PACKAGE_URL = new URL("../package.json", ESM_LEXER_MODULE_URL);
41
+ const GENERATOR_URL = new URL(import.meta.url);
42
+ const utf8 = new TextEncoder();
43
+ const assetMapBrand = new WeakSet();
44
+ const assetMapConstructorToken = Object.freeze({});
45
+
46
+ function codeUnitCompare(left, right) {
47
+ return left < right ? -1 : (left > right ? 1 : 0);
48
+ }
49
+
50
+ const LOCAL_MODULES = Object.freeze({
51
+ "/assets/browser-app.js": new URL("./browser-app.js", import.meta.url),
52
+ "/assets/pwa-update-handoff-store.js": new URL("./pwa-update-handoff-store.js", import.meta.url),
53
+ "/assets/cloud-session-client.js": new URL("./cloud-session-client.js", import.meta.url),
54
+ "/assets/web-release.js": new URL("./web-release.js", import.meta.url),
55
+ "/assets/direct-chat-client.js": new URL("./direct-chat-client.js", import.meta.url),
56
+ "/assets/vision-image-client.js": new URL("./vision-image-client.js", import.meta.url),
57
+ "/assets/vision-image-sanitizer.js": new URL("./vision-image-sanitizer.js", import.meta.url),
58
+ "/assets/aginti-client.js": new URL("./aginti-client.js", import.meta.url),
59
+ "/assets/aginti-protocol.js": new URL("./aginti-protocol.js", import.meta.url),
60
+ "/assets/presentation-state.js": new URL("./presentation-state.js", import.meta.url),
61
+ "/assets/pwa-assets.js": new URL("./pwa-assets.js", import.meta.url),
62
+ "/assets/safe-rendering.js": new URL("./safe-rendering.js", import.meta.url),
63
+ });
64
+
65
+ function exactOptions(value) {
66
+ if (value === null || typeof value !== "object" || Array.isArray(value)
67
+ || Object.getPrototypeOf(value) !== Object.prototype) throw new TypeError("asset map options must be a plain object");
68
+ const descriptors = Object.getOwnPropertyDescriptors(value);
69
+ const allowed = new Set(["bootstrapSource", "katexSource", "versionLabel", "basePath", "title", "loginPath", "name", "shortName"]);
70
+ for (const key of Reflect.ownKeys(descriptors)) {
71
+ if (typeof key !== "string" || !allowed.has(key) || !descriptors[key].enumerable || !Object.hasOwn(descriptors[key], "value")) {
72
+ throw new TypeError("asset map options contain an unsupported field or accessor");
73
+ }
74
+ }
75
+ return value;
76
+ }
77
+
78
+ function source(value, name, maximum = 2 * 1024 * 1024) {
79
+ if (typeof value !== "string" || value.length < 1 || value.length > maximum || /\u0000/u.test(value)) {
80
+ throw new TypeError(`${name} is invalid`);
81
+ }
82
+ return value;
83
+ }
84
+
85
+ function releaseLabel(value) {
86
+ if (value === undefined) return "r";
87
+ if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._~-]{0,23}$/u.test(value)) {
88
+ throw new TypeError("versionLabel must be a portable 1-24 character label");
89
+ }
90
+ return value;
91
+ }
92
+
93
+ function frozenHeaders(headers = {}) {
94
+ if (headers === null || typeof headers !== "object" || Array.isArray(headers)
95
+ || Object.getPrototypeOf(headers) !== Object.prototype) throw new TypeError("asset headers are invalid");
96
+ const result = {};
97
+ for (const [name, value] of Object.entries(headers)) {
98
+ if (!/^[a-z][a-z0-9-]{0,63}$/u.test(name) || typeof value !== "string" || /[\r\n\u0000]/u.test(value)) {
99
+ throw new TypeError("asset header is invalid");
100
+ }
101
+ result[name] = value;
102
+ }
103
+ return Object.freeze(result);
104
+ }
105
+
106
+ function descriptor(contentType, body, {
107
+ cacheControl = "no-store",
108
+ headers = {},
109
+ } = {}) {
110
+ if (typeof contentType !== "string" || !/^[a-z]+\/[a-z0-9.+-]+(?:; charset=utf-8)?$/u.test(contentType)) {
111
+ throw new TypeError("asset contentType is invalid");
112
+ }
113
+ if (typeof body !== "string" && !(body instanceof Uint8Array)) throw new TypeError("asset body is invalid");
114
+ return Object.freeze({
115
+ contentType,
116
+ cacheControl,
117
+ headers: frozenHeaders(headers),
118
+ body: body instanceof Uint8Array ? body.slice() : body,
119
+ });
120
+ }
121
+
122
+ function cloneDescriptor(value) {
123
+ return Object.freeze({
124
+ contentType: value.contentType,
125
+ cacheControl: value.cacheControl,
126
+ headers: Object.freeze({ ...value.headers }),
127
+ body: value.body instanceof Uint8Array ? value.body.slice() : value.body,
128
+ });
129
+ }
130
+
131
+ function bodyBytes(body) {
132
+ return body instanceof Uint8Array ? body : utf8.encode(body);
133
+ }
134
+
135
+ function sha256(value) {
136
+ return createHash("sha256").update(value).digest("hex");
137
+ }
138
+
139
+ function canonicalEntryRecords(entries) {
140
+ return entries.map(([route, value]) => ({
141
+ route,
142
+ contentType: value.contentType,
143
+ cacheControl: value.cacheControl,
144
+ headers: Object.fromEntries(Object.entries(value.headers).sort(([left], [right]) => codeUnitCompare(left, right))),
145
+ size: bodyBytes(value.body).byteLength,
146
+ bodySha256: sha256(bodyBytes(value.body)),
147
+ })).sort((left, right) => codeUnitCompare(left.route, right.route));
148
+ }
149
+
150
+ function digestEntrySet(entries, extra = {}) {
151
+ return sha256(utf8.encode(JSON.stringify({
152
+ schemaVersion: "1",
153
+ entries: canonicalEntryRecords(entries),
154
+ extra,
155
+ })));
156
+ }
157
+
158
+ function canonicalMime(contentType) {
159
+ return contentType.toLowerCase().split(";", 1)[0].trim();
160
+ }
161
+
162
+ function shellIntegrity(entries) {
163
+ return Object.freeze(entries.map(([url, value]) => Object.freeze({
164
+ url,
165
+ contentType: canonicalMime(value.contentType),
166
+ headers: Object.freeze(Object.fromEntries(Object.entries(value.headers).sort(([left], [right]) => codeUnitCompare(left, right)))),
167
+ sha256: sha256(bodyBytes(value.body)),
168
+ size: bodyBytes(value.body).byteLength,
169
+ })));
170
+ }
171
+
172
+ async function moduleSpecifiers(value, name) {
173
+ let imports;
174
+ try {
175
+ await initializeEsmLexer;
176
+ [imports] = await parseEsm(value, name);
177
+ } catch {
178
+ throw new TypeError(`${name} is not valid ECMAScript module source`);
179
+ }
180
+ if (!Array.isArray(imports)) throw new TypeError(`${name} could not be lexed safely`);
181
+ const results = [];
182
+ for (const entry of imports) {
183
+ if (entry === null || typeof entry !== "object" || !Number.isSafeInteger(entry.s) || !Number.isSafeInteger(entry.e)
184
+ || !Number.isSafeInteger(entry.d) || entry.s < 0 || entry.e < entry.s || entry.e > value.length) {
185
+ throw new TypeError(`${name} contains an invalid module-lexer record`);
186
+ }
187
+ if (entry.d === -2) continue;
188
+ if (typeof entry.n !== "string") throw new TypeError(`${name} contains a non-literal dynamic import`);
189
+ if (entry.n.length < 1 || entry.n.length > 512) throw new TypeError(`${name} contains an invalid module specifier`);
190
+ results.push(entry.n);
191
+ }
192
+ return Object.freeze([...new Set(results)]);
193
+ }
194
+
195
+ function resolveModuleDependency(importerRoute, specifier, releaseNamespace) {
196
+ if (!specifier.startsWith("./") && !specifier.startsWith("../")) {
197
+ throw new TypeError(`${importerRoute} contains a non-relative module import`);
198
+ }
199
+ if (/[\\?#\u0000-\u001f\u007f]/u.test(specifier) || /%(?:2e|2f|5c)/iu.test(specifier)) {
200
+ throw new TypeError(`${importerRoute} contains a version-ambiguous module import`);
201
+ }
202
+ const resolved = new URL(specifier, `https://assets.invalid${importerRoute}`);
203
+ if (resolved.origin !== "https://assets.invalid" || !resolved.pathname.startsWith(`${releaseNamespace}/`)) {
204
+ throw new TypeError(`${importerRoute} imports outside its immutable release namespace`);
205
+ }
206
+ return resolved.pathname;
207
+ }
208
+
209
+ async function validateModuleGraph(entries, entryRoute, expectedRoutes, releaseNamespace) {
210
+ const modules = new Map(entries.filter(([, value]) => canonicalMime(value.contentType) === "text/javascript"));
211
+ const dependencies = new Map();
212
+ for (const [route, value] of modules) {
213
+ const imports = (await moduleSpecifiers(value.body, route))
214
+ .map((specifier) => resolveModuleDependency(route, specifier, releaseNamespace));
215
+ for (const dependency of imports) {
216
+ if (!modules.has(dependency)) throw new TypeError(`${route} imports missing immutable module ${dependency}`);
217
+ }
218
+ dependencies.set(route, imports);
219
+ }
220
+ const visited = new Set();
221
+ const queue = [entryRoute];
222
+ while (queue.length) {
223
+ const route = queue.shift();
224
+ if (visited.has(route)) continue;
225
+ if (!modules.has(route)) throw new TypeError(`PWA module entry is missing: ${route}`);
226
+ visited.add(route);
227
+ queue.push(...(dependencies.get(route) ?? []));
228
+ }
229
+ const unreachable = expectedRoutes.filter((route) => !visited.has(route));
230
+ if (unreachable.length) throw new TypeError(`PWA bootstrap does not reach modules: ${unreachable.join(", ")}`);
231
+ return Object.freeze([...visited].sort(codeUnitCompare));
232
+ }
233
+
234
+ function buildShellEntries({
235
+ releaseVersion,
236
+ basePath,
237
+ title,
238
+ loginPath,
239
+ name,
240
+ shortName,
241
+ bootstrap,
242
+ katex,
243
+ localModules,
244
+ }) {
245
+ const scope = normalizeAgentWebBasePath(basePath);
246
+ const base = scope === "/" ? "" : scope.slice(0, -1);
247
+ const versioned = (route) => `${base}${versionedAgentWebAsset(route, releaseVersion)}`;
248
+ const manifest = createPwaManifest({ basePath: scope, name, shortName, version: releaseVersion });
249
+ const secured = (contentType, body, headers = {}) => descriptor(contentType, body, {
250
+ headers: { ...STANDALONE_SHELL_SECURITY_HEADERS, ...headers },
251
+ });
252
+ const root = secured("text/html; charset=utf-8", createAppShellHtml({ basePath: scope, title, loginPath, version: releaseVersion }), {
253
+ "content-security-policy": STANDALONE_ROOT_CONTENT_SECURITY_POLICY,
254
+ });
255
+ return [
256
+ [scope, root],
257
+ [`${scope}${agentWebBuildQuery(releaseVersion)}`, root],
258
+ [`${base}/manifest.webmanifest${agentWebBuildQuery(releaseVersion)}`, secured("application/manifest+json; charset=utf-8", `${JSON.stringify(manifest)}\n`)],
259
+ [versioned("/assets/app.css"), secured("text/css; charset=utf-8", BRIGHT_APP_CSS)],
260
+ [versioned("/assets/app.js"), secured("text/javascript; charset=utf-8", bootstrap)],
261
+ [versioned("/assets/katex.mjs"), secured("text/javascript; charset=utf-8", katex)],
262
+ [versioned("/assets/icon-192.png"), secured("image/png", createPwaIcon(192))],
263
+ [versioned("/assets/icon-512.png"), secured("image/png", createPwaIcon(512))],
264
+ ...localModules.map(([route, moduleSource]) => [
265
+ versioned(route),
266
+ secured("text/javascript; charset=utf-8", moduleSource),
267
+ ]),
268
+ ];
269
+ }
270
+
271
+ function workerDescriptor(body) {
272
+ return descriptor("text/javascript; charset=utf-8", body, {
273
+ cacheControl: "no-store, no-cache, must-revalidate",
274
+ headers: { pragma: "no-cache", expires: "0" },
275
+ });
276
+ }
277
+
278
+ function checkedMapMetadata(value) {
279
+ const keys = [
280
+ "releaseVersion", "contentDigest", "finalMapDigest", "finalDigestContext", "generatorVersion", "moduleLexerVersion",
281
+ "basePath", "scopeIdentity", "cacheName", "buildQuery", "releaseNamespace", "serviceWorkerRoute", "shellRoutes", "moduleRoutes",
282
+ ];
283
+ if (value === null || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype
284
+ || Object.keys(value).sort(codeUnitCompare).join("\u0000") !== [...keys].sort(codeUnitCompare).join("\u0000")) {
285
+ throw new TypeError("asset map metadata is invalid");
286
+ }
287
+ for (const key of ["contentDigest", "finalMapDigest"]) {
288
+ if (typeof value[key] !== "string" || !/^[a-f0-9]{64}$/u.test(value[key])) throw new TypeError(`asset map ${key} is invalid`);
289
+ }
290
+ for (const key of ["releaseVersion", "generatorVersion", "moduleLexerVersion", "basePath", "scopeIdentity", "cacheName", "buildQuery", "releaseNamespace", "serviceWorkerRoute"]) {
291
+ if (typeof value[key] !== "string" || !value[key]) throw new TypeError(`asset map ${key} is invalid`);
292
+ }
293
+ if (!Array.isArray(value.shellRoutes) || !Array.isArray(value.moduleRoutes)
294
+ || [...value.shellRoutes, ...value.moduleRoutes].some((route) => typeof route !== "string" || !route.startsWith("/"))) {
295
+ throw new TypeError("asset map route metadata is invalid");
296
+ }
297
+ const context = value.finalDigestContext;
298
+ if (context === null || typeof context !== "object" || Array.isArray(context)
299
+ || Object.keys(context).sort(codeUnitCompare).join("\u0000") !== ["contentDigest", "esmLexerSourceSha256", "esmLexerVersion", "generatorVersion", "katexVersion"].sort(codeUnitCompare).join("\u0000")
300
+ || context.contentDigest !== value.contentDigest || context.generatorVersion !== value.generatorVersion
301
+ || context.esmLexerVersion !== value.moduleLexerVersion || context.katexVersion !== AGENT_WEB_KATEX_VERSION
302
+ || typeof context.esmLexerSourceSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(context.esmLexerSourceSha256)) {
303
+ throw new TypeError("asset map final digest context is invalid");
304
+ }
305
+ return value;
306
+ }
307
+
308
+ class StandalonePwaAssetMap {
309
+ #entries;
310
+
311
+ constructor(token, entries, metadata) {
312
+ if (token !== assetMapConstructorToken) throw new TypeError("Standalone PWA asset maps can only be created by the production factory");
313
+ checkedMapMetadata(metadata);
314
+ this.#entries = new Map(entries.map(([route, value]) => [route, cloneDescriptor(value)]));
315
+ this.routes = Object.freeze([...this.#entries.keys()].sort(codeUnitCompare));
316
+ for (const [key, value] of Object.entries(metadata)) this[key] = Array.isArray(value) ? Object.freeze([...value]) : value;
317
+ assetMapBrand.add(this);
318
+ Object.freeze(this);
319
+ }
320
+
321
+ has(requestTarget) { return this.#entries.has(requestTarget); }
322
+
323
+ get(requestTarget) {
324
+ const value = this.#entries.get(requestTarget);
325
+ return value === undefined ? undefined : cloneDescriptor(value);
326
+ }
327
+ }
328
+
329
+ Object.freeze(StandalonePwaAssetMap.prototype);
330
+
331
+ function snapshotEntries(assetMap) {
332
+ return assetMap.routes.map((route) => {
333
+ const value = assetMap.get(route);
334
+ if (!value) throw new TypeError(`PWA asset ${route} is missing`);
335
+ return [route, value];
336
+ });
337
+ }
338
+
339
+ export function verifyStandaloneAssetMap(assetMap) {
340
+ if (!(assetMap instanceof StandalonePwaAssetMap) || !assetMapBrand.has(assetMap)) throw new TypeError("assetMap is not a factory-authenticated map");
341
+ if (typeof assetMap.contentDigest !== "string" || !/^[a-f0-9]{64}$/u.test(assetMap.contentDigest)
342
+ || !assetMap.releaseVersion.endsWith(`-${assetMap.contentDigest}`)) {
343
+ throw new TypeError("PWA release is not bound to its content digest");
344
+ }
345
+ if (assetMap.scopeIdentity !== agentWebScopeIdentity(assetMap.basePath)
346
+ || assetMap.cacheName !== agentWebCacheName(assetMap.releaseVersion, { basePath: assetMap.basePath })) {
347
+ throw new TypeError("PWA cache identity is not bound to its normalized scope");
348
+ }
349
+ const versionedRoot = `${assetMap.basePath}${agentWebBuildQuery(assetMap.releaseVersion)}`;
350
+ if (!assetMap.has(versionedRoot)) throw new TypeError("PWA versioned navigation route is missing");
351
+ for (const route of assetMap.shellRoutes) if (!assetMap.has(route)) throw new TypeError(`PWA shell route is missing: ${route}`);
352
+ const worker = assetMap.get(assetMap.serviceWorkerRoute);
353
+ if (!worker || worker.cacheControl !== "no-store, no-cache, must-revalidate"
354
+ || worker.headers.pragma !== "no-cache" || worker.headers.expires !== "0") {
355
+ throw new TypeError("service worker response must disable intermediary caching");
356
+ }
357
+ const entries = snapshotEntries(assetMap);
358
+ if (digestEntrySet(entries, assetMap.finalDigestContext) !== assetMap.finalMapDigest) {
359
+ throw new TypeError("PWA final map digest verification failed");
360
+ }
361
+ return assetMap;
362
+ }
363
+
364
+ export const validateStandaloneAssetMap = verifyStandaloneAssetMap;
365
+
366
+ export async function createStandaloneAssetMap(options = {}) {
367
+ const input = exactOptions(options);
368
+ const bootstrap = source(input.bootstrapSource, "bootstrapSource", 512 * 1024);
369
+ const basePath = normalizeAgentWebBasePath(input.basePath ?? "/");
370
+ const label = releaseLabel(input.versionLabel);
371
+ const title = input.title ?? "LazyingArt Agent";
372
+ const loginPath = input.loginPath ?? "/api/login";
373
+ const name = input.name ?? "LazyingArt Agent";
374
+ const shortName = input.shortName ?? "Lazying Agent";
375
+ const [installedKatex, katexPackageText, generatorSource, esmLexerSource, esmLexerPackageText, ...moduleTexts] = await Promise.all([
376
+ readFile(KATEX_MODULE_URL, "utf8"),
377
+ readFile(KATEX_PACKAGE_URL, "utf8"),
378
+ readFile(GENERATOR_URL, "utf8"),
379
+ readFile(ESM_LEXER_MODULE_URL, "utf8"),
380
+ readFile(ESM_LEXER_PACKAGE_URL, "utf8"),
381
+ ...Object.values(LOCAL_MODULES).map((url) => readFile(url, "utf8")),
382
+ ]);
383
+ const katexPackage = JSON.parse(katexPackageText);
384
+ const esmLexerPackage = JSON.parse(esmLexerPackageText);
385
+ if (katexPackage.version !== AGENT_WEB_KATEX_VERSION) throw new TypeError(`installed KaTeX must be exactly ${AGENT_WEB_KATEX_VERSION}`);
386
+ if (esmLexerPackage.version !== AGENT_WEB_ESM_LEXER_VERSION) throw new TypeError(`installed es-module-lexer must be exactly ${AGENT_WEB_ESM_LEXER_VERSION}`);
387
+ if (input.katexSource !== undefined && source(input.katexSource, "katexSource") !== installedKatex) {
388
+ throw new TypeError(`katexSource must exactly match installed pinned KaTeX ${AGENT_WEB_KATEX_VERSION}`);
389
+ }
390
+ const katex = installedKatex;
391
+ const localModules = Object.keys(LOCAL_MODULES).map((route, index) => [route, source(moduleTexts[index], route)]);
392
+ const placeholderShell = buildShellEntries({
393
+ releaseVersion: PLACEHOLDER_RELEASE,
394
+ basePath,
395
+ title,
396
+ loginPath,
397
+ name,
398
+ shortName,
399
+ bootstrap,
400
+ katex,
401
+ localModules,
402
+ });
403
+ const base = basePath === "/" ? "" : basePath.slice(0, -1);
404
+ const placeholderWorker = createServiceWorkerSource({
405
+ basePath,
406
+ version: PLACEHOLDER_RELEASE,
407
+ contentDigest: PLACEHOLDER_DIGEST,
408
+ shellAssets: shellIntegrity(placeholderShell),
409
+ });
410
+ const templateEntries = [...placeholderShell, [`${base}/sw.js`, workerDescriptor(placeholderWorker)]];
411
+ const templateContext = Object.freeze({
412
+ generatorVersion: AGENT_WEB_GENERATOR_VERSION,
413
+ generatorSourceSha256: sha256(utf8.encode(generatorSource)),
414
+ esmLexerVersion: AGENT_WEB_ESM_LEXER_VERSION,
415
+ esmLexerSourceSha256: sha256(utf8.encode(esmLexerSource)),
416
+ katexVersion: AGENT_WEB_KATEX_VERSION,
417
+ });
418
+ const contentDigest = digestEntrySet(templateEntries, templateContext);
419
+ const releaseVersion = `${label}-${contentDigest}`;
420
+ const shellEntries = buildShellEntries({ releaseVersion, basePath, title, loginPath, name, shortName, bootstrap, katex, localModules });
421
+ const versioned = (route) => `${base}${versionedAgentWebAsset(route, releaseVersion)}`;
422
+ const releaseNamespace = `${base}${AGENT_WEB_RELEASE_ROOT}/${encodeURIComponent(releaseVersion)}`;
423
+ const expectedModuleRoutes = AGENT_WEB_MODULE_ROUTES.map(versioned);
424
+ const reachableModules = await validateModuleGraph(shellEntries, versioned("/assets/app.js"), expectedModuleRoutes, releaseNamespace);
425
+ const serviceWorkerRoute = `${base}/sw.js`;
426
+ const shellRoutes = shellEntries.map(([route]) => route);
427
+ const worker = createServiceWorkerSource({
428
+ basePath,
429
+ version: releaseVersion,
430
+ contentDigest,
431
+ shellAssets: shellIntegrity(shellEntries),
432
+ });
433
+ const entries = [...shellEntries, [serviceWorkerRoute, workerDescriptor(worker)]];
434
+ const finalDigestContext = Object.freeze({
435
+ contentDigest,
436
+ generatorVersion: AGENT_WEB_GENERATOR_VERSION,
437
+ esmLexerVersion: AGENT_WEB_ESM_LEXER_VERSION,
438
+ esmLexerSourceSha256: sha256(utf8.encode(esmLexerSource)),
439
+ katexVersion: AGENT_WEB_KATEX_VERSION,
440
+ });
441
+ const finalMapDigest = digestEntrySet(entries, finalDigestContext);
442
+ const map = new StandalonePwaAssetMap(assetMapConstructorToken, entries, {
443
+ releaseVersion,
444
+ contentDigest,
445
+ finalMapDigest,
446
+ finalDigestContext,
447
+ generatorVersion: AGENT_WEB_GENERATOR_VERSION,
448
+ moduleLexerVersion: AGENT_WEB_ESM_LEXER_VERSION,
449
+ basePath,
450
+ scopeIdentity: agentWebScopeIdentity(basePath),
451
+ cacheName: agentWebCacheName(releaseVersion, { basePath }),
452
+ buildQuery: agentWebBuildQuery(releaseVersion),
453
+ releaseNamespace,
454
+ serviceWorkerRoute,
455
+ shellRoutes,
456
+ moduleRoutes: reachableModules,
457
+ });
458
+ for (const logicalRoute of ["/assets/app.css", ...AGENT_WEB_MODULE_ROUTES, "/assets/icon-192.png", "/assets/icon-512.png"]) {
459
+ if (map.has(`${base}${logicalRoute}`)) throw new TypeError("unversioned release asset leaked into the map");
460
+ }
461
+ return verifyStandaloneAssetMap(map);
462
+ }