@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.
- package/LICENSE +22 -0
- package/README.md +438 -0
- package/docs/architecture.md +503 -0
- package/package.json +43 -0
- package/src/aginti-adapter.js +602 -0
- package/src/chat-context.js +1020 -0
- package/src/chat-migrations.js +947 -0
- package/src/chat-store.js +3308 -0
- package/src/cli.js +134 -0
- package/src/cloud-server.js +2043 -0
- package/src/contracts.js +103 -0
- package/src/deterministic-context-summarizer.js +254 -0
- package/src/direct-chat-capability-limits.js +66 -0
- package/src/direct-chat-contract.js +3 -0
- package/src/errors.js +50 -0
- package/src/http-contract.js +592 -0
- package/src/index.js +88 -0
- package/src/localllm-connector.js +667 -0
- package/src/migrations.js +231 -0
- package/src/operator-health.js +184 -0
- package/src/password-verifier.js +131 -0
- package/src/service-config.js +547 -0
- package/src/service.js +408 -0
- package/src/sqlite-health.js +83 -0
- package/src/storage-path.js +130 -0
- package/src/store.js +914 -0
- package/src/validation.js +181 -0
- package/src/vision-attachment.js +404 -0
- package/src/web/aginti-client.js +552 -0
- package/src/web/aginti-protocol.js +1146 -0
- package/src/web/asset-map.js +462 -0
- package/src/web/browser-app.js +6491 -0
- package/src/web/cloud-session-client.js +427 -0
- package/src/web/direct-chat-client.js +1482 -0
- package/src/web/index.js +10 -0
- package/src/web/presentation-state.js +107 -0
- package/src/web/pwa-assets.js +854 -0
- package/src/web/pwa-update-handoff-store.js +179 -0
- package/src/web/safe-rendering.js +836 -0
- package/src/web/vision-image-client.js +546 -0
- package/src/web/vision-image-sanitizer.js +168 -0
- 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
|
+
}
|