@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,179 @@
|
|
|
1
|
+
const HANDOFF_ID = /^[a-f0-9]{64}$/u;
|
|
2
|
+
const RELEASE_ID = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,23}-[a-f0-9]{64}$/u;
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 2_000;
|
|
4
|
+
const MAX_AGE_MS = 5 * 60 * 1_000;
|
|
5
|
+
const MAX_RECORDS = 4;
|
|
6
|
+
const MAX_CIPHERTEXT_BYTES = 16 * 1024 * 1024 + 160 * 1024 + 20;
|
|
7
|
+
|
|
8
|
+
function storedEnvelope(value) {
|
|
9
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)
|
|
10
|
+
|| ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return null;
|
|
11
|
+
const keys = Object.keys(value).sort().join(",");
|
|
12
|
+
if (keys !== "ciphertext,createdAt,expiresAt,handoffId,iv,schemaVersion,scope,sourceRelease,targetRelease"
|
|
13
|
+
|| !["1", "2"].includes(value.schemaVersion)
|
|
14
|
+
|| typeof value.scope !== "string" || value.scope.length < 1 || value.scope.length > 160
|
|
15
|
+
|| !HANDOFF_ID.test(value.handoffId) || !RELEASE_ID.test(value.sourceRelease) || !RELEASE_ID.test(value.targetRelease)
|
|
16
|
+
|| !Number.isSafeInteger(value.createdAt) || value.createdAt < 0
|
|
17
|
+
|| value.expiresAt !== value.createdAt + MAX_AGE_MS
|
|
18
|
+
|| !(value.iv instanceof Uint8Array) || value.iv.byteLength !== 12
|
|
19
|
+
|| !(value.ciphertext instanceof Uint8Array) || value.ciphertext.byteLength < 17
|
|
20
|
+
|| value.ciphertext.byteLength > MAX_CIPHERTEXT_BYTES) return null;
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function updateHandoffStorageKeysToDelete(entries, {
|
|
25
|
+
instant,
|
|
26
|
+
protectedKey = null,
|
|
27
|
+
maximumRecords = MAX_RECORDS,
|
|
28
|
+
} = {}) {
|
|
29
|
+
if (!Array.isArray(entries) || !Number.isSafeInteger(instant) || instant < 0
|
|
30
|
+
|| !Number.isSafeInteger(maximumRecords) || maximumRecords < 1 || maximumRecords > 16) {
|
|
31
|
+
throw new TypeError("update handoff cleanup input is invalid");
|
|
32
|
+
}
|
|
33
|
+
const retained = [];
|
|
34
|
+
const deleted = new Set();
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (!entry || typeof entry !== "object" || typeof entry.key !== "string") continue;
|
|
37
|
+
const record = storedEnvelope(entry.value);
|
|
38
|
+
const expectedKey = record === null ? null : `${record.scope}\u0000${record.handoffId}`;
|
|
39
|
+
if (record === null || entry.key !== expectedKey || record.expiresAt < instant) {
|
|
40
|
+
if (entry.key !== protectedKey) deleted.add(entry.key);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
retained.push({ key: entry.key, createdAt: record.createdAt });
|
|
44
|
+
}
|
|
45
|
+
retained.sort((left, right) => right.createdAt - left.createdAt || left.key.localeCompare(right.key));
|
|
46
|
+
for (const entry of retained.slice(maximumRecords)) {
|
|
47
|
+
if (entry.key !== protectedKey) deleted.add(entry.key);
|
|
48
|
+
}
|
|
49
|
+
return Object.freeze([...deleted]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createBrowserUpdateHandoffStore({
|
|
53
|
+
indexedDB = globalThis.indexedDB,
|
|
54
|
+
setTimeoutImpl = globalThis.setTimeout,
|
|
55
|
+
clearTimeoutImpl = globalThis.clearTimeout,
|
|
56
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
57
|
+
now = Date.now,
|
|
58
|
+
} = {}) {
|
|
59
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 250 || timeoutMs > 10_000) {
|
|
60
|
+
throw new TypeError("update handoff storage timeout is invalid");
|
|
61
|
+
}
|
|
62
|
+
if (typeof now !== "function") throw new TypeError("update handoff storage clock is invalid");
|
|
63
|
+
const openDatabase = () => new Promise((resolve, reject) => {
|
|
64
|
+
if (!indexedDB || typeof indexedDB.open !== "function") {
|
|
65
|
+
reject(new TypeError("browser update handoff storage is unavailable"));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let settled = false;
|
|
69
|
+
let request;
|
|
70
|
+
const finish = (operation, value) => {
|
|
71
|
+
if (settled) {
|
|
72
|
+
if (operation === resolve) value?.close?.();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
settled = true;
|
|
76
|
+
clearTimeoutImpl?.(timer);
|
|
77
|
+
operation(value);
|
|
78
|
+
};
|
|
79
|
+
const timer = setTimeoutImpl?.(
|
|
80
|
+
() => finish(reject, new TypeError("browser update handoff storage timed out")),
|
|
81
|
+
timeoutMs,
|
|
82
|
+
);
|
|
83
|
+
try { request = indexedDB.open("lazying-agent-web-update-handoff", 1); }
|
|
84
|
+
catch (error) { finish(reject, error); return; }
|
|
85
|
+
request.onupgradeneeded = () => {
|
|
86
|
+
const database = request.result;
|
|
87
|
+
if (!database.objectStoreNames.contains("handoffs")) database.createObjectStore("handoffs");
|
|
88
|
+
};
|
|
89
|
+
request.onerror = () => finish(reject, request.error ?? new TypeError("browser update handoff storage failed"));
|
|
90
|
+
request.onblocked = () => finish(reject, new TypeError("browser update handoff storage is blocked"));
|
|
91
|
+
request.onsuccess = () => finish(resolve, request.result);
|
|
92
|
+
});
|
|
93
|
+
const transact = async (mode, operation) => {
|
|
94
|
+
const database = await openDatabase();
|
|
95
|
+
try {
|
|
96
|
+
return await new Promise((resolve, reject) => {
|
|
97
|
+
let settled = false;
|
|
98
|
+
let result;
|
|
99
|
+
let transaction;
|
|
100
|
+
const finish = (callback, value) => {
|
|
101
|
+
if (settled) return;
|
|
102
|
+
settled = true;
|
|
103
|
+
clearTimeoutImpl?.(timer);
|
|
104
|
+
callback(value);
|
|
105
|
+
};
|
|
106
|
+
const timer = setTimeoutImpl?.(() => {
|
|
107
|
+
try { transaction?.abort(); } catch { /* A timed-out transaction is already unusable. */ }
|
|
108
|
+
finish(reject, new TypeError("browser update handoff transaction timed out"));
|
|
109
|
+
}, timeoutMs);
|
|
110
|
+
try {
|
|
111
|
+
transaction = database.transaction("handoffs", mode);
|
|
112
|
+
result = operation(transaction.objectStore("handoffs"), transaction);
|
|
113
|
+
} catch (error) { finish(reject, error); return; }
|
|
114
|
+
transaction.oncomplete = () => finish(resolve, typeof result === "function" ? result() : result);
|
|
115
|
+
transaction.onerror = () => finish(
|
|
116
|
+
reject,
|
|
117
|
+
transaction.error ?? new TypeError("browser update handoff transaction failed"),
|
|
118
|
+
);
|
|
119
|
+
transaction.onabort = () => finish(
|
|
120
|
+
reject,
|
|
121
|
+
transaction.error ?? new TypeError("browser update handoff transaction aborted"),
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
} finally { database.close(); }
|
|
125
|
+
};
|
|
126
|
+
const storageKey = (scope, handoffId) => {
|
|
127
|
+
if (typeof scope !== "string" || scope.length < 1 || scope.length > 160 || !HANDOFF_ID.test(handoffId)) {
|
|
128
|
+
throw new TypeError("update handoff storage key is invalid");
|
|
129
|
+
}
|
|
130
|
+
return `${scope}\u0000${handoffId}`;
|
|
131
|
+
};
|
|
132
|
+
const prune = (store, protectedKey = null) => {
|
|
133
|
+
const entries = [];
|
|
134
|
+
const request = store.openCursor();
|
|
135
|
+
request.onsuccess = () => {
|
|
136
|
+
const cursor = request.result;
|
|
137
|
+
if (cursor) {
|
|
138
|
+
entries.push({ key: cursor.key, value: cursor.value });
|
|
139
|
+
cursor.continue();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const instant = Number(now());
|
|
143
|
+
for (const key of updateHandoffStorageKeysToDelete(entries, { instant, protectedKey })) store.delete(key);
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
return Object.freeze({
|
|
147
|
+
async save(record) {
|
|
148
|
+
const instant = Number(now());
|
|
149
|
+
const envelope = storedEnvelope(record);
|
|
150
|
+
if (envelope === null || !Number.isSafeInteger(instant) || instant < 0
|
|
151
|
+
|| envelope.createdAt > instant + 30_000 || envelope.expiresAt < instant) {
|
|
152
|
+
throw new TypeError("encrypted update handoff is invalid");
|
|
153
|
+
}
|
|
154
|
+
const key = storageKey(record?.scope, record?.handoffId);
|
|
155
|
+
await transact("readwrite", (store) => {
|
|
156
|
+
store.put(record, key);
|
|
157
|
+
prune(store, key);
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
async take(scope, handoffId) {
|
|
161
|
+
const key = storageKey(scope, handoffId);
|
|
162
|
+
let value;
|
|
163
|
+
await transact("readwrite", (store, transaction) => {
|
|
164
|
+
const request = store.get(key);
|
|
165
|
+
request.onsuccess = () => {
|
|
166
|
+
value = request.result;
|
|
167
|
+
store.delete(key);
|
|
168
|
+
prune(store);
|
|
169
|
+
};
|
|
170
|
+
request.onerror = () => transaction.abort();
|
|
171
|
+
});
|
|
172
|
+
return value ?? null;
|
|
173
|
+
},
|
|
174
|
+
async discard(scope, handoffId) {
|
|
175
|
+
const key = storageKey(scope, handoffId);
|
|
176
|
+
await transact("readwrite", (store) => { store.delete(key); });
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|