@canonmsg/agent-sdk 12.4.1 → 12.6.1
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/README.md +37 -6
- package/dist/chunks/{chunk-6PKXASSK.js → chunk-5J4YPLNW.js} +394 -1687
- package/dist/chunks/{chunk-NXL4C2R6.js → chunk-DAJ7L7TN.js} +954 -429
- package/dist/chunks/chunk-FVYKLH6I.js +1475 -0
- package/dist/chunks/chunk-HH7KR22P.js +406 -0
- package/dist/chunks/chunk-QK37HAHC.js +118 -0
- package/dist/chunks/{chunk-GWQF4WFT.js → chunk-S7BV74DN.js} +734 -812
- package/dist/chunks/{mls-crypto-LGPCXM3K.js → mls-crypto-4DZT2UFH.js} +2 -1
- package/dist/cli.js +70 -32
- package/dist/drivers/acp.d.ts +56 -0
- package/dist/drivers/codex.d.ts +1 -2
- package/dist/drivers/connector.d.ts +5 -4
- package/dist/drivers/subprocess.d.ts +35 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +12 -3
- package/dist/internal/backend-contracts/connectorRelay.d.ts +34 -0
- package/dist/internal/backend-contracts/endpoint.d.ts +1 -1
- package/dist/internal/backend-contracts/endpointContentPolicy.d.ts +2 -2
- package/dist/internal/backend-contracts/index.d.ts +1 -0
- package/dist/internal/backend-contracts/loggedOutSessions.d.ts +7 -0
- package/dist/internal/backend-contracts/mlsDevCredential.d.ts +3 -0
- package/dist/internal/endpoint/engine.d.ts +8 -1
- package/dist/internal/endpoint/mls-dev-client.d.ts +11 -0
- package/dist/internal/endpoint/mls-dev-text.d.ts +3 -0
- package/dist/internal/endpoint/node-mls-custody-protocol.d.ts +5 -0
- package/dist/internal/endpoint/node.d.ts +1 -0
- package/dist/media.js +4 -2
- package/dist/mls/darwin-arm64/canon-mls-custody +0 -0
- package/dist/mls/linux-arm64/canon-mls-custody +0 -0
- package/dist/mls/linux-x64/canon-mls-custody +0 -0
- package/dist/mls/wasm/canon_mls_wasm.js +363 -0
- package/dist/mls/wasm/canon_mls_wasm_bg.wasm +0 -0
- package/dist/mls-wasm-host.js +258 -0
- package/dist/runner/mls-bundled.d.ts +15 -2
- package/dist/runner/mls-managed.d.ts +10 -2
- package/dist/runner/mls.d.ts +7 -1
- package/dist/runner/relay.d.ts +3 -3
- package/dist/runner/runner.d.ts +18 -7
- package/dist/runner/types.d.ts +12 -4
- package/package.json +1 -1
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
EndpointError,
|
|
3
|
+
copyJson,
|
|
4
|
+
createEndpointEventHub,
|
|
5
|
+
createHttpEndpointTransport,
|
|
6
|
+
createNodeEndpointStore,
|
|
7
|
+
validateEndpointContentBoundary
|
|
8
|
+
} from "./chunk-FVYKLH6I.js";
|
|
9
|
+
import {
|
|
3
10
|
CANON_ENDPOINT_PROTOCOL,
|
|
4
11
|
CANON_VERB_WIRE_SCHEMA_VERSION,
|
|
5
|
-
CLIENT_UPDATE_REQUIRED,
|
|
6
12
|
ENDPOINT_ACTION_PATTERN,
|
|
7
13
|
ENDPOINT_ID_PATTERN,
|
|
8
14
|
MAX_PUBLIC_RUNTIME_COMMANDS,
|
|
9
|
-
MLS_DEV_CREDENTIAL_MAX_BYTES,
|
|
10
|
-
MLS_DEV_CREDENTIAL_RECONCILIATION_MAX_BYTES,
|
|
11
15
|
MLS_DEV_DISCLOSED_PROCESSING_FIELDS,
|
|
12
|
-
MLS_DEV_ENROLLMENT_MAX_BYTES,
|
|
13
16
|
MLS_DEV_GROUP_RECEIPT_CLOCK_SKEW,
|
|
14
17
|
MLS_DEV_INTERACTION_LIMITS,
|
|
15
|
-
MLS_DEV_KEY_PACKAGES_MAX_BYTES,
|
|
16
18
|
MLS_DEV_REPLY_QUOTE_MAX_CHARACTERS,
|
|
17
19
|
MLS_DEV_TEXT_PROTOCOL_V3,
|
|
18
20
|
MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS,
|
|
@@ -22,8 +24,6 @@ import {
|
|
|
22
24
|
__name,
|
|
23
25
|
assertMlsDevInteractionPayloadBinding,
|
|
24
26
|
canonicalEndpointJson,
|
|
25
|
-
classifyTransferHttpFailure,
|
|
26
|
-
classifyTransferReceipt,
|
|
27
27
|
createMlsDevCredentialSigningInput,
|
|
28
28
|
decodePreparedJsonContent,
|
|
29
29
|
describeAttachment,
|
|
@@ -32,14 +32,11 @@ import {
|
|
|
32
32
|
encodeMlsPrincipalAliasInput,
|
|
33
33
|
endpointContentBoundary,
|
|
34
34
|
endpointOperationRequestRetention,
|
|
35
|
-
formatCanonClientHeader,
|
|
36
35
|
formatMembershipChangeNotice,
|
|
37
36
|
getCanonEnvironmentContract,
|
|
38
37
|
isCanonMediaOrigin,
|
|
39
38
|
isCanonUploadUrl,
|
|
40
|
-
isEndpointOperationReceipt,
|
|
41
39
|
isMlsDevInteractionAnswerAllowed,
|
|
42
|
-
isMlsDevReceiptClockSkew,
|
|
43
40
|
isMlsDevTextProtocol,
|
|
44
41
|
isMlsEnvironmentId,
|
|
45
42
|
isMlsPrincipalAlias,
|
|
@@ -61,7 +58,6 @@ import {
|
|
|
61
58
|
parseMlsDevCredentialReconciliationComplete,
|
|
62
59
|
parseMlsDevCredentialReconciliationReceipt,
|
|
63
60
|
parseMlsDevCredentialRevocationReceipt,
|
|
64
|
-
parseMlsDevCredentialRevoke,
|
|
65
61
|
parseMlsDevCredentialToken,
|
|
66
62
|
parseMlsDevDeviceBinding,
|
|
67
63
|
parseMlsDevDeviceCommand,
|
|
@@ -110,1428 +106,44 @@ import {
|
|
|
110
106
|
readMembershipChangeMetadata,
|
|
111
107
|
resolveAllowedUploadMimeType,
|
|
112
108
|
sanitizeDownloadFileName
|
|
113
|
-
} from "./chunk-
|
|
109
|
+
} from "./chunk-S7BV74DN.js";
|
|
114
110
|
|
|
115
111
|
// src/media.ts
|
|
116
112
|
import { createWriteStream as createWriteStream2 } from "node:fs";
|
|
117
113
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
118
|
-
import { mkdir as
|
|
119
|
-
import { basename, dirname as
|
|
114
|
+
import { mkdir as mkdir2, open as open2, rename as rename2, stat as stat2, unlink as unlink2 } from "node:fs/promises";
|
|
115
|
+
import { basename, dirname as dirname2, extname, join as join5 } from "node:path";
|
|
120
116
|
import { Readable, Transform as Transform2 } from "node:stream";
|
|
121
117
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
122
118
|
|
|
123
|
-
// ../endpoint/src/node.ts
|
|
124
|
-
import { mkdir, chmod } from "node:fs/promises";
|
|
125
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
126
|
-
import { join as join2, resolve as resolve3 } from "node:path";
|
|
127
|
-
import { DatabaseSync } from "node:sqlite";
|
|
128
|
-
|
|
129
|
-
// ../endpoint/src/sqlite.ts
|
|
130
|
-
async function createSqliteEndpointStore(options) {
|
|
131
|
-
const { database, namespace } = options;
|
|
132
|
-
if (!namespace) throw new Error("Endpoint store namespace is required");
|
|
133
|
-
await database.transaction(async (tx) => {
|
|
134
|
-
await tx.run("CREATE TABLE IF NOT EXISTS canon_endpoint_records (namespace TEXT NOT NULL, key TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY(namespace, key))");
|
|
135
|
-
});
|
|
136
|
-
return {
|
|
137
|
-
transaction: /* @__PURE__ */ __name((work) => database.transaction(async (sql) => {
|
|
138
|
-
const transaction = {
|
|
139
|
-
async get(key) {
|
|
140
|
-
const rows = await sql.all("SELECT value FROM canon_endpoint_records WHERE namespace = ? AND key = ?", [namespace, key]);
|
|
141
|
-
return rows[0] ? JSON.parse(rows[0].value) : void 0;
|
|
142
|
-
},
|
|
143
|
-
async put(key, value) {
|
|
144
|
-
await sql.run("INSERT INTO canon_endpoint_records(namespace, key, value) VALUES (?, ?, ?) ON CONFLICT(namespace, key) DO UPDATE SET value = excluded.value", [namespace, key, JSON.stringify(value)]);
|
|
145
|
-
},
|
|
146
|
-
async delete(key) {
|
|
147
|
-
await sql.run("DELETE FROM canon_endpoint_records WHERE namespace = ? AND key = ?", [namespace, key]);
|
|
148
|
-
},
|
|
149
|
-
async scan(prefix3, scanOptions = {}) {
|
|
150
|
-
const limit = Math.max(1, Math.min(scanOptions.limit ?? 100, 1e3));
|
|
151
|
-
const rows = await sql.all(
|
|
152
|
-
"SELECT key, value FROM canon_endpoint_records WHERE namespace = ? AND key >= ? AND key < ? AND key > ? ORDER BY key LIMIT ?",
|
|
153
|
-
[namespace, prefix3, `${prefix3}\uFFFF`, scanOptions.after ?? "", limit]
|
|
154
|
-
);
|
|
155
|
-
return rows.map((row) => ({ key: row.key, value: JSON.parse(row.value) }));
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
return work(transaction);
|
|
159
|
-
}), "transaction"),
|
|
160
|
-
async close() {
|
|
161
|
-
await database.close?.();
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
__name(createSqliteEndpointStore, "createSqliteEndpointStore");
|
|
166
|
-
|
|
167
|
-
// ../endpoint/src/node-mls-custody.ts
|
|
168
|
-
import { spawn } from "node:child_process";
|
|
169
|
-
import { createHash } from "node:crypto";
|
|
170
|
-
import { lstat, open, realpath } from "node:fs/promises";
|
|
171
|
-
import { constants } from "node:fs";
|
|
172
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
173
|
-
import { platform } from "node:os";
|
|
174
|
-
|
|
175
|
-
// ../endpoint/src/events.ts
|
|
176
|
-
function createEndpointEventHub(input) {
|
|
177
|
-
const observers = /* @__PURE__ */ new Set();
|
|
178
|
-
let controller;
|
|
179
|
-
let running;
|
|
180
|
-
let closed = false;
|
|
181
|
-
let connected = false;
|
|
182
|
-
let terminalError = false;
|
|
183
|
-
let lastEventId;
|
|
184
|
-
let subscribedScopes = "";
|
|
185
|
-
const principalScopes = /* @__PURE__ */ __name(() => [...new Set([...observers].flatMap(({ hooks }) => hooks?.principalScopes ?? []))].sort(), "principalScopes");
|
|
186
|
-
const families = /* @__PURE__ */ __name(() => [...new Set([...observers].flatMap(({ hooks }) => hooks?.families ?? ["messages", "contacts"]))].sort(), "families");
|
|
187
|
-
const subscriptionKey = /* @__PURE__ */ __name(() => JSON.stringify([families(), principalScopes()]), "subscriptionKey");
|
|
188
|
-
const safe = /* @__PURE__ */ __name((callback) => {
|
|
189
|
-
try {
|
|
190
|
-
callback?.();
|
|
191
|
-
} catch {
|
|
192
|
-
}
|
|
193
|
-
}, "safe");
|
|
194
|
-
async function loop(signal) {
|
|
195
|
-
let attempt = 0;
|
|
196
|
-
while (!signal.aborted && observers.size) {
|
|
197
|
-
let retryAfterMs = 0;
|
|
198
|
-
let handling = false;
|
|
199
|
-
try {
|
|
200
|
-
if (!input.transport.events) return;
|
|
201
|
-
for await (const event of input.transport.events({ lastEventId, signal, families: families(), principalScopes: principalScopes(), onConnected: /* @__PURE__ */ __name(() => {
|
|
202
|
-
connected = true;
|
|
203
|
-
for (const { hooks } of observers) safe(hooks?.onConnected);
|
|
204
|
-
input.invalidate();
|
|
205
|
-
}, "onConnected") })) {
|
|
206
|
-
if (signal.aborted) break;
|
|
207
|
-
validateEndpointContentBoundary("event", event.kind, event.data);
|
|
208
|
-
if (event.kind === "error") throw decodeEndpointError(event.data, { stream: true });
|
|
209
|
-
handling = true;
|
|
210
|
-
await input.onEvent?.(event);
|
|
211
|
-
handling = false;
|
|
212
|
-
attempt = 0;
|
|
213
|
-
if (event.kind === "replay.expired") lastEventId = void 0;
|
|
214
|
-
else if (event.id) lastEventId = event.id;
|
|
215
|
-
for (const observer of observers) safe(() => observer.listener(event));
|
|
216
|
-
if (/^(artifact\.|message\.|conversations?\.|contacts?\.|agent\.context$|replay\.|voice\.session\.)/.test(event.kind)) input.invalidate(event);
|
|
217
|
-
}
|
|
218
|
-
} catch (error) {
|
|
219
|
-
if (!signal.aborted) {
|
|
220
|
-
terminalError = !handling && error instanceof EndpointError && !error.retryable;
|
|
221
|
-
if (error instanceof EndpointError && typeof error.retryAfterMs === "number" && Number.isFinite(error.retryAfterMs)) retryAfterMs = Math.max(0, Math.min(3e5, error.retryAfterMs));
|
|
222
|
-
for (const { hooks } of observers) safe(() => hooks?.onError?.(error instanceof Error ? error : new EndpointError("Stream connection failed")));
|
|
223
|
-
}
|
|
224
|
-
} finally {
|
|
225
|
-
if (connected) {
|
|
226
|
-
connected = false;
|
|
227
|
-
safe(input.onDisconnected);
|
|
228
|
-
for (const { hooks } of observers) safe(hooks?.onDisconnected);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (signal.aborted || !observers.size || terminalError) break;
|
|
232
|
-
const delay = Math.max(retryAfterMs, Math.min(3e4, 500 * 2 ** Math.min(attempt++, 6)) * (0.8 + Math.random() * 0.4));
|
|
233
|
-
await new Promise((resolve6) => {
|
|
234
|
-
const finish = /* @__PURE__ */ __name(() => {
|
|
235
|
-
clearTimeout(timer);
|
|
236
|
-
signal.removeEventListener("abort", finish);
|
|
237
|
-
resolve6();
|
|
238
|
-
}, "finish");
|
|
239
|
-
const timer = setTimeout(finish, delay);
|
|
240
|
-
timer.unref?.();
|
|
241
|
-
signal.addEventListener("abort", finish, { once: true });
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
__name(loop, "loop");
|
|
246
|
-
function start() {
|
|
247
|
-
if (running || closed || terminalError || !observers.size || !input.transport.events) return;
|
|
248
|
-
controller = new AbortController();
|
|
249
|
-
subscribedScopes = subscriptionKey();
|
|
250
|
-
running = loop(controller.signal).finally(() => {
|
|
251
|
-
running = void 0;
|
|
252
|
-
controller = void 0;
|
|
253
|
-
if (!closed && observers.size) start();
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
__name(start, "start");
|
|
257
|
-
return {
|
|
258
|
-
isConnected: /* @__PURE__ */ __name(() => connected, "isConnected"),
|
|
259
|
-
observe(listener, hooks) {
|
|
260
|
-
if (closed) throw new EndpointError("Endpoint is closed", { code: "ENDPOINT_CLOSED", retryable: false });
|
|
261
|
-
const observer = { listener, hooks };
|
|
262
|
-
observers.add(observer);
|
|
263
|
-
if (connected) safe(hooks?.onConnected);
|
|
264
|
-
if (running && subscribedScopes !== subscriptionKey()) controller?.abort();
|
|
265
|
-
start();
|
|
266
|
-
return () => {
|
|
267
|
-
observers.delete(observer);
|
|
268
|
-
if (!observers.size || subscribedScopes !== subscriptionKey()) controller?.abort();
|
|
269
|
-
};
|
|
270
|
-
},
|
|
271
|
-
async close() {
|
|
272
|
-
closed = true;
|
|
273
|
-
controller?.abort();
|
|
274
|
-
observers.clear();
|
|
275
|
-
await running;
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
__name(createEndpointEventHub, "createEndpointEventHub");
|
|
280
|
-
var MAX_EVENT_FRAME_CHARS = 2 * 1024 * 1024;
|
|
281
|
-
var DECODE_SLICE_BYTES = 64 * 1024;
|
|
282
|
-
async function* readEndpointEvents(response, options) {
|
|
283
|
-
if (!response.body) throw new EndpointError("Canon returned an empty event stream", { code: "INVALID_EVENT_STREAM" });
|
|
284
|
-
const reader = response.body.getReader();
|
|
285
|
-
const decoder = new TextDecoder();
|
|
286
|
-
let buffer = "";
|
|
287
|
-
let kind = "message";
|
|
288
|
-
let id2;
|
|
289
|
-
let data = [];
|
|
290
|
-
let dataSize = 0;
|
|
291
|
-
let frameSize = 0;
|
|
292
|
-
const abort = /* @__PURE__ */ __name(() => {
|
|
293
|
-
void reader.cancel().catch(() => void 0);
|
|
294
|
-
}, "abort");
|
|
295
|
-
options.signal.addEventListener("abort", abort, { once: true });
|
|
296
|
-
try {
|
|
297
|
-
while (!options.signal.aborted) {
|
|
298
|
-
let timer;
|
|
299
|
-
const chunk = await Promise.race([
|
|
300
|
-
reader.read(),
|
|
301
|
-
new Promise((_, reject) => {
|
|
302
|
-
timer = setTimeout(() => {
|
|
303
|
-
void reader.cancel().catch(() => void 0);
|
|
304
|
-
reject(new EndpointError("Canon event stream stalled", { code: "STREAM_IDLE" }));
|
|
305
|
-
}, options.idleTimeoutMs ?? 6e4);
|
|
306
|
-
})
|
|
307
|
-
]).finally(() => {
|
|
308
|
-
if (timer) clearTimeout(timer);
|
|
309
|
-
});
|
|
310
|
-
if (chunk.done) break;
|
|
311
|
-
for (let offset = 0; offset < chunk.value.length; offset += DECODE_SLICE_BYTES) {
|
|
312
|
-
buffer += decoder.decode(chunk.value.subarray(offset, offset + DECODE_SLICE_BYTES), { stream: true });
|
|
313
|
-
let newline;
|
|
314
|
-
while ((newline = buffer.indexOf("\n")) !== -1) {
|
|
315
|
-
const line = buffer.slice(0, newline).replace(/\r$/, "");
|
|
316
|
-
buffer = buffer.slice(newline + 1);
|
|
317
|
-
frameSize += line.length + 1;
|
|
318
|
-
if (frameSize > MAX_EVENT_FRAME_CHARS) throw new EndpointError("Canon event frame is too large", { code: "INVALID_EVENT_STREAM" });
|
|
319
|
-
if (!line) {
|
|
320
|
-
if (data.length) {
|
|
321
|
-
const value2 = JSON.parse(data.join("\n"));
|
|
322
|
-
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new EndpointError("Invalid Canon event payload", { code: "INVALID_EVENT_STREAM" });
|
|
323
|
-
validateEndpointContentBoundary("event", kind, value2);
|
|
324
|
-
if (kind === "error") throw decodeEndpointError(value2, { stream: true, ...!response.ok ? { status: response.status } : {}, retryAfter: response.headers.get("Retry-After") });
|
|
325
|
-
yield { ...id2 ? { id: id2 } : {}, kind, data: value2 };
|
|
326
|
-
}
|
|
327
|
-
kind = "message";
|
|
328
|
-
id2 = void 0;
|
|
329
|
-
data = [];
|
|
330
|
-
dataSize = 0;
|
|
331
|
-
frameSize = 0;
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
if (line.startsWith(":")) continue;
|
|
335
|
-
const separator = line.indexOf(":");
|
|
336
|
-
const field = separator < 0 ? line : line.slice(0, separator);
|
|
337
|
-
const value = separator < 0 ? "" : line.slice(separator + 1).replace(/^ /, "");
|
|
338
|
-
if (field === "event") kind = value;
|
|
339
|
-
else if (field === "id" && !value.includes("\0")) id2 = value;
|
|
340
|
-
else if (field === "data") {
|
|
341
|
-
data.push(value);
|
|
342
|
-
dataSize += value.length + 1;
|
|
343
|
-
if (dataSize > MAX_EVENT_FRAME_CHARS) throw new EndpointError("Canon event frame is too large", { code: "INVALID_EVENT_STREAM" });
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
if (frameSize + buffer.length > MAX_EVENT_FRAME_CHARS) throw new EndpointError("Canon event frame is too large", { code: "INVALID_EVENT_STREAM" });
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
} finally {
|
|
350
|
-
options.signal.removeEventListener("abort", abort);
|
|
351
|
-
await reader.cancel().catch(() => void 0);
|
|
352
|
-
reader.releaseLock();
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
__name(readEndpointEvents, "readEndpointEvents");
|
|
356
|
-
|
|
357
|
-
// ../endpoint/src/mls-dev-transport.ts
|
|
358
|
-
var utf8 = new TextEncoder();
|
|
359
|
-
function failure(code, status = 0, retryable = false, retryAfterMs) {
|
|
360
|
-
return new EndpointError("MLS Dev request failed", { code, status, retryable, ...retryAfterMs === void 0 ? {} : { retryAfterMs } });
|
|
361
|
-
}
|
|
362
|
-
__name(failure, "failure");
|
|
363
|
-
function discard(response) {
|
|
364
|
-
try {
|
|
365
|
-
void response.body?.cancel().catch(() => {
|
|
366
|
-
});
|
|
367
|
-
} catch {
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
__name(discard, "discard");
|
|
371
|
-
function createMlsDevDeviceHttpTransport(config, dependencies) {
|
|
372
|
-
const invalidConfig = /* @__PURE__ */ __name(() => failure("INVALID_MLS_DEV_TRANSPORT_CONFIG"), "invalidConfig");
|
|
373
|
-
const timeoutMs = dependencies.timeoutMs ?? 3e4;
|
|
374
|
-
const environment = dependencies.environmentId;
|
|
375
|
-
if (!isMlsEnvironmentId(environment) || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 12e4 || !config || !Array.isArray(config.trustedOrigins) || !config.trustedOrigins.length || config.trustedOrigins.length > 16 || config.allowSyntheticLoopbackHttp !== void 0 && typeof config.allowSyntheticLoopbackHttp !== "boolean") throw invalidConfig();
|
|
376
|
-
function url(value) {
|
|
377
|
-
try {
|
|
378
|
-
if (typeof value !== "string" || value.length > 2048) throw invalidConfig();
|
|
379
|
-
const parsed = new URL(value);
|
|
380
|
-
const localHttp = config.allowSyntheticLoopbackHttp === true && parsed.protocol === "http:" && (parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]");
|
|
381
|
-
if (parsed.href !== value || parsed.username || parsed.password || parsed.search || parsed.hash || parsed.protocol !== "https:" && !localHttp) throw invalidConfig();
|
|
382
|
-
return parsed;
|
|
383
|
-
} catch {
|
|
384
|
-
throw invalidConfig();
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
__name(url, "url");
|
|
388
|
-
const origins = new Set(config.trustedOrigins.map((value) => {
|
|
389
|
-
const parsed = url(value + "/");
|
|
390
|
-
if (parsed.origin !== value || parsed.pathname !== "/") throw invalidConfig();
|
|
391
|
-
return parsed.origin;
|
|
392
|
-
}));
|
|
393
|
-
function route(value) {
|
|
394
|
-
const parsed = url(value);
|
|
395
|
-
if (!origins.has(parsed.origin) || !/^\/(?:[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*\/?)?$/.test(parsed.pathname)) throw invalidConfig();
|
|
396
|
-
return parsed.href.replace(/\/$/, "");
|
|
397
|
-
}
|
|
398
|
-
__name(route, "route");
|
|
399
|
-
const enrollmentUrl = route(config.enrollmentUrl);
|
|
400
|
-
const credentialUrl = route(config.credentialUrl);
|
|
401
|
-
const reconciliationUrl = route(config.reconciliationUrl);
|
|
402
|
-
const keyPackagesUrl = config.keyPackagesUrl === void 0 ? void 0 : route(config.keyPackagesUrl);
|
|
403
|
-
const observations = /* @__PURE__ */ new Map();
|
|
404
|
-
async function call(base, action, input, parseInput, parseOutput, maxBytes, signal) {
|
|
405
|
-
let request;
|
|
406
|
-
let body;
|
|
407
|
-
try {
|
|
408
|
-
request = parseInput(input);
|
|
409
|
-
body = JSON.stringify(request);
|
|
410
|
-
if (utf8.encode(body).length > maxBytes) throw new Error();
|
|
411
|
-
} catch {
|
|
412
|
-
throw failure("INVALID_MLS_DEV_REQUEST");
|
|
413
|
-
}
|
|
414
|
-
const controller = new AbortController();
|
|
415
|
-
let stopError;
|
|
416
|
-
let rejectStopped;
|
|
417
|
-
const stopped = new Promise((_resolve, reject) => {
|
|
418
|
-
rejectStopped = reject;
|
|
419
|
-
});
|
|
420
|
-
void stopped.catch(() => {
|
|
421
|
-
});
|
|
422
|
-
const stop = /* @__PURE__ */ __name((error) => {
|
|
423
|
-
if (stopError) return;
|
|
424
|
-
stopError = error;
|
|
425
|
-
controller.abort();
|
|
426
|
-
rejectStopped(error);
|
|
427
|
-
}, "stop");
|
|
428
|
-
const abort = /* @__PURE__ */ __name(() => stop(failure("MLS_DEV_REQUEST_ABORTED")), "abort");
|
|
429
|
-
if (signal?.aborted) abort();
|
|
430
|
-
else signal?.addEventListener("abort", abort, { once: true });
|
|
431
|
-
const timeout = setTimeout(() => stop(failure("MLS_DEV_REQUEST_TIMEOUT", 0, true)), timeoutMs);
|
|
432
|
-
async function bounded(work) {
|
|
433
|
-
if (stopError) throw stopError;
|
|
434
|
-
const result = await Promise.race([Promise.resolve().then(() => {
|
|
435
|
-
if (stopError) throw stopError;
|
|
436
|
-
return work();
|
|
437
|
-
}), stopped]);
|
|
438
|
-
if (stopError) throw stopError;
|
|
439
|
-
return result;
|
|
440
|
-
}
|
|
441
|
-
__name(bounded, "bounded");
|
|
442
|
-
const target = base + "/" + action;
|
|
443
|
-
let response;
|
|
444
|
-
try {
|
|
445
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
446
|
-
let token;
|
|
447
|
-
try {
|
|
448
|
-
token = await bounded(() => dependencies.credentials.getToken(attempt > 0));
|
|
449
|
-
} catch {
|
|
450
|
-
throw stopError ?? failure("MLS_DEV_AUTHENTICATION_UNAVAILABLE", 0, true);
|
|
451
|
-
}
|
|
452
|
-
if (typeof token !== "string" || !token || token.length > 8185 || /\s/.test(token)) throw failure("UNAUTHENTICATED", 401);
|
|
453
|
-
let ownership;
|
|
454
|
-
try {
|
|
455
|
-
ownership = dependencies.ownershipHeaders();
|
|
456
|
-
} catch {
|
|
457
|
-
throw failure("INVALID_OWNERSHIP_VERSION");
|
|
458
|
-
}
|
|
459
|
-
response = await bounded(async () => {
|
|
460
|
-
let value;
|
|
461
|
-
try {
|
|
462
|
-
value = await dependencies.fetch(target, {
|
|
463
|
-
method: "POST",
|
|
464
|
-
redirect: "error",
|
|
465
|
-
cache: "no-store",
|
|
466
|
-
credentials: "omit",
|
|
467
|
-
referrerPolicy: "no-referrer",
|
|
468
|
-
signal: controller.signal,
|
|
469
|
-
headers: {
|
|
470
|
-
Authorization: "Bearer " + token,
|
|
471
|
-
"Content-Type": "application/json",
|
|
472
|
-
Accept: "application/json",
|
|
473
|
-
"X-Canon-Endpoint-Protocol": CANON_ENDPOINT_PROTOCOL,
|
|
474
|
-
...ownership
|
|
475
|
-
},
|
|
476
|
-
body
|
|
477
|
-
});
|
|
478
|
-
} catch {
|
|
479
|
-
throw stopError ?? failure("MLS_DEV_REQUEST_UNAVAILABLE", 0, true);
|
|
480
|
-
}
|
|
481
|
-
if (stopError) discard(value);
|
|
482
|
-
return value;
|
|
483
|
-
});
|
|
484
|
-
if (response.redirected || response.url !== target || response.status >= 300 && response.status < 400) {
|
|
485
|
-
throw failure("MLS_DEV_REDIRECT_REJECTED");
|
|
486
|
-
}
|
|
487
|
-
const responseEnvironment = response.headers.get("X-Canon-Environment-Id");
|
|
488
|
-
const gateway = response.status === 429 || response.status >= 500;
|
|
489
|
-
if (responseEnvironment !== environment && !(responseEnvironment === null && gateway)) throw failure("ENVIRONMENT_MISMATCH", 409);
|
|
490
|
-
if (response.status === 401 && attempt === 0) {
|
|
491
|
-
discard(response);
|
|
492
|
-
response = void 0;
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
if (!response.ok) {
|
|
496
|
-
const retry = response.headers.get("Retry-After");
|
|
497
|
-
const seconds = retry && /^\d{1,6}$/.test(retry) ? Number(retry) : 0;
|
|
498
|
-
throw failure("HTTP_" + response.status, response.status, gateway, seconds > 0 ? seconds * 1e3 : void 0);
|
|
499
|
-
}
|
|
500
|
-
if (!/^application\/json(?:\s*;|$)/i.test(response.headers.get("Content-Type") ?? "")) throw failure("INVALID_MLS_DEV_RESPONSE", 502);
|
|
501
|
-
const length = response.headers.get("Content-Length");
|
|
502
|
-
if (length !== null && (!/^\d+$/.test(length) || Number(length) > maxBytes)) throw failure("INVALID_MLS_DEV_RESPONSE", 502);
|
|
503
|
-
const reader = response.body?.getReader();
|
|
504
|
-
if (!reader) throw failure("INVALID_MLS_DEV_RESPONSE", 502);
|
|
505
|
-
let count = 0;
|
|
506
|
-
let text = "";
|
|
507
|
-
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
508
|
-
try {
|
|
509
|
-
for (; ; ) {
|
|
510
|
-
const chunk = await bounded(() => reader.read());
|
|
511
|
-
if (chunk.done) break;
|
|
512
|
-
count += chunk.value.byteLength;
|
|
513
|
-
if (count > maxBytes) throw failure("INVALID_MLS_DEV_RESPONSE", 502);
|
|
514
|
-
text += decoder.decode(chunk.value, { stream: true });
|
|
515
|
-
}
|
|
516
|
-
text += decoder.decode();
|
|
517
|
-
const result = parseOutput(JSON.parse(text));
|
|
518
|
-
if (stopError) throw stopError;
|
|
519
|
-
return { request, result };
|
|
520
|
-
} catch (error) {
|
|
521
|
-
if (error === stopError) throw error;
|
|
522
|
-
throw failure("INVALID_MLS_DEV_RESPONSE", 502);
|
|
523
|
-
} finally {
|
|
524
|
-
try {
|
|
525
|
-
void reader.cancel().catch(() => {
|
|
526
|
-
});
|
|
527
|
-
} catch {
|
|
528
|
-
}
|
|
529
|
-
reader.releaseLock();
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
throw failure("UNAUTHENTICATED", 401);
|
|
533
|
-
} catch (error) {
|
|
534
|
-
if (error instanceof EndpointError) throw error;
|
|
535
|
-
throw stopError ?? failure("MLS_DEV_REQUEST_UNAVAILABLE", 0, true);
|
|
536
|
-
} finally {
|
|
537
|
-
if (response) discard(response);
|
|
538
|
-
clearTimeout(timeout);
|
|
539
|
-
signal?.removeEventListener("abort", abort);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
__name(call, "call");
|
|
543
|
-
const invalidResponse = /* @__PURE__ */ __name(() => failure("INVALID_MLS_DEV_RESPONSE", 502), "invalidResponse");
|
|
544
|
-
return {
|
|
545
|
-
...keyPackagesUrl ? { keyPackages: {
|
|
546
|
-
async publish(input, signal) {
|
|
547
|
-
const { request, result } = await call(
|
|
548
|
-
keyPackagesUrl,
|
|
549
|
-
"publish",
|
|
550
|
-
input,
|
|
551
|
-
parseMlsDevKeyPackagePublish,
|
|
552
|
-
parseMlsDevKeyPackagePublicationReceipt,
|
|
553
|
-
MLS_DEV_KEY_PACKAGES_MAX_BYTES,
|
|
554
|
-
signal
|
|
555
|
-
);
|
|
556
|
-
if (result.requestId !== request.requestId) throw invalidResponse();
|
|
557
|
-
return result;
|
|
558
|
-
},
|
|
559
|
-
async claim(input, signal) {
|
|
560
|
-
const { request, result } = await call(
|
|
561
|
-
keyPackagesUrl,
|
|
562
|
-
"claim",
|
|
563
|
-
input,
|
|
564
|
-
parseMlsDevKeyPackageClaim,
|
|
565
|
-
parseMlsDevKeyPackageClaimReceipt,
|
|
566
|
-
MLS_DEV_KEY_PACKAGES_MAX_BYTES,
|
|
567
|
-
signal
|
|
568
|
-
);
|
|
569
|
-
if (result.requestId !== request.requestId || result.recipientDeviceId !== request.recipientDeviceId || result.claimantDeviceId !== parseMlsDevCredentialToken(request.claimantCredential).unsigned.claims.device.deviceId || canonicalEndpointJson(result.preconditions) !== canonicalEndpointJson(request.preconditions)) throw invalidResponse();
|
|
570
|
-
return result;
|
|
571
|
-
}
|
|
572
|
-
} } : {},
|
|
573
|
-
async beginEnrollment(input, signal) {
|
|
574
|
-
const { request, result } = await call(enrollmentUrl, "begin", input, parseMlsDevEnrollmentBegin, parseMlsDevEnrollmentChallenge, MLS_DEV_ENROLLMENT_MAX_BYTES, signal);
|
|
575
|
-
if (result.installationId !== request.installationId || result.signaturePublicKey !== request.signaturePublicKey) throw invalidResponse();
|
|
576
|
-
return result;
|
|
577
|
-
},
|
|
578
|
-
async completeEnrollment(input, signal) {
|
|
579
|
-
return (await call(enrollmentUrl, "complete", input, parseMlsDevEnrollmentComplete, parseMlsDevEnrollmentResult, MLS_DEV_ENROLLMENT_MAX_BYTES, signal)).result;
|
|
580
|
-
},
|
|
581
|
-
async beginCredential(input, signal) {
|
|
582
|
-
const { request, result } = await call(credentialUrl, "begin", input, parseMlsDevCredentialBegin, parseMlsDevCredentialChallenge, MLS_DEV_CREDENTIAL_MAX_BYTES, signal);
|
|
583
|
-
if (result.credential.claims.device.deviceId !== request.deviceId || result.credential.claims.generation !== request.expectedCredentialGeneration + 1) throw invalidResponse();
|
|
584
|
-
return result;
|
|
585
|
-
},
|
|
586
|
-
async completeCredential(input, signal) {
|
|
587
|
-
return (await call(credentialUrl, "complete", input, parseMlsDevCredentialComplete, parseMlsDevCredentialReceipt, MLS_DEV_CREDENTIAL_MAX_BYTES, signal)).result;
|
|
588
|
-
},
|
|
589
|
-
async beginReconciliation(input, signal) {
|
|
590
|
-
const { request, result } = await call(reconciliationUrl, "begin", input, parseMlsDevCredentialReconciliationBegin, parseMlsDevCredentialReconciliationChallenge, MLS_DEV_CREDENTIAL_RECONCILIATION_MAX_BYTES, signal);
|
|
591
|
-
if (result.device.deviceId !== request.deviceId || result.requestId !== request.requestId || result.expectedCredentialGeneration !== request.expectedCredentialGeneration) throw invalidResponse();
|
|
592
|
-
return result;
|
|
593
|
-
},
|
|
594
|
-
async completeReconciliation(input, signal) {
|
|
595
|
-
const { request, result } = await call(reconciliationUrl, "complete", input, parseMlsDevCredentialReconciliationComplete, parseMlsDevCredentialReconciliationReceipt, MLS_DEV_CREDENTIAL_RECONCILIATION_MAX_BYTES, signal);
|
|
596
|
-
if (result.challenge.challengeId !== request.challengeId) throw invalidResponse();
|
|
597
|
-
const key = JSON.stringify(result);
|
|
598
|
-
observations.set(key, true);
|
|
599
|
-
if (observations.size > 32) observations.delete(observations.keys().next().value);
|
|
600
|
-
return result;
|
|
601
|
-
},
|
|
602
|
-
async revokeCredential(input, signal) {
|
|
603
|
-
const { request, result } = await call(credentialUrl, "revoke", input, parseMlsDevCredentialRevoke, parseMlsDevCredentialRevocationReceipt, MLS_DEV_CREDENTIAL_MAX_BYTES, signal);
|
|
604
|
-
if (result.deviceId !== request.deviceId) throw invalidResponse();
|
|
605
|
-
return result;
|
|
606
|
-
},
|
|
607
|
-
verifyGenerationObservation(receipt) {
|
|
608
|
-
try {
|
|
609
|
-
return observations.has(JSON.stringify(parseMlsDevCredentialReconciliationReceipt(receipt)));
|
|
610
|
-
} catch {
|
|
611
|
-
return false;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
__name(createMlsDevDeviceHttpTransport, "createMlsDevDeviceHttpTransport");
|
|
617
|
-
|
|
618
|
-
// ../endpoint/src/transport.ts
|
|
619
|
-
var EndpointError = class extends Error {
|
|
620
|
-
static {
|
|
621
|
-
__name(this, "EndpointError");
|
|
622
|
-
}
|
|
623
|
-
status;
|
|
624
|
-
code;
|
|
625
|
-
retryable;
|
|
626
|
-
operationId;
|
|
627
|
-
retryAfterMs;
|
|
628
|
-
constructor(message, options = {}) {
|
|
629
|
-
super(message);
|
|
630
|
-
this.name = "EndpointError";
|
|
631
|
-
this.status = options.status ?? 0;
|
|
632
|
-
this.code = options.code ?? "ENDPOINT_UNAVAILABLE";
|
|
633
|
-
this.retryable = options.retryable ?? classifyTransferHttpFailure(this.status) === "retry";
|
|
634
|
-
this.operationId = options.operationId;
|
|
635
|
-
this.retryAfterMs = options.retryAfterMs;
|
|
636
|
-
}
|
|
637
|
-
};
|
|
638
|
-
var retryableStreamErrors = /* @__PURE__ */ new Set([
|
|
639
|
-
"rate_limited",
|
|
640
|
-
"connection_limit",
|
|
641
|
-
"service_overloaded",
|
|
642
|
-
"listener_failed",
|
|
643
|
-
"connection_reclaimed",
|
|
644
|
-
"connection_stale",
|
|
645
|
-
"server_shutting_down",
|
|
646
|
-
"authorization_changed",
|
|
647
|
-
"authorization_check_failed"
|
|
648
|
-
]);
|
|
649
|
-
function decodeEndpointError(value, options = {}) {
|
|
650
|
-
const details = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
651
|
-
const status = options.status ?? (typeof details.status === "number" && Number.isInteger(details.status) && details.status >= 400 && details.status <= 599 ? details.status : 0);
|
|
652
|
-
const code = typeof details.code === "string" ? details.code : options.stream ? "STREAM_UNAVAILABLE" : `HTTP_${status}`;
|
|
653
|
-
const positive = /* @__PURE__ */ __name((value2) => typeof value2 === "number" && Number.isFinite(value2) && value2 > 0 ? value2 : void 0, "positive");
|
|
654
|
-
const retryHeader = options.retryAfter?.trim();
|
|
655
|
-
const retryAfterMs = positive(retryHeader ? /^\d+(?:\.\d+)?$/.test(retryHeader) ? Number(retryHeader) * 1e3 : Date.parse(retryHeader) - Date.now() : void 0) ?? positive(details.retryAfterMs) ?? positive(typeof details.retryAfter === "number" ? details.retryAfter * 1e3 : void 0);
|
|
656
|
-
const accessTokenExpired = status === 401 && code === "CONNECTOR_ACCESS_TOKEN_EXPIRED";
|
|
657
|
-
const retryable = details.retryable !== false && (status ? accessTokenExpired || status === 429 || status >= 500 : options.stream ? retryableStreamErrors.has(code) : true);
|
|
658
|
-
return new EndpointError(typeof details.error === "string" ? details.error : typeof details.message === "string" ? details.message : `Canon ${options.stream ? "stream" : "request"} failed${status ? ` (${status})` : ""}`, {
|
|
659
|
-
status,
|
|
660
|
-
code,
|
|
661
|
-
retryable,
|
|
662
|
-
...retryAfterMs === void 0 ? {} : { retryAfterMs }
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
__name(decodeEndpointError, "decodeEndpointError");
|
|
666
|
-
async function readHttpError(response, signal) {
|
|
667
|
-
if (response.headers.get("Content-Type")?.toLowerCase().startsWith("text/event-stream")) {
|
|
668
|
-
try {
|
|
669
|
-
for await (const _event of readEndpointEvents(response, { signal })) {
|
|
670
|
-
}
|
|
671
|
-
} catch (error) {
|
|
672
|
-
if (error instanceof EndpointError) return error;
|
|
673
|
-
throw error;
|
|
674
|
-
}
|
|
675
|
-
} else {
|
|
676
|
-
return decodeEndpointError(await response.json().catch(() => null), { status: response.status, retryAfter: response.headers.get("Retry-After") });
|
|
677
|
-
}
|
|
678
|
-
return decodeEndpointError(null, { status: response.status, retryAfter: response.headers.get("Retry-After"), stream: true });
|
|
679
|
-
}
|
|
680
|
-
__name(readHttpError, "readHttpError");
|
|
681
|
-
async function readBoundedGroupJson(response, signal) {
|
|
682
|
-
const maximum = 4 * 1024 * 1024;
|
|
683
|
-
const invalid2 = /* @__PURE__ */ __name(() => new EndpointError("Invalid MLS group response", { status: 502, code: "INVALID_MLS_DEV_GROUP_RESPONSE", retryable: false }), "invalid");
|
|
684
|
-
const length = response.headers.get("Content-Length");
|
|
685
|
-
if (length !== null && (!/^\d+$/.test(length) || Number(length) > maximum)) throw invalid2();
|
|
686
|
-
const reader = response.body?.getReader();
|
|
687
|
-
if (!reader) throw invalid2();
|
|
688
|
-
let rejectAbort;
|
|
689
|
-
const stopped = new Promise((_resolve, reject) => {
|
|
690
|
-
rejectAbort = reject;
|
|
691
|
-
});
|
|
692
|
-
void stopped.catch(() => {
|
|
693
|
-
});
|
|
694
|
-
const abort = /* @__PURE__ */ __name(() => rejectAbort(new EndpointError("MLS group request aborted", { code: "MLS_DEV_REQUEST_ABORTED", retryable: true })), "abort");
|
|
695
|
-
if (signal.aborted) abort();
|
|
696
|
-
else signal.addEventListener("abort", abort, { once: true });
|
|
697
|
-
try {
|
|
698
|
-
const decoder = new TextDecoder("utf-8", { fatal: true }), parts = [];
|
|
699
|
-
let size = 0;
|
|
700
|
-
for (; ; ) {
|
|
701
|
-
if (signal.aborted) throw new EndpointError("MLS group request aborted", { code: "MLS_DEV_REQUEST_ABORTED", retryable: true });
|
|
702
|
-
const chunk = await Promise.race([reader.read(), stopped]);
|
|
703
|
-
if (chunk.done) break;
|
|
704
|
-
size += chunk.value.byteLength;
|
|
705
|
-
if (size > maximum) throw invalid2();
|
|
706
|
-
parts.push(decoder.decode(chunk.value, { stream: true }));
|
|
707
|
-
}
|
|
708
|
-
parts.push(decoder.decode());
|
|
709
|
-
return JSON.parse(parts.join(""));
|
|
710
|
-
} catch (error) {
|
|
711
|
-
if (error instanceof EndpointError) throw error;
|
|
712
|
-
throw invalid2();
|
|
713
|
-
} finally {
|
|
714
|
-
signal.removeEventListener("abort", abort);
|
|
715
|
-
try {
|
|
716
|
-
void reader.cancel().catch(() => {
|
|
717
|
-
});
|
|
718
|
-
} catch {
|
|
719
|
-
}
|
|
720
|
-
reader.releaseLock();
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
__name(readBoundedGroupJson, "readBoundedGroupJson");
|
|
724
|
-
function hasEnvironmentMismatch(response, expected) {
|
|
725
|
-
const environment = response.headers.get("X-Canon-Environment-Id");
|
|
726
|
-
return environment !== expected && !(environment === null && (response.status === 429 || response.status >= 500));
|
|
727
|
-
}
|
|
728
|
-
__name(hasEnvironmentMismatch, "hasEnvironmentMismatch");
|
|
729
|
-
function validateEndpointContentBoundary(lane, name, payload) {
|
|
730
|
-
try {
|
|
731
|
-
endpointContentBoundary(lane, name, payload);
|
|
732
|
-
} catch {
|
|
733
|
-
throw new EndpointError("Endpoint content lane requires a protocol update", { code: "ENDPOINT_CONTENT_UNCLASSIFIED", retryable: false });
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
__name(validateEndpointContentBoundary, "validateEndpointContentBoundary");
|
|
737
|
-
function createHttpEndpointTransport(options) {
|
|
738
|
-
const fetcher = options.fetch ?? globalThis.fetch;
|
|
739
|
-
const clientHeaders = options.client ? { [CANON_CLIENT_HEADER]: formatCanonClientHeader(options.client) } : {};
|
|
740
|
-
const updateRequired = /* @__PURE__ */ __name((error) => {
|
|
741
|
-
if (error.code === CLIENT_UPDATE_REQUIRED) try {
|
|
742
|
-
options.onClientUpdateRequired?.(error);
|
|
743
|
-
} catch {
|
|
744
|
-
}
|
|
745
|
-
return error;
|
|
746
|
-
}, "updateRequired");
|
|
747
|
-
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
748
|
-
let resolvedStreamId;
|
|
749
|
-
function streamIdentity() {
|
|
750
|
-
return resolvedStreamId ??= Promise.resolve().then(async () => {
|
|
751
|
-
const value = typeof options.streamId === "function" ? await options.streamId() : options.streamId;
|
|
752
|
-
if (value !== void 0 && (typeof value !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(value))) {
|
|
753
|
-
throw new EndpointError("Invalid logical stream identity", { code: "INVALID_STREAM_ID", retryable: false });
|
|
754
|
-
}
|
|
755
|
-
return value;
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
__name(streamIdentity, "streamIdentity");
|
|
759
|
-
function ownershipHeaders() {
|
|
760
|
-
const version = typeof options.ownershipVersion === "function" ? options.ownershipVersion() : options.ownershipVersion;
|
|
761
|
-
if (version === void 0) return {};
|
|
762
|
-
if (!Number.isSafeInteger(version) || version < 0) throw new EndpointError("Invalid endpoint ownership generation", { code: "INVALID_OWNERSHIP_VERSION", retryable: false });
|
|
763
|
-
return { "X-Canon-Ownership-Version": String(version) };
|
|
764
|
-
}
|
|
765
|
-
__name(ownershipHeaders, "ownershipHeaders");
|
|
766
|
-
async function call(path, method, body, signal, origin = baseUrl, groupResponse = false) {
|
|
767
|
-
for (let authAttempt = 0; authAttempt < 2; authAttempt++) {
|
|
768
|
-
const token = await options.credentials.getToken(authAttempt > 0);
|
|
769
|
-
if (!token) throw new EndpointError("Authentication is required", { status: 401, code: "UNAUTHENTICATED", retryable: false });
|
|
770
|
-
const controller = new AbortController();
|
|
771
|
-
const abort = /* @__PURE__ */ __name(() => controller.abort(signal?.reason), "abort");
|
|
772
|
-
if (signal?.aborted) abort();
|
|
773
|
-
else signal?.addEventListener("abort", abort, { once: true });
|
|
774
|
-
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3e4);
|
|
775
|
-
try {
|
|
776
|
-
const response = await fetcher(`${origin.replace(/\/$/, "")}${path}`, {
|
|
777
|
-
method,
|
|
778
|
-
signal: controller.signal,
|
|
779
|
-
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "X-Canon-Endpoint-Protocol": CANON_ENDPOINT_PROTOCOL, ...clientHeaders, ...ownershipHeaders() },
|
|
780
|
-
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
781
|
-
});
|
|
782
|
-
if (hasEnvironmentMismatch(response, options.environmentId)) {
|
|
783
|
-
throw new EndpointError("Canon environment does not match this endpoint", { status: 409, code: "ENVIRONMENT_MISMATCH", retryable: false });
|
|
784
|
-
}
|
|
785
|
-
if (response.status === 401 && authAttempt === 0) {
|
|
786
|
-
await response.body?.cancel();
|
|
787
|
-
continue;
|
|
788
|
-
}
|
|
789
|
-
if (!response.ok) throw updateRequired(await readHttpError(response, controller.signal));
|
|
790
|
-
return groupResponse ? await readBoundedGroupJson(response, controller.signal) : await response.json().catch(() => null);
|
|
791
|
-
} finally {
|
|
792
|
-
clearTimeout(timeout);
|
|
793
|
-
signal?.removeEventListener("abort", abort);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
throw new EndpointError("Authentication is required", { status: 401, code: "UNAUTHENTICATED", retryable: false });
|
|
797
|
-
}
|
|
798
|
-
__name(call, "call");
|
|
799
|
-
function receipt(value, operationId, installationId, action) {
|
|
800
|
-
if (!isEndpointOperationReceipt(value) || classifyTransferReceipt(value, { protocol: CANON_ENDPOINT_PROTOCOL, operationId, installationId, action: action ?? value.action }) === "invalid") throw new EndpointError("Canon returned an invalid operation receipt", { status: 502, code: "INVALID_RECEIPT" });
|
|
801
|
-
if (value.operationId !== operationId || value.installationId !== installationId || action !== void 0 && value.action !== action) throw new EndpointError("Canon returned another operation receipt", { status: 502, code: "INVALID_RECEIPT" });
|
|
802
|
-
validateEndpointContentBoundary("operation.receipt", value.action, value);
|
|
803
|
-
return value;
|
|
804
|
-
}
|
|
805
|
-
__name(receipt, "receipt");
|
|
806
|
-
return {
|
|
807
|
-
...options.mlsDevDevice ? { mlsDevDevice: createMlsDevDeviceHttpTransport(options.mlsDevDevice, {
|
|
808
|
-
environmentId: options.environmentId,
|
|
809
|
-
credentials: options.credentials,
|
|
810
|
-
fetch: fetcher,
|
|
811
|
-
timeoutMs: options.timeoutMs,
|
|
812
|
-
ownershipHeaders
|
|
813
|
-
}) } : {},
|
|
814
|
-
async capabilities() {
|
|
815
|
-
const result = await call("/endpoint/capabilities", "GET", void 0);
|
|
816
|
-
if (!result || typeof result !== "object" || result.protocol !== CANON_ENDPOINT_PROTOCOL || !Array.isArray(result.actions) || !Array.isArray(result.queries)) {
|
|
817
|
-
throw new EndpointError("Canon returned invalid endpoint capabilities", { status: 502, code: "INVALID_CAPABILITIES" });
|
|
818
|
-
}
|
|
819
|
-
return result;
|
|
820
|
-
},
|
|
821
|
-
...options.streamUrl ? {
|
|
822
|
-
async *events(streamOptions) {
|
|
823
|
-
const streamId = await streamIdentity();
|
|
824
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
825
|
-
const token = await options.credentials.getToken(attempt > 0);
|
|
826
|
-
if (!token) throw new EndpointError("Authentication is required", { status: 401, code: "UNAUTHENTICATED", retryable: false });
|
|
827
|
-
const connection = new AbortController();
|
|
828
|
-
const abort = /* @__PURE__ */ __name(() => connection.abort(streamOptions.signal.reason), "abort");
|
|
829
|
-
if (streamOptions.signal.aborted) abort();
|
|
830
|
-
else streamOptions.signal.addEventListener("abort", abort, { once: true });
|
|
831
|
-
const handshake = setTimeout(() => connection.abort(), options.timeoutMs ?? 3e4);
|
|
832
|
-
try {
|
|
833
|
-
const scopeQuery = streamOptions.principalScopes?.length ? `&principalScopes=${encodeURIComponent([...new Set(streamOptions.principalScopes)].sort().join(","))}` : "";
|
|
834
|
-
const response = await fetcher(`${options.streamUrl.replace(/\/$/, "")}/endpoint/events?events=${encodeURIComponent((streamOptions.families ?? ["messages", "contacts"]).join(","))}${scopeQuery}`, {
|
|
835
|
-
signal: connection.signal,
|
|
836
|
-
headers: { Authorization: `Bearer ${token}`, Accept: "text/event-stream", "X-Canon-Endpoint-Protocol": CANON_ENDPOINT_PROTOCOL, ...clientHeaders, ...streamId ? { "X-Canon-Stream-Id": streamId } : {}, ...ownershipHeaders(), ...streamOptions.lastEventId ? { "Last-Event-ID": streamOptions.lastEventId } : {} }
|
|
837
|
-
});
|
|
838
|
-
if (hasEnvironmentMismatch(response, options.environmentId)) {
|
|
839
|
-
await response.body?.cancel();
|
|
840
|
-
throw new EndpointError("Canon stream environment does not match this endpoint", { status: 409, code: "ENVIRONMENT_MISMATCH", retryable: false });
|
|
841
|
-
}
|
|
842
|
-
if (response.status === 401 && attempt === 0) {
|
|
843
|
-
await response.body?.cancel();
|
|
844
|
-
continue;
|
|
845
|
-
}
|
|
846
|
-
if (!response.ok) throw updateRequired(await readHttpError(response, connection.signal));
|
|
847
|
-
if (!response.headers.get("Content-Type")?.toLowerCase().startsWith("text/event-stream")) {
|
|
848
|
-
await response.body?.cancel();
|
|
849
|
-
throw new EndpointError("Canon returned an invalid event stream", { status: 502, code: "INVALID_EVENT_STREAM" });
|
|
850
|
-
}
|
|
851
|
-
clearTimeout(handshake);
|
|
852
|
-
let connected = false;
|
|
853
|
-
for await (const event of readEndpointEvents(response, { ...streamOptions, signal: connection.signal })) {
|
|
854
|
-
if (!connected) {
|
|
855
|
-
connected = true;
|
|
856
|
-
streamOptions.onConnected?.();
|
|
857
|
-
}
|
|
858
|
-
yield event;
|
|
859
|
-
}
|
|
860
|
-
return;
|
|
861
|
-
} finally {
|
|
862
|
-
clearTimeout(handshake);
|
|
863
|
-
streamOptions.signal.removeEventListener("abort", abort);
|
|
864
|
-
connection.abort();
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
} : {},
|
|
869
|
-
async execute(operation, signal) {
|
|
870
|
-
validateEndpointContentBoundary("operation.request", operation.action, operation);
|
|
871
|
-
return receipt(await call("/endpoint/operations", "POST", operation, signal, baseUrl, operation.action === "mls_group_control"), operation.operationId, operation.installationId, operation.action);
|
|
872
|
-
},
|
|
873
|
-
async getOperation(operationId, installationId, signal) {
|
|
874
|
-
try {
|
|
875
|
-
return receipt(await call(`/endpoint/operations/${encodeURIComponent(operationId)}?installationId=${encodeURIComponent(installationId)}`, "GET", void 0, signal, baseUrl, true), operationId, installationId);
|
|
876
|
-
} catch (error) {
|
|
877
|
-
if (error instanceof EndpointError && error.status === 404) return null;
|
|
878
|
-
throw error;
|
|
879
|
-
}
|
|
880
|
-
},
|
|
881
|
-
async query(query, parameters, signal) {
|
|
882
|
-
validateEndpointContentBoundary("query.request", query, parameters);
|
|
883
|
-
if (query === "admit_messages" && !options.streamUrl) throw new EndpointError("Agent recovery requires the shared stream endpoint", { code: "STREAM_CONFIGURATION_REQUIRED", retryable: false });
|
|
884
|
-
const response = await call("/endpoint/query", "POST", { protocol: CANON_ENDPOINT_PROTOCOL, query, parameters }, signal, query === "admit_messages" ? options.streamUrl : baseUrl, ["mls_group_controls", "mls_group_feed"].includes(query));
|
|
885
|
-
if (!response || typeof response !== "object" || response.protocol !== CANON_ENDPOINT_PROTOCOL || !Object.hasOwn(response, "result")) {
|
|
886
|
-
throw new EndpointError("Canon returned an invalid query response", { status: 502, code: "INVALID_QUERY_RESPONSE" });
|
|
887
|
-
}
|
|
888
|
-
validateEndpointContentBoundary("query.result", query, response.result);
|
|
889
|
-
return response.result;
|
|
890
|
-
},
|
|
891
|
-
async publish(action, body, signal) {
|
|
892
|
-
validateEndpointContentBoundary("ephemeral.request", action, body);
|
|
893
|
-
const response = await call("/endpoint/ephemeral", "POST", { protocol: CANON_ENDPOINT_PROTOCOL, action, body }, signal);
|
|
894
|
-
if (!response || typeof response !== "object" || response.protocol !== CANON_ENDPOINT_PROTOCOL || !Object.hasOwn(response, "result")) {
|
|
895
|
-
throw new EndpointError("Canon returned an invalid ephemeral response", { status: 502, code: "INVALID_EPHEMERAL_RESPONSE" });
|
|
896
|
-
}
|
|
897
|
-
validateEndpointContentBoundary("ephemeral.result", action, response.result);
|
|
898
|
-
return response.result;
|
|
899
|
-
}
|
|
900
|
-
};
|
|
901
|
-
}
|
|
902
|
-
__name(createHttpEndpointTransport, "createHttpEndpointTransport");
|
|
903
|
-
|
|
904
|
-
// ../endpoint/src/node-mls-custody-protocol.ts
|
|
905
|
-
var NodeMlsDevCustodyError = class extends Error {
|
|
906
|
-
static {
|
|
907
|
-
__name(this, "NodeMlsDevCustodyError");
|
|
908
|
-
}
|
|
909
|
-
code = "MLS_NODE_CUSTODY_REJECTED";
|
|
910
|
-
constructor() {
|
|
911
|
-
super("MLS_NODE_CUSTODY_REJECTED");
|
|
912
|
-
this.name = "NodeMlsDevCustodyError";
|
|
913
|
-
}
|
|
914
|
-
};
|
|
915
|
-
var MAX_INPUT = 8 * 1024 * 1024;
|
|
916
|
-
var MAX_OUTPUT = 4 * 1024 * 1024;
|
|
917
|
-
var DOMAIN = Buffer.from("canon.mls.agent-host.v1\0");
|
|
918
|
-
function rejected() {
|
|
919
|
-
throw new NodeMlsDevCustodyError();
|
|
920
|
-
}
|
|
921
|
-
__name(rejected, "rejected");
|
|
922
|
-
function custodyValue(output, operation) {
|
|
923
|
-
const parsed = parseJson(output);
|
|
924
|
-
if (operation === 4 && isMlsDevReceiptClockSkew(parsed)) throw new EndpointError("Canon accepted this entry ahead of this device's clock", { code: MLS_DEV_GROUP_RECEIPT_CLOCK_SKEW, retryable: true });
|
|
925
|
-
const result = record(parsed, ["version", "ok", "value"]);
|
|
926
|
-
requireValue(result.version === 1 && result.ok === true);
|
|
927
|
-
return result.value;
|
|
928
|
-
}
|
|
929
|
-
__name(custodyValue, "custodyValue");
|
|
930
|
-
function custodyFailure(error) {
|
|
931
|
-
if (error instanceof EndpointError && error.code === MLS_DEV_GROUP_RECEIPT_CLOCK_SKEW) throw error;
|
|
932
|
-
return rejected();
|
|
933
|
-
}
|
|
934
|
-
__name(custodyFailure, "custodyFailure");
|
|
935
|
-
function requireValue(value) {
|
|
936
|
-
if (!value) rejected();
|
|
937
|
-
}
|
|
938
|
-
__name(requireValue, "requireValue");
|
|
939
|
-
function record(value, fields) {
|
|
940
|
-
requireValue(value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype);
|
|
941
|
-
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
942
|
-
requireValue(Object.keys(descriptors).length === fields.length && fields.every((key) => descriptors[key] && "value" in descriptors[key]));
|
|
943
|
-
return value;
|
|
944
|
-
}
|
|
945
|
-
__name(record, "record");
|
|
946
|
-
function sealed(value, maximum, nullable = false) {
|
|
947
|
-
if (nullable && value === null) return null;
|
|
948
|
-
requireValue(typeof value === "string" && value.length <= maximum && value.startsWith("v1:"));
|
|
949
|
-
const body = value.slice(3);
|
|
950
|
-
requireValue(body.length >= 40 && Buffer.from(body, "base64").toString("base64") === body);
|
|
951
|
-
return value;
|
|
952
|
-
}
|
|
953
|
-
__name(sealed, "sealed");
|
|
954
|
-
function frame(operation, values) {
|
|
955
|
-
const chunks = [DOMAIN, Buffer.from([operation])];
|
|
956
|
-
let length = DOMAIN.length + 1;
|
|
957
|
-
for (const value of values) {
|
|
958
|
-
const bytes = value === null ? null : Buffer.from(value, "utf8");
|
|
959
|
-
const prefix3 = Buffer.alloc(4);
|
|
960
|
-
prefix3.writeUInt32BE(bytes?.length ?? 4294967295);
|
|
961
|
-
chunks.push(prefix3);
|
|
962
|
-
if (bytes) chunks.push(bytes);
|
|
963
|
-
length += 4 + (bytes?.length ?? 0);
|
|
964
|
-
requireValue(length <= MAX_INPUT);
|
|
965
|
-
}
|
|
966
|
-
return Buffer.concat(chunks, length);
|
|
967
|
-
}
|
|
968
|
-
__name(frame, "frame");
|
|
969
|
-
function parseJson(bytes) {
|
|
970
|
-
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
971
|
-
const value = JSON.parse(text);
|
|
972
|
-
let offset = 0;
|
|
973
|
-
const whitespace = /* @__PURE__ */ __name(() => {
|
|
974
|
-
while (/\s/.test(text[offset] ?? "") && offset < text.length) offset++;
|
|
975
|
-
}, "whitespace");
|
|
976
|
-
const string = /* @__PURE__ */ __name(() => {
|
|
977
|
-
const start = offset++;
|
|
978
|
-
while (offset < text.length) {
|
|
979
|
-
const c = text[offset++];
|
|
980
|
-
if (c === "\\") offset++;
|
|
981
|
-
else if (c === '"') return JSON.parse(text.slice(start, offset));
|
|
982
|
-
}
|
|
983
|
-
return rejected();
|
|
984
|
-
}, "string");
|
|
985
|
-
const scan = /* @__PURE__ */ __name((depth) => {
|
|
986
|
-
requireValue(depth <= 64);
|
|
987
|
-
whitespace();
|
|
988
|
-
const c = text[offset];
|
|
989
|
-
if (c === "{") {
|
|
990
|
-
offset++;
|
|
991
|
-
whitespace();
|
|
992
|
-
const keys = /* @__PURE__ */ new Set();
|
|
993
|
-
if (text[offset] === "}") {
|
|
994
|
-
offset++;
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
while (true) {
|
|
998
|
-
whitespace();
|
|
999
|
-
requireValue(text[offset] === '"');
|
|
1000
|
-
const key = string();
|
|
1001
|
-
requireValue(!keys.has(key));
|
|
1002
|
-
keys.add(key);
|
|
1003
|
-
whitespace();
|
|
1004
|
-
requireValue(text[offset++] === ":");
|
|
1005
|
-
scan(depth + 1);
|
|
1006
|
-
whitespace();
|
|
1007
|
-
if (text[offset++] === "}") return;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
if (c === "[") {
|
|
1011
|
-
offset++;
|
|
1012
|
-
whitespace();
|
|
1013
|
-
if (text[offset] === "]") {
|
|
1014
|
-
offset++;
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
while (true) {
|
|
1018
|
-
scan(depth + 1);
|
|
1019
|
-
whitespace();
|
|
1020
|
-
if (text[offset++] === "]") return;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
if (c === '"') {
|
|
1024
|
-
string();
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
1027
|
-
while (offset < text.length && !/[\s,\]}]/.test(text[offset])) offset++;
|
|
1028
|
-
}, "scan");
|
|
1029
|
-
scan(0);
|
|
1030
|
-
whitespace();
|
|
1031
|
-
requireValue(offset === text.length);
|
|
1032
|
-
return value;
|
|
1033
|
-
}
|
|
1034
|
-
__name(parseJson, "parseJson");
|
|
1035
|
-
function typedNodeMlsDevCustody(job, invoke, close) {
|
|
1036
|
-
return Object.freeze({
|
|
1037
|
-
prepareProtected: /* @__PURE__ */ __name(((context, commandValue, wrappedState) => job(async (signal) => {
|
|
1038
|
-
const contextJson = JSON.stringify(parseMlsDevDeviceContext(context)), command = parseMlsDevDeviceCommand(commandValue);
|
|
1039
|
-
const wrapped = sealed(wrappedState, 6144, true);
|
|
1040
|
-
requireValue(command.op === "create" === (wrapped === null));
|
|
1041
|
-
requireValue(await invoke(1, [contextJson], signal) === null);
|
|
1042
|
-
return parseMlsDevDevicePreparation(await invoke(2, [contextJson, JSON.stringify(command), wrapped], signal), command.op);
|
|
1043
|
-
})), "prepareProtected"),
|
|
1044
|
-
prepareProtectedKeyPackage: /* @__PURE__ */ __name(((context, command, wrappedState) => job(async (signal) => {
|
|
1045
|
-
const contextJson = JSON.stringify(parseMlsDevDeviceContext(context)), commandJson = JSON.stringify(parseMlsDevKeyPackageCommand(command));
|
|
1046
|
-
const wrapped = sealed(wrappedState, 6144);
|
|
1047
|
-
requireValue(await invoke(1, [contextJson], signal) === null);
|
|
1048
|
-
return parseMlsDevKeyPackagePreparation(await invoke(3, [contextJson, commandJson, wrapped], signal));
|
|
1049
|
-
})), "prepareProtectedKeyPackage"),
|
|
1050
|
-
prepareProtectedGroup: /* @__PURE__ */ __name(((identity, group, command, identityState, groupState, packageState) => job(async (signal) => {
|
|
1051
|
-
const contextJson = JSON.stringify(parseMlsDevDeviceContext(identity)), groupJson = JSON.stringify(parseMlsDevGroupContext(group)), commandJson = JSON.stringify(parseMlsDevGroupCommand(command));
|
|
1052
|
-
const wrapped = sealed(identityState, 6144), protectedGroup = sealed(groupState, 3 * 1024 * 1024, true), protectedPackage = sealed(packageState, 192 * 1024, true);
|
|
1053
|
-
requireValue(await invoke(1, [contextJson], signal) === null);
|
|
1054
|
-
const prepared = parseMlsDevGroupPreparation(await invoke(4, [contextJson, groupJson, commandJson, wrapped, protectedGroup, protectedPackage], signal));
|
|
1055
|
-
const expected = ["create", "add", "remove"].includes(command.op) ? ["control"] : ["join", "process", "finalize"].includes(command.op) ? ["applied"] : command.op === "encrypt_text" || command.op === "encrypt_item" ? ["application"] : command.op === "encrypt_interaction" ? ["interaction_application"] : command.op === "process_application" ? ["text", "item", "interaction", "invalid"] : command.op === "open_secret_answer" ? ["secret"] : ["discarded"];
|
|
1056
|
-
requireValue(expected.includes(prepared.payload.kind));
|
|
1057
|
-
if (prepared.payload.kind === "application") requireValue(command.op === "encrypt_item" === (prepared.payload.payload !== void 0));
|
|
1058
|
-
return prepared;
|
|
1059
|
-
})), "prepareProtectedGroup"),
|
|
1060
|
-
close
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
|
-
__name(typedNodeMlsDevCustody, "typedNodeMlsDevCustody");
|
|
1064
|
-
|
|
1065
|
-
// ../endpoint/src/node-mls-custody.ts
|
|
1066
|
-
var EXECUTABLE = "Contents/MacOS/CanonMlsAgent";
|
|
1067
|
-
var HASH = /^[0-9a-f]{64}$/;
|
|
1068
|
-
var matches = /* @__PURE__ */ __name((pattern, value) => pattern.exec(value)?.[0] === value, "matches");
|
|
1069
|
-
var hash = /* @__PURE__ */ __name((bytes) => createHash("sha256").update(bytes).digest("hex"), "hash");
|
|
1070
|
-
function createNodeMlsDevCustody(options) {
|
|
1071
|
-
const captured = (() => {
|
|
1072
|
-
try {
|
|
1073
|
-
const keys = ["bundlePath", "manifestSha256", "executableSha256", "bundleIdentifier", "teamIdentifier"];
|
|
1074
|
-
if (Object.hasOwn(options, "timeoutMs")) keys.push("timeoutMs");
|
|
1075
|
-
if (Object.hasOwn(options, "signal")) keys.push("signal");
|
|
1076
|
-
const data = record(options, keys);
|
|
1077
|
-
requireValue(typeof data.bundlePath === "string" && isAbsolute(data.bundlePath) && resolve(data.bundlePath) === data.bundlePath && data.bundlePath.endsWith(".app"));
|
|
1078
|
-
for (const key of ["manifestSha256", "executableSha256"]) requireValue(typeof data[key] === "string" && HASH.test(data[key]));
|
|
1079
|
-
requireValue(typeof data.bundleIdentifier === "string" && matches(/^[A-Za-z0-9][A-Za-z0-9.-]{2,150}$/, data.bundleIdentifier));
|
|
1080
|
-
requireValue(typeof data.teamIdentifier === "string" && matches(/^[A-Z0-9]{10}$/, data.teamIdentifier));
|
|
1081
|
-
const timeoutMs = data.timeoutMs ?? 3e4;
|
|
1082
|
-
requireValue(typeof timeoutMs === "number" && Number.isSafeInteger(timeoutMs) && timeoutMs >= 100 && timeoutMs <= 6e4);
|
|
1083
|
-
requireValue(data.signal === void 0 || data.signal instanceof AbortSignal);
|
|
1084
|
-
return Object.freeze({ ...data, timeoutMs });
|
|
1085
|
-
} catch {
|
|
1086
|
-
return rejected();
|
|
1087
|
-
}
|
|
1088
|
-
})();
|
|
1089
|
-
let closed = false;
|
|
1090
|
-
const jobs = /* @__PURE__ */ new Set(), children = /* @__PURE__ */ new Set();
|
|
1091
|
-
const alive = /* @__PURE__ */ __name((signal) => requireValue(!closed && !signal.aborted && !captured.signal?.aborted), "alive");
|
|
1092
|
-
async function file(path, maximum) {
|
|
1093
|
-
const info = await lstat(path);
|
|
1094
|
-
requireValue(info.isFile() && !info.isSymbolicLink() && (info.mode & 18) === 0 && info.size > 0 && info.size <= maximum && (info.uid === process.getuid?.() || info.uid === 0) && await realpath(path) === path);
|
|
1095
|
-
const fd = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
1096
|
-
try {
|
|
1097
|
-
const current = await fd.stat();
|
|
1098
|
-
requireValue(current.ino === info.ino && current.dev === info.dev && current.size === info.size);
|
|
1099
|
-
const bytes = Buffer.alloc(current.size);
|
|
1100
|
-
let count = 0;
|
|
1101
|
-
while (count < bytes.length) {
|
|
1102
|
-
const read = await fd.read(bytes, count, bytes.length - count, count);
|
|
1103
|
-
requireValue(read.bytesRead > 0);
|
|
1104
|
-
count += read.bytesRead;
|
|
1105
|
-
}
|
|
1106
|
-
const after = await fd.stat();
|
|
1107
|
-
requireValue(after.size === current.size && after.mtimeMs === current.mtimeMs);
|
|
1108
|
-
return bytes;
|
|
1109
|
-
} finally {
|
|
1110
|
-
await fd.close();
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
__name(file, "file");
|
|
1114
|
-
function command(binary, args, input, signal, maximum) {
|
|
1115
|
-
alive(signal);
|
|
1116
|
-
const result = new Promise((resolveCommand, rejectCommand) => {
|
|
1117
|
-
const child = spawn(binary, args, { detached: true, stdio: ["pipe", "pipe", "pipe"], env: { PATH: "/usr/bin:/bin" } });
|
|
1118
|
-
let invalid2 = false, stdoutBytes = 0, stderrBytes = 0;
|
|
1119
|
-
const chunks = [];
|
|
1120
|
-
const kill = /* @__PURE__ */ __name(() => {
|
|
1121
|
-
try {
|
|
1122
|
-
if (child.pid) process.kill(-child.pid, "SIGKILL");
|
|
1123
|
-
} catch (error) {
|
|
1124
|
-
if (error.code !== "ESRCH") invalid2 = true;
|
|
1125
|
-
}
|
|
1126
|
-
}, "kill");
|
|
1127
|
-
const cancel = /* @__PURE__ */ __name(() => {
|
|
1128
|
-
invalid2 = true;
|
|
1129
|
-
kill();
|
|
1130
|
-
}, "cancel");
|
|
1131
|
-
signal.addEventListener("abort", cancel, { once: true });
|
|
1132
|
-
child.stdout.on("data", (chunk) => {
|
|
1133
|
-
stdoutBytes += chunk.length;
|
|
1134
|
-
if (stdoutBytes > maximum) cancel();
|
|
1135
|
-
else chunks.push(chunk);
|
|
1136
|
-
});
|
|
1137
|
-
child.stderr.on("data", (chunk) => {
|
|
1138
|
-
stderrBytes += chunk.length;
|
|
1139
|
-
if (stderrBytes > 65536) cancel();
|
|
1140
|
-
});
|
|
1141
|
-
child.on("error", cancel);
|
|
1142
|
-
child.stdin.on("error", cancel);
|
|
1143
|
-
child.on("close", (code) => {
|
|
1144
|
-
signal.removeEventListener("abort", cancel);
|
|
1145
|
-
kill();
|
|
1146
|
-
if (invalid2 || code !== 0 || signal.aborted || closed) rejectCommand(new NodeMlsDevCustodyError());
|
|
1147
|
-
else resolveCommand(Buffer.concat(chunks));
|
|
1148
|
-
});
|
|
1149
|
-
if (signal.aborted) cancel();
|
|
1150
|
-
else child.stdin.end(input);
|
|
1151
|
-
});
|
|
1152
|
-
children.add(result);
|
|
1153
|
-
void result.then(() => children.delete(result), () => children.delete(result));
|
|
1154
|
-
return result;
|
|
1155
|
-
}
|
|
1156
|
-
__name(command, "command");
|
|
1157
|
-
async function verify(signal) {
|
|
1158
|
-
alive(signal);
|
|
1159
|
-
requireValue(platform() === "darwin");
|
|
1160
|
-
const directory = await lstat(captured.bundlePath);
|
|
1161
|
-
requireValue(directory.isDirectory() && !directory.isSymbolicLink() && (directory.mode & 18) === 0 && await realpath(captured.bundlePath) === captured.bundlePath);
|
|
1162
|
-
const manifestBytes = await file(captured.bundlePath + ".manifest.json", 65536);
|
|
1163
|
-
alive(signal);
|
|
1164
|
-
requireValue(hash(manifestBytes) === captured.manifestSha256);
|
|
1165
|
-
const manifest = record(parseJson(manifestBytes), ["schemaVersion", "purpose", "buildStatus", "bundleIdentifier", "teamIdentifier", "executable", "infoPlist", "nativeLibrarySha256", "sourceSha", "sourceDirty", "sourceHashes"]);
|
|
1166
|
-
requireValue(manifest.schemaVersion === 1 && manifest.purpose === "canon.mls.dev-macos-custody" && manifest.buildStatus === "signed" && manifest.bundleIdentifier === captured.bundleIdentifier && manifest.teamIdentifier === captured.teamIdentifier);
|
|
1167
|
-
for (const [name, expectedPath, maximum] of [["executable", EXECUTABLE, 128 * 1024 * 1024], ["infoPlist", "Contents/Info.plist", 32768]]) {
|
|
1168
|
-
const artifact = record(manifest[name], ["path", "sha256", "bytes"]);
|
|
1169
|
-
requireValue(artifact.path === expectedPath && typeof artifact.sha256 === "string" && HASH.test(artifact.sha256));
|
|
1170
|
-
const bytes = await file(join(captured.bundlePath, expectedPath), maximum);
|
|
1171
|
-
alive(signal);
|
|
1172
|
-
requireValue(artifact.bytes === bytes.length && artifact.sha256 === hash(bytes));
|
|
1173
|
-
if (name === "executable") requireValue(artifact.sha256 === captured.executableSha256);
|
|
1174
|
-
}
|
|
1175
|
-
const requirement = `anchor apple generic and identifier "${captured.bundleIdentifier}" and certificate leaf[subject.OU] = "${captured.teamIdentifier}"`;
|
|
1176
|
-
await command("/usr/bin/codesign", ["--verify", "--strict", "--all-architectures", `-R=${requirement}`, captured.bundlePath], null, signal, 16384);
|
|
1177
|
-
alive(signal);
|
|
1178
|
-
return join(captured.bundlePath, EXECUTABLE);
|
|
1179
|
-
}
|
|
1180
|
-
__name(verify, "verify");
|
|
1181
|
-
async function invoke(operation, values, signal) {
|
|
1182
|
-
const executable = await verify(signal);
|
|
1183
|
-
alive(signal);
|
|
1184
|
-
const output = await command(executable, [], frame(operation, values), signal, MAX_OUTPUT);
|
|
1185
|
-
alive(signal);
|
|
1186
|
-
await verify(signal);
|
|
1187
|
-
alive(signal);
|
|
1188
|
-
return custodyValue(output, operation);
|
|
1189
|
-
}
|
|
1190
|
-
__name(invoke, "invoke");
|
|
1191
|
-
async function job(work) {
|
|
1192
|
-
const controller = new AbortController();
|
|
1193
|
-
let timer;
|
|
1194
|
-
const cancel = /* @__PURE__ */ __name(() => controller.abort(), "cancel");
|
|
1195
|
-
jobs.add(controller);
|
|
1196
|
-
try {
|
|
1197
|
-
alive(controller.signal);
|
|
1198
|
-
captured.signal?.addEventListener("abort", cancel, { once: true });
|
|
1199
|
-
const cancelled = new Promise((_, reject) => {
|
|
1200
|
-
controller.signal.addEventListener("abort", () => reject(new NodeMlsDevCustodyError()), { once: true });
|
|
1201
|
-
timer = setTimeout(cancel, captured.timeoutMs);
|
|
1202
|
-
});
|
|
1203
|
-
return await Promise.race([work(controller.signal), cancelled]);
|
|
1204
|
-
} catch (error) {
|
|
1205
|
-
return custodyFailure(error);
|
|
1206
|
-
} finally {
|
|
1207
|
-
if (timer) clearTimeout(timer);
|
|
1208
|
-
controller.abort();
|
|
1209
|
-
captured.signal?.removeEventListener("abort", cancel);
|
|
1210
|
-
jobs.delete(controller);
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
__name(job, "job");
|
|
1214
|
-
return typedNodeMlsDevCustody(job, invoke, async () => {
|
|
1215
|
-
closed = true;
|
|
1216
|
-
for (const job2 of jobs) job2.abort();
|
|
1217
|
-
await Promise.allSettled([...children]);
|
|
1218
|
-
});
|
|
1219
|
-
}
|
|
1220
|
-
__name(createNodeMlsDevCustody, "createNodeMlsDevCustody");
|
|
1221
|
-
|
|
1222
|
-
// ../endpoint/src/portable-node-mls-custody.ts
|
|
1223
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
1224
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
1225
|
-
import { constants as constants2 } from "node:fs";
|
|
1226
|
-
import { lstat as lstat2, open as open2, realpath as realpath2 } from "node:fs/promises";
|
|
1227
|
-
import { dirname, isAbsolute as isAbsolute2, relative, resolve as resolve2 } from "node:path";
|
|
1228
|
-
import { platform as platform2 } from "node:os";
|
|
1229
|
-
var HASH2 = /^[0-9a-f]{64}$/;
|
|
1230
|
-
var absolute = /* @__PURE__ */ __name((value) => typeof value === "string" && isAbsolute2(value) && resolve2(value) === value && !value.includes("\0"), "absolute");
|
|
1231
|
-
var hash2 = /* @__PURE__ */ __name((bytes) => createHash2("sha256").update(bytes).digest("hex"), "hash");
|
|
1232
|
-
var sameIdentity = /* @__PURE__ */ __name((a, b) => a.version === b.version && a.environmentId === b.environmentId && a.installationId === b.installationId && a.principal.type === b.principal.type && a.principal.id === b.principal.id && ("ownershipVersion" in a.principal ? a.principal.ownershipVersion : void 0) === ("ownershipVersion" in b.principal ? b.principal.ownershipVersion : void 0), "sameIdentity");
|
|
1233
|
-
function createPortableNodeMlsDevCustody(options) {
|
|
1234
|
-
const captured = (() => {
|
|
1235
|
-
try {
|
|
1236
|
-
const fields = ["executablePath", "executableSha256", "configurationPath", "configurationSha256", "secretSource"];
|
|
1237
|
-
if (Object.hasOwn(options, "timeoutMs")) fields.push("timeoutMs");
|
|
1238
|
-
if (Object.hasOwn(options, "signal")) fields.push("signal");
|
|
1239
|
-
const value = record(options, fields), source = record(value.secretSource, ["kind", "path"]);
|
|
1240
|
-
requireValue(absolute(value.executablePath) && absolute(value.configurationPath) && absolute(source.path) && source.kind === "managed-file");
|
|
1241
|
-
for (const field of ["executableSha256", "configurationSha256"]) requireValue(typeof value[field] === "string" && HASH2.test(value[field]));
|
|
1242
|
-
const timeoutMs = value.timeoutMs ?? 3e4;
|
|
1243
|
-
requireValue(typeof timeoutMs === "number" && Number.isSafeInteger(timeoutMs) && timeoutMs >= 100 && timeoutMs <= 6e4);
|
|
1244
|
-
requireValue(value.signal === void 0 || value.signal instanceof AbortSignal);
|
|
1245
|
-
return Object.freeze({ ...value, secretSource: Object.freeze({ ...source }), timeoutMs });
|
|
1246
|
-
} catch {
|
|
1247
|
-
return rejected();
|
|
1248
|
-
}
|
|
1249
|
-
})();
|
|
1250
|
-
let closed = false;
|
|
1251
|
-
const jobs = /* @__PURE__ */ new Set(), children = /* @__PURE__ */ new Set();
|
|
1252
|
-
const alive = /* @__PURE__ */ __name((signal) => requireValue(!closed && !signal.aborted && !captured.signal?.aborted), "alive");
|
|
1253
|
-
async function trustedExecutableAncestry() {
|
|
1254
|
-
for (let path = dirname(captured.executablePath); ; path = dirname(path)) {
|
|
1255
|
-
const info = await lstat2(path);
|
|
1256
|
-
requireValue(info.isDirectory() && !info.isSymbolicLink() && (info.uid === process.getuid?.() || info.uid === 0) && (info.mode & 18) === 0);
|
|
1257
|
-
if (dirname(path) === path) return;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
__name(trustedExecutableAncestry, "trustedExecutableAncestry");
|
|
1261
|
-
async function checkedFile(path, maximum, secret = false) {
|
|
1262
|
-
requireValue(await realpath2(path) === path);
|
|
1263
|
-
const info = await lstat2(path);
|
|
1264
|
-
requireValue(info.isFile() && !info.isSymbolicLink() && info.nlink === 1 && info.size > 0 && info.size <= maximum && (info.uid === process.getuid?.() || info.uid === 0) && (info.mode & (secret ? 63 : 18)) === 0);
|
|
1265
|
-
const file = await open2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
1266
|
-
try {
|
|
1267
|
-
const current = await file.stat();
|
|
1268
|
-
requireValue(current.ino === info.ino && current.dev === info.dev && current.size === info.size && current.mode === info.mode && current.uid === info.uid && current.nlink === 1 && await realpath2(path) === path);
|
|
1269
|
-
return file;
|
|
1270
|
-
} catch (error) {
|
|
1271
|
-
await file.close();
|
|
1272
|
-
throw error;
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
__name(checkedFile, "checkedFile");
|
|
1276
|
-
async function publicBytes(path, maximum) {
|
|
1277
|
-
const file = await checkedFile(path, maximum);
|
|
1278
|
-
try {
|
|
1279
|
-
const before = await file.stat(), bytes = Buffer.alloc(before.size);
|
|
1280
|
-
let length = 0;
|
|
1281
|
-
while (length < bytes.length) {
|
|
1282
|
-
const read = await file.read(bytes, length, bytes.length - length, length);
|
|
1283
|
-
requireValue(read.bytesRead > 0);
|
|
1284
|
-
length += read.bytesRead;
|
|
1285
|
-
}
|
|
1286
|
-
const after = await file.stat();
|
|
1287
|
-
requireValue(after.size === before.size && after.mtimeMs === before.mtimeMs && after.ctimeMs === before.ctimeMs);
|
|
1288
|
-
return bytes;
|
|
1289
|
-
} finally {
|
|
1290
|
-
await file.close();
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
__name(publicBytes, "publicBytes");
|
|
1294
|
-
async function verify(signal) {
|
|
1295
|
-
alive(signal);
|
|
1296
|
-
requireValue(platform2() === "darwin" || platform2() === "linux");
|
|
1297
|
-
await trustedExecutableAncestry();
|
|
1298
|
-
alive(signal);
|
|
1299
|
-
const binary = await publicBytes(captured.executablePath, 128 * 1024 * 1024);
|
|
1300
|
-
alive(signal);
|
|
1301
|
-
requireValue(hash2(binary) === captured.executableSha256);
|
|
1302
|
-
const bytes = await publicBytes(captured.configurationPath, 65536);
|
|
1303
|
-
alive(signal);
|
|
1304
|
-
requireValue(hash2(bytes) === captured.configurationSha256);
|
|
1305
|
-
const config = record(parseJson(bytes), ["version", "environmentId", "context", "vaultPath", "issuerKeys", "receiptKeys"]);
|
|
1306
|
-
requireValue(config.version === 1 && isMlsEnvironmentId(config.environmentId) && absolute(config.vaultPath));
|
|
1307
|
-
const configuredContext = record(config.context, ["version", "environmentId", "principal", "installationId"]);
|
|
1308
|
-
const context = parseMlsDevDeviceContext({ ...configuredContext, revision: 0 });
|
|
1309
|
-
requireValue(context.environmentId === config.environmentId);
|
|
1310
|
-
const vaultParent = dirname(config.vaultPath);
|
|
1311
|
-
requireValue(await realpath2(vaultParent) === vaultParent);
|
|
1312
|
-
const parent = await lstat2(vaultParent);
|
|
1313
|
-
requireValue(parent.isDirectory() && parent.uid === process.getuid?.() && (parent.mode & 63) === 0);
|
|
1314
|
-
const fromVault = relative(vaultParent, captured.secretSource.path);
|
|
1315
|
-
requireValue(fromVault.startsWith("../") || isAbsolute2(fromVault));
|
|
1316
|
-
requireValue(captured.secretSource.path !== captured.configurationPath && captured.secretSource.path !== captured.executablePath);
|
|
1317
|
-
return { context, vaultParent };
|
|
1318
|
-
}
|
|
1319
|
-
__name(verify, "verify");
|
|
1320
|
-
async function command(input, provision, signal) {
|
|
1321
|
-
const secret = await checkedFile(captured.secretSource.path, 32, true);
|
|
1322
|
-
try {
|
|
1323
|
-
requireValue((await secret.stat()).size === 32);
|
|
1324
|
-
alive(signal);
|
|
1325
|
-
const result = new Promise((resolveCommand, rejectCommand) => {
|
|
1326
|
-
const child = spawn2(
|
|
1327
|
-
captured.executablePath,
|
|
1328
|
-
[
|
|
1329
|
-
"--config",
|
|
1330
|
-
captured.configurationPath,
|
|
1331
|
-
"--config-sha256",
|
|
1332
|
-
captured.configurationSha256,
|
|
1333
|
-
"--secret-fd",
|
|
1334
|
-
"3",
|
|
1335
|
-
...provision ? ["--provision"] : []
|
|
1336
|
-
],
|
|
1337
|
-
{ detached: true, stdio: ["pipe", "pipe", "pipe", secret.fd], env: { PATH: "/usr/bin:/bin" } }
|
|
1338
|
-
);
|
|
1339
|
-
let invalid2 = false, stdoutBytes = 0, stderrBytes = 0;
|
|
1340
|
-
const chunks = [];
|
|
1341
|
-
const kill = /* @__PURE__ */ __name(() => {
|
|
1342
|
-
try {
|
|
1343
|
-
if (child.pid) process.kill(-child.pid, "SIGKILL");
|
|
1344
|
-
} catch (error) {
|
|
1345
|
-
if (error.code !== "ESRCH") invalid2 = true;
|
|
1346
|
-
}
|
|
1347
|
-
}, "kill");
|
|
1348
|
-
const cancel = /* @__PURE__ */ __name(() => {
|
|
1349
|
-
invalid2 = true;
|
|
1350
|
-
kill();
|
|
1351
|
-
}, "cancel");
|
|
1352
|
-
signal.addEventListener("abort", cancel, { once: true });
|
|
1353
|
-
child.stdout.on("data", (chunk) => {
|
|
1354
|
-
stdoutBytes += chunk.length;
|
|
1355
|
-
if (stdoutBytes > MAX_OUTPUT) cancel();
|
|
1356
|
-
else chunks.push(chunk);
|
|
1357
|
-
});
|
|
1358
|
-
child.stderr.on("data", (chunk) => {
|
|
1359
|
-
stderrBytes += chunk.length;
|
|
1360
|
-
if (stderrBytes > 65536) cancel();
|
|
1361
|
-
});
|
|
1362
|
-
child.on("error", cancel);
|
|
1363
|
-
child.stdin.on("error", cancel);
|
|
1364
|
-
child.on("close", (code) => {
|
|
1365
|
-
signal.removeEventListener("abort", cancel);
|
|
1366
|
-
kill();
|
|
1367
|
-
if (invalid2 || code !== 0 || signal.aborted || closed) rejectCommand(new NodeMlsDevCustodyError());
|
|
1368
|
-
else resolveCommand(Buffer.concat(chunks));
|
|
1369
|
-
});
|
|
1370
|
-
if (signal.aborted) cancel();
|
|
1371
|
-
else child.stdin.end(input);
|
|
1372
|
-
});
|
|
1373
|
-
children.add(result);
|
|
1374
|
-
void result.then(() => children.delete(result), () => children.delete(result));
|
|
1375
|
-
return await result;
|
|
1376
|
-
} finally {
|
|
1377
|
-
await secret.close();
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
__name(command, "command");
|
|
1381
|
-
async function invoke(operation, values, signal) {
|
|
1382
|
-
const config = await verify(signal);
|
|
1383
|
-
requireValue(values[0] !== null && sameIdentity(parseMlsDevDeviceContext(JSON.parse(values[0])), config.context));
|
|
1384
|
-
const output = await command(frame(operation, values), false, signal);
|
|
1385
|
-
alive(signal);
|
|
1386
|
-
await verify(signal);
|
|
1387
|
-
alive(signal);
|
|
1388
|
-
return custodyValue(output, operation);
|
|
1389
|
-
}
|
|
1390
|
-
__name(invoke, "invoke");
|
|
1391
|
-
async function job(work) {
|
|
1392
|
-
const controller = new AbortController();
|
|
1393
|
-
let timer;
|
|
1394
|
-
const cancel = /* @__PURE__ */ __name(() => controller.abort(), "cancel");
|
|
1395
|
-
jobs.add(controller);
|
|
1396
|
-
try {
|
|
1397
|
-
alive(controller.signal);
|
|
1398
|
-
captured.signal?.addEventListener("abort", cancel, { once: true });
|
|
1399
|
-
const cancellation = new Promise((_, reject) => {
|
|
1400
|
-
controller.signal.addEventListener("abort", () => reject(new NodeMlsDevCustodyError()), { once: true });
|
|
1401
|
-
timer = setTimeout(cancel, captured.timeoutMs);
|
|
1402
|
-
});
|
|
1403
|
-
return await Promise.race([work(controller.signal), cancellation]);
|
|
1404
|
-
} catch (error) {
|
|
1405
|
-
return custodyFailure(error);
|
|
1406
|
-
} finally {
|
|
1407
|
-
if (timer) clearTimeout(timer);
|
|
1408
|
-
controller.abort();
|
|
1409
|
-
captured.signal?.removeEventListener("abort", cancel);
|
|
1410
|
-
jobs.delete(controller);
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
__name(job, "job");
|
|
1414
|
-
const custody = typedNodeMlsDevCustody(job, invoke, async () => {
|
|
1415
|
-
closed = true;
|
|
1416
|
-
for (const job2 of jobs) job2.abort();
|
|
1417
|
-
await Promise.allSettled([...children]);
|
|
1418
|
-
});
|
|
1419
|
-
return Object.freeze({ ...custody, provision: /* @__PURE__ */ __name((context) => job(async (signal) => {
|
|
1420
|
-
const config = await verify(signal);
|
|
1421
|
-
requireValue(sameIdentity(parseMlsDevDeviceContext(context), config.context));
|
|
1422
|
-
const output = await command(null, true, signal);
|
|
1423
|
-
alive(signal);
|
|
1424
|
-
await verify(signal);
|
|
1425
|
-
alive(signal);
|
|
1426
|
-
const result = record(parseJson(output), ["version", "ok", "value"]);
|
|
1427
|
-
requireValue(result.version === 1 && result.ok === true && result.value === null);
|
|
1428
|
-
}), "provision") });
|
|
1429
|
-
}
|
|
1430
|
-
__name(createPortableNodeMlsDevCustody, "createPortableNodeMlsDevCustody");
|
|
1431
|
-
|
|
1432
|
-
// ../endpoint/src/node.ts
|
|
1433
|
-
var databases = /* @__PURE__ */ new Map();
|
|
1434
|
-
async function createNodeEndpointStore(options) {
|
|
1435
|
-
const directory = resolve3(options.directory);
|
|
1436
|
-
await mkdir(directory, { recursive: true, mode: 448 });
|
|
1437
|
-
const filename = join2(directory, `${createHash3("sha256").update(options.namespace).digest("hex")}.sqlite`);
|
|
1438
|
-
let opening = databases.get(filename);
|
|
1439
|
-
if (!opening) {
|
|
1440
|
-
opening = (async () => {
|
|
1441
|
-
const sqlite = new DatabaseSync(filename);
|
|
1442
|
-
try {
|
|
1443
|
-
await chmod(filename, 384);
|
|
1444
|
-
sqlite.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000;");
|
|
1445
|
-
return { sqlite, tail: Promise.resolve(), references: 0 };
|
|
1446
|
-
} catch (error) {
|
|
1447
|
-
sqlite.close();
|
|
1448
|
-
throw error;
|
|
1449
|
-
}
|
|
1450
|
-
})();
|
|
1451
|
-
databases.set(filename, opening);
|
|
1452
|
-
void opening.catch(() => {
|
|
1453
|
-
if (databases.get(filename) === opening) databases.delete(filename);
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
|
-
const shared = await opening;
|
|
1457
|
-
if (shared.closed) return createNodeEndpointStore(options);
|
|
1458
|
-
shared.references++;
|
|
1459
|
-
let closing;
|
|
1460
|
-
const database = {
|
|
1461
|
-
transaction(work) {
|
|
1462
|
-
if (closing) return Promise.reject(new Error("Endpoint store is closed"));
|
|
1463
|
-
const result = shared.tail.then(async () => {
|
|
1464
|
-
shared.sqlite.exec("BEGIN IMMEDIATE");
|
|
1465
|
-
try {
|
|
1466
|
-
const transaction = {
|
|
1467
|
-
async all(sql, parameters = []) {
|
|
1468
|
-
return shared.sqlite.prepare(sql).all(...parameters);
|
|
1469
|
-
},
|
|
1470
|
-
async run(sql, parameters = []) {
|
|
1471
|
-
shared.sqlite.prepare(sql).run(...parameters);
|
|
1472
|
-
}
|
|
1473
|
-
};
|
|
1474
|
-
const value = await work(transaction);
|
|
1475
|
-
shared.sqlite.exec("COMMIT");
|
|
1476
|
-
return value;
|
|
1477
|
-
} catch (error) {
|
|
1478
|
-
shared.sqlite.exec("ROLLBACK");
|
|
1479
|
-
throw error;
|
|
1480
|
-
}
|
|
1481
|
-
});
|
|
1482
|
-
shared.tail = result.catch(() => void 0);
|
|
1483
|
-
return result;
|
|
1484
|
-
},
|
|
1485
|
-
close() {
|
|
1486
|
-
if (closing) return closing;
|
|
1487
|
-
closing = shared.tail.then(() => {
|
|
1488
|
-
shared.references--;
|
|
1489
|
-
if (!shared.references) {
|
|
1490
|
-
shared.closed = true;
|
|
1491
|
-
if (databases.get(filename) === opening) databases.delete(filename);
|
|
1492
|
-
shared.sqlite.close();
|
|
1493
|
-
}
|
|
1494
|
-
});
|
|
1495
|
-
return closing;
|
|
1496
|
-
}
|
|
1497
|
-
};
|
|
1498
|
-
try {
|
|
1499
|
-
return await createSqliteEndpointStore({ database, namespace: options.namespace });
|
|
1500
|
-
} catch (error) {
|
|
1501
|
-
await database.close?.();
|
|
1502
|
-
throw error;
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
__name(createNodeEndpointStore, "createNodeEndpointStore");
|
|
1506
|
-
|
|
1507
119
|
// ../core/src/node-media.ts
|
|
1508
|
-
import { createHash
|
|
120
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
1509
121
|
import { createReadStream, createWriteStream } from "node:fs";
|
|
1510
|
-
import { mkdir
|
|
1511
|
-
import { join
|
|
122
|
+
import { mkdir, open, readdir, rename, stat, unlink } from "node:fs/promises";
|
|
123
|
+
import { join } from "node:path";
|
|
1512
124
|
import { Transform } from "node:stream";
|
|
1513
125
|
import { pipeline } from "node:stream/promises";
|
|
1514
126
|
var MAX_BYTES = 100 * 1024 * 1024;
|
|
1515
127
|
function createNodeEndpointMediaBytes(options) {
|
|
1516
128
|
const expectedDigests = /* @__PURE__ */ new Map();
|
|
1517
|
-
const initialized =
|
|
129
|
+
const initialized = mkdir(options.directory, { recursive: true, mode: 448 });
|
|
1518
130
|
const referencePath = /* @__PURE__ */ __name((reference) => {
|
|
1519
131
|
if (!/^[a-f0-9]{64}\.bytes$/.test(reference)) throw new Error("Invalid retained Canon media reference");
|
|
1520
|
-
return
|
|
132
|
+
return join(options.directory, reference);
|
|
1521
133
|
}, "referencePath");
|
|
1522
134
|
async function digest(source) {
|
|
1523
|
-
const handle = await
|
|
135
|
+
const handle = await open(source, "r");
|
|
1524
136
|
try {
|
|
1525
137
|
const info = await handle.stat();
|
|
1526
138
|
if (!info.isFile() || info.size < 1 || info.size > MAX_BYTES) throw new Error("Canon media must be a regular file from 1 byte to 100 MiB");
|
|
1527
|
-
const
|
|
139
|
+
const hash = createHash("sha256");
|
|
1528
140
|
let size = 0;
|
|
1529
141
|
for await (const chunk of handle.createReadStream({ autoClose: false })) {
|
|
1530
142
|
size += chunk.length;
|
|
1531
143
|
if (size > MAX_BYTES) throw new Error("Canon media grew beyond its upload limit");
|
|
1532
|
-
|
|
144
|
+
hash.update(chunk);
|
|
1533
145
|
}
|
|
1534
|
-
const result =
|
|
146
|
+
const result = hash.digest("hex");
|
|
1535
147
|
expectedDigests.set(source, result);
|
|
1536
148
|
return result;
|
|
1537
149
|
} finally {
|
|
@@ -1545,36 +157,38 @@ function createNodeEndpointMediaBytes(options) {
|
|
|
1545
157
|
await initialized;
|
|
1546
158
|
const expected = expectedDigests.get(source);
|
|
1547
159
|
if (!expected) throw new Error("Canon media must be fingerprinted before retaining bytes");
|
|
1548
|
-
const reference = `${
|
|
160
|
+
const reference = `${createHash("sha256").update(key).digest("hex")}.bytes`;
|
|
1549
161
|
const destination = referencePath(reference);
|
|
1550
162
|
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
1551
|
-
const sourceHandle = await
|
|
163
|
+
const sourceHandle = await open(source, "r");
|
|
1552
164
|
try {
|
|
1553
165
|
const info = await sourceHandle.stat();
|
|
1554
166
|
if (!info.isFile()) throw new Error("Canon media source is no longer a regular file");
|
|
1555
|
-
const
|
|
167
|
+
const hash = createHash("sha256");
|
|
1556
168
|
let size = 0;
|
|
1557
169
|
await pipeline(sourceHandle.createReadStream({ autoClose: false }), new Transform({
|
|
1558
170
|
transform(chunk, _encoding, callback) {
|
|
1559
171
|
size += chunk.length;
|
|
1560
172
|
if (size > MAX_BYTES) return callback(new Error("Canon media grew beyond its upload limit"));
|
|
1561
|
-
|
|
173
|
+
hash.update(chunk);
|
|
1562
174
|
callback(null, chunk);
|
|
1563
175
|
}
|
|
1564
176
|
}), createWriteStream(temporary, { flags: "wx", mode: 384 }));
|
|
1565
|
-
if (
|
|
1566
|
-
const retained = await
|
|
177
|
+
if (hash.digest("hex") !== expected) throw new Error("Canon media changed while preparing delivery");
|
|
178
|
+
const retained = await open(temporary, "r+");
|
|
1567
179
|
try {
|
|
1568
180
|
await retained.sync();
|
|
1569
181
|
} finally {
|
|
1570
182
|
await retained.close();
|
|
1571
183
|
}
|
|
1572
184
|
await rename(temporary, destination);
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
185
|
+
if (process.platform !== "win32") {
|
|
186
|
+
const directory = await open(options.directory, "r");
|
|
187
|
+
try {
|
|
188
|
+
await directory.sync();
|
|
189
|
+
} finally {
|
|
190
|
+
await directory.close();
|
|
191
|
+
}
|
|
1578
192
|
}
|
|
1579
193
|
return reference;
|
|
1580
194
|
} catch (error) {
|
|
@@ -1625,7 +239,7 @@ function createNodeEndpointMediaBytes(options) {
|
|
|
1625
239
|
async discardAll() {
|
|
1626
240
|
await initialized;
|
|
1627
241
|
for (const name of await readdir(options.directory)) {
|
|
1628
|
-
if (/^[a-f0-9]{64}\.bytes(?:\.[a-f0-9-]+\.tmp)?$/.test(name)) await unlink(
|
|
242
|
+
if (/^[a-f0-9]{64}\.bytes(?:\.[a-f0-9-]+\.tmp)?$/.test(name)) await unlink(join(options.directory, name));
|
|
1629
243
|
}
|
|
1630
244
|
}
|
|
1631
245
|
};
|
|
@@ -1777,15 +391,15 @@ function normalizeDefaults(environmentId, value) {
|
|
|
1777
391
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1778
392
|
throw new Error(`Invalid Canon runtime endpoint defaults for ${environmentId}`);
|
|
1779
393
|
}
|
|
1780
|
-
const
|
|
1781
|
-
const firebaseWebApiKey = typeof
|
|
394
|
+
const record6 = value;
|
|
395
|
+
const firebaseWebApiKey = typeof record6.firebaseWebApiKey === "string" ? record6.firebaseWebApiKey.trim() : "";
|
|
1782
396
|
if (!firebaseWebApiKey) {
|
|
1783
397
|
throw new Error(`Missing Canon runtime endpoint default: ${environmentId}.firebaseWebApiKey`);
|
|
1784
398
|
}
|
|
1785
399
|
return Object.freeze({
|
|
1786
|
-
apiBaseUrl: normalizedHttpsUrl(
|
|
1787
|
-
streamUrl: normalizedHttpsUrl(
|
|
1788
|
-
rtdbUrl: normalizedHttpsUrl(
|
|
400
|
+
apiBaseUrl: normalizedHttpsUrl(record6.apiBaseUrl, `${environmentId}.apiBaseUrl`),
|
|
401
|
+
streamUrl: normalizedHttpsUrl(record6.streamUrl, `${environmentId}.streamUrl`),
|
|
402
|
+
rtdbUrl: normalizedHttpsUrl(record6.rtdbUrl, `${environmentId}.rtdbUrl`),
|
|
1789
403
|
firebaseWebApiKey
|
|
1790
404
|
});
|
|
1791
405
|
}
|
|
@@ -1879,13 +493,7 @@ var DEFAULT_CANON_RUNTIME_ENDPOINTS = requireCanonRuntimeEndpointDefaults(
|
|
|
1879
493
|
var DEFAULT_BASE_URL = DEFAULT_CANON_RUNTIME_ENDPOINTS.apiBaseUrl;
|
|
1880
494
|
|
|
1881
495
|
// ../core/src/client.ts
|
|
1882
|
-
import { createHash as
|
|
1883
|
-
|
|
1884
|
-
// ../endpoint/src/store.ts
|
|
1885
|
-
function copyJson(value) {
|
|
1886
|
-
return JSON.parse(JSON.stringify(value));
|
|
1887
|
-
}
|
|
1888
|
-
__name(copyJson, "copyJson");
|
|
496
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
1889
497
|
|
|
1890
498
|
// ../endpoint/src/prepared-content.ts
|
|
1891
499
|
var protocolKey = /* @__PURE__ */ __name((scope5) => `protocol-state/${encodeURIComponent(scope5)}`, "protocolKey");
|
|
@@ -2052,23 +660,23 @@ async function isFirstOrderedOperation(tx, operation) {
|
|
|
2052
660
|
}
|
|
2053
661
|
}
|
|
2054
662
|
__name(isFirstOrderedOperation, "isFirstOrderedOperation");
|
|
2055
|
-
var inboundReadyKey = /* @__PURE__ */ __name((
|
|
2056
|
-
async function orderInbound(tx,
|
|
2057
|
-
if (
|
|
2058
|
-
const message =
|
|
2059
|
-
const sourceTime = message?.createdAt ??
|
|
663
|
+
var inboundReadyKey = /* @__PURE__ */ __name((record6) => record6.queueKey ?? `inbound-ready/${record6.event.id}`, "inboundReadyKey");
|
|
664
|
+
async function orderInbound(tx, record6) {
|
|
665
|
+
if (record6.queueKey) return record6;
|
|
666
|
+
const message = record6.event.data.message;
|
|
667
|
+
const sourceTime = message?.createdAt ?? record6.event.data.createdAt;
|
|
2060
668
|
const millis = typeof sourceTime === "string" ? Date.parse(sourceTime) : typeof sourceTime === "number" ? sourceTime : NaN;
|
|
2061
|
-
const timestamp = Number.isFinite(millis) ? new Date(millis).toISOString() :
|
|
2062
|
-
const tie = Number.isFinite(millis) ? `0-${lexicalIdentity(
|
|
2063
|
-
return { ...
|
|
669
|
+
const timestamp = Number.isFinite(millis) ? new Date(millis).toISOString() : record6.createdAt;
|
|
670
|
+
const tie = Number.isFinite(millis) ? `0-${lexicalIdentity(record6.event.id)}` : `1-${ordinal(await nextQueueSequence(tx, "inbound"))}`;
|
|
671
|
+
return { ...record6, queueKey: `inbound-ready/${scope(record6.event.conversationId)}/${timestamp}/${tie}` };
|
|
2064
672
|
}
|
|
2065
673
|
__name(orderInbound, "orderInbound");
|
|
2066
|
-
async function enqueueInbound(tx,
|
|
2067
|
-
await tx.put(inboundReadyKey(
|
|
674
|
+
async function enqueueInbound(tx, record6) {
|
|
675
|
+
await tx.put(inboundReadyKey(record6), { id: record6.event.id, kind: record6.event.kind, conversationId: record6.event.conversationId });
|
|
2068
676
|
}
|
|
2069
677
|
__name(enqueueInbound, "enqueueInbound");
|
|
2070
|
-
async function removeInboundReady(tx,
|
|
2071
|
-
await tx.delete(inboundReadyKey(
|
|
678
|
+
async function removeInboundReady(tx, record6) {
|
|
679
|
+
await tx.delete(inboundReadyKey(record6));
|
|
2072
680
|
}
|
|
2073
681
|
__name(removeInboundReady, "removeInboundReady");
|
|
2074
682
|
|
|
@@ -2203,11 +811,11 @@ function createMessageGroupCoordinator(input) {
|
|
|
2203
811
|
const group = await tx.get(groupKey(id2));
|
|
2204
812
|
if (!group) return;
|
|
2205
813
|
const children = await Promise.all(group.children.map((child) => tx.get(`operation/${child.operationId}`)));
|
|
2206
|
-
const
|
|
814
|
+
const rejected = children.find((child) => child?.status === "rejected" || child?.status === "succeeded" && !messageGroupChildSucceeded(child));
|
|
2207
815
|
const uncertain = children.find((child) => child?.status === "uncertain");
|
|
2208
816
|
const succeeded = children.every(messageGroupChildSucceeded);
|
|
2209
|
-
const status = succeeded ? "succeeded" :
|
|
2210
|
-
const error =
|
|
817
|
+
const status = succeeded ? "succeeded" : rejected || group.cancelled ? "rejected" : uncertain ? "uncertain" : "pending";
|
|
818
|
+
const error = rejected?.receipt?.error ?? (rejected ? { code: "PROACTIVE_NOT_ADMITTED", status: 403, message: "Proactive message was not admitted", retryable: false } : void 0) ?? (group.cancelled ? { code: "OPERATION_DISCARDED", message: "Remaining message parts were cancelled", retryable: false } : uncertain?.receipt?.error);
|
|
2211
819
|
return {
|
|
2212
820
|
operationId: id2,
|
|
2213
821
|
installationId: group.installationId,
|
|
@@ -2597,12 +1205,12 @@ async function assertMlsDevPlaintextAllowed(tx, intent2, lane = "operation.reque
|
|
|
2597
1205
|
const conversationId = endpointIntentConversationId(intent2);
|
|
2598
1206
|
if (!conversationId) return;
|
|
2599
1207
|
if (!await isMlsDevRoomProtected(tx, conversationId)) return;
|
|
2600
|
-
const
|
|
1208
|
+
const failure = /* @__PURE__ */ __name(() => new EndpointError("This MLS conversation requires encrypted content", {
|
|
2601
1209
|
code: "MLS_DEV_ENCRYPTED_CONTENT_REQUIRED",
|
|
2602
1210
|
retryable: false
|
|
2603
1211
|
}), "failure");
|
|
2604
1212
|
const action = intent2.action === "verb_execute" ? intent2.body.verb : intent2.action;
|
|
2605
|
-
if (typeof action === "string" && MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS.some((unsupported) => unsupported === action)) throw
|
|
1213
|
+
if (typeof action === "string" && MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS.some((unsupported) => unsupported === action)) throw failure();
|
|
2606
1214
|
const disclosed = lane === "operation.request" && Object.hasOwn(MLS_DEV_DISCLOSED_PROCESSING_FIELDS, intent2.action) ? MLS_DEV_DISCLOSED_PROCESSING_FIELDS[intent2.action] : void 0;
|
|
2607
1215
|
if (disclosed && !intent2.envelope && Object.keys(intent2.body).every((field) => disclosed.includes(field))) return;
|
|
2608
1216
|
const payload = lane === "operation.request" ? { body: { encoding: "json", value: intent2.body }, ...intent2.envelope ? { envelope: intent2.envelope } : {} } : intent2.body;
|
|
@@ -2610,7 +1218,7 @@ async function assertMlsDevPlaintextAllowed(tx, intent2, lane = "operation.reque
|
|
|
2610
1218
|
try {
|
|
2611
1219
|
boundary = endpointContentBoundary(lane, intent2.action, payload);
|
|
2612
1220
|
} catch {
|
|
2613
|
-
throw
|
|
1221
|
+
throw failure();
|
|
2614
1222
|
}
|
|
2615
1223
|
const privateLeaf = /* @__PURE__ */ __name((value, path) => {
|
|
2616
1224
|
if (value && typeof value === "object") {
|
|
@@ -2620,8 +1228,8 @@ async function assertMlsDevPlaintextAllowed(tx, intent2, lane = "operation.reque
|
|
|
2620
1228
|
return boundary.classify(path) !== "public-metadata";
|
|
2621
1229
|
}, "privateLeaf");
|
|
2622
1230
|
if (lane === "operation.request") {
|
|
2623
|
-
if (privateLeaf(intent2.body, ["body", "value"]) || intent2.envelope && privateLeaf(intent2.envelope, ["envelope"])) throw
|
|
2624
|
-
} else if (privateLeaf(intent2.body, [])) throw
|
|
1231
|
+
if (privateLeaf(intent2.body, ["body", "value"]) || intent2.envelope && privateLeaf(intent2.envelope, ["envelope"])) throw failure();
|
|
1232
|
+
} else if (privateLeaf(intent2.body, [])) throw failure();
|
|
2625
1233
|
}
|
|
2626
1234
|
__name(assertMlsDevPlaintextAllowed, "assertMlsDevPlaintextAllowed");
|
|
2627
1235
|
|
|
@@ -2630,15 +1238,15 @@ function isInboundProjectionKind(kind) {
|
|
|
2630
1238
|
return ["artifact.changed", "message.updated", "message.media_terminal"].includes(kind);
|
|
2631
1239
|
}
|
|
2632
1240
|
__name(isInboundProjectionKind, "isInboundProjectionKind");
|
|
2633
|
-
async function settleInboundProjection(tx,
|
|
2634
|
-
if (!isInboundProjectionKind(
|
|
2635
|
-
await removeInboundReady(tx,
|
|
2636
|
-
if (
|
|
2637
|
-
await tx.put(`inbound/${
|
|
2638
|
-
event: copyJson(
|
|
1241
|
+
async function settleInboundProjection(tx, record6, timestamp) {
|
|
1242
|
+
if (!isInboundProjectionKind(record6.event.kind)) return false;
|
|
1243
|
+
await removeInboundReady(tx, record6);
|
|
1244
|
+
if (record6.state !== "settled" && record6.state !== "uncertain" && !record6.externalClaim) {
|
|
1245
|
+
await tx.put(`inbound/${record6.event.id}`, {
|
|
1246
|
+
event: copyJson(record6.event),
|
|
2639
1247
|
state: "settled",
|
|
2640
1248
|
reason: "projection_hint",
|
|
2641
|
-
createdAt:
|
|
1249
|
+
createdAt: record6.createdAt,
|
|
2642
1250
|
updatedAt: timestamp
|
|
2643
1251
|
});
|
|
2644
1252
|
}
|
|
@@ -2707,9 +1315,9 @@ async function recordInboundEvent(tx, incoming, timestamp) {
|
|
|
2707
1315
|
return true;
|
|
2708
1316
|
}
|
|
2709
1317
|
if (existing) return false;
|
|
2710
|
-
const
|
|
2711
|
-
await tx.put(key,
|
|
2712
|
-
await enqueueInbound(tx,
|
|
1318
|
+
const record6 = await orderInbound(tx, { event: copyJson(event), state: "observed", createdAt: timestamp, updatedAt: timestamp });
|
|
1319
|
+
await tx.put(key, record6);
|
|
1320
|
+
await enqueueInbound(tx, record6);
|
|
2713
1321
|
return true;
|
|
2714
1322
|
}
|
|
2715
1323
|
__name(recordInboundEvent, "recordInboundEvent");
|
|
@@ -2740,15 +1348,15 @@ async function recordVerifiedMlsInboundEvent(tx, event, feedOrdinal, timestamp,
|
|
|
2740
1348
|
if (!event.conversationId || !Number.isSafeInteger(feedOrdinal) || feedOrdinal < 1) throw new Error("Invalid verified MLS input");
|
|
2741
1349
|
const key = `inbound/${event.id}`, existing = await tx.get(key);
|
|
2742
1350
|
if (existing && existing.reason !== "mls_plaintext_withheld") return false;
|
|
2743
|
-
const
|
|
1351
|
+
const record6 = {
|
|
2744
1352
|
event: copyJson(event),
|
|
2745
1353
|
state: own ? "settled" : "observed",
|
|
2746
1354
|
...own ? { reason: "own_mls_message" } : { queueKey: `inbound-ready/${encodeURIComponent(event.conversationId)}/mls/${String(feedOrdinal).padStart(16, "0")}/${encodeURIComponent(event.id)}` },
|
|
2747
1355
|
createdAt: timestamp,
|
|
2748
1356
|
updatedAt: timestamp
|
|
2749
1357
|
};
|
|
2750
|
-
await tx.put(key,
|
|
2751
|
-
if (!own) await enqueueInbound(tx,
|
|
1358
|
+
await tx.put(key, record6);
|
|
1359
|
+
if (!own) await enqueueInbound(tx, record6);
|
|
2752
1360
|
return true;
|
|
2753
1361
|
}
|
|
2754
1362
|
__name(recordVerifiedMlsInboundEvent, "recordVerifiedMlsInboundEvent");
|
|
@@ -2763,10 +1371,10 @@ function createInboundConsumer(input) {
|
|
|
2763
1371
|
let timer;
|
|
2764
1372
|
const scheduled = /* @__PURE__ */ new Set();
|
|
2765
1373
|
const rooms = /* @__PURE__ */ new Map();
|
|
2766
|
-
function
|
|
2767
|
-
return
|
|
1374
|
+
function matches(record6) {
|
|
1375
|
+
return record6.event.kind === options.kind && (!options.conversationId || record6.event.conversationId === options.conversationId);
|
|
2768
1376
|
}
|
|
2769
|
-
__name(
|
|
1377
|
+
__name(matches, "matches");
|
|
2770
1378
|
function report(error) {
|
|
2771
1379
|
try {
|
|
2772
1380
|
options.onError?.(error);
|
|
@@ -2788,15 +1396,15 @@ function createInboundConsumer(input) {
|
|
|
2788
1396
|
if (await input.refreshHandoff?.(id2)) return true;
|
|
2789
1397
|
const selected = await store.transaction(async (tx) => {
|
|
2790
1398
|
const current = await tx.get(`inbound/${id2}`);
|
|
2791
|
-
if (!current || current.deleted || !
|
|
1399
|
+
if (!current || current.deleted || !matches(current) || current.state === "uncertain" || current.state === "settled") return { blocked: false };
|
|
2792
1400
|
if (current.lease && current.lease.expiresAt > clock()) return { blocked: current.lease.owner !== owner };
|
|
2793
1401
|
const next = { ...current, state: "offered", lease: { owner, expiresAt: clock() + leaseMs }, updatedAt: new Date(clock()).toISOString() };
|
|
2794
1402
|
delete next.handoffGeneration;
|
|
2795
1403
|
await tx.put(`inbound/${id2}`, next);
|
|
2796
1404
|
return { record: next, blocked: false };
|
|
2797
1405
|
});
|
|
2798
|
-
const
|
|
2799
|
-
if (!
|
|
1406
|
+
const record6 = selected.record;
|
|
1407
|
+
if (!record6) return !selected.blocked;
|
|
2800
1408
|
const heartbeat = setInterval(() => {
|
|
2801
1409
|
void store.transaction(async (tx) => {
|
|
2802
1410
|
const current = await tx.get(`inbound/${id2}`);
|
|
@@ -2807,8 +1415,8 @@ function createInboundConsumer(input) {
|
|
|
2807
1415
|
}, Math.floor(leaseMs / 3));
|
|
2808
1416
|
heartbeat.unref?.();
|
|
2809
1417
|
try {
|
|
2810
|
-
await input.beforeOffer?.(
|
|
2811
|
-
await options.offer(
|
|
1418
|
+
await input.beforeOffer?.(record6.event);
|
|
1419
|
+
await options.offer(record6.event);
|
|
2812
1420
|
const handedOff = await endpoint.getInbound(id2);
|
|
2813
1421
|
return !handedOff || handedOff.state !== "deferred" && handedOff.state !== "observed";
|
|
2814
1422
|
} catch (error) {
|
|
@@ -2874,8 +1482,8 @@ function createInboundConsumer(input) {
|
|
|
2874
1482
|
const rows = await tx.scan("inbound-ready/", { after, limit: 100 });
|
|
2875
1483
|
for (const row of rows) {
|
|
2876
1484
|
if (!isInboundProjectionKind(row.value.kind)) continue;
|
|
2877
|
-
const
|
|
2878
|
-
if (
|
|
1485
|
+
const record6 = await tx.get(`inbound/${row.value.id}`);
|
|
1486
|
+
if (record6 && await settleInboundProjection(tx, record6, new Date(clock()).toISOString())) await tx.delete(row.key);
|
|
2879
1487
|
}
|
|
2880
1488
|
return rows;
|
|
2881
1489
|
});
|
|
@@ -3362,16 +1970,16 @@ function createEndpointMigration(input) {
|
|
|
3362
1970
|
if (canonicalEndpointJson(await tx.get(activeKey)) !== canonicalEndpointJson(progress)) return;
|
|
3363
1971
|
const cursor = await tx.get(`cursor/${room.conversationId}`);
|
|
3364
1972
|
if (cursor && (cursor.migrationInventorySha256 !== manifest.inventorySha256 || cursor.cursor !== room.cursor)) invalid("Recovery cursor changed during migration", "MIGRATION_BASELINE_EXISTS");
|
|
3365
|
-
for (const
|
|
3366
|
-
const existing = await tx.get(`inbound/${
|
|
1973
|
+
for (const record6 of records) {
|
|
1974
|
+
const existing = await tx.get(`inbound/${record6.event.id}`);
|
|
3367
1975
|
if (existing && existing.migrationInventorySha256 !== manifest.inventorySha256 && !["observed", "deferred"].includes(existing.state)) invalid("Existing native work requires reconciliation before importing", "MIGRATION_INBOX_CONFLICT");
|
|
3368
1976
|
}
|
|
3369
|
-
for (const
|
|
3370
|
-
const previous = await tx.get(`inbound/${
|
|
1977
|
+
for (const record6 of records) {
|
|
1978
|
+
const previous = await tx.get(`inbound/${record6.event.id}`);
|
|
3371
1979
|
if (previous) await removeInboundReady(tx, previous);
|
|
3372
|
-
const ordered = await orderInbound(tx,
|
|
3373
|
-
await tx.put(`inbound/${
|
|
3374
|
-
if (
|
|
1980
|
+
const ordered = await orderInbound(tx, record6);
|
|
1981
|
+
await tx.put(`inbound/${record6.event.id}`, ordered);
|
|
1982
|
+
if (record6.state === "observed" || record6.state === "deferred") await enqueueInbound(tx, ordered);
|
|
3375
1983
|
}
|
|
3376
1984
|
const done = progress.item + batch.length >= room.items.length;
|
|
3377
1985
|
await tx.put(activeKey, { ...progress, room: progress.room + (done ? 1 : 0), item: done ? 0 : progress.item + batch.length });
|
|
@@ -3466,7 +2074,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
3466
2074
|
const running = /* @__PURE__ */ new Map();
|
|
3467
2075
|
const staging = /* @__PURE__ */ new Map();
|
|
3468
2076
|
const cancelledStages = /* @__PURE__ */ new Set();
|
|
3469
|
-
const
|
|
2077
|
+
const hash = options.hash ?? (async (value) => {
|
|
3470
2078
|
const result = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
3471
2079
|
return [...new Uint8Array(result)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3472
2080
|
});
|
|
@@ -3528,7 +2136,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
3528
2136
|
let activeUploads = 0;
|
|
3529
2137
|
const uploadWaiters = [];
|
|
3530
2138
|
async function upload(work) {
|
|
3531
|
-
if (activeUploads >= 2) await new Promise((
|
|
2139
|
+
if (activeUploads >= 2) await new Promise((resolve3) => uploadWaiters.push(resolve3));
|
|
3532
2140
|
else activeUploads += 1;
|
|
3533
2141
|
try {
|
|
3534
2142
|
if (closed) throw new Error("Endpoint media pipeline is closed");
|
|
@@ -3591,11 +2199,11 @@ function createEndpointMediaPipeline(options) {
|
|
|
3591
2199
|
const check = probeBackgroundCapability();
|
|
3592
2200
|
const waitMs = Math.max(0, options2.maxProbeWaitMs);
|
|
3593
2201
|
if (waitMs === 0) return false;
|
|
3594
|
-
return new Promise((
|
|
3595
|
-
const timer2 = setTimeout(() =>
|
|
2202
|
+
return new Promise((resolve3) => {
|
|
2203
|
+
const timer2 = setTimeout(() => resolve3(false), waitMs);
|
|
3596
2204
|
void check.then((enabled) => {
|
|
3597
2205
|
clearTimeout(timer2);
|
|
3598
|
-
|
|
2206
|
+
resolve3(enabled);
|
|
3599
2207
|
});
|
|
3600
2208
|
});
|
|
3601
2209
|
}
|
|
@@ -3744,7 +2352,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
3744
2352
|
}
|
|
3745
2353
|
const staged = (async () => {
|
|
3746
2354
|
if (closed || sendOptions?.signal?.aborted) throw new DOMException("Attachment cancelled", "AbortError");
|
|
3747
|
-
const fingerprint = await
|
|
2355
|
+
const fingerprint = await hash(canonicalEndpointJson({ message: input.message, files: input.files.map((file) => ({ descriptor: file.descriptor, source: background.sourceIdentity(file.source) })) }));
|
|
3748
2356
|
if (closed || sendOptions?.signal?.aborted) throw new DOMException("Attachment cancelled", "AbortError");
|
|
3749
2357
|
const owner = await acquire(id2);
|
|
3750
2358
|
try {
|
|
@@ -3782,10 +2390,10 @@ function createEndpointMediaPipeline(options) {
|
|
|
3782
2390
|
}) }), "prepare"),
|
|
3783
2391
|
commit: /* @__PURE__ */ __name(async (tx, preparedContent) => {
|
|
3784
2392
|
if (closed || sendOptions?.signal?.aborted) throw new DOMException("Attachment cancelled", "AbortError");
|
|
3785
|
-
const
|
|
3786
|
-
await writeInTransaction(tx,
|
|
2393
|
+
const sealed = { ...flow, preparedContent };
|
|
2394
|
+
await writeInTransaction(tx, sealed, owner);
|
|
3787
2395
|
await tx.delete(`preparation/${operationId}`);
|
|
3788
|
-
return
|
|
2396
|
+
return sealed;
|
|
3789
2397
|
}, "commit")
|
|
3790
2398
|
});
|
|
3791
2399
|
notifyFlow(committed);
|
|
@@ -3839,7 +2447,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
3839
2447
|
checkOpen();
|
|
3840
2448
|
digests.push(await bytes.digest(file.source));
|
|
3841
2449
|
}
|
|
3842
|
-
const fingerprint = await
|
|
2450
|
+
const fingerprint = await hash(canonicalEndpointJson({ ...prepareOnly ? { purpose: "prepare" } : {}, message: input.message, files: input.files.map((file, index) => ({ ...file.descriptor, digest: digests[index] })) }));
|
|
3843
2451
|
checkOpen();
|
|
3844
2452
|
const owner = await acquire(id2);
|
|
3845
2453
|
const controller = new AbortController();
|
|
@@ -3937,8 +2545,8 @@ function createEndpointMediaPipeline(options) {
|
|
|
3937
2545
|
if (!operation || ["rejected", "uncertain", "succeeded"].includes(operation.status)) throw error;
|
|
3938
2546
|
const receipt = await endpoint.waitForOperation(error.operationId, { signal: controller.signal });
|
|
3939
2547
|
if (receipt.status !== "succeeded" || receipt.resultError) {
|
|
3940
|
-
const
|
|
3941
|
-
throw new EndpointError(
|
|
2548
|
+
const failure = receipt.resultError ?? receipt.error;
|
|
2549
|
+
throw new EndpointError(failure?.message ?? "Attachment delivery requires attention", { code: failure?.code ?? "OPERATION_UNCERTAIN", status: failure?.status, retryable: failure?.retryable ?? false, operationId: error.operationId });
|
|
3942
2550
|
}
|
|
3943
2551
|
return receipt.result;
|
|
3944
2552
|
}
|
|
@@ -4090,7 +2698,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
4090
2698
|
await write(flow, owner);
|
|
4091
2699
|
}
|
|
4092
2700
|
for (let wait = 1e3; file.attachment?.processingStatus === "processing"; wait = Math.min(wait * 2, 15e3)) {
|
|
4093
|
-
await new Promise((
|
|
2701
|
+
await new Promise((resolve3) => setTimeout(resolve3, wait));
|
|
4094
2702
|
if (closed || controller.signal.aborted) throw new DOMException("Attachment cancelled", "AbortError");
|
|
4095
2703
|
const current = await endpoint.query("get_upload", { uploadId: file.uploadId });
|
|
4096
2704
|
if (current.attachment) {
|
|
@@ -4272,8 +2880,8 @@ function createInboundHandoffs(input) {
|
|
|
4272
2880
|
if (currentEntry(entry)) entries2.delete(entry.handle.id);
|
|
4273
2881
|
}
|
|
4274
2882
|
__name(forget, "forget");
|
|
4275
|
-
function owns(entry,
|
|
4276
|
-
return !!
|
|
2883
|
+
function owns(entry, record6) {
|
|
2884
|
+
return !!record6 && record6.handoffGeneration === entry.generation && (record6.state === "uncertain" || !record6.lease || record6.lease.owner === entry.leaseOwner);
|
|
4277
2885
|
}
|
|
4278
2886
|
__name(owns, "owns");
|
|
4279
2887
|
async function applyDisposition(entry, kind, reason) {
|
|
@@ -4361,7 +2969,7 @@ function createInboundHandoffs(input) {
|
|
|
4361
2969
|
entries2.set(id2, entry);
|
|
4362
2970
|
capabilities.set(handle, entry);
|
|
4363
2971
|
try {
|
|
4364
|
-
const
|
|
2972
|
+
const record6 = await store.transaction(async (tx) => {
|
|
4365
2973
|
const current = await tx.get(`inbound/${id2}`);
|
|
4366
2974
|
if (current && await settleInboundProjection(tx, current, now())) return void 0;
|
|
4367
2975
|
if (!current || current.deleted || current.state === "uncertain" || current.state === "settled") return void 0;
|
|
@@ -4377,11 +2985,11 @@ function createInboundHandoffs(input) {
|
|
|
4377
2985
|
await tx.put(`inbound/${id2}`, next);
|
|
4378
2986
|
return next;
|
|
4379
2987
|
});
|
|
4380
|
-
if (!
|
|
2988
|
+
if (!record6) {
|
|
4381
2989
|
forget(entry, "released");
|
|
4382
2990
|
return null;
|
|
4383
2991
|
}
|
|
4384
|
-
conversationId =
|
|
2992
|
+
conversationId = record6.event.conversationId;
|
|
4385
2993
|
if (closing) {
|
|
4386
2994
|
await handle.release("consumer-closed");
|
|
4387
2995
|
return null;
|
|
@@ -4403,8 +3011,8 @@ function createInboundHandoffs(input) {
|
|
|
4403
3011
|
if (!selected.length || closing || selected.some((entry) => !currentEntry(entry) || entry.phase !== "queued")) return { status: "unavailable" };
|
|
4404
3012
|
if (selected.some((entry) => entry.transition || entry.request)) return { status: "deferred" };
|
|
4405
3013
|
let finishClaim;
|
|
4406
|
-
const boundary = new Promise((
|
|
4407
|
-
finishClaim =
|
|
3014
|
+
const boundary = new Promise((resolve3) => {
|
|
3015
|
+
finishClaim = resolve3;
|
|
4408
3016
|
});
|
|
4409
3017
|
for (const entry of selected) {
|
|
4410
3018
|
entry.phase = "claiming";
|
|
@@ -4413,11 +3021,11 @@ function createInboundHandoffs(input) {
|
|
|
4413
3021
|
let claimed = false;
|
|
4414
3022
|
try {
|
|
4415
3023
|
const records = await store.transaction(async (tx) => Promise.all(selected.map((entry) => tx.get(`inbound/${entry.handle.id}`))));
|
|
4416
|
-
await input.assertReady(records.filter((
|
|
3024
|
+
await input.assertReady(records.filter((record6) => !!record6));
|
|
4417
3025
|
const claim = await store.transaction(async (tx) => {
|
|
4418
3026
|
if (await tx.get("migration/active")) throw new EndpointError("Migration is incomplete; native execution is gated", { code: "MIGRATION_IN_PROGRESS", retryable: false });
|
|
4419
3027
|
const current = await Promise.all(selected.map((entry) => tx.get(`inbound/${entry.handle.id}`)));
|
|
4420
|
-
const eligible = current.map((
|
|
3028
|
+
const eligible = current.map((record6, index) => owns(selected[index], record6) && !record6.deleted && record6.state !== "uncertain" && record6.state !== "settled");
|
|
4421
3029
|
if (eligible.some((value) => !value)) return { claimed: false, eligible };
|
|
4422
3030
|
for (let index = 0; index < current.length; index++) {
|
|
4423
3031
|
const next = { ...current[index], state: "uncertain", updatedAt: now() };
|
|
@@ -4442,9 +3050,9 @@ function createInboundHandoffs(input) {
|
|
|
4442
3050
|
await store.transaction(async (tx) => {
|
|
4443
3051
|
for (const entry of selected) {
|
|
4444
3052
|
if (entry.request) continue;
|
|
4445
|
-
const
|
|
4446
|
-
if (!owns(entry,
|
|
4447
|
-
const next = { ...
|
|
3053
|
+
const record6 = await tx.get(`inbound/${entry.handle.id}`);
|
|
3054
|
+
if (!owns(entry, record6) || record6.state !== "uncertain") continue;
|
|
3055
|
+
const next = { ...record6, state: "offered", lease: { owner: entry.leaseOwner, expiresAt: clock() + leaseMs }, updatedAt: now() };
|
|
4448
3056
|
await tx.put(`inbound/${entry.handle.id}`, next);
|
|
4449
3057
|
await enqueueInbound(tx, next);
|
|
4450
3058
|
}
|
|
@@ -4545,9 +3153,9 @@ function createInboundHandoffs(input) {
|
|
|
4545
3153
|
if (!entry) return false;
|
|
4546
3154
|
if (entry.phase !== "queued" || entry.transition) return true;
|
|
4547
3155
|
const held = await store.transaction(async (tx) => {
|
|
4548
|
-
const
|
|
4549
|
-
if (!owns(entry,
|
|
4550
|
-
await tx.put(`inbound/${id2}`, { ...
|
|
3156
|
+
const record6 = await tx.get(`inbound/${id2}`);
|
|
3157
|
+
if (!owns(entry, record6) || record6.deleted || record6.state === "settled" || record6.state === "uncertain") return false;
|
|
3158
|
+
await tx.put(`inbound/${id2}`, { ...record6, lease: { owner: entry.leaseOwner, expiresAt: clock() + leaseMs } });
|
|
4551
3159
|
return true;
|
|
4552
3160
|
});
|
|
4553
3161
|
if (!held) forget(entry, "released");
|
|
@@ -4666,9 +3274,9 @@ function createExternalInboundClaims(input) {
|
|
|
4666
3274
|
if (!current || current.state !== "uncertain" || !claim?.replyIntent || claim.requestId !== requestId || claim.installationGeneration !== generation2 || claim.replyOperationId && claim.replyOperationId !== operationId) {
|
|
4667
3275
|
throw new EndpointError("Reply no longer belongs to this claim", { code: "INBOUND_CLAIM_CONFLICT", retryable: false });
|
|
4668
3276
|
}
|
|
4669
|
-
const
|
|
4670
|
-
if (current.deleted && !
|
|
4671
|
-
return { current, claim, generation: generation2, sealed
|
|
3277
|
+
const sealed = !!await tx.get(`preparation/${operationId}`) || !!await tx.get(`operation/${operationId}`);
|
|
3278
|
+
if (current.deleted && !sealed) throw new EndpointError("Reply source was deleted before preparation", { code: "INBOUND_CLAIM_CONFLICT", retryable: false });
|
|
3279
|
+
return { current, claim, generation: generation2, sealed };
|
|
4672
3280
|
}, "read");
|
|
4673
3281
|
const before = await input.store.transaction(read);
|
|
4674
3282
|
if (before.sealed) return { event: before.current.event, replayed: true };
|
|
@@ -4676,8 +3284,8 @@ function createExternalInboundClaims(input) {
|
|
|
4676
3284
|
const authored = copyJson(prepared.intent);
|
|
4677
3285
|
const fingerprint = await input.hash(canonicalEndpointJson({ ...authored, ...input.ownershipVersion !== void 0 ? { ownershipVersion: input.ownershipVersion } : {} }));
|
|
4678
3286
|
return input.store.transaction(async (tx) => {
|
|
4679
|
-
const { current, claim, generation: generation2, sealed
|
|
4680
|
-
if (
|
|
3287
|
+
const { current, claim, generation: generation2, sealed } = await read(tx);
|
|
3288
|
+
if (sealed) return { event: current.event, replayed: true };
|
|
4681
3289
|
const event = prepared.event;
|
|
4682
3290
|
const admissionId = event.data.replyAuthority?.admissionId ?? null;
|
|
4683
3291
|
if (event.id !== id2 || event.kind !== "message.created" || !event.durable || event.conversationId !== claim.source.conversationId || event.data.message?.id !== claim.source.messageId || admissionId !== claim.source.admissionId || authored.action !== "send_message" || authored.envelope || authored.preconditions || authored.body.conversationId !== claim.source.conversationId || authored.body.replyTo !== claim.source.messageId || authored.body.messageId !== claim.replyIntent.messageId || authored.body.metadata?.turnId !== claim.replyIntent.turnId || canonicalEndpointJson(authored.body.replyAuthority) !== canonicalEndpointJson(event.data.replyAuthority)) {
|
|
@@ -4752,7 +3360,7 @@ function same(left, right) {
|
|
|
4752
3360
|
return canonicalEndpointJson(left) === canonicalEndpointJson(right);
|
|
4753
3361
|
}
|
|
4754
3362
|
__name(same, "same");
|
|
4755
|
-
function
|
|
3363
|
+
function record(value, required, optional = []) {
|
|
4756
3364
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail();
|
|
4757
3365
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
4758
3366
|
const keys = Reflect.ownKeys(descriptors);
|
|
@@ -4765,7 +3373,7 @@ function record2(value, required, optional = []) {
|
|
|
4765
3373
|
}
|
|
4766
3374
|
return result;
|
|
4767
3375
|
}
|
|
4768
|
-
__name(
|
|
3376
|
+
__name(record, "record");
|
|
4769
3377
|
function uuid(value) {
|
|
4770
3378
|
if (typeof value !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value)) fail();
|
|
4771
3379
|
return value;
|
|
@@ -4813,7 +3421,7 @@ function createMlsDevDeviceCoordinator(input) {
|
|
|
4813
3421
|
return void 0;
|
|
4814
3422
|
}
|
|
4815
3423
|
if (snapshot.protocolState === null || raw === void 0 || snapshot.protocolRevision === 0) fail();
|
|
4816
|
-
const stored =
|
|
3424
|
+
const stored = record(snapshot.protocolState, ["version", "binding", "wrappedState", "signaturePublicKey"]);
|
|
4817
3425
|
if (stored.version !== 1 || !same(parseMlsDevDeviceBinding(stored.binding), binding)) fail();
|
|
4818
3426
|
const prepared = parseMlsDevDevicePreparation({
|
|
4819
3427
|
wrappedState: stored.wrappedState,
|
|
@@ -4825,7 +3433,7 @@ function createMlsDevDeviceCoordinator(input) {
|
|
|
4825
3433
|
wrappedState: prepared.wrappedState,
|
|
4826
3434
|
signaturePublicKey: prepared.payload.signaturePublicKey
|
|
4827
3435
|
};
|
|
4828
|
-
const data =
|
|
3436
|
+
const data = record(raw, ["version", "binding", "signaturePublicKey"], ["registration", "credential", "generationObservation", "revocation"]);
|
|
4829
3437
|
if (data.version !== 1 || !same(parseMlsDevDeviceBinding(data.binding), binding) || data.signaturePublicKey !== envelope2.signaturePublicKey) fail();
|
|
4830
3438
|
const metadata = { version: 1, binding, signaturePublicKey: envelope2.signaturePublicKey };
|
|
4831
3439
|
if (Object.hasOwn(data, "registration")) {
|
|
@@ -4893,7 +3501,7 @@ function createMlsDevDeviceCoordinator(input) {
|
|
|
4893
3501
|
async function readProof(tx, op, id2) {
|
|
4894
3502
|
const raw = await tx.get(proofKey(op, id2));
|
|
4895
3503
|
if (raw === void 0) return void 0;
|
|
4896
|
-
const data =
|
|
3504
|
+
const data = record(raw, ["command", "request"]);
|
|
4897
3505
|
const command = parseMlsDevDeviceCommand(data.command);
|
|
4898
3506
|
if (command.op !== op || command.challenge.challengeId !== id2) fail();
|
|
4899
3507
|
const request = parseComplete(op, data.request);
|
|
@@ -5198,12 +3806,26 @@ function parseFlow(value) {
|
|
|
5198
3806
|
return fail2();
|
|
5199
3807
|
}
|
|
5200
3808
|
__name(parseFlow, "parseFlow");
|
|
3809
|
+
var MAX_CHALLENGE_CLOCK_LAG_MS = 3e4;
|
|
5201
3810
|
function createMlsDevDeviceClient(options, context) {
|
|
5202
3811
|
const principal = JSON.parse(JSON.stringify(options.principal));
|
|
5203
3812
|
const { prepareProtected, verifyCredential } = options;
|
|
5204
3813
|
const transport = context.transport;
|
|
5205
3814
|
let running;
|
|
5206
3815
|
let renewalRetryAtMs = 0;
|
|
3816
|
+
async function untilIssued(challenge) {
|
|
3817
|
+
const lag = challenge.issuedAtMs - context.clock();
|
|
3818
|
+
if (lag <= 0 || lag > MAX_CHALLENGE_CLOCK_LAG_MS) return;
|
|
3819
|
+
await new Promise((resolve3) => {
|
|
3820
|
+
const timer = setTimeout(resolve3, lag + 1);
|
|
3821
|
+
context.signal.addEventListener("abort", () => {
|
|
3822
|
+
clearTimeout(timer);
|
|
3823
|
+
resolve3();
|
|
3824
|
+
}, { once: true });
|
|
3825
|
+
});
|
|
3826
|
+
context.assertOpen();
|
|
3827
|
+
}
|
|
3828
|
+
__name(untilIssued, "untilIssued");
|
|
5207
3829
|
function guardedStore(expected) {
|
|
5208
3830
|
return { close: /* @__PURE__ */ __name(async () => {
|
|
5209
3831
|
}, "close"), transaction: /* @__PURE__ */ __name((work) => context.store.transaction(async (tx) => {
|
|
@@ -5219,7 +3841,7 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5219
3841
|
const store = guardedStore();
|
|
5220
3842
|
const fence = /* @__PURE__ */ __name(() => store.transaction(async () => {
|
|
5221
3843
|
}), "fence");
|
|
5222
|
-
async function initialized() {
|
|
3844
|
+
async function initialized(storedPrincipalId) {
|
|
5223
3845
|
context.assertOpen();
|
|
5224
3846
|
const { identity, installationGeneration } = await context.ready();
|
|
5225
3847
|
await fence();
|
|
@@ -5230,7 +3852,7 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5230
3852
|
const binding = parseMlsDevDeviceBinding({
|
|
5231
3853
|
version: 1,
|
|
5232
3854
|
environmentId: identity.environmentId,
|
|
5233
|
-
principal: { ...principal, id: await context.mlsPrincipalId() },
|
|
3855
|
+
principal: { ...principal, id: storedPrincipalId ?? await context.mlsPrincipalId() },
|
|
5234
3856
|
installationId: identity.installationId
|
|
5235
3857
|
});
|
|
5236
3858
|
const coordinator = /* @__PURE__ */ __name((journal) => createMlsDevDeviceCoordinator({
|
|
@@ -5373,6 +3995,7 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5373
3995
|
await replace({ version: 1, id: context.ids(), goal: "enroll", phase: "enrollment", begin: captured.begin });
|
|
5374
3996
|
continue;
|
|
5375
3997
|
}
|
|
3998
|
+
if (!previous2) await untilIssued(captured.challenge);
|
|
5376
3999
|
const request2 = previous2 ?? await device.prepareEnrollmentProof(captured.challenge);
|
|
5377
4000
|
const receipt2 = await external(() => http.completeEnrollment(request2, context.signal));
|
|
5378
4001
|
const view3 = await device.acceptRegistration(captured.challenge.challengeId, receipt2);
|
|
@@ -5398,6 +4021,7 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5398
4021
|
await replace({ version: 1, id: context.ids(), goal: "renew", phase: "credential", begin: captured.begin });
|
|
5399
4022
|
continue;
|
|
5400
4023
|
}
|
|
4024
|
+
if (!previous2) await untilIssued(captured.challenge);
|
|
5401
4025
|
const request2 = previous2 ?? await device.prepareCredentialProof(captured.challenge, captured.begin.expectedCredentialGeneration);
|
|
5402
4026
|
const receipt2 = await external(() => http.completeCredential(request2, context.signal));
|
|
5403
4027
|
const accepted2 = await device.acceptCredential(captured.challenge.challengeId, receipt2);
|
|
@@ -5420,6 +4044,7 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5420
4044
|
await replace(await reconciliation(await device.read()));
|
|
5421
4045
|
continue;
|
|
5422
4046
|
}
|
|
4047
|
+
if (!previous) await untilIssued(captured.challenge);
|
|
5423
4048
|
const request = previous ?? await device.prepareCredentialReconciliationProof(captured.challenge, captured.begin.expectedCredentialGeneration);
|
|
5424
4049
|
const receipt = await external(() => http.completeReconciliation(request, context.signal));
|
|
5425
4050
|
const accepted = await device.acceptGenerationObservation(captured.challenge.challengeId, receipt);
|
|
@@ -5517,6 +4142,20 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5517
4142
|
}
|
|
5518
4143
|
}
|
|
5519
4144
|
__name(ensureReady, "ensureReady");
|
|
4145
|
+
async function revoke() {
|
|
4146
|
+
const { coordinator, transport: http } = await initialized();
|
|
4147
|
+
const device = coordinator(store), view3 = await device.read();
|
|
4148
|
+
if (view3.status === "revoked" || !view3.registration) return view3;
|
|
4149
|
+
const receipt = await http.revokeCredential({ version: 1, deviceId: view3.registration.device.deviceId }, context.signal);
|
|
4150
|
+
return device.acceptRevocation(receipt);
|
|
4151
|
+
}
|
|
4152
|
+
__name(revoke, "revoke");
|
|
4153
|
+
async function revokeOther(deviceId) {
|
|
4154
|
+
const { coordinator, transport: http } = await initialized();
|
|
4155
|
+
if ((await coordinator(store).read()).registration?.device.deviceId === deviceId) throw new EndpointError("This device leaves by signing out", { code: "MLS_DEV_DEVICE_IS_CURRENT", retryable: false });
|
|
4156
|
+
await http.revokeCredential({ version: 1, deviceId }, context.signal);
|
|
4157
|
+
}
|
|
4158
|
+
__name(revokeOther, "revokeOther");
|
|
5520
4159
|
function run(goal) {
|
|
5521
4160
|
context.assertOpen();
|
|
5522
4161
|
const preceding = running;
|
|
@@ -5543,12 +4182,27 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5543
4182
|
return promise;
|
|
5544
4183
|
}
|
|
5545
4184
|
__name(run, "run");
|
|
4185
|
+
function read(options2) {
|
|
4186
|
+
return context.run(async () => {
|
|
4187
|
+
let storedPrincipalId;
|
|
4188
|
+
if (options2?.localOnly) {
|
|
4189
|
+
await context.ready();
|
|
4190
|
+
const saved = await store.transaction((tx) => tx.get("mls-dev-device/identity"));
|
|
4191
|
+
if (!saved) return void 0;
|
|
4192
|
+
storedPrincipalId = parseMlsDevDeviceBinding(saved.binding).principal.id;
|
|
4193
|
+
}
|
|
4194
|
+
return (await initialized(storedPrincipalId)).coordinator(store).read();
|
|
4195
|
+
});
|
|
4196
|
+
}
|
|
4197
|
+
__name(read, "read");
|
|
5546
4198
|
return Object.freeze({
|
|
5547
|
-
read
|
|
4199
|
+
read,
|
|
5548
4200
|
enroll: /* @__PURE__ */ __name(() => run("enroll"), "enroll"),
|
|
5549
4201
|
renew: /* @__PURE__ */ __name(() => run("renew"), "renew"),
|
|
5550
4202
|
reconcile: /* @__PURE__ */ __name(() => run("reconcile"), "reconcile"),
|
|
5551
|
-
ensureReady: /* @__PURE__ */ __name(() => run("ready"), "ensureReady")
|
|
4203
|
+
ensureReady: /* @__PURE__ */ __name(() => run("ready"), "ensureReady"),
|
|
4204
|
+
revoke: /* @__PURE__ */ __name(() => context.run(revoke), "revoke"),
|
|
4205
|
+
revokeOther: /* @__PURE__ */ __name((deviceId) => context.run(() => revokeOther(deviceId)), "revokeOther")
|
|
5552
4206
|
});
|
|
5553
4207
|
}
|
|
5554
4208
|
__name(createMlsDevDeviceClient, "createMlsDevDeviceClient");
|
|
@@ -5559,20 +4213,20 @@ function fail3(code = "MLS_DEV_KEY_PACKAGE_STATE_INVALID", retryable = false) {
|
|
|
5559
4213
|
throw new EndpointError(code, { code, retryable });
|
|
5560
4214
|
}
|
|
5561
4215
|
__name(fail3, "fail");
|
|
5562
|
-
function
|
|
4216
|
+
function record2(value, keys) {
|
|
5563
4217
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
|
|
5564
4218
|
const fields = Object.getOwnPropertyDescriptors(value);
|
|
5565
4219
|
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((key) => !Object.hasOwn(fields, key) || !Object.hasOwn(fields[key], "value") || !fields[key].enumerable)) fail3();
|
|
5566
4220
|
return Object.fromEntries(keys.map((key) => [key, fields[key].value]));
|
|
5567
4221
|
}
|
|
5568
|
-
__name(
|
|
4222
|
+
__name(record2, "record");
|
|
5569
4223
|
function createMlsDevKeyPackageService(options, context, store, initialized, readPrepared) {
|
|
5570
4224
|
const verifyCredential = options.verifyCredential;
|
|
5571
4225
|
const fence = /* @__PURE__ */ __name(() => store.transaction(async () => {
|
|
5572
4226
|
}), "fence");
|
|
5573
4227
|
const network = /* @__PURE__ */ __name(() => context.transport?.keyPackages ?? fail3("MLS_DEV_KEY_PACKAGE_TRANSPORT_REQUIRED"), "network");
|
|
5574
4228
|
function parseJournal(raw, binding, current, op) {
|
|
5575
|
-
const value =
|
|
4229
|
+
const value = record2(raw, ["version", "binding", "registration", "request", "receipt"]);
|
|
5576
4230
|
const registration = parseMlsDevEnrollmentResult(value.registration);
|
|
5577
4231
|
if (value.version !== 1 || !same3(parseMlsDevDeviceBinding(value.binding), binding) || !same3(registration, current.registration)) fail3("MLS_DEV_KEY_PACKAGE_BINDING_MISMATCH");
|
|
5578
4232
|
return {
|
|
@@ -5678,7 +4332,7 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
5678
4332
|
equivalent: /* @__PURE__ */ __name((a, b) => same3({ ...a, claimantCredential: null }, { ...b, claimantCredential: null }), "equivalent"),
|
|
5679
4333
|
forgetsRefusal: true,
|
|
5680
4334
|
async make(tx, _binding, current) {
|
|
5681
|
-
const raw = input ??
|
|
4335
|
+
const raw = input ?? record2(
|
|
5682
4336
|
(await tx.get("mls-dev-key-package/claim/" + id2))?.request,
|
|
5683
4337
|
["version", "requestId", "claimantCredential", "recipientDeviceId", "preconditions"]
|
|
5684
4338
|
);
|
|
@@ -5710,11 +4364,11 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
5710
4364
|
__name(run, "run");
|
|
5711
4365
|
return {
|
|
5712
4366
|
publish(input) {
|
|
5713
|
-
const id2 = parseMlsDevKeyPackageId(
|
|
4367
|
+
const id2 = parseMlsDevKeyPackageId(record2(input, ["keyPackageId"]).keyPackageId);
|
|
5714
4368
|
return run(async () => Object.freeze({ status: "published", receipt: await exchange(id2, true, publication(id2)) ?? fail3() }));
|
|
5715
4369
|
},
|
|
5716
4370
|
claim(input) {
|
|
5717
|
-
const data =
|
|
4371
|
+
const data = record2(input, ["requestId", "recipientDeviceId", "preconditions"]);
|
|
5718
4372
|
const frozen = Object.freeze({
|
|
5719
4373
|
requestId: parseMlsDevKeyPackageId(data.requestId),
|
|
5720
4374
|
recipientDeviceId: parseMlsDevKeyPackageId(data.recipientDeviceId),
|
|
@@ -5791,20 +4445,20 @@ function fail4(code = "MLS_DEV_KEY_PACKAGE_STATE_INVALID", retryable = false) {
|
|
|
5791
4445
|
}
|
|
5792
4446
|
__name(fail4, "fail");
|
|
5793
4447
|
var same4 = /* @__PURE__ */ __name((a, b) => canonicalEndpointJson(a) === canonicalEndpointJson(b), "same");
|
|
5794
|
-
function
|
|
4448
|
+
function record3(value, keys) {
|
|
5795
4449
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail4();
|
|
5796
4450
|
const fields = Object.getOwnPropertyDescriptors(value);
|
|
5797
4451
|
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((key) => !Object.hasOwn(fields, key) || !Object.hasOwn(fields[key], "value") || !fields[key].enumerable)) fail4();
|
|
5798
4452
|
return Object.fromEntries(keys.map((key) => [key, fields[key].value]));
|
|
5799
4453
|
}
|
|
5800
|
-
__name(
|
|
4454
|
+
__name(record3, "record");
|
|
5801
4455
|
function boundPayload(command, payload) {
|
|
5802
4456
|
const claims = parseMlsDevCredentialToken(command.credential).unsigned.claims;
|
|
5803
4457
|
if (payload.keyPackageId !== command.keyPackageId || payload.deviceId !== claims.device.deviceId || payload.credentialId !== claims.credentialId || payload.credentialGeneration !== claims.generation || payload.signaturePublicKey !== claims.device.signaturePublicKey || payload.ciphersuite !== claims.device.ciphersuite || payload.notBeforeMs < claims.issuedAtMs || payload.expiresAtMs > claims.expiresAtMs) fail4("MLS_DEV_KEY_PACKAGE_BINDING_MISMATCH");
|
|
5804
4458
|
}
|
|
5805
4459
|
__name(boundPayload, "boundPayload");
|
|
5806
4460
|
function intent(value, binding, id2) {
|
|
5807
|
-
const data =
|
|
4461
|
+
const data = record3(value, ["version", "binding", "command", "payload"]);
|
|
5808
4462
|
const command = parseMlsDevKeyPackageCommand(data.command), payload = parseMlsDevKeyPackagePayload(data.payload);
|
|
5809
4463
|
if (data.version !== 1 || !same4(parseMlsDevDeviceBinding(data.binding), binding) || command.keyPackageId !== id2) fail4();
|
|
5810
4464
|
const device = parseMlsDevCredentialToken(command.credential).unsigned.claims.device;
|
|
@@ -5823,7 +4477,7 @@ async function captureMlsDevPreparedKeyPackage(tx, binding, id2, current) {
|
|
|
5823
4477
|
const stored = intent(raw, binding, id2);
|
|
5824
4478
|
await assertMlsDevKeyPackageUnconsumed(tx, id2, stored.payload.keyPackageRef);
|
|
5825
4479
|
if (!same4(parseMlsDevCredentialToken(stored.command.credential).unsigned.claims.device, current.registration.device)) fail4("MLS_DEV_KEY_PACKAGE_BINDING_MISMATCH");
|
|
5826
|
-
const state =
|
|
4480
|
+
const state = record3(snapshot.protocolState, ["version", "binding", "keyPackageId", "wrappedState", "payload"]);
|
|
5827
4481
|
const preparation = parseMlsDevKeyPackagePreparation({ wrappedState: state.wrappedState, payload: state.payload });
|
|
5828
4482
|
if (state.version !== 1 || !same4(parseMlsDevDeviceBinding(state.binding), binding) || state.keyPackageId !== id2 || !same4(preparation.payload, stored.payload)) fail4();
|
|
5829
4483
|
return {
|
|
@@ -5890,7 +4544,7 @@ function createMlsDevKeyPackageClient(options, context) {
|
|
|
5890
4544
|
if (historical) {
|
|
5891
4545
|
const consumed = await tx.get(`mls-dev-key-package/consumed/${id2}`);
|
|
5892
4546
|
if (consumed !== void 0) {
|
|
5893
|
-
const data =
|
|
4547
|
+
const data = record3(consumed, ["version", "binding", "payload", "conversationId", "groupId", "controlSequence"]);
|
|
5894
4548
|
const payload = parseMlsDevKeyPackagePayload(data.payload);
|
|
5895
4549
|
if (data.version !== 1 || !same4(parseMlsDevDeviceBinding(data.binding), binding) || payload.keyPackageId !== id2 || payload.deviceId !== current.registration.device.deviceId || payload.signaturePublicKey !== current.registration.device.signaturePublicKey) fail4();
|
|
5896
4550
|
return payload;
|
|
@@ -6048,7 +4702,7 @@ function createMlsDevKeyPackageClient(options, context) {
|
|
|
6048
4702
|
return {
|
|
6049
4703
|
...service,
|
|
6050
4704
|
prepare(input) {
|
|
6051
|
-
const data =
|
|
4705
|
+
const data = record3(input, ["keyPackageId"]);
|
|
6052
4706
|
return run(parseMlsDevKeyPackageId(data.keyPackageId), true).then((result) => result ?? fail4());
|
|
6053
4707
|
},
|
|
6054
4708
|
read: /* @__PURE__ */ __name((id2) => run(id2, false), "read"),
|
|
@@ -6298,6 +4952,27 @@ function mlsDevReplyQuote(value, maximum) {
|
|
|
6298
4952
|
return quoted ? [...quoted.trim()].slice(0, maximum).join("") || void 0 : void 0;
|
|
6299
4953
|
}
|
|
6300
4954
|
__name(mlsDevReplyQuote, "mlsDevReplyQuote");
|
|
4955
|
+
var MLS_DEV_INBOX_PREVIEW_MAX = 200;
|
|
4956
|
+
async function readMlsDevInboxPreview(tx, conversationId, messageId) {
|
|
4957
|
+
const value = await tx.get(mlsDevTextMessageKey(conversationId, messageId));
|
|
4958
|
+
if (!value || value.deleted || await tx.get(mlsDevTextDeletedKey(conversationId, messageId)) !== void 0) return void 0;
|
|
4959
|
+
if (value.message.kind === "interaction.request") return { text: value.message.interaction.title, contentType: "text" };
|
|
4960
|
+
const text = mlsDevReplyQuote(value, MLS_DEV_INBOX_PREVIEW_MAX);
|
|
4961
|
+
return text ? { text, contentType: value.message.kind === "item" ? value.message.item.type : "text" } : void 0;
|
|
4962
|
+
}
|
|
4963
|
+
__name(readMlsDevInboxPreview, "readMlsDevInboxPreview");
|
|
4964
|
+
async function overlayMlsDevInboxPreviews(tx, page) {
|
|
4965
|
+
const rows = page?.conversations;
|
|
4966
|
+
if (!Array.isArray(rows)) return page;
|
|
4967
|
+
const conversations = [];
|
|
4968
|
+
for (const row of rows) {
|
|
4969
|
+
const messageId = row.lastMessage?.messageId;
|
|
4970
|
+
const preview = typeof row.id === "string" && typeof messageId === "string" ? await readMlsDevInboxPreview(tx, row.id, messageId) : void 0;
|
|
4971
|
+
conversations.push(preview ? { ...row, lastMessage: { ...row.lastMessage, ...preview } } : row);
|
|
4972
|
+
}
|
|
4973
|
+
return { ...page, conversations };
|
|
4974
|
+
}
|
|
4975
|
+
__name(overlayMlsDevInboxPreviews, "overlayMlsDevInboxPreviews");
|
|
6301
4976
|
function projectMlsDevTextMessage(value, account) {
|
|
6302
4977
|
const message = value.message;
|
|
6303
4978
|
if (message.kind === "item") return projectMlsDevItemMessage({ ...value, message }, account);
|
|
@@ -6413,7 +5088,7 @@ var MLS_ERASED_SENDER_ID = "erased_user";
|
|
|
6413
5088
|
var checked = /* @__PURE__ */ new Map();
|
|
6414
5089
|
var checkKey = /* @__PURE__ */ __name((environmentId, alias, opening) => JSON.stringify([environmentId, alias, opening.type, opening.accountId, opening.salt]), "checkKey");
|
|
6415
5090
|
var hexBytes = /* @__PURE__ */ __name((hex) => Uint8Array.from(hex.match(/../g) ?? [], (pair) => Number.parseInt(pair, 16)), "hexBytes");
|
|
6416
|
-
async function opensMlsAlias(environmentId, alias, opening,
|
|
5091
|
+
async function opensMlsAlias(environmentId, alias, opening, hash) {
|
|
6417
5092
|
let value;
|
|
6418
5093
|
try {
|
|
6419
5094
|
value = parseMlsPrincipalOpening(opening);
|
|
@@ -6424,7 +5099,7 @@ async function opensMlsAlias(environmentId, alias, opening, hash3) {
|
|
|
6424
5099
|
const key = checkKey(environmentId, alias, value);
|
|
6425
5100
|
let opens = checked.get(key);
|
|
6426
5101
|
if (opens === void 0) {
|
|
6427
|
-
const digest = await
|
|
5102
|
+
const digest = await hash(encodeMlsPrincipalAliasInput(environmentId, value));
|
|
6428
5103
|
opens = /^[0-9a-f]{64}$/.test(digest) && MLS_PRINCIPAL_ALIAS_PREFIX + encodeCanonicalMlsBase64Url(hexBytes(digest)) === alias;
|
|
6429
5104
|
if (checked.size >= 4096) checked.clear();
|
|
6430
5105
|
checked.set(key, opens);
|
|
@@ -6442,8 +5117,8 @@ function openings(room) {
|
|
|
6442
5117
|
}
|
|
6443
5118
|
}
|
|
6444
5119
|
__name(openings, "openings");
|
|
6445
|
-
async function checkMlsRoomOpenings(room, environmentId,
|
|
6446
|
-
for (const [alias, opening] of Object.entries(openings(room))) await opensMlsAlias(environmentId, alias, opening,
|
|
5120
|
+
async function checkMlsRoomOpenings(room, environmentId, hash) {
|
|
5121
|
+
for (const [alias, opening] of Object.entries(openings(room))) await opensMlsAlias(environmentId, alias, opening, hash);
|
|
6447
5122
|
}
|
|
6448
5123
|
__name(checkMlsRoomOpenings, "checkMlsRoomOpenings");
|
|
6449
5124
|
function mlsRoomAccounts(room, environmentId) {
|
|
@@ -6459,8 +5134,8 @@ function mlsAccountOf(accounts, principal) {
|
|
|
6459
5134
|
}
|
|
6460
5135
|
__name(mlsAccountOf, "mlsAccountOf");
|
|
6461
5136
|
function mlsAliasOf(accounts, accountId, type, admitted) {
|
|
6462
|
-
const
|
|
6463
|
-
return
|
|
5137
|
+
const matches = [...accounts].filter(([alias, identity]) => identity.accountId === accountId && identity.type === type && (!admitted || admitted.includes(alias))).map(([alias]) => alias);
|
|
5138
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
6464
5139
|
}
|
|
6465
5140
|
__name(mlsAliasOf, "mlsAliasOf");
|
|
6466
5141
|
async function primeMlsRoomIdentities(input) {
|
|
@@ -6507,6 +5182,11 @@ async function roomRemoval(tx, conversationId) {
|
|
|
6507
5182
|
}
|
|
6508
5183
|
}
|
|
6509
5184
|
__name(roomRemoval, "roomRemoval");
|
|
5185
|
+
async function roomRetired(tx, conversationId) {
|
|
5186
|
+
const room = await tx.get(`conversation/${conversationId}`);
|
|
5187
|
+
return Array.isArray(room?.mlsRetiredDeviceIds) ? room.mlsRetiredDeviceIds : [];
|
|
5188
|
+
}
|
|
5189
|
+
__name(roomRetired, "roomRetired");
|
|
6510
5190
|
async function assertRoomOpen(tx, conversationId, roster) {
|
|
6511
5191
|
const removal = await roomRemoval(tx, conversationId);
|
|
6512
5192
|
if (removal && removal.status !== "pending") fail6("MLS_DEV_GROUP_CLOSED");
|
|
@@ -6523,19 +5203,19 @@ function id(value) {
|
|
|
6523
5203
|
return value;
|
|
6524
5204
|
}
|
|
6525
5205
|
__name(id, "id");
|
|
6526
|
-
function
|
|
5206
|
+
function record4(value, required, optional = []) {
|
|
6527
5207
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail6();
|
|
6528
5208
|
const descriptors = Object.getOwnPropertyDescriptors(value), fields = Reflect.ownKeys(descriptors);
|
|
6529
5209
|
if (required.some((key) => !Object.hasOwn(descriptors, key)) || fields.some((key) => typeof key !== "string" || ![...required, ...optional].includes(key) || !Object.hasOwn(descriptors[key], "value") || !descriptors[key].enumerable)) fail6();
|
|
6530
5210
|
return Object.fromEntries(fields.map((key) => [key, descriptors[key].value]));
|
|
6531
5211
|
}
|
|
6532
|
-
__name(
|
|
5212
|
+
__name(record4, "record");
|
|
6533
5213
|
function envelope(value, binding, conversationId) {
|
|
6534
5214
|
if (value === null) return null;
|
|
6535
|
-
const data =
|
|
5215
|
+
const data = record4(value, ["version", "binding", "conversationId", "groupId", "wrappedState", "head", "roster", "removed", "pending"], ["protocol"]);
|
|
6536
5216
|
if (data.version !== 1 || !same6(parseMlsDevDeviceBinding(data.binding), binding) || data.conversationId !== conversationId || typeof data.removed !== "boolean") fail6();
|
|
6537
5217
|
const head = data.head === null ? null : parseMlsDevGroupHead(data.head), roster = parseMlsDevGroupRoster(data.roster);
|
|
6538
|
-
const pending = data.pending === null ? null :
|
|
5218
|
+
const pending = data.pending === null ? null : record4(data.pending, ["operationId", "control"]);
|
|
6539
5219
|
const control = pending ? parseMlsDevGroupControl(pending.control) : null;
|
|
6540
5220
|
parseMlsDevGroupPreparation({ wrappedState: data.wrappedState, payload: control ? { kind: "control", control } : { kind: "applied", head: head ?? fail6(), roster, removed: data.removed, consumedKeyPackageId: null } });
|
|
6541
5221
|
if (control && (control.conversationId !== conversationId || control.groupId !== data.groupId || !same6(control.expectedHead, head))) fail6();
|
|
@@ -6557,7 +5237,7 @@ function envelope(value, binding, conversationId) {
|
|
|
6557
5237
|
__name(envelope, "envelope");
|
|
6558
5238
|
function authority(value, conversationId) {
|
|
6559
5239
|
if (value === void 0) return void 0;
|
|
6560
|
-
const data =
|
|
5240
|
+
const data = record4(value, ["version", "marker", "membershipRevision", "admissionId", "roster", "terminal", "observedSequence"]);
|
|
6561
5241
|
const parsed = parseMlsDevGroupQueryResult({
|
|
6562
5242
|
version: data.version,
|
|
6563
5243
|
conversationId,
|
|
@@ -6583,7 +5263,7 @@ function authority(value, conversationId) {
|
|
|
6583
5263
|
__name(authority, "authority");
|
|
6584
5264
|
function recoveryFailure(value, conversationId) {
|
|
6585
5265
|
if (value === void 0) return void 0;
|
|
6586
|
-
const data =
|
|
5266
|
+
const data = record4(value, ["version", "conversationId", "code", "lastAppliedSequence", "observedAtMs"]);
|
|
6587
5267
|
if (data.version !== 1 || data.conversationId !== conversationId || !roomRecoveryCodes.some((code) => code === data.code) || !Number.isSafeInteger(data.lastAppliedSequence) || data.lastAppliedSequence < 0 || !Number.isSafeInteger(data.observedAtMs) || data.observedAtMs < 0) fail6();
|
|
6588
5268
|
return Object.freeze({
|
|
6589
5269
|
version: 1,
|
|
@@ -7186,7 +5866,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7186
5866
|
__name(catchUpFeed, "catchUpFeed");
|
|
7187
5867
|
const canonItemReference = /* @__PURE__ */ __name((item, conversationId, environmentId) => isCanonUploadUrl(item.url, { environmentId, conversationId, uploadId: item.uploadId }) && (item.thumbnailUrl === void 0 || isCanonUploadUrl(item.thumbnailUrl, { environmentId, conversationId, uploadId: item.uploadId, poster: true })), "canonItemReference");
|
|
7188
5868
|
function itemInput(attachment, caption) {
|
|
7189
|
-
const data =
|
|
5869
|
+
const data = record4(attachment, ["kind", "url", "mimeType", "sizeBytes", "uploadId"], ["fileName", "width", "height", "durationMs", "thumbnailUrl", "processingStatus"]);
|
|
7190
5870
|
if (data.processingStatus !== void 0 && data.processingStatus !== null && data.processingStatus !== "ready") fail6("MLS_DEV_ITEM_PROCESSING", true);
|
|
7191
5871
|
const raw = {
|
|
7192
5872
|
type: data.kind,
|
|
@@ -7218,7 +5898,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7218
5898
|
}
|
|
7219
5899
|
__name(validItem, "validItem");
|
|
7220
5900
|
function textInput(value) {
|
|
7221
|
-
const data =
|
|
5901
|
+
const data = record4(value, ["conversationId"], ["text", "messageId", "contentType", "mentions", "attachments", "replyTo", "replyToPosition", "metadata", "forwarded", "contactCardUserId"]);
|
|
7222
5902
|
const attachments = data.attachments ?? [];
|
|
7223
5903
|
if (!Array.isArray(attachments) || attachments.length > 1) fail6(Array.isArray(attachments) ? "MLS_DEV_ONE_ITEM_PER_MESSAGE" : "MLS_DEV_TEXT_ONLY");
|
|
7224
5904
|
const item = attachments.length ? itemInput(attachments[0], data.text) : void 0;
|
|
@@ -7238,11 +5918,18 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7238
5918
|
await primeIdentities(conversationId, init);
|
|
7239
5919
|
return store.transaction(async (tx) => {
|
|
7240
5920
|
const room = await tx.get(`conversation/${conversationId}`);
|
|
7241
|
-
if (room?.mlsAdmissionHold !== true) return Object.freeze({ held: false, waitingPrincipalIds: Object.freeze([]) });
|
|
7242
5921
|
const group = envelope((await readPreparationSnapshot(tx, scope4(conversationId))).protocolState, init.binding, conversationId);
|
|
5922
|
+
if (room?.mlsAdmissionHold !== true) {
|
|
5923
|
+
const joining = !!room && !!room.mls && (!group || group.removed);
|
|
5924
|
+
return Object.freeze({ held: joining, waitingPrincipalIds: Object.freeze(joining ? [init.identity.principalId] : []) });
|
|
5925
|
+
}
|
|
7243
5926
|
const accounts = mlsRoomAccounts(room, init.binding.environmentId);
|
|
7244
5927
|
const admitted = new Set((group && !group.removed ? group.roster : []).map((entry) => mlsAccountOf(accounts, entry.device.principal)));
|
|
7245
|
-
const waiting = [
|
|
5928
|
+
const waiting = [.../* @__PURE__ */ new Set([
|
|
5929
|
+
...Array.isArray(room.memberIds) ? room.memberIds : [],
|
|
5930
|
+
...typeof room.mlsInviteeId === "string" ? [room.mlsInviteeId] : [],
|
|
5931
|
+
...Array.isArray(room.mlsPendingFounderIds) ? room.mlsPendingFounderIds : []
|
|
5932
|
+
])].filter((memberId) => typeof memberId === "string" && !admitted.has(memberId));
|
|
7246
5933
|
return Object.freeze({ held: waiting.length > 0, waitingPrincipalIds: Object.freeze(waiting) });
|
|
7247
5934
|
});
|
|
7248
5935
|
}
|
|
@@ -7417,14 +6104,14 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7417
6104
|
}
|
|
7418
6105
|
__name(aliasedInteraction, "aliasedInteraction");
|
|
7419
6106
|
function interactionInput(value) {
|
|
7420
|
-
const data =
|
|
6107
|
+
const data = record4(value, ["conversationId"], ["messageId", "operationId", "guardId", "request", "response", "withdraw"]);
|
|
7421
6108
|
const conversationId = id(data.conversationId), messageId = data.messageId === void 0 ? void 0 : id(data.messageId);
|
|
7422
6109
|
const roles = ["request", "response", "withdraw"].filter((role) => data[role] !== void 0);
|
|
7423
6110
|
if (roles.length !== 1) fail6("INVALID_MLS_DEV_INTERACTION_INPUT");
|
|
7424
6111
|
try {
|
|
7425
6112
|
if (data.request !== void 0) {
|
|
7426
6113
|
if (principal.type !== "ai_agent") fail6("MLS_DEV_INTERACTION_REQUESTER_INVALID");
|
|
7427
|
-
const input =
|
|
6114
|
+
const input = record4(
|
|
7428
6115
|
data.request,
|
|
7429
6116
|
["requestId", "kind", "title", "responderIds", "expiresAtMs"],
|
|
7430
6117
|
["details", "options", "multiple", "risk", "allowSession", "scopeKey"]
|
|
@@ -7440,7 +6127,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7440
6127
|
}
|
|
7441
6128
|
if (data.response !== void 0) {
|
|
7442
6129
|
if (principal.type !== "human") fail6("MLS_DEV_INTERACTION_RESPONDER_INVALID");
|
|
7443
|
-
const input =
|
|
6130
|
+
const input = record4(data.response, ["requestId", "agentId"], ["answer", "secret"]);
|
|
7444
6131
|
const binding = { role: "response", requestId: input.requestId, agentId: input.agentId };
|
|
7445
6132
|
if (Object.hasOwn(input, "secret") === Object.hasOwn(input, "answer")) fail6("INVALID_MLS_DEV_INTERACTION_INPUT");
|
|
7446
6133
|
if (Object.hasOwn(input, "answer")) {
|
|
@@ -7918,12 +6605,13 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7918
6605
|
async function commitHeldRemoval(conversationId) {
|
|
7919
6606
|
const init = await initialized();
|
|
7920
6607
|
const target = await store.transaction(async (tx) => {
|
|
7921
|
-
const removal = await roomRemoval(tx, conversationId);
|
|
7922
|
-
if (removal
|
|
6608
|
+
const removal = await roomRemoval(tx, conversationId), retired = await roomRetired(tx, conversationId);
|
|
6609
|
+
if (removal ? removal.status !== "pending" : !retired.length) return void 0;
|
|
7923
6610
|
const current = await capture(tx, await readPreparationSnapshot(tx, scope4(conversationId)), init, conversationId), group = current.envelope;
|
|
7924
|
-
const held = new Set(removal
|
|
7925
|
-
if (!group?.head || group.removed || group.pending || current.authority?.terminal || held.has(
|
|
7926
|
-
const
|
|
6611
|
+
const held = new Set(removal?.pending.flatMap((entry) => entry.deviceIds)), own = current.device.registration.device.deviceId;
|
|
6612
|
+
if (!group?.head || group.removed || group.pending || current.authority?.terminal || held.has(own)) return void 0;
|
|
6613
|
+
const stays = /* @__PURE__ */ __name((entry) => group.roster.some((other) => other !== entry && other.device.principal.id === entry.device.principal.id), "stays");
|
|
6614
|
+
const removedDeviceId = group.roster.filter((entry) => removal ? held.has(entry.device.deviceId) : entry.device.deviceId !== own && retired.includes(entry.device.deviceId) && stays(entry)).map((entry) => entry.device.deviceId).sort()[0];
|
|
7927
6615
|
return removedDeviceId ? {
|
|
7928
6616
|
deviceId: current.device.registration.device.deviceId,
|
|
7929
6617
|
removedDeviceId,
|
|
@@ -8017,7 +6705,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
8017
6705
|
__name(run, "run");
|
|
8018
6706
|
const client = {
|
|
8019
6707
|
createRoom(input) {
|
|
8020
|
-
const data =
|
|
6708
|
+
const data = record4(input, ["conversationId", "name", "memberIds"], ["operationId", "protocol"]);
|
|
8021
6709
|
const conversationId = id(data.conversationId), operationId = id(data.operationId ?? context.ids());
|
|
8022
6710
|
if (typeof data.name !== "string" || data.name.trim().length > 100) fail6("INVALID_MLS_DEV_GROUP_INPUT");
|
|
8023
6711
|
if (data.protocol !== void 0 && !isMlsDevTextProtocol(data.protocol)) fail6("MLS_DEV_TEXT_PROTOCOL_UNSUPPORTED");
|
|
@@ -8071,15 +6759,15 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
8071
6759
|
});
|
|
8072
6760
|
},
|
|
8073
6761
|
create: /* @__PURE__ */ __name((input) => {
|
|
8074
|
-
const data =
|
|
6762
|
+
const data = record4(input, ["conversationId"], ["operationId"]);
|
|
8075
6763
|
return run(() => serializeInboundPreparation(store, id(data.conversationId), () => submit("create", data)));
|
|
8076
6764
|
}, "create"),
|
|
8077
6765
|
add: /* @__PURE__ */ __name((input) => {
|
|
8078
|
-
const data =
|
|
6766
|
+
const data = record4(input, ["conversationId", "claimRequestId"], ["operationId"]);
|
|
8079
6767
|
return run(() => serializeInboundPreparation(store, id(data.conversationId), () => submit("add", data)));
|
|
8080
6768
|
}, "add"),
|
|
8081
6769
|
remove: /* @__PURE__ */ __name((input) => {
|
|
8082
|
-
const data =
|
|
6770
|
+
const data = record4(input, ["conversationId", "removedDeviceId"], ["operationId"]);
|
|
8083
6771
|
return run(() => serializeInboundPreparation(store, id(data.conversationId), () => submit("remove", data)));
|
|
8084
6772
|
}, "remove"),
|
|
8085
6773
|
refresh: /* @__PURE__ */ __name((value) => run(() => serializeInboundPreparation(store, id(value), () => refresh(id(value)))), "refresh"),
|
|
@@ -8094,13 +6782,13 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
8094
6782
|
}, false), "heldCurrent"),
|
|
8095
6783
|
read: /* @__PURE__ */ __name((value) => run(() => read(id(value)), false), "read"),
|
|
8096
6784
|
readVerifiedMessageBinding: /* @__PURE__ */ __name((input) => {
|
|
8097
|
-
const data =
|
|
6785
|
+
const data = record4(input, ["conversationId", "messageId"]);
|
|
8098
6786
|
const conversationId = id(data.conversationId), messageId = id(data.messageId);
|
|
8099
6787
|
return run(() => readVerifiedMessageBinding(conversationId, messageId), false);
|
|
8100
6788
|
}, "readVerifiedMessageBinding"),
|
|
8101
6789
|
readCurrentAudience: /* @__PURE__ */ __name((value) => run(() => serializeInboundPreparation(store, id(value), () => readCurrentAudience(id(value)))), "readCurrentAudience"),
|
|
8102
6790
|
assertCurrentAudience: /* @__PURE__ */ __name((value) => {
|
|
8103
|
-
const data =
|
|
6791
|
+
const data = record4(value, ["version", "protocol", "conversationId", "groupId", "ownDeviceId", "head", "membershipRevision", "admissionId", "roster", "feedOrdinal"]);
|
|
8104
6792
|
const expected = JSON.parse(canonicalEndpointJson(data)), conversationId = id(expected.conversationId);
|
|
8105
6793
|
if (!Number.isSafeInteger(expected.feedOrdinal) || expected.feedOrdinal < 1) fail6("MLS_DEV_AUDIENCE_CHANGED");
|
|
8106
6794
|
return run(() => serializeInboundPreparation(store, conversationId, async () => {
|
|
@@ -8122,12 +6810,12 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
8122
6810
|
return run(() => serializeInboundPreparation(store, input.conversationId, () => sendInteraction(input, operationId, guardId)));
|
|
8123
6811
|
}, "sendInteraction"),
|
|
8124
6812
|
readInteraction: /* @__PURE__ */ __name(async (value) => {
|
|
8125
|
-
const data =
|
|
6813
|
+
const data = record4(value, ["conversationId", "agentId", "requestId"]);
|
|
8126
6814
|
const conversationId = id(data.conversationId), agentId = id(data.agentId), requestId = id(data.requestId);
|
|
8127
6815
|
return run(() => serializeInboundPreparation(store, conversationId, () => readInteraction(conversationId, agentId, requestId)), false);
|
|
8128
6816
|
}, "readInteraction"),
|
|
8129
6817
|
openSecretAnswer: /* @__PURE__ */ __name(async (value) => {
|
|
8130
|
-
const data =
|
|
6818
|
+
const data = record4(value, ["conversationId", "requestId"]), conversationId = id(data.conversationId), requestId = id(data.requestId);
|
|
8131
6819
|
return run(() => serializeInboundPreparation(store, conversationId, () => openSecretAnswer(conversationId, requestId)));
|
|
8132
6820
|
}, "openSecretAnswer")
|
|
8133
6821
|
};
|
|
@@ -8135,13 +6823,14 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
8135
6823
|
client,
|
|
8136
6824
|
async commitHeldRemoval(conversationId) {
|
|
8137
6825
|
const cid = id(conversationId);
|
|
8138
|
-
|
|
6826
|
+
const due = await store.transaction(async (tx) => (await roomRemoval(tx, cid))?.status === "pending" || (await roomRetired(tx, cid)).length > 0);
|
|
6827
|
+
if (!due) return void 0;
|
|
8139
6828
|
return run(() => serializeInboundPreparation(store, cid, () => commitHeldRemoval(cid)));
|
|
8140
6829
|
},
|
|
8141
6830
|
/** Forwards an encrypted text by re-encryption: this device's own decrypted copy, sent as new text to another
|
|
8142
6831
|
* encrypted room (mls-payload-v3.md §4). Items and cards are not forwarded yet. */
|
|
8143
6832
|
forwardText(value, operationId, guardId) {
|
|
8144
|
-
const data =
|
|
6833
|
+
const data = record4(value, ["sourceConversationId", "messageId", "targetConversationId"]);
|
|
8145
6834
|
const source = id(data.sourceConversationId), messageId = id(data.messageId), target = id(data.targetConversationId);
|
|
8146
6835
|
const selectedId = id(operationId), selectedGuard = guardId === void 0 ? void 0 : id(guardId);
|
|
8147
6836
|
return run(async () => {
|
|
@@ -8298,7 +6987,7 @@ function createEndpoint(options) {
|
|
|
8298
6987
|
const { store, transport } = options;
|
|
8299
6988
|
const clock = options.clock ?? Date.now;
|
|
8300
6989
|
const ids = options.ids ?? randomId;
|
|
8301
|
-
const
|
|
6990
|
+
const hash = options.hash ?? sha256;
|
|
8302
6991
|
const hashBytes = options.hashBytes ?? sha256Bytes;
|
|
8303
6992
|
const owner = ids();
|
|
8304
6993
|
const leaseMs = Math.max(5e3, options.leaseMs ?? 6e4);
|
|
@@ -8411,7 +7100,7 @@ function createEndpoint(options) {
|
|
|
8411
7100
|
const mlsDevDevice = options.mlsDevDevice ? createMlsDevDeviceClient(options.mlsDevDevice, mlsDevContext) : void 0;
|
|
8412
7101
|
const mlsDevKeyPackages = options.mlsDevDevice?.prepareProtectedKeyPackage ? createMlsDevKeyPackageClient(options.mlsDevDevice, mlsDevContext) : void 0;
|
|
8413
7102
|
const mlsDevGroups = options.mlsDevDevice?.prepareProtectedGroup ? createMlsDevGroups(options.mlsDevDevice, mlsDevContext, mlsDevKeyPackages, {
|
|
8414
|
-
hash
|
|
7103
|
+
hash,
|
|
8415
7104
|
hashBytes,
|
|
8416
7105
|
readRoom,
|
|
8417
7106
|
resolveProtocol: /* @__PURE__ */ __name((conversationId) => resolveMlsRoomMode(conversationId).then((value) => value.protocol), "resolveProtocol"),
|
|
@@ -8440,7 +7129,7 @@ function createEndpoint(options) {
|
|
|
8440
7129
|
scheduleAdmission(conversationId, 0);
|
|
8441
7130
|
}, "controlSettled"),
|
|
8442
7131
|
// Whatever fetched them (a hint, the reconnect sweep, a send, recovery), the room's local reads show them now.
|
|
8443
|
-
historyChanged: /* @__PURE__ */ __name((conversationId) => queryObserver.invalidate((query, parameters) => parameters.conversationId === conversationId && ["list_messages", "get_messages", "get_runtime_card_state"].includes(query)), "historyChanged"),
|
|
7132
|
+
historyChanged: /* @__PURE__ */ __name((conversationId) => queryObserver.invalidate((query, parameters) => options.localInboxPreviews && query === "list_conversations" || parameters.conversationId === conversationId && ["list_messages", "get_messages", "get_runtime_card_state"].includes(query)), "historyChanged"),
|
|
8444
7133
|
streamLive: /* @__PURE__ */ __name(() => events.isConnected(), "streamLive")
|
|
8445
7134
|
}) : void 0;
|
|
8446
7135
|
async function inMlsEnvironment() {
|
|
@@ -8571,13 +7260,14 @@ function createEndpoint(options) {
|
|
|
8571
7260
|
if (!mlsDevGroups || await mlsDevGroups.client.read(conversationId)) return;
|
|
8572
7261
|
if (!isMlsDevTextProtocol((await resolveMlsRoomMode(conversationId, false)).protocol) || await readServerRoomMode() !== "default") return;
|
|
8573
7262
|
const { installationId } = await initialization;
|
|
8574
|
-
const digest = await
|
|
7263
|
+
const digest = await hash(canonicalEndpointJson(["canon.mls.room-birth.v1", installationId, conversationId]));
|
|
8575
7264
|
await mlsDevGroups.client.create({ conversationId, operationId: `mls-birth-${digest.slice(0, 48)}` }).catch((error) => {
|
|
8576
7265
|
if (!(error instanceof EndpointError) || ["ENDPOINT_CLOSED", "PREPARATION_INSTALLATION_FENCED"].includes(error.code)) throw error;
|
|
8577
7266
|
});
|
|
8578
7267
|
}
|
|
8579
7268
|
__name(createBornGroup, "createBornGroup");
|
|
8580
7269
|
const admissionRuns = /* @__PURE__ */ new Map(), admitting = /* @__PURE__ */ new Set(), admissionDelays = /* @__PURE__ */ new Map();
|
|
7270
|
+
const announcementsSeen = /* @__PURE__ */ new Map();
|
|
8581
7271
|
const admissionTimers = /* @__PURE__ */ new Map();
|
|
8582
7272
|
const ADMISSION_IDLE_MS = 10 * 6e4, CLAIM_JOURNAL_KEEP_MS = 60 * 6e4;
|
|
8583
7273
|
const admissionUnfinished = /* @__PURE__ */ new Set();
|
|
@@ -8609,9 +7299,11 @@ function createEndpoint(options) {
|
|
|
8609
7299
|
const missing = [...local?.memberIds ?? [], ...typeof invitee === "string" ? [invitee] : []].some((memberId) => !inRoster.has(memberId)) || admissionUnfinished.has(conversationId);
|
|
8610
7300
|
if (!missing) admissionDelays.delete(conversationId);
|
|
8611
7301
|
const delay = admissionDelays.get(conversationId) ?? admissionRetry.initial;
|
|
8612
|
-
const
|
|
7302
|
+
const announced = local?.mlsDeviceAnnouncedAtMs;
|
|
7303
|
+
const due = announced !== announcementsSeen.get(conversationId) ? -Infinity : (admissionRuns.get(conversationId) ?? -Infinity) + (missing ? delay : ADMISSION_IDLE_MS);
|
|
8613
7304
|
if (clock() < due) return missing ? scheduleAdmission(conversationId, due - clock()) : void 0;
|
|
8614
7305
|
admitting.add(conversationId);
|
|
7306
|
+
announcementsSeen.set(conversationId, announced);
|
|
8615
7307
|
try {
|
|
8616
7308
|
admissionRuns.set(conversationId, clock());
|
|
8617
7309
|
await mlsDevKeyPackages.pruneClaims(clock() - CLAIM_JOURNAL_KEEP_MS);
|
|
@@ -8988,7 +7680,7 @@ function createEndpoint(options) {
|
|
|
8988
7680
|
observe: events.observe,
|
|
8989
7681
|
isProtocolReady: /* @__PURE__ */ __name(async (id2) => isMlsDevTextProtocol((await resolveMlsRoomMode(id2)).protocol) ? await mlsDevGroups?.isTextReady(id2) ?? false : void 0, "isProtocolReady")
|
|
8990
7682
|
});
|
|
8991
|
-
const migration = createEndpointMigration({ store, ready: /* @__PURE__ */ __name(() => initialization, "ready"), query: /* @__PURE__ */ __name((name, parameters) => endpoint.query(name, parameters), "query"), hash
|
|
7683
|
+
const migration = createEndpointMigration({ store, ready: /* @__PURE__ */ __name(() => initialization, "ready"), query: /* @__PURE__ */ __name((name, parameters) => endpoint.query(name, parameters), "query"), hash, clock });
|
|
8992
7684
|
const handoffs = createInboundHandoffs({
|
|
8993
7685
|
store,
|
|
8994
7686
|
owner,
|
|
@@ -9000,16 +7692,16 @@ function createEndpoint(options) {
|
|
|
9000
7692
|
assertCurrent: assertCurrentInstallation,
|
|
9001
7693
|
assertReady: /* @__PURE__ */ __name(async (records) => {
|
|
9002
7694
|
if (options.admitRecoveredMessages) {
|
|
9003
|
-
for (const id2 of new Set(records.map((
|
|
7695
|
+
for (const id2 of new Set(records.map((record6) => record6.event.conversationId).filter((id3) => !!id3))) await recovery.assertReady(id2);
|
|
9004
7696
|
}
|
|
9005
7697
|
}, "assertReady")
|
|
9006
7698
|
});
|
|
9007
|
-
const externalInbound = createExternalInboundClaims({ store, clock, hash
|
|
7699
|
+
const externalInbound = createExternalInboundClaims({ store, clock, hash, ownershipVersion: options.identity.ownershipVersion, ready: /* @__PURE__ */ __name(() => initialization, "ready"), assertOpen, assertCurrent: assertCurrentInstallation });
|
|
9008
7700
|
const messageGroups = createMessageGroupCoordinator({
|
|
9009
7701
|
store,
|
|
9010
7702
|
endpoint: /* @__PURE__ */ __name(() => endpoint, "endpoint"),
|
|
9011
7703
|
ready: /* @__PURE__ */ __name(() => initialization, "ready"),
|
|
9012
|
-
hash
|
|
7704
|
+
hash,
|
|
9013
7705
|
clock,
|
|
9014
7706
|
wake: /* @__PURE__ */ __name(() => scheduleRetry(0), "wake"),
|
|
9015
7707
|
preparationGeneration: /* @__PURE__ */ __name(() => installationGeneration, "preparationGeneration"),
|
|
@@ -9157,7 +7849,7 @@ function createEndpoint(options) {
|
|
|
9157
7849
|
const index = group?.children.findIndex((child) => child.operationId === operationId) ?? -1;
|
|
9158
7850
|
const previous = group && index > 0 ? await tx.get(key(group.children[index - 1].operationId)) : void 0;
|
|
9159
7851
|
if (previous?.status === "rejected" || previous?.status === "succeeded" && !messageGroupChildSucceeded(previous)) {
|
|
9160
|
-
const
|
|
7852
|
+
const rejected = { ...current, status: "rejected", updatedAt: now(), receipt: {
|
|
9161
7853
|
protocol: CANON_ENDPOINT_PROTOCOL,
|
|
9162
7854
|
operationId,
|
|
9163
7855
|
installationId: current.installationId,
|
|
@@ -9167,12 +7859,12 @@ function createEndpoint(options) {
|
|
|
9167
7859
|
updatedAt: now(),
|
|
9168
7860
|
error: { code: "DEPENDENCY_REJECTED", message: "An earlier part of this reply could not be delivered", retryable: false }
|
|
9169
7861
|
} };
|
|
9170
|
-
delete
|
|
9171
|
-
delete
|
|
9172
|
-
await tx.put(key(operationId),
|
|
9173
|
-
await removeOperationOutbox(tx,
|
|
9174
|
-
await settleOperationOrder(tx,
|
|
9175
|
-
return { operation:
|
|
7862
|
+
delete rejected.request;
|
|
7863
|
+
delete rejected.preparedContent;
|
|
7864
|
+
await tx.put(key(operationId), rejected);
|
|
7865
|
+
await removeOperationOutbox(tx, rejected);
|
|
7866
|
+
await settleOperationOrder(tx, rejected);
|
|
7867
|
+
return { operation: rejected, acquired: false };
|
|
9176
7868
|
}
|
|
9177
7869
|
if (!messageGroups.allowed(current.messageGroupId) || !group || index < 0 || group.cancelled || index > 0 && !messageGroupChildSucceeded(previous)) return { operation: current, acquired: false };
|
|
9178
7870
|
}
|
|
@@ -9299,8 +7991,8 @@ function createEndpoint(options) {
|
|
|
9299
7991
|
await persistReceipt(operation, receipt);
|
|
9300
7992
|
return receipt;
|
|
9301
7993
|
} catch (error) {
|
|
9302
|
-
const
|
|
9303
|
-
if (reconcileOnly &&
|
|
7994
|
+
const failure = safeError(error);
|
|
7995
|
+
if (reconcileOnly && failure.retryable) {
|
|
9304
7996
|
await store.transaction(async (tx) => {
|
|
9305
7997
|
const current = await tx.get(key(operationId));
|
|
9306
7998
|
if (current?.lease?.owner === owner) {
|
|
@@ -9309,17 +8001,17 @@ function createEndpoint(options) {
|
|
|
9309
8001
|
await tx.put(key(operationId), next);
|
|
9310
8002
|
}
|
|
9311
8003
|
});
|
|
9312
|
-
return deferredReceipt(operation,
|
|
8004
|
+
return deferredReceipt(operation, failure);
|
|
9313
8005
|
}
|
|
9314
8006
|
const updated = await store.transaction(async (tx) => {
|
|
9315
8007
|
const current = await tx.get(key(operationId));
|
|
9316
|
-
if (!current) throw
|
|
8008
|
+
if (!current) throw failure;
|
|
9317
8009
|
if (current.lease?.owner !== owner || terminal(current.status)) return current;
|
|
9318
|
-
const lookupUncertain = resolvingReceipt && !
|
|
9319
|
-
const generationUncertain = current.attempted &&
|
|
9320
|
-
const contentModeUncertain = current.attempted && checkingContentMode && !
|
|
9321
|
-
const guardUncertain = current.attempted && checkingDeliveryGuard && !
|
|
9322
|
-
const next = { ...current, status: current.status === "uncertain" || lookupUncertain || generationUncertain || contentModeUncertain || guardUncertain ? "uncertain" :
|
|
8010
|
+
const lookupUncertain = resolvingReceipt && !failure.retryable;
|
|
8011
|
+
const generationUncertain = current.attempted && failure.code === "PREPARATION_INSTALLATION_FENCED";
|
|
8012
|
+
const contentModeUncertain = current.attempted && checkingContentMode && !failure.retryable;
|
|
8013
|
+
const guardUncertain = current.attempted && checkingDeliveryGuard && !failure.retryable;
|
|
8014
|
+
const next = { ...current, status: current.status === "uncertain" || lookupUncertain || generationUncertain || contentModeUncertain || guardUncertain ? "uncertain" : failure.retryable ? "deferred" : "rejected", updatedAt: now(), nextAttemptAt: clock() + Math.max(failure.retryAfterMs ?? 0, Math.min(3e4, 1e3 * 2 ** Math.min(current.attempts, 5))) };
|
|
9323
8015
|
delete next.lease;
|
|
9324
8016
|
if (current.sensitive) {
|
|
9325
8017
|
delete next.request;
|
|
@@ -9329,14 +8021,14 @@ function createEndpoint(options) {
|
|
|
9329
8021
|
next.receipt = { protocol: CANON_ENDPOINT_PROTOCOL, operationId: current.operationId, installationId: current.installationId, action: current.action, status: "uncertain", error: {
|
|
9330
8022
|
code: generationUncertain ? "PREPARATION_INSTALLATION_FENCED" : guardUncertain ? "DELIVERY_GUARD_RECONCILIATION_REQUIRED" : contentModeUncertain ? "CONTENT_MODE_RECONCILIATION_REQUIRED" : "RECEIPT_LOOKUP_REJECTED",
|
|
9331
8023
|
message: generationUncertain ? "The installation changed before the attempted operation could be reconciled" : contentModeUncertain || guardUncertain ? "The attempted operation requires receipt reconciliation before its outcome is known" : "The attempted operation could not be reconciled with the current credentials",
|
|
9332
|
-
status:
|
|
8024
|
+
status: failure.status,
|
|
9333
8025
|
retryable: false
|
|
9334
8026
|
}, createdAt: current.createdAt, updatedAt: now() };
|
|
9335
8027
|
delete next.request;
|
|
9336
8028
|
delete next.preparedContent;
|
|
9337
8029
|
delete next.nextAttemptAt;
|
|
9338
|
-
} else if (!
|
|
9339
|
-
next.receipt = { protocol: CANON_ENDPOINT_PROTOCOL, operationId: current.operationId, installationId: current.installationId, action: current.action, status: "rejected", error: { code:
|
|
8030
|
+
} else if (!failure.retryable && current.status !== "uncertain") {
|
|
8031
|
+
next.receipt = { protocol: CANON_ENDPOINT_PROTOCOL, operationId: current.operationId, installationId: current.installationId, action: current.action, status: "rejected", error: { code: failure.code, message: current.sensitive ? "Sensitive operation could not complete" : failure.message, status: failure.status, retryable: false }, createdAt: current.createdAt, updatedAt: now() };
|
|
9340
8032
|
delete next.nextAttemptAt;
|
|
9341
8033
|
}
|
|
9342
8034
|
await tx.put(key(operationId), next);
|
|
@@ -9357,8 +8049,8 @@ function createEndpoint(options) {
|
|
|
9357
8049
|
if (updated.messageGroupId && terminal(updated.status)) await messageGroups.settled(updated);
|
|
9358
8050
|
if (terminal(updated.status)) scheduleRetry(0);
|
|
9359
8051
|
notify(updated);
|
|
9360
|
-
if (
|
|
9361
|
-
return deferredReceipt(updated,
|
|
8052
|
+
if (failure.retryable && updated.status !== "uncertain" && !closed) scheduleRetry(Math.max(1e3, (updated.nextAttemptAt ?? clock()) - clock()));
|
|
8053
|
+
return deferredReceipt(updated, failure);
|
|
9362
8054
|
} finally {
|
|
9363
8055
|
clearInterval(heartbeat);
|
|
9364
8056
|
}
|
|
@@ -9409,7 +8101,7 @@ function createEndpoint(options) {
|
|
|
9409
8101
|
}
|
|
9410
8102
|
const authored = JSON.parse(canonicalEndpointJson(intent2));
|
|
9411
8103
|
const sensitive = executeOptions.sensitive === true || endpointOperationRequestRetention(authored.action, authored.body, authored.envelope) === "memory";
|
|
9412
|
-
const inputFingerprint = sensitive ? void 0 : await
|
|
8104
|
+
const inputFingerprint = sensitive ? void 0 : await hash(canonicalEndpointJson({ ...authored, ...identity.ownershipVersion !== void 0 ? { ownershipVersion: identity.ownershipVersion } : {} }));
|
|
9413
8105
|
const previous = await store.transaction(async (tx) => {
|
|
9414
8106
|
await assertCurrentInstallation(tx);
|
|
9415
8107
|
return tx.get(key(operationId));
|
|
@@ -9461,8 +8153,8 @@ function createEndpoint(options) {
|
|
|
9461
8153
|
if (!sensitive && !previous) await reservePreparedIdentity(store, { operationId, action: authored.action, fingerprint: inputFingerprint, installationGeneration });
|
|
9462
8154
|
const normalized = await prepareEndpointIntent({ intent: authored, previousPreconditions: previous?.preconditions, query: /* @__PURE__ */ __name((name, parameters) => endpoint.query(name, parameters), "query") });
|
|
9463
8155
|
const routedBody = { ...normalized.body, ...normalized.envelope };
|
|
9464
|
-
const sensitiveFingerprint = sensitive ? await
|
|
9465
|
-
const fingerprint = await
|
|
8156
|
+
const sensitiveFingerprint = sensitive ? await hash(canonicalEndpointJson({ ...normalized, ...identity.ownershipVersion !== void 0 ? { ownershipVersion: identity.ownershipVersion } : {} })) : void 0;
|
|
8157
|
+
const fingerprint = await hash(canonicalEndpointJson({ ...sensitive ? { action: normalized.action, preconditions: normalized.preconditions } : normalized, ...identity.ownershipVersion !== void 0 ? { ownershipVersion: identity.ownershipVersion } : {} }));
|
|
9466
8158
|
assertOpen();
|
|
9467
8159
|
const includePreparedContent = !sensitive && negotiatedCapabilities?.preparedContentVersion === 1;
|
|
9468
8160
|
const request = {
|
|
@@ -9514,6 +8206,13 @@ function createEndpoint(options) {
|
|
|
9514
8206
|
createdAt: timestamp,
|
|
9515
8207
|
updatedAt: timestamp
|
|
9516
8208
|
};
|
|
8209
|
+
if (normalized.action === "start_conversation") {
|
|
8210
|
+
const opener = normalized.body.opener;
|
|
8211
|
+
if (opener?.withheld && typeof opener.messageId === "string") {
|
|
8212
|
+
const pending = await tx.get(`${openerPrefix()}/${encodeURIComponent(opener.messageId)}`);
|
|
8213
|
+
if (pending?.start?.operationId === operationId) created.openerFingerprint = pending.openerFingerprint;
|
|
8214
|
+
}
|
|
8215
|
+
}
|
|
9517
8216
|
return commitOperationRecord(tx, created);
|
|
9518
8217
|
}, "commitOperation");
|
|
9519
8218
|
const operation = sensitive ? await store.transaction(async (tx) => {
|
|
@@ -9607,7 +8306,7 @@ function createEndpoint(options) {
|
|
|
9607
8306
|
return store.transaction((tx) => tx.get(key(operationId)));
|
|
9608
8307
|
},
|
|
9609
8308
|
waitForOperation(operationId, waitOptions = {}) {
|
|
9610
|
-
return new Promise((
|
|
8309
|
+
return new Promise((resolve3, reject) => {
|
|
9611
8310
|
let finished = false;
|
|
9612
8311
|
let checking = false;
|
|
9613
8312
|
let poll;
|
|
@@ -9621,7 +8320,7 @@ function createEndpoint(options) {
|
|
|
9621
8320
|
controller.signal.removeEventListener("abort", abort);
|
|
9622
8321
|
waitOptions.signal?.removeEventListener("abort", abort);
|
|
9623
8322
|
if (error) reject(error);
|
|
9624
|
-
else
|
|
8323
|
+
else resolve3(receipt);
|
|
9625
8324
|
}, "finish");
|
|
9626
8325
|
const abort = /* @__PURE__ */ __name(() => finish(void 0, new EndpointError("Delivery remains in the endpoint journal for reconciliation", {
|
|
9627
8326
|
code: "OPERATION_PENDING",
|
|
@@ -9706,6 +8405,7 @@ function createEndpoint(options) {
|
|
|
9706
8405
|
}
|
|
9707
8406
|
const result = await transport.query(name, normalized, controller.signal);
|
|
9708
8407
|
if (name === "mls_interactions") return await accountMlsInteractions(result);
|
|
8408
|
+
if (name === "list_conversations" && options.localInboxPreviews && mlsDevGroups) return await store.transaction((tx) => overlayMlsDevInboxPreviews(tx, result));
|
|
9709
8409
|
return result;
|
|
9710
8410
|
}).finally(() => {
|
|
9711
8411
|
reads.delete(queryKey);
|
|
@@ -9740,7 +8440,7 @@ function createEndpoint(options) {
|
|
|
9740
8440
|
observeEvents: events.observe,
|
|
9741
8441
|
createMediaPipeline(bytes, mediaOptions) {
|
|
9742
8442
|
assertOpen();
|
|
9743
|
-
const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash
|
|
8443
|
+
const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash, ids, clock, background: mediaOptions?.background });
|
|
9744
8444
|
mediaPipelines2.add(pipeline3);
|
|
9745
8445
|
return pipeline3;
|
|
9746
8446
|
},
|
|
@@ -9766,19 +8466,27 @@ function createEndpoint(options) {
|
|
|
9766
8466
|
},
|
|
9767
8467
|
async startWithheldOpener(input) {
|
|
9768
8468
|
assertOpen();
|
|
8469
|
+
input = copyJson(input);
|
|
9769
8470
|
await initialization;
|
|
9770
8471
|
if (typeof input.messageId !== "string" || !input.messageId || typeof input.text !== "string")
|
|
9771
8472
|
throw new EndpointError("Invalid deferred opening line", { status: 400, code: "INVALID_DEFERRED_OPENER", retryable: false });
|
|
9772
|
-
const digest = await
|
|
8473
|
+
const digest = await hash(canonicalEndpointJson(["canon.withheld-opener.v1", input.messageId]));
|
|
9773
8474
|
const operationId = `start-opener-${digest.slice(0, 48)}`;
|
|
9774
8475
|
const body = {
|
|
9775
8476
|
targetUserId: input.targetUserId,
|
|
9776
8477
|
opener: { withheld: true, messageId: input.messageId },
|
|
9777
8478
|
...input.selection ? { selection: copyJson(input.selection) } : {}
|
|
9778
8479
|
};
|
|
8480
|
+
const openerFingerprint = await hash(canonicalEndpointJson([body, input.text]));
|
|
9779
8481
|
const recordKey = `${openerPrefix()}/${encodeURIComponent(input.messageId)}`;
|
|
9780
8482
|
await store.transaction(async (tx) => {
|
|
9781
8483
|
await assertCurrentInstallation(tx);
|
|
8484
|
+
const operation = await tx.get(key(operationId));
|
|
8485
|
+
if (operation) {
|
|
8486
|
+
if (operation.openerFingerprint !== openerFingerprint)
|
|
8487
|
+
throw new EndpointError("Opening line ID was already used for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId });
|
|
8488
|
+
return;
|
|
8489
|
+
}
|
|
9782
8490
|
const existing = await tx.get(recordKey);
|
|
9783
8491
|
if (existing && (existing.text !== input.text || canonicalEndpointJson(existing.start?.body) !== canonicalEndpointJson(body)))
|
|
9784
8492
|
throw new EndpointError("Opening line ID was already used for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId });
|
|
@@ -9787,6 +8495,7 @@ function createEndpoint(options) {
|
|
|
9787
8495
|
messageId: input.messageId,
|
|
9788
8496
|
text: input.text,
|
|
9789
8497
|
requestId: null,
|
|
8498
|
+
openerFingerprint,
|
|
9790
8499
|
operationId: `send-opener-${digest.slice(0, 48)}`,
|
|
9791
8500
|
start: { operationId, body }
|
|
9792
8501
|
});
|
|
@@ -9905,15 +8614,15 @@ function createEndpoint(options) {
|
|
|
9905
8614
|
if (!eventIds.length) return false;
|
|
9906
8615
|
if (options.admitRecoveredMessages) {
|
|
9907
8616
|
const records = await Promise.all(eventIds.map((id2) => endpoint.getInbound(id2)));
|
|
9908
|
-
for (const id2 of new Set(records.map((
|
|
8617
|
+
for (const id2 of new Set(records.map((record6) => record6?.event.conversationId).filter((id3) => !!id3))) await recovery.assertReady(id2);
|
|
9909
8618
|
}
|
|
9910
8619
|
return store.transaction(async (tx) => {
|
|
9911
8620
|
if (await tx.get("migration/active")) throw new EndpointError("Migration is incomplete; native execution is gated", { code: "MIGRATION_IN_PROGRESS", retryable: false });
|
|
9912
8621
|
const records = await Promise.all(eventIds.map((id2) => tx.get(`inbound/${id2}`)));
|
|
9913
8622
|
let projection = false;
|
|
9914
|
-
for (const
|
|
8623
|
+
for (const record6 of records) if (record6 && await settleInboundProjection(tx, record6, now())) projection = true;
|
|
9915
8624
|
if (projection) return false;
|
|
9916
|
-
if (records.some((
|
|
8625
|
+
if (records.some((record6) => !record6 || record6.deleted || record6.externalClaim || record6.handoffGeneration && record6.lease || record6.state === "uncertain" || record6.state === "settled" || record6.lease && !record6.lease.owner.startsWith(`${owner}:`) && record6.lease.expiresAt > clock())) return false;
|
|
9917
8626
|
for (let i = 0; i < records.length; i++) {
|
|
9918
8627
|
const next = { ...records[i], state: "uncertain", updatedAt: now() };
|
|
9919
8628
|
delete next.lease;
|
|
@@ -10068,7 +8777,7 @@ function createEndpoint(options) {
|
|
|
10068
8777
|
return endpoint;
|
|
10069
8778
|
}
|
|
10070
8779
|
__name(createEndpoint, "createEndpoint");
|
|
10071
|
-
async function matchingRecords(tx, prefix3, options,
|
|
8780
|
+
async function matchingRecords(tx, prefix3, options, matches) {
|
|
10072
8781
|
const limit = Math.max(1, Math.min(options.limit ?? 100, 1e3));
|
|
10073
8782
|
const result = [];
|
|
10074
8783
|
let after = options.after ? `${prefix3}${options.after}` : void 0;
|
|
@@ -10076,7 +8785,7 @@ async function matchingRecords(tx, prefix3, options, matches2) {
|
|
|
10076
8785
|
const batch = await tx.scan(prefix3, { after, limit: 100 });
|
|
10077
8786
|
for (const entry of batch) {
|
|
10078
8787
|
after = entry.key;
|
|
10079
|
-
if (
|
|
8788
|
+
if (matches(entry.value)) result.push(entry.value);
|
|
10080
8789
|
if (result.length === limit) return result;
|
|
10081
8790
|
}
|
|
10082
8791
|
if (batch.length < 100) return result;
|
|
@@ -10085,9 +8794,9 @@ async function matchingRecords(tx, prefix3, options, matches2) {
|
|
|
10085
8794
|
__name(matchingRecords, "matchingRecords");
|
|
10086
8795
|
|
|
10087
8796
|
// ../core/src/endpoint-client.ts
|
|
10088
|
-
import { createHash as
|
|
8797
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10089
8798
|
import { homedir } from "node:os";
|
|
10090
|
-
import { join as
|
|
8799
|
+
import { join as join2 } from "node:path";
|
|
10091
8800
|
var mediaPipelines = /* @__PURE__ */ new WeakMap();
|
|
10092
8801
|
function acquireFileMediaPipeline(endpoint, options, fetchImpl) {
|
|
10093
8802
|
const existing = mediaPipelines.get(endpoint);
|
|
@@ -10097,7 +8806,7 @@ function acquireFileMediaPipeline(endpoint, options, fetchImpl) {
|
|
|
10097
8806
|
if (options.endpoint && !options.mediaBytes && !options.mediaDirectory && !options.stateDirectory) {
|
|
10098
8807
|
throw new Error("An injected Canon cloud endpoint requires durable mediaBytes or a persistent mediaDirectory.");
|
|
10099
8808
|
}
|
|
10100
|
-
const directory = options.mediaDirectory ??
|
|
8809
|
+
const directory = options.mediaDirectory ?? join2(options.stateDirectory ?? process.env.CANON_ENDPOINT_STATE_DIR ?? join2(process.env.CANON_HOME || join2(homedir(), ".canon"), "endpoint"), "media", identity.installationId);
|
|
10101
8810
|
const pipeline3 = endpoint.createMediaPipeline(options.mediaBytes ?? createNodeEndpointMediaBytes({ directory, fetch: fetchImpl }));
|
|
10102
8811
|
if (options.autoDrain !== false) void pipeline3.resumePending();
|
|
10103
8812
|
return pipeline3;
|
|
@@ -10119,7 +8828,7 @@ async function acquireAgentEndpoint(input) {
|
|
|
10119
8828
|
}
|
|
10120
8829
|
const requestedMls = input.options.mlsDev;
|
|
10121
8830
|
const mls = requestedMls ? await (async () => {
|
|
10122
|
-
const crypto2 = await import("./mls-crypto-
|
|
8831
|
+
const crypto2 = await import("./mls-crypto-4DZT2UFH.js");
|
|
10123
8832
|
const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
|
|
10124
8833
|
if (configuration.environmentId !== environmentId || baseUrl !== CANON_RUNTIME_ENDPOINT_DEFAULTS[configuration.environmentId]?.apiBaseUrl || configuration.principalIds && !configuration.principalIds.includes(input.principalId) || !Number.isSafeInteger(input.ownershipVersion) || input.ownershipVersion < 0) throw new Error("MLS_DEV_AGENT_CONFIGURATION_BINDING_MISMATCH");
|
|
10125
8834
|
const custody = requestedMls.custody;
|
|
@@ -10148,8 +8857,8 @@ async function acquireAgentEndpoint(input) {
|
|
|
10148
8857
|
}
|
|
10149
8858
|
const streamUrl = input.options.streamUrl ?? Object.entries(CANON_RUNTIME_ENDPOINT_DEFAULTS).find(([id2]) => id2 === environmentId)?.[1]?.streamUrl;
|
|
10150
8859
|
if (!streamUrl) throw new Error("Canon endpoint requires a streamUrl for this environment.");
|
|
10151
|
-
const directory = input.options.stateDirectory ?? process.env.CANON_ENDPOINT_STATE_DIR ??
|
|
10152
|
-
const namespace =
|
|
8860
|
+
const directory = input.options.stateDirectory ?? process.env.CANON_ENDPOINT_STATE_DIR ?? join2(process.env.CANON_HOME || join2(homedir(), ".canon"), "endpoint");
|
|
8861
|
+
const namespace = createHash2("sha256").update(`${environmentId}
|
|
10153
8862
|
${baseUrl}
|
|
10154
8863
|
${input.principalId}
|
|
10155
8864
|
${input.ownershipVersion}`).digest("hex");
|
|
@@ -10891,7 +9600,7 @@ function asCanonApiError(error) {
|
|
|
10891
9600
|
}
|
|
10892
9601
|
__name(asCanonApiError, "asCanonApiError");
|
|
10893
9602
|
function stableOperationId(...parts) {
|
|
10894
|
-
return
|
|
9603
|
+
return createHash3("sha256").update(JSON.stringify(parts)).digest("hex");
|
|
10895
9604
|
}
|
|
10896
9605
|
__name(stableOperationId, "stableOperationId");
|
|
10897
9606
|
function normalizeMessage(raw) {
|
|
@@ -11151,14 +9860,14 @@ async function resumeRegistration(options) {
|
|
|
11151
9860
|
if (status.status === "rejected") return { status: "rejected", requestId };
|
|
11152
9861
|
const remaining = deadline - Date.now();
|
|
11153
9862
|
if (remaining <= 0) return { status: "pending", requestId };
|
|
11154
|
-
await new Promise((
|
|
9863
|
+
await new Promise((resolve3, reject) => {
|
|
11155
9864
|
const abort = /* @__PURE__ */ __name(() => {
|
|
11156
9865
|
clearTimeout(timer);
|
|
11157
9866
|
reject(signal.reason);
|
|
11158
9867
|
}, "abort");
|
|
11159
9868
|
const timer = setTimeout(() => {
|
|
11160
9869
|
signal?.removeEventListener("abort", abort);
|
|
11161
|
-
|
|
9870
|
+
resolve3();
|
|
11162
9871
|
}, Math.min(pollIntervalMs, remaining));
|
|
11163
9872
|
signal?.addEventListener("abort", abort, { once: true });
|
|
11164
9873
|
if (signal?.aborted) abort();
|
|
@@ -11171,7 +9880,7 @@ __name(resumeRegistration, "resumeRegistration");
|
|
|
11171
9880
|
import { existsSync, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync } from "node:fs";
|
|
11172
9881
|
import { closeSync as closeSync2, openSync as openSync2 } from "node:fs";
|
|
11173
9882
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
11174
|
-
import { join as
|
|
9883
|
+
import { join as join3, resolve } from "node:path";
|
|
11175
9884
|
import { homedir as homedir2 } from "node:os";
|
|
11176
9885
|
|
|
11177
9886
|
// ../core/src/process-lock.ts
|
|
@@ -11212,15 +9921,15 @@ function claimStaleLockReaper(lockPath) {
|
|
|
11212
9921
|
__name(claimStaleLockReaper, "claimStaleLockReaper");
|
|
11213
9922
|
|
|
11214
9923
|
// ../core/src/agent-profiles.ts
|
|
11215
|
-
var CANON_DIR = process.env.CANON_HOME ?
|
|
11216
|
-
var AGENTS_PATH =
|
|
11217
|
-
var LOCKS_DIR =
|
|
11218
|
-
var PENDING_REGISTRATIONS_PATH =
|
|
9924
|
+
var CANON_DIR = process.env.CANON_HOME ? resolve(process.env.CANON_HOME) : join3(homedir2(), ".canon");
|
|
9925
|
+
var AGENTS_PATH = join3(CANON_DIR, "agents.json");
|
|
9926
|
+
var LOCKS_DIR = join3(CANON_DIR, "locks");
|
|
9927
|
+
var PENDING_REGISTRATIONS_PATH = join3(CANON_DIR, "pending-registrations.json");
|
|
11219
9928
|
var AGENTS_JSON_BOOTSTRAP_ENV = "CANON_AGENTS_JSON_BOOTSTRAP";
|
|
11220
9929
|
function canonPaths(home) {
|
|
11221
9930
|
if (!home) return { dir: CANON_DIR, agents: AGENTS_PATH, locks: LOCKS_DIR };
|
|
11222
|
-
const dir =
|
|
11223
|
-
return { dir, agents:
|
|
9931
|
+
const dir = resolve(home);
|
|
9932
|
+
return { dir, agents: join3(dir, "agents.json"), locks: join3(dir, "locks") };
|
|
11224
9933
|
}
|
|
11225
9934
|
__name(canonPaths, "canonPaths");
|
|
11226
9935
|
function normalizeAgentProfile(name, value, source) {
|
|
@@ -11387,7 +10096,7 @@ function sameProcess(owner) {
|
|
|
11387
10096
|
}
|
|
11388
10097
|
__name(sameProcess, "sameProcess");
|
|
11389
10098
|
function isProfileLocked(profile, home) {
|
|
11390
|
-
const lockPath =
|
|
10099
|
+
const lockPath = join3(canonPaths(home).locks, `${profile}.lock`);
|
|
11391
10100
|
try {
|
|
11392
10101
|
const lock = parseLock(readFileSync2(lockPath, "utf-8"));
|
|
11393
10102
|
const pid = lock?.pid;
|
|
@@ -11412,7 +10121,7 @@ __name(parseLock, "parseLock");
|
|
|
11412
10121
|
function acquireLock(profile, home) {
|
|
11413
10122
|
const locks = canonPaths(home).locks;
|
|
11414
10123
|
mkdirSync(locks, { recursive: true });
|
|
11415
|
-
const lockPath =
|
|
10124
|
+
const lockPath = join3(locks, `${profile}.lock`);
|
|
11416
10125
|
const token = randomUUID5();
|
|
11417
10126
|
const payload = JSON.stringify({
|
|
11418
10127
|
pid: process.pid,
|
|
@@ -11464,7 +10173,7 @@ function acquireLock(profile, home) {
|
|
|
11464
10173
|
}
|
|
11465
10174
|
__name(acquireLock, "acquireLock");
|
|
11466
10175
|
function releaseLock(profile, token, home) {
|
|
11467
|
-
const lockPath =
|
|
10176
|
+
const lockPath = join3(canonPaths(home).locks, `${profile}.lock`);
|
|
11468
10177
|
if (token) {
|
|
11469
10178
|
try {
|
|
11470
10179
|
const lock = parseLock(readFileSync2(lockPath, "utf-8"));
|
|
@@ -11761,7 +10470,7 @@ var RuntimeRequestManager = class {
|
|
|
11761
10470
|
}
|
|
11762
10471
|
const key = pendingKey(kind, conversationId, effectiveId);
|
|
11763
10472
|
const signal = options.signal;
|
|
11764
|
-
return await new Promise((
|
|
10473
|
+
return await new Promise((resolve3, reject) => {
|
|
11765
10474
|
let settled = false;
|
|
11766
10475
|
const finishCommon = /* @__PURE__ */ __name(() => {
|
|
11767
10476
|
settled = true;
|
|
@@ -11787,7 +10496,7 @@ var RuntimeRequestManager = class {
|
|
|
11787
10496
|
} catch {
|
|
11788
10497
|
}
|
|
11789
10498
|
}
|
|
11790
|
-
|
|
10499
|
+
resolve3(finalResult);
|
|
11791
10500
|
})();
|
|
11792
10501
|
}, "settle");
|
|
11793
10502
|
const abort = /* @__PURE__ */ __name((error) => {
|
|
@@ -11921,7 +10630,7 @@ var RuntimeRequestManager = class {
|
|
|
11921
10630
|
pending.wakeRequested = false;
|
|
11922
10631
|
return Promise.resolve();
|
|
11923
10632
|
}
|
|
11924
|
-
return new Promise((
|
|
10633
|
+
return new Promise((resolve3) => {
|
|
11925
10634
|
let done = false;
|
|
11926
10635
|
const finish = /* @__PURE__ */ __name(() => {
|
|
11927
10636
|
if (done) return;
|
|
@@ -11933,7 +10642,7 @@ var RuntimeRequestManager = class {
|
|
|
11933
10642
|
current.wake = () => {
|
|
11934
10643
|
};
|
|
11935
10644
|
}
|
|
11936
|
-
|
|
10645
|
+
resolve3();
|
|
11937
10646
|
}, "finish");
|
|
11938
10647
|
const timer = setTimeout(finish, ms);
|
|
11939
10648
|
const entry = this.pending.get(key);
|
|
@@ -12710,11 +11419,11 @@ function buildRuntimeActivityItemPath(conversationId, agentId, itemId) {
|
|
|
12710
11419
|
return `${buildRuntimeActivityPath(conversationId, agentId)}/${runtimeActivityItemKey(itemId)}`;
|
|
12711
11420
|
}
|
|
12712
11421
|
__name(buildRuntimeActivityItemPath, "buildRuntimeActivityItemPath");
|
|
12713
|
-
function
|
|
11422
|
+
function record5(value) {
|
|
12714
11423
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected runtime projection object");
|
|
12715
11424
|
return value;
|
|
12716
11425
|
}
|
|
12717
|
-
__name(
|
|
11426
|
+
__name(record5, "record");
|
|
12718
11427
|
var PROJECTIONS = /* @__PURE__ */ new Set(["agent-session", "turn-state", "runtime-info", "runtime-activity", "streaming"]);
|
|
12719
11428
|
function createRTDBClientHandle(client) {
|
|
12720
11429
|
async function scope5(path) {
|
|
@@ -12741,40 +11450,40 @@ function createRTDBClientHandle(client) {
|
|
|
12741
11450
|
const target = await scope5(path);
|
|
12742
11451
|
const clear = method === "remove" || data === null;
|
|
12743
11452
|
if (target.family === "agent-runtime") {
|
|
12744
|
-
if (clear ||
|
|
12745
|
-
const value =
|
|
11453
|
+
if (clear || record5(data).updatedAt === 0) return client.updateRuntimeStatus({ online: false });
|
|
11454
|
+
const value = record5(data);
|
|
12746
11455
|
if (typeof value.clientType !== "string" || !value.clientType) throw new Error("Missing runtime client type");
|
|
12747
11456
|
return client.updateRuntimeStatus({
|
|
12748
11457
|
runtime: value.clientType,
|
|
12749
11458
|
...typeof value.hostMode === "boolean" ? { hostMode: value.hostMode } : {},
|
|
12750
|
-
...value.runtimeDescriptor ? { runtimeDescriptor:
|
|
11459
|
+
...value.runtimeDescriptor ? { runtimeDescriptor: record5(value.runtimeDescriptor) } : {}
|
|
12751
11460
|
});
|
|
12752
11461
|
}
|
|
12753
11462
|
if (target.family === "agent-session") {
|
|
12754
11463
|
if (clear) return client.publishRuntimeSession(target.conversationId, "clear");
|
|
12755
|
-
const state = sanitizeAgentSessionSnapshotPatch(
|
|
11464
|
+
const state = sanitizeAgentSessionSnapshotPatch(record5(data));
|
|
12756
11465
|
return client.publishRuntimeSession(target.conversationId, "patch", state);
|
|
12757
11466
|
}
|
|
12758
11467
|
if (target.family === "runtime-info") {
|
|
12759
11468
|
if (clear) return client.publishRuntimeInfo(target.conversationId, "clear");
|
|
12760
|
-
const { updatedAt: _updatedAt, ...info } =
|
|
11469
|
+
const { updatedAt: _updatedAt, ...info } = record5(data);
|
|
12761
11470
|
return client.publishRuntimeInfo(target.conversationId, method === "patch" ? "patch" : "replace", info);
|
|
12762
11471
|
}
|
|
12763
11472
|
if (target.family === "runtime-activity") {
|
|
12764
11473
|
if (clear) return client.publishRuntimeActivity(target.conversationId, target.itemId ? { mode: "remove", itemId: target.itemId } : { mode: "clear" });
|
|
12765
11474
|
if (!target.itemId) throw new Error("Write one runtime activity item");
|
|
12766
|
-
const item =
|
|
11475
|
+
const item = record5(data);
|
|
12767
11476
|
if (typeof item.id !== "string" || runtimeActivityItemKey(item.id) !== target.itemId) throw new Error("Runtime activity identity mismatch");
|
|
12768
11477
|
return client.publishRuntimeActivity(target.conversationId, { mode: "upsert", item });
|
|
12769
11478
|
}
|
|
12770
11479
|
if (target.family === "streaming") {
|
|
12771
11480
|
if (clear) return client.clearStreaming(target.conversationId);
|
|
12772
|
-
const { updatedAt: _updatedAt, startedAt: _startedAt, ...patch2 } =
|
|
11481
|
+
const { updatedAt: _updatedAt, startedAt: _startedAt, ...patch2 } = record5(data);
|
|
12773
11482
|
if (Object.keys(patch2).some((key) => !["text", "status", "messageId", "turnId", "blocks"].includes(key))) throw new Error("Unsupported streaming projection field");
|
|
12774
11483
|
return client.patchRuntimeStreaming(target.conversationId, patch2);
|
|
12775
11484
|
}
|
|
12776
11485
|
if (target.family === "turn-state") {
|
|
12777
|
-
const value = clear ? { state: "idle", queueDepth: 0, turnId: null, openedAt: null, turnUpdatedAt: null } :
|
|
11486
|
+
const value = clear ? { state: "idle", queueDepth: 0, turnId: null, openedAt: null, turnUpdatedAt: null } : record5(data);
|
|
12778
11487
|
const { updatedAt: _updatedAt, completedAt: _completedAt, openedAt, turnUpdatedAt, ...turn } = value;
|
|
12779
11488
|
return client.setRuntimeTurn({
|
|
12780
11489
|
...turn,
|
|
@@ -13159,12 +11868,12 @@ __name(applyFenceLine, "applyFenceLine");
|
|
|
13159
11868
|
function scanFenceState(text, fence) {
|
|
13160
11869
|
if (!text) return fence;
|
|
13161
11870
|
const lines = text.split("\n");
|
|
13162
|
-
let
|
|
11871
|
+
let open3 = fence;
|
|
13163
11872
|
for (let index = 0; index < lines.length; index += 1) {
|
|
13164
11873
|
if (index === lines.length - 1 && lines[index] === "") continue;
|
|
13165
|
-
|
|
11874
|
+
open3 = applyFenceLine(open3, lines[index]);
|
|
13166
11875
|
}
|
|
13167
|
-
return
|
|
11876
|
+
return open3;
|
|
13168
11877
|
}
|
|
13169
11878
|
__name(scanFenceState, "scanFenceState");
|
|
13170
11879
|
function codePointUtf8Bytes(codePoint) {
|
|
@@ -13375,12 +12084,12 @@ __name(sendMessageWithRetryChunked, "sendMessageWithRetryChunked");
|
|
|
13375
12084
|
|
|
13376
12085
|
// ../core/src/cli-metadata.ts
|
|
13377
12086
|
import { readFileSync as readFileSync3, realpathSync } from "node:fs";
|
|
13378
|
-
import { dirname
|
|
12087
|
+
import { dirname, resolve as resolve2 } from "node:path";
|
|
13379
12088
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13380
12089
|
function isDirectExecution(moduleUrl) {
|
|
13381
12090
|
const entry = process.argv[1];
|
|
13382
12091
|
if (!entry) return false;
|
|
13383
|
-
const abs =
|
|
12092
|
+
const abs = resolve2(entry);
|
|
13384
12093
|
let real = abs;
|
|
13385
12094
|
try {
|
|
13386
12095
|
real = realpathSync(abs);
|
|
@@ -13390,13 +12099,13 @@ function isDirectExecution(moduleUrl) {
|
|
|
13390
12099
|
}
|
|
13391
12100
|
__name(isDirectExecution, "isDirectExecution");
|
|
13392
12101
|
function readCliPackageVersion(moduleUrl) {
|
|
13393
|
-
let current =
|
|
12102
|
+
let current = dirname(fileURLToPath(moduleUrl));
|
|
13394
12103
|
while (true) {
|
|
13395
12104
|
try {
|
|
13396
|
-
const manifest = JSON.parse(readFileSync3(
|
|
12105
|
+
const manifest = JSON.parse(readFileSync3(resolve2(current, "package.json"), "utf8"));
|
|
13397
12106
|
return typeof manifest.version === "string" ? manifest.version : null;
|
|
13398
12107
|
} catch {
|
|
13399
|
-
const parent =
|
|
12108
|
+
const parent = dirname(current);
|
|
13400
12109
|
if (parent === current) return null;
|
|
13401
12110
|
current = parent;
|
|
13402
12111
|
}
|
|
@@ -13435,7 +12144,7 @@ function runCli(moduleUrl, main, onError, metadata) {
|
|
|
13435
12144
|
__name(runCli, "runCli");
|
|
13436
12145
|
|
|
13437
12146
|
// ../core/src/completed-output-journal.ts
|
|
13438
|
-
import { createHash as
|
|
12147
|
+
import { createHash as createHash4, randomUUID as randomUUID7 } from "node:crypto";
|
|
13439
12148
|
import {
|
|
13440
12149
|
closeSync as closeSync3,
|
|
13441
12150
|
existsSync as existsSync2,
|
|
@@ -13448,12 +12157,13 @@ import {
|
|
|
13448
12157
|
unlinkSync as unlinkSync3,
|
|
13449
12158
|
writeFileSync as writeFileSync3
|
|
13450
12159
|
} from "node:fs";
|
|
13451
|
-
import { join as
|
|
12160
|
+
import { join as join4 } from "node:path";
|
|
13452
12161
|
function createCompletedOutputJournal(directory, scope5, options) {
|
|
13453
12162
|
const label = options.label;
|
|
13454
|
-
const folder =
|
|
13455
|
-
const filename = /* @__PURE__ */ __name((turnId) =>
|
|
12163
|
+
const folder = join4(directory, createHash4("sha256").update(JSON.stringify(scope5)).digest("hex"));
|
|
12164
|
+
const filename = /* @__PURE__ */ __name((turnId) => join4(folder, `${createHash4("sha256").update(turnId).digest("hex")}.json`), "filename");
|
|
13456
12165
|
function syncDirectory() {
|
|
12166
|
+
if (process.platform === "win32") return;
|
|
13457
12167
|
const fd = openSync3(folder, "r");
|
|
13458
12168
|
try {
|
|
13459
12169
|
fsyncSync(fd);
|
|
@@ -13462,8 +12172,8 @@ function createCompletedOutputJournal(directory, scope5, options) {
|
|
|
13462
12172
|
}
|
|
13463
12173
|
}
|
|
13464
12174
|
__name(syncDirectory, "syncDirectory");
|
|
13465
|
-
function validate(
|
|
13466
|
-
if (
|
|
12175
|
+
function validate(record6) {
|
|
12176
|
+
if (record6?.version !== 1 || typeof record6.turnId !== "string" || !record6.turnId || typeof record6.conversationId !== "string" || !record6.conversationId || typeof record6.createdAt !== "string" || options.validate && !options.validate(record6)) {
|
|
13467
12177
|
throw new Error(`Invalid completed ${label} output record; operator reconciliation is required`);
|
|
13468
12178
|
}
|
|
13469
12179
|
}
|
|
@@ -13478,21 +12188,21 @@ function createCompletedOutputJournal(directory, scope5, options) {
|
|
|
13478
12188
|
} catch (error) {
|
|
13479
12189
|
if (error.code !== "ESRCH") continue;
|
|
13480
12190
|
}
|
|
13481
|
-
const path =
|
|
13482
|
-
let
|
|
12191
|
+
const path = join4(folder, name);
|
|
12192
|
+
let record6;
|
|
13483
12193
|
try {
|
|
13484
|
-
|
|
13485
|
-
validate(
|
|
12194
|
+
record6 = JSON.parse(readFileSync4(path, "utf8"));
|
|
12195
|
+
validate(record6);
|
|
13486
12196
|
} catch {
|
|
13487
12197
|
unlinkSync3(path);
|
|
13488
12198
|
throw new Error(`Incomplete ${label} output write removed; reconcile its native transcript`);
|
|
13489
12199
|
}
|
|
13490
|
-
const destination = filename(
|
|
13491
|
-
if (destination !==
|
|
12200
|
+
const destination = filename(record6.turnId);
|
|
12201
|
+
if (destination !== join4(folder, `${match[1]}.json`)) {
|
|
13492
12202
|
throw new Error(`Completed ${label} output temporary file identity mismatch`);
|
|
13493
12203
|
}
|
|
13494
12204
|
if (existsSync2(destination)) {
|
|
13495
|
-
if (readFileSync4(destination, "utf8") !== JSON.stringify(
|
|
12205
|
+
if (readFileSync4(destination, "utf8") !== JSON.stringify(record6)) {
|
|
13496
12206
|
throw new Error(`Conflicting completed ${label} outputs require reconciliation`);
|
|
13497
12207
|
}
|
|
13498
12208
|
unlinkSync3(path);
|
|
@@ -13502,11 +12212,11 @@ function createCompletedOutputJournal(directory, scope5, options) {
|
|
|
13502
12212
|
}
|
|
13503
12213
|
__name(recoverTemporaryFiles, "recoverTemporaryFiles");
|
|
13504
12214
|
return {
|
|
13505
|
-
save(
|
|
13506
|
-
validate(
|
|
12215
|
+
save(record6) {
|
|
12216
|
+
validate(record6);
|
|
13507
12217
|
mkdirSync2(folder, { recursive: true, mode: 448 });
|
|
13508
|
-
const path = filename(
|
|
13509
|
-
const serialized = JSON.stringify(
|
|
12218
|
+
const path = filename(record6.turnId);
|
|
12219
|
+
const serialized = JSON.stringify(record6);
|
|
13510
12220
|
if (existsSync2(path)) {
|
|
13511
12221
|
if (readFileSync4(path, "utf8") !== serialized) throw new Error(`Completed ${label} output cannot be replaced`);
|
|
13512
12222
|
return;
|
|
@@ -13534,10 +12244,10 @@ function createCompletedOutputJournal(directory, scope5, options) {
|
|
|
13534
12244
|
if (!existsSync2(folder)) return [];
|
|
13535
12245
|
recoverTemporaryFiles();
|
|
13536
12246
|
return readdirSync(folder).filter((name) => name.endsWith(".json")).map((name) => {
|
|
13537
|
-
const
|
|
13538
|
-
validate(
|
|
13539
|
-
if (filename(
|
|
13540
|
-
return
|
|
12247
|
+
const record6 = JSON.parse(readFileSync4(join4(folder, name), "utf8"));
|
|
12248
|
+
validate(record6);
|
|
12249
|
+
if (filename(record6.turnId) !== join4(folder, name)) throw new Error(`Completed ${label} output identity mismatch`);
|
|
12250
|
+
return record6;
|
|
13541
12251
|
}).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
13542
12252
|
},
|
|
13543
12253
|
remove(turnId) {
|
|
@@ -13551,8 +12261,8 @@ function createCompletedOutputJournal(directory, scope5, options) {
|
|
|
13551
12261
|
};
|
|
13552
12262
|
}
|
|
13553
12263
|
__name(createCompletedOutputJournal, "createCompletedOutputJournal");
|
|
13554
|
-
function assertCompletedOutputAudience(
|
|
13555
|
-
if (!
|
|
12264
|
+
function assertCompletedOutputAudience(record6, current, label) {
|
|
12265
|
+
if (!record6.audience || !current || record6.audience.membershipRevision !== void 0 && record6.audience.membershipRevision !== current.membershipRevision || JSON.stringify([...record6.audience.memberIds].sort()) !== JSON.stringify([...current.memberIds].sort())) {
|
|
13556
12266
|
throw new Error(`Completed ${label} output audience changed; operator reconciliation is required`);
|
|
13557
12267
|
}
|
|
13558
12268
|
}
|
|
@@ -13560,9 +12270,9 @@ __name(assertCompletedOutputAudience, "assertCompletedOutputAudience");
|
|
|
13560
12270
|
|
|
13561
12271
|
// src/bounded-file-read.ts
|
|
13562
12272
|
var BoundedFileReadError = class extends Error {
|
|
13563
|
-
constructor(
|
|
13564
|
-
super(
|
|
13565
|
-
this.failure =
|
|
12273
|
+
constructor(failure) {
|
|
12274
|
+
super(failure);
|
|
12275
|
+
this.failure = failure;
|
|
13566
12276
|
this.name = "BoundedFileReadError";
|
|
13567
12277
|
}
|
|
13568
12278
|
failure;
|
|
@@ -13601,7 +12311,7 @@ var ANTHROPIC_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
|
13601
12311
|
"image/gif",
|
|
13602
12312
|
"image/webp"
|
|
13603
12313
|
]);
|
|
13604
|
-
var DEFAULT_MEDIA_CACHE_DIR =
|
|
12314
|
+
var DEFAULT_MEDIA_CACHE_DIR = join5(CANON_DIR, "media-cache");
|
|
13605
12315
|
var DEFAULT_MEDIA_MATERIALIZATION_BYTES = 10 * 1024 * 1024;
|
|
13606
12316
|
var MAX_CANON_MEDIA_BYTES = 100 * 1024 * 1024;
|
|
13607
12317
|
var MAX_ANTHROPIC_IMAGE_RAW_BYTES = 7 * 1024 * 1024;
|
|
@@ -13669,7 +12379,7 @@ function inferExtension(input) {
|
|
|
13669
12379
|
}
|
|
13670
12380
|
__name(inferExtension, "inferExtension");
|
|
13671
12381
|
function conversationMediaCacheDir(input) {
|
|
13672
|
-
return
|
|
12382
|
+
return join5(
|
|
13673
12383
|
input.rootDir ?? DEFAULT_MEDIA_CACHE_DIR,
|
|
13674
12384
|
sanitizeSegment(input.agentId, "agent"),
|
|
13675
12385
|
sanitizeSegment(input.conversationId, "conversation")
|
|
@@ -13685,7 +12395,7 @@ function buildCachePath(input) {
|
|
|
13685
12395
|
const fallbackName = `${input.attachment.kind}-${input.index}${extension}`;
|
|
13686
12396
|
const preferredName = input.attachment.fileName ? sanitizeDownloadFileName(input.attachment.fileName, fallbackName) : fallbackName;
|
|
13687
12397
|
const fileName = extname(preferredName) ? preferredName : `${preferredName}${extension}`;
|
|
13688
|
-
return
|
|
12398
|
+
return join5(
|
|
13689
12399
|
conversationMediaCacheDir({ agentId: input.agentId, conversationId: input.conversationId, rootDir }),
|
|
13690
12400
|
sanitizeSegment(input.messageId, "message"),
|
|
13691
12401
|
`${String(input.index).padStart(2, "0")}-${fileName}`
|
|
@@ -13798,7 +12508,7 @@ async function materializeAttachment(attachment, options) {
|
|
|
13798
12508
|
index: options.index ?? 0,
|
|
13799
12509
|
rootDir: options.rootDir
|
|
13800
12510
|
});
|
|
13801
|
-
await
|
|
12511
|
+
await mkdir2(dirname2(path), { recursive: true });
|
|
13802
12512
|
const maxBytes = resolveMaxBytes(options.maxBytes);
|
|
13803
12513
|
const expectedBytes = typeof attachment.sizeBytes === "number" && Number.isSafeInteger(attachment.sizeBytes) && attachment.sizeBytes >= 0 ? attachment.sizeBytes : null;
|
|
13804
12514
|
if (expectedBytes !== null && expectedBytes > maxBytes) {
|
|
@@ -14014,7 +12724,7 @@ async function toAnthropicImageBlock(attachment, options) {
|
|
|
14014
12724
|
}
|
|
14015
12725
|
__name(toAnthropicImageBlock, "toAnthropicImageBlock");
|
|
14016
12726
|
async function readAnthropicImageBytes(attachment, maxRawBytes) {
|
|
14017
|
-
const handle = await
|
|
12727
|
+
const handle = await open2(attachment.path, "r");
|
|
14018
12728
|
try {
|
|
14019
12729
|
return await readBoundedRegularFileHandle(handle, maxRawBytes);
|
|
14020
12730
|
} catch (error) {
|
|
@@ -14120,9 +12830,6 @@ function getCodexImagePath(attachment) {
|
|
|
14120
12830
|
__name(getCodexImagePath, "getCodexImagePath");
|
|
14121
12831
|
|
|
14122
12832
|
export {
|
|
14123
|
-
NodeMlsDevCustodyError,
|
|
14124
|
-
createNodeMlsDevCustody,
|
|
14125
|
-
createPortableNodeMlsDevCustody,
|
|
14126
12833
|
resolveCanonRuntimeConnection,
|
|
14127
12834
|
verifyCanonRuntimeConnection,
|
|
14128
12835
|
CanonClient,
|