@ours.network/cli 0.6.0 → 1.0.0

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.
@@ -0,0 +1,2530 @@
1
+ import {
2
+ apiTokenPath,
3
+ assertConfigNotDrifted,
4
+ explicitApiToken,
5
+ loadConfig,
6
+ resolveApiToken
7
+ } from "./chunk-FXKFSNKS.js";
8
+
9
+ // ../../src/boot.ts
10
+ import { adapt_wrapper } from "@adapt-toolkit/sdk/executables";
11
+
12
+ // ../../src/runtime/env.ts
13
+ import { timingSafeEqual } from "node:crypto";
14
+
15
+ // ../../src/startup-progress.ts
16
+ import * as fs from "node:fs";
17
+ import { join } from "node:path";
18
+ var STARTUP_PROGRESS_FILENAME = "startup-progress.json";
19
+ var STARTUP_PROGRESS_VERSION = 1;
20
+ function startupProgressPath(stateDir) {
21
+ return join(stateDir, STARTUP_PROGRESS_FILENAME);
22
+ }
23
+ function createStartupProgressReporter(stateDir, opts = {}) {
24
+ const pid = opts.pid ?? process.pid;
25
+ const now = opts.now ?? Date.now;
26
+ const startedAt = now();
27
+ const path = startupProgressPath(stateDir);
28
+ const tmp = `${path}.${pid}.tmp`;
29
+ const heartbeatMs = Math.max(100, opts.heartbeatMs ?? 2e3);
30
+ let stopped = false;
31
+ let writeDisabled = false;
32
+ let progress = {
33
+ version: STARTUP_PROGRESS_VERSION,
34
+ pid,
35
+ bootId: `${pid}-${startedAt}`,
36
+ phase: "initializing",
37
+ startedAt,
38
+ updatedAt: startedAt
39
+ };
40
+ const write = () => {
41
+ if (writeDisabled) return;
42
+ try {
43
+ fs.mkdirSync(stateDir, { recursive: true, mode: 448 });
44
+ fs.writeFileSync(tmp, `${JSON.stringify(progress)}
45
+ `, { mode: 384 });
46
+ fs.renameSync(tmp, path);
47
+ try {
48
+ fs.chmodSync(path, 384);
49
+ } catch {
50
+ }
51
+ } catch {
52
+ writeDisabled = true;
53
+ try {
54
+ fs.rmSync(tmp, { force: true });
55
+ } catch {
56
+ }
57
+ }
58
+ };
59
+ const update = (phase, counts) => {
60
+ progress = {
61
+ version: STARTUP_PROGRESS_VERSION,
62
+ pid,
63
+ bootId: progress.bootId,
64
+ phase,
65
+ ...counts ? { completed: counts.completed, total: counts.total } : {},
66
+ startedAt,
67
+ updatedAt: now()
68
+ };
69
+ write();
70
+ };
71
+ write();
72
+ const heartbeat = setInterval(() => {
73
+ if (stopped) return;
74
+ progress = { ...progress, updatedAt: now() };
75
+ write();
76
+ }, heartbeatMs);
77
+ heartbeat.unref?.();
78
+ const finish = (phase) => {
79
+ if (stopped) return;
80
+ update(phase);
81
+ stopped = true;
82
+ clearInterval(heartbeat);
83
+ };
84
+ return {
85
+ update,
86
+ ready: () => finish("ready"),
87
+ failed: () => finish("failed")
88
+ };
89
+ }
90
+
91
+ // ../../src/runtime/env.ts
92
+ var VERSION = typeof __OURS_VERSION__ !== "undefined" ? __OURS_VERSION__ : "0.0.0-dev";
93
+ var CONFIG = loadConfig();
94
+ var STATE_DIR = CONFIG.stateDir;
95
+ var BROKER_URL = CONFIG.brokerUrl;
96
+ var TRANSPORT = process.env.OURS_TRANSPORT ?? "http";
97
+ var PORT = CONFIG.port;
98
+ var GC_INTERVAL_MS = CONFIG.gcIntervalMs;
99
+ var API_VISIBILITY = CONFIG.apiVisibility;
100
+ var startupHeartbeatMs = Number(process.env.OURS_TEST_STARTUP_HEARTBEAT_MS || "") || void 0;
101
+ var startupProgress = TRANSPORT === "http" ? createStartupProgressReporter(STATE_DIR, { heartbeatMs: startupHeartbeatMs }) : null;
102
+ var log = (...parts) => process.stderr.write(`ours: ${parts.join(" ")}
103
+ `);
104
+ function resolveDaemonToken() {
105
+ if (API_VISIBILITY === "open") return null;
106
+ if (API_VISIBILITY === "shared") {
107
+ const explicit = explicitApiToken(CONFIG);
108
+ if (!explicit) {
109
+ throw new Error(
110
+ `apiVisibility=shared requires an operator-supplied token so it can be distributed to cross-user agents \u2014 set OURS_API_TOKEN or "apiToken" in config (${apiTokenPath(CONFIG)} is not used for shared mode). Use apiVisibility=owner for a same-user-only auto-token, or =open to disable auth.`
111
+ );
112
+ }
113
+ return explicit;
114
+ }
115
+ return resolveApiToken(CONFIG, { generate: true }).token;
116
+ }
117
+ var API_TOKEN = resolveDaemonToken();
118
+ function tokenFromReq(req) {
119
+ const direct = req.headers["x-ours-api-token"];
120
+ if (typeof direct === "string" && direct) return direct;
121
+ const auth = req.headers["authorization"];
122
+ if (typeof auth === "string") {
123
+ const m = /^Bearer\s+(.+)$/i.exec(auth.trim());
124
+ if (m) return m[1];
125
+ }
126
+ return void 0;
127
+ }
128
+ function authOk(req) {
129
+ if (API_TOKEN === null) return true;
130
+ const provided = tokenFromReq(req);
131
+ if (!provided) return false;
132
+ const a = Buffer.from(provided);
133
+ const b = Buffer.from(API_TOKEN);
134
+ return a.length === b.length && timingSafeEqual(a, b);
135
+ }
136
+ function requireAuth(req, res) {
137
+ if (authOk(req)) return true;
138
+ res.writeHead(401, { "Content-Type": "application/json" });
139
+ res.end(JSON.stringify({ error: "unauthorized" }));
140
+ return false;
141
+ }
142
+ var NAME_RE = /^[A-Za-z0-9 _.@-]{1,64}$/;
143
+ var BOOK_DIR_NAME = "contact-book";
144
+ function validateName(name) {
145
+ if (!NAME_RE.test(name)) {
146
+ return "name must be 1-64 chars of letters, digits, space, _ . @ or -";
147
+ }
148
+ if (name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
149
+ return "invalid name";
150
+ }
151
+ if (name === BOOK_DIR_NAME) {
152
+ return `"${BOOK_DIR_NAME}" is reserved for the local contact book`;
153
+ }
154
+ if (name === "root.json" || name === "bindings.json") {
155
+ return `"${name}" is reserved for daemon bookkeeping`;
156
+ }
157
+ return null;
158
+ }
159
+
160
+ // ../../src/runtime/unit.ts
161
+ import { resolve, join as join2, dirname } from "node:path";
162
+ import { fileURLToPath } from "node:url";
163
+ import * as fs2 from "node:fs";
164
+ function locateUnit() {
165
+ const here = dirname(fileURLToPath(import.meta.url));
166
+ const override = process.env.OURS_UNIT_DIR;
167
+ const noCap = process.env.OURS_ADVERTISE_MIGRATE === "0";
168
+ const withNoCap = (dir) => noCap ? [`${dir}-nocap`] : [dir];
169
+ const candidates = override ? withNoCap(resolve(override)) : [join2(here, "mufl_code"), join2(here, "..", "mufl_code")].flatMap(withNoCap);
170
+ for (const dir of candidates) {
171
+ if (!fs2.existsSync(dir)) continue;
172
+ const muflo = fs2.readdirSync(dir).find((f) => f.endsWith(".muflo"));
173
+ if (muflo) {
174
+ const hash = muflo.slice(0, -".muflo".length);
175
+ const contents = new Uint8Array(fs2.readFileSync(join2(dir, muflo)));
176
+ return { dir, hash, contents };
177
+ }
178
+ }
179
+ throw new Error(
180
+ `no compiled .muflo packet found (looked in: ${candidates.join(", ")})` + (noCap ? ' \u2014 OURS_ADVERTISE_MIGRATE=0 requires a "<dir>-nocap" packet variant (compiled with $advertise = [core.e2e] only); build it or unset the env.' : "")
181
+ );
182
+ }
183
+ var UNIT;
184
+ function setUnit(u) {
185
+ UNIT = u;
186
+ }
187
+
188
+ // ../../src/identity/model.ts
189
+ import { join as join3 } from "node:path";
190
+ import { createHash } from "node:crypto";
191
+ import * as fs3 from "node:fs";
192
+
193
+ // ../../src/constants.ts
194
+ var FILE_SELECTION_CAP = 32;
195
+
196
+ // ../../src/identity/model.ts
197
+ var wrapper;
198
+ function setWrapper(w) {
199
+ wrapper = w;
200
+ }
201
+ var identities = /* @__PURE__ */ new Map();
202
+ var registrar = null;
203
+ var registrarAdBlob = null;
204
+ function setRegistrar(id) {
205
+ registrar = id;
206
+ }
207
+ function setRegistrarAdBlob(blob) {
208
+ registrarAdBlob = blob;
209
+ }
210
+ var identityDir = (name) => join3(STATE_DIR, name);
211
+ var keyPath = (dir) => join3(dir, "identity.key");
212
+ var dataPath = (dir) => join3(dir, "state_data.bin");
213
+ var notifyLogPath = (dir) => join3(dir, "notifications.log");
214
+ var unreadPath = (dir) => join3(dir, "unread.json");
215
+ var filesDirFor = (id) => join3(id.dir, "files");
216
+ var tempMetaPath = (dir) => join3(dir, "temp.json");
217
+ var hashLeaseToken = (token) => createHash("sha256").update(token).digest("hex");
218
+ function writeTempMetaFile(dir, meta) {
219
+ fs3.writeFileSync(
220
+ tempMetaPath(dir),
221
+ JSON.stringify({ v: 1, owner: { token_sha256: meta.owner.tokenHash, pid: meta.owner.pid }, created_at: meta.createdAt }),
222
+ { mode: 384 }
223
+ );
224
+ }
225
+ function readTempMetaFile(dir) {
226
+ try {
227
+ const raw = JSON.parse(fs3.readFileSync(tempMetaPath(dir), "utf8"));
228
+ if (raw.v !== 1 || typeof raw.owner?.token_sha256 !== "string" || typeof raw.owner?.pid !== "number") return null;
229
+ return {
230
+ owner: { tokenHash: raw.owner.token_sha256, pid: raw.owner.pid },
231
+ createdAt: typeof raw.created_at === "number" ? raw.created_at : 0
232
+ };
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+ function tightenIdentityPerms() {
238
+ for (const name of listPersistedNames()) {
239
+ const dir = join3(STATE_DIR, name);
240
+ try {
241
+ fs3.chmodSync(dir, 448);
242
+ } catch (err) {
243
+ log(`[${name}] chmod 0700 failed:`, String(err));
244
+ }
245
+ for (const f of [dataPath(dir), keyPath(dir)]) {
246
+ if (!fs3.existsSync(f)) continue;
247
+ try {
248
+ fs3.chmodSync(f, 384);
249
+ } catch (err) {
250
+ log(`[${name}] chmod 0600 ${f} failed:`, String(err));
251
+ }
252
+ }
253
+ }
254
+ }
255
+ var isWireId = (s) => /^[A-Za-z0-9]+$/.test(s) && s.length > 0 && s.length <= 128;
256
+ var isSelectableWireId = (s) => /^[A-Fa-f0-9]{64}$/.test(s);
257
+ function findIdentityFile(id, wireId) {
258
+ if (!isWireId(wireId)) return null;
259
+ const dir = filesDirFor(id);
260
+ let entries;
261
+ try {
262
+ entries = fs3.readdirSync(dir);
263
+ } catch {
264
+ return null;
265
+ }
266
+ const prefix = `${wireId}-`;
267
+ const match = entries.find((name) => name.startsWith(prefix));
268
+ return match ? join3(dir, match) : null;
269
+ }
270
+ function listPersistedNames() {
271
+ if (!fs3.existsSync(STATE_DIR)) return [];
272
+ return fs3.readdirSync(STATE_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && fs3.existsSync(keyPath(join3(STATE_DIR, d.name)))).map((d) => d.name);
273
+ }
274
+
275
+ // ../../src/identity/lease.ts
276
+ import { join as join4 } from "node:path";
277
+ import * as fs4 from "node:fs";
278
+ var leases = /* @__PURE__ */ new Map();
279
+ var tombstones = /* @__PURE__ */ new Set();
280
+ var sessionHeaders = /* @__PURE__ */ new Map();
281
+ var outboundRemovalInFlight = /* @__PURE__ */ new Set();
282
+ function pidAlive(pid) {
283
+ if (!Number.isInteger(pid) || pid <= 0) return false;
284
+ try {
285
+ process.kill(pid, 0);
286
+ return true;
287
+ } catch (err) {
288
+ return err.code === "EPERM";
289
+ }
290
+ }
291
+ function leaseByToken(token) {
292
+ for (const l of leases.values()) if (l.token === token) return l;
293
+ return void 0;
294
+ }
295
+ var bindingsSnapshotPath = () => join4(STATE_DIR, "bindings.json");
296
+ function persistBindings() {
297
+ try {
298
+ fs4.mkdirSync(STATE_DIR, { recursive: true });
299
+ const tmp = `${bindingsSnapshotPath()}.tmp`;
300
+ fs4.writeFileSync(tmp, JSON.stringify({
301
+ pid: process.pid,
302
+ bound: [...leases.keys()],
303
+ holders: [...leases.values()].map((l) => ({ identity: l.identity, pid: l.pid }))
304
+ }));
305
+ fs4.renameSync(tmp, bindingsSnapshotPath());
306
+ } catch (err) {
307
+ log("failed to persist bindings snapshot:", String(err));
308
+ }
309
+ }
310
+ function resolveBound(sid) {
311
+ const token = sessionHeaders.get(sid)?.token;
312
+ if (!token) {
313
+ return { code: "NOT_BOUND", error: "No identity bound to this session. Call choose_identity (or create_identity) first." };
314
+ }
315
+ if (tombstones.has(token)) {
316
+ return { code: "BINDING_REASSIGNED", error: "Your identity binding was reassigned to another session. Call choose_identity again to continue." };
317
+ }
318
+ const lease = leaseByToken(token);
319
+ if (!lease) {
320
+ return { code: "NOT_BOUND", error: "No identity bound to this session. Call choose_identity (or create_identity) first." };
321
+ }
322
+ const id = identities.get(lease.identity);
323
+ if (!id) {
324
+ leases.delete(lease.identity);
325
+ persistBindings();
326
+ return { code: "BOUND_IDENTITY_GONE", error: `The bound identity "${lease.identity}" no longer exists. Choose another with choose_identity.` };
327
+ }
328
+ if (id.temp?.closing) {
329
+ return {
330
+ code: "TEMP_CLOSING",
331
+ error: `The temporary identity "${id.name}" is closing and no longer accepts work. Its contacts are being notified and its local state deleted.`
332
+ };
333
+ }
334
+ lease.sid = sid;
335
+ const pid = sessionHeaders.get(sid)?.pid;
336
+ if (pid) lease.pid = pid;
337
+ return { id };
338
+ }
339
+ function bindSession(sid, name) {
340
+ const hdr = sessionHeaders.get(sid);
341
+ const token = hdr?.token;
342
+ if (!token) return;
343
+ for (const [n, l] of [...leases]) if (l.token === token && n !== name) leases.delete(n);
344
+ const prevEpoch = leases.get(name)?.epoch ?? 0;
345
+ leases.set(name, { identity: name, token, pid: hdr?.pid ?? 0, sid, epoch: prevEpoch + 1, boundAt: Date.now() });
346
+ tombstones.delete(token);
347
+ persistBindings();
348
+ }
349
+
350
+ // ../../src/identity/hierarchy.ts
351
+ import { join as join8 } from "node:path";
352
+ import * as fs9 from "node:fs";
353
+
354
+ // ../../src/mufl/tx.ts
355
+ import { AdaptObjectLifetime as AdaptObjectLifetime2 } from "@adapt-toolkit/sdk/common";
356
+ import { object_to_adapt_value } from "@adapt-toolkit/sdk/wrapper";
357
+
358
+ // ../../src/state.ts
359
+ import * as fs8 from "node:fs";
360
+ import { randomBytes as randomBytes2 } from "node:crypto";
361
+
362
+ // ../../src/identity/provision.ts
363
+ import * as fs7 from "node:fs";
364
+ import { randomBytes } from "node:crypto";
365
+ import { PacketWrapperConfigurator } from "@adapt-toolkit/sdk/wrappers";
366
+
367
+ // ../../src/mufl/handlers.ts
368
+ import { AdaptObjectLifetime } from "@adapt-toolkit/sdk/common";
369
+
370
+ // ../../src/notify.ts
371
+ import { join as join6 } from "node:path";
372
+ import * as fs5 from "node:fs";
373
+
374
+ // ../../src/inbox.ts
375
+ var E2E_RECV_EVENTS = /* @__PURE__ */ new Set(["e2e_app_recv", "migration_deferred_flush"]);
376
+ function e2eWireIdsFromEvents(events) {
377
+ const out = /* @__PURE__ */ new Set();
378
+ for (const ev of events) {
379
+ if (!ev || typeof ev !== "object") continue;
380
+ const event = ev.event;
381
+ if (typeof event !== "string" || !E2E_RECV_EVENTS.has(event)) continue;
382
+ const wire = ev.wire_id;
383
+ if (typeof wire === "string" && wire.length > 0) out.add(wire);
384
+ }
385
+ return out;
386
+ }
387
+ function encryptionFor(wireId, e2eWireIds) {
388
+ return wireId && e2eWireIds.has(wireId) ? "e2e" : "legacy";
389
+ }
390
+ function toMessageJson(m, e2eWireIds) {
391
+ const encryption = encryptionFor(m.wire_id, e2eWireIds);
392
+ return {
393
+ msg_id: m.msg_id,
394
+ wire_id: m.wire_id,
395
+ from: { id: m.sender_id, name: m.sender_name },
396
+ encryption,
397
+ transport: encryption === "e2e" ? "double_ratchet" : "legacy_box",
398
+ text: m.text,
399
+ date: m.date,
400
+ status: m.status,
401
+ reply_to: m.reply_to
402
+ };
403
+ }
404
+ function buildMessagesPayload(msgs, e2eWireIds) {
405
+ const messages = msgs.map((m) => toMessageJson(m, e2eWireIds));
406
+ return { count: messages.length, messages };
407
+ }
408
+
409
+ // ../../src/events.ts
410
+ var nonEmptyString = (value) => typeof value === "string" && value.length > 0;
411
+ var syncRequired = (source_event) => ({
412
+ event: "sync_required",
413
+ reason: "legacy_or_malformed_event",
414
+ source_event
415
+ });
416
+ function normalizeNotificationEvent(record) {
417
+ if (!nonEmptyString(record.event)) return syncRequired("unknown");
418
+ if (record.event === "message_received") {
419
+ if (!nonEmptyString(record.sender_id) || !nonEmptyString(record.sender_name) || !nonEmptyString(record.msg_id) || !nonEmptyString(record.wire_id) || !nonEmptyString(record.date)) return syncRequired("message_received");
420
+ return {
421
+ event: "message_received",
422
+ sender_id: record.sender_id,
423
+ sender_name: record.sender_name,
424
+ from: nonEmptyString(record.from) ? record.from : record.sender_name,
425
+ msg_id: record.msg_id,
426
+ wire_id: record.wire_id,
427
+ date: record.date
428
+ };
429
+ }
430
+ if (record.event === "receipt_received") {
431
+ const kind = record.kind;
432
+ const wireIds = record.wire_ids;
433
+ if (!nonEmptyString(record.sender_id) || kind !== "delivered" && kind !== "read" || !Array.isArray(wireIds) || wireIds.length === 0 || !wireIds.every(nonEmptyString)) {
434
+ return syncRequired("receipt_received");
435
+ }
436
+ return {
437
+ event: "receipt_received",
438
+ sender_id: record.sender_id,
439
+ kind,
440
+ wire_ids: [...wireIds],
441
+ // Older receipt callbacks did not carry protocol time. Observation time
442
+ // is explicitly a fallback, not a claim about when the peer sent it.
443
+ date: nonEmptyString(record.date) ? record.date : (/* @__PURE__ */ new Date()).toISOString()
444
+ };
445
+ }
446
+ return record;
447
+ }
448
+ var defaults = {
449
+ event: () => {
450
+ },
451
+ notification: () => {
452
+ }
453
+ };
454
+ var handlers = { ...defaults };
455
+ function setDaemonEventHandler(name, handler) {
456
+ handlers[name] = handler;
457
+ return () => {
458
+ if (handlers[name] === handler) handlers[name] = defaults[name];
459
+ };
460
+ }
461
+ function emitDaemonEvent(id, record) {
462
+ const normalized = normalizeNotificationEvent(record);
463
+ handlers.event({
464
+ identity: id.name,
465
+ event: normalized.event,
466
+ record: normalized
467
+ });
468
+ }
469
+ function emitDaemonNotification(identityName, summary) {
470
+ handlers.notification(identityName, summary);
471
+ }
472
+
473
+ // ../../src/render/adapt-to-json.ts
474
+ import { basename as basename2, join as join5 } from "node:path";
475
+ import { createHash as createHash2, randomUUID } from "node:crypto";
476
+ import { mkdir, open, rename, unlink, lstat } from "node:fs/promises";
477
+ import { brotliCompressSync, brotliDecompressSync } from "node:zlib";
478
+
479
+ // ../../src/files.ts
480
+ import { basename, extname } from "node:path";
481
+ var FILE_MIME = {
482
+ ".png": "image/png",
483
+ ".jpg": "image/jpeg",
484
+ ".jpeg": "image/jpeg",
485
+ ".gif": "image/gif",
486
+ ".webp": "image/webp",
487
+ ".svg": "image/svg+xml",
488
+ ".bmp": "image/bmp",
489
+ ".ico": "image/x-icon",
490
+ ".pdf": "application/pdf",
491
+ ".txt": "text/plain",
492
+ ".md": "text/markdown",
493
+ ".json": "application/json",
494
+ ".csv": "text/csv",
495
+ ".html": "text/html",
496
+ ".xml": "application/xml",
497
+ ".zip": "application/zip",
498
+ ".gz": "application/gzip",
499
+ ".tar": "application/x-tar",
500
+ ".mp3": "audio/mpeg",
501
+ ".wav": "audio/wav",
502
+ ".mp4": "video/mp4",
503
+ ".mov": "video/quicktime"
504
+ };
505
+ function mimeFromExt(p) {
506
+ return FILE_MIME[extname(p).toLowerCase()] ?? "application/octet-stream";
507
+ }
508
+ function sanitizeFilename(name) {
509
+ const base = basename(name).replace(/[^A-Za-z0-9._-]/g, "_");
510
+ return base.length ? base.slice(0, 200) : "file";
511
+ }
512
+
513
+ // ../../src/transcribe.ts
514
+ var VOICE_MESSAGE_MIME_PARAM = "x-ours-kind=voice-message";
515
+ var VOICE_MESSAGE_FILENAME_PREFIX = "voice-message-";
516
+ function isVoiceMessage(mime, filename) {
517
+ const parts = (mime ?? "").toLowerCase().split(";").map((p) => p.trim());
518
+ if (parts[0].startsWith("audio/") && parts.slice(1).includes(VOICE_MESSAGE_MIME_PARAM)) return true;
519
+ return filename.toLowerCase().startsWith(VOICE_MESSAGE_FILENAME_PREFIX) && parts[0].startsWith("audio/");
520
+ }
521
+ function baseMime(mime) {
522
+ return (mime ?? "").split(";")[0].trim() || "application/octet-stream";
523
+ }
524
+ var STT_PROVIDERS = ["openai-compatible", "elevenlabs", "deepgram", "custom"];
525
+ var STT_MAX_BYTES_DEFAULT = 5 * 1024 * 1024;
526
+ var STT_TIMEOUT_MS_DEFAULT = 6e4;
527
+ function sttStatus(cfg) {
528
+ const hint = "configure it in config.json `stt: {}` or via OURS_STT_* env";
529
+ if (!cfg?.provider) {
530
+ return { ready: false, reason: `no STT provider configured (stt.provider: ${STT_PROVIDERS.join(" | ")}) \u2014 ${hint}` };
531
+ }
532
+ const provider = cfg.provider.trim().toLowerCase();
533
+ if (!STT_PROVIDERS.includes(provider)) {
534
+ return { ready: false, reason: `unknown STT provider "${cfg.provider}" (expected: ${STT_PROVIDERS.join(" | ")})` };
535
+ }
536
+ if (!cfg.apiKey?.trim()) {
537
+ return { ready: false, reason: `STT provider "${provider}" is set but the API key is missing (stt.apiKey / OURS_STT_API_KEY)` };
538
+ }
539
+ if (provider === "openai-compatible") {
540
+ if (!cfg.baseUrl?.trim()) {
541
+ return { ready: false, reason: "openai-compatible STT needs stt.baseUrl / OURS_STT_BASE_URL (e.g. your provider's /v1 root) \u2014 no endpoint is assumed" };
542
+ }
543
+ if (!cfg.model?.trim()) {
544
+ return { ready: false, reason: "openai-compatible STT needs stt.model / OURS_STT_MODEL (passed to the provider verbatim) \u2014 no model is assumed" };
545
+ }
546
+ }
547
+ if (provider === "elevenlabs" && !cfg.model?.trim()) {
548
+ return { ready: false, reason: "elevenlabs STT needs stt.model / OURS_STT_MODEL (the model_id, e.g. from your ElevenLabs account) \u2014 no model is assumed" };
549
+ }
550
+ if (provider === "custom") {
551
+ if (!cfg.custom?.url?.trim()) {
552
+ return { ready: false, reason: "custom STT needs stt.custom.url (the full endpoint URL of your provider)" };
553
+ }
554
+ const wantsModel = cfg.custom.url.includes("{model}") || cfg.custom.modelField !== void 0 && cfg.custom.modelField !== "";
555
+ if (wantsModel && !cfg.model?.trim()) {
556
+ return { ready: false, reason: "the custom STT template references a model (url {model} or modelField) but stt.model / OURS_STT_MODEL is not set" };
557
+ }
558
+ }
559
+ return { ready: true, provider };
560
+ }
561
+ function textAtPath(obj, path) {
562
+ let cur = obj;
563
+ for (const seg of path.split(".")) {
564
+ if (cur === null || typeof cur !== "object") return void 0;
565
+ cur = cur[seg];
566
+ }
567
+ return typeof cur === "string" ? cur : void 0;
568
+ }
569
+ async function request(url, init, timeoutMs, secrets = []) {
570
+ const aborter = new AbortController();
571
+ const timer = setTimeout(() => aborter.abort(), timeoutMs);
572
+ try {
573
+ const resp = await fetch(url, { ...init, signal: aborter.signal });
574
+ if (!resp.ok) {
575
+ let detail = await resp.text().catch(() => "");
576
+ for (const secret of secrets) {
577
+ if (secret) detail = detail.split(secret).join("[redacted]");
578
+ }
579
+ return { ok: false, error: `STT HTTP ${resp.status}${detail ? `: ${detail.slice(0, 200)}` : ""}` };
580
+ }
581
+ return { ok: true, json: await resp.json() };
582
+ } catch (err) {
583
+ if (err?.name === "AbortError") {
584
+ return { ok: false, error: `STT timeout after ${timeoutMs}ms` };
585
+ }
586
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
587
+ } finally {
588
+ clearTimeout(timer);
589
+ }
590
+ }
591
+ var audioBlob = (bytes, mime) => (
592
+ // Copy into a plain Uint8Array — a Buffer is not a valid BlobPart.
593
+ new Blob([new Uint8Array(bytes)], { type: baseMime(mime) })
594
+ );
595
+ var openaiCompatible = async (bytes, filename, mime, cfg, timeoutMs) => {
596
+ const form = new FormData();
597
+ form.set("file", audioBlob(bytes, mime), filename);
598
+ form.set("model", cfg.model.trim());
599
+ form.set("response_format", "json");
600
+ if (cfg.language?.trim()) form.set("language", cfg.language.trim());
601
+ const r = await request(
602
+ `${cfg.baseUrl.trim().replace(/\/$/, "")}/audio/transcriptions`,
603
+ { method: "POST", headers: { Authorization: `Bearer ${cfg.apiKey.trim()}` }, body: form },
604
+ timeoutMs,
605
+ [cfg.apiKey.trim()]
606
+ );
607
+ if (!r.ok) return r;
608
+ const text = textAtPath(r.json, "text");
609
+ return text !== void 0 ? { ok: true, text } : { ok: false, error: "STT response missing text" };
610
+ };
611
+ var elevenlabs = async (bytes, filename, mime, cfg, timeoutMs) => {
612
+ const form = new FormData();
613
+ form.set("file", audioBlob(bytes, mime), filename);
614
+ form.set("model_id", cfg.model.trim());
615
+ if (cfg.language?.trim()) form.set("language_code", cfg.language.trim());
616
+ const r = await request(
617
+ `${(cfg.baseUrl?.trim() || "https://api.elevenlabs.io").replace(/\/$/, "")}/v1/speech-to-text`,
618
+ { method: "POST", headers: { "xi-api-key": cfg.apiKey.trim() }, body: form },
619
+ timeoutMs,
620
+ [cfg.apiKey.trim()]
621
+ );
622
+ if (!r.ok) return r;
623
+ const text = textAtPath(r.json, "text");
624
+ return text !== void 0 ? { ok: true, text } : { ok: false, error: "STT response missing text" };
625
+ };
626
+ var deepgram = async (bytes, _filename, mime, cfg, timeoutMs) => {
627
+ const params = new URLSearchParams();
628
+ if (cfg.model?.trim()) params.set("model", cfg.model.trim());
629
+ if (cfg.language?.trim()) params.set("language", cfg.language.trim());
630
+ const q = params.toString();
631
+ const r = await request(
632
+ `${(cfg.baseUrl?.trim() || "https://api.deepgram.com").replace(/\/$/, "")}/v1/listen${q ? `?${q}` : ""}`,
633
+ {
634
+ method: "POST",
635
+ headers: { Authorization: `Token ${cfg.apiKey.trim()}`, "Content-Type": baseMime(mime) },
636
+ body: new Uint8Array(bytes)
637
+ },
638
+ timeoutMs,
639
+ [cfg.apiKey.trim()]
640
+ );
641
+ if (!r.ok) return r;
642
+ const text = textAtPath(r.json, "results.channels.0.alternatives.0.transcript");
643
+ return text !== void 0 ? { ok: true, text } : { ok: false, error: "STT response missing transcript" };
644
+ };
645
+ var custom = async (bytes, filename, mime, cfg, timeoutMs) => {
646
+ const t = cfg.custom;
647
+ const model = cfg.model?.trim() ?? "";
648
+ const url = t.url.replaceAll("{model}", encodeURIComponent(model));
649
+ const headers = {};
650
+ const authName = t.authHeaderName?.trim() || "Authorization";
651
+ const authValue = (t.authHeaderTemplate ?? "Bearer {key}").replaceAll("{key}", cfg.apiKey.trim());
652
+ if (authValue) headers[authName] = authValue;
653
+ const mode = t.bodyMode ?? "multipart";
654
+ const fileField = t.fileField?.trim() || "file";
655
+ const modelField = t.modelField === void 0 ? "model" : t.modelField.trim();
656
+ let body;
657
+ if (mode === "multipart") {
658
+ const form = new FormData();
659
+ form.set(fileField, audioBlob(bytes, mime), filename);
660
+ if (modelField && model) form.set(modelField, model);
661
+ for (const [k, v] of Object.entries(t.extraFields ?? {})) form.set(k, v);
662
+ body = form;
663
+ } else if (mode === "raw") {
664
+ headers["Content-Type"] = baseMime(mime);
665
+ body = new Uint8Array(bytes);
666
+ } else {
667
+ headers["Content-Type"] = "application/json";
668
+ body = JSON.stringify({
669
+ [fileField]: bytes.toString("base64"),
670
+ ...modelField && model ? { [modelField]: model } : {},
671
+ ...t.extraFields ?? {}
672
+ });
673
+ }
674
+ const r = await request(url, { method: t.method?.trim() || "POST", headers, body }, timeoutMs, [cfg.apiKey.trim()]);
675
+ if (!r.ok) return r;
676
+ const text = textAtPath(r.json, t.responseTextPath?.trim() || "text");
677
+ return text !== void 0 ? { ok: true, text } : { ok: false, error: `STT response has no text at "${t.responseTextPath?.trim() || "text"}"` };
678
+ };
679
+ var ADAPTERS = {
680
+ "openai-compatible": openaiCompatible,
681
+ elevenlabs,
682
+ deepgram,
683
+ custom
684
+ };
685
+ async function transcribeVoice(bytes, filename, mime, cfg) {
686
+ const status = sttStatus(cfg);
687
+ if (!status.ready) return { ok: false, error: status.reason };
688
+ const maxBytes = cfg.maxBytes ?? STT_MAX_BYTES_DEFAULT;
689
+ if (bytes.length > maxBytes) {
690
+ return { ok: false, error: `voice message is ${bytes.length} B \u2014 over the ${maxBytes} B STT limit (stt.maxBytes)` };
691
+ }
692
+ const timeoutMs = cfg.timeoutMs ?? STT_TIMEOUT_MS_DEFAULT;
693
+ try {
694
+ return await ADAPTERS[status.provider](bytes, filename, mime, cfg, timeoutMs);
695
+ } catch (err) {
696
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
697
+ }
698
+ }
699
+ function voiceErrorCategory(error) {
700
+ if (/limit|over .* bytes/i.test(error)) return "size_limit";
701
+ if (/timeout/i.test(error)) return "timeout";
702
+ if (/HTTP \d+/i.test(error)) return "provider_http";
703
+ if (/missing|no text|no transcript/i.test(error)) return "invalid_response";
704
+ return "provider_error";
705
+ }
706
+ function structuredVoiceOutcome(readiness, outcome, association) {
707
+ if (!readiness.ready || outcome.kind === "unconfigured") {
708
+ return {
709
+ configured: false,
710
+ attempted: false,
711
+ status: "unavailable",
712
+ provider: null,
713
+ text: null,
714
+ error_category: "not_configured",
715
+ audio_path: association.audioPath,
716
+ file_wire_id: association.wireId
717
+ };
718
+ }
719
+ if (outcome.kind === "transcript") {
720
+ return {
721
+ configured: true,
722
+ attempted: true,
723
+ status: "succeeded",
724
+ provider: readiness.provider,
725
+ text: outcome.text,
726
+ error_category: null,
727
+ audio_path: association.audioPath,
728
+ file_wire_id: association.wireId
729
+ };
730
+ }
731
+ return {
732
+ configured: true,
733
+ attempted: true,
734
+ status: "failed",
735
+ provider: readiness.provider,
736
+ text: null,
737
+ error_category: voiceErrorCategory(outcome.error),
738
+ audio_path: association.audioPath,
739
+ file_wire_id: association.wireId
740
+ };
741
+ }
742
+ function voiceDeliveryLine(args, outcome) {
743
+ const head = ` \u2022 \u{1F3A4} voice message from ${args.sender} (${args.sizeBytes} B)`;
744
+ const tail = `audio saved \u2192 ${args.savedPath} {${args.wire}}`;
745
+ switch (outcome.kind) {
746
+ case "transcript":
747
+ return `${head}: "${outcome.text}" \u2014 transcribed from voice message (STT); ${tail}`;
748
+ case "unconfigured":
749
+ return `${head}: cannot transcribe \u2014 ${outcome.reason}. Tell the user you can't listen to voice messages until the operator configures transcription on this ours-mcp server, and ask them to send text meanwhile; ${tail}`;
750
+ case "failed":
751
+ return `${head}: transcription failed (${outcome.error}). Tell the user their voice message could not be transcribed right now and ask them to send text or retry; ${tail}`;
752
+ }
753
+ }
754
+
755
+ // ../../src/render/adapt-to-json.ts
756
+ function renderContacts(v) {
757
+ const out = [];
758
+ if (v.IsNil()) return out;
759
+ for (const key of v.GetKeys()) {
760
+ const c = v.Reduce(key);
761
+ if (c.IsNil()) continue;
762
+ out.push({
763
+ name: c.Reduce("name").Visualize(),
764
+ container_id: c.Reduce("container_id").Visualize()
765
+ });
766
+ }
767
+ return out;
768
+ }
769
+ function renderImportRenames(v) {
770
+ const out = {};
771
+ if (v.IsNil()) return out;
772
+ for (const key of v.GetKeys()) {
773
+ const n = v.Reduce(key);
774
+ if (!n.IsNil()) out[typeof key === "string" ? key : key.Visualize()] = n.Visualize();
775
+ }
776
+ return out;
777
+ }
778
+ function renderInbox(v) {
779
+ const out = [];
780
+ if (v.IsNil()) return out;
781
+ for (let i = 0; ; i++) {
782
+ const m = v.Reduce(i);
783
+ if (m.IsNil()) break;
784
+ const rt = m.Reduce("reply_to");
785
+ let reply_to = null;
786
+ if (!rt.IsNil()) {
787
+ reply_to = { wire_id: rt.Reduce("wire_id").Visualize() };
788
+ const s = rt.Reduce("sentence");
789
+ if (!s.IsNil()) reply_to.sentence = parseInt(s.Visualize(), 10);
790
+ }
791
+ out.push({
792
+ msg_id: parseInt(m.Reduce("msg_id").Visualize(), 10),
793
+ sender_id: m.Reduce("sender_id").Visualize(),
794
+ sender_name: m.Reduce("sender_name").Visualize(),
795
+ text: m.Reduce("text").Visualize(),
796
+ date: m.Reduce("date").Visualize(),
797
+ status: m.Reduce("status").Visualize(),
798
+ wire_id: m.Reduce("wire_id").Visualize(),
799
+ reply_to
800
+ });
801
+ }
802
+ return out;
803
+ }
804
+ function renderFileMetadata(v) {
805
+ const files = [];
806
+ if (v.IsNil()) return files;
807
+ for (let i = 0; ; i++) {
808
+ const f = v.Reduce(i);
809
+ if (f.IsNil()) break;
810
+ const filename = f.Reduce("filename").Visualize();
811
+ const mime = f.Reduce("mime").Visualize() || "application/octet-stream";
812
+ const reply = f.Reduce("reply_to");
813
+ let reply_to = null;
814
+ if (!reply.IsNil()) {
815
+ reply_to = { wire_id: reply.Reduce("wire_id").Visualize() };
816
+ const sentence = parseInt(reply.Reduce("sentence").Visualize(), 10);
817
+ if (Number.isSafeInteger(sentence) && sentence > 0) reply_to.sentence = sentence;
818
+ }
819
+ files.push({
820
+ file_id: parseInt(f.Reduce("file_id").Visualize(), 10),
821
+ wire_id: f.Reduce("wire_id").Visualize(),
822
+ from: { id: f.Reduce("sender_id").Visualize(), name: f.Reduce("sender_name").Visualize() },
823
+ filename,
824
+ mime,
825
+ size: parseInt(f.Reduce("size").Visualize(), 10),
826
+ size_source: "received_payload",
827
+ status: f.Reduce("status").Visualize(),
828
+ date: f.Reduce("date").Visualize(),
829
+ sha256: null,
830
+ reply_to,
831
+ kind: isVoiceMessage(mime, filename) ? "voice_message" : "file"
832
+ });
833
+ }
834
+ return files;
835
+ }
836
+ async function writeReceivedFileSafely(dir, outPath, bytes) {
837
+ await mkdir(dir, { recursive: true, mode: 448 });
838
+ const dirStat = await lstat(dir);
839
+ if (!dirStat.isDirectory() || dirStat.isSymbolicLink()) throw new Error("received-files directory is not a safe directory");
840
+ const tmp = join5(dir, `.${basename2(outPath)}.${randomUUID()}.tmp`);
841
+ const handle = await open(tmp, "wx", 384);
842
+ try {
843
+ await handle.writeFile(bytes);
844
+ await handle.sync();
845
+ } catch (error) {
846
+ await handle.close().catch(() => {
847
+ });
848
+ await unlink(tmp).catch(() => {
849
+ });
850
+ throw error;
851
+ }
852
+ await handle.close();
853
+ try {
854
+ await rename(tmp, outPath);
855
+ } catch (error) {
856
+ await unlink(tmp).catch(() => {
857
+ });
858
+ throw error;
859
+ }
860
+ }
861
+ async function writeIncomingFiles(id, v) {
862
+ const files = [];
863
+ if (v.IsNil()) return { text: "No new files.", files };
864
+ const dir = filesDirFor(id);
865
+ const lines = [];
866
+ for (let i = 0; ; i++) {
867
+ const f = v.Reduce(i);
868
+ if (f.IsNil()) break;
869
+ const name = f.Reduce("filename").Visualize();
870
+ const mime = f.Reduce("mime").Visualize() || "application/octet-stream";
871
+ const sender = f.Reduce("sender_name").Visualize();
872
+ const senderId = f.Reduce("sender_id").Visualize();
873
+ const fileId = parseInt(f.Reduce("file_id").Visualize(), 10);
874
+ const date = f.Reduce("date").Visualize();
875
+ const wire = f.Reduce("wire_id").Visualize();
876
+ const bytes = Buffer.from(f.Reduce("data").GetBinary());
877
+ const sha256 = createHash2("sha256").update(bytes).digest("hex");
878
+ const outPath = join5(dir, `${wire}-${sanitizeFilename(name)}`);
879
+ await writeReceivedFileSafely(dir, outPath, bytes);
880
+ const base = {
881
+ file_id: fileId,
882
+ wire_id: wire,
883
+ from: { id: senderId, name: sender },
884
+ filename: name,
885
+ path: outPath,
886
+ mime,
887
+ size: bytes.length,
888
+ sha256,
889
+ status: "processed",
890
+ date,
891
+ kind: isVoiceMessage(mime, name) ? "voice_message" : "file",
892
+ sender
893
+ };
894
+ if (isVoiceMessage(mime, name)) {
895
+ const st = sttStatus(CONFIG.stt);
896
+ let outcome;
897
+ if (!st.ready) {
898
+ outcome = { kind: "unconfigured", reason: st.reason };
899
+ } else {
900
+ const r = await transcribeVoice(bytes, name, mime, CONFIG.stt);
901
+ outcome = r.ok ? { kind: "transcript", text: r.text } : { kind: "failed", error: r.error };
902
+ }
903
+ base.transcription = structuredVoiceOutcome(st, outcome, { audioPath: outPath, wireId: wire });
904
+ lines.push(voiceDeliveryLine({ sender, wire, savedPath: outPath, sizeBytes: bytes.length }, outcome));
905
+ files.push(base);
906
+ continue;
907
+ }
908
+ files.push(base);
909
+ lines.push(` \u2022 ${name} (${mime}, ${bytes.length} B, sha256 ${sha256}) from ${sender} \u2192 ${outPath} {${wire}}`);
910
+ }
911
+ if (lines.length === 0) return { text: "No new files.", files };
912
+ return {
913
+ text: `${lines.length} new file(s) written to your identity's files dir \u2014 paths + metadata below (bytes stay on disk, never in this result). If your OS user can read the path, use it directly; otherwise use save_file({ wire_id, dest_path }) to stream a copy to a path you can write:
914
+ ${lines.join("\n")}`,
915
+ files
916
+ };
917
+ }
918
+ function renderPending(v) {
919
+ const out = [];
920
+ if (v.IsNil()) return out;
921
+ for (const key of v.GetKeys()) {
922
+ const p = v.Reduce(key);
923
+ if (p.IsNil()) continue;
924
+ out.push({
925
+ container_id: typeof key === "string" ? key : key.Visualize(),
926
+ name: p.Reduce("name").Visualize(),
927
+ queued: parseInt(p.Reduce("queued").Visualize(), 10) || 0
928
+ });
929
+ }
930
+ return out;
931
+ }
932
+ function renderContactRoots(v) {
933
+ const out = {};
934
+ if (v.IsNil()) return out;
935
+ for (const key of v.GetKeys()) {
936
+ const r = v.Reduce(key);
937
+ if (r.IsNil()) continue;
938
+ out[typeof key === "string" ? key : key.Visualize()] = {
939
+ root_cid: r.Reduce("root_cid").Visualize(),
940
+ root_name: r.Reduce("root_name").Visualize(),
941
+ role_id: r.Reduce("role_id").Visualize()
942
+ };
943
+ }
944
+ return out;
945
+ }
946
+ function encodeWireBin(raw) {
947
+ return brotliCompressSync(raw).toString("base64url");
948
+ }
949
+ function decodeWireBin(s) {
950
+ return brotliDecompressSync(Buffer.from(s.replace(/\s+/g, ""), "base64url"));
951
+ }
952
+
953
+ // ../../src/notify.ts
954
+ var installed = null;
955
+ function setNotifyHook(fn) {
956
+ installed?.uninstall();
957
+ installed = fn === null ? null : { hook: fn, uninstall: setDaemonEventHandler("notification", fn) };
958
+ }
959
+ function clearNotifyHook(fn) {
960
+ if (installed?.hook === fn) {
961
+ installed.uninstall();
962
+ installed = null;
963
+ }
964
+ }
965
+ function fireNotify(identityName, summary) {
966
+ emitDaemonNotification(identityName, summary);
967
+ }
968
+ var notifyHooks = {
969
+ onNotify: (identityName, summary) => fireNotify(identityName, summary)
970
+ };
971
+ var NOTIFY_KIND_SETS = {
972
+ // Genuine inbound arrivals — something addressed to this identity landed.
973
+ inbound: ["message_received", "file_received"],
974
+ // Introduction bookkeeping — not an arrival, but an away agent must act on it.
975
+ intro: ["local_contact_request", "pending_message"],
976
+ // What `ours-mcp watch` surfaces: arrivals plus the intro events. This is
977
+ // 7a7ec16's whitelist exactly, and it is a UNION rather than a third list so
978
+ // the two cannot drift apart.
979
+ get wake() {
980
+ return [...NOTIFY_KIND_SETS.inbound, ...NOTIFY_KIND_SETS.intro];
981
+ }
982
+ };
983
+ function parseNotifyKinds(kindsParam) {
984
+ if (kindsParam === null || kindsParam === "") return null;
985
+ const keep = /* @__PURE__ */ new Set();
986
+ for (const raw of kindsParam.split(",")) {
987
+ const kind = raw.trim();
988
+ if (kind === "") continue;
989
+ const events = NOTIFY_KIND_SETS[kind];
990
+ if (!events) throw new Error(kind);
991
+ for (const e of events) keep.add(e);
992
+ }
993
+ return keep;
994
+ }
995
+ function notifyEventMatches(event, keep) {
996
+ if (keep === null) return true;
997
+ return typeof event.event === "string" && keep.has(event.event);
998
+ }
999
+ var notifyWaiters = /* @__PURE__ */ new Map();
1000
+ function fireNotifyWaiters(name) {
1001
+ const set = notifyWaiters.get(name);
1002
+ if (!set || set.size === 0) return;
1003
+ for (const w of [...set]) {
1004
+ try {
1005
+ w();
1006
+ } catch {
1007
+ }
1008
+ }
1009
+ }
1010
+ function waitForNotify(name, ms) {
1011
+ return new Promise((resolve2) => {
1012
+ let set = notifyWaiters.get(name);
1013
+ if (!set) {
1014
+ set = /* @__PURE__ */ new Set();
1015
+ notifyWaiters.set(name, set);
1016
+ }
1017
+ let done = false;
1018
+ const finish = () => {
1019
+ if (done) return;
1020
+ done = true;
1021
+ set.delete(fn);
1022
+ if (set.size === 0) notifyWaiters.delete(name);
1023
+ clearTimeout(timer);
1024
+ resolve2();
1025
+ };
1026
+ const fn = finish;
1027
+ const timer = setTimeout(finish, ms);
1028
+ set.add(fn);
1029
+ });
1030
+ }
1031
+ function appendNotifyLog(id, event) {
1032
+ try {
1033
+ fs5.mkdirSync(id.dir, { recursive: true, mode: 448 });
1034
+ fs5.appendFileSync(notifyLogPath(id.dir), JSON.stringify(event) + "\n");
1035
+ } catch (err) {
1036
+ log(`[${id.name}] failed to append notifications.log:`, String(err));
1037
+ }
1038
+ fireNotifyWaiters(id.name);
1039
+ emitDaemonEvent(id, event);
1040
+ }
1041
+ function readE2eWireIds(id) {
1042
+ const logPath = notifyLogPath(id.dir);
1043
+ let text = "";
1044
+ try {
1045
+ text = fs5.readFileSync(logPath, "utf8");
1046
+ } catch {
1047
+ return /* @__PURE__ */ new Set();
1048
+ }
1049
+ const events = [];
1050
+ for (const line of text.split("\n")) {
1051
+ if (!line.trim()) continue;
1052
+ try {
1053
+ events.push(JSON.parse(line));
1054
+ } catch {
1055
+ }
1056
+ }
1057
+ return e2eWireIdsFromEvents(events);
1058
+ }
1059
+ function binHexField(av, field) {
1060
+ const x = av.Reduce(field);
1061
+ return x.IsNil() ? "" : Buffer.from(x.GetBinary()).toString("hex");
1062
+ }
1063
+ var NOTIFY_LONGPOLL_MS = Number(process.env.OURS_NOTIFY_LONGPOLL_MS) > 0 ? Number(process.env.OURS_NOTIFY_LONGPOLL_MS) : 25e3;
1064
+ var NOTIFY_RECHECK_MS = 250;
1065
+ function notifyLogSize(logPath) {
1066
+ try {
1067
+ return fs5.statSync(logPath).size;
1068
+ } catch {
1069
+ return 0;
1070
+ }
1071
+ }
1072
+ function readNotifyRange(logPath, from, to) {
1073
+ if (to <= from) return { events: [], cursor: from };
1074
+ const buf = Buffer.alloc(to - from);
1075
+ let read = 0;
1076
+ try {
1077
+ const fd = fs5.openSync(logPath, "r");
1078
+ try {
1079
+ read = fs5.readSync(fd, buf, 0, buf.length, from);
1080
+ } finally {
1081
+ fs5.closeSync(fd);
1082
+ }
1083
+ } catch {
1084
+ return { events: [], cursor: from };
1085
+ }
1086
+ const slice = buf.subarray(0, read);
1087
+ const lastNl = slice.lastIndexOf(10);
1088
+ if (lastNl === -1) return { events: [], cursor: from };
1089
+ const events = [];
1090
+ for (const line of slice.subarray(0, lastNl + 1).toString("utf8").split("\n")) {
1091
+ if (!line.trim()) continue;
1092
+ try {
1093
+ events.push(JSON.parse(line));
1094
+ } catch {
1095
+ }
1096
+ }
1097
+ return { events, cursor: from + lastNl + 1 };
1098
+ }
1099
+ async function serveNotifications(req, res, name, sinceParam, kindsParam = null) {
1100
+ const logPath = notifyLogPath(join6(STATE_DIR, name));
1101
+ const send = (cursor, events) => {
1102
+ if (res.writableEnded) return;
1103
+ res.writeHead(200, { "Content-Type": "application/json" });
1104
+ res.end(JSON.stringify({ cursor, events }));
1105
+ };
1106
+ let keep;
1107
+ try {
1108
+ keep = parseNotifyKinds(kindsParam);
1109
+ } catch (bad) {
1110
+ if (!res.writableEnded) {
1111
+ res.writeHead(400, { "Content-Type": "application/json" });
1112
+ res.end(JSON.stringify({
1113
+ error: `unknown notification kind "${String(bad.message)}" \u2014 known kinds: ${Object.keys(NOTIFY_KIND_SETS).sort().join(", ")}`
1114
+ }));
1115
+ }
1116
+ return;
1117
+ }
1118
+ if (sinceParam === null || sinceParam === "tip") {
1119
+ send(notifyLogSize(logPath), []);
1120
+ return;
1121
+ }
1122
+ let since = parseInt(sinceParam, 10);
1123
+ if (!Number.isFinite(since) || since < 0) {
1124
+ if (!res.writableEnded) {
1125
+ res.writeHead(400, { "Content-Type": "application/json" });
1126
+ res.end(JSON.stringify({ error: "invalid since cursor" }));
1127
+ }
1128
+ return;
1129
+ }
1130
+ let aborted = false;
1131
+ req.on("close", () => {
1132
+ aborted = true;
1133
+ });
1134
+ const deadline = Date.now() + NOTIFY_LONGPOLL_MS;
1135
+ while (!aborted) {
1136
+ const size = notifyLogSize(logPath);
1137
+ if (since > size) since = 0;
1138
+ if (size > since) {
1139
+ const { events, cursor } = readNotifyRange(logPath, since, size);
1140
+ since = cursor;
1141
+ const delivered = keep === null ? events : events.filter((e) => notifyEventMatches(e, keep));
1142
+ if (delivered.length > 0) {
1143
+ send(cursor, delivered);
1144
+ return;
1145
+ }
1146
+ }
1147
+ if (Date.now() >= deadline) break;
1148
+ await waitForNotify(name, Math.min(NOTIFY_RECHECK_MS, deadline - Date.now()));
1149
+ }
1150
+ if (!aborted) send(since, []);
1151
+ }
1152
+ function refreshUnread(id) {
1153
+ try {
1154
+ const { unread, unreadFiles } = withScope((lt) => {
1155
+ const inbox = renderInbox(readonlyTx(id, "::actor::list_incoming_messages", lt));
1156
+ const unread2 = inbox.filter((m) => m.status === "unread");
1157
+ const filesAv = readonlyTx(id, "::actor::list_incoming_files", lt);
1158
+ const unreadFiles2 = [];
1159
+ if (!filesAv.IsNil()) {
1160
+ for (let i = 0; ; i++) {
1161
+ const f = filesAv.Reduce(i);
1162
+ if (f.IsNil()) break;
1163
+ if (f.Reduce("status").Visualize() !== "unread") continue;
1164
+ unreadFiles2.push({
1165
+ file_id: f.Reduce("file_id").Visualize(),
1166
+ sender_id: f.Reduce("sender_id").Visualize(),
1167
+ from: f.Reduce("sender_name").Visualize(),
1168
+ filename: f.Reduce("filename").Visualize(),
1169
+ mime: f.Reduce("mime").Visualize(),
1170
+ bytes: f.Reduce("size").Visualize(),
1171
+ date: f.Reduce("date").Visualize(),
1172
+ wire_id: f.Reduce("wire_id").Visualize()
1173
+ });
1174
+ }
1175
+ }
1176
+ return { unread: unread2, unreadFiles: unreadFiles2 };
1177
+ });
1178
+ const snapshot = {
1179
+ count: unread.length,
1180
+ recent: unread.slice(-10).map((m) => ({ from: m.sender_name, msg_id: m.msg_id, date: m.date })),
1181
+ files: unreadFiles.length,
1182
+ unread_files: unreadFiles.slice(-10)
1183
+ };
1184
+ fs5.mkdirSync(id.dir, { recursive: true, mode: 448 });
1185
+ const tmp = `${unreadPath(id.dir)}.tmp`;
1186
+ fs5.writeFileSync(tmp, JSON.stringify(snapshot));
1187
+ fs5.renameSync(tmp, unreadPath(id.dir));
1188
+ } catch (err) {
1189
+ log(`[${id.name}] failed to refresh unread snapshot:`, String(err));
1190
+ }
1191
+ }
1192
+ function unreadSummary() {
1193
+ const out = [];
1194
+ let entries = [];
1195
+ try {
1196
+ entries = fs5.readdirSync(STATE_DIR, { withFileTypes: true });
1197
+ } catch {
1198
+ return { identities: out };
1199
+ }
1200
+ for (const entry of entries) {
1201
+ if (!entry.isDirectory() || validateName(entry.name) !== null) continue;
1202
+ let value;
1203
+ try {
1204
+ value = JSON.parse(fs5.readFileSync(unreadPath(join6(STATE_DIR, entry.name)), "utf8"));
1205
+ } catch {
1206
+ continue;
1207
+ }
1208
+ const count = Number.isSafeInteger(value.count) && Number(value.count) >= 0 ? Number(value.count) : 0;
1209
+ const files = Number.isSafeInteger(value.files) && Number(value.files) >= 0 ? Number(value.files) : 0;
1210
+ if (count === 0 && files === 0) continue;
1211
+ const recent = Array.isArray(value.recent) ? value.recent.slice(-10).flatMap((raw) => {
1212
+ if (!raw || typeof raw !== "object") return [];
1213
+ const m = raw;
1214
+ if (typeof m.from !== "string" || !["string", "number"].includes(typeof m.msg_id) || typeof m.date !== "string") return [];
1215
+ return [{ from: m.from, msg_id: m.msg_id, date: m.date }];
1216
+ }) : [];
1217
+ const unreadFiles = Array.isArray(value.unread_files) ? value.unread_files.slice(-10).flatMap((raw) => {
1218
+ if (!raw || typeof raw !== "object") return [];
1219
+ const f = raw;
1220
+ if (typeof f.from !== "string" || typeof f.filename !== "string" || typeof f.mime !== "string" || typeof f.wire_id !== "string") return [];
1221
+ return [{
1222
+ from: f.from,
1223
+ filename: f.filename,
1224
+ mime: f.mime,
1225
+ wire_id: f.wire_id,
1226
+ ...typeof f.sender_id === "string" ? { sender_id: f.sender_id } : {},
1227
+ ...["string", "number"].includes(typeof f.file_id) ? { file_id: f.file_id } : {},
1228
+ ...["string", "number"].includes(typeof f.bytes) ? { bytes: f.bytes } : {},
1229
+ ...typeof f.date === "string" ? { date: f.date } : {}
1230
+ }];
1231
+ }) : [];
1232
+ out.push({ name: entry.name, count, recent, files, unread_files: unreadFiles });
1233
+ }
1234
+ out.sort((a, b) => String(a.name).localeCompare(String(b.name)));
1235
+ return { identities: out };
1236
+ }
1237
+
1238
+ // ../../src/sweeps.ts
1239
+ function renderDegraded(av) {
1240
+ const out = [];
1241
+ const arr = av.Reduce("degraded");
1242
+ if (arr.IsNil()) return out;
1243
+ for (let i = 0; ; i++) {
1244
+ const e = arr.Reduce(i);
1245
+ if (e.IsNil()) break;
1246
+ out.push({
1247
+ cid: e.Reduce("container_id").Visualize(),
1248
+ name: e.Reduce("name").Visualize(),
1249
+ attempts: Number(e.Reduce("attempts").Visualize()),
1250
+ queued: Number(e.Reduce("queued").Visualize())
1251
+ });
1252
+ }
1253
+ return out;
1254
+ }
1255
+ function renderDeferredQueues(av) {
1256
+ const out = [];
1257
+ const arr = av.Reduce("queues");
1258
+ if (arr.IsNil()) return out;
1259
+ for (let i = 0; ; i++) {
1260
+ const e = arr.Reduce(i);
1261
+ if (e.IsNil()) break;
1262
+ out.push({
1263
+ cid: e.Reduce("container_id").Visualize(),
1264
+ queued: Number(e.Reduce("queued").Visualize()),
1265
+ degraded: e.Reduce("degraded").GetBoolean()
1266
+ });
1267
+ }
1268
+ return out;
1269
+ }
1270
+ async function flushDeferredFor(id, contactCid) {
1271
+ try {
1272
+ const flushed = await withScopeAsync(async (lt) => {
1273
+ const r = await mutatingTx(id, "::a2a_messaging::flush_deferred", { contact: contactCid }, lt);
1274
+ return Number(r.Reduce("flushed").Visualize());
1275
+ });
1276
+ if (flushed > 0) log(`[${id.name}] flushed ${flushed} deferred message(s) to ${contactCid.slice(0, 12)}\u2026`);
1277
+ } catch (err) {
1278
+ log(`[${id.name}] deferred flush to ${contactCid.slice(0, 12)}\u2026 failed:`, String(err));
1279
+ }
1280
+ }
1281
+ async function contactRestoreSweep(id) {
1282
+ try {
1283
+ const requested = await withScopeAsync(async (lt) => {
1284
+ const r = await mutatingTx(id, "::a2a_messaging::restore_degraded_contacts", {}, lt);
1285
+ return Number(r.Reduce("requested").Visualize());
1286
+ });
1287
+ if (requested > 0) log(`[${id.name}] contact restore requested for ${requested} degraded contact(s)`);
1288
+ const queues = withScope((lt) => renderDeferredQueues(readonlyTx(id, "::a2a_messaging::list_deferred_queues", lt)));
1289
+ for (const q of queues) {
1290
+ if (!q.degraded) await flushDeferredFor(id, q.cid);
1291
+ }
1292
+ } catch (err) {
1293
+ log(`[${id.name}] contact-restore sweep failed:`, String(err));
1294
+ }
1295
+ }
1296
+ async function capabilityReconcileSweep(id) {
1297
+ try {
1298
+ const result = await withScopeAsync(async (lt) => {
1299
+ const r = await mutatingTx(id, "::a2a_messaging::reconcile_advertise", {}, lt);
1300
+ const num = (field) => r.Reduce(field).IsNil() ? 0 : Number(r.Reduce(field).Visualize());
1301
+ return {
1302
+ changed: /true/i.test(r.Reduce("changed").Visualize()),
1303
+ capabilityAdvertised: num("capability_advertised"),
1304
+ legacyReadvertised: num("legacy_readvertised")
1305
+ };
1306
+ });
1307
+ if (result.capabilityAdvertised > 0 || result.legacyReadvertised > 0) {
1308
+ log(`[${id.name}] capability reconcile: changed=${result.changed} advertised=${result.capabilityAdvertised} legacy_migration_bootstrap=${result.legacyReadvertised}`);
1309
+ }
1310
+ } catch (err) {
1311
+ log(`[${id.name}] capability reconcile sweep failed:`, String(err));
1312
+ }
1313
+ }
1314
+ var capReconcileScheduled = /* @__PURE__ */ new Set();
1315
+ function scheduleCapabilityReconcile(id) {
1316
+ if (capReconcileScheduled.has(id.name)) return;
1317
+ capReconcileScheduled.add(id.name);
1318
+ for (const ms of [2e3, 15e3]) {
1319
+ setTimeout(() => {
1320
+ if (ms === 15e3) capReconcileScheduled.delete(id.name);
1321
+ const cur = identities.get(id.name);
1322
+ if (cur) void capabilityReconcileSweep(cur);
1323
+ }, ms);
1324
+ }
1325
+ }
1326
+ async function e2eRecoverySweep(id) {
1327
+ try {
1328
+ const readvertised = await withScopeAsync(async (lt) => {
1329
+ const r = await mutatingTx(id, "::a2a_messaging::readvertise_e2e_recovery", {}, lt);
1330
+ return Number(r.Reduce("readvertised").Visualize());
1331
+ });
1332
+ if (readvertised > 0) log(`[${id.name}] re-advertised fresh AD to ${readvertised} e2e contact(s) (session recovery)`);
1333
+ } catch (err) {
1334
+ log(`[${id.name}] e2e recovery re-advertise failed:`, String(err));
1335
+ }
1336
+ try {
1337
+ await withScopeAsync(async (lt) => {
1338
+ await mutatingTx(id, "::a2a_messaging::sweep_e2e_migrations", {}, lt);
1339
+ });
1340
+ } catch (err) {
1341
+ log(`[${id.name}] e2e migration sweep failed:`, String(err));
1342
+ }
1343
+ try {
1344
+ const s = await withScopeAsync(async (lt) => {
1345
+ const r = await mutatingTx(id, "::a2a_messaging::redrive_unacked_sweep", {}, lt);
1346
+ const num = (f) => r.Reduce(f).IsNil() ? 0 : Number(r.Reduce(f).Visualize());
1347
+ return { redriven: num("redriven_contacts"), purged: num("purged_contacts"), deferred: num("deferred_contacts") };
1348
+ });
1349
+ if (s.redriven > 0 || s.purged > 0 || s.deferred > 0) {
1350
+ log(`[${id.name}] unacked sweep: redriven=${s.redriven} ttl_purged=${s.purged} deferred=${s.deferred} contact(s)`);
1351
+ }
1352
+ } catch (err) {
1353
+ log(`[${id.name}] unacked redrive sweep failed:`, String(err));
1354
+ }
1355
+ }
1356
+
1357
+ // ../../src/mufl/handlers.ts
1358
+ function wireHandlers(id, hooks) {
1359
+ id.pw.on_return_data = (data) => {
1360
+ const lt = new AdaptObjectLifetime();
1361
+ data.Attach(lt);
1362
+ try {
1363
+ const kind = data.Reduce("kind").Visualize();
1364
+ if (kind === "save_state") {
1365
+ saveStateFailClosed(id);
1366
+ return;
1367
+ }
1368
+ if (kind === "notify_agent") {
1369
+ const payload = data.Reduce("payload");
1370
+ const event = payload.Reduce("event").Visualize();
1371
+ if (event === "message_received") {
1372
+ const sender = payload.Reduce("sender_name").Visualize();
1373
+ const senderIdValue = payload.Reduce("sender_id");
1374
+ const senderId = senderIdValue.IsNil() ? "" : senderIdValue.Visualize();
1375
+ const msgId = payload.Reduce("msg_id").Visualize();
1376
+ const wireIdValue = payload.Reduce("wire_id");
1377
+ const wireId = wireIdValue.IsNil() ? "" : wireIdValue.Visualize();
1378
+ const date = payload.Reduce("date").Visualize();
1379
+ appendNotifyLog(id, {
1380
+ event: "message_received",
1381
+ from: sender,
1382
+ sender_name: sender,
1383
+ sender_id: senderId,
1384
+ msg_id: msgId,
1385
+ wire_id: wireId,
1386
+ date
1387
+ });
1388
+ refreshUnread(id);
1389
+ process.nextTick(
1390
+ () => hooks.onNotify(id.name, `[${id.name}] new message from ${sender} (#${msgId})`)
1391
+ );
1392
+ } else if (event === "receipt_received") {
1393
+ const senderIdValue = payload.Reduce("sender_id");
1394
+ const senderId = senderIdValue.IsNil() ? "" : String(senderIdValue.Visualize());
1395
+ const kindValue = payload.Reduce("kind");
1396
+ const receiptKind = kindValue.IsNil() ? "" : String(kindValue.Visualize());
1397
+ const wireIds = [];
1398
+ const wireIdsValue = payload.Reduce("wire_ids");
1399
+ if (!wireIdsValue.IsNil()) {
1400
+ for (let i = 0; ; i++) {
1401
+ const wireId = wireIdsValue.Reduce(i);
1402
+ if (wireId.IsNil()) break;
1403
+ wireIds.push(String(wireId.Visualize()));
1404
+ }
1405
+ }
1406
+ const dateValue = payload.Reduce("date");
1407
+ const date = dateValue.IsNil() ? (/* @__PURE__ */ new Date()).toISOString() : String(dateValue.Visualize());
1408
+ appendNotifyLog(id, {
1409
+ event: "receipt_received",
1410
+ sender_id: senderId,
1411
+ kind: receiptKind,
1412
+ wire_ids: wireIds,
1413
+ date
1414
+ });
1415
+ } else if (event === "file_received") {
1416
+ const sender = payload.Reduce("sender_name").Visualize();
1417
+ const senderId = payload.Reduce("sender_id").Visualize();
1418
+ const fileId = payload.Reduce("file_id").Visualize();
1419
+ const wireId = payload.Reduce("wire_id").Visualize();
1420
+ const filename = payload.Reduce("filename").Visualize();
1421
+ const mime = payload.Reduce("mime").Visualize();
1422
+ const bytes = payload.Reduce("bytes").Visualize();
1423
+ const date = payload.Reduce("date").Visualize();
1424
+ appendNotifyLog(id, {
1425
+ event: "file_received",
1426
+ from: sender,
1427
+ sender_id: senderId,
1428
+ file_id: fileId,
1429
+ wire_id: wireId,
1430
+ filename,
1431
+ mime,
1432
+ bytes,
1433
+ date
1434
+ });
1435
+ refreshUnread(id);
1436
+ process.nextTick(
1437
+ () => hooks.onNotify(
1438
+ id.name,
1439
+ `[${id.name}] new file ${filename} (${bytes} B) from ${sender} (sender_id ${senderId}, file_id ${fileId}, wire_id ${wireId})`
1440
+ )
1441
+ );
1442
+ } else if (event === "contact_accepted") {
1443
+ const name = payload.Reduce("name").Visualize();
1444
+ const cid = payload.Reduce("container_id").Visualize();
1445
+ scheduleCapabilityReconcile(id);
1446
+ process.nextTick(
1447
+ () => hooks.onNotify(id.name, `[${id.name}] contact "${name}" (${cid}) accepted your invite.`)
1448
+ );
1449
+ } else if (event === "local_contact_added") {
1450
+ const name = payload.Reduce("name").Visualize();
1451
+ const cid = payload.Reduce("container_id").Visualize();
1452
+ scheduleCapabilityReconcile(id);
1453
+ process.nextTick(
1454
+ () => hooks.onNotify(id.name, `[${id.name}] local contact "${name}" (${cid}) connected via the contact book.`)
1455
+ );
1456
+ } else if (event === "contact_name_collision") {
1457
+ const message = payload.Reduce("message").Visualize();
1458
+ const desired = payload.Reduce("desired").Visualize();
1459
+ const assigned = payload.Reduce("assigned").Visualize();
1460
+ const existingCid = payload.Reduce("existing_container_id").Visualize();
1461
+ const cid = payload.Reduce("container_id").Visualize();
1462
+ appendNotifyLog(id, { event: "contact_name_collision", desired, assigned, existing_container_id: existingCid, container_id: cid });
1463
+ log(`[${id.name}] CONTACT NAME COLLISION \u2014 ${message}`);
1464
+ process.nextTick(() => hooks.onNotify(id.name, `[${id.name}] \u26A0 ${message}`));
1465
+ } else if (event === "sibling_contact_added") {
1466
+ const name = payload.Reduce("name").Visualize();
1467
+ const cid = payload.Reduce("container_id").Visualize();
1468
+ appendNotifyLog(id, { event: "sibling_contact_added", from: name });
1469
+ scheduleCapabilityReconcile(id);
1470
+ process.nextTick(
1471
+ () => hooks.onNotify(id.name, `[${id.name}] sibling "${name}" (${cid}) connected (intra-root auto-accept).`)
1472
+ );
1473
+ } else if (event === "local_contact_request") {
1474
+ const name = payload.Reduce("name").Visualize();
1475
+ const cid = payload.Reduce("container_id").Visualize();
1476
+ appendNotifyLog(id, { event: "local_contact_request", from: name });
1477
+ process.nextTick(
1478
+ () => hooks.onNotify(
1479
+ id.name,
1480
+ `[${id.name}] pending local introduction from "${name}" (${cid}) \u2014 approve or reject with respond_to_introduction.`
1481
+ )
1482
+ );
1483
+ } else if (event === "pending_message") {
1484
+ const name = payload.Reduce("sender_name").Visualize();
1485
+ const queued = payload.Reduce("queued").Visualize();
1486
+ appendNotifyLog(id, { event: "pending_message", from: name, queued });
1487
+ process.nextTick(
1488
+ () => hooks.onNotify(id.name, `[${id.name}] "${name}" queued a message awaiting introduction approval (${queued} queued).`)
1489
+ );
1490
+ } else if (event === "e2e_restore_rejected") {
1491
+ appendNotifyLog(id, { event: "e2e_restore_rejected" });
1492
+ log(`[${id.name}] E2E RESTORE REJECTED \u2014 corrupt session blob failed pickle_key validation; reset to empty e2e state, self-heal fallback will re-establish`);
1493
+ process.nextTick(
1494
+ () => hooks.onNotify(id.name, `[${id.name}] persisted e2e sessions were corrupt \u2014 rejected cleanly, sessions re-establishing`)
1495
+ );
1496
+ } else if (event === "contact_restored") {
1497
+ const name = payload.Reduce("name").Visualize();
1498
+ const cid = payload.Reduce("container_id").Visualize();
1499
+ appendNotifyLog(id, { event: "contact_restored", from: name });
1500
+ log(`[${id.name}] contact "${name}" restored (re-keyed)`);
1501
+ process.nextTick(() => void flushDeferredFor(id, String(cid)));
1502
+ } else if (event === "migration_active") {
1503
+ const cid = payload.Reduce("cid").Visualize();
1504
+ const role = payload.Reduce("role").Visualize();
1505
+ const epoch = binHexField(payload, "epoch");
1506
+ const sid = binHexField(payload, "session_id");
1507
+ appendNotifyLog(id, { event: "migration_active", cid: String(cid), role: String(role), ...epoch ? { epoch } : {}, ...sid ? { session_id: sid } : {} });
1508
+ log(`[migration] active cid=${cid} role=${role}${epoch ? ` epoch=${epoch}` : ""}${sid ? ` session_id=${sid}` : ""}`);
1509
+ } else if (event === "e2e_app_send") {
1510
+ const cid = payload.Reduce("cid").Visualize();
1511
+ const sid = binHexField(payload, "session_id");
1512
+ const olm = payload.Reduce("olm_type").Visualize();
1513
+ const wireId = payload.Reduce("wire_id").Visualize();
1514
+ const retained = payload.Reduce("retained").IsNil() ? void 0 : payload.Reduce("retained").GetBoolean();
1515
+ const evicted = [];
1516
+ const ev = payload.Reduce("evicted");
1517
+ if (!ev.IsNil()) {
1518
+ for (let i = 0; ; i++) {
1519
+ const e = ev.Reduce(i);
1520
+ if (e.IsNil()) break;
1521
+ evicted.push(String(e.Visualize()));
1522
+ }
1523
+ }
1524
+ appendNotifyLog(id, { event: "e2e_app_send", cid: String(cid), session_id: sid, olm_type: String(olm), wire_id: String(wireId), ...retained === false ? { retained: false } : {}, ...evicted.length ? { evicted } : {} });
1525
+ log(`[${id.name}] [e2e-app] send cid=${cid} session_id=${sid} olm_type=${olm} wire_id=${wireId}${retained === false ? " retained=false" : ""}${evicted.length ? ` evicted=${evicted.join(",")}` : ""}`);
1526
+ if (evicted.length) {
1527
+ process.nextTick(
1528
+ () => hooks.onNotify(id.name, `[${id.name}] redrive window overflow: ${evicted.length} older unacked send(s) lost their auto-resend guarantee (${evicted.join(", ")})`)
1529
+ );
1530
+ }
1531
+ } else if (event === "dedup_degraded") {
1532
+ const cid = payload.Reduce("cid").Visualize();
1533
+ const droppedWire = payload.Reduce("dropped_wire_id").Visualize();
1534
+ appendNotifyLog(id, { event: "dedup_degraded", cid: String(cid), dropped_wire_id: String(droppedWire) });
1535
+ log(`[${id.name}] [e2e-app] DEDUP DEGRADED cid=${cid} dropped_wire_id=${droppedWire} (storage ceiling \u2014 a late redrive of that id could re-deposit)`);
1536
+ process.nextTick(
1537
+ () => hooks.onNotify(id.name, `[${id.name}] dedup window overflowed for ${String(cid).slice(0, 12)}\u2026 \u2014 one oldest entry dropped (guarantee loss surfaced)`)
1538
+ );
1539
+ } else if (event === "e2e_delivery_expired") {
1540
+ const cid = payload.Reduce("cid").Visualize();
1541
+ const wireIds = [];
1542
+ const wl = payload.Reduce("wire_ids");
1543
+ if (!wl.IsNil()) {
1544
+ for (let i = 0; ; i++) {
1545
+ const e = wl.Reduce(i);
1546
+ if (e.IsNil()) break;
1547
+ wireIds.push(String(e.Visualize()));
1548
+ }
1549
+ }
1550
+ appendNotifyLog(id, { event: "e2e_delivery_expired", cid: String(cid), wire_ids: wireIds });
1551
+ log(`[${id.name}] [e2e-app] delivery EXPIRED (2-day TTL, receipt never arrived) cid=${cid} wire_ids=${wireIds.join(",")}`);
1552
+ process.nextTick(
1553
+ () => hooks.onNotify(id.name, `[${id.name}] ${wireIds.length} message(s)/file(s) to ${String(cid).slice(0, 12)}\u2026 expired undelivered (no receipt within 2 days)`)
1554
+ );
1555
+ } else if (event === "e2e_app_recv") {
1556
+ const cid = payload.Reduce("cid").Visualize();
1557
+ const sid = binHexField(payload, "session_id");
1558
+ const ok = payload.Reduce("ok").GetBoolean();
1559
+ const wireId = payload.Reduce("wire_id").Visualize();
1560
+ const code = payload.Reduce("code").IsNil() ? void 0 : String(payload.Reduce("code").Visualize());
1561
+ const duplicate = !payload.Reduce("duplicate").IsNil();
1562
+ const isFile = !payload.Reduce("file").IsNil();
1563
+ appendNotifyLog(id, { event: "e2e_app_recv", cid: String(cid), session_id: sid, ok, wire_id: String(wireId), ...code ? { code } : {}, ...duplicate ? { duplicate: true } : {}, ...isFile ? { file: true } : {} });
1564
+ log(`[${id.name}] [e2e-app] recv cid=${cid} session_id=${sid} ok=${ok} wire_id=${wireId}${code ? ` code=${code}` : ""}${duplicate ? " duplicate=true" : ""}${isFile ? " file=true" : ""}`);
1565
+ } else if (event === "e2e_rekey") {
1566
+ const cid = payload.Reduce("cid").Visualize();
1567
+ const role = payload.Reduce("role").Visualize();
1568
+ const sid = binHexField(payload, "session_id");
1569
+ const attempts = payload.Reduce("attempts").IsNil() ? void 0 : String(payload.Reduce("attempts").Visualize());
1570
+ const supports = payload.Reduce("peer_supports").IsNil() ? void 0 : String(payload.Reduce("peer_supports").Visualize());
1571
+ const rejected = payload.Reduce("rejected").IsNil() ? void 0 : String(payload.Reduce("rejected").Visualize());
1572
+ appendNotifyLog(id, { event: "e2e_rekey", cid: String(cid), role: String(role), ...sid ? { session_id: sid } : {}, ...attempts ? { attempts } : {}, ...supports ? { peer_supports: supports } : {}, ...rejected ? { rejected } : {} });
1573
+ log(`[${id.name}] [e2e-rekey] cid=${cid} role=${role}${sid ? ` session_id=${sid}` : ""}${attempts ? ` attempts=${attempts}` : ""}${supports ? ` peer_supports=${supports}` : ""}${rejected ? ` rejected=${rejected}` : ""}`);
1574
+ } else if (event === "migration_deferred_flush") {
1575
+ const cid = payload.Reduce("cid").Visualize();
1576
+ const wireId = payload.Reduce("wire_id").Visualize();
1577
+ appendNotifyLog(id, { event: "migration_deferred_flush", cid: String(cid), wire_id: String(wireId) });
1578
+ log(`[migration] flush-notify cid=${cid} wire_id=${wireId} (deferred\u2192e2e; core delivers)`);
1579
+ } else if (event === "migration_stalled") {
1580
+ const cid = payload.Reduce("cid").Visualize();
1581
+ const phase = payload.Reduce("phase").Visualize();
1582
+ const attempts = payload.Reduce("attempts").Visualize();
1583
+ appendNotifyLog(id, { event: "migration_stalled", cid: String(cid), phase: String(phase), attempts: String(attempts) });
1584
+ log(`[migration] stalled-notify cid=${cid} phase=${phase} attempts=${attempts} (core re-drives via sweep)`);
1585
+ } else if (event === "downgrade_refused") {
1586
+ const cid = payload.Reduce("cid").Visualize();
1587
+ const wireAv = payload.Reduce("wire_id");
1588
+ const wireId = wireAv.IsNil() ? "" : String(wireAv.Visualize());
1589
+ appendNotifyLog(id, { event: "downgrade_refused", cid: String(cid), ...wireId ? { wire_id: wireId } : {} });
1590
+ log(`[e2e-route] downgrade-dropped cid=${cid}${wireId ? ` wire_id=${wireId}` : ""} (legacy plaintext from a migrated peer \u2014 dropped by core)`);
1591
+ process.nextTick(
1592
+ () => hooks.onNotify(id.name, `[${id.name}] a message from a migrated contact was rejected as an unsafe downgrade (dropped).`)
1593
+ );
1594
+ } else if (event === "contact_removed") {
1595
+ const cid = String(payload.Reduce("cid").Visualize());
1596
+ const outbound = outboundRemovalInFlight.has(id.name);
1597
+ appendNotifyLog(id, { event: "contact_removed", cid, by: outbound ? "local" : "peer" });
1598
+ if (!outbound) {
1599
+ log(`[${id.name}] contact ${cid} removed you (authenticated peer removal notice) \u2014 dropped from contacts`);
1600
+ process.nextTick(
1601
+ () => hooks.onNotify(id.name, `[${id.name}] contact ${cid.slice(0, 12)}\u2026 removed you as a contact (the removal was applied locally too).`)
1602
+ );
1603
+ }
1604
+ } else if (event === "monitoring_disabled") {
1605
+ const cid = String(payload.Reduce("cid").Visualize());
1606
+ const causeAv = payload.Reduce("cause");
1607
+ const cause = causeAv.IsNil() ? "" : String(causeAv.Visualize());
1608
+ appendNotifyLog(id, { event: "monitoring_disabled", cid, ...cause ? { cause } : {} });
1609
+ log(`[${id.name}] monitoring disabled (cid=${cid}${cause ? ` cause=${cause}` : ""})`);
1610
+ process.nextTick(
1611
+ () => hooks.onNotify(id.name, `[${id.name}] the monitoring/control-plane binding was disabled${cause === "control_plane_removed_contact" ? " \u2014 the control plane removed you as a contact" : ""}.`)
1612
+ );
1613
+ } else if (event === "control_request" || event === "host_provision_child" || event === "host_destroy_child" || event === "host_mint_child_invite" || event === "host_set_child_monitoring") {
1614
+ log(`[${id.name}] ${event}: the daemon-side control plane is removed \u2014 ignoring`);
1615
+ } else {
1616
+ appendNotifyLog(id, { event });
1617
+ }
1618
+ return;
1619
+ }
1620
+ const p = id.pending.shift();
1621
+ if (!p) return;
1622
+ clearTimeout(p.timer);
1623
+ p.resolve(data.Reduce("payload").Detach());
1624
+ } finally {
1625
+ lt.Finalize();
1626
+ }
1627
+ };
1628
+ id.pw.on_transaction_failure = (message) => {
1629
+ const p = id.pending.shift();
1630
+ if (p) {
1631
+ clearTimeout(p.timer);
1632
+ p.reject(new Error(message));
1633
+ } else {
1634
+ log(`[${id.name}] inbound transaction rejected:`, message);
1635
+ appendNotifyLog(id, { event: "inbound_error", message });
1636
+ process.nextTick(
1637
+ () => hooks.onNotify(id.name, `[${id.name}] inbound transaction rejected: ${message}`)
1638
+ );
1639
+ }
1640
+ };
1641
+ }
1642
+
1643
+ // ../../src/book.ts
1644
+ import { join as join7 } from "node:path";
1645
+ import * as fs6 from "node:fs";
1646
+ var bookDir = () => join7(STATE_DIR, BOOK_DIR_NAME);
1647
+ var registrarKeyPath = () => join7(bookDir(), "registrar.key");
1648
+ var bookPath = () => join7(bookDir(), "book.json");
1649
+ function readBook() {
1650
+ try {
1651
+ const parsed = JSON.parse(fs6.readFileSync(bookPath(), "utf8"));
1652
+ return parsed && typeof parsed.entries === "object" ? parsed.entries : {};
1653
+ } catch {
1654
+ return {};
1655
+ }
1656
+ }
1657
+ function writeBook(entries) {
1658
+ fs6.mkdirSync(bookDir(), { recursive: true });
1659
+ const tmp = `${bookPath()}.tmp`;
1660
+ fs6.writeFileSync(tmp, JSON.stringify({ v: 1, entries }, null, 2), { mode: 384 });
1661
+ fs6.renameSync(tmp, bookPath());
1662
+ }
1663
+ function exportAdBlob(id) {
1664
+ return withScope(
1665
+ (lt) => Buffer.from(readonlyTx(id, "::actor::export_address_document", lt).GetBinary())
1666
+ );
1667
+ }
1668
+ function exportSigningSecret(id) {
1669
+ return withScope(
1670
+ (lt) => Buffer.from(readonlyTx(id, "::actor::export_signing_secret", lt).Serialize()).toString("hex")
1671
+ );
1672
+ }
1673
+ async function publishToBook(id) {
1674
+ if (!registrar) throw new Error("registrar is not available");
1675
+ const adBlob = exportAdBlob(id);
1676
+ const registrarSig = await withScopeAsync(async (lt) => {
1677
+ const sigData = await mutatingTx(registrar, "::actor::sign_book_entry", {
1678
+ name: id.name,
1679
+ ad: registrar.pw.packet.NewBinaryFromBuffer(adBlob).Attach(lt)
1680
+ }, lt);
1681
+ return Buffer.from(sigData.Reduce("sig").GetBinary()).toString("base64url");
1682
+ });
1683
+ const entries = readBook();
1684
+ entries[id.name] = {
1685
+ v: 1,
1686
+ name: id.name,
1687
+ container_id: id.cid,
1688
+ address_document: adBlob.toString("base64url"),
1689
+ published_at: (/* @__PURE__ */ new Date()).toISOString(),
1690
+ registrar_sig: registrarSig
1691
+ };
1692
+ writeBook(entries);
1693
+ log(`[${id.name}] published to the local contact book`);
1694
+ }
1695
+ function unpublishFromBook(id) {
1696
+ const entries = readBook();
1697
+ const hit = Object.entries(entries).find(([, e]) => e.container_id === id.cid);
1698
+ if (!hit) return;
1699
+ delete entries[hit[0]];
1700
+ writeBook(entries);
1701
+ log(`[${id.name}] removed from the local contact book (entry "${hit[0]}")`);
1702
+ }
1703
+ async function pinRegistrar(id) {
1704
+ if (!registrarAdBlob) throw new Error("registrar is not available");
1705
+ await withScopeAsync(async (lt) => {
1706
+ await mutatingTx(id, "::actor::pin_registrar", {
1707
+ registrar_ad: id.pw.packet.NewBinaryFromBuffer(registrarAdBlob).Attach(lt)
1708
+ }, lt);
1709
+ });
1710
+ }
1711
+
1712
+ // ../../src/identity/provision.ts
1713
+ function createPacket(name, seed, dir, track = true, signingSecret, deferExposure = false) {
1714
+ const config = new PacketWrapperConfigurator();
1715
+ config.deferred_exposure = deferExposure;
1716
+ const args = [
1717
+ "--unit_hash",
1718
+ UNIT.hash,
1719
+ "--seed_phrase",
1720
+ seed,
1721
+ "--unit_dir_path",
1722
+ UNIT.dir
1723
+ ];
1724
+ if (signingSecret) {
1725
+ args.push("--init_trn_argument", JSON.stringify(signingSecret));
1726
+ }
1727
+ config.process_arguments(args);
1728
+ return new Promise((resolveCreate, rejectCreate) => {
1729
+ const timer = setTimeout(
1730
+ () => rejectCreate(new Error(`packet creation for "${name}" timed out`)),
1731
+ 3e4
1732
+ );
1733
+ wrapper.packet_manager.create_packet(
1734
+ config,
1735
+ (pw) => {
1736
+ clearTimeout(timer);
1737
+ const id = {
1738
+ name,
1739
+ cid: withScope((lt) => pw.packet.GetContainerID().Attach(lt).Visualize()),
1740
+ pw,
1741
+ dir,
1742
+ pending: [],
1743
+ lock: Promise.resolve()
1744
+ };
1745
+ wireHandlers(id, notifyHooks);
1746
+ if (track) identities.set(name, id);
1747
+ log(`[${name}] packet created \u2014 container id ${id.cid}`);
1748
+ resolveCreate(id);
1749
+ },
1750
+ UNIT.contents
1751
+ );
1752
+ });
1753
+ }
1754
+ var reservedNames = new Set(listPersistedNames());
1755
+ if (reservedNames.size > 0) {
1756
+ log(`reserved ${reservedNames.size} persisted identity name(s) at module load: ${[...reservedNames].join(", ")}`);
1757
+ }
1758
+ async function provisionIdentity(name, opts = { exposeLocal: true, localAutoAccept: true }) {
1759
+ if (reservedNames.has(name) || identities.has(name)) {
1760
+ throw new Error(
1761
+ `identity name "${name}" is reserved \u2014 a persisted identity with this name is restoring, present, or being provisioned; refusing to provision over it`
1762
+ );
1763
+ }
1764
+ reservedNames.add(name);
1765
+ try {
1766
+ const dir = identityDir(name);
1767
+ fs7.mkdirSync(dir, { recursive: true, mode: 448 });
1768
+ let tempMeta;
1769
+ if (opts.temp) {
1770
+ tempMeta = { owner: { tokenHash: opts.temp.tokenHash, pid: opts.temp.pid }, createdAt: Date.now() };
1771
+ writeTempMetaFile(dir, tempMeta);
1772
+ }
1773
+ const seed = randomBytes(24).toString("hex");
1774
+ const id = await createPacket(name, seed, dir);
1775
+ if (tempMeta) id.temp = tempMeta;
1776
+ fs7.writeFileSync(keyPath(dir), exportSigningSecret(id), { mode: 384 });
1777
+ await withScopeAsync(async (lt) => {
1778
+ await mutatingTx(id, "::a2a_messaging::set_my_name", { name }, lt);
1779
+ });
1780
+ await pinRegistrar(id);
1781
+ if (!opts.localAutoAccept) {
1782
+ await withScopeAsync(async (lt) => {
1783
+ await mutatingTx(id, "::actor::set_local_policy", { auto_accept: false }, lt);
1784
+ });
1785
+ }
1786
+ if (opts.exposeLocal) {
1787
+ await publishToBook(id);
1788
+ }
1789
+ saveStateFailClosed(id);
1790
+ return id;
1791
+ } catch (err) {
1792
+ reservedNames.delete(name);
1793
+ throw err;
1794
+ }
1795
+ }
1796
+ async function restoreIdentity(name) {
1797
+ const dir = identityDir(name);
1798
+ const secret = fs7.readFileSync(keyPath(dir), "utf8").trim();
1799
+ const id = await createPacket(name, "", dir, false, secret, true);
1800
+ log(`[${name}] created QUARANTINED (no routing/broker registration, not client-bindable) \u2014 importing state before exposure`);
1801
+ const holdMs = Number(process.env.OURS_TEST_RESTORE_HOLD_MS || "") || 0;
1802
+ if (holdMs > 0) {
1803
+ log(`[${name}] TEST HOLD: keeping restore open ${holdMs}ms (OURS_TEST_RESTORE_HOLD_MS)`);
1804
+ await new Promise((r) => setTimeout(r, holdMs));
1805
+ }
1806
+ const isTimeout = (err) => /timed out waiting for the transaction result/.test(String(err));
1807
+ const tearDownUnexposed = (step, why, err) => {
1808
+ log(`[${name}] ${step} ${why} \u2014 identity left UNEXPOSED and UNTRACKED (fail-closed)`);
1809
+ try {
1810
+ appendNotifyLog(id, { event: "restore_fail_closed", step, error: String(err).slice(0, 300) });
1811
+ } catch {
1812
+ }
1813
+ identities.delete(name);
1814
+ try {
1815
+ wrapper.remove_packet(id.cid);
1816
+ } catch (e2) {
1817
+ log(`[${name}] quarantined-packet teardown failed:`, String(e2));
1818
+ }
1819
+ throw new Error(`identity "${name}": ${step} failed during restore (${why}) \u2014 left unexposed (fail-closed)`);
1820
+ };
1821
+ const failClosed = (step, err) => tearDownUnexposed(step, "outcome UNKNOWN (timeout): the transaction may still execute and exposure would race it", err);
1822
+ if (hasSavedState(dir)) {
1823
+ let imported = false;
1824
+ try {
1825
+ if (process.env.OURS_TEST_FORCE_IMPORT_TIMEOUT === "1") {
1826
+ throw new Error("timed out waiting for the transaction result (forced by OURS_TEST_FORCE_IMPORT_TIMEOUT)");
1827
+ }
1828
+ const buf = fs7.readFileSync(dataPath(dir));
1829
+ await withScopeAsync(async (lt) => {
1830
+ const adaptData = id.pw.packet.ParseValue(new Uint8Array(buf)).Attach(lt);
1831
+ const importTimeoutMs = Number(process.env.OURS_IMPORT_TIMEOUT_MS || "") || void 0;
1832
+ await mutatingTx(id, "::actor::import_state", adaptData, lt, importTimeoutMs);
1833
+ });
1834
+ imported = true;
1835
+ log(`[${name}] state import completed (positively observed)`);
1836
+ } catch (err) {
1837
+ if (isTimeout(err)) failClosed("import_state", err);
1838
+ log(`[${name}] FAILED TO IMPORT SAVED STATE \u2014 continuing with the reseeded identity; surviving contacts (if the blob was partially migrated) self-heal via contact restore:`, String(err));
1839
+ appendNotifyLog(id, { event: "state_import_failed", error: String(err).slice(0, 300) });
1840
+ try {
1841
+ const failedPath = `${dataPath(dir)}.failed-${Date.now()}`;
1842
+ fs7.renameSync(dataPath(dir), failedPath);
1843
+ fs7.chmodSync(failedPath, 384);
1844
+ log(`[${name}] unreadable state blob preserved as state_data.bin.failed-*`);
1845
+ } catch {
1846
+ }
1847
+ }
1848
+ if (imported) {
1849
+ try {
1850
+ const st = await withScopeAsync(async (lt) => {
1851
+ const r = await mutatingTx(id, "::a2a_messaging::commit_e2e_restore", {}, lt);
1852
+ const status = r.Reduce("status").Visualize();
1853
+ const sessions = r.Reduce("sessions").IsNil() ? "0" : r.Reduce("sessions").Visualize();
1854
+ return { status: String(status), sessions: String(sessions) };
1855
+ });
1856
+ log(`[${name}] e2e restore commit: status=${st.status} sessions=${st.sessions}`);
1857
+ } catch (err) {
1858
+ if (isTimeout(err)) failClosed("commit_e2e_restore", err);
1859
+ log(`[${name}] E2E RESTORE REJECTED \u2014 staged session blob failed pickle_key validation; discarding it, fresh account + self-heal fallback take over: ${String(err).slice(0, 260)}`);
1860
+ appendNotifyLog(id, { event: "e2e_restore_rejected", origin: "restore", error: String(err).slice(0, 300) });
1861
+ try {
1862
+ await withScopeAsync(async (lt) => {
1863
+ await mutatingTx(id, "::a2a_messaging::reject_e2e_restore", {}, lt);
1864
+ });
1865
+ } catch (err2) {
1866
+ if (isTimeout(err2)) failClosed("reject_e2e_restore", err2);
1867
+ log(`[${name}] reject_e2e_restore failed (staging is transient; continuing):`, String(err2));
1868
+ }
1869
+ }
1870
+ try {
1871
+ id.pw.refresh_identity_proof_document();
1872
+ log(`[${name}] transport IPD refreshed from post-import state`);
1873
+ } catch (err) {
1874
+ tearDownUnexposed("ipd_refresh", "FAILED (IPD would advertise a stale bundle)", err);
1875
+ }
1876
+ let coherent = false;
1877
+ let ikShort = "";
1878
+ try {
1879
+ if (process.env.OURS_TEST_FORCE_IPD_INCOHERENT === "1") {
1880
+ throw new Error("forced incoherent (OURS_TEST_FORCE_IPD_INCOHERENT)");
1881
+ }
1882
+ const ik = withScope((lt) => String(readonlyTx(id, "::a2a_messaging::e2e_self_fp", lt).Reduce("ik").Visualize()));
1883
+ const ipdVis = id.pw.identity_proof_document.Visualize();
1884
+ const ikHex = ik.replace(/^0x/i, "");
1885
+ coherent = ikHex.length >= 32 && ipdVis.toLowerCase().includes(ikHex.toLowerCase());
1886
+ ikShort = ik.slice(0, 18);
1887
+ appendNotifyLog(id, { event: "ipd_coherence", ik, coherent });
1888
+ } catch (err) {
1889
+ tearDownUnexposed("ipd_coherence", "probe FAILED (coherence unprovable)", err);
1890
+ }
1891
+ log(`[${name}] IPD/e2e coherence: account_ik=${ikShort}\u2026 ipd_advertises_it=${coherent}`);
1892
+ if (!coherent) {
1893
+ tearDownUnexposed("ipd_coherence", "IPD does NOT advertise the live account ik", new Error("incoherent transport IPD"));
1894
+ }
1895
+ }
1896
+ }
1897
+ wrapper.expose_packet(id.cid);
1898
+ identities.set(name, id);
1899
+ log(`[${name}] EXPOSED (routing + broker registration) \u2014 import phase complete`);
1900
+ await contactRestoreSweep(id);
1901
+ refreshUnread(id);
1902
+ return id;
1903
+ }
1904
+
1905
+ // ../../src/state.ts
1906
+ async function ensureRegistrar() {
1907
+ fs8.mkdirSync(bookDir(), { recursive: true });
1908
+ let secret;
1909
+ try {
1910
+ secret = fs8.readFileSync(registrarKeyPath(), "utf8").trim();
1911
+ } catch {
1912
+ }
1913
+ const seed = randomBytes2(24).toString("hex");
1914
+ setRegistrar(await createPacket(BOOK_DIR_NAME, seed, bookDir(), false, secret));
1915
+ if (!secret) {
1916
+ fs8.writeFileSync(registrarKeyPath(), exportSigningSecret(registrar), { mode: 384 });
1917
+ }
1918
+ setRegistrarAdBlob(exportAdBlob(registrar));
1919
+ log(`contact-book registrar ready (${registrar.cid})`);
1920
+ }
1921
+ function hasSavedState(dir) {
1922
+ try {
1923
+ return fs8.existsSync(dataPath(dir)) && fs8.statSync(dataPath(dir)).size > 0;
1924
+ } catch {
1925
+ return false;
1926
+ }
1927
+ }
1928
+ function saveState(id) {
1929
+ const bytes = withScope(
1930
+ (lt) => Buffer.from(readonlyTx(id, "::actor::export_state", lt).Serialize())
1931
+ );
1932
+ fs8.mkdirSync(id.dir, { recursive: true, mode: 448 });
1933
+ const final = dataPath(id.dir);
1934
+ const tmp = `${final}.tmp`;
1935
+ let fd;
1936
+ try {
1937
+ fd = fs8.openSync(tmp, "w", 384);
1938
+ fs8.fchmodSync(fd, 384);
1939
+ fs8.writeFileSync(fd, bytes);
1940
+ fs8.fsyncSync(fd);
1941
+ fs8.closeSync(fd);
1942
+ fd = void 0;
1943
+ fs8.renameSync(tmp, final);
1944
+ fs8.chmodSync(final, 384);
1945
+ try {
1946
+ fs8.chmodSync(keyPath(id.dir), 384);
1947
+ } catch {
1948
+ }
1949
+ const dirFd = fs8.openSync(id.dir, "r");
1950
+ try {
1951
+ fs8.fsyncSync(dirFd);
1952
+ } finally {
1953
+ fs8.closeSync(dirFd);
1954
+ }
1955
+ } catch (err) {
1956
+ if (fd !== void 0) {
1957
+ try {
1958
+ fs8.closeSync(fd);
1959
+ } catch {
1960
+ }
1961
+ }
1962
+ try {
1963
+ fs8.rmSync(tmp, { force: true });
1964
+ } catch {
1965
+ }
1966
+ throw err;
1967
+ }
1968
+ }
1969
+ function saveStateFailClosed(id) {
1970
+ try {
1971
+ saveState(id);
1972
+ if (id.persistFailed) {
1973
+ id.persistFailed = false;
1974
+ log(`[${id.name}] persist recovered \u2014 quarantine lifted`);
1975
+ appendNotifyLog(id, { event: "persist_recovered" });
1976
+ }
1977
+ } catch (err) {
1978
+ id.persistFailed = true;
1979
+ log(`[${id.name}] PERSIST FAILED \u2014 identity quarantined (fail-closed), outbound of this txn withheld:`, String(err));
1980
+ try {
1981
+ appendNotifyLog(id, { event: "persist_failed", error: String(err).slice(0, 300) });
1982
+ } catch {
1983
+ }
1984
+ process.nextTick(
1985
+ () => fireNotify(id.name, `[${id.name}] PERSIST FAILED \u2014 messaging quarantined until the state file is writable again`)
1986
+ );
1987
+ throw err;
1988
+ }
1989
+ }
1990
+
1991
+ // ../../src/mufl/tx.ts
1992
+ function withScope(fn) {
1993
+ const lt = new AdaptObjectLifetime2();
1994
+ try {
1995
+ return fn(lt);
1996
+ } finally {
1997
+ lt.Finalize();
1998
+ }
1999
+ }
2000
+ async function withScopeAsync(fn) {
2001
+ const lt = new AdaptObjectLifetime2();
2002
+ try {
2003
+ return await fn(lt);
2004
+ } finally {
2005
+ lt.Finalize();
2006
+ }
2007
+ }
2008
+ function readonlyTx(id, name, lt, targ) {
2009
+ const envelope = object_to_adapt_value({ name, targ });
2010
+ const result = id.pw.packet.ExecuteTransaction(envelope);
2011
+ envelope.Destroy();
2012
+ return lt ? result.Attach(lt) : result;
2013
+ }
2014
+ async function withLock(id, fn) {
2015
+ const prev = id.lock;
2016
+ let release;
2017
+ id.lock = new Promise((r) => release = r);
2018
+ await prev;
2019
+ try {
2020
+ return await fn();
2021
+ } finally {
2022
+ release();
2023
+ }
2024
+ }
2025
+ function enqueueMutation(id, envelope, timeoutMs = 25e3) {
2026
+ return new Promise((res, rej) => {
2027
+ const timer = setTimeout(() => {
2028
+ const i = id.pending.findIndex((p) => p.timer === timer);
2029
+ if (i >= 0) id.pending.splice(i, 1);
2030
+ rej(new Error("timed out waiting for the transaction result"));
2031
+ }, timeoutMs);
2032
+ id.pending.push({ resolve: res, reject: rej, timer });
2033
+ id.pw.add_client_message(envelope);
2034
+ });
2035
+ }
2036
+ function mutatingTx(id, name, targ, lt, timeoutMs) {
2037
+ if (id.persistFailed) {
2038
+ try {
2039
+ saveStateFailClosed(id);
2040
+ } catch (err) {
2041
+ return Promise.reject(new Error(
2042
+ `identity "${id.name}" is quarantined: state persist is failing (${String(err)}) \u2014 mutations are rejected until state_data.bin is writable again`
2043
+ ));
2044
+ }
2045
+ }
2046
+ const envelope = object_to_adapt_value({ name, targ });
2047
+ return withLock(id, () => enqueueMutation(id, envelope, timeoutMs)).then(
2048
+ (payload) => {
2049
+ envelope.Destroy();
2050
+ if (id.persistFailed) {
2051
+ try {
2052
+ payload.Destroy();
2053
+ } catch {
2054
+ }
2055
+ throw new Error(
2056
+ `identity "${id.name}": state persist FAILED \u2014 this transaction's outbound was withheld (fail-closed); fix the state directory and retry`
2057
+ );
2058
+ }
2059
+ return lt ? payload.Attach(lt) : payload;
2060
+ },
2061
+ (err) => {
2062
+ try {
2063
+ envelope.Destroy();
2064
+ } catch {
2065
+ }
2066
+ throw err;
2067
+ }
2068
+ );
2069
+ }
2070
+
2071
+ // ../../src/identity/hierarchy.ts
2072
+ function setRootName(name) {
2073
+ rootName = name;
2074
+ }
2075
+ var rootMarkerPath = () => join8(STATE_DIR, "root.json");
2076
+ var rootName = null;
2077
+ function readRootMarker() {
2078
+ try {
2079
+ const parsed = JSON.parse(fs9.readFileSync(rootMarkerPath(), "utf8"));
2080
+ return typeof parsed.name === "string" ? parsed.name : null;
2081
+ } catch {
2082
+ return null;
2083
+ }
2084
+ }
2085
+ function writeRootMarker(name) {
2086
+ fs9.mkdirSync(STATE_DIR, { recursive: true });
2087
+ const tmp = `${rootMarkerPath()}.tmp`;
2088
+ fs9.writeFileSync(tmp, JSON.stringify({ v: 1, name }));
2089
+ fs9.renameSync(tmp, rootMarkerPath());
2090
+ }
2091
+ function clearRootMarker() {
2092
+ fs9.rmSync(rootMarkerPath(), { force: true });
2093
+ }
2094
+ function describeIdentity(id) {
2095
+ return withScope((lt) => {
2096
+ const v = readonlyTx(id, "::actor::describe_identity", lt);
2097
+ return {
2098
+ bio: v.Reduce("bio").Visualize(),
2099
+ persona: v.Reduce("persona").Visualize(),
2100
+ hasCert: v.Reduce("has_cert").GetBoolean(),
2101
+ roleId: v.Reduce("role_id").Visualize(),
2102
+ rootCid: v.Reduce("root_cid").Visualize(),
2103
+ rootName: v.Reduce("root_name").Visualize(),
2104
+ monitoringEnabled: v.Reduce("monitoring_enabled").GetBoolean()
2105
+ };
2106
+ });
2107
+ }
2108
+ async function delegateRole(root, role) {
2109
+ await withScopeAsync(async (lt) => {
2110
+ const roleAd = exportAdBlob(role);
2111
+ const signed = await mutatingTx(root, "::actor::sign_delegation", {
2112
+ role_ad: root.pw.packet.NewBinaryFromBuffer(roleAd).Attach(lt),
2113
+ role_id: role.name
2114
+ }, lt);
2115
+ const certBlob = Buffer.from(signed.Reduce("cert").GetBinary());
2116
+ const profileData = await mutatingTx(root, "::actor::export_root_profile", {}, lt);
2117
+ const profileBlob = Buffer.from(profileData.Reduce("profile").GetBinary());
2118
+ const rootAdBlob = exportAdBlob(root);
2119
+ const roleAdV1 = Buffer.from(
2120
+ (await mutatingTx(role, "::actor::export_v1_address_document", {}, lt)).Reduce("ad").GetBinary()
2121
+ );
2122
+ const signedV1 = await mutatingTx(root, "::actor::sign_delegation", {
2123
+ role_ad: root.pw.packet.NewBinaryFromBuffer(roleAdV1).Attach(lt),
2124
+ role_id: role.name
2125
+ }, lt);
2126
+ const certV1Blob = Buffer.from(signedV1.Reduce("cert").GetBinary());
2127
+ await mutatingTx(role, "::actor::set_delegation", {
2128
+ cert: role.pw.packet.NewBinaryFromBuffer(certBlob).Attach(lt),
2129
+ root_ad: role.pw.packet.NewBinaryFromBuffer(rootAdBlob).Attach(lt),
2130
+ root_profile: role.pw.packet.NewBinaryFromBuffer(profileBlob).Attach(lt),
2131
+ cert_v1: role.pw.packet.NewBinaryFromBuffer(certV1Blob).Attach(lt)
2132
+ }, lt);
2133
+ });
2134
+ log(`[${role.name}] delegated as a role under root "${root.name}"`);
2135
+ }
2136
+ async function establishRoot(id) {
2137
+ rootName = id.name;
2138
+ writeRootMarker(id.name);
2139
+ const adopted = [];
2140
+ const failed = [];
2141
+ for (const other of identities.values()) {
2142
+ if (other.name === id.name) continue;
2143
+ if (other.temp) continue;
2144
+ try {
2145
+ await delegateRole(id, other);
2146
+ adopted.push(other.name);
2147
+ } catch (err) {
2148
+ log(`failed to adopt "${other.name}" as a role under new root "${id.name}":`, String(err));
2149
+ failed.push(other.name);
2150
+ }
2151
+ }
2152
+ log(`[${id.name}] established as the host root${adopted.length ? ` (adopted ${adopted.length} role(s))` : ""}`);
2153
+ return { adopted, failed };
2154
+ }
2155
+
2156
+ // ../../src/identity/lifecycle.ts
2157
+ import { join as join9 } from "node:path";
2158
+ import * as fs10 from "node:fs";
2159
+ function deleteIdentityCompletely(id) {
2160
+ try {
2161
+ wrapper.remove_packet(id.cid);
2162
+ } catch (err) {
2163
+ log(`remove_packet(${id.cid}) failed:`, String(err));
2164
+ }
2165
+ identities.delete(id.name);
2166
+ try {
2167
+ unpublishFromBook(id);
2168
+ } catch (err) {
2169
+ log(`failed to unpublish "${id.name}" from the contact book:`, String(err));
2170
+ }
2171
+ if (leases.has(id.name)) {
2172
+ leases.delete(id.name);
2173
+ persistBindings();
2174
+ }
2175
+ if (id.name === rootName) {
2176
+ setRootName(null);
2177
+ clearRootMarker();
2178
+ }
2179
+ try {
2180
+ fs10.rmSync(id.dir, { recursive: true, force: true });
2181
+ reservedNames.delete(id.name);
2182
+ } catch (err) {
2183
+ return `deleting ${id.dir} failed: ${String(err)}`;
2184
+ }
2185
+ return null;
2186
+ }
2187
+ function closeTemporaryIdentity(id, cause) {
2188
+ const t = id.temp;
2189
+ if (!t) return Promise.reject(new Error(`identity "${id.name}" is not temporary`));
2190
+ if (t.closing) return t.closing;
2191
+ t.closing = (async () => {
2192
+ log(`[${id.name}] closing temporary identity (${cause})`);
2193
+ let contacts = [];
2194
+ try {
2195
+ contacts = withScope((lt) => renderContacts(readonlyTx(id, "::a2a_messaging::list_contacts", lt)));
2196
+ } catch (err) {
2197
+ log(`[${id.name}] contact snapshot failed during close (continuing with local cleanup):`, String(err));
2198
+ }
2199
+ let notified = 0;
2200
+ let failed = 0;
2201
+ outboundRemovalInFlight.add(id.name);
2202
+ try {
2203
+ for (const c of contacts) {
2204
+ try {
2205
+ const queued = await withScopeAsync(async (lt) => {
2206
+ const data = await mutatingTx(id, "::a2a_messaging::remove_contact", { contact: c.container_id }, lt);
2207
+ const nv = data.Reduce("notified");
2208
+ return nv.IsNil() ? false : nv.GetBoolean();
2209
+ });
2210
+ if (queued) notified++;
2211
+ else failed++;
2212
+ } catch (err) {
2213
+ failed++;
2214
+ log(`[${id.name}] remove-me to ${c.container_id} failed (continuing):`, String(err));
2215
+ }
2216
+ }
2217
+ } finally {
2218
+ outboundRemovalInFlight.delete(id.name);
2219
+ }
2220
+ const deleteError = deleteIdentityCompletely(id);
2221
+ if (deleteError) {
2222
+ log(`[${id.name}] temporary identity close: LOCAL DELETE FAILED \u2014 ${deleteError}`);
2223
+ } else {
2224
+ log(
2225
+ `[${id.name}] temporary identity closed: ${contacts.length} contact(s), ${notified} remove-me notice(s) queued, ${failed} not sent (delivery unverified by design)`
2226
+ );
2227
+ }
2228
+ return { attempted: contacts.length, notified, failed, deleteError };
2229
+ })();
2230
+ return t.closing;
2231
+ }
2232
+ function sweepStaleTempIdentities() {
2233
+ for (const id of [...identities.values()]) {
2234
+ const t = id.temp;
2235
+ if (!t || t.closing) continue;
2236
+ if (pidAlive(t.owner.pid)) continue;
2237
+ const lease = leases.get(id.name);
2238
+ if (lease && pidAlive(lease.pid)) continue;
2239
+ void closeTemporaryIdentity(id, `stale lease \u2014 owner pid ${t.owner.pid} is dead`).catch(
2240
+ (err) => log(`[${id.name}] stale-temp reclaim failed:`, String(err))
2241
+ );
2242
+ }
2243
+ }
2244
+ function sweepOrphanTempDirs() {
2245
+ if (!fs10.existsSync(STATE_DIR)) return;
2246
+ for (const d of fs10.readdirSync(STATE_DIR, { withFileTypes: true })) {
2247
+ if (!d.isDirectory() || identities.has(d.name)) continue;
2248
+ const dir = join9(STATE_DIR, d.name);
2249
+ const meta = readTempMetaFile(dir);
2250
+ if (!meta || pidAlive(meta.owner.pid)) continue;
2251
+ try {
2252
+ fs10.rmSync(dir, { recursive: true, force: true });
2253
+ reservedNames.delete(d.name);
2254
+ log(`[${d.name}] removed orphaned temporary-identity dir (owner pid ${meta.owner.pid} dead, no live packet)`);
2255
+ } catch (err) {
2256
+ log(`[${d.name}] failed to remove orphaned temporary-identity dir:`, String(err));
2257
+ }
2258
+ }
2259
+ }
2260
+
2261
+ // ../../src/cluster/manifest.ts
2262
+ function manifestCapabilities(id) {
2263
+ try {
2264
+ return withScope((lt) => {
2265
+ const caps = readonlyTx(id, "::a2a_capabilities::get_manifest", lt).Reduce("capabilities");
2266
+ if (caps.IsNil()) return [];
2267
+ return [...caps.GetKeys()].map((k) => typeof k === "string" ? k : k.Visualize());
2268
+ });
2269
+ } catch {
2270
+ return [];
2271
+ }
2272
+ }
2273
+
2274
+ // ../../src/cluster/control.ts
2275
+ var envelopeDispatch = () => ENVELOPE_DISPATCH;
2276
+ var ENVELOPE_DISPATCH = true;
2277
+ var PROTOCOL_VERSION = ENVELOPE_DISPATCH ? 2 : 1;
2278
+ function enumerateChildren(root, lt) {
2279
+ const children = [];
2280
+ for (const id of identities.values()) {
2281
+ if (id.name === root.name) continue;
2282
+ const info = describeIdentity(id);
2283
+ if (info.rootCid !== root.cid) continue;
2284
+ const childAd = readonlyTx(id, "::actor::export_address_document_native", lt);
2285
+ children.push({
2286
+ cid: childAd.Reduce("identity").Reduce("container_id"),
2287
+ role_id: info.roleId,
2288
+ name: id.name,
2289
+ bio: info.bio,
2290
+ persona: info.persona,
2291
+ caps: manifestCapabilities(id),
2292
+ // RR-4: real caps, so a backfilled member isn't hard-blocked
2293
+ child_ad: childAd
2294
+ });
2295
+ }
2296
+ return children;
2297
+ }
2298
+ var sweepBusy = /* @__PURE__ */ new Set();
2299
+ async function clusterSweep(root) {
2300
+ if (sweepBusy.has(root.name)) return;
2301
+ sweepBusy.add(root.name);
2302
+ try {
2303
+ await withScopeAsync(async (lt) => {
2304
+ await mutatingTx(root, "::a2a_cluster::reconcile", { pending_handle: "", children: enumerateChildren(root, lt) }, lt);
2305
+ });
2306
+ } catch (err) {
2307
+ log(`[${root.name}] cluster sweep failed:`, String(err));
2308
+ } finally {
2309
+ sweepBusy.delete(root.name);
2310
+ }
2311
+ }
2312
+ var SWEEP_INTERVAL_MS = 6e4;
2313
+ function startClusterSweep() {
2314
+ setInterval(() => {
2315
+ if (!ENVELOPE_DISPATCH) return;
2316
+ for (const id of identities.values()) {
2317
+ if (describeIdentity(id).roleId === "") void clusterSweep(id);
2318
+ }
2319
+ }, SWEEP_INTERVAL_MS).unref();
2320
+ }
2321
+
2322
+ // ../../src/boot.ts
2323
+ var booted = false;
2324
+ function wrapperBooted() {
2325
+ return booted;
2326
+ }
2327
+ function guardBoot() {
2328
+ if (booted) {
2329
+ throw new Error(
2330
+ 'bootWrapper() has already run in this process. It boots the ADAPT environment, which can only be initialised once \u2014 a second call fails inside the native layer with "Failed to invoke initializer in ADAPT environment", which names neither the double init nor the caller. NOTE THAT startDaemon() CALLS bootWrapper() ITSELF: the two are alternatives, not steps. Use startDaemon() for the HTTP daemon, or bootWrapper() alone for a stdio/in-process host \u2014 never both.'
2331
+ );
2332
+ }
2333
+ booted = true;
2334
+ assertConfigNotDrifted();
2335
+ }
2336
+ async function bootWrapper() {
2337
+ guardBoot();
2338
+ startupProgress?.update("wrapper");
2339
+ setUnit(locateUnit());
2340
+ const argv = [
2341
+ "--broker_address",
2342
+ BROKER_URL,
2343
+ "--test_mode",
2344
+ "--logger_config",
2345
+ "--level",
2346
+ "INFO",
2347
+ "--stdout",
2348
+ "stderr",
2349
+ "--logger_config_end"
2350
+ ];
2351
+ log(`booting wrapper (unit ${UNIT.hash.slice(0, 12)}\u2026, broker ${BROKER_URL})`);
2352
+ setWrapper(await adapt_wrapper.start(argv));
2353
+ wrapper.on_packet_created_cb = (cid) => log(`wrapper: packet ready ${cid.slice(0, 12)}\u2026`);
2354
+ wrapper.start();
2355
+ const bootHoldMs = Number(process.env.OURS_TEST_BOOT_HOLD_MS || "") || 0;
2356
+ if (bootHoldMs > 0) {
2357
+ log(`TEST HOLD: boot window open ${bootHoldMs}ms (OURS_TEST_BOOT_HOLD_MS)`);
2358
+ await new Promise((r) => setTimeout(r, bootHoldMs));
2359
+ }
2360
+ startupProgress?.update("registrar");
2361
+ try {
2362
+ await ensureRegistrar();
2363
+ } catch (err) {
2364
+ log("failed to start the contact-book registrar (local contact book disabled):", String(err));
2365
+ }
2366
+ tightenIdentityPerms();
2367
+ const names = listPersistedNames();
2368
+ const fakeRestoreCount = Math.max(0, Number(process.env.OURS_TEST_FAKE_RESTORE_COUNT || "") || 0);
2369
+ const fakeRestoreMs = Math.max(0, Number(process.env.OURS_TEST_FAKE_RESTORE_MS || "") || 0);
2370
+ const restoreTotal = names.length === 0 && fakeRestoreCount > 0 ? fakeRestoreCount : names.length;
2371
+ startupProgress?.update("identities", { completed: 0, total: restoreTotal });
2372
+ for (const n of names) reservedNames.add(n);
2373
+ if (names.length === 0 && fakeRestoreCount > 0) {
2374
+ log(`TEST: simulating ${fakeRestoreCount} fake identity restore(s), ${fakeRestoreMs}ms each`);
2375
+ for (let i = 0; i < fakeRestoreCount; i++) {
2376
+ if (fakeRestoreMs > 0) await new Promise((r) => setTimeout(r, fakeRestoreMs));
2377
+ startupProgress?.update("identities", { completed: i + 1, total: fakeRestoreCount });
2378
+ }
2379
+ } else if (names.length === 0) {
2380
+ log("no persisted identities \u2014 start with create_identity");
2381
+ } else {
2382
+ log(`restoring ${names.length} identit${names.length === 1 ? "y" : "ies"}: ${names.join(", ")}`);
2383
+ for (const [index, name] of names.entries()) {
2384
+ try {
2385
+ const id = await restoreIdentity(name);
2386
+ if (registrar) {
2387
+ await pinRegistrar(id);
2388
+ }
2389
+ const tempMeta = readTempMetaFile(id.dir);
2390
+ if (tempMeta) id.temp = tempMeta;
2391
+ } catch (err) {
2392
+ log(`failed to restore "${name}":`, String(err));
2393
+ }
2394
+ startupProgress?.update("identities", { completed: index + 1, total: names.length });
2395
+ }
2396
+ }
2397
+ startupProgress?.update("reconciliation");
2398
+ startClusterSweep();
2399
+ setRootName(readRootMarker());
2400
+ if (rootName && !identities.has(rootName)) {
2401
+ log(`root marker names a missing identity "${rootName}" \u2014 clearing it`);
2402
+ setRootName(null);
2403
+ clearRootMarker();
2404
+ }
2405
+ if (rootName) log(`root identity: ${rootName}`);
2406
+ {
2407
+ const hostRoot = rootName ? identities.get(rootName) : void 0;
2408
+ if (rootName && !hostRoot) {
2409
+ log(`re-delegation on upgrade DEGRADED: root "${rootName}" is not among the restored identities \u2014 role certs not refreshed this boot`);
2410
+ }
2411
+ const refreshedRoles = [];
2412
+ if (hostRoot) {
2413
+ for (const id of identities.values()) {
2414
+ if (id.name === hostRoot.name) continue;
2415
+ let info;
2416
+ try {
2417
+ info = describeIdentity(id);
2418
+ } catch {
2419
+ info = void 0;
2420
+ }
2421
+ if (!info || !info.hasCert) continue;
2422
+ if (info.rootCid !== hostRoot.cid) {
2423
+ log(`[${id.name}] re-delegation skipped: delegated by a different root (${info.rootCid.slice(0, 12)}\u2026), not this host root \u2014 left as-is`);
2424
+ continue;
2425
+ }
2426
+ try {
2427
+ await delegateRole(hostRoot, id);
2428
+ refreshedRoles.push(id);
2429
+ } catch (err) {
2430
+ appendNotifyLog(id, { event: "redelegation_failed", error: String(err).slice(0, 300) });
2431
+ log(`[${id.name}] boot re-delegation (upgrade cert refresh) failed:`, String(err));
2432
+ }
2433
+ }
2434
+ }
2435
+ for (const id of identities.values()) {
2436
+ await capabilityReconcileSweep(id);
2437
+ await e2eRecoverySweep(id);
2438
+ for (const ms of [1e4, 3e4, 9e4]) {
2439
+ setTimeout(() => {
2440
+ capabilityReconcileSweep(id).catch(() => {
2441
+ });
2442
+ }, ms);
2443
+ setTimeout(() => {
2444
+ e2eRecoverySweep(id).catch(() => {
2445
+ });
2446
+ }, ms);
2447
+ }
2448
+ }
2449
+ if (refreshedRoles.length > 0) log(`refreshed ${refreshedRoles.length} role delegation cert(s) against the live AD on boot`);
2450
+ }
2451
+ persistBindings();
2452
+ sweepOrphanTempDirs();
2453
+ sweepStaleTempIdentities();
2454
+ }
2455
+
2456
+ export {
2457
+ VERSION,
2458
+ CONFIG,
2459
+ STATE_DIR,
2460
+ BROKER_URL,
2461
+ PORT,
2462
+ GC_INTERVAL_MS,
2463
+ API_VISIBILITY,
2464
+ startupProgress,
2465
+ log,
2466
+ requireAuth,
2467
+ validateName,
2468
+ FILE_SELECTION_CAP,
2469
+ identities,
2470
+ registrar,
2471
+ hashLeaseToken,
2472
+ writeTempMetaFile,
2473
+ isSelectableWireId,
2474
+ findIdentityFile,
2475
+ leases,
2476
+ tombstones,
2477
+ sessionHeaders,
2478
+ outboundRemovalInFlight,
2479
+ pidAlive,
2480
+ leaseByToken,
2481
+ persistBindings,
2482
+ resolveBound,
2483
+ bindSession,
2484
+ buildMessagesPayload,
2485
+ mimeFromExt,
2486
+ renderContacts,
2487
+ renderImportRenames,
2488
+ renderInbox,
2489
+ renderFileMetadata,
2490
+ writeIncomingFiles,
2491
+ renderPending,
2492
+ renderContactRoots,
2493
+ encodeWireBin,
2494
+ decodeWireBin,
2495
+ setNotifyHook,
2496
+ clearNotifyHook,
2497
+ appendNotifyLog,
2498
+ readE2eWireIds,
2499
+ serveNotifications,
2500
+ refreshUnread,
2501
+ unreadSummary,
2502
+ renderDegraded,
2503
+ contactRestoreSweep,
2504
+ capabilityReconcileSweep,
2505
+ scheduleCapabilityReconcile,
2506
+ e2eRecoverySweep,
2507
+ readBook,
2508
+ exportAdBlob,
2509
+ publishToBook,
2510
+ unpublishFromBook,
2511
+ reservedNames,
2512
+ provisionIdentity,
2513
+ saveState,
2514
+ withScope,
2515
+ withScopeAsync,
2516
+ readonlyTx,
2517
+ mutatingTx,
2518
+ rootName,
2519
+ describeIdentity,
2520
+ delegateRole,
2521
+ establishRoot,
2522
+ deleteIdentityCompletely,
2523
+ closeTemporaryIdentity,
2524
+ sweepStaleTempIdentities,
2525
+ envelopeDispatch,
2526
+ PROTOCOL_VERSION,
2527
+ clusterSweep,
2528
+ wrapperBooted,
2529
+ bootWrapper
2530
+ };