@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
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LazyingArt contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# LazyingAgentWeb
|
|
2
|
+
|
|
3
|
+
`@lazyingart/agent-web` is the standalone cloud PWA and HTTP/BFF for
|
|
4
|
+
`llm.lazying.art`. It provides a usable browser chat surface while keeping
|
|
5
|
+
AgInTi, LocalLLM, and LazyEdge independently replaceable.
|
|
6
|
+
|
|
7
|
+
> **Deployment status (v0.1.29 candidate; v0.1.27 production):** production is promoted
|
|
8
|
+
> independently from repository commits, with immutable acceptance receipts and
|
|
9
|
+
> a verified rollback release. The current deployment enables AgInTi Agent only
|
|
10
|
+
> through the accepted native AgInTi capability proof; if that proof is absent
|
|
11
|
+
> or disabled, the BFF fails closed and Agent is unavailable. Direct Chat stays
|
|
12
|
+
> separate and does not enable or authorize Agent. Current accepted Agent
|
|
13
|
+
> requests can execute one exact fenced `python` block without model planning,
|
|
14
|
+
> retain public failure reasons across reload, and follow exact idempotent Resume
|
|
15
|
+
> successor runs with an optional corrected prompt. The accepted production release makes
|
|
16
|
+
> completed Agent conversations truly multi-turn: later prompts resume the exact
|
|
17
|
+
> terminal predecessor, retry an uncertain mutation once with the same idempotency
|
|
18
|
+
> key, and keep any rejected prompt editable without an optimistic duplicate.
|
|
19
|
+
> Direct Chat remembers the user's non-private workspace-mode preference across
|
|
20
|
+
> a full reload, while
|
|
21
|
+
> confirmed sign-out fences stale history reads. The accepted v0.1.23 release also
|
|
22
|
+
> keeps failed-predecessor and corrected-success messages in chronological
|
|
23
|
+
> order when their verified output and plot artifacts are restored after reload.
|
|
24
|
+
> The baseline accepted artifacts remain declarative plot, table, and Markdown
|
|
25
|
+
> schemas. Responsive plot artifacts in v0.1.25 use the available message width,
|
|
26
|
+
> retain readable axes on DPR3 phones, wrap legends, and cannot be reflowed into
|
|
27
|
+
> the sidebar column by privacy masking. The accepted v0.1.27 release also makes an
|
|
28
|
+
> already-restored Agent-thread selection idempotent: clicking it cannot launch
|
|
29
|
+
> a redundant ledger replay that detaches and rebuilds messages or figures, while
|
|
30
|
+
> an invalid replay or unconfirmed mutation remains explicitly reopenable for
|
|
31
|
+
> authoritative recovery. A brand-new conversation whose creation response is
|
|
32
|
+
> lost retains its exact idempotency ticket for the next Send instead of
|
|
33
|
+
> deadlocking or minting a duplicate. Numeric plot axes choose the shortest
|
|
34
|
+
> distinguishing visible tick labels while retaining exact
|
|
35
|
+
> values in accessible plot metadata. The v0.1.29 candidate also preserves an
|
|
36
|
+
> unsent Agent follow-up across a release-fenced full reload: the encrypted
|
|
37
|
+
> handoff retains its mode and owned thread, replays that thread after reload,
|
|
38
|
+
> derives the authoritative terminal predecessor, and only then enables the
|
|
39
|
+
> restored draft. The next Send therefore resumes the same Agent run instead of
|
|
40
|
+
> silently changing to Direct Chat. v0.1.24 preserves the v0.1.23
|
|
41
|
+
> backward-compatible, default-disabled grounded-search extension: only an exact
|
|
42
|
+
> AgInTi capability may reveal the explicit Search
|
|
43
|
+
> controls, bind `{mode: web|papers|both, limit: 1..20}` to one Agent input, and
|
|
44
|
+
> admit bounded HTTPS-only `sources` artifacts. It also adds an exact-origin,
|
|
45
|
+
> session-and-CSRF-bound fallback for iOS/PWA requests that omit all or part of
|
|
46
|
+
> otherwise-valid Fetch Metadata,
|
|
47
|
+
> retained mobile CSRF state, release-pinned requests with encrypted draft/image
|
|
48
|
+
> refresh handoff, resume-time session validation, and secret-free rejection telemetry.
|
|
49
|
+
> The browser has no direct LocalLLM search route.
|
|
50
|
+
> Voice messages and general artifact uploads remain unavailable. The next
|
|
51
|
+
> capability-gated artifact extension adds local-only PDF/TeX downloads: AgInTi
|
|
52
|
+
> retains the bytes, while the cloud BFF only streams an authenticated bounded
|
|
53
|
+
> response and never stores or caches it.
|
|
54
|
+
|
|
55
|
+
The ownership model, Chat/Agent data planes, recovery semantics, artifact
|
|
56
|
+
boundary, and replaceable-node contract are specified in
|
|
57
|
+
[docs/architecture.md](docs/architecture.md).
|
|
58
|
+
|
|
59
|
+
## Implemented surface
|
|
60
|
+
|
|
61
|
+
- A bright-by-default installable PWA with persistent theme and workspace-mode preferences,
|
|
62
|
+
browser-password-manager integration, durable thread restoration, resumable
|
|
63
|
+
streaming, explicit cancellation, optional one-to-four-image Direct Chat input,
|
|
64
|
+
Markdown, KaTeX math, and safe declarative plot/table/Markdown rendering,
|
|
65
|
+
plus capability-gated text-only source cards that never fetch automatically
|
|
66
|
+
and safe Open/Download controls for AgInTi-owned PDF/TeX files.
|
|
67
|
+
- A root-only Node HTTP/BFF with exact routes, host/origin/fetch-metadata/CSRF
|
|
68
|
+
enforcement, opaque remembered sessions, bounded request/stream/job
|
|
69
|
+
admission, owner-safe response projections, and graceful job draining.
|
|
70
|
+
- `CloudIndexStore` for cloud accounts, digest-only browser sessions,
|
|
71
|
+
presentation-only AgInTi thread indexes, delivery cursors, and closed
|
|
72
|
+
idempotency receipts.
|
|
73
|
+
- `DirectChatStore` for cloud-owned Direct Chat threads and hash-linked message
|
|
74
|
+
ledgers, atomic user-message/generation start, durable fenced dispatch leases,
|
|
75
|
+
exact-once assistant finalization, replayable deltas, cancellation, bounded
|
|
76
|
+
retention, compaction snapshots, private immutable vision attachments, and
|
|
77
|
+
receipt-authorized Direct Chat thread deletion.
|
|
78
|
+
- `DirectChatContextCoordinator` for bounded LocalLLM context assembly and
|
|
79
|
+
provenance-bound chat compaction. The standalone service uses a deterministic
|
|
80
|
+
local summarizer that performs no model or network call, so compaction cannot
|
|
81
|
+
bypass the single-inference admission fence. Summaries are explicitly
|
|
82
|
+
labeled untrusted conversation data and never gain system, developer,
|
|
83
|
+
policy, tool, or Agent authority.
|
|
84
|
+
- `createLocalLlmConnector()` for a fixed set of LocalLLM model aliases over an
|
|
85
|
+
exact authenticated `127.0.0.1` OpenAI-compatible `/v1` endpoint. It validates
|
|
86
|
+
models and SSE frames, bounds input/output, rejects redirects and partial
|
|
87
|
+
redispatch, sends canonical images only through the fixed `localllm-vision`
|
|
88
|
+
alias, and has no hosted-provider fallback.
|
|
89
|
+
- A fail-closed AgInTi BFF transport and cloud-owned stateless adapter. The
|
|
90
|
+
browser can call only the frozen public protocol; the server derives
|
|
91
|
+
identity/session context, validates exact requests and responses, and sends
|
|
92
|
+
only `x-aginti-principal-id`, `x-aginti-browser-session-id`, and standard
|
|
93
|
+
`Idempotency-Key` authority to AgInTi. LazyEdge remains an opaque transport;
|
|
94
|
+
Agent state and decisions never move into this package. Search-bearing run
|
|
95
|
+
inputs receive a fresh server-side capability preflight, so a disabled or
|
|
96
|
+
legacy AgInTi never receives the extension field. The exact `file` capability
|
|
97
|
+
negotiates creation only; a verified file event and receipt remain durable
|
|
98
|
+
read authority across a read-compatible rollback. File bytes use a separate
|
|
99
|
+
authenticated release-bound GET/HEAD BFF route that converts one validated
|
|
100
|
+
browser range into structured AgInTi input, streams with backpressure, and
|
|
101
|
+
never forwards browser credentials or headers to the local service.
|
|
102
|
+
|
|
103
|
+
## Component boundaries
|
|
104
|
+
|
|
105
|
+
| Component | Owns | Must not own |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| LazyingAgentWeb | Cloud accounts and browser sessions; Direct Chat history, deltas, and chat-only context; AgInTi presentation pointers and delivery cursors; PWA and safe rendering | Agent plans, Agent messages/context/compaction, tools, execution, artifact authority, inference implementation, tunnels |
|
|
108
|
+
| AgInTi | Authoritative Agent threads, runs, plans, context, compaction, tools, event ledger, cancellation, and artifacts | Cloud login/session policy, LocalLLM inference implementation, LazyEdge transport |
|
|
109
|
+
| LocalLLM | Text, coding, embedding, and vision inference | Chat persistence, Agent orchestration, cloud accounts, edge transport |
|
|
110
|
+
| LazyEdge | Authenticated, replaceable transport between the cloud BFF and exact enrolled local services | Chat or Agent semantics, model behavior, cloud presentation state |
|
|
111
|
+
|
|
112
|
+
Direct Chat is deliberately cloud-owned because LocalLLM inference is
|
|
113
|
+
stateless. Agent mode is only another frontend for AgInTi: the cloud database
|
|
114
|
+
must not contain AgInTi messages, summaries, plans, tool calls/results,
|
|
115
|
+
commands, workspace paths, runtime policy, artifact bodies, or sandbox state.
|
|
116
|
+
Removing a cloud Agent index cannot delete its authoritative AgInTi thread.
|
|
117
|
+
|
|
118
|
+
## PWA releases and latest-version reload
|
|
119
|
+
|
|
120
|
+
`createStandaloneAssetMap()` builds and brands a complete release map. Its
|
|
121
|
+
immutable release ID is derived from the full shell content and pinned build
|
|
122
|
+
inputs, and every JavaScript module, CSS file, KaTeX module, and icon is placed
|
|
123
|
+
under `/assets/r/<release>/`. The server accepts only that verified branded map;
|
|
124
|
+
it cannot be paired with a caller-invented release ID.
|
|
125
|
+
|
|
126
|
+
The update authority is the stable `/sw.js` route, served with `no-store`,
|
|
127
|
+
`no-cache`, and `must-revalidate`. The browser registers it with
|
|
128
|
+
`updateViaCache: "none"`. Controlled pages check at startup and on bounded
|
|
129
|
+
foreground/online transitions, while a fresh uncontrolled install skips a
|
|
130
|
+
redundant immediate update race. A complete successor shell is verified by
|
|
131
|
+
exact URL, MIME type, security headers, byte length, and SHA-256 before it can
|
|
132
|
+
wait for activation.
|
|
133
|
+
The page proves a waiting worker's release over a one-shot message channel and
|
|
134
|
+
suppresses the banner only when it exactly matches the loaded shell. The UI
|
|
135
|
+
offers **Update** and **Later** for a verified successor, conservatively falling
|
|
136
|
+
back to the same offer for an unresponsive legacy worker; Update reloads once
|
|
137
|
+
only after the new worker controls the tab. Activation retains the current and
|
|
138
|
+
immediately previous verified shell. An offline or failed update leaves the
|
|
139
|
+
current app usable.
|
|
140
|
+
|
|
141
|
+
If Update is confirmed while a definitively unsent Direct Chat composer is
|
|
142
|
+
eligible, or while an exact Agent composer is idle or follows an authoritative
|
|
143
|
+
terminal run, the page stores one bounded AES-GCM ciphertext in a dedicated
|
|
144
|
+
IndexedDB store. The v0.1.29 inner payload schema v3 preserves the exact mode,
|
|
145
|
+
owned Agent thread when present, explicit Search settings or No Search, draft,
|
|
146
|
+
and bounded images. Its random key exists only in the replacement navigation
|
|
147
|
+
fragment, which browsers never send in HTTP requests. The fragment deliberately
|
|
148
|
+
remains while authenticated take and decryption are pending; only a bounded
|
|
149
|
+
recovery retry or verified release hop reattaches it, and successful recovery
|
|
150
|
+
then scrubs it. The exact account, scope, source/target releases, expiry, digest,
|
|
151
|
+
and canonical image contract are revalidated; restoration never dispatches the
|
|
152
|
+
draft. Expired, malformed, and excess orphan records are pruned. Passwords,
|
|
153
|
+
active sends or generations, nonterminal Agent work, and ambiguous mutations
|
|
154
|
+
remain reload blockers.
|
|
155
|
+
|
|
156
|
+
If a release fence is discovered while same-account sign-in is only restoring
|
|
157
|
+
an already server-owned Agent or Direct Chat conversation, v3 may carry an
|
|
158
|
+
empty exact thread selection as read-recovery metadata. It never carries or
|
|
159
|
+
replays a mutation ticket. A durable ciphertext can also cross a signed-out
|
|
160
|
+
release fence through a key-authenticated opaque hop; account verification
|
|
161
|
+
still happens only in the successor before any plaintext is shown.
|
|
162
|
+
|
|
163
|
+
A v0.1.27 handoff has an inner schema-v2 payload that cannot prove its original
|
|
164
|
+
conversation mode or Search choice. v0.1.29 therefore keeps that recovered
|
|
165
|
+
composer read-only until the user explicitly chooses its destination and then
|
|
166
|
+
confirms **Search** or **No Search**; it never guesses and never auto-sends.
|
|
167
|
+
|
|
168
|
+
The handoff accepts at most four canonical images and 16 MiB in aggregate. Any
|
|
169
|
+
larger, active, ambiguous, malformed, expired, or ownership-mismatched draft is
|
|
170
|
+
ineligible; the current page stays open rather than discarding it.
|
|
171
|
+
|
|
172
|
+
Only immutable public shell assets enter Cache Storage. Login/session, Direct
|
|
173
|
+
Chat, Agent, SSE, artifact, and upload traffic always bypasses it. Production
|
|
174
|
+
must stage the entire immutable namespace before atomically switching the root
|
|
175
|
+
HTML and stable worker response.
|
|
176
|
+
|
|
177
|
+
Authenticated history and original attachment bytes remain authoritative in
|
|
178
|
+
the cloud SQLite stores. The PWA may keep only a disposable, bounded per-tab
|
|
179
|
+
Blob LRU for viewport-near attachment previews; it is never written to browser
|
|
180
|
+
storage and is purged across authentication, account, and release boundaries.
|
|
181
|
+
Historical rendered previews have a separate four-image / 64 MiB estimated
|
|
182
|
+
decoded-pixel limit. Eviction revokes the object URL and leaves a tap-to-reload
|
|
183
|
+
placeholder, so scrolling through a long image thread cannot retain every
|
|
184
|
+
decoded surface. Up to four staged or just-sent composer images are transient
|
|
185
|
+
rather than part of this history cache and are revoked at their existing send, view, and
|
|
186
|
+
authentication boundaries.
|
|
187
|
+
The server similarly caches only successful integrity-audit state, bounded by
|
|
188
|
+
thread and invalidated by every local write or SQLite `data_version` change.
|
|
189
|
+
It does not duplicate or relax validation of private message data.
|
|
190
|
+
|
|
191
|
+
## Public package entry point
|
|
192
|
+
|
|
193
|
+
The package root exports the implemented server and storage primitives plus the
|
|
194
|
+
browser/PWA protocol:
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
import {
|
|
198
|
+
CloudIndexStore,
|
|
199
|
+
DirectChatContextCoordinator,
|
|
200
|
+
DirectChatStore,
|
|
201
|
+
createAgintiAgentAdapter,
|
|
202
|
+
createCloudServer,
|
|
203
|
+
createLocalLlmConnector,
|
|
204
|
+
createStandaloneAssetMap,
|
|
205
|
+
failClosedCapabilities
|
|
206
|
+
} from '@lazyingart/agent-web';
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The three runtime stores/coordinators are intentionally injected into
|
|
210
|
+
`createCloudServer()` rather than hidden behind globals. The LocalLLM connector
|
|
211
|
+
also receives its transport credential through a server-side provider; neither
|
|
212
|
+
that credential nor an AgInTi/LazyEdge credential is sent to the browser or
|
|
213
|
+
stored in this repository.
|
|
214
|
+
|
|
215
|
+
## Standalone service configuration
|
|
216
|
+
|
|
217
|
+
`lazying-agent-web serve` reads one owner-only JSON configuration and separate
|
|
218
|
+
owner-only `LoadCredential` files. A secret-free shape is:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"schema": "lazying-agent-service/v1",
|
|
223
|
+
"listen": { "host": "127.0.0.1", "port": 18543 },
|
|
224
|
+
"publicOrigin": "https://llm.lazying.art",
|
|
225
|
+
"account": {
|
|
226
|
+
"username": "lachlanchen",
|
|
227
|
+
"principalId": "principal_account_one",
|
|
228
|
+
"displayName": "Lachlan"
|
|
229
|
+
},
|
|
230
|
+
"state": {
|
|
231
|
+
"cloudIndexDatabase": "/var/lib/lazying-agent-web/cloud/index.sqlite",
|
|
232
|
+
"directChatDatabase": "/var/lib/lazying-agent-web/chat/chat.sqlite"
|
|
233
|
+
},
|
|
234
|
+
"pwa": {
|
|
235
|
+
"versionLabel": "release",
|
|
236
|
+
"title": "LazyingArt Agent",
|
|
237
|
+
"name": "LazyingArt Agent",
|
|
238
|
+
"shortName": "Lazying Agent"
|
|
239
|
+
},
|
|
240
|
+
"localLlm": {
|
|
241
|
+
"baseUrl": "http://127.0.0.1:18008/v1",
|
|
242
|
+
"allowedModelAliases": ["localllm-deep", "localllm-vision"],
|
|
243
|
+
"defaultModelAlias": "localllm-deep",
|
|
244
|
+
"vision": { "enabled": false }
|
|
245
|
+
},
|
|
246
|
+
"aginti": {
|
|
247
|
+
"enabled": true,
|
|
248
|
+
"baseUrl": "http://127.0.0.1:18009"
|
|
249
|
+
},
|
|
250
|
+
"credentials": {
|
|
251
|
+
"passwordHash": "login-password-hash",
|
|
252
|
+
"localLlmToken": "localllm-token",
|
|
253
|
+
"agintiToken": "aginti-token"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The filenames identify distinct credential files; raw password verifiers and
|
|
259
|
+
bearer values never appear in the JSON. Credential directories and files may
|
|
260
|
+
be systemd `LoadCredential` material owned by root (including its read-only
|
|
261
|
+
root-group delivery modes under `/run/credentials/<unit>/`) or owner-only
|
|
262
|
+
files owned by the service account;
|
|
263
|
+
symlinks, hard links, world access, and any non-owner write access remain
|
|
264
|
+
rejected. The preferred fixed-parameter scrypt verifier uses a 64-byte
|
|
265
|
+
derived key, while the canonical 32-byte verifier used by the current v0.2
|
|
266
|
+
login is accepted for a password-preserving migration. Set `aginti` to
|
|
267
|
+
`{ "enabled": false }`
|
|
268
|
+
and omit `credentials.agintiToken` when the Agent transport is intentionally
|
|
269
|
+
absent. Configuring the transport does not claim Agent readiness: capability
|
|
270
|
+
discovery stays fail-closed until AgInTi itself proves its native API, policy,
|
|
271
|
+
sandbox, and current resource admission.
|
|
272
|
+
|
|
273
|
+
Before switching an edge proxy to a candidate build, derive its exact,
|
|
274
|
+
secret-free static allowlist from that same installed package and config:
|
|
275
|
+
|
|
276
|
+
```sh
|
|
277
|
+
lazying-agent-web edge-routes --config /etc/lazying-agent-web/service.json
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The JSON contract contains the candidate content-bound `releaseId`, the exact
|
|
281
|
+
`GET`/`HEAD` paths, and the query-bearing request targets used by the service
|
|
282
|
+
worker. Stage and validate the proxy from this output, switch the proxy and app
|
|
283
|
+
as one release, then verify every request target before retiring the previous
|
|
284
|
+
allowlist. Unknown or foreign release assets must continue to return 404.
|
|
285
|
+
|
|
286
|
+
Operators can inspect the deployed release and its decoupled runtime state
|
|
287
|
+
without adding a public HTTP endpoint:
|
|
288
|
+
|
|
289
|
+
```sh
|
|
290
|
+
lazying-agent-web health --config /etc/lazying-agent-web/service.json
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The command opens `CloudIndexStore` and `DirectChatStore` state read-only and
|
|
294
|
+
reports them independently. LocalLLM and configured AgInTi probes are bounded;
|
|
295
|
+
their errors collapse to fixed reason codes. Paths, origins, account identity,
|
|
296
|
+
credentials, and raw upstream responses are never emitted. LazyEdge is always
|
|
297
|
+
reported as `not_probed` with `healthClaim: false`; use LazyEdge doctor for
|
|
298
|
+
transport health. A degraded or unavailable report exits nonzero. This is an
|
|
299
|
+
operator diagnostic, not a public liveness endpoint: `/health` and
|
|
300
|
+
`/api/health` remain default-deny 404s, and dependency health does not gate the
|
|
301
|
+
static shell.
|
|
302
|
+
|
|
303
|
+
Direct Chat vision remains fail-closed when `localLlm.vision` is absent or
|
|
304
|
+
disabled. Enabling it requires the fixed `localllm-vision` alias while keeping a
|
|
305
|
+
different default text alias. The PWA accepts one to four JPEG, PNG, HEIC, or
|
|
306
|
+
HEIF still images plus a non-empty prompt and accepts each source file up to
|
|
307
|
+
24 MiB. HEIC/HEIF support uses only a feature-detected native browser decoder;
|
|
308
|
+
AVIF, sequences, conflicting brands, and malformed ISO-BMFF framing fail closed.
|
|
309
|
+
The browser redraws and downscales every accepted source sequentially through a
|
|
310
|
+
canvas to remove source metadata, and only canonical JPEG/PNG bytes can cross
|
|
311
|
+
the wire or enter storage. Slow native decoding exposes a visible and accessible
|
|
312
|
+
`Preparing images…` state; timeout, cancellation, session changes, and PWA
|
|
313
|
+
controller changes fence late decoder completions. It enforces 4 MiB per
|
|
314
|
+
canonical image and 16 MiB per message. A separate preview is bounded to 512
|
|
315
|
+
pixels and 512 KiB so a visible
|
|
316
|
+
mobile gallery never retains the full upload surfaces.
|
|
317
|
+
The server independently validates MIME, framing, dimensions, metadata absence,
|
|
318
|
+
digest, ordering, unique attachment IDs, count, and aggregate bytes before
|
|
319
|
+
committing the user message, every private image, and the generation atomically.
|
|
320
|
+
|
|
321
|
+
A minimal in-process storage-only probe is:
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
import { CloudIndexStore, createCapabilityContract } from '@lazyingart/agent-web';
|
|
325
|
+
|
|
326
|
+
const store = new CloudIndexStore({
|
|
327
|
+
databasePath: '/srv/lazying-agent-web/private/index.sqlite'
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
console.log(createCapabilityContract());
|
|
331
|
+
console.log(store.healthCheck()); // DirectChatStore exposes the same safe shape.
|
|
332
|
+
store.close();
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Runtime
|
|
336
|
+
|
|
337
|
+
- Node.js 22.21.0 or newer, with the built-in `node:sqlite` module.
|
|
338
|
+
- Exact runtime dependencies `es-module-lexer@2.3.1` and `katex@0.16.47`.
|
|
339
|
+
- A local filesystem with normal POSIX ownership and locking semantics for the
|
|
340
|
+
private SQLite directories.
|
|
341
|
+
|
|
342
|
+
`node:sqlite` emits an experimental warning on the tested Node 22.21.0 runtime.
|
|
343
|
+
No native SQLite addon or browser CDN is used. The pinned parser verifies the
|
|
344
|
+
complete immutable module graph, and the pinned KaTeX module provides local
|
|
345
|
+
math rendering under the release namespace.
|
|
346
|
+
|
|
347
|
+
## Storage and HTTP safety
|
|
348
|
+
|
|
349
|
+
Both SQLite stores require absolute on-disk paths. They create state directories
|
|
350
|
+
with mode `0700` and databases with mode `0600`, and reject symlinks, foreign
|
|
351
|
+
owners/application IDs, insecure permissions, hard-linked database files,
|
|
352
|
+
future schemas, migration checksum drift, integrity failures, and foreign-key
|
|
353
|
+
corruption. SQLite uses `DELETE` journaling, `FULL` synchronous writes, foreign
|
|
354
|
+
keys, `trusted_schema=OFF`, disabled extension loading, a bounded busy timeout,
|
|
355
|
+
and `BEGIN IMMEDIATE` mutations.
|
|
356
|
+
|
|
357
|
+
Raw browser session and CSRF tokens are never stored; `CloudIndexStore` retains
|
|
358
|
+
only SHA-256 digests. An HTTP adapter derives every `accountId` from the verified
|
|
359
|
+
browser session. Browser payloads and public JSON/SSE projections never choose
|
|
360
|
+
or expose that owner identifier.
|
|
361
|
+
|
|
362
|
+
Browser-session admission first removes expired rows. At the per-account cap,
|
|
363
|
+
a successful new login atomically rotates only that account's oldest-issued
|
|
364
|
+
session, with a deterministic digest tie-break; token collisions fail before
|
|
365
|
+
eviction. This keeps sign-in available without deleting another account's
|
|
366
|
+
session or temporarily exceeding the cap.
|
|
367
|
+
|
|
368
|
+
Idempotency rows are bounded closed receipts rather than arbitrary response
|
|
369
|
+
caches. Direct Chat starts a user message and its pending generation in one
|
|
370
|
+
transaction. A durable owner digest plus monotonic fence prevents two cloud
|
|
371
|
+
workers from dispatching the same generation concurrently; a stale worker
|
|
372
|
+
cannot append or finalize after losing its lease.
|
|
373
|
+
|
|
374
|
+
Direct Chat deletion is a distinct exact mutation:
|
|
375
|
+
`POST /api/chat/threads/delete` requires the authenticated browser session,
|
|
376
|
+
CSRF proof, a caller-generated idempotency key, and the exact current
|
|
377
|
+
revision/hash cursor. The store refuses an active generation, a stale cursor,
|
|
378
|
+
or a trailing user message whose send acceptance is unresolved. On success it
|
|
379
|
+
atomically writes a content-free schema-v5 deletion receipt containing only
|
|
380
|
+
identity, cursor metadata, and digests before removing the thread and its
|
|
381
|
+
private descendants. The receipt is immutable, supports an exact retry without
|
|
382
|
+
retaining the raw key or deleted content, and permanently retires that
|
|
383
|
+
account/thread identifier. This route deletes only cloud-owned
|
|
384
|
+
Direct Chat state; deleting an Agent presentation index or an authoritative
|
|
385
|
+
AgInTi thread remains a separate contract.
|
|
386
|
+
|
|
387
|
+
Canonical attachment bytes are durable only in the owner-private Direct Chat
|
|
388
|
+
database. The message ledger and browser API expose a size/dimension/MIME/SHA-256
|
|
389
|
+
descriptor, never the bytes or base64. Authenticated previews are `no-store`
|
|
390
|
+
responses, and image data never enters Cache Storage, localStorage, or
|
|
391
|
+
sessionStorage. IndexedDB is used only for the encrypted, expiring, one-shot
|
|
392
|
+
confirmed-update handoff described above; it is not chat history or a retry
|
|
393
|
+
queue. Base64 exists only transiently in the browser's exact in-memory retry
|
|
394
|
+
ticket and the bounded browser-to-BFF and BFF-to-LocalLLM request bodies. The
|
|
395
|
+
browser serializes a prepared image request once before entering the network
|
|
396
|
+
ambiguity boundary, reuses those exact bytes only when a status probe proves
|
|
397
|
+
the generation absent, and releases the raw images and serialized ticket as
|
|
398
|
+
soon as the server accepts the durable turn.
|
|
399
|
+
|
|
400
|
+
Ordered attachment responses use an explicit message-list schema request. A
|
|
401
|
+
previous PWA that omits it receives the first descriptor in the legacy singular
|
|
402
|
+
shape, so a stale open tab keeps its text/history protocol valid until the
|
|
403
|
+
content-versioned PWA refresh takes control.
|
|
404
|
+
|
|
405
|
+
Schema v5 adds the durable authority receipts required for safe Direct Chat
|
|
406
|
+
thread deletion. It is now the common Direct Chat schema whether vision is
|
|
407
|
+
enabled or disabled, so the ordered v4 attachment tables are materialized on
|
|
408
|
+
upgrade while image use remains fail-closed at the application boundary.
|
|
409
|
+
Existing v3 single-image rows still migrate to ordered position zero. A v5-aware
|
|
410
|
+
build running with vision disabled can serve authenticated previews and exact
|
|
411
|
+
retries of previously committed image turns, but refuses new image turns and
|
|
412
|
+
follow-ups that would reuse stored images.
|
|
413
|
+
|
|
414
|
+
A pre-v5 binary cannot reopen the migrated database. Before activation, block
|
|
415
|
+
every dynamic API, stop the service, verify sidecar-free SQLite `DELETE`
|
|
416
|
+
journal state, take an offline private database backup, and preflight a copy of
|
|
417
|
+
that backup with the candidate release. The snapshot may be restored only
|
|
418
|
+
while all dynamic APIs remain blocked and before any v5 write authority or the
|
|
419
|
+
v5 deletion API is activated. After that boundary, preserve the live v5
|
|
420
|
+
database and use only a v5-aware rollback release; restoring the older snapshot
|
|
421
|
+
could discard accepted messages or deletion authority.
|
|
422
|
+
|
|
423
|
+
The production server is designed to bind on loopback behind Caddy. It trusts
|
|
424
|
+
the configured public authority/client-address headers only from that local
|
|
425
|
+
proxy boundary. It does not terminate public TLS, manage a LazyEdge tunnel, or
|
|
426
|
+
launch LocalLLM/AgInTi/sandboxes itself.
|
|
427
|
+
|
|
428
|
+
## Checks
|
|
429
|
+
|
|
430
|
+
Run the offline checks with:
|
|
431
|
+
|
|
432
|
+
```sh
|
|
433
|
+
npm run check
|
|
434
|
+
npm test
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
These checks do not deploy the package or exercise the blocked live
|
|
438
|
+
Docker/model acceptance gate.
|