@canonmsg/agent-sdk 12.5.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 +34 -3
- package/dist/chunks/{chunk-TFSURFF6.js → chunk-5J4YPLNW.js} +349 -1684
- package/dist/chunks/{chunk-3KFVBOVI.js → chunk-DAJ7L7TN.js} +751 -371
- 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-C2WJT3VX.js → chunk-S7BV74DN.js} +719 -813
- package/dist/chunks/{mls-crypto-Y44BOSEM.js → mls-crypto-4DZT2UFH.js} +2 -1
- package/dist/cli.js +67 -25
- package/dist/drivers/acp.d.ts +56 -0
- package/dist/drivers/codex.d.ts +1 -2
- 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/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 +3 -0
- package/dist/internal/endpoint/mls-dev-client.d.ts +2 -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 +6 -3
- package/dist/runner/types.d.ts +6 -1
- 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) => {
|
|
@@ -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);
|
|
@@ -5525,6 +4150,12 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5525
4150
|
return device.acceptRevocation(receipt);
|
|
5526
4151
|
}
|
|
5527
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");
|
|
5528
4159
|
function run(goal) {
|
|
5529
4160
|
context.assertOpen();
|
|
5530
4161
|
const preceding = running;
|
|
@@ -5570,7 +4201,8 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5570
4201
|
renew: /* @__PURE__ */ __name(() => run("renew"), "renew"),
|
|
5571
4202
|
reconcile: /* @__PURE__ */ __name(() => run("reconcile"), "reconcile"),
|
|
5572
4203
|
ensureReady: /* @__PURE__ */ __name(() => run("ready"), "ensureReady"),
|
|
5573
|
-
revoke: /* @__PURE__ */ __name(() => context.run(revoke), "revoke")
|
|
4204
|
+
revoke: /* @__PURE__ */ __name(() => context.run(revoke), "revoke"),
|
|
4205
|
+
revokeOther: /* @__PURE__ */ __name((deviceId) => context.run(() => revokeOther(deviceId)), "revokeOther")
|
|
5574
4206
|
});
|
|
5575
4207
|
}
|
|
5576
4208
|
__name(createMlsDevDeviceClient, "createMlsDevDeviceClient");
|
|
@@ -5581,20 +4213,20 @@ function fail3(code = "MLS_DEV_KEY_PACKAGE_STATE_INVALID", retryable = false) {
|
|
|
5581
4213
|
throw new EndpointError(code, { code, retryable });
|
|
5582
4214
|
}
|
|
5583
4215
|
__name(fail3, "fail");
|
|
5584
|
-
function
|
|
4216
|
+
function record2(value, keys) {
|
|
5585
4217
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
|
|
5586
4218
|
const fields = Object.getOwnPropertyDescriptors(value);
|
|
5587
4219
|
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((key) => !Object.hasOwn(fields, key) || !Object.hasOwn(fields[key], "value") || !fields[key].enumerable)) fail3();
|
|
5588
4220
|
return Object.fromEntries(keys.map((key) => [key, fields[key].value]));
|
|
5589
4221
|
}
|
|
5590
|
-
__name(
|
|
4222
|
+
__name(record2, "record");
|
|
5591
4223
|
function createMlsDevKeyPackageService(options, context, store, initialized, readPrepared) {
|
|
5592
4224
|
const verifyCredential = options.verifyCredential;
|
|
5593
4225
|
const fence = /* @__PURE__ */ __name(() => store.transaction(async () => {
|
|
5594
4226
|
}), "fence");
|
|
5595
4227
|
const network = /* @__PURE__ */ __name(() => context.transport?.keyPackages ?? fail3("MLS_DEV_KEY_PACKAGE_TRANSPORT_REQUIRED"), "network");
|
|
5596
4228
|
function parseJournal(raw, binding, current, op) {
|
|
5597
|
-
const value =
|
|
4229
|
+
const value = record2(raw, ["version", "binding", "registration", "request", "receipt"]);
|
|
5598
4230
|
const registration = parseMlsDevEnrollmentResult(value.registration);
|
|
5599
4231
|
if (value.version !== 1 || !same3(parseMlsDevDeviceBinding(value.binding), binding) || !same3(registration, current.registration)) fail3("MLS_DEV_KEY_PACKAGE_BINDING_MISMATCH");
|
|
5600
4232
|
return {
|
|
@@ -5700,7 +4332,7 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
5700
4332
|
equivalent: /* @__PURE__ */ __name((a, b) => same3({ ...a, claimantCredential: null }, { ...b, claimantCredential: null }), "equivalent"),
|
|
5701
4333
|
forgetsRefusal: true,
|
|
5702
4334
|
async make(tx, _binding, current) {
|
|
5703
|
-
const raw = input ??
|
|
4335
|
+
const raw = input ?? record2(
|
|
5704
4336
|
(await tx.get("mls-dev-key-package/claim/" + id2))?.request,
|
|
5705
4337
|
["version", "requestId", "claimantCredential", "recipientDeviceId", "preconditions"]
|
|
5706
4338
|
);
|
|
@@ -5732,11 +4364,11 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
5732
4364
|
__name(run, "run");
|
|
5733
4365
|
return {
|
|
5734
4366
|
publish(input) {
|
|
5735
|
-
const id2 = parseMlsDevKeyPackageId(
|
|
4367
|
+
const id2 = parseMlsDevKeyPackageId(record2(input, ["keyPackageId"]).keyPackageId);
|
|
5736
4368
|
return run(async () => Object.freeze({ status: "published", receipt: await exchange(id2, true, publication(id2)) ?? fail3() }));
|
|
5737
4369
|
},
|
|
5738
4370
|
claim(input) {
|
|
5739
|
-
const data =
|
|
4371
|
+
const data = record2(input, ["requestId", "recipientDeviceId", "preconditions"]);
|
|
5740
4372
|
const frozen = Object.freeze({
|
|
5741
4373
|
requestId: parseMlsDevKeyPackageId(data.requestId),
|
|
5742
4374
|
recipientDeviceId: parseMlsDevKeyPackageId(data.recipientDeviceId),
|
|
@@ -5813,20 +4445,20 @@ function fail4(code = "MLS_DEV_KEY_PACKAGE_STATE_INVALID", retryable = false) {
|
|
|
5813
4445
|
}
|
|
5814
4446
|
__name(fail4, "fail");
|
|
5815
4447
|
var same4 = /* @__PURE__ */ __name((a, b) => canonicalEndpointJson(a) === canonicalEndpointJson(b), "same");
|
|
5816
|
-
function
|
|
4448
|
+
function record3(value, keys) {
|
|
5817
4449
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail4();
|
|
5818
4450
|
const fields = Object.getOwnPropertyDescriptors(value);
|
|
5819
4451
|
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((key) => !Object.hasOwn(fields, key) || !Object.hasOwn(fields[key], "value") || !fields[key].enumerable)) fail4();
|
|
5820
4452
|
return Object.fromEntries(keys.map((key) => [key, fields[key].value]));
|
|
5821
4453
|
}
|
|
5822
|
-
__name(
|
|
4454
|
+
__name(record3, "record");
|
|
5823
4455
|
function boundPayload(command, payload) {
|
|
5824
4456
|
const claims = parseMlsDevCredentialToken(command.credential).unsigned.claims;
|
|
5825
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");
|
|
5826
4458
|
}
|
|
5827
4459
|
__name(boundPayload, "boundPayload");
|
|
5828
4460
|
function intent(value, binding, id2) {
|
|
5829
|
-
const data =
|
|
4461
|
+
const data = record3(value, ["version", "binding", "command", "payload"]);
|
|
5830
4462
|
const command = parseMlsDevKeyPackageCommand(data.command), payload = parseMlsDevKeyPackagePayload(data.payload);
|
|
5831
4463
|
if (data.version !== 1 || !same4(parseMlsDevDeviceBinding(data.binding), binding) || command.keyPackageId !== id2) fail4();
|
|
5832
4464
|
const device = parseMlsDevCredentialToken(command.credential).unsigned.claims.device;
|
|
@@ -5845,7 +4477,7 @@ async function captureMlsDevPreparedKeyPackage(tx, binding, id2, current) {
|
|
|
5845
4477
|
const stored = intent(raw, binding, id2);
|
|
5846
4478
|
await assertMlsDevKeyPackageUnconsumed(tx, id2, stored.payload.keyPackageRef);
|
|
5847
4479
|
if (!same4(parseMlsDevCredentialToken(stored.command.credential).unsigned.claims.device, current.registration.device)) fail4("MLS_DEV_KEY_PACKAGE_BINDING_MISMATCH");
|
|
5848
|
-
const state =
|
|
4480
|
+
const state = record3(snapshot.protocolState, ["version", "binding", "keyPackageId", "wrappedState", "payload"]);
|
|
5849
4481
|
const preparation = parseMlsDevKeyPackagePreparation({ wrappedState: state.wrappedState, payload: state.payload });
|
|
5850
4482
|
if (state.version !== 1 || !same4(parseMlsDevDeviceBinding(state.binding), binding) || state.keyPackageId !== id2 || !same4(preparation.payload, stored.payload)) fail4();
|
|
5851
4483
|
return {
|
|
@@ -5912,7 +4544,7 @@ function createMlsDevKeyPackageClient(options, context) {
|
|
|
5912
4544
|
if (historical) {
|
|
5913
4545
|
const consumed = await tx.get(`mls-dev-key-package/consumed/${id2}`);
|
|
5914
4546
|
if (consumed !== void 0) {
|
|
5915
|
-
const data =
|
|
4547
|
+
const data = record3(consumed, ["version", "binding", "payload", "conversationId", "groupId", "controlSequence"]);
|
|
5916
4548
|
const payload = parseMlsDevKeyPackagePayload(data.payload);
|
|
5917
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();
|
|
5918
4550
|
return payload;
|
|
@@ -6070,7 +4702,7 @@ function createMlsDevKeyPackageClient(options, context) {
|
|
|
6070
4702
|
return {
|
|
6071
4703
|
...service,
|
|
6072
4704
|
prepare(input) {
|
|
6073
|
-
const data =
|
|
4705
|
+
const data = record3(input, ["keyPackageId"]);
|
|
6074
4706
|
return run(parseMlsDevKeyPackageId(data.keyPackageId), true).then((result) => result ?? fail4());
|
|
6075
4707
|
},
|
|
6076
4708
|
read: /* @__PURE__ */ __name((id2) => run(id2, false), "read"),
|
|
@@ -6320,6 +4952,27 @@ function mlsDevReplyQuote(value, maximum) {
|
|
|
6320
4952
|
return quoted ? [...quoted.trim()].slice(0, maximum).join("") || void 0 : void 0;
|
|
6321
4953
|
}
|
|
6322
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");
|
|
6323
4976
|
function projectMlsDevTextMessage(value, account) {
|
|
6324
4977
|
const message = value.message;
|
|
6325
4978
|
if (message.kind === "item") return projectMlsDevItemMessage({ ...value, message }, account);
|
|
@@ -6435,7 +5088,7 @@ var MLS_ERASED_SENDER_ID = "erased_user";
|
|
|
6435
5088
|
var checked = /* @__PURE__ */ new Map();
|
|
6436
5089
|
var checkKey = /* @__PURE__ */ __name((environmentId, alias, opening) => JSON.stringify([environmentId, alias, opening.type, opening.accountId, opening.salt]), "checkKey");
|
|
6437
5090
|
var hexBytes = /* @__PURE__ */ __name((hex) => Uint8Array.from(hex.match(/../g) ?? [], (pair) => Number.parseInt(pair, 16)), "hexBytes");
|
|
6438
|
-
async function opensMlsAlias(environmentId, alias, opening,
|
|
5091
|
+
async function opensMlsAlias(environmentId, alias, opening, hash) {
|
|
6439
5092
|
let value;
|
|
6440
5093
|
try {
|
|
6441
5094
|
value = parseMlsPrincipalOpening(opening);
|
|
@@ -6446,7 +5099,7 @@ async function opensMlsAlias(environmentId, alias, opening, hash3) {
|
|
|
6446
5099
|
const key = checkKey(environmentId, alias, value);
|
|
6447
5100
|
let opens = checked.get(key);
|
|
6448
5101
|
if (opens === void 0) {
|
|
6449
|
-
const digest = await
|
|
5102
|
+
const digest = await hash(encodeMlsPrincipalAliasInput(environmentId, value));
|
|
6450
5103
|
opens = /^[0-9a-f]{64}$/.test(digest) && MLS_PRINCIPAL_ALIAS_PREFIX + encodeCanonicalMlsBase64Url(hexBytes(digest)) === alias;
|
|
6451
5104
|
if (checked.size >= 4096) checked.clear();
|
|
6452
5105
|
checked.set(key, opens);
|
|
@@ -6464,8 +5117,8 @@ function openings(room) {
|
|
|
6464
5117
|
}
|
|
6465
5118
|
}
|
|
6466
5119
|
__name(openings, "openings");
|
|
6467
|
-
async function checkMlsRoomOpenings(room, environmentId,
|
|
6468
|
-
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);
|
|
6469
5122
|
}
|
|
6470
5123
|
__name(checkMlsRoomOpenings, "checkMlsRoomOpenings");
|
|
6471
5124
|
function mlsRoomAccounts(room, environmentId) {
|
|
@@ -6481,8 +5134,8 @@ function mlsAccountOf(accounts, principal) {
|
|
|
6481
5134
|
}
|
|
6482
5135
|
__name(mlsAccountOf, "mlsAccountOf");
|
|
6483
5136
|
function mlsAliasOf(accounts, accountId, type, admitted) {
|
|
6484
|
-
const
|
|
6485
|
-
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;
|
|
6486
5139
|
}
|
|
6487
5140
|
__name(mlsAliasOf, "mlsAliasOf");
|
|
6488
5141
|
async function primeMlsRoomIdentities(input) {
|
|
@@ -6529,6 +5182,11 @@ async function roomRemoval(tx, conversationId) {
|
|
|
6529
5182
|
}
|
|
6530
5183
|
}
|
|
6531
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");
|
|
6532
5190
|
async function assertRoomOpen(tx, conversationId, roster) {
|
|
6533
5191
|
const removal = await roomRemoval(tx, conversationId);
|
|
6534
5192
|
if (removal && removal.status !== "pending") fail6("MLS_DEV_GROUP_CLOSED");
|
|
@@ -6545,19 +5203,19 @@ function id(value) {
|
|
|
6545
5203
|
return value;
|
|
6546
5204
|
}
|
|
6547
5205
|
__name(id, "id");
|
|
6548
|
-
function
|
|
5206
|
+
function record4(value, required, optional = []) {
|
|
6549
5207
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail6();
|
|
6550
5208
|
const descriptors = Object.getOwnPropertyDescriptors(value), fields = Reflect.ownKeys(descriptors);
|
|
6551
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();
|
|
6552
5210
|
return Object.fromEntries(fields.map((key) => [key, descriptors[key].value]));
|
|
6553
5211
|
}
|
|
6554
|
-
__name(
|
|
5212
|
+
__name(record4, "record");
|
|
6555
5213
|
function envelope(value, binding, conversationId) {
|
|
6556
5214
|
if (value === null) return null;
|
|
6557
|
-
const data =
|
|
5215
|
+
const data = record4(value, ["version", "binding", "conversationId", "groupId", "wrappedState", "head", "roster", "removed", "pending"], ["protocol"]);
|
|
6558
5216
|
if (data.version !== 1 || !same6(parseMlsDevDeviceBinding(data.binding), binding) || data.conversationId !== conversationId || typeof data.removed !== "boolean") fail6();
|
|
6559
5217
|
const head = data.head === null ? null : parseMlsDevGroupHead(data.head), roster = parseMlsDevGroupRoster(data.roster);
|
|
6560
|
-
const pending = data.pending === null ? null :
|
|
5218
|
+
const pending = data.pending === null ? null : record4(data.pending, ["operationId", "control"]);
|
|
6561
5219
|
const control = pending ? parseMlsDevGroupControl(pending.control) : null;
|
|
6562
5220
|
parseMlsDevGroupPreparation({ wrappedState: data.wrappedState, payload: control ? { kind: "control", control } : { kind: "applied", head: head ?? fail6(), roster, removed: data.removed, consumedKeyPackageId: null } });
|
|
6563
5221
|
if (control && (control.conversationId !== conversationId || control.groupId !== data.groupId || !same6(control.expectedHead, head))) fail6();
|
|
@@ -6579,7 +5237,7 @@ function envelope(value, binding, conversationId) {
|
|
|
6579
5237
|
__name(envelope, "envelope");
|
|
6580
5238
|
function authority(value, conversationId) {
|
|
6581
5239
|
if (value === void 0) return void 0;
|
|
6582
|
-
const data =
|
|
5240
|
+
const data = record4(value, ["version", "marker", "membershipRevision", "admissionId", "roster", "terminal", "observedSequence"]);
|
|
6583
5241
|
const parsed = parseMlsDevGroupQueryResult({
|
|
6584
5242
|
version: data.version,
|
|
6585
5243
|
conversationId,
|
|
@@ -6605,7 +5263,7 @@ function authority(value, conversationId) {
|
|
|
6605
5263
|
__name(authority, "authority");
|
|
6606
5264
|
function recoveryFailure(value, conversationId) {
|
|
6607
5265
|
if (value === void 0) return void 0;
|
|
6608
|
-
const data =
|
|
5266
|
+
const data = record4(value, ["version", "conversationId", "code", "lastAppliedSequence", "observedAtMs"]);
|
|
6609
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();
|
|
6610
5268
|
return Object.freeze({
|
|
6611
5269
|
version: 1,
|
|
@@ -7208,7 +5866,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7208
5866
|
__name(catchUpFeed, "catchUpFeed");
|
|
7209
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");
|
|
7210
5868
|
function itemInput(attachment, caption) {
|
|
7211
|
-
const data =
|
|
5869
|
+
const data = record4(attachment, ["kind", "url", "mimeType", "sizeBytes", "uploadId"], ["fileName", "width", "height", "durationMs", "thumbnailUrl", "processingStatus"]);
|
|
7212
5870
|
if (data.processingStatus !== void 0 && data.processingStatus !== null && data.processingStatus !== "ready") fail6("MLS_DEV_ITEM_PROCESSING", true);
|
|
7213
5871
|
const raw = {
|
|
7214
5872
|
type: data.kind,
|
|
@@ -7240,7 +5898,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7240
5898
|
}
|
|
7241
5899
|
__name(validItem, "validItem");
|
|
7242
5900
|
function textInput(value) {
|
|
7243
|
-
const data =
|
|
5901
|
+
const data = record4(value, ["conversationId"], ["text", "messageId", "contentType", "mentions", "attachments", "replyTo", "replyToPosition", "metadata", "forwarded", "contactCardUserId"]);
|
|
7244
5902
|
const attachments = data.attachments ?? [];
|
|
7245
5903
|
if (!Array.isArray(attachments) || attachments.length > 1) fail6(Array.isArray(attachments) ? "MLS_DEV_ONE_ITEM_PER_MESSAGE" : "MLS_DEV_TEXT_ONLY");
|
|
7246
5904
|
const item = attachments.length ? itemInput(attachments[0], data.text) : void 0;
|
|
@@ -7260,8 +5918,11 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7260
5918
|
await primeIdentities(conversationId, init);
|
|
7261
5919
|
return store.transaction(async (tx) => {
|
|
7262
5920
|
const room = await tx.get(`conversation/${conversationId}`);
|
|
7263
|
-
if (room?.mlsAdmissionHold !== true) return Object.freeze({ held: false, waitingPrincipalIds: Object.freeze([]) });
|
|
7264
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
|
+
}
|
|
7265
5926
|
const accounts = mlsRoomAccounts(room, init.binding.environmentId);
|
|
7266
5927
|
const admitted = new Set((group && !group.removed ? group.roster : []).map((entry) => mlsAccountOf(accounts, entry.device.principal)));
|
|
7267
5928
|
const waiting = [.../* @__PURE__ */ new Set([
|
|
@@ -7443,14 +6104,14 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7443
6104
|
}
|
|
7444
6105
|
__name(aliasedInteraction, "aliasedInteraction");
|
|
7445
6106
|
function interactionInput(value) {
|
|
7446
|
-
const data =
|
|
6107
|
+
const data = record4(value, ["conversationId"], ["messageId", "operationId", "guardId", "request", "response", "withdraw"]);
|
|
7447
6108
|
const conversationId = id(data.conversationId), messageId = data.messageId === void 0 ? void 0 : id(data.messageId);
|
|
7448
6109
|
const roles = ["request", "response", "withdraw"].filter((role) => data[role] !== void 0);
|
|
7449
6110
|
if (roles.length !== 1) fail6("INVALID_MLS_DEV_INTERACTION_INPUT");
|
|
7450
6111
|
try {
|
|
7451
6112
|
if (data.request !== void 0) {
|
|
7452
6113
|
if (principal.type !== "ai_agent") fail6("MLS_DEV_INTERACTION_REQUESTER_INVALID");
|
|
7453
|
-
const input =
|
|
6114
|
+
const input = record4(
|
|
7454
6115
|
data.request,
|
|
7455
6116
|
["requestId", "kind", "title", "responderIds", "expiresAtMs"],
|
|
7456
6117
|
["details", "options", "multiple", "risk", "allowSession", "scopeKey"]
|
|
@@ -7466,7 +6127,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7466
6127
|
}
|
|
7467
6128
|
if (data.response !== void 0) {
|
|
7468
6129
|
if (principal.type !== "human") fail6("MLS_DEV_INTERACTION_RESPONDER_INVALID");
|
|
7469
|
-
const input =
|
|
6130
|
+
const input = record4(data.response, ["requestId", "agentId"], ["answer", "secret"]);
|
|
7470
6131
|
const binding = { role: "response", requestId: input.requestId, agentId: input.agentId };
|
|
7471
6132
|
if (Object.hasOwn(input, "secret") === Object.hasOwn(input, "answer")) fail6("INVALID_MLS_DEV_INTERACTION_INPUT");
|
|
7472
6133
|
if (Object.hasOwn(input, "answer")) {
|
|
@@ -7944,12 +6605,13 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7944
6605
|
async function commitHeldRemoval(conversationId) {
|
|
7945
6606
|
const init = await initialized();
|
|
7946
6607
|
const target = await store.transaction(async (tx) => {
|
|
7947
|
-
const removal = await roomRemoval(tx, conversationId);
|
|
7948
|
-
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;
|
|
7949
6610
|
const current = await capture(tx, await readPreparationSnapshot(tx, scope4(conversationId)), init, conversationId), group = current.envelope;
|
|
7950
|
-
const held = new Set(removal
|
|
7951
|
-
if (!group?.head || group.removed || group.pending || current.authority?.terminal || held.has(
|
|
7952
|
-
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];
|
|
7953
6615
|
return removedDeviceId ? {
|
|
7954
6616
|
deviceId: current.device.registration.device.deviceId,
|
|
7955
6617
|
removedDeviceId,
|
|
@@ -8043,7 +6705,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
8043
6705
|
__name(run, "run");
|
|
8044
6706
|
const client = {
|
|
8045
6707
|
createRoom(input) {
|
|
8046
|
-
const data =
|
|
6708
|
+
const data = record4(input, ["conversationId", "name", "memberIds"], ["operationId", "protocol"]);
|
|
8047
6709
|
const conversationId = id(data.conversationId), operationId = id(data.operationId ?? context.ids());
|
|
8048
6710
|
if (typeof data.name !== "string" || data.name.trim().length > 100) fail6("INVALID_MLS_DEV_GROUP_INPUT");
|
|
8049
6711
|
if (data.protocol !== void 0 && !isMlsDevTextProtocol(data.protocol)) fail6("MLS_DEV_TEXT_PROTOCOL_UNSUPPORTED");
|
|
@@ -8097,15 +6759,15 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
8097
6759
|
});
|
|
8098
6760
|
},
|
|
8099
6761
|
create: /* @__PURE__ */ __name((input) => {
|
|
8100
|
-
const data =
|
|
6762
|
+
const data = record4(input, ["conversationId"], ["operationId"]);
|
|
8101
6763
|
return run(() => serializeInboundPreparation(store, id(data.conversationId), () => submit("create", data)));
|
|
8102
6764
|
}, "create"),
|
|
8103
6765
|
add: /* @__PURE__ */ __name((input) => {
|
|
8104
|
-
const data =
|
|
6766
|
+
const data = record4(input, ["conversationId", "claimRequestId"], ["operationId"]);
|
|
8105
6767
|
return run(() => serializeInboundPreparation(store, id(data.conversationId), () => submit("add", data)));
|
|
8106
6768
|
}, "add"),
|
|
8107
6769
|
remove: /* @__PURE__ */ __name((input) => {
|
|
8108
|
-
const data =
|
|
6770
|
+
const data = record4(input, ["conversationId", "removedDeviceId"], ["operationId"]);
|
|
8109
6771
|
return run(() => serializeInboundPreparation(store, id(data.conversationId), () => submit("remove", data)));
|
|
8110
6772
|
}, "remove"),
|
|
8111
6773
|
refresh: /* @__PURE__ */ __name((value) => run(() => serializeInboundPreparation(store, id(value), () => refresh(id(value)))), "refresh"),
|
|
@@ -8120,13 +6782,13 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
8120
6782
|
}, false), "heldCurrent"),
|
|
8121
6783
|
read: /* @__PURE__ */ __name((value) => run(() => read(id(value)), false), "read"),
|
|
8122
6784
|
readVerifiedMessageBinding: /* @__PURE__ */ __name((input) => {
|
|
8123
|
-
const data =
|
|
6785
|
+
const data = record4(input, ["conversationId", "messageId"]);
|
|
8124
6786
|
const conversationId = id(data.conversationId), messageId = id(data.messageId);
|
|
8125
6787
|
return run(() => readVerifiedMessageBinding(conversationId, messageId), false);
|
|
8126
6788
|
}, "readVerifiedMessageBinding"),
|
|
8127
6789
|
readCurrentAudience: /* @__PURE__ */ __name((value) => run(() => serializeInboundPreparation(store, id(value), () => readCurrentAudience(id(value)))), "readCurrentAudience"),
|
|
8128
6790
|
assertCurrentAudience: /* @__PURE__ */ __name((value) => {
|
|
8129
|
-
const data =
|
|
6791
|
+
const data = record4(value, ["version", "protocol", "conversationId", "groupId", "ownDeviceId", "head", "membershipRevision", "admissionId", "roster", "feedOrdinal"]);
|
|
8130
6792
|
const expected = JSON.parse(canonicalEndpointJson(data)), conversationId = id(expected.conversationId);
|
|
8131
6793
|
if (!Number.isSafeInteger(expected.feedOrdinal) || expected.feedOrdinal < 1) fail6("MLS_DEV_AUDIENCE_CHANGED");
|
|
8132
6794
|
return run(() => serializeInboundPreparation(store, conversationId, async () => {
|
|
@@ -8148,12 +6810,12 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
8148
6810
|
return run(() => serializeInboundPreparation(store, input.conversationId, () => sendInteraction(input, operationId, guardId)));
|
|
8149
6811
|
}, "sendInteraction"),
|
|
8150
6812
|
readInteraction: /* @__PURE__ */ __name(async (value) => {
|
|
8151
|
-
const data =
|
|
6813
|
+
const data = record4(value, ["conversationId", "agentId", "requestId"]);
|
|
8152
6814
|
const conversationId = id(data.conversationId), agentId = id(data.agentId), requestId = id(data.requestId);
|
|
8153
6815
|
return run(() => serializeInboundPreparation(store, conversationId, () => readInteraction(conversationId, agentId, requestId)), false);
|
|
8154
6816
|
}, "readInteraction"),
|
|
8155
6817
|
openSecretAnswer: /* @__PURE__ */ __name(async (value) => {
|
|
8156
|
-
const data =
|
|
6818
|
+
const data = record4(value, ["conversationId", "requestId"]), conversationId = id(data.conversationId), requestId = id(data.requestId);
|
|
8157
6819
|
return run(() => serializeInboundPreparation(store, conversationId, () => openSecretAnswer(conversationId, requestId)));
|
|
8158
6820
|
}, "openSecretAnswer")
|
|
8159
6821
|
};
|
|
@@ -8161,13 +6823,14 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
8161
6823
|
client,
|
|
8162
6824
|
async commitHeldRemoval(conversationId) {
|
|
8163
6825
|
const cid = id(conversationId);
|
|
8164
|
-
|
|
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;
|
|
8165
6828
|
return run(() => serializeInboundPreparation(store, cid, () => commitHeldRemoval(cid)));
|
|
8166
6829
|
},
|
|
8167
6830
|
/** Forwards an encrypted text by re-encryption: this device's own decrypted copy, sent as new text to another
|
|
8168
6831
|
* encrypted room (mls-payload-v3.md §4). Items and cards are not forwarded yet. */
|
|
8169
6832
|
forwardText(value, operationId, guardId) {
|
|
8170
|
-
const data =
|
|
6833
|
+
const data = record4(value, ["sourceConversationId", "messageId", "targetConversationId"]);
|
|
8171
6834
|
const source = id(data.sourceConversationId), messageId = id(data.messageId), target = id(data.targetConversationId);
|
|
8172
6835
|
const selectedId = id(operationId), selectedGuard = guardId === void 0 ? void 0 : id(guardId);
|
|
8173
6836
|
return run(async () => {
|
|
@@ -8324,7 +6987,7 @@ function createEndpoint(options) {
|
|
|
8324
6987
|
const { store, transport } = options;
|
|
8325
6988
|
const clock = options.clock ?? Date.now;
|
|
8326
6989
|
const ids = options.ids ?? randomId;
|
|
8327
|
-
const
|
|
6990
|
+
const hash = options.hash ?? sha256;
|
|
8328
6991
|
const hashBytes = options.hashBytes ?? sha256Bytes;
|
|
8329
6992
|
const owner = ids();
|
|
8330
6993
|
const leaseMs = Math.max(5e3, options.leaseMs ?? 6e4);
|
|
@@ -8437,7 +7100,7 @@ function createEndpoint(options) {
|
|
|
8437
7100
|
const mlsDevDevice = options.mlsDevDevice ? createMlsDevDeviceClient(options.mlsDevDevice, mlsDevContext) : void 0;
|
|
8438
7101
|
const mlsDevKeyPackages = options.mlsDevDevice?.prepareProtectedKeyPackage ? createMlsDevKeyPackageClient(options.mlsDevDevice, mlsDevContext) : void 0;
|
|
8439
7102
|
const mlsDevGroups = options.mlsDevDevice?.prepareProtectedGroup ? createMlsDevGroups(options.mlsDevDevice, mlsDevContext, mlsDevKeyPackages, {
|
|
8440
|
-
hash
|
|
7103
|
+
hash,
|
|
8441
7104
|
hashBytes,
|
|
8442
7105
|
readRoom,
|
|
8443
7106
|
resolveProtocol: /* @__PURE__ */ __name((conversationId) => resolveMlsRoomMode(conversationId).then((value) => value.protocol), "resolveProtocol"),
|
|
@@ -8466,7 +7129,7 @@ function createEndpoint(options) {
|
|
|
8466
7129
|
scheduleAdmission(conversationId, 0);
|
|
8467
7130
|
}, "controlSettled"),
|
|
8468
7131
|
// Whatever fetched them (a hint, the reconnect sweep, a send, recovery), the room's local reads show them now.
|
|
8469
|
-
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"),
|
|
8470
7133
|
streamLive: /* @__PURE__ */ __name(() => events.isConnected(), "streamLive")
|
|
8471
7134
|
}) : void 0;
|
|
8472
7135
|
async function inMlsEnvironment() {
|
|
@@ -8597,13 +7260,14 @@ function createEndpoint(options) {
|
|
|
8597
7260
|
if (!mlsDevGroups || await mlsDevGroups.client.read(conversationId)) return;
|
|
8598
7261
|
if (!isMlsDevTextProtocol((await resolveMlsRoomMode(conversationId, false)).protocol) || await readServerRoomMode() !== "default") return;
|
|
8599
7262
|
const { installationId } = await initialization;
|
|
8600
|
-
const digest = await
|
|
7263
|
+
const digest = await hash(canonicalEndpointJson(["canon.mls.room-birth.v1", installationId, conversationId]));
|
|
8601
7264
|
await mlsDevGroups.client.create({ conversationId, operationId: `mls-birth-${digest.slice(0, 48)}` }).catch((error) => {
|
|
8602
7265
|
if (!(error instanceof EndpointError) || ["ENDPOINT_CLOSED", "PREPARATION_INSTALLATION_FENCED"].includes(error.code)) throw error;
|
|
8603
7266
|
});
|
|
8604
7267
|
}
|
|
8605
7268
|
__name(createBornGroup, "createBornGroup");
|
|
8606
7269
|
const admissionRuns = /* @__PURE__ */ new Map(), admitting = /* @__PURE__ */ new Set(), admissionDelays = /* @__PURE__ */ new Map();
|
|
7270
|
+
const announcementsSeen = /* @__PURE__ */ new Map();
|
|
8607
7271
|
const admissionTimers = /* @__PURE__ */ new Map();
|
|
8608
7272
|
const ADMISSION_IDLE_MS = 10 * 6e4, CLAIM_JOURNAL_KEEP_MS = 60 * 6e4;
|
|
8609
7273
|
const admissionUnfinished = /* @__PURE__ */ new Set();
|
|
@@ -8635,9 +7299,11 @@ function createEndpoint(options) {
|
|
|
8635
7299
|
const missing = [...local?.memberIds ?? [], ...typeof invitee === "string" ? [invitee] : []].some((memberId) => !inRoster.has(memberId)) || admissionUnfinished.has(conversationId);
|
|
8636
7300
|
if (!missing) admissionDelays.delete(conversationId);
|
|
8637
7301
|
const delay = admissionDelays.get(conversationId) ?? admissionRetry.initial;
|
|
8638
|
-
const
|
|
7302
|
+
const announced = local?.mlsDeviceAnnouncedAtMs;
|
|
7303
|
+
const due = announced !== announcementsSeen.get(conversationId) ? -Infinity : (admissionRuns.get(conversationId) ?? -Infinity) + (missing ? delay : ADMISSION_IDLE_MS);
|
|
8639
7304
|
if (clock() < due) return missing ? scheduleAdmission(conversationId, due - clock()) : void 0;
|
|
8640
7305
|
admitting.add(conversationId);
|
|
7306
|
+
announcementsSeen.set(conversationId, announced);
|
|
8641
7307
|
try {
|
|
8642
7308
|
admissionRuns.set(conversationId, clock());
|
|
8643
7309
|
await mlsDevKeyPackages.pruneClaims(clock() - CLAIM_JOURNAL_KEEP_MS);
|
|
@@ -9014,7 +7680,7 @@ function createEndpoint(options) {
|
|
|
9014
7680
|
observe: events.observe,
|
|
9015
7681
|
isProtocolReady: /* @__PURE__ */ __name(async (id2) => isMlsDevTextProtocol((await resolveMlsRoomMode(id2)).protocol) ? await mlsDevGroups?.isTextReady(id2) ?? false : void 0, "isProtocolReady")
|
|
9016
7682
|
});
|
|
9017
|
-
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 });
|
|
9018
7684
|
const handoffs = createInboundHandoffs({
|
|
9019
7685
|
store,
|
|
9020
7686
|
owner,
|
|
@@ -9026,16 +7692,16 @@ function createEndpoint(options) {
|
|
|
9026
7692
|
assertCurrent: assertCurrentInstallation,
|
|
9027
7693
|
assertReady: /* @__PURE__ */ __name(async (records) => {
|
|
9028
7694
|
if (options.admitRecoveredMessages) {
|
|
9029
|
-
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);
|
|
9030
7696
|
}
|
|
9031
7697
|
}, "assertReady")
|
|
9032
7698
|
});
|
|
9033
|
-
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 });
|
|
9034
7700
|
const messageGroups = createMessageGroupCoordinator({
|
|
9035
7701
|
store,
|
|
9036
7702
|
endpoint: /* @__PURE__ */ __name(() => endpoint, "endpoint"),
|
|
9037
7703
|
ready: /* @__PURE__ */ __name(() => initialization, "ready"),
|
|
9038
|
-
hash
|
|
7704
|
+
hash,
|
|
9039
7705
|
clock,
|
|
9040
7706
|
wake: /* @__PURE__ */ __name(() => scheduleRetry(0), "wake"),
|
|
9041
7707
|
preparationGeneration: /* @__PURE__ */ __name(() => installationGeneration, "preparationGeneration"),
|
|
@@ -9183,7 +7849,7 @@ function createEndpoint(options) {
|
|
|
9183
7849
|
const index = group?.children.findIndex((child) => child.operationId === operationId) ?? -1;
|
|
9184
7850
|
const previous = group && index > 0 ? await tx.get(key(group.children[index - 1].operationId)) : void 0;
|
|
9185
7851
|
if (previous?.status === "rejected" || previous?.status === "succeeded" && !messageGroupChildSucceeded(previous)) {
|
|
9186
|
-
const
|
|
7852
|
+
const rejected = { ...current, status: "rejected", updatedAt: now(), receipt: {
|
|
9187
7853
|
protocol: CANON_ENDPOINT_PROTOCOL,
|
|
9188
7854
|
operationId,
|
|
9189
7855
|
installationId: current.installationId,
|
|
@@ -9193,12 +7859,12 @@ function createEndpoint(options) {
|
|
|
9193
7859
|
updatedAt: now(),
|
|
9194
7860
|
error: { code: "DEPENDENCY_REJECTED", message: "An earlier part of this reply could not be delivered", retryable: false }
|
|
9195
7861
|
} };
|
|
9196
|
-
delete
|
|
9197
|
-
delete
|
|
9198
|
-
await tx.put(key(operationId),
|
|
9199
|
-
await removeOperationOutbox(tx,
|
|
9200
|
-
await settleOperationOrder(tx,
|
|
9201
|
-
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 };
|
|
9202
7868
|
}
|
|
9203
7869
|
if (!messageGroups.allowed(current.messageGroupId) || !group || index < 0 || group.cancelled || index > 0 && !messageGroupChildSucceeded(previous)) return { operation: current, acquired: false };
|
|
9204
7870
|
}
|
|
@@ -9325,8 +7991,8 @@ function createEndpoint(options) {
|
|
|
9325
7991
|
await persistReceipt(operation, receipt);
|
|
9326
7992
|
return receipt;
|
|
9327
7993
|
} catch (error) {
|
|
9328
|
-
const
|
|
9329
|
-
if (reconcileOnly &&
|
|
7994
|
+
const failure = safeError(error);
|
|
7995
|
+
if (reconcileOnly && failure.retryable) {
|
|
9330
7996
|
await store.transaction(async (tx) => {
|
|
9331
7997
|
const current = await tx.get(key(operationId));
|
|
9332
7998
|
if (current?.lease?.owner === owner) {
|
|
@@ -9335,17 +8001,17 @@ function createEndpoint(options) {
|
|
|
9335
8001
|
await tx.put(key(operationId), next);
|
|
9336
8002
|
}
|
|
9337
8003
|
});
|
|
9338
|
-
return deferredReceipt(operation,
|
|
8004
|
+
return deferredReceipt(operation, failure);
|
|
9339
8005
|
}
|
|
9340
8006
|
const updated = await store.transaction(async (tx) => {
|
|
9341
8007
|
const current = await tx.get(key(operationId));
|
|
9342
|
-
if (!current) throw
|
|
8008
|
+
if (!current) throw failure;
|
|
9343
8009
|
if (current.lease?.owner !== owner || terminal(current.status)) return current;
|
|
9344
|
-
const lookupUncertain = resolvingReceipt && !
|
|
9345
|
-
const generationUncertain = current.attempted &&
|
|
9346
|
-
const contentModeUncertain = current.attempted && checkingContentMode && !
|
|
9347
|
-
const guardUncertain = current.attempted && checkingDeliveryGuard && !
|
|
9348
|
-
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))) };
|
|
9349
8015
|
delete next.lease;
|
|
9350
8016
|
if (current.sensitive) {
|
|
9351
8017
|
delete next.request;
|
|
@@ -9355,14 +8021,14 @@ function createEndpoint(options) {
|
|
|
9355
8021
|
next.receipt = { protocol: CANON_ENDPOINT_PROTOCOL, operationId: current.operationId, installationId: current.installationId, action: current.action, status: "uncertain", error: {
|
|
9356
8022
|
code: generationUncertain ? "PREPARATION_INSTALLATION_FENCED" : guardUncertain ? "DELIVERY_GUARD_RECONCILIATION_REQUIRED" : contentModeUncertain ? "CONTENT_MODE_RECONCILIATION_REQUIRED" : "RECEIPT_LOOKUP_REJECTED",
|
|
9357
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",
|
|
9358
|
-
status:
|
|
8024
|
+
status: failure.status,
|
|
9359
8025
|
retryable: false
|
|
9360
8026
|
}, createdAt: current.createdAt, updatedAt: now() };
|
|
9361
8027
|
delete next.request;
|
|
9362
8028
|
delete next.preparedContent;
|
|
9363
8029
|
delete next.nextAttemptAt;
|
|
9364
|
-
} else if (!
|
|
9365
|
-
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() };
|
|
9366
8032
|
delete next.nextAttemptAt;
|
|
9367
8033
|
}
|
|
9368
8034
|
await tx.put(key(operationId), next);
|
|
@@ -9383,8 +8049,8 @@ function createEndpoint(options) {
|
|
|
9383
8049
|
if (updated.messageGroupId && terminal(updated.status)) await messageGroups.settled(updated);
|
|
9384
8050
|
if (terminal(updated.status)) scheduleRetry(0);
|
|
9385
8051
|
notify(updated);
|
|
9386
|
-
if (
|
|
9387
|
-
return deferredReceipt(updated,
|
|
8052
|
+
if (failure.retryable && updated.status !== "uncertain" && !closed) scheduleRetry(Math.max(1e3, (updated.nextAttemptAt ?? clock()) - clock()));
|
|
8053
|
+
return deferredReceipt(updated, failure);
|
|
9388
8054
|
} finally {
|
|
9389
8055
|
clearInterval(heartbeat);
|
|
9390
8056
|
}
|
|
@@ -9435,7 +8101,7 @@ function createEndpoint(options) {
|
|
|
9435
8101
|
}
|
|
9436
8102
|
const authored = JSON.parse(canonicalEndpointJson(intent2));
|
|
9437
8103
|
const sensitive = executeOptions.sensitive === true || endpointOperationRequestRetention(authored.action, authored.body, authored.envelope) === "memory";
|
|
9438
|
-
const inputFingerprint = sensitive ? void 0 : await
|
|
8104
|
+
const inputFingerprint = sensitive ? void 0 : await hash(canonicalEndpointJson({ ...authored, ...identity.ownershipVersion !== void 0 ? { ownershipVersion: identity.ownershipVersion } : {} }));
|
|
9439
8105
|
const previous = await store.transaction(async (tx) => {
|
|
9440
8106
|
await assertCurrentInstallation(tx);
|
|
9441
8107
|
return tx.get(key(operationId));
|
|
@@ -9487,8 +8153,8 @@ function createEndpoint(options) {
|
|
|
9487
8153
|
if (!sensitive && !previous) await reservePreparedIdentity(store, { operationId, action: authored.action, fingerprint: inputFingerprint, installationGeneration });
|
|
9488
8154
|
const normalized = await prepareEndpointIntent({ intent: authored, previousPreconditions: previous?.preconditions, query: /* @__PURE__ */ __name((name, parameters) => endpoint.query(name, parameters), "query") });
|
|
9489
8155
|
const routedBody = { ...normalized.body, ...normalized.envelope };
|
|
9490
|
-
const sensitiveFingerprint = sensitive ? await
|
|
9491
|
-
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 } : {} }));
|
|
9492
8158
|
assertOpen();
|
|
9493
8159
|
const includePreparedContent = !sensitive && negotiatedCapabilities?.preparedContentVersion === 1;
|
|
9494
8160
|
const request = {
|
|
@@ -9640,7 +8306,7 @@ function createEndpoint(options) {
|
|
|
9640
8306
|
return store.transaction((tx) => tx.get(key(operationId)));
|
|
9641
8307
|
},
|
|
9642
8308
|
waitForOperation(operationId, waitOptions = {}) {
|
|
9643
|
-
return new Promise((
|
|
8309
|
+
return new Promise((resolve3, reject) => {
|
|
9644
8310
|
let finished = false;
|
|
9645
8311
|
let checking = false;
|
|
9646
8312
|
let poll;
|
|
@@ -9654,7 +8320,7 @@ function createEndpoint(options) {
|
|
|
9654
8320
|
controller.signal.removeEventListener("abort", abort);
|
|
9655
8321
|
waitOptions.signal?.removeEventListener("abort", abort);
|
|
9656
8322
|
if (error) reject(error);
|
|
9657
|
-
else
|
|
8323
|
+
else resolve3(receipt);
|
|
9658
8324
|
}, "finish");
|
|
9659
8325
|
const abort = /* @__PURE__ */ __name(() => finish(void 0, new EndpointError("Delivery remains in the endpoint journal for reconciliation", {
|
|
9660
8326
|
code: "OPERATION_PENDING",
|
|
@@ -9739,6 +8405,7 @@ function createEndpoint(options) {
|
|
|
9739
8405
|
}
|
|
9740
8406
|
const result = await transport.query(name, normalized, controller.signal);
|
|
9741
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));
|
|
9742
8409
|
return result;
|
|
9743
8410
|
}).finally(() => {
|
|
9744
8411
|
reads.delete(queryKey);
|
|
@@ -9773,7 +8440,7 @@ function createEndpoint(options) {
|
|
|
9773
8440
|
observeEvents: events.observe,
|
|
9774
8441
|
createMediaPipeline(bytes, mediaOptions) {
|
|
9775
8442
|
assertOpen();
|
|
9776
|
-
const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash
|
|
8443
|
+
const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash, ids, clock, background: mediaOptions?.background });
|
|
9777
8444
|
mediaPipelines2.add(pipeline3);
|
|
9778
8445
|
return pipeline3;
|
|
9779
8446
|
},
|
|
@@ -9803,14 +8470,14 @@ function createEndpoint(options) {
|
|
|
9803
8470
|
await initialization;
|
|
9804
8471
|
if (typeof input.messageId !== "string" || !input.messageId || typeof input.text !== "string")
|
|
9805
8472
|
throw new EndpointError("Invalid deferred opening line", { status: 400, code: "INVALID_DEFERRED_OPENER", retryable: false });
|
|
9806
|
-
const digest = await
|
|
8473
|
+
const digest = await hash(canonicalEndpointJson(["canon.withheld-opener.v1", input.messageId]));
|
|
9807
8474
|
const operationId = `start-opener-${digest.slice(0, 48)}`;
|
|
9808
8475
|
const body = {
|
|
9809
8476
|
targetUserId: input.targetUserId,
|
|
9810
8477
|
opener: { withheld: true, messageId: input.messageId },
|
|
9811
8478
|
...input.selection ? { selection: copyJson(input.selection) } : {}
|
|
9812
8479
|
};
|
|
9813
|
-
const openerFingerprint = await
|
|
8480
|
+
const openerFingerprint = await hash(canonicalEndpointJson([body, input.text]));
|
|
9814
8481
|
const recordKey = `${openerPrefix()}/${encodeURIComponent(input.messageId)}`;
|
|
9815
8482
|
await store.transaction(async (tx) => {
|
|
9816
8483
|
await assertCurrentInstallation(tx);
|
|
@@ -9947,15 +8614,15 @@ function createEndpoint(options) {
|
|
|
9947
8614
|
if (!eventIds.length) return false;
|
|
9948
8615
|
if (options.admitRecoveredMessages) {
|
|
9949
8616
|
const records = await Promise.all(eventIds.map((id2) => endpoint.getInbound(id2)));
|
|
9950
|
-
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);
|
|
9951
8618
|
}
|
|
9952
8619
|
return store.transaction(async (tx) => {
|
|
9953
8620
|
if (await tx.get("migration/active")) throw new EndpointError("Migration is incomplete; native execution is gated", { code: "MIGRATION_IN_PROGRESS", retryable: false });
|
|
9954
8621
|
const records = await Promise.all(eventIds.map((id2) => tx.get(`inbound/${id2}`)));
|
|
9955
8622
|
let projection = false;
|
|
9956
|
-
for (const
|
|
8623
|
+
for (const record6 of records) if (record6 && await settleInboundProjection(tx, record6, now())) projection = true;
|
|
9957
8624
|
if (projection) return false;
|
|
9958
|
-
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;
|
|
9959
8626
|
for (let i = 0; i < records.length; i++) {
|
|
9960
8627
|
const next = { ...records[i], state: "uncertain", updatedAt: now() };
|
|
9961
8628
|
delete next.lease;
|
|
@@ -10110,7 +8777,7 @@ function createEndpoint(options) {
|
|
|
10110
8777
|
return endpoint;
|
|
10111
8778
|
}
|
|
10112
8779
|
__name(createEndpoint, "createEndpoint");
|
|
10113
|
-
async function matchingRecords(tx, prefix3, options,
|
|
8780
|
+
async function matchingRecords(tx, prefix3, options, matches) {
|
|
10114
8781
|
const limit = Math.max(1, Math.min(options.limit ?? 100, 1e3));
|
|
10115
8782
|
const result = [];
|
|
10116
8783
|
let after = options.after ? `${prefix3}${options.after}` : void 0;
|
|
@@ -10118,7 +8785,7 @@ async function matchingRecords(tx, prefix3, options, matches2) {
|
|
|
10118
8785
|
const batch = await tx.scan(prefix3, { after, limit: 100 });
|
|
10119
8786
|
for (const entry of batch) {
|
|
10120
8787
|
after = entry.key;
|
|
10121
|
-
if (
|
|
8788
|
+
if (matches(entry.value)) result.push(entry.value);
|
|
10122
8789
|
if (result.length === limit) return result;
|
|
10123
8790
|
}
|
|
10124
8791
|
if (batch.length < 100) return result;
|
|
@@ -10127,9 +8794,9 @@ async function matchingRecords(tx, prefix3, options, matches2) {
|
|
|
10127
8794
|
__name(matchingRecords, "matchingRecords");
|
|
10128
8795
|
|
|
10129
8796
|
// ../core/src/endpoint-client.ts
|
|
10130
|
-
import { createHash as
|
|
8797
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10131
8798
|
import { homedir } from "node:os";
|
|
10132
|
-
import { join as
|
|
8799
|
+
import { join as join2 } from "node:path";
|
|
10133
8800
|
var mediaPipelines = /* @__PURE__ */ new WeakMap();
|
|
10134
8801
|
function acquireFileMediaPipeline(endpoint, options, fetchImpl) {
|
|
10135
8802
|
const existing = mediaPipelines.get(endpoint);
|
|
@@ -10139,7 +8806,7 @@ function acquireFileMediaPipeline(endpoint, options, fetchImpl) {
|
|
|
10139
8806
|
if (options.endpoint && !options.mediaBytes && !options.mediaDirectory && !options.stateDirectory) {
|
|
10140
8807
|
throw new Error("An injected Canon cloud endpoint requires durable mediaBytes or a persistent mediaDirectory.");
|
|
10141
8808
|
}
|
|
10142
|
-
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);
|
|
10143
8810
|
const pipeline3 = endpoint.createMediaPipeline(options.mediaBytes ?? createNodeEndpointMediaBytes({ directory, fetch: fetchImpl }));
|
|
10144
8811
|
if (options.autoDrain !== false) void pipeline3.resumePending();
|
|
10145
8812
|
return pipeline3;
|
|
@@ -10161,7 +8828,7 @@ async function acquireAgentEndpoint(input) {
|
|
|
10161
8828
|
}
|
|
10162
8829
|
const requestedMls = input.options.mlsDev;
|
|
10163
8830
|
const mls = requestedMls ? await (async () => {
|
|
10164
|
-
const crypto2 = await import("./mls-crypto-
|
|
8831
|
+
const crypto2 = await import("./mls-crypto-4DZT2UFH.js");
|
|
10165
8832
|
const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
|
|
10166
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");
|
|
10167
8834
|
const custody = requestedMls.custody;
|
|
@@ -10190,8 +8857,8 @@ async function acquireAgentEndpoint(input) {
|
|
|
10190
8857
|
}
|
|
10191
8858
|
const streamUrl = input.options.streamUrl ?? Object.entries(CANON_RUNTIME_ENDPOINT_DEFAULTS).find(([id2]) => id2 === environmentId)?.[1]?.streamUrl;
|
|
10192
8859
|
if (!streamUrl) throw new Error("Canon endpoint requires a streamUrl for this environment.");
|
|
10193
|
-
const directory = input.options.stateDirectory ?? process.env.CANON_ENDPOINT_STATE_DIR ??
|
|
10194
|
-
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}
|
|
10195
8862
|
${baseUrl}
|
|
10196
8863
|
${input.principalId}
|
|
10197
8864
|
${input.ownershipVersion}`).digest("hex");
|
|
@@ -10933,7 +9600,7 @@ function asCanonApiError(error) {
|
|
|
10933
9600
|
}
|
|
10934
9601
|
__name(asCanonApiError, "asCanonApiError");
|
|
10935
9602
|
function stableOperationId(...parts) {
|
|
10936
|
-
return
|
|
9603
|
+
return createHash3("sha256").update(JSON.stringify(parts)).digest("hex");
|
|
10937
9604
|
}
|
|
10938
9605
|
__name(stableOperationId, "stableOperationId");
|
|
10939
9606
|
function normalizeMessage(raw) {
|
|
@@ -11193,14 +9860,14 @@ async function resumeRegistration(options) {
|
|
|
11193
9860
|
if (status.status === "rejected") return { status: "rejected", requestId };
|
|
11194
9861
|
const remaining = deadline - Date.now();
|
|
11195
9862
|
if (remaining <= 0) return { status: "pending", requestId };
|
|
11196
|
-
await new Promise((
|
|
9863
|
+
await new Promise((resolve3, reject) => {
|
|
11197
9864
|
const abort = /* @__PURE__ */ __name(() => {
|
|
11198
9865
|
clearTimeout(timer);
|
|
11199
9866
|
reject(signal.reason);
|
|
11200
9867
|
}, "abort");
|
|
11201
9868
|
const timer = setTimeout(() => {
|
|
11202
9869
|
signal?.removeEventListener("abort", abort);
|
|
11203
|
-
|
|
9870
|
+
resolve3();
|
|
11204
9871
|
}, Math.min(pollIntervalMs, remaining));
|
|
11205
9872
|
signal?.addEventListener("abort", abort, { once: true });
|
|
11206
9873
|
if (signal?.aborted) abort();
|
|
@@ -11213,7 +9880,7 @@ __name(resumeRegistration, "resumeRegistration");
|
|
|
11213
9880
|
import { existsSync, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync } from "node:fs";
|
|
11214
9881
|
import { closeSync as closeSync2, openSync as openSync2 } from "node:fs";
|
|
11215
9882
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
11216
|
-
import { join as
|
|
9883
|
+
import { join as join3, resolve } from "node:path";
|
|
11217
9884
|
import { homedir as homedir2 } from "node:os";
|
|
11218
9885
|
|
|
11219
9886
|
// ../core/src/process-lock.ts
|
|
@@ -11254,15 +9921,15 @@ function claimStaleLockReaper(lockPath) {
|
|
|
11254
9921
|
__name(claimStaleLockReaper, "claimStaleLockReaper");
|
|
11255
9922
|
|
|
11256
9923
|
// ../core/src/agent-profiles.ts
|
|
11257
|
-
var CANON_DIR = process.env.CANON_HOME ?
|
|
11258
|
-
var AGENTS_PATH =
|
|
11259
|
-
var LOCKS_DIR =
|
|
11260
|
-
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");
|
|
11261
9928
|
var AGENTS_JSON_BOOTSTRAP_ENV = "CANON_AGENTS_JSON_BOOTSTRAP";
|
|
11262
9929
|
function canonPaths(home) {
|
|
11263
9930
|
if (!home) return { dir: CANON_DIR, agents: AGENTS_PATH, locks: LOCKS_DIR };
|
|
11264
|
-
const dir =
|
|
11265
|
-
return { dir, agents:
|
|
9931
|
+
const dir = resolve(home);
|
|
9932
|
+
return { dir, agents: join3(dir, "agents.json"), locks: join3(dir, "locks") };
|
|
11266
9933
|
}
|
|
11267
9934
|
__name(canonPaths, "canonPaths");
|
|
11268
9935
|
function normalizeAgentProfile(name, value, source) {
|
|
@@ -11429,7 +10096,7 @@ function sameProcess(owner) {
|
|
|
11429
10096
|
}
|
|
11430
10097
|
__name(sameProcess, "sameProcess");
|
|
11431
10098
|
function isProfileLocked(profile, home) {
|
|
11432
|
-
const lockPath =
|
|
10099
|
+
const lockPath = join3(canonPaths(home).locks, `${profile}.lock`);
|
|
11433
10100
|
try {
|
|
11434
10101
|
const lock = parseLock(readFileSync2(lockPath, "utf-8"));
|
|
11435
10102
|
const pid = lock?.pid;
|
|
@@ -11454,7 +10121,7 @@ __name(parseLock, "parseLock");
|
|
|
11454
10121
|
function acquireLock(profile, home) {
|
|
11455
10122
|
const locks = canonPaths(home).locks;
|
|
11456
10123
|
mkdirSync(locks, { recursive: true });
|
|
11457
|
-
const lockPath =
|
|
10124
|
+
const lockPath = join3(locks, `${profile}.lock`);
|
|
11458
10125
|
const token = randomUUID5();
|
|
11459
10126
|
const payload = JSON.stringify({
|
|
11460
10127
|
pid: process.pid,
|
|
@@ -11506,7 +10173,7 @@ function acquireLock(profile, home) {
|
|
|
11506
10173
|
}
|
|
11507
10174
|
__name(acquireLock, "acquireLock");
|
|
11508
10175
|
function releaseLock(profile, token, home) {
|
|
11509
|
-
const lockPath =
|
|
10176
|
+
const lockPath = join3(canonPaths(home).locks, `${profile}.lock`);
|
|
11510
10177
|
if (token) {
|
|
11511
10178
|
try {
|
|
11512
10179
|
const lock = parseLock(readFileSync2(lockPath, "utf-8"));
|
|
@@ -11803,7 +10470,7 @@ var RuntimeRequestManager = class {
|
|
|
11803
10470
|
}
|
|
11804
10471
|
const key = pendingKey(kind, conversationId, effectiveId);
|
|
11805
10472
|
const signal = options.signal;
|
|
11806
|
-
return await new Promise((
|
|
10473
|
+
return await new Promise((resolve3, reject) => {
|
|
11807
10474
|
let settled = false;
|
|
11808
10475
|
const finishCommon = /* @__PURE__ */ __name(() => {
|
|
11809
10476
|
settled = true;
|
|
@@ -11829,7 +10496,7 @@ var RuntimeRequestManager = class {
|
|
|
11829
10496
|
} catch {
|
|
11830
10497
|
}
|
|
11831
10498
|
}
|
|
11832
|
-
|
|
10499
|
+
resolve3(finalResult);
|
|
11833
10500
|
})();
|
|
11834
10501
|
}, "settle");
|
|
11835
10502
|
const abort = /* @__PURE__ */ __name((error) => {
|
|
@@ -11963,7 +10630,7 @@ var RuntimeRequestManager = class {
|
|
|
11963
10630
|
pending.wakeRequested = false;
|
|
11964
10631
|
return Promise.resolve();
|
|
11965
10632
|
}
|
|
11966
|
-
return new Promise((
|
|
10633
|
+
return new Promise((resolve3) => {
|
|
11967
10634
|
let done = false;
|
|
11968
10635
|
const finish = /* @__PURE__ */ __name(() => {
|
|
11969
10636
|
if (done) return;
|
|
@@ -11975,7 +10642,7 @@ var RuntimeRequestManager = class {
|
|
|
11975
10642
|
current.wake = () => {
|
|
11976
10643
|
};
|
|
11977
10644
|
}
|
|
11978
|
-
|
|
10645
|
+
resolve3();
|
|
11979
10646
|
}, "finish");
|
|
11980
10647
|
const timer = setTimeout(finish, ms);
|
|
11981
10648
|
const entry = this.pending.get(key);
|
|
@@ -12752,11 +11419,11 @@ function buildRuntimeActivityItemPath(conversationId, agentId, itemId) {
|
|
|
12752
11419
|
return `${buildRuntimeActivityPath(conversationId, agentId)}/${runtimeActivityItemKey(itemId)}`;
|
|
12753
11420
|
}
|
|
12754
11421
|
__name(buildRuntimeActivityItemPath, "buildRuntimeActivityItemPath");
|
|
12755
|
-
function
|
|
11422
|
+
function record5(value) {
|
|
12756
11423
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected runtime projection object");
|
|
12757
11424
|
return value;
|
|
12758
11425
|
}
|
|
12759
|
-
__name(
|
|
11426
|
+
__name(record5, "record");
|
|
12760
11427
|
var PROJECTIONS = /* @__PURE__ */ new Set(["agent-session", "turn-state", "runtime-info", "runtime-activity", "streaming"]);
|
|
12761
11428
|
function createRTDBClientHandle(client) {
|
|
12762
11429
|
async function scope5(path) {
|
|
@@ -12783,40 +11450,40 @@ function createRTDBClientHandle(client) {
|
|
|
12783
11450
|
const target = await scope5(path);
|
|
12784
11451
|
const clear = method === "remove" || data === null;
|
|
12785
11452
|
if (target.family === "agent-runtime") {
|
|
12786
|
-
if (clear ||
|
|
12787
|
-
const value =
|
|
11453
|
+
if (clear || record5(data).updatedAt === 0) return client.updateRuntimeStatus({ online: false });
|
|
11454
|
+
const value = record5(data);
|
|
12788
11455
|
if (typeof value.clientType !== "string" || !value.clientType) throw new Error("Missing runtime client type");
|
|
12789
11456
|
return client.updateRuntimeStatus({
|
|
12790
11457
|
runtime: value.clientType,
|
|
12791
11458
|
...typeof value.hostMode === "boolean" ? { hostMode: value.hostMode } : {},
|
|
12792
|
-
...value.runtimeDescriptor ? { runtimeDescriptor:
|
|
11459
|
+
...value.runtimeDescriptor ? { runtimeDescriptor: record5(value.runtimeDescriptor) } : {}
|
|
12793
11460
|
});
|
|
12794
11461
|
}
|
|
12795
11462
|
if (target.family === "agent-session") {
|
|
12796
11463
|
if (clear) return client.publishRuntimeSession(target.conversationId, "clear");
|
|
12797
|
-
const state = sanitizeAgentSessionSnapshotPatch(
|
|
11464
|
+
const state = sanitizeAgentSessionSnapshotPatch(record5(data));
|
|
12798
11465
|
return client.publishRuntimeSession(target.conversationId, "patch", state);
|
|
12799
11466
|
}
|
|
12800
11467
|
if (target.family === "runtime-info") {
|
|
12801
11468
|
if (clear) return client.publishRuntimeInfo(target.conversationId, "clear");
|
|
12802
|
-
const { updatedAt: _updatedAt, ...info } =
|
|
11469
|
+
const { updatedAt: _updatedAt, ...info } = record5(data);
|
|
12803
11470
|
return client.publishRuntimeInfo(target.conversationId, method === "patch" ? "patch" : "replace", info);
|
|
12804
11471
|
}
|
|
12805
11472
|
if (target.family === "runtime-activity") {
|
|
12806
11473
|
if (clear) return client.publishRuntimeActivity(target.conversationId, target.itemId ? { mode: "remove", itemId: target.itemId } : { mode: "clear" });
|
|
12807
11474
|
if (!target.itemId) throw new Error("Write one runtime activity item");
|
|
12808
|
-
const item =
|
|
11475
|
+
const item = record5(data);
|
|
12809
11476
|
if (typeof item.id !== "string" || runtimeActivityItemKey(item.id) !== target.itemId) throw new Error("Runtime activity identity mismatch");
|
|
12810
11477
|
return client.publishRuntimeActivity(target.conversationId, { mode: "upsert", item });
|
|
12811
11478
|
}
|
|
12812
11479
|
if (target.family === "streaming") {
|
|
12813
11480
|
if (clear) return client.clearStreaming(target.conversationId);
|
|
12814
|
-
const { updatedAt: _updatedAt, startedAt: _startedAt, ...patch2 } =
|
|
11481
|
+
const { updatedAt: _updatedAt, startedAt: _startedAt, ...patch2 } = record5(data);
|
|
12815
11482
|
if (Object.keys(patch2).some((key) => !["text", "status", "messageId", "turnId", "blocks"].includes(key))) throw new Error("Unsupported streaming projection field");
|
|
12816
11483
|
return client.patchRuntimeStreaming(target.conversationId, patch2);
|
|
12817
11484
|
}
|
|
12818
11485
|
if (target.family === "turn-state") {
|
|
12819
|
-
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);
|
|
12820
11487
|
const { updatedAt: _updatedAt, completedAt: _completedAt, openedAt, turnUpdatedAt, ...turn } = value;
|
|
12821
11488
|
return client.setRuntimeTurn({
|
|
12822
11489
|
...turn,
|
|
@@ -13201,12 +11868,12 @@ __name(applyFenceLine, "applyFenceLine");
|
|
|
13201
11868
|
function scanFenceState(text, fence) {
|
|
13202
11869
|
if (!text) return fence;
|
|
13203
11870
|
const lines = text.split("\n");
|
|
13204
|
-
let
|
|
11871
|
+
let open3 = fence;
|
|
13205
11872
|
for (let index = 0; index < lines.length; index += 1) {
|
|
13206
11873
|
if (index === lines.length - 1 && lines[index] === "") continue;
|
|
13207
|
-
|
|
11874
|
+
open3 = applyFenceLine(open3, lines[index]);
|
|
13208
11875
|
}
|
|
13209
|
-
return
|
|
11876
|
+
return open3;
|
|
13210
11877
|
}
|
|
13211
11878
|
__name(scanFenceState, "scanFenceState");
|
|
13212
11879
|
function codePointUtf8Bytes(codePoint) {
|
|
@@ -13417,12 +12084,12 @@ __name(sendMessageWithRetryChunked, "sendMessageWithRetryChunked");
|
|
|
13417
12084
|
|
|
13418
12085
|
// ../core/src/cli-metadata.ts
|
|
13419
12086
|
import { readFileSync as readFileSync3, realpathSync } from "node:fs";
|
|
13420
|
-
import { dirname
|
|
12087
|
+
import { dirname, resolve as resolve2 } from "node:path";
|
|
13421
12088
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13422
12089
|
function isDirectExecution(moduleUrl) {
|
|
13423
12090
|
const entry = process.argv[1];
|
|
13424
12091
|
if (!entry) return false;
|
|
13425
|
-
const abs =
|
|
12092
|
+
const abs = resolve2(entry);
|
|
13426
12093
|
let real = abs;
|
|
13427
12094
|
try {
|
|
13428
12095
|
real = realpathSync(abs);
|
|
@@ -13432,13 +12099,13 @@ function isDirectExecution(moduleUrl) {
|
|
|
13432
12099
|
}
|
|
13433
12100
|
__name(isDirectExecution, "isDirectExecution");
|
|
13434
12101
|
function readCliPackageVersion(moduleUrl) {
|
|
13435
|
-
let current =
|
|
12102
|
+
let current = dirname(fileURLToPath(moduleUrl));
|
|
13436
12103
|
while (true) {
|
|
13437
12104
|
try {
|
|
13438
|
-
const manifest = JSON.parse(readFileSync3(
|
|
12105
|
+
const manifest = JSON.parse(readFileSync3(resolve2(current, "package.json"), "utf8"));
|
|
13439
12106
|
return typeof manifest.version === "string" ? manifest.version : null;
|
|
13440
12107
|
} catch {
|
|
13441
|
-
const parent =
|
|
12108
|
+
const parent = dirname(current);
|
|
13442
12109
|
if (parent === current) return null;
|
|
13443
12110
|
current = parent;
|
|
13444
12111
|
}
|
|
@@ -13477,7 +12144,7 @@ function runCli(moduleUrl, main, onError, metadata) {
|
|
|
13477
12144
|
__name(runCli, "runCli");
|
|
13478
12145
|
|
|
13479
12146
|
// ../core/src/completed-output-journal.ts
|
|
13480
|
-
import { createHash as
|
|
12147
|
+
import { createHash as createHash4, randomUUID as randomUUID7 } from "node:crypto";
|
|
13481
12148
|
import {
|
|
13482
12149
|
closeSync as closeSync3,
|
|
13483
12150
|
existsSync as existsSync2,
|
|
@@ -13490,12 +12157,13 @@ import {
|
|
|
13490
12157
|
unlinkSync as unlinkSync3,
|
|
13491
12158
|
writeFileSync as writeFileSync3
|
|
13492
12159
|
} from "node:fs";
|
|
13493
|
-
import { join as
|
|
12160
|
+
import { join as join4 } from "node:path";
|
|
13494
12161
|
function createCompletedOutputJournal(directory, scope5, options) {
|
|
13495
12162
|
const label = options.label;
|
|
13496
|
-
const folder =
|
|
13497
|
-
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");
|
|
13498
12165
|
function syncDirectory() {
|
|
12166
|
+
if (process.platform === "win32") return;
|
|
13499
12167
|
const fd = openSync3(folder, "r");
|
|
13500
12168
|
try {
|
|
13501
12169
|
fsyncSync(fd);
|
|
@@ -13504,8 +12172,8 @@ function createCompletedOutputJournal(directory, scope5, options) {
|
|
|
13504
12172
|
}
|
|
13505
12173
|
}
|
|
13506
12174
|
__name(syncDirectory, "syncDirectory");
|
|
13507
|
-
function validate(
|
|
13508
|
-
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)) {
|
|
13509
12177
|
throw new Error(`Invalid completed ${label} output record; operator reconciliation is required`);
|
|
13510
12178
|
}
|
|
13511
12179
|
}
|
|
@@ -13520,21 +12188,21 @@ function createCompletedOutputJournal(directory, scope5, options) {
|
|
|
13520
12188
|
} catch (error) {
|
|
13521
12189
|
if (error.code !== "ESRCH") continue;
|
|
13522
12190
|
}
|
|
13523
|
-
const path =
|
|
13524
|
-
let
|
|
12191
|
+
const path = join4(folder, name);
|
|
12192
|
+
let record6;
|
|
13525
12193
|
try {
|
|
13526
|
-
|
|
13527
|
-
validate(
|
|
12194
|
+
record6 = JSON.parse(readFileSync4(path, "utf8"));
|
|
12195
|
+
validate(record6);
|
|
13528
12196
|
} catch {
|
|
13529
12197
|
unlinkSync3(path);
|
|
13530
12198
|
throw new Error(`Incomplete ${label} output write removed; reconcile its native transcript`);
|
|
13531
12199
|
}
|
|
13532
|
-
const destination = filename(
|
|
13533
|
-
if (destination !==
|
|
12200
|
+
const destination = filename(record6.turnId);
|
|
12201
|
+
if (destination !== join4(folder, `${match[1]}.json`)) {
|
|
13534
12202
|
throw new Error(`Completed ${label} output temporary file identity mismatch`);
|
|
13535
12203
|
}
|
|
13536
12204
|
if (existsSync2(destination)) {
|
|
13537
|
-
if (readFileSync4(destination, "utf8") !== JSON.stringify(
|
|
12205
|
+
if (readFileSync4(destination, "utf8") !== JSON.stringify(record6)) {
|
|
13538
12206
|
throw new Error(`Conflicting completed ${label} outputs require reconciliation`);
|
|
13539
12207
|
}
|
|
13540
12208
|
unlinkSync3(path);
|
|
@@ -13544,11 +12212,11 @@ function createCompletedOutputJournal(directory, scope5, options) {
|
|
|
13544
12212
|
}
|
|
13545
12213
|
__name(recoverTemporaryFiles, "recoverTemporaryFiles");
|
|
13546
12214
|
return {
|
|
13547
|
-
save(
|
|
13548
|
-
validate(
|
|
12215
|
+
save(record6) {
|
|
12216
|
+
validate(record6);
|
|
13549
12217
|
mkdirSync2(folder, { recursive: true, mode: 448 });
|
|
13550
|
-
const path = filename(
|
|
13551
|
-
const serialized = JSON.stringify(
|
|
12218
|
+
const path = filename(record6.turnId);
|
|
12219
|
+
const serialized = JSON.stringify(record6);
|
|
13552
12220
|
if (existsSync2(path)) {
|
|
13553
12221
|
if (readFileSync4(path, "utf8") !== serialized) throw new Error(`Completed ${label} output cannot be replaced`);
|
|
13554
12222
|
return;
|
|
@@ -13576,10 +12244,10 @@ function createCompletedOutputJournal(directory, scope5, options) {
|
|
|
13576
12244
|
if (!existsSync2(folder)) return [];
|
|
13577
12245
|
recoverTemporaryFiles();
|
|
13578
12246
|
return readdirSync(folder).filter((name) => name.endsWith(".json")).map((name) => {
|
|
13579
|
-
const
|
|
13580
|
-
validate(
|
|
13581
|
-
if (filename(
|
|
13582
|
-
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;
|
|
13583
12251
|
}).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
13584
12252
|
},
|
|
13585
12253
|
remove(turnId) {
|
|
@@ -13593,8 +12261,8 @@ function createCompletedOutputJournal(directory, scope5, options) {
|
|
|
13593
12261
|
};
|
|
13594
12262
|
}
|
|
13595
12263
|
__name(createCompletedOutputJournal, "createCompletedOutputJournal");
|
|
13596
|
-
function assertCompletedOutputAudience(
|
|
13597
|
-
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())) {
|
|
13598
12266
|
throw new Error(`Completed ${label} output audience changed; operator reconciliation is required`);
|
|
13599
12267
|
}
|
|
13600
12268
|
}
|
|
@@ -13602,9 +12270,9 @@ __name(assertCompletedOutputAudience, "assertCompletedOutputAudience");
|
|
|
13602
12270
|
|
|
13603
12271
|
// src/bounded-file-read.ts
|
|
13604
12272
|
var BoundedFileReadError = class extends Error {
|
|
13605
|
-
constructor(
|
|
13606
|
-
super(
|
|
13607
|
-
this.failure =
|
|
12273
|
+
constructor(failure) {
|
|
12274
|
+
super(failure);
|
|
12275
|
+
this.failure = failure;
|
|
13608
12276
|
this.name = "BoundedFileReadError";
|
|
13609
12277
|
}
|
|
13610
12278
|
failure;
|
|
@@ -13643,7 +12311,7 @@ var ANTHROPIC_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
|
13643
12311
|
"image/gif",
|
|
13644
12312
|
"image/webp"
|
|
13645
12313
|
]);
|
|
13646
|
-
var DEFAULT_MEDIA_CACHE_DIR =
|
|
12314
|
+
var DEFAULT_MEDIA_CACHE_DIR = join5(CANON_DIR, "media-cache");
|
|
13647
12315
|
var DEFAULT_MEDIA_MATERIALIZATION_BYTES = 10 * 1024 * 1024;
|
|
13648
12316
|
var MAX_CANON_MEDIA_BYTES = 100 * 1024 * 1024;
|
|
13649
12317
|
var MAX_ANTHROPIC_IMAGE_RAW_BYTES = 7 * 1024 * 1024;
|
|
@@ -13711,7 +12379,7 @@ function inferExtension(input) {
|
|
|
13711
12379
|
}
|
|
13712
12380
|
__name(inferExtension, "inferExtension");
|
|
13713
12381
|
function conversationMediaCacheDir(input) {
|
|
13714
|
-
return
|
|
12382
|
+
return join5(
|
|
13715
12383
|
input.rootDir ?? DEFAULT_MEDIA_CACHE_DIR,
|
|
13716
12384
|
sanitizeSegment(input.agentId, "agent"),
|
|
13717
12385
|
sanitizeSegment(input.conversationId, "conversation")
|
|
@@ -13727,7 +12395,7 @@ function buildCachePath(input) {
|
|
|
13727
12395
|
const fallbackName = `${input.attachment.kind}-${input.index}${extension}`;
|
|
13728
12396
|
const preferredName = input.attachment.fileName ? sanitizeDownloadFileName(input.attachment.fileName, fallbackName) : fallbackName;
|
|
13729
12397
|
const fileName = extname(preferredName) ? preferredName : `${preferredName}${extension}`;
|
|
13730
|
-
return
|
|
12398
|
+
return join5(
|
|
13731
12399
|
conversationMediaCacheDir({ agentId: input.agentId, conversationId: input.conversationId, rootDir }),
|
|
13732
12400
|
sanitizeSegment(input.messageId, "message"),
|
|
13733
12401
|
`${String(input.index).padStart(2, "0")}-${fileName}`
|
|
@@ -13840,7 +12508,7 @@ async function materializeAttachment(attachment, options) {
|
|
|
13840
12508
|
index: options.index ?? 0,
|
|
13841
12509
|
rootDir: options.rootDir
|
|
13842
12510
|
});
|
|
13843
|
-
await
|
|
12511
|
+
await mkdir2(dirname2(path), { recursive: true });
|
|
13844
12512
|
const maxBytes = resolveMaxBytes(options.maxBytes);
|
|
13845
12513
|
const expectedBytes = typeof attachment.sizeBytes === "number" && Number.isSafeInteger(attachment.sizeBytes) && attachment.sizeBytes >= 0 ? attachment.sizeBytes : null;
|
|
13846
12514
|
if (expectedBytes !== null && expectedBytes > maxBytes) {
|
|
@@ -14056,7 +12724,7 @@ async function toAnthropicImageBlock(attachment, options) {
|
|
|
14056
12724
|
}
|
|
14057
12725
|
__name(toAnthropicImageBlock, "toAnthropicImageBlock");
|
|
14058
12726
|
async function readAnthropicImageBytes(attachment, maxRawBytes) {
|
|
14059
|
-
const handle = await
|
|
12727
|
+
const handle = await open2(attachment.path, "r");
|
|
14060
12728
|
try {
|
|
14061
12729
|
return await readBoundedRegularFileHandle(handle, maxRawBytes);
|
|
14062
12730
|
} catch (error) {
|
|
@@ -14162,9 +12830,6 @@ function getCodexImagePath(attachment) {
|
|
|
14162
12830
|
__name(getCodexImagePath, "getCodexImagePath");
|
|
14163
12831
|
|
|
14164
12832
|
export {
|
|
14165
|
-
NodeMlsDevCustodyError,
|
|
14166
|
-
createNodeMlsDevCustody,
|
|
14167
|
-
createPortableNodeMlsDevCustody,
|
|
14168
12833
|
resolveCanonRuntimeConnection,
|
|
14169
12834
|
verifyCanonRuntimeConnection,
|
|
14170
12835
|
CanonClient,
|