@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,503 @@
1
+ # LazyingAgentWeb architecture
2
+
3
+ This document describes the implemented standalone package boundary.
4
+ Production is promoted independently from repository commits, with immutable
5
+ acceptance receipts and a verified rollback release. The historical Agent gate
6
+ is fail-closed. The v0.1.29 candidate remains compatible with current v0.1.27
7
+ production, preserving its capability-gated Search UI and enabling Agent only through the
8
+ accepted native AgInTi capability proof; without that proof, Agent remains
9
+ unavailable while Direct Chat continues as a separate LocalLLM data plane.
10
+ Voice messages and general artifact uploads remain unavailable. The file
11
+ artifact extension is limited to capability-gated, local-only PDF/TeX output;
12
+ AgInTi owns its bytes and deletion lifecycle, while the BFF is a non-caching
13
+ authenticated streaming surface.
14
+ Grounded search is a backward-compatible, default-disabled protocol extension:
15
+ the UI remains absent
16
+ unless AgInTi proves the exact Search capability, and current production makes
17
+ no claim that it does. The baseline Agent artifact surface is declarative plot,
18
+ table, and Markdown; the negotiated extension adds only bounded text/HTTPS
19
+ `sources` artifacts. A separately advertised `file` kind adds only bounded
20
+ filename/MIME/size/digest metadata to that public envelope.
21
+ Exact requests containing one canonical fenced `python` block take a
22
+ deterministic bounded-execution path, bypass model planning, and preserve
23
+ failure and successor-run state for durable reload and exact idempotent Resume
24
+ behavior, including an optional corrected prompt. The PWA persists only the
25
+ non-private Chat/Agent workspace preference, never browser-owned chat history.
26
+ After a terminal Agent run, a later prompt resumes that exact predecessor as a
27
+ successor run instead of trying to start the thread again. Creation, start, and
28
+ resume mutations use immutable idempotency keys; one retry may resolve an
29
+ uncertain transport result without duplicating a run. A rejected prompt remains
30
+ editable and is not rendered as accepted history. Plot artifacts explicitly
31
+ occupy the workspace column and scale to the available message width, with
32
+ readable mobile ticks, non-scaling strokes, and wrapping legends.
33
+ When a release fence requires a full-page refresh while an Agent follow-up is
34
+ still unsent, the encrypted handoff retains the Agent mode and owned thread ID.
35
+ The successor page reloads and verifies that thread, derives its authoritative
36
+ terminal run, and restores the draft without dispatching it. A later Send uses
37
+ the normal Resume mutation for that predecessor; the handoff never stores a
38
+ possibly stale run ID and never routes Agent work into Direct Chat.
39
+ Once a verified Agent thread has settled, selecting that same thread is an
40
+ idempotent view operation: it preserves the existing message and artifact DOM
41
+ instead of starting a redundant ledger replay. A failed or nonterminal replay
42
+ remains reopenable so recovery is never hidden by that optimization. An Agent
43
+ mutation with an unusable response enters the same fail-closed history fence:
44
+ the draft stays editable, but another mutation is rejected until reopening the
45
+ thread completes an authoritative read without redispatching the draft.
46
+ If the thread-creation response itself remains unavailable, the browser has no
47
+ thread identity to reopen; it instead retains the exact creation body and
48
+ idempotency key in memory. The next Send confirms that same creation before
49
+ starting one run, while thread navigation, mode changes, and PWA activation stay
50
+ fenced so they cannot discard or duplicate the ambiguous operation.
51
+ Numeric x-axis ticks use the shortest precision that still distinguishes every
52
+ displayed value. Their exact values remain in per-tick accessible labels and the
53
+ plot description, so responsive compaction does not discard analytical meaning.
54
+ When a failed or cancelled predecessor has no persisted assistant message, the
55
+ browser reserves that run's chronological assistant position before replaying
56
+ verified history, so a corrected successor's output and artifacts remain after
57
+ the earlier failure instead of being visually displaced by it.
58
+
59
+ ## Product boundary
60
+
61
+ `llm.lazying.art` is one user experience over four independently useful
62
+ products. Integration does not transfer ownership between them.
63
+
64
+ | Product | Owns | Standalone surface | Never owns |
65
+ | --- | --- | --- | --- |
66
+ | LazyingAgentWeb | Cloud accounts, browser sessions, Direct Chat history/context/deltas, AgInTi presentation indexes, delivery cursors, safe rendering | PWA, cloud HTTP/BFF, SQLite stores, and Direct Chat context coordinator | Agent plans/messages/context, tools, Docker, Agent execution, inference implementation, tunnels |
67
+ | AgInTi | Agent threads, plans, context and compaction, tool/event ledger, execution, artifacts and cancellation | `aginti-cli`, Studio, native authenticated integration API | Cloud login, TLS ingress, LocalLLM implementation |
68
+ | LocalLLM | Text, coding, embedding and vision inference | OpenAI-compatible loopback API and its own local UI | Agent orchestration, cloud accounts, transport |
69
+ | LazyEdge | Exact authenticated transport between reviewed loopback services | CLI, manifests, doctor, render/apply/rollback operations | Chat or agent semantics, planning, inference, cloud persistence |
70
+
71
+ The PWA presents two explicit modes:
72
+
73
+ - **Chat** is a cloud-owned conversation product backed by stateless LocalLLM
74
+ inference. Its messages and bounded chat-only context summaries may be stored
75
+ by LazyingAgentWeb.
76
+ - **AgInTi Agent** is an alternate frontend for AgInTi. AgInTi remains the only
77
+ authority for its messages, context, compaction, plans, tools, runs and
78
+ artifacts. The cloud stores only presentation pointers and delivery cursors.
79
+
80
+ No code in LazyEdge implements either mode. It transports declared exact HTTP
81
+ contracts and can be replaced without migrating chat or agent state.
82
+
83
+ ## Data planes
84
+
85
+ Direct chat:
86
+
87
+ ```text
88
+ browser -> Caddy -> LazyingAgentWeb -> LazyEdge -> LocalLLM
89
+ ```
90
+
91
+ Agent mode:
92
+
93
+ ```text
94
+ browser -> Caddy -> LazyingAgentWeb -> LazyEdge -> AgInTi native API
95
+ |-> LocalLLM
96
+ `-> rootless sandbox
97
+ ```
98
+
99
+ LazyingAgentWeb owns the stateless adapter at the application boundary. It
100
+ derives `x-aginti-principal-id` and `x-aginti-browser-session-id` from the
101
+ authenticated cloud session and uses standard `Idempotency-Key` only for
102
+ mutations. LazyEdge treats these as opaque application headers while stripping
103
+ its own reserved `x-lazyedge-*` namespace. AgInTi alone interprets the identity,
104
+ idempotency, thread, run, context, tool, and artifact semantics.
105
+
106
+ The browser never receives a relay, LocalLLM, AgInTi-integration, SSH or
107
+ sandbox credential. Each arrow authenticates independently. Caddy exposes only
108
+ reviewed exact web routes; every internal listener remains loopback-only.
109
+
110
+ ## Cloud HTTP/BFF boundary
111
+
112
+ The implemented cloud server is a root-only Node service intended to bind on
113
+ `127.0.0.1` behind Caddy. It serves a factory-branded PWA asset map and exact
114
+ login, session, Direct Chat, and AgInTi-transport routes. It enforces the
115
+ configured HTTPS public origin, Caddy-overwritten authority/client-address
116
+ headers, Fetch Metadata, CSRF, idempotency, exact request schemas, body/time
117
+ limits, bounded per-session and global stream admission, and graceful draining
118
+ of Direct Chat jobs.
119
+
120
+ Every browser API response carries the immutable current release identity. New
121
+ shells pin that identity on each request; an explicit mismatch is rejected
122
+ before body ingestion, while a missing request header remains a bounded
123
+ compatibility path for already-open predecessor tabs. Exact-origin iOS and PWA
124
+ requests may omit all or part of otherwise-valid Fetch Metadata: login remains
125
+ available, session/logout retain their normal CSRF rules, and Chat/Agent proceed
126
+ only after the browser session and CSRF mutation proof validate. Any present
127
+ wrong Fetch Metadata value still fails closed. Bounded outcome records contain only route/status
128
+ categories and these gate results—never prompts, identifiers, cookies, tokens,
129
+ or credentials.
130
+
131
+ All owner identity is derived from the verified browser session. Public JSON
132
+ and SSE responses are copied through allowlisted projections and never expose
133
+ the internal `accountId`. The BFF accepts a stateless AgInTi adapter interface;
134
+ it does not persist adapter credentials or acquire Agent authority. Missing,
135
+ malformed, or unaccepted AgInTi capabilities collapse to the frozen disabled
136
+ capability contract.
137
+
138
+ The HTTP server does not terminate public TLS, manage Caddy or LazyEdge, launch
139
+ LocalLLM, start AgInTi, or create a sandbox. Those remain independently operated
140
+ components.
141
+
142
+ ## Persistence
143
+
144
+ Cloud-authoritative storage is divided so ownership is visible in the schema:
145
+
146
+ 1. identity, browser sessions and CSRF bindings;
147
+ 2. direct-chat threads, messages and chat-only context windows;
148
+ 3. non-authoritative AgInTi thread presentation pointers;
149
+ 4. non-authoritative event delivery sequence/hash cursors;
150
+ 5. closed idempotency receipts, immutable Direct Chat deletion receipts, and
151
+ bounded retention metadata.
152
+
153
+ `CloudIndexStore` and `DirectChatStore` use separate SQLite application IDs and
154
+ migration chains. `CloudIndexStore` holds items 1, 3, 4, and their bounded
155
+ receipts. `DirectChatStore` holds item 2, a hash-linked immutable message ledger,
156
+ replayable generation deltas, terminal receipts, compaction snapshots, and
157
+ durable dispatch-lease metadata. When Direct Chat vision is enabled, it also
158
+ holds immutable canonical attachment bytes in an owner-private table; the
159
+ message ledger contains only the attachment MIME, size, dimensions, opaque ID,
160
+ and SHA-256 descriptor bound into its hash. The split makes it difficult for a future
161
+ presentation migration to silently acquire Direct Chat or Agent authority.
162
+
163
+ Schema v5 gives Direct Chat hard deletion an explicit durable authority. The
164
+ exact `POST /api/chat/threads/delete` mutation binds the authenticated account,
165
+ thread ID, current revision/hash cursor, CSRF proof, and idempotency key. It
166
+ refuses a current generation, stale cursor, or trailing user message with
167
+ unresolved send acceptance. In one immediate transaction, an immutable,
168
+ content-free receipt retaining only identity, cursor metadata, and digests is
169
+ written before the messages, attachments, generation state, compactions,
170
+ mutation receipts, and owning thread are removed. Exact ambiguous retries
171
+ replay the raw-key-free receipt, and its account/thread uniqueness permanently
172
+ retires the thread identifier. This authority applies only to cloud-owned
173
+ Direct Chat threads; Agent presentation-index removal and authoritative AgInTi
174
+ thread deletion remain separate operations.
175
+
176
+ ### Private data and cache placement
177
+
178
+ The browser does not persist ordinary threads, messages, generations, image
179
+ previews, session tokens, or retry state in Cache Storage, localStorage,
180
+ sessionStorage, or IndexedDB. Authenticated history remains authoritative in
181
+ the cloud SQLite stores. The selected composer images and rendered attachment
182
+ URLs are page-memory state and are revoked at view and authentication
183
+ boundaries. Historical attachments are fetched only as their messages approach
184
+ the viewport, with bounded concurrency and a 16 MiB per-tab Blob LRU.
185
+ Historical object URLs and decoded surfaces have their own four-preview / 64
186
+ MiB estimated decoded-pixel LRU. Evicting either tier revokes the affected URL
187
+ and restores a tap-to-reload placeholder; the compressed-Blob bound therefore
188
+ cannot be bypassed by a historical image element retaining an evicted Blob.
189
+ Both history tiers are disposable, account-scoped by lifecycle rather than
190
+ durable identity, and are purged on logout, authentication loss, account
191
+ transition, or release activation. Up to four staged or just-sent composer images
192
+ are separate transient page memory and are revoked at their send, view, and
193
+ authentication boundaries. The encrypted, expiring, one-shot confirmed-update
194
+ composer handoff described below is the only narrow IndexedDB exception and
195
+ accepts at most four images and 16 MiB of canonical bytes.
196
+
197
+ The server may reuse a completed per-thread integrity audit from a bounded
198
+ in-process LRU. Entries are keyed by account and thread, guarded by SQLite's
199
+ `data_version`, and cleared before and after every local write transaction.
200
+ An external connection commit changes that version and invalidates the entire
201
+ store-local audit cache before another result can be reused. Write
202
+ preconditions and postconditions always bypass the cache, and opening a store
203
+ still performs the full database audit. This cache stores only the fact that a
204
+ specific database snapshot passed validation; it is not a second message or
205
+ attachment store.
206
+
207
+ Remembered browser sessions are independently bounded per account. Admission
208
+ purges expired rows first; when an account is full, the same immediate
209
+ transaction removes exactly its oldest-issued active session and inserts the
210
+ new digest-only session. Existing-token collisions are rejected before any
211
+ eviction, and deterministic selection plus account-qualified deletion prevents
212
+ cross-account rotation.
213
+
214
+ Schema v5 is the common Direct Chat migration target because deletion safety
215
+ depends on its durable authority receipts. The ordered schema-v4 attachment
216
+ tables are therefore materialized even when vision is disabled; attachment use
217
+ remains gated at the application boundary. Existing v3 attachment rows migrate
218
+ to ordered position zero atomically. A v5-aware service running with vision
219
+ disabled still serves authenticated previews and exact retries of committed
220
+ turns, but refuses new image turns and follow-ups that would reuse stored images.
221
+
222
+ A pre-v5 binary cannot reopen the migrated database. The activation boundary
223
+ blocks every dynamic API, stops the service, verifies sidecar-free SQLite
224
+ `DELETE` journal state, takes an offline private database backup, and preflights
225
+ a copy with the candidate release. That snapshot is restorable only while all
226
+ dynamic APIs remain blocked and before any v5 write authority or deletion API
227
+ activation. After that boundary, every rollback preserves the live v5 database
228
+ and uses a v5-aware binary; an older snapshot could discard accepted messages
229
+ or deletion authority.
230
+
231
+ The cloud database must never contain AgInTi plans, agent context or summaries,
232
+ tool calls/results, commands, workspace paths, runtime policy, raw artifacts or
233
+ Docker state. Losing the presentation index cannot destroy an AgInTi thread.
234
+
235
+ AgInTi persists an append-only, hash-linked typed event ledger and its own
236
+ authoritative snapshots. Compaction records the exact source range, ledger
237
+ head, policy/permission digests, preserved evidence and unresolved work; it
238
+ does not rewrite the source ledger. Tool side effects are idempotent in AgInTi,
239
+ not reconstructed by the cloud.
240
+
241
+ Direct Chat compaction is different and remains cloud-owned. The
242
+ `DirectChatContextCoordinator` independently revalidates every source message
243
+ and hash, preserves a bounded exact recent suffix, and may ask only an injected
244
+ local summarizer to compact completed history. A summary carries exact source
245
+ revision/hash provenance and is labeled as untrusted conversation data with no
246
+ system, developer, policy, tool, or instruction authority. The coordinator
247
+ prepares capacity before the atomic turn starts; it never rewrites history or
248
+ runs a summarizer while a generation is active.
249
+
250
+ The standalone service injects a deterministic, networkless summarizer by
251
+ default. It does not call LocalLLM or any hosted model, so proactive compaction
252
+ cannot create a second inference outside the durable global dispatch fence. A
253
+ future model-assisted summarizer would require the same cross-process
254
+ admission, lease, and recovery contract as normal Direct Chat generation.
255
+
256
+ ## Streaming and recovery
257
+
258
+ Direct Chat:
259
+
260
+ - The PWA accepts one to four JPEG, PNG, HEIC, or HEIF still images and requires
261
+ a text prompt. It accepts source files up to 24 MiB. JPEG/PNG source geometry
262
+ is checked before decode; HEIC/HEIF files pass a bounded, byte-authoritative
263
+ ISO-BMFF `ftyp` classifier before a feature-detected native decode, then face
264
+ the same decoded-pixel bound. AVIF, sequences, conflicting brands, and
265
+ malformed boxes fail closed. Canvas redraw/downscaling discards source
266
+ metadata, and only canonical JPEG/PNG output may cross the wire or enter
267
+ storage. Native preparation has an abort signal, one bounded deadline, a
268
+ visible `Preparing images…` state, and epoch fences for mode, session, logout,
269
+ and service-worker controller changes. Canonical output is limited to 4 MiB
270
+ per image and 16 MiB per message. Its visible composer/message preview is
271
+ independently bounded to 512 pixels and 512 KiB per image. No image is placed in
272
+ Cache Storage, localStorage, or sessionStorage. A user-confirmed PWA update
273
+ may place one encrypted, expiring, one-shot multi-image unsent-composer handoff in a
274
+ dedicated IndexedDB store; it is never history, a send queue, or auto-sent.
275
+ - The BFF independently validates canonical base64, MIME/signature, structure,
276
+ dimensions, metadata absence, digest, order, unique identifiers, count, and
277
+ aggregate bytes. It atomically commits the prompt, descriptor-bound ledger
278
+ row, all private BLOBs, and pending generation. Public message records contain
279
+ only descriptors; authenticated preview bytes are
280
+ same-origin `no-store` responses.
281
+ - The browser prepares stable thread/message/generation/idempotency identifiers.
282
+ The store commits the user message and pending generation atomically, so an
283
+ ambiguous retry returns the same turn without a second dispatch intent. Image
284
+ JSON is serialized once before dispatch. On a lost response the browser first
285
+ probes the stable generation ID; it re-uploads only when an authoritative 404
286
+ proves absence. Once accepted, raw composer bytes and the serialized retry
287
+ ticket are released before generation finishes.
288
+ - Direct Chat thread deletion is a separate cursor-bound, idempotent POST. The
289
+ browser disables it during history restoration, finalization, an active
290
+ generation, or ambiguous send acceptance; it clears the selected presentation
291
+ only after authoritative success. A transport ambiguity retries the identical
292
+ prepared ticket. The server's durable receipt permits exact replay while
293
+ permanently preventing reuse of the deleted thread ID.
294
+ - Before calling LocalLLM, one cloud worker claims a durable lease with a
295
+ monotonic fence and marks dispatch started. Append, finalize, failure, and
296
+ renewal require that proof. A restarted or stale worker cannot continue after
297
+ losing the lease, and a partially streamed stateless generation is never
298
+ blindly redispatched.
299
+ - Each bounded text delta is persisted before it is exposed. Browser reconnect
300
+ replays after its last sequence, then follows new SSE data. Finalization adds
301
+ one assistant message to the same hash-linked ledger exactly once.
302
+ - Explicit Stop durably cancels the generation and invalidates its lease.
303
+ Viewer disconnect only detaches the stream; server job limits and shutdown
304
+ draining remain authoritative. Upload reading has a bounded four-minute
305
+ ceiling for the largest valid request; accepted text generation remains
306
+ bounded to two minutes and accepted vision generation has a separate
307
+ ten-minute ceiling.
308
+
309
+ AgInTi Agent:
310
+
311
+ - A start mutation uses a caller-generated idempotency key. Ambiguous delivery
312
+ is retried with the same key; AgInTi must return the original run instead of
313
+ dispatching again.
314
+ - AgInTi persists an event before emitting it and assigns a strictly increasing
315
+ sequence plus hash-chain fields. The cloud validates and reserializes only a
316
+ bounded public envelope; it does not cache or regenerate the authoritative
317
+ event.
318
+ - Browser reconnect supplies the last accepted sequence/hash. Reconnect only
319
+ replays; it never starts or resumes work.
320
+ - Viewer disconnect detaches. Explicit Stop is an idempotent AgInTi
321
+ cancellation. If the tunnel is offline, the UI says cancellation is pending
322
+ until AgInTi confirms a terminal state; AgInTi's hard runtime limit remains
323
+ the final bound.
324
+
325
+ ## PWA release and update lifecycle
326
+
327
+ `createStandaloneAssetMap()` derives each immutable release identifier from the
328
+ complete shell content plus pinned generator, module-lexer, and KaTeX build
329
+ inputs. The branded map records a second digest over final descriptors. The
330
+ HTML metadata, service-worker cache name, manifest, exact security headers, and
331
+ complete browser module graph must all prove the same release. JavaScript, CSS,
332
+ KaTeX, and icons live below `/assets/r/<release>/`; relative module imports
333
+ therefore cannot mix files from two deployments.
334
+
335
+ `/sw.js` is deliberately stable and is always served with `no-store`,
336
+ `no-cache`, and `must-revalidate`. The browser registers that one URL with
337
+ `updateViaCache: "none"`. An already-controlled page checks it at startup and
338
+ on a bounded foreground or online transition; a fresh uncontrolled install
339
+ skips the redundant immediate check and joins the same periodic schedule. The
340
+ stable registration can therefore discover v1, v2, v3, and later releases
341
+ without another version endpoint. A successor installs a separate complete
342
+ shell cache but remains waiting. Before showing anything, the page asks that
343
+ specific worker to prove its immutable release over a one-shot message channel.
344
+ A positively verified worker matching the loaded HTML is silent; a verified
345
+ successor, or a legacy worker that cannot answer within the bounded proof
346
+ window, is offered through **Update** or **Later**:
347
+
348
+ - **Later** leaves the current worker and offline shell untouched.
349
+ - **Update** explicitly requests activation and reloads the page exactly once
350
+ after `controllerchange`.
351
+ - A failed or offline update leaves the current app usable and retries after
352
+ connectivity returns.
353
+
354
+ A confirmed Update or explicit API release mismatch can carry a definitively
355
+ unsent Direct Chat composer, an idle Agent composer, or an Agent draft following
356
+ an authoritatively verified terminal run across that reload. Active sends or
357
+ generations, nonterminal Agent work, ambiguous mutations, passwords, and other
358
+ browser-held workflows remain reload blockers. The page encrypts a bounded
359
+ record with AES-GCM. Its v0.1.29 inner payload schema v3 preserves the exact
360
+ mode, owned Agent thread ID when present, explicit Search settings or No Search,
361
+ draft, and up to four canonical images.
362
+
363
+ The random key exists only in a URL fragment and is therefore never sent in an
364
+ HTTP request. The page retains that fragment until the authenticated atomic
365
+ take and decryption succeed, instead of orphaning the encrypted IndexedDB row
366
+ during sign-in or a reload. It is reattached only to a bounded recovery retry or
367
+ an authenticated, chain-proved release hop, then scrubbed after successful
368
+ recovery. The account, scope, source/target release chain, age, digest, and image
369
+ contract are revalidated before the protected composer is unlocked; restoration
370
+ never dispatches a request. Malformed, expired, and excess orphan ciphertexts
371
+ are pruned.
372
+
373
+ The same v3 envelope can contain an empty exact thread selection when a release
374
+ fence interrupts same-account authenticated read recovery. That record resumes
375
+ only server-owned Agent or Direct Chat history and contains no mutation ticket.
376
+ If the session expires before the hop, a retained durable ciphertext is carried
377
+ opaquely with a key-authenticated successor proof; the successor must still
378
+ authenticate the bound account before decryption.
379
+
380
+ The v0.1.27 inner schema-v2 payload did not carry authoritative mode or Search
381
+ state. When v0.1.29 opens one, it keeps the visible composer fenced until the
382
+ user explicitly chooses the destination conversation and separately confirms
383
+ **Search** or **No Search**. It does not infer either choice. A v0.1.29
384
+ schema-v3 payload instead restores its exact mode, owned thread, and Search
385
+ choice, subject to current account ownership and capability verification.
386
+
387
+ `pageshow` and visible-state resume revalidate the server session. A revoked
388
+ session returns to sign-in while keeping unsent composer work in page memory;
389
+ an exact newer release uses a version-addressed navigation and the encrypted
390
+ handoff rather than discarding that work.
391
+
392
+ Before caching, the service worker requires the exact same-origin URL, status,
393
+ MIME type, declared shell security headers, byte length, and SHA-256 for every
394
+ asset. Activation keeps only the current and immediately previous verified
395
+ shell for its normalized scope. The page performs at most one reload per tab
396
+ after a confirmed `controllerchange`.
397
+
398
+ Only the immutable public shell is cached. Login, session, Direct Chat, Agent,
399
+ SSE, artifact, upload, and all other API responses bypass Cache Storage. The
400
+ server must stage the complete release namespace before atomically switching
401
+ the root HTML and stable service-worker response.
402
+
403
+ ## LocalLLM connector
404
+
405
+ The Direct Chat connector accepts only an unprivileged exact
406
+ `http://127.0.0.1:<port>/v1` authority, representing the reviewed local LazyEdge
407
+ service endpoint. A server-side provider supplies its bearer credential for
408
+ each request. The connector uses an allowlist of fixed `localllm-*` aliases,
409
+ checks `/models` readiness, sends bounded provenance-checked Direct Chat
410
+ context, and consumes only strict OpenAI-compatible SSE text deltas. A thread
411
+ uses its text alias until its first image; that turn and later turns use the
412
+ fixed `localllm-vision` alias and receive the latest image-bearing message's
413
+ complete ordered attachment set as one `image_url` content part per image,
414
+ followed by the text part. Base64 is created only for that
415
+ bounded in-flight connector request and is never written to a ledger, log,
416
+ receipt, cache, or browser storage.
417
+
418
+ Redirects, compressed or malformed streams, oversized frames/output, unknown
419
+ aliases, and partial-generation redispatch fail closed. The connector has no
420
+ tool interface and no hosted-provider, model, node, or authority fallback.
421
+
422
+ ## Artifacts and visualization
423
+
424
+ AgInTi registers artifacts by opaque ID and validates ownership, provenance,
425
+ size, type and digest. The cloud never accepts a model-supplied path or URL.
426
+ Initial inline rendering is limited to exact versioned declarative plot, table
427
+ and Markdown schemas. A separately negotiated Search capability adds a
428
+ `sources` schema of at most 20 entries and 48 KiB total. Each entry has an exact
429
+ one-based index, literal title/snippet/provider metadata, `web` or `paper` kind,
430
+ nullable canonical publication date and DOI, and a credential-free HTTPS URL.
431
+ Source cards create text nodes and `noopener noreferrer` anchors only; they do
432
+ not issue fetches, previews, preloads, or image requests. Plot data is finite,
433
+ URL-free and expression-free; the browser builds DOM/SVG with text nodes.
434
+ File artifacts accept only a safe basename, `application/pdf`,
435
+ `application/x-tex`, or `text/x-tex`, a 1-16 MiB byte count, and a lowercase
436
+ SHA-256 digest. `GET`/`HEAD /api/agent/artifacts/<opaque-id>/content?v=<release>` derives
437
+ AgInTi principal and browser-session authority from the authenticated cookie,
438
+ converts at most one validated start-based Range into structured internal JSON,
439
+ and streams the raw response with backpressure and a hard delivery deadline.
440
+ The exact `file` capability gates creation, not historical reads: after a
441
+ verified file event and receipt, read-compatible rollback releases may omit the
442
+ creation capability while authenticated content access remains available.
443
+ The BFF reconstructs attachment disposition and security headers; it never
444
+ forwards browser cookies, tokens, or Range, buffers the complete file, writes
445
+ artifact bytes to cloud storage, or permits the service worker to cache them.
446
+ Unknown or foreign ownership is indistinguishable at 404, locally removed bytes
447
+ return 410 while metadata may remain, and stale releases fail before transport.
448
+ Active HTML and SVG are never served on the authenticated origin, while PDF/TeX
449
+ files remain attachment-only. General file uploads and voice inputs remain
450
+ disabled. Direct Chat's bounded multi-image
451
+ input is the narrow exception: it is descriptor-bound, owner-private,
452
+ metadata-stripped, independently revalidated, and has no Agent or artifact
453
+ authority.
454
+
455
+ ## Replaceable nodes
456
+
457
+ Every enrolled compute node advertises a stable node identity plus independently
458
+ versioned contracts for AgInTi, LocalLLM and transport. A capability response is
459
+ truthful only when it includes the implementation version, instance identity,
460
+ policy digest, isolation digest, health/admission state and supported artifact
461
+ schemas. The cloud must not infer capability from a TCP connection or a
462
+ self-asserted boolean.
463
+
464
+ A Raspberry Pi, Jetson, Kria, workstation or robot may provide a subset of
465
+ services. Threads remain pinned to their AgInTi authority node until an explicit
466
+ AgInTi export/import or migration succeeds. There is no silent hosted-provider,
467
+ node or model fallback. Removing a node removes routing only after its owned
468
+ threads are migrated or intentionally left offline.
469
+
470
+ ## Independent health and failure semantics
471
+
472
+ - LazyingAgentWeb's operator-only `health --config` contract reports
473
+ `CloudIndexStore` and `DirectChatStore` independently, binds the result to the
474
+ exact shell release, and gives LocalLLM and configured AgInTi separate bounded
475
+ states. It emits only fixed projections and does not add a public health
476
+ route.
477
+ - LazyEdge doctor proves transport policy, listeners and tunnel health, not
478
+ application capability. LazyingAgentWeb therefore always reports LazyEdge as
479
+ `not_probed` and makes no transport-health claim.
480
+ - AgInTi readiness proves native ownership, durable idempotency/event state,
481
+ fixed runtime policy and current sandbox/resource admission.
482
+ - LocalLLM reports API/model availability without claiming agent readiness.
483
+ - Static PWA/login remains available during local outages. Chat and Agent show
484
+ distinct dependency failures and never fall back to a hosted provider.
485
+
486
+ ## Promotion rule
487
+
488
+ Agent mode is fail-closed: promotion requires automated adversarial tests to
489
+ prove ownership, CSRF/schema enforcement, exact routes, event replay,
490
+ idempotency, cancellation, context durability, artifact isolation, resource
491
+ admission, tunnel outage and rollback. A live Docker/model acceptance run is
492
+ additionally blocked whenever the shared-workstation resource policy fails.
493
+ Releases are immutable and retain the current and immediately previous
494
+ reproducible package with an executable rollback. Passing offline package tests
495
+ alone does not authorize deployment. Current v0.1.27 production exposes Agent
496
+ only while AgInTi returns the accepted native capability proof; removing or
497
+ invalidating that proof disables Agent. A live PWA or Direct Chat deployment
498
+ alone neither authorizes nor implies Agent enablement, and Direct Chat remains a
499
+ separate LocalLLM path. Search-bearing Agent start/resume requests are
500
+ preflighted against AgInTi's current capability before the mutation is
501
+ forwarded; there is no Web-to-LocalLLM search route. Voice messages and general
502
+ artifact uploads remain outside the accepted capability; local PDF/TeX output
503
+ requires AgInTi's exact `file` capability and the independent content route.
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@lazyingart/agent-web",
3
+ "version": "0.1.40",
4
+ "description": "Cloud presentation and delivery-index foundation for the LazyingArt agent web application",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/lachlanchen/LazyingAgentWeb.git"
8
+ },
9
+ "homepage": "https://llm.lazying.art",
10
+ "bugs": {
11
+ "url": "https://github.com/lachlanchen/LazyingAgentWeb/issues"
12
+ },
13
+ "type": "module",
14
+ "engines": {
15
+ "node": ">=22.21.0"
16
+ },
17
+ "bin": {
18
+ "lazying-agent-web": "./src/cli.js"
19
+ },
20
+ "exports": {
21
+ ".": "./src/index.js",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "src/",
26
+ "docs/",
27
+ "README.md"
28
+ ],
29
+ "scripts": {
30
+ "check": "node scripts/check-syntax.js",
31
+ "test": "node --test --test-concurrency=1 test/*.test.js"
32
+ },
33
+ "sideEffects": false,
34
+ "license": "MIT",
35
+ "publishConfig": {
36
+ "access": "public",
37
+ "registry": "https://registry.npmjs.org/"
38
+ },
39
+ "dependencies": {
40
+ "es-module-lexer": "2.3.1",
41
+ "katex": "0.16.47"
42
+ }
43
+ }