@ours.network/cowork 0.4.1-nightly.20260816.4aaf940 → 0.5.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.
package/dist/daemon.js CHANGED
@@ -10,6 +10,446 @@ var __export = (target, all) => {
10
10
  __defProp(target, name, { get: all[name], enumerable: true });
11
11
  };
12
12
 
13
+ // src/adapt.ts
14
+ import * as fs from "node:fs";
15
+ import { dirname, join, resolve } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ import { brotliCompressSync, brotliDecompressSync, constants as zlibConstants } from "node:zlib";
18
+ function withScope(fn) {
19
+ const lifetime = new AdaptObjectLifetime();
20
+ try {
21
+ return fn(lifetime);
22
+ } finally {
23
+ lifetime.Finalize();
24
+ }
25
+ }
26
+ async function withScopeAsync(fn) {
27
+ const lifetime = new AdaptObjectLifetime();
28
+ try {
29
+ return await fn(lifetime);
30
+ } finally {
31
+ lifetime.Finalize();
32
+ }
33
+ }
34
+ function locateUnit() {
35
+ const here = dirname(fileURLToPath(import.meta.url));
36
+ const override = process.env.OURS_COWORK_UNIT_DIR;
37
+ const candidates = override ? [resolve(override)] : [join(here, "mufl_code"), join(here, "..", "mufl_code")];
38
+ for (const dir of candidates) {
39
+ if (!fs.existsSync(dir)) continue;
40
+ const compiled = fs.readdirSync(dir).find((name) => name.endsWith(".muflo"));
41
+ if (compiled) {
42
+ return {
43
+ dir,
44
+ hash: compiled.slice(0, -".muflo".length),
45
+ contents: new Uint8Array(fs.readFileSync(join(dir, compiled)))
46
+ };
47
+ }
48
+ }
49
+ throw new Error(`no compiled .muflo packet found (looked in: ${candidates.join(", ")})`);
50
+ }
51
+ function wireHandlers(packet, hooks, log) {
52
+ const settleAfterActionLoop = (pending) => {
53
+ queueMicrotask(() => {
54
+ if (packet.pending[0] !== pending || !pending.payload || pending.callbackError) return;
55
+ packet.pending.shift();
56
+ clearTimeout(pending.timer);
57
+ pending.settled = true;
58
+ pending.resolve(pending.payload);
59
+ });
60
+ };
61
+ packet.pw.on_return_data = (data) => {
62
+ const lifetime = new AdaptObjectLifetime();
63
+ data.Attach(lifetime);
64
+ try {
65
+ if (packet.isClosed) return;
66
+ const kind = data.Reduce("kind").Visualize();
67
+ if (kind === "save_state") {
68
+ try {
69
+ hooks.onSaveState();
70
+ } catch (error) {
71
+ const pending2 = packet.pending[0];
72
+ if (pending2) pending2.callbackError = asError(error);
73
+ throw error;
74
+ }
75
+ return;
76
+ }
77
+ if (kind === "notify_agent") {
78
+ const payload = data.Reduce("payload");
79
+ hooks.onNotify(payload.Reduce("event").Visualize(), payload);
80
+ return;
81
+ }
82
+ const pending = packet.pending[0];
83
+ if (!pending) return;
84
+ if (pending.expired) {
85
+ packet.releaseExpired(pending);
86
+ return;
87
+ }
88
+ if (pending.payload) pending.payload.Destroy();
89
+ pending.payload = data.Reduce("payload").Detach();
90
+ settleAfterActionLoop(pending);
91
+ } finally {
92
+ lifetime.Finalize();
93
+ }
94
+ };
95
+ packet.pw.on_transaction_failure = (message) => {
96
+ log(`[${packet.name}] transaction rejected (origin uncorrelated):`, message);
97
+ };
98
+ }
99
+ function asError(error) {
100
+ return error instanceof Error ? error : new Error(String(error));
101
+ }
102
+ function packInvite(raw) {
103
+ return brotliCompressSync(raw, {
104
+ params: {
105
+ [zlibConstants.BROTLI_PARAM_QUALITY]: 11,
106
+ [zlibConstants.BROTLI_PARAM_SIZE_HINT]: raw.length
107
+ }
108
+ }).toString("base64url");
109
+ }
110
+ function unpackInvite(encoded, maximumBytes) {
111
+ const normalized = encoded.replace(/\s+/g, "");
112
+ if (maximumBytes !== void 0 && Buffer.byteLength(encoded, "utf8") > maximumBytes || normalized.length === 0 || !/^[A-Za-z0-9_-]+$/.test(normalized)) {
113
+ throw new Error("the invite blob is empty, oversized, or invalid base64url");
114
+ }
115
+ const compressed = Buffer.from(normalized, "base64url");
116
+ if (compressed.length === 0) throw new Error("the invite blob is empty or invalid base64url");
117
+ return Buffer.from(maximumBytes === void 0 ? brotliDecompressSync(compressed) : brotliDecompressSync(compressed, { maxOutputLength: maximumBytes }));
118
+ }
119
+ var commonSdk, executableSdk, wrapperSdk, wrappersSdk, AdaptObjectLifetime, adapt_wrapper, object_to_adapt_value, PacketWrapperConfigurator, LATE_RESULT_DRAIN_MS, Packet, AdaptHostShutdownError, AdaptHost;
120
+ var init_adapt = __esm({
121
+ async "src/adapt.ts"() {
122
+ "use strict";
123
+ [commonSdk, executableSdk, wrapperSdk, wrappersSdk] = await Promise.all([
124
+ import("@adapt-toolkit/sdk/common"),
125
+ import("@adapt-toolkit/sdk/executables"),
126
+ import("@adapt-toolkit/sdk/wrapper"),
127
+ import("@adapt-toolkit/sdk/wrappers")
128
+ ]);
129
+ ({ AdaptObjectLifetime } = commonSdk);
130
+ ({ adapt_wrapper } = executableSdk);
131
+ ({ object_to_adapt_value } = wrapperSdk);
132
+ ({ PacketWrapperConfigurator } = wrappersSdk);
133
+ LATE_RESULT_DRAIN_MS = 50;
134
+ Packet = class {
135
+ pending = [];
136
+ lock = Promise.resolve();
137
+ expiredDrain;
138
+ releaseExpiredDrain;
139
+ closedError;
140
+ terminalNotified = false;
141
+ onTerminal;
142
+ terminalListeners = /* @__PURE__ */ new Set();
143
+ makeEnvelope;
144
+ name;
145
+ cid;
146
+ pw;
147
+ constructor(name, cid, pw, onTerminal = () => {
148
+ }, makeEnvelope = (transactionName, targ) => object_to_adapt_value({ name: transactionName, targ })) {
149
+ this.name = name;
150
+ this.cid = cid;
151
+ this.pw = pw;
152
+ this.onTerminal = onTerminal;
153
+ this.makeEnvelope = makeEnvelope;
154
+ }
155
+ get isClosed() {
156
+ return this.closedError !== void 0;
157
+ }
158
+ onTerminalClose(listener) {
159
+ if (this.closedError) {
160
+ listener(this.closedError);
161
+ return () => {
162
+ };
163
+ }
164
+ this.terminalListeners.add(listener);
165
+ return () => this.terminalListeners.delete(listener);
166
+ }
167
+ assertOpen() {
168
+ if (this.closedError) throw this.closedError;
169
+ }
170
+ readonlyTx(name, lifetime) {
171
+ this.assertOpen();
172
+ const envelope = this.makeEnvelope(name, void 0);
173
+ try {
174
+ const result = this.pw.packet.ExecuteTransaction(envelope);
175
+ return lifetime ? result.Attach(lifetime) : result;
176
+ } finally {
177
+ envelope.Destroy();
178
+ }
179
+ }
180
+ async withLock(fn) {
181
+ this.assertOpen();
182
+ const previous = this.lock;
183
+ let release;
184
+ this.lock = new Promise((resolveLock) => {
185
+ release = resolveLock;
186
+ });
187
+ await previous;
188
+ try {
189
+ this.assertOpen();
190
+ if (this.expiredDrain) await this.expiredDrain;
191
+ this.assertOpen();
192
+ return await fn();
193
+ } finally {
194
+ release();
195
+ }
196
+ }
197
+ enqueue(envelope, timeoutMs) {
198
+ return new Promise((resolveResult, rejectResult) => {
199
+ const timer = setTimeout(() => {
200
+ const pending2 = this.pending.find((candidate) => candidate.timer === timer);
201
+ if (!pending2 || pending2.settled) return;
202
+ pending2.settled = true;
203
+ pending2.expired = true;
204
+ pending2.reject(new Error(`timed out waiting for transaction result on packet "${this.name}"`));
205
+ this.expiredDrain = new Promise((resolveDrain) => {
206
+ this.releaseExpiredDrain = resolveDrain;
207
+ });
208
+ pending2.lateTimer = setTimeout(() => {
209
+ if (this.closedError || !pending2.expired || !this.pending.includes(pending2)) return;
210
+ this.close(new Error(
211
+ `packet "${this.name}" closed after a timed-out transaction produced no correlated callback`
212
+ ));
213
+ }, LATE_RESULT_DRAIN_MS);
214
+ }, timeoutMs);
215
+ const pending = { resolve: resolveResult, reject: rejectResult, timer };
216
+ this.pending.push(pending);
217
+ try {
218
+ this.pw.add_client_message(envelope);
219
+ } catch (error) {
220
+ clearTimeout(timer);
221
+ const index = this.pending.indexOf(pending);
222
+ if (index >= 0) this.pending.splice(index, 1);
223
+ rejectResult(asError(error));
224
+ }
225
+ });
226
+ }
227
+ mutatingTx(name, targ, lifetime, timeoutMs = 25e3) {
228
+ let envelope;
229
+ try {
230
+ this.assertOpen();
231
+ envelope = this.makeEnvelope(name, targ);
232
+ } catch (error) {
233
+ return Promise.reject(asError(error));
234
+ }
235
+ return this.withLock(() => this.enqueue(envelope, timeoutMs)).then(
236
+ (payload) => {
237
+ envelope.Destroy();
238
+ return lifetime ? payload.Attach(lifetime) : payload;
239
+ },
240
+ (error) => {
241
+ envelope.Destroy();
242
+ throw error;
243
+ }
244
+ );
245
+ }
246
+ newBinary(bytes, lifetime) {
247
+ this.assertOpen();
248
+ const value = this.pw.packet.NewBinaryFromBuffer(bytes);
249
+ return lifetime ? value.Attach(lifetime) : value;
250
+ }
251
+ close(error = new Error(`packet "${this.name}" is closed`)) {
252
+ if (this.closedError) return;
253
+ this.closedError = error;
254
+ const pending = this.pending.splice(0);
255
+ for (const call of pending) {
256
+ clearTimeout(call.timer);
257
+ if (call.lateTimer) clearTimeout(call.lateTimer);
258
+ call.payload?.Destroy();
259
+ if (!call.settled) {
260
+ call.settled = true;
261
+ call.reject(error);
262
+ }
263
+ }
264
+ this.releaseExpiredDrain?.();
265
+ this.releaseExpiredDrain = void 0;
266
+ this.expiredDrain = void 0;
267
+ if (!this.terminalNotified) {
268
+ this.terminalNotified = true;
269
+ for (const listener of this.terminalListeners) listener(error);
270
+ this.terminalListeners.clear();
271
+ this.onTerminal(error);
272
+ }
273
+ }
274
+ releaseExpired(pending) {
275
+ const index = this.pending.indexOf(pending);
276
+ if (index >= 0) this.pending.splice(index, 1);
277
+ if (pending.lateTimer) clearTimeout(pending.lateTimer);
278
+ pending.payload?.Destroy();
279
+ this.releaseExpiredDrain?.();
280
+ this.releaseExpiredDrain = void 0;
281
+ this.expiredDrain = void 0;
282
+ }
283
+ };
284
+ AdaptHostShutdownError = class extends AggregateError {
285
+ requiresProcessExit;
286
+ constructor(errors, requiresProcessExit) {
287
+ super(errors, "AdaptHost shutdown encountered errors");
288
+ this.name = "AdaptHostShutdownError";
289
+ this.requiresProcessExit = requiresProcessExit;
290
+ }
291
+ };
292
+ AdaptHost = class {
293
+ wrapper;
294
+ packets = /* @__PURE__ */ new Map();
295
+ exposedPackets = /* @__PURE__ */ new Set();
296
+ brokerUrl;
297
+ log;
298
+ unit;
299
+ shutdownWrapper;
300
+ constructor(brokerUrl, log = () => {
301
+ }, options = {}) {
302
+ this.brokerUrl = brokerUrl;
303
+ this.log = log;
304
+ this.unit = options.unit ?? locateUnit();
305
+ this.shutdownWrapper = options.shutdownWrapper;
306
+ }
307
+ get packetCount() {
308
+ return this.packets.size;
309
+ }
310
+ async boot() {
311
+ if (this.wrapper) return;
312
+ this.wrapper = await adapt_wrapper.start([
313
+ "--broker_address",
314
+ this.brokerUrl,
315
+ "--test_mode",
316
+ "--logger_config",
317
+ "--level",
318
+ "WARNING",
319
+ "--stdout",
320
+ "stderr",
321
+ "--logger_config_end"
322
+ ]);
323
+ this.wrapper.on_packet_created_cb = (cid) => {
324
+ this.log(`wrapper: packet ready ${cid.slice(0, 12)}\u2026`);
325
+ };
326
+ this.wrapper.start();
327
+ }
328
+ createPacket(name, seed, signingSecret, options = {}) {
329
+ const wrapper = this.wrapper;
330
+ if (!wrapper) return Promise.reject(new Error("AdaptHost.boot() must complete before creating packets"));
331
+ const config = new PacketWrapperConfigurator();
332
+ config.deferred_exposure = options.deferredExposure ?? false;
333
+ const args = [
334
+ "--unit_hash",
335
+ this.unit.hash,
336
+ "--seed_phrase",
337
+ seed,
338
+ "--unit_dir_path",
339
+ this.unit.dir
340
+ ];
341
+ if (signingSecret) args.push("--init_trn_argument", JSON.stringify(signingSecret));
342
+ config.process_arguments(args);
343
+ return new Promise((resolveCreate, rejectCreate) => {
344
+ let settled = false;
345
+ const timer = setTimeout(() => {
346
+ settled = true;
347
+ rejectCreate(new Error(`packet creation for "${name}" timed out`));
348
+ }, 3e4);
349
+ try {
350
+ wrapper.packet_manager.create_packet(config, (pw) => {
351
+ if (settled) {
352
+ pw.dispose();
353
+ return;
354
+ }
355
+ settled = true;
356
+ clearTimeout(timer);
357
+ const cid = withScope((lifetime) => pw.packet.GetContainerID().Attach(lifetime).Visualize());
358
+ let packet;
359
+ packet = new Packet(name, cid, pw, (error) => {
360
+ queueMicrotask(() => {
361
+ if (this.packets.get(cid) !== packet) return;
362
+ try {
363
+ this.removePacket(cid, error);
364
+ } catch (removeError) {
365
+ this.log(`[${name}] terminal packet removal failed:`, removeError);
366
+ }
367
+ });
368
+ });
369
+ this.packets.set(cid, packet);
370
+ if (!config.deferred_exposure) this.exposedPackets.add(cid);
371
+ resolveCreate(packet);
372
+ }, this.unit.contents);
373
+ } catch (error) {
374
+ if (settled) return;
375
+ settled = true;
376
+ clearTimeout(timer);
377
+ rejectCreate(asError(error));
378
+ }
379
+ });
380
+ }
381
+ exposePacket(cid) {
382
+ const wrapper = this.wrapper;
383
+ const packet = this.packets.get(cid);
384
+ if (!wrapper || !packet) throw new Error(`cannot expose unknown packet ${cid}`);
385
+ if (packet.isClosed) throw new Error(`cannot expose closed packet ${cid}`);
386
+ wrapper.expose_packet(cid);
387
+ this.exposedPackets.add(cid);
388
+ }
389
+ isPacketExposed(cid) {
390
+ return this.exposedPackets.has(cid);
391
+ }
392
+ removePacket(cid, error = new Error(`packet ${cid} removed from host`)) {
393
+ const wrapper = this.wrapper;
394
+ if (!wrapper) throw new Error("AdaptHost is not booted");
395
+ const packet = this.packets.get(cid);
396
+ if (!packet) return;
397
+ packet.close(error);
398
+ wrapper.remove_packet(cid);
399
+ this.packets.delete(cid);
400
+ this.exposedPackets.delete(cid);
401
+ }
402
+ close() {
403
+ if (!this.wrapper) return;
404
+ const errors = [];
405
+ for (const cid of [...this.packets.keys()]) {
406
+ try {
407
+ this.removePacket(cid, new Error("AdaptHost closed"));
408
+ } catch (error) {
409
+ errors.push(asError(error));
410
+ }
411
+ }
412
+ this.wrapper = void 0;
413
+ if (errors.length) throw new AggregateError(errors, "failed to remove all hosted packets");
414
+ }
415
+ /**
416
+ * Release every public SDK resource. SDK 0.10.12 exposes packet disposal but
417
+ * not its private broker client's stop(), so a real native wrapper reports
418
+ * that the owning process must exit after graceful daemon cleanup.
419
+ */
420
+ async shutdown() {
421
+ const wrapper = this.wrapper;
422
+ if (!wrapper) return { requiresProcessExit: false };
423
+ const errors = [];
424
+ let requiresProcessExit = true;
425
+ try {
426
+ this.close();
427
+ } catch (error) {
428
+ errors.push(error);
429
+ }
430
+ try {
431
+ if (this.shutdownWrapper) {
432
+ await this.shutdownWrapper(wrapper);
433
+ requiresProcessExit = false;
434
+ } else {
435
+ const futureWrapper = wrapper;
436
+ const publicStop = futureWrapper.shutdown ?? futureWrapper.stop ?? futureWrapper.dispose;
437
+ if (typeof publicStop === "function") {
438
+ await publicStop.call(wrapper);
439
+ requiresProcessExit = false;
440
+ }
441
+ }
442
+ } catch (error) {
443
+ errors.push(error);
444
+ }
445
+ this.wrapper = void 0;
446
+ if (errors.length > 0) throw new AdaptHostShutdownError(errors, requiresProcessExit);
447
+ return { requiresProcessExit };
448
+ }
449
+ };
450
+ }
451
+ });
452
+
13
453
  // node_modules/zod/v3/helpers/util.js
14
454
  var util, objectUtil, ZodParsedType, getParsedType;
15
455
  var init_util = __esm({
@@ -421,8 +861,8 @@ var init_parseUtil = __esm({
421
861
  "node_modules/zod/v3/helpers/parseUtil.js"() {
422
862
  init_errors();
423
863
  init_en();
424
- makeIssue = (params) => {
425
- const { data, path, errorMaps, issueData } = params;
864
+ makeIssue = (params2) => {
865
+ const { data, path, errorMaps, issueData } = params2;
426
866
  const fullPath = [...path, ...issueData.path || []];
427
867
  const fullIssue = {
428
868
  ...issueData,
@@ -531,17 +971,17 @@ var init_errorUtil = __esm({
531
971
  });
532
972
 
533
973
  // node_modules/zod/v3/types.js
534
- function processCreateParams(params) {
535
- if (!params)
974
+ function processCreateParams(params2) {
975
+ if (!params2)
536
976
  return {};
537
- const { errorMap: errorMap2, invalid_type_error, required_error, description } = params;
977
+ const { errorMap: errorMap2, invalid_type_error, required_error, description } = params2;
538
978
  if (errorMap2 && (invalid_type_error || required_error)) {
539
979
  throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);
540
980
  }
541
981
  if (errorMap2)
542
982
  return { errorMap: errorMap2, description };
543
983
  const customMap = (iss, ctx) => {
544
- const { message } = params;
984
+ const { message } = params2;
545
985
  if (iss.code === "invalid_enum_value") {
546
986
  return { message: message ?? ctx.defaultError };
547
987
  }
@@ -688,15 +1128,15 @@ function mergeValues(a, b) {
688
1128
  return { valid: false };
689
1129
  }
690
1130
  }
691
- function createZodEnum(values, params) {
1131
+ function createZodEnum(values, params2) {
692
1132
  return new ZodEnum({
693
1133
  values,
694
1134
  typeName: ZodFirstPartyTypeKind.ZodEnum,
695
- ...processCreateParams(params)
1135
+ ...processCreateParams(params2)
696
1136
  });
697
1137
  }
698
- function cleanParams(params, data) {
699
- const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params;
1138
+ function cleanParams(params2, data) {
1139
+ const p = typeof params2 === "function" ? params2(data) : typeof params2 === "string" ? { message: params2 } : params2;
700
1140
  const p2 = typeof p === "string" ? { message: p } : p;
701
1141
  return p2;
702
1142
  }
@@ -707,16 +1147,16 @@ function custom(check, _params = {}, fatal) {
707
1147
  if (r instanceof Promise) {
708
1148
  return r.then((r2) => {
709
1149
  if (!r2) {
710
- const params = cleanParams(_params, data);
711
- const _fatal = params.fatal ?? fatal ?? true;
712
- ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
1150
+ const params2 = cleanParams(_params, data);
1151
+ const _fatal = params2.fatal ?? fatal ?? true;
1152
+ ctx.addIssue({ code: "custom", ...params2, fatal: _fatal });
713
1153
  }
714
1154
  });
715
1155
  }
716
1156
  if (!r) {
717
- const params = cleanParams(_params, data);
718
- const _fatal = params.fatal ?? fatal ?? true;
719
- ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
1157
+ const params2 = cleanParams(_params, data);
1158
+ const _fatal = params2.fatal ?? fatal ?? true;
1159
+ ctx.addIssue({ code: "custom", ...params2, fatal: _fatal });
720
1160
  }
721
1161
  return;
722
1162
  });
@@ -809,20 +1249,20 @@ var init_types = __esm({
809
1249
  const result = this._parse(input);
810
1250
  return Promise.resolve(result);
811
1251
  }
812
- parse(data, params) {
813
- const result = this.safeParse(data, params);
1252
+ parse(data, params2) {
1253
+ const result = this.safeParse(data, params2);
814
1254
  if (result.success)
815
1255
  return result.data;
816
1256
  throw result.error;
817
1257
  }
818
- safeParse(data, params) {
1258
+ safeParse(data, params2) {
819
1259
  const ctx = {
820
1260
  common: {
821
1261
  issues: [],
822
- async: params?.async ?? false,
823
- contextualErrorMap: params?.errorMap
1262
+ async: params2?.async ?? false,
1263
+ contextualErrorMap: params2?.errorMap
824
1264
  },
825
- path: params?.path || [],
1265
+ path: params2?.path || [],
826
1266
  schemaErrorMap: this._def.errorMap,
827
1267
  parent: null,
828
1268
  data,
@@ -867,20 +1307,20 @@ var init_types = __esm({
867
1307
  issues: ctx.common.issues
868
1308
  });
869
1309
  }
870
- async parseAsync(data, params) {
871
- const result = await this.safeParseAsync(data, params);
1310
+ async parseAsync(data, params2) {
1311
+ const result = await this.safeParseAsync(data, params2);
872
1312
  if (result.success)
873
1313
  return result.data;
874
1314
  throw result.error;
875
1315
  }
876
- async safeParseAsync(data, params) {
1316
+ async safeParseAsync(data, params2) {
877
1317
  const ctx = {
878
1318
  common: {
879
1319
  issues: [],
880
- contextualErrorMap: params?.errorMap,
1320
+ contextualErrorMap: params2?.errorMap,
881
1321
  async: true
882
1322
  },
883
- path: params?.path || [],
1323
+ path: params2?.path || [],
884
1324
  schemaErrorMap: this._def.errorMap,
885
1325
  parent: null,
886
1326
  data,
@@ -1606,12 +2046,12 @@ var init_types = __esm({
1606
2046
  return max;
1607
2047
  }
1608
2048
  };
1609
- ZodString.create = (params) => {
2049
+ ZodString.create = (params2) => {
1610
2050
  return new ZodString({
1611
2051
  checks: [],
1612
2052
  typeName: ZodFirstPartyTypeKind.ZodString,
1613
- coerce: params?.coerce ?? false,
1614
- ...processCreateParams(params)
2053
+ coerce: params2?.coerce ?? false,
2054
+ ...processCreateParams(params2)
1615
2055
  });
1616
2056
  };
1617
2057
  ZodNumber = class _ZodNumber extends ZodType {
@@ -1838,12 +2278,12 @@ var init_types = __esm({
1838
2278
  return Number.isFinite(min) && Number.isFinite(max);
1839
2279
  }
1840
2280
  };
1841
- ZodNumber.create = (params) => {
2281
+ ZodNumber.create = (params2) => {
1842
2282
  return new ZodNumber({
1843
2283
  checks: [],
1844
2284
  typeName: ZodFirstPartyTypeKind.ZodNumber,
1845
- coerce: params?.coerce || false,
1846
- ...processCreateParams(params)
2285
+ coerce: params2?.coerce || false,
2286
+ ...processCreateParams(params2)
1847
2287
  });
1848
2288
  };
1849
2289
  ZodBigInt = class _ZodBigInt extends ZodType {
@@ -2010,12 +2450,12 @@ var init_types = __esm({
2010
2450
  return max;
2011
2451
  }
2012
2452
  };
2013
- ZodBigInt.create = (params) => {
2453
+ ZodBigInt.create = (params2) => {
2014
2454
  return new ZodBigInt({
2015
2455
  checks: [],
2016
2456
  typeName: ZodFirstPartyTypeKind.ZodBigInt,
2017
- coerce: params?.coerce ?? false,
2018
- ...processCreateParams(params)
2457
+ coerce: params2?.coerce ?? false,
2458
+ ...processCreateParams(params2)
2019
2459
  });
2020
2460
  };
2021
2461
  ZodBoolean = class extends ZodType {
@@ -2036,11 +2476,11 @@ var init_types = __esm({
2036
2476
  return OK(input.data);
2037
2477
  }
2038
2478
  };
2039
- ZodBoolean.create = (params) => {
2479
+ ZodBoolean.create = (params2) => {
2040
2480
  return new ZodBoolean({
2041
2481
  typeName: ZodFirstPartyTypeKind.ZodBoolean,
2042
- coerce: params?.coerce || false,
2043
- ...processCreateParams(params)
2482
+ coerce: params2?.coerce || false,
2483
+ ...processCreateParams(params2)
2044
2484
  });
2045
2485
  };
2046
2486
  ZodDate = class _ZodDate extends ZodType {
@@ -2144,12 +2584,12 @@ var init_types = __esm({
2144
2584
  return max != null ? new Date(max) : null;
2145
2585
  }
2146
2586
  };
2147
- ZodDate.create = (params) => {
2587
+ ZodDate.create = (params2) => {
2148
2588
  return new ZodDate({
2149
2589
  checks: [],
2150
- coerce: params?.coerce || false,
2590
+ coerce: params2?.coerce || false,
2151
2591
  typeName: ZodFirstPartyTypeKind.ZodDate,
2152
- ...processCreateParams(params)
2592
+ ...processCreateParams(params2)
2153
2593
  });
2154
2594
  };
2155
2595
  ZodSymbol = class extends ZodType {
@@ -2167,10 +2607,10 @@ var init_types = __esm({
2167
2607
  return OK(input.data);
2168
2608
  }
2169
2609
  };
2170
- ZodSymbol.create = (params) => {
2610
+ ZodSymbol.create = (params2) => {
2171
2611
  return new ZodSymbol({
2172
2612
  typeName: ZodFirstPartyTypeKind.ZodSymbol,
2173
- ...processCreateParams(params)
2613
+ ...processCreateParams(params2)
2174
2614
  });
2175
2615
  };
2176
2616
  ZodUndefined = class extends ZodType {
@@ -2188,10 +2628,10 @@ var init_types = __esm({
2188
2628
  return OK(input.data);
2189
2629
  }
2190
2630
  };
2191
- ZodUndefined.create = (params) => {
2631
+ ZodUndefined.create = (params2) => {
2192
2632
  return new ZodUndefined({
2193
2633
  typeName: ZodFirstPartyTypeKind.ZodUndefined,
2194
- ...processCreateParams(params)
2634
+ ...processCreateParams(params2)
2195
2635
  });
2196
2636
  };
2197
2637
  ZodNull = class extends ZodType {
@@ -2209,10 +2649,10 @@ var init_types = __esm({
2209
2649
  return OK(input.data);
2210
2650
  }
2211
2651
  };
2212
- ZodNull.create = (params) => {
2652
+ ZodNull.create = (params2) => {
2213
2653
  return new ZodNull({
2214
2654
  typeName: ZodFirstPartyTypeKind.ZodNull,
2215
- ...processCreateParams(params)
2655
+ ...processCreateParams(params2)
2216
2656
  });
2217
2657
  };
2218
2658
  ZodAny = class extends ZodType {
@@ -2224,10 +2664,10 @@ var init_types = __esm({
2224
2664
  return OK(input.data);
2225
2665
  }
2226
2666
  };
2227
- ZodAny.create = (params) => {
2667
+ ZodAny.create = (params2) => {
2228
2668
  return new ZodAny({
2229
2669
  typeName: ZodFirstPartyTypeKind.ZodAny,
2230
- ...processCreateParams(params)
2670
+ ...processCreateParams(params2)
2231
2671
  });
2232
2672
  };
2233
2673
  ZodUnknown = class extends ZodType {
@@ -2239,10 +2679,10 @@ var init_types = __esm({
2239
2679
  return OK(input.data);
2240
2680
  }
2241
2681
  };
2242
- ZodUnknown.create = (params) => {
2682
+ ZodUnknown.create = (params2) => {
2243
2683
  return new ZodUnknown({
2244
2684
  typeName: ZodFirstPartyTypeKind.ZodUnknown,
2245
- ...processCreateParams(params)
2685
+ ...processCreateParams(params2)
2246
2686
  });
2247
2687
  };
2248
2688
  ZodNever = class extends ZodType {
@@ -2256,10 +2696,10 @@ var init_types = __esm({
2256
2696
  return INVALID;
2257
2697
  }
2258
2698
  };
2259
- ZodNever.create = (params) => {
2699
+ ZodNever.create = (params2) => {
2260
2700
  return new ZodNever({
2261
2701
  typeName: ZodFirstPartyTypeKind.ZodNever,
2262
- ...processCreateParams(params)
2702
+ ...processCreateParams(params2)
2263
2703
  });
2264
2704
  };
2265
2705
  ZodVoid = class extends ZodType {
@@ -2277,10 +2717,10 @@ var init_types = __esm({
2277
2717
  return OK(input.data);
2278
2718
  }
2279
2719
  };
2280
- ZodVoid.create = (params) => {
2720
+ ZodVoid.create = (params2) => {
2281
2721
  return new ZodVoid({
2282
2722
  typeName: ZodFirstPartyTypeKind.ZodVoid,
2283
- ...processCreateParams(params)
2723
+ ...processCreateParams(params2)
2284
2724
  });
2285
2725
  };
2286
2726
  ZodArray = class _ZodArray extends ZodType {
@@ -2374,14 +2814,14 @@ var init_types = __esm({
2374
2814
  return this.min(1, message);
2375
2815
  }
2376
2816
  };
2377
- ZodArray.create = (schema, params) => {
2817
+ ZodArray.create = (schema, params2) => {
2378
2818
  return new ZodArray({
2379
2819
  type: schema,
2380
2820
  minLength: null,
2381
2821
  maxLength: null,
2382
2822
  exactLength: null,
2383
2823
  typeName: ZodFirstPartyTypeKind.ZodArray,
2384
- ...processCreateParams(params)
2824
+ ...processCreateParams(params2)
2385
2825
  });
2386
2826
  };
2387
2827
  ZodObject = class _ZodObject extends ZodType {
@@ -2695,31 +3135,31 @@ var init_types = __esm({
2695
3135
  return createZodEnum(util.objectKeys(this.shape));
2696
3136
  }
2697
3137
  };
2698
- ZodObject.create = (shape, params) => {
3138
+ ZodObject.create = (shape, params2) => {
2699
3139
  return new ZodObject({
2700
3140
  shape: () => shape,
2701
3141
  unknownKeys: "strip",
2702
3142
  catchall: ZodNever.create(),
2703
3143
  typeName: ZodFirstPartyTypeKind.ZodObject,
2704
- ...processCreateParams(params)
3144
+ ...processCreateParams(params2)
2705
3145
  });
2706
3146
  };
2707
- ZodObject.strictCreate = (shape, params) => {
3147
+ ZodObject.strictCreate = (shape, params2) => {
2708
3148
  return new ZodObject({
2709
3149
  shape: () => shape,
2710
3150
  unknownKeys: "strict",
2711
3151
  catchall: ZodNever.create(),
2712
3152
  typeName: ZodFirstPartyTypeKind.ZodObject,
2713
- ...processCreateParams(params)
3153
+ ...processCreateParams(params2)
2714
3154
  });
2715
3155
  };
2716
- ZodObject.lazycreate = (shape, params) => {
3156
+ ZodObject.lazycreate = (shape, params2) => {
2717
3157
  return new ZodObject({
2718
3158
  shape,
2719
3159
  unknownKeys: "strip",
2720
3160
  catchall: ZodNever.create(),
2721
3161
  typeName: ZodFirstPartyTypeKind.ZodObject,
2722
- ...processCreateParams(params)
3162
+ ...processCreateParams(params2)
2723
3163
  });
2724
3164
  };
2725
3165
  ZodUnion = class extends ZodType {
@@ -2806,11 +3246,11 @@ var init_types = __esm({
2806
3246
  return this._def.options;
2807
3247
  }
2808
3248
  };
2809
- ZodUnion.create = (types, params) => {
3249
+ ZodUnion.create = (types, params2) => {
2810
3250
  return new ZodUnion({
2811
3251
  options: types,
2812
3252
  typeName: ZodFirstPartyTypeKind.ZodUnion,
2813
- ...processCreateParams(params)
3253
+ ...processCreateParams(params2)
2814
3254
  });
2815
3255
  };
2816
3256
  getDiscriminator = (type) => {
@@ -2897,7 +3337,7 @@ var init_types = __esm({
2897
3337
  * @param types an array of object schemas
2898
3338
  * @param params
2899
3339
  */
2900
- static create(discriminator, options, params) {
3340
+ static create(discriminator, options, params2) {
2901
3341
  const optionsMap = /* @__PURE__ */ new Map();
2902
3342
  for (const type of options) {
2903
3343
  const discriminatorValues = getDiscriminator(type.shape[discriminator]);
@@ -2916,7 +3356,7 @@ var init_types = __esm({
2916
3356
  discriminator,
2917
3357
  options,
2918
3358
  optionsMap,
2919
- ...processCreateParams(params)
3359
+ ...processCreateParams(params2)
2920
3360
  });
2921
3361
  }
2922
3362
  };
@@ -2965,12 +3405,12 @@ var init_types = __esm({
2965
3405
  }
2966
3406
  }
2967
3407
  };
2968
- ZodIntersection.create = (left, right, params) => {
3408
+ ZodIntersection.create = (left, right, params2) => {
2969
3409
  return new ZodIntersection({
2970
3410
  left,
2971
3411
  right,
2972
3412
  typeName: ZodFirstPartyTypeKind.ZodIntersection,
2973
- ...processCreateParams(params)
3413
+ ...processCreateParams(params2)
2974
3414
  });
2975
3415
  };
2976
3416
  ZodTuple = class _ZodTuple extends ZodType {
@@ -3029,7 +3469,7 @@ var init_types = __esm({
3029
3469
  });
3030
3470
  }
3031
3471
  };
3032
- ZodTuple.create = (schemas, params) => {
3472
+ ZodTuple.create = (schemas, params2) => {
3033
3473
  if (!Array.isArray(schemas)) {
3034
3474
  throw new Error("You must pass an array of schemas to z.tuple([ ... ])");
3035
3475
  }
@@ -3037,7 +3477,7 @@ var init_types = __esm({
3037
3477
  items: schemas,
3038
3478
  typeName: ZodFirstPartyTypeKind.ZodTuple,
3039
3479
  rest: null,
3040
- ...processCreateParams(params)
3480
+ ...processCreateParams(params2)
3041
3481
  });
3042
3482
  };
3043
3483
  ZodRecord = class _ZodRecord extends ZodType {
@@ -3151,12 +3591,12 @@ var init_types = __esm({
3151
3591
  }
3152
3592
  }
3153
3593
  };
3154
- ZodMap.create = (keyType, valueType, params) => {
3594
+ ZodMap.create = (keyType, valueType, params2) => {
3155
3595
  return new ZodMap({
3156
3596
  valueType,
3157
3597
  keyType,
3158
3598
  typeName: ZodFirstPartyTypeKind.ZodMap,
3159
- ...processCreateParams(params)
3599
+ ...processCreateParams(params2)
3160
3600
  });
3161
3601
  };
3162
3602
  ZodSet = class _ZodSet extends ZodType {
@@ -3235,13 +3675,13 @@ var init_types = __esm({
3235
3675
  return this.min(1, message);
3236
3676
  }
3237
3677
  };
3238
- ZodSet.create = (valueType, params) => {
3678
+ ZodSet.create = (valueType, params2) => {
3239
3679
  return new ZodSet({
3240
3680
  valueType,
3241
3681
  minSize: null,
3242
3682
  maxSize: null,
3243
3683
  typeName: ZodFirstPartyTypeKind.ZodSet,
3244
- ...processCreateParams(params)
3684
+ ...processCreateParams(params2)
3245
3685
  });
3246
3686
  };
3247
3687
  ZodFunction = class _ZodFunction extends ZodType {
@@ -3281,18 +3721,18 @@ var init_types = __esm({
3281
3721
  }
3282
3722
  });
3283
3723
  }
3284
- const params = { errorMap: ctx.common.contextualErrorMap };
3724
+ const params2 = { errorMap: ctx.common.contextualErrorMap };
3285
3725
  const fn = ctx.data;
3286
3726
  if (this._def.returns instanceof ZodPromise) {
3287
3727
  const me = this;
3288
3728
  return OK(async function(...args) {
3289
3729
  const error = new ZodError([]);
3290
- const parsedArgs = await me._def.args.parseAsync(args, params).catch((e) => {
3730
+ const parsedArgs = await me._def.args.parseAsync(args, params2).catch((e) => {
3291
3731
  error.addIssue(makeArgsIssue(args, e));
3292
3732
  throw error;
3293
3733
  });
3294
3734
  const result = await Reflect.apply(fn, this, parsedArgs);
3295
- const parsedReturns = await me._def.returns._def.type.parseAsync(result, params).catch((e) => {
3735
+ const parsedReturns = await me._def.returns._def.type.parseAsync(result, params2).catch((e) => {
3296
3736
  error.addIssue(makeReturnsIssue(result, e));
3297
3737
  throw error;
3298
3738
  });
@@ -3301,12 +3741,12 @@ var init_types = __esm({
3301
3741
  } else {
3302
3742
  const me = this;
3303
3743
  return OK(function(...args) {
3304
- const parsedArgs = me._def.args.safeParse(args, params);
3744
+ const parsedArgs = me._def.args.safeParse(args, params2);
3305
3745
  if (!parsedArgs.success) {
3306
3746
  throw new ZodError([makeArgsIssue(args, parsedArgs.error)]);
3307
3747
  }
3308
3748
  const result = Reflect.apply(fn, this, parsedArgs.data);
3309
- const parsedReturns = me._def.returns.safeParse(result, params);
3749
+ const parsedReturns = me._def.returns.safeParse(result, params2);
3310
3750
  if (!parsedReturns.success) {
3311
3751
  throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]);
3312
3752
  }
@@ -3340,12 +3780,12 @@ var init_types = __esm({
3340
3780
  const validatedFunc = this.parse(func);
3341
3781
  return validatedFunc;
3342
3782
  }
3343
- static create(args, returns, params) {
3783
+ static create(args, returns, params2) {
3344
3784
  return new _ZodFunction({
3345
3785
  args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()),
3346
3786
  returns: returns || ZodUnknown.create(),
3347
3787
  typeName: ZodFirstPartyTypeKind.ZodFunction,
3348
- ...processCreateParams(params)
3788
+ ...processCreateParams(params2)
3349
3789
  });
3350
3790
  }
3351
3791
  };
@@ -3359,11 +3799,11 @@ var init_types = __esm({
3359
3799
  return lazySchema._parse({ data: ctx.data, path: ctx.path, parent: ctx });
3360
3800
  }
3361
3801
  };
3362
- ZodLazy.create = (getter, params) => {
3802
+ ZodLazy.create = (getter, params2) => {
3363
3803
  return new ZodLazy({
3364
3804
  getter,
3365
3805
  typeName: ZodFirstPartyTypeKind.ZodLazy,
3366
- ...processCreateParams(params)
3806
+ ...processCreateParams(params2)
3367
3807
  });
3368
3808
  };
3369
3809
  ZodLiteral = class extends ZodType {
@@ -3383,11 +3823,11 @@ var init_types = __esm({
3383
3823
  return this._def.value;
3384
3824
  }
3385
3825
  };
3386
- ZodLiteral.create = (value, params) => {
3826
+ ZodLiteral.create = (value, params2) => {
3387
3827
  return new ZodLiteral({
3388
3828
  value,
3389
3829
  typeName: ZodFirstPartyTypeKind.ZodLiteral,
3390
- ...processCreateParams(params)
3830
+ ...processCreateParams(params2)
3391
3831
  });
3392
3832
  };
3393
3833
  ZodEnum = class _ZodEnum extends ZodType {
@@ -3486,11 +3926,11 @@ var init_types = __esm({
3486
3926
  return this._def.values;
3487
3927
  }
3488
3928
  };
3489
- ZodNativeEnum.create = (values, params) => {
3929
+ ZodNativeEnum.create = (values, params2) => {
3490
3930
  return new ZodNativeEnum({
3491
3931
  values,
3492
3932
  typeName: ZodFirstPartyTypeKind.ZodNativeEnum,
3493
- ...processCreateParams(params)
3933
+ ...processCreateParams(params2)
3494
3934
  });
3495
3935
  };
3496
3936
  ZodPromise = class extends ZodType {
@@ -3516,11 +3956,11 @@ var init_types = __esm({
3516
3956
  }));
3517
3957
  }
3518
3958
  };
3519
- ZodPromise.create = (schema, params) => {
3959
+ ZodPromise.create = (schema, params2) => {
3520
3960
  return new ZodPromise({
3521
3961
  type: schema,
3522
3962
  typeName: ZodFirstPartyTypeKind.ZodPromise,
3523
- ...processCreateParams(params)
3963
+ ...processCreateParams(params2)
3524
3964
  });
3525
3965
  };
3526
3966
  ZodEffects = class extends ZodType {
@@ -3646,20 +4086,20 @@ var init_types = __esm({
3646
4086
  util.assertNever(effect);
3647
4087
  }
3648
4088
  };
3649
- ZodEffects.create = (schema, effect, params) => {
4089
+ ZodEffects.create = (schema, effect, params2) => {
3650
4090
  return new ZodEffects({
3651
4091
  schema,
3652
4092
  typeName: ZodFirstPartyTypeKind.ZodEffects,
3653
4093
  effect,
3654
- ...processCreateParams(params)
4094
+ ...processCreateParams(params2)
3655
4095
  });
3656
4096
  };
3657
- ZodEffects.createWithPreprocess = (preprocess, schema, params) => {
4097
+ ZodEffects.createWithPreprocess = (preprocess, schema, params2) => {
3658
4098
  return new ZodEffects({
3659
4099
  schema,
3660
4100
  effect: { type: "preprocess", transform: preprocess },
3661
4101
  typeName: ZodFirstPartyTypeKind.ZodEffects,
3662
- ...processCreateParams(params)
4102
+ ...processCreateParams(params2)
3663
4103
  });
3664
4104
  };
3665
4105
  ZodOptional = class extends ZodType {
@@ -3674,11 +4114,11 @@ var init_types = __esm({
3674
4114
  return this._def.innerType;
3675
4115
  }
3676
4116
  };
3677
- ZodOptional.create = (type, params) => {
4117
+ ZodOptional.create = (type, params2) => {
3678
4118
  return new ZodOptional({
3679
4119
  innerType: type,
3680
4120
  typeName: ZodFirstPartyTypeKind.ZodOptional,
3681
- ...processCreateParams(params)
4121
+ ...processCreateParams(params2)
3682
4122
  });
3683
4123
  };
3684
4124
  ZodNullable = class extends ZodType {
@@ -3693,11 +4133,11 @@ var init_types = __esm({
3693
4133
  return this._def.innerType;
3694
4134
  }
3695
4135
  };
3696
- ZodNullable.create = (type, params) => {
4136
+ ZodNullable.create = (type, params2) => {
3697
4137
  return new ZodNullable({
3698
4138
  innerType: type,
3699
4139
  typeName: ZodFirstPartyTypeKind.ZodNullable,
3700
- ...processCreateParams(params)
4140
+ ...processCreateParams(params2)
3701
4141
  });
3702
4142
  };
3703
4143
  ZodDefault = class extends ZodType {
@@ -3717,12 +4157,12 @@ var init_types = __esm({
3717
4157
  return this._def.innerType;
3718
4158
  }
3719
4159
  };
3720
- ZodDefault.create = (type, params) => {
4160
+ ZodDefault.create = (type, params2) => {
3721
4161
  return new ZodDefault({
3722
4162
  innerType: type,
3723
4163
  typeName: ZodFirstPartyTypeKind.ZodDefault,
3724
- defaultValue: typeof params.default === "function" ? params.default : () => params.default,
3725
- ...processCreateParams(params)
4164
+ defaultValue: typeof params2.default === "function" ? params2.default : () => params2.default,
4165
+ ...processCreateParams(params2)
3726
4166
  });
3727
4167
  };
3728
4168
  ZodCatch = class extends ZodType {
@@ -3770,12 +4210,12 @@ var init_types = __esm({
3770
4210
  return this._def.innerType;
3771
4211
  }
3772
4212
  };
3773
- ZodCatch.create = (type, params) => {
4213
+ ZodCatch.create = (type, params2) => {
3774
4214
  return new ZodCatch({
3775
4215
  innerType: type,
3776
4216
  typeName: ZodFirstPartyTypeKind.ZodCatch,
3777
- catchValue: typeof params.catch === "function" ? params.catch : () => params.catch,
3778
- ...processCreateParams(params)
4217
+ catchValue: typeof params2.catch === "function" ? params2.catch : () => params2.catch,
4218
+ ...processCreateParams(params2)
3779
4219
  });
3780
4220
  };
3781
4221
  ZodNaN = class extends ZodType {
@@ -3793,10 +4233,10 @@ var init_types = __esm({
3793
4233
  return { status: "valid", value: input.data };
3794
4234
  }
3795
4235
  };
3796
- ZodNaN.create = (params) => {
4236
+ ZodNaN.create = (params2) => {
3797
4237
  return new ZodNaN({
3798
4238
  typeName: ZodFirstPartyTypeKind.ZodNaN,
3799
- ...processCreateParams(params)
4239
+ ...processCreateParams(params2)
3800
4240
  });
3801
4241
  };
3802
4242
  BRAND = Symbol("zod_brand");
@@ -3884,11 +4324,11 @@ var init_types = __esm({
3884
4324
  return this._def.innerType;
3885
4325
  }
3886
4326
  };
3887
- ZodReadonly.create = (type, params) => {
4327
+ ZodReadonly.create = (type, params2) => {
3888
4328
  return new ZodReadonly({
3889
4329
  innerType: type,
3890
4330
  typeName: ZodFirstPartyTypeKind.ZodReadonly,
3891
- ...processCreateParams(params)
4331
+ ...processCreateParams(params2)
3892
4332
  });
3893
4333
  };
3894
4334
  late = {
@@ -3932,9 +4372,9 @@ var init_types = __esm({
3932
4372
  ZodFirstPartyTypeKind2["ZodPipeline"] = "ZodPipeline";
3933
4373
  ZodFirstPartyTypeKind2["ZodReadonly"] = "ZodReadonly";
3934
4374
  })(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
3935
- instanceOfType = (cls, params = {
4375
+ instanceOfType = (cls, params2 = {
3936
4376
  message: `Input not instance of ${cls.name}`
3937
- }) => custom((data) => data instanceof cls, params);
4377
+ }) => custom((data) => data instanceof cls, params2);
3938
4378
  stringType = ZodString.create;
3939
4379
  numberType = ZodNumber.create;
3940
4380
  nanType = ZodNaN.create;
@@ -4119,29 +4559,26 @@ var init_zod = __esm({
4119
4559
  // src/config.ts
4120
4560
  import * as nodeFs from "node:fs";
4121
4561
  import { homedir } from "node:os";
4122
- import { dirname, isAbsolute, join, parse, resolve } from "node:path";
4123
- function daemonMode(config) {
4124
- return config.daemon?.mode ?? "embedded";
4125
- }
4562
+ import { dirname as dirname2, isAbsolute, join as join2, parse, resolve as resolve2 } from "node:path";
4126
4563
  function defaultConfig(home = homedir()) {
4127
4564
  return {
4128
4565
  version: 1,
4129
4566
  brokerUrl: "wss://broker1.ours.network",
4130
- stateDir: resolve(home, ".ours-cowork"),
4567
+ stateDir: resolve2(home, ".ours-cowork"),
4131
4568
  rest: { enabled: true, port: 3052 }
4132
4569
  };
4133
4570
  }
4134
4571
  function loadConfig(env = process.env, io = {}) {
4135
- const fs3 = io.fs ?? nodeFs;
4572
+ const fs2 = io.fs ?? nodeFs;
4136
4573
  const defaults = defaultConfig(io.home);
4137
- const configPath = resolve(env.OURS_COWORK_CONFIG ?? join(io.home ?? homedir(), ".ours-cowork", "config.json"));
4574
+ const configPath = resolve2(env.OURS_COWORK_CONFIG ?? join2(io.home ?? homedir(), ".ours-cowork", "config.json"));
4138
4575
  let file = defaults;
4139
- const stat = lstatIfPresent(fs3, configPath);
4576
+ const stat = lstatIfPresent(fs2, configPath);
4140
4577
  if (stat) {
4141
- assertSecureFile(fs3, configPath, "config file");
4578
+ assertSecureFile(fs2, configPath, "config file");
4142
4579
  let parsed;
4143
4580
  try {
4144
- parsed = JSON.parse(readSecureFile(fs3, configPath, "config file").toString("utf8"));
4581
+ parsed = JSON.parse(readSecureFile(fs2, configPath, "config file").toString("utf8"));
4145
4582
  } catch (error) {
4146
4583
  throw new CoworkConfigError(`malformed cowork config at ${configPath}`, { cause: error });
4147
4584
  }
@@ -4154,100 +4591,42 @@ function loadConfig(env = process.env, io = {}) {
4154
4591
  throw new CoworkConfigError(`configured cowork config does not exist: ${configPath}`);
4155
4592
  }
4156
4593
  const restPort = env.OURS_COWORK_REST_PORT === void 0 ? void 0 : parsePort(env.OURS_COWORK_REST_PORT);
4157
- const daemon = mergeDaemonSelection(env, file.daemon);
4158
4594
  try {
4159
4595
  return CoworkConfigSchema.parse({
4160
4596
  version: 1,
4161
4597
  brokerUrl: env.OURS_COWORK_BROKER_URL ?? file.brokerUrl,
4162
- stateDir: resolve(env.OURS_COWORK_STATE_DIR ?? file.stateDir),
4598
+ stateDir: resolve2(env.OURS_COWORK_STATE_DIR ?? file.stateDir),
4163
4599
  rest: {
4164
4600
  enabled: restPort === void 0 ? file.rest.enabled : true,
4165
4601
  port: restPort ?? file.rest.port
4166
- },
4167
- ...daemon === void 0 ? {} : { daemon }
4602
+ }
4168
4603
  });
4169
4604
  } catch (error) {
4170
4605
  throw new CoworkConfigError("invalid effective cowork config", { cause: error });
4171
4606
  }
4172
4607
  }
4173
- function mergeDaemonSelection(env, file) {
4174
- const mode = env.OURS_COWORK_DAEMON_MODE;
4175
- const endpoint = env.OURS_COWORK_DAEMON_ENDPOINT;
4176
- const stateDir = env.OURS_COWORK_DAEMON_STATE_DIR;
4177
- if (mode === void 0 && endpoint === void 0 && stateDir === void 0) return file;
4178
- if (mode !== void 0 && mode !== "embedded" && mode !== "external") {
4179
- throw new CoworkConfigError('OURS_COWORK_DAEMON_MODE must be "embedded" or "external"');
4180
- }
4181
- const effectiveMode = mode ?? file?.mode ?? "external";
4182
- if (effectiveMode === "embedded") {
4183
- if (endpoint !== void 0 || stateDir !== void 0) {
4184
- throw new CoworkConfigError(
4185
- 'OURS_COWORK_DAEMON_ENDPOINT and OURS_COWORK_DAEMON_STATE_DIR require daemon mode "external"'
4186
- );
4187
- }
4188
- return { mode: "embedded" };
4189
- }
4190
- const effectiveEndpoint = endpoint ?? file?.endpoint;
4191
- const effectiveStateDir = stateDir ?? file?.stateDir;
4192
- return {
4193
- mode: "external",
4194
- ...effectiveEndpoint === void 0 ? {} : { endpoint: effectiveEndpoint },
4195
- ...effectiveStateDir === void 0 ? {} : { stateDir: effectiveStateDir }
4196
- };
4197
- }
4198
- function isDaemonOrigin(value) {
4199
- let url;
4200
- try {
4201
- url = new URL(value);
4202
- } catch {
4203
- return false;
4204
- }
4205
- return (url.protocol === "http:" || url.protocol === "https:") && url.username === "" && url.password === "" && url.search === "" && url.hash === "" && (url.pathname === "" || url.pathname === "/");
4206
- }
4207
- function isConfidentialDaemonEndpoint(value) {
4208
- let url;
4209
- try {
4210
- url = new URL(value);
4211
- } catch {
4212
- return false;
4213
- }
4214
- return url.protocol === "https:" || isLoopbackHost(url.hostname);
4215
- }
4216
- function isLoopbackHost(hostname) {
4217
- const host = hostname.toLowerCase().replace(/^\[(.*)\]$/, "$1");
4218
- if (host === "localhost") return true;
4219
- if (host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
4220
- const octets = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
4221
- if (!octets) return false;
4222
- const parts = octets.slice(1).map(Number);
4223
- return parts.every((part) => part <= 255) && parts[0] === 127;
4224
- }
4225
- function normalizeDaemonEndpoint(value) {
4226
- const url = new URL(value);
4227
- return `${url.protocol}//${url.host}`;
4228
- }
4229
4608
  function ensureRuntimeState(config, io = {}) {
4230
4609
  const parsed = CoworkConfigSchema.parse(config);
4231
- const fs3 = io.fs ?? nodeFs;
4232
- const stateDir = resolve(parsed.stateDir);
4233
- assertSecureAncestors(fs3, stateDir, "state directory");
4234
- const existing = lstatIfPresent(fs3, stateDir);
4610
+ const fs2 = io.fs ?? nodeFs;
4611
+ const stateDir = resolve2(parsed.stateDir);
4612
+ assertSecureAncestors(fs2, stateDir, "state directory");
4613
+ const existing = lstatIfPresent(fs2, stateDir);
4235
4614
  if (!existing) {
4236
- createSecureDirectoryTree(fs3, stateDir, "state directory");
4615
+ createSecureDirectoryTree(fs2, stateDir, "state directory");
4237
4616
  }
4238
- assertSecureDirectory(fs3, stateDir, "state directory");
4239
- const roomsPath = join(stateDir, "rooms");
4240
- const rooms = lstatIfPresent(fs3, roomsPath);
4617
+ assertSecureDirectory(fs2, stateDir, "state directory");
4618
+ const roomsPath = join2(stateDir, "rooms");
4619
+ const rooms = lstatIfPresent(fs2, roomsPath);
4241
4620
  if (!rooms) {
4242
- fs3.mkdirSync(roomsPath, { mode: DIRECTORY_MODE });
4243
- secureOpenedDirectory(fs3, roomsPath, "rooms directory");
4244
- fsyncDirectory(fs3, stateDir);
4621
+ fs2.mkdirSync(roomsPath, { mode: DIRECTORY_MODE });
4622
+ secureOpenedDirectory(fs2, roomsPath, "rooms directory");
4623
+ fsyncDirectory(fs2, stateDir);
4245
4624
  }
4246
- assertSecureDirectory(fs3, roomsPath, "rooms directory");
4625
+ assertSecureDirectory(fs2, roomsPath, "rooms directory");
4247
4626
  return {
4248
- socketPath: join(stateDir, "management.sock"),
4249
- pidPath: join(stateDir, "daemon.pid"),
4250
- lockPath: join(stateDir, "daemon.lock")
4627
+ socketPath: join2(stateDir, "management.sock"),
4628
+ pidPath: join2(stateDir, "daemon.pid"),
4629
+ lockPath: join2(stateDir, "daemon.lock")
4251
4630
  };
4252
4631
  }
4253
4632
  function parsePort(value) {
@@ -4258,15 +4637,15 @@ function parsePort(value) {
4258
4637
  if (port > 65535) throw new CoworkConfigError("OURS_COWORK_REST_PORT must be from 1 to 65535");
4259
4638
  return port;
4260
4639
  }
4261
- function assertSecureAncestors(fs3, path, label) {
4262
- const absolute = isAbsolute(path) ? path : resolve(path);
4640
+ function assertSecureAncestors(fs2, path, label) {
4641
+ const absolute = isAbsolute(path) ? path : resolve2(path);
4263
4642
  const root = parse(absolute).root;
4264
- const rootOwner = fs3.lstatSync(root).uid;
4643
+ const rootOwner = fs2.lstatSync(root).uid;
4265
4644
  let cursor = root;
4266
4645
  const components = absolute.slice(root.length).split("/").filter(Boolean);
4267
4646
  for (const [index, component] of components.entries()) {
4268
- cursor = join(cursor, component);
4269
- const stat = lstatIfPresent(fs3, cursor);
4647
+ cursor = join2(cursor, component);
4648
+ const stat = lstatIfPresent(fs2, cursor);
4270
4649
  if (stat?.isSymbolicLink()) throw new CoworkConfigError(`${label} must not traverse a symbolic link (symlink): ${cursor}`);
4271
4650
  if (!stat) break;
4272
4651
  if (!stat.isDirectory()) {
@@ -4285,39 +4664,39 @@ function assertTrustedAncestor(stat, rootOwner, path, label) {
4285
4664
  throw new CoworkConfigError(`${label} has an unsafe writable ancestor: ${path}`);
4286
4665
  }
4287
4666
  }
4288
- function createSecureDirectoryTree(fs3, path, label) {
4667
+ function createSecureDirectoryTree(fs2, path, label) {
4289
4668
  const missing = [];
4290
4669
  let cursor = path;
4291
- while (!lstatIfPresent(fs3, cursor)) {
4670
+ while (!lstatIfPresent(fs2, cursor)) {
4292
4671
  missing.push(cursor);
4293
- const parent = dirname(cursor);
4672
+ const parent = dirname2(cursor);
4294
4673
  if (parent === cursor) throw new CoworkConfigError(`cannot locate an existing ancestor for ${label}`);
4295
4674
  cursor = parent;
4296
4675
  }
4297
- assertSecureAncestors(fs3, path, label);
4676
+ assertSecureAncestors(fs2, path, label);
4298
4677
  for (const directory of missing.reverse()) {
4299
- fs3.mkdirSync(directory, { mode: DIRECTORY_MODE });
4300
- secureOpenedDirectory(fs3, directory, label);
4301
- fsyncDirectory(fs3, dirname(directory));
4678
+ fs2.mkdirSync(directory, { mode: DIRECTORY_MODE });
4679
+ secureOpenedDirectory(fs2, directory, label);
4680
+ fsyncDirectory(fs2, dirname2(directory));
4302
4681
  }
4303
4682
  }
4304
- function secureOpenedDirectory(fs3, path, label) {
4683
+ function secureOpenedDirectory(fs2, path, label) {
4305
4684
  let fd;
4306
4685
  try {
4307
- fd = fs3.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
4308
- const opened = fs3.fstatSync(fd);
4309
- const current = fs3.lstatSync(path);
4686
+ fd = fs2.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
4687
+ const opened = fs2.fstatSync(fd);
4688
+ const current = fs2.lstatSync(path);
4310
4689
  if (!opened.isDirectory() || current.isSymbolicLink() || opened.dev !== current.dev || opened.ino !== current.ino) {
4311
4690
  throw new CoworkConfigError(`${label} changed while opening`);
4312
4691
  }
4313
- fs3.fchmodSync(fd, DIRECTORY_MODE);
4314
- fs3.fsyncSync(fd);
4692
+ fs2.fchmodSync(fd, DIRECTORY_MODE);
4693
+ fs2.fsyncSync(fd);
4315
4694
  } finally {
4316
- if (fd !== void 0) fs3.closeSync(fd);
4695
+ if (fd !== void 0) fs2.closeSync(fd);
4317
4696
  }
4318
4697
  }
4319
- function assertSecureDirectory(fs3, path, label) {
4320
- const stat = fs3.lstatSync(path);
4698
+ function assertSecureDirectory(fs2, path, label) {
4699
+ const stat = fs2.lstatSync(path);
4321
4700
  if (stat.isSymbolicLink()) throw new CoworkConfigError(`${label} must not be a symbolic link (symlink)`);
4322
4701
  if (!stat.isDirectory()) throw new CoworkConfigError(`${label} must be a directory`);
4323
4702
  if ((stat.mode & 511) !== DIRECTORY_MODE) {
@@ -4325,8 +4704,8 @@ function assertSecureDirectory(fs3, path, label) {
4325
4704
  }
4326
4705
  assertOwner(stat, label);
4327
4706
  }
4328
- function assertSecureFile(fs3, path, label) {
4329
- const stat = fs3.lstatSync(path);
4707
+ function assertSecureFile(fs2, path, label) {
4708
+ const stat = fs2.lstatSync(path);
4330
4709
  if (stat.isSymbolicLink()) throw new CoworkConfigError(`${label} must not be a symbolic link (symlink)`);
4331
4710
  if (!stat.isFile() || stat.nlink !== 1) throw new CoworkConfigError(`${label} must be a single-link regular file`);
4332
4711
  if ((stat.mode & 511) !== FILE_MODE) throw new CoworkConfigError(`${label} mode must be 0600`);
@@ -4337,38 +4716,38 @@ function assertOwner(stat, label) {
4337
4716
  throw new CoworkConfigError(`${label} must be owned by the current user`);
4338
4717
  }
4339
4718
  }
4340
- function readSecureFile(fs3, path, label) {
4719
+ function readSecureFile(fs2, path, label) {
4341
4720
  let fd;
4342
4721
  try {
4343
- fd = fs3.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
4344
- const opened = fs3.fstatSync(fd);
4345
- const current = fs3.lstatSync(path);
4722
+ fd = fs2.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
4723
+ const opened = fs2.fstatSync(fd);
4724
+ const current = fs2.lstatSync(path);
4346
4725
  if (!opened.isFile() || opened.nlink !== 1 || (opened.mode & 511) !== FILE_MODE || typeof process.getuid === "function" && opened.uid !== process.getuid() || current.isSymbolicLink() || current.dev !== opened.dev || current.ino !== opened.ino) {
4347
4726
  throw new CoworkConfigError(`${label} changed while opening`);
4348
4727
  }
4349
- return fs3.readFileSync(fd);
4728
+ return fs2.readFileSync(fd);
4350
4729
  } finally {
4351
- if (fd !== void 0) fs3.closeSync(fd);
4730
+ if (fd !== void 0) fs2.closeSync(fd);
4352
4731
  }
4353
4732
  }
4354
- function fsyncDirectory(fs3, path) {
4733
+ function fsyncDirectory(fs2, path) {
4355
4734
  let fd;
4356
4735
  try {
4357
- fd = fs3.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
4358
- fs3.fsyncSync(fd);
4736
+ fd = fs2.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
4737
+ fs2.fsyncSync(fd);
4359
4738
  } finally {
4360
- if (fd !== void 0) fs3.closeSync(fd);
4739
+ if (fd !== void 0) fs2.closeSync(fd);
4361
4740
  }
4362
4741
  }
4363
- function lstatIfPresent(fs3, path) {
4742
+ function lstatIfPresent(fs2, path) {
4364
4743
  try {
4365
- return fs3.lstatSync(path);
4744
+ return fs2.lstatSync(path);
4366
4745
  } catch (error) {
4367
4746
  if (error.code === "ENOENT") return void 0;
4368
4747
  throw error;
4369
4748
  }
4370
4749
  }
4371
- var DIRECTORY_MODE, FILE_MODE, NO_FOLLOW, CoworkDaemonSchema, CoworkConfigSchema, CoworkConfigError;
4750
+ var DIRECTORY_MODE, FILE_MODE, NO_FOLLOW, CoworkConfigSchema, CoworkConfigError;
4372
4751
  var init_config = __esm({
4373
4752
  "src/config.ts"() {
4374
4753
  "use strict";
@@ -4376,33 +4755,6 @@ var init_config = __esm({
4376
4755
  DIRECTORY_MODE = 448;
4377
4756
  FILE_MODE = 384;
4378
4757
  NO_FOLLOW = nodeFs.constants.O_NOFOLLOW ?? 0;
4379
- CoworkDaemonSchema = external_exports.object({
4380
- mode: external_exports.enum(["embedded", "external"]),
4381
- endpoint: external_exports.string().url().refine(isDaemonOrigin, "daemon.endpoint must be an http(s) origin with no credentials, path, query, or fragment").refine(isConfidentialDaemonEndpoint, "daemon.endpoint must use https:// unless the daemon is on this host (http:// is allowed only for localhost, 127.0.0.0/8, or [::1])").transform(normalizeDaemonEndpoint).optional(),
4382
- stateDir: external_exports.string().min(1).transform((value) => resolve(value)).optional()
4383
- }).strict().superRefine((value, ctx) => {
4384
- if (value.mode === "external") {
4385
- for (const field of ["endpoint", "stateDir"]) {
4386
- if (value[field] === void 0) {
4387
- ctx.addIssue({
4388
- code: external_exports.ZodIssueCode.custom,
4389
- path: [field],
4390
- message: `daemon.${field} is required when daemon.mode is "external"`
4391
- });
4392
- }
4393
- }
4394
- return;
4395
- }
4396
- for (const field of ["endpoint", "stateDir"]) {
4397
- if (value[field] !== void 0) {
4398
- ctx.addIssue({
4399
- code: external_exports.ZodIssueCode.custom,
4400
- path: [field],
4401
- message: `daemon.${field} is only valid when daemon.mode is "external"`
4402
- });
4403
- }
4404
- }
4405
- });
4406
4758
  CoworkConfigSchema = external_exports.object({
4407
4759
  version: external_exports.literal(1),
4408
4760
  brokerUrl: external_exports.string().url().refine((value) => {
@@ -4413,10 +4765,7 @@ var init_config = __esm({
4413
4765
  rest: external_exports.object({
4414
4766
  enabled: external_exports.boolean(),
4415
4767
  port: external_exports.number().int().min(1).max(65535)
4416
- }).strict(),
4417
- // Absent unless an operator selected a daemon explicitly, so an untouched
4418
- // deployment keeps producing byte-identical effective configuration.
4419
- daemon: CoworkDaemonSchema.optional()
4768
+ }).strict()
4420
4769
  }).strict();
4421
4770
  CoworkConfigError = class extends Error {
4422
4771
  constructor(message, options) {
@@ -4427,271 +4776,6 @@ var init_config = __esm({
4427
4776
  }
4428
4777
  });
4429
4778
 
4430
- // src/ours-runtime.ts
4431
- import { randomBytes } from "node:crypto";
4432
- import * as fs from "node:fs";
4433
- import { join as join2, resolve as resolve2 } from "node:path";
4434
- function createOursHost(config, log = () => {
4435
- }) {
4436
- return daemonMode(config) === "external" ? new ExternalOursHost(config, log) : new EmbeddedOursHost(config, log);
4437
- }
4438
- function sleep(ms, signal) {
4439
- return new Promise((resolveSleep) => {
4440
- const timer = setTimeout(finish, ms);
4441
- signal.addEventListener("abort", finish, { once: true });
4442
- function finish() {
4443
- clearTimeout(timer);
4444
- signal.removeEventListener("abort", finish);
4445
- resolveSleep();
4446
- }
4447
- });
4448
- }
4449
- function sdkRuntimeStateDir(config) {
4450
- return resolve2(config.stateDir, "ours-sdk");
4451
- }
4452
- function configureOwnedSdkEnvironment(config) {
4453
- const stateDir = sdkRuntimeStateDir(config);
4454
- fs.mkdirSync(stateDir, { recursive: true, mode: 448 });
4455
- fs.chmodSync(stateDir, 448);
4456
- process.env.OURS_CONFIG = join2(stateDir, "config.json");
4457
- process.env.OURS_STATE_DIR = stateDir;
4458
- process.env.OURS_BROKER_URL = config.brokerUrl;
4459
- process.env.OURS_PORT = "0";
4460
- process.env.OURS_API_VISIBILITY = "owner";
4461
- process.env.OURS_TRANSPORT = "http";
4462
- process.env.OURS_AUTOSTART = "false";
4463
- process.env.OURS_GC_INTERVAL_MS = "3600000";
4464
- delete process.env.OURS_API_TOKEN;
4465
- return stateDir;
4466
- }
4467
- var WATCH_RETRY_MIN_MS, WATCH_RETRY_MAX_MS, EmbeddedOursHost, ExternalOursHost;
4468
- var init_ours_runtime = __esm({
4469
- "src/ours-runtime.ts"() {
4470
- "use strict";
4471
- init_config();
4472
- WATCH_RETRY_MIN_MS = 500;
4473
- WATCH_RETRY_MAX_MS = 3e4;
4474
- EmbeddedOursHost = class {
4475
- config;
4476
- log;
4477
- listeners = /* @__PURE__ */ new Set();
4478
- handle;
4479
- Client;
4480
- apiToken;
4481
- closed = false;
4482
- constructor(config, log = () => {
4483
- }) {
4484
- this.config = config;
4485
- this.log = log;
4486
- }
4487
- async boot() {
4488
- if (this.handle) return;
4489
- if (this.closed) throw new Error("embedded ours SDK runtime cannot restart in the same process");
4490
- const runtimeDir = configureOwnedSdkEnvironment(this.config);
4491
- let handle;
4492
- try {
4493
- const [{ OursClient }, { startDaemon }] = await Promise.all([
4494
- import("@ours.network/sdk"),
4495
- import("@ours.network/sdk/daemon")
4496
- ]);
4497
- handle = await startDaemon({
4498
- version: "@ours.network/cowork@vNext",
4499
- handleSignals: false,
4500
- onIdentityNotify: (identityName) => {
4501
- for (const listener of this.listeners) {
4502
- try {
4503
- listener(identityName);
4504
- } catch (error) {
4505
- this.log(`cowork SDK notification listener failed for ${identityName}:`, error);
4506
- }
4507
- }
4508
- }
4509
- });
4510
- const tokenPath = join2(runtimeDir, "daemon-token");
4511
- const apiToken = fs.readFileSync(tokenPath, "utf8").trim();
4512
- if (!apiToken) throw new Error("embedded ours SDK runtime created an empty owner token");
4513
- this.Client = OursClient;
4514
- this.apiToken = apiToken;
4515
- this.handle = handle;
4516
- } catch (error) {
4517
- await handle?.close();
4518
- throw new Error("failed to start the embedded ours SDK runtime", { cause: error });
4519
- }
4520
- }
4521
- createClient(leaseToken = `cowork-${randomBytes(16).toString("hex")}`) {
4522
- if (!this.handle || !this.Client || !this.apiToken) {
4523
- throw new Error("embedded ours SDK runtime is not booted");
4524
- }
4525
- return new this.Client({
4526
- url: `http://127.0.0.1:${this.handle.port}`,
4527
- leaseToken,
4528
- apiToken: this.apiToken
4529
- });
4530
- }
4531
- onIdentityNotify(listener) {
4532
- this.listeners.add(listener);
4533
- return () => this.listeners.delete(listener);
4534
- }
4535
- close() {
4536
- }
4537
- async shutdown() {
4538
- if (this.closed) return { requiresProcessExit: true };
4539
- this.closed = true;
4540
- const handle = this.handle;
4541
- this.handle = void 0;
4542
- this.Client = void 0;
4543
- this.apiToken = void 0;
4544
- this.listeners.clear();
4545
- await handle?.close();
4546
- return { requiresProcessExit: true };
4547
- }
4548
- };
4549
- ExternalOursHost = class {
4550
- endpoint;
4551
- daemonStateDir;
4552
- log;
4553
- listeners = /* @__PURE__ */ new Set();
4554
- watchers = /* @__PURE__ */ new Map();
4555
- watchLeaseToken = `cowork-watch-${randomBytes(16).toString("hex")}`;
4556
- Client;
4557
- resolved;
4558
- watchClient;
4559
- closed = false;
4560
- constructor(config, log = () => {
4561
- }) {
4562
- const daemon = config.daemon;
4563
- if (daemon?.mode !== "external" || daemon.endpoint === void 0 || daemon.stateDir === void 0) {
4564
- throw new Error("external ours daemon mode requires both daemon.endpoint and daemon.stateDir");
4565
- }
4566
- this.endpoint = daemon.endpoint;
4567
- this.daemonStateDir = daemon.stateDir;
4568
- this.log = log;
4569
- }
4570
- async boot() {
4571
- if (this.resolved) return;
4572
- if (this.closed) throw new Error("external ours daemon host cannot restart in the same process");
4573
- const { OursClient, resolveDaemonConfig, assertDaemonStateDir } = await import("@ours.network/sdk");
4574
- let resolved;
4575
- try {
4576
- resolved = resolveDaemonConfig({ endpoint: this.endpoint, expectStateDir: this.daemonStateDir });
4577
- } catch (error) {
4578
- throw new Error(`cowork cannot select the external ours daemon at ${this.endpoint}`, { cause: error });
4579
- }
4580
- try {
4581
- await assertDaemonStateDir(resolved);
4582
- } catch (error) {
4583
- throw new Error(
4584
- `the external ours daemon at ${this.endpoint} is unavailable or does not own ${this.daemonStateDir}`,
4585
- { cause: error }
4586
- );
4587
- }
4588
- this.Client = OursClient;
4589
- this.resolved = resolved;
4590
- this.watchClient = this.createClient(this.watchLeaseToken);
4591
- }
4592
- createClient(leaseToken = `cowork-${randomBytes(16).toString("hex")}`) {
4593
- if (!this.Client || !this.resolved) throw new Error("external ours daemon host is not booted");
4594
- return new this.Client({
4595
- url: this.resolved.baseUrl.value,
4596
- leaseToken,
4597
- ...this.resolved.token === void 0 ? {} : { apiToken: this.resolved.token.value }
4598
- });
4599
- }
4600
- onIdentityNotify(listener) {
4601
- this.listeners.add(listener);
4602
- return () => this.listeners.delete(listener);
4603
- }
4604
- trackIdentity(identityName) {
4605
- if (!this.watchClient) throw new Error("external ours daemon host is not booted");
4606
- const existing = this.watchers.get(identityName);
4607
- if (existing) return () => this.untrack(identityName, existing);
4608
- const controller = new AbortController();
4609
- const watcher = { controller, work: Promise.resolve() };
4610
- watcher.work = this.follow(identityName, controller.signal);
4611
- this.watchers.set(identityName, watcher);
4612
- return () => this.untrack(identityName, watcher);
4613
- }
4614
- close() {
4615
- }
4616
- async shutdown() {
4617
- if (this.closed) return { requiresProcessExit: false };
4618
- this.closed = true;
4619
- this.listeners.clear();
4620
- const watchers = [...this.watchers.values()];
4621
- this.watchers.clear();
4622
- for (const watcher of watchers) watcher.controller.abort();
4623
- await Promise.allSettled(watchers.map((watcher) => watcher.work));
4624
- const client = this.watchClient;
4625
- this.watchClient = void 0;
4626
- this.Client = void 0;
4627
- this.resolved = void 0;
4628
- if (client) await client.releaseLease().catch(() => {
4629
- });
4630
- return { requiresProcessExit: false };
4631
- }
4632
- untrack(identityName, watcher) {
4633
- if (this.watchers.get(identityName) !== watcher) return;
4634
- this.watchers.delete(identityName);
4635
- watcher.controller.abort();
4636
- }
4637
- /**
4638
- * Long-poll one identity forever, reconnecting with bounded backoff.
4639
- *
4640
- * A watch primes at the daemon's tip, so a reconnected watch never replays
4641
- * what arrived while it was down. Losing those arrivals would be silent, so
4642
- * every reconnect announces the identity once. The consumer's reaction to an
4643
- * announcement is a full state refresh, which is exactly the recovery this
4644
- * needs; it is idempotent and self-coalescing, so the extra announcement can
4645
- * only cost one refresh.
4646
- *
4647
- * ORDER IS LOAD-BEARING. The resync is issued only AFTER the replacement
4648
- * watch's request is already in flight, because that request is what fixes
4649
- * the new tip. Announcing first would read the inbox at T0 and fix the tip at
4650
- * T1 > T0, and anything arriving in between would be in neither — the very
4651
- * gap this exists to close. This way every arrival lands in the refresh, on
4652
- * the stream, or (harmlessly) in both.
4653
- */
4654
- async follow(identityName, signal) {
4655
- let backoffMs = WATCH_RETRY_MIN_MS;
4656
- let resyncPending = false;
4657
- while (!signal.aborted) {
4658
- const client = this.watchClient;
4659
- if (!client) return;
4660
- try {
4661
- const stream = client.watchNotifications(identityName, { signal });
4662
- let step = stream.next();
4663
- if (resyncPending) {
4664
- resyncPending = false;
4665
- this.announce(identityName);
4666
- }
4667
- for (let settled = await step; !settled.done; settled = await step) {
4668
- backoffMs = WATCH_RETRY_MIN_MS;
4669
- this.announce(identityName);
4670
- step = stream.next();
4671
- }
4672
- } catch (error) {
4673
- if (signal.aborted) return;
4674
- resyncPending = true;
4675
- this.log(`[${identityName}] external ours daemon notification watch failed:`, error);
4676
- }
4677
- if (signal.aborted) return;
4678
- await sleep(backoffMs, signal);
4679
- backoffMs = Math.min(backoffMs * 2, WATCH_RETRY_MAX_MS);
4680
- }
4681
- }
4682
- announce(identityName) {
4683
- for (const listener of this.listeners) {
4684
- try {
4685
- listener(identityName);
4686
- } catch (error) {
4687
- this.log(`cowork SDK notification listener failed for ${identityName}:`, error);
4688
- }
4689
- }
4690
- }
4691
- };
4692
- }
4693
- });
4694
-
4695
4779
  // src/contracts.ts
4696
4780
  import { createHash } from "node:crypto";
4697
4781
  function utf8Bounded(label, maximumBytes) {
@@ -4721,15 +4805,15 @@ function isStrictRfc3339(value) {
4721
4805
  function normalizeRoomName(value) {
4722
4806
  return value.trim().normalize("NFC");
4723
4807
  }
4724
- function roomIdentityName(roomId) {
4725
- return `${ROOM_IDENTITY_PREFIX}${LowerCrockfordUlidSchema.parse(roomId)}`;
4808
+ function roomIdentityName(roomName) {
4809
+ return `${ROOM_IDENTITY_PREFIX}${RoomNameSchema.parse(roomName)}`;
4726
4810
  }
4727
4811
  function legacyRoomIdentityName(roomId) {
4728
4812
  return `cowork-room-${LowerCrockfordUlidSchema.parse(roomId)}`;
4729
4813
  }
4730
4814
  function refineRoomLineage(room, context) {
4731
4815
  const pendingIdentityName = legacyRoomIdentityName(room.room_id);
4732
- const currentPendingIdentityName = roomIdentityName(room.room_id);
4816
+ const currentPendingIdentityName = room.room_name === void 0 ? void 0 : roomIdentityName(room.room_name);
4733
4817
  const exactPacketPending = room.state === "provisioning" && room.status === "packet_pending" && (room.identity_name === pendingIdentityName || room.identity_name === currentPendingIdentityName) && room.invites.length === 0 && room.seats.length === 0 && room.activated_at === void 0 && room.closed_at === void 0;
4734
4818
  if (room.identity_cid === "" && !exactPacketPending) {
4735
4819
  context.addIssue({
@@ -4786,6 +4870,7 @@ function migrateRoomV1(room, mintParticipantId) {
4786
4870
  version: 2,
4787
4871
  mission: { ...room.mission, briefing_version: 1 },
4788
4872
  role_briefings: {},
4873
+ rest_roles: [],
4789
4874
  anonymous: false,
4790
4875
  quiet_membership: false,
4791
4876
  membership_epoch: 0,
@@ -4856,7 +4941,7 @@ function refineMessageCategory(message, context) {
4856
4941
  }
4857
4942
  }
4858
4943
  }
4859
- var MAX_TEXT_BYTES, MAX_FILE_BYTES, MAX_HISTORY_PAGE_BYTES, MAX_MANAGEMENT_RESPONSE_BYTES, MAX_EXTERNAL_INVITE_BYTES, MAX_FILE_NAME_BYTES, MAX_MIME_BYTES, MAX_ROLE_BYTES, MAX_ROOM_NAME_CHARACTERS, NonEmptyStringSchema, PositiveSafeIntegerSchema, LowerCrockfordUlidSchema, Rfc3339Schema, RoomNameSchema, ROOM_IDENTITY_PREFIX, RoleSchema, MissionTextSchema, MessageTextSchema, FileNameSchema, FileMimeSchema, RoomStateSchema, SeatStateSchema, InviteModeSchema, DEFAULT_ROLE, InviteStateSchema, RelayStatusSchema, SeatV1Schema, SeatSchema, RoomInviteSchema, MissionV1Schema, MissionSchema, RoleBriefingSchema, RoomCommonShape, RoomV1Schema, CurrentRoomSchema, RoomSchema, CreateRoomInputSchema, UpdateRoomInputSchema, RoleBriefingSetInputSchema, RoleBriefingDeleteInputSchema, PostMessageInputSchema, ContainerIdSchema, AcceptExternalInviteInputSchema, AuthorSnapshotSchema, RecordCommonShape, AppendCommonShape, MembershipNoticeSchema, AuthorAliasSchema, ReplyReferenceSchema, MessageShape, RelayIntentShape, RelayResultStatusSchema, RelayResultShape, FileShape, MembershipIntentShape, MembershipResultShape, CloseNoticeIntentShape, CloseNoticeResultShape, MessageRecordSchema, FileRecordSchema, RelayIntentRecordSchema, RelayResultRecordSchema, MembershipIntentRecordSchema, MembershipResultRecordSchema, CloseNoticeIntentRecordSchema, CloseNoticeResultRecordSchema, RawCommunicationRecordSchema, CommunicationRecordSchema, AppendRecordSchema;
4944
+ var MAX_TEXT_BYTES, MAX_FILE_BYTES, MAX_HISTORY_PAGE_BYTES, MAX_MANAGEMENT_RESPONSE_BYTES, MAX_EXTERNAL_INVITE_BYTES, MAX_FILE_NAME_BYTES, MAX_MIME_BYTES, MAX_ROLE_BYTES, MAX_ROOM_NAME_CHARACTERS, NonEmptyStringSchema, PositiveSafeIntegerSchema, LowerCrockfordUlidSchema, Rfc3339Schema, RoomNameSchema, ROOM_IDENTITY_PREFIX, RoleSchema, MissionTextSchema, MessageTextSchema, FileNameSchema, FileMimeSchema, RoomStateSchema, SeatStateSchema, InviteModeSchema, DEFAULT_ROLE, InviteStateSchema, RelayStatusSchema, SeatV1Schema, SeatSchema, RoomInviteSchema, MissionV1Schema, MissionSchema, RoleBriefingSchema, RoomCommonShape, RoomV1Schema, CurrentRoomSchema, RoomSchema, CreateRoomInputSchema, UpdateRoomInputSchema, RoleBriefingSetInputSchema, RoleBriefingDeleteInputSchema, PostMessageInputSchema, PostAsRoleInputSchema, RestRoleInputSchema, ContainerIdSchema, AcceptExternalInviteInputSchema, AuthorSnapshotSchema, RecordCommonShape, AppendCommonShape, MembershipNoticeSchema, AuthorAliasSchema, MessageShape, RelayIntentShape, RelayResultStatusSchema, RelayResultShape, FileShape, MembershipIntentShape, MembershipResultShape, CloseNoticeIntentShape, CloseNoticeResultShape, MessageRecordSchema, FileRecordSchema, RelayIntentRecordSchema, RelayResultRecordSchema, MembershipIntentRecordSchema, MembershipResultRecordSchema, CloseNoticeIntentRecordSchema, CloseNoticeResultRecordSchema, RawCommunicationRecordSchema, CommunicationRecordSchema, AppendRecordSchema;
4860
4945
  var init_contracts = __esm({
4861
4946
  "src/contracts.ts"() {
4862
4947
  "use strict";
@@ -4889,7 +4974,7 @@ var init_contracts = __esm({
4889
4974
  });
4890
4975
  }
4891
4976
  });
4892
- ROOM_IDENTITY_PREFIX = "ours-cowork-";
4977
+ ROOM_IDENTITY_PREFIX = "ours-cowork-room:";
4893
4978
  RoleSchema = utf8Bounded("role", MAX_ROLE_BYTES);
4894
4979
  MissionTextSchema = utf8Bounded("mission text", MAX_TEXT_BYTES);
4895
4980
  MessageTextSchema = utf8Bounded("message text", MAX_TEXT_BYTES);
@@ -5087,6 +5172,13 @@ var init_contracts = __esm({
5087
5172
  version: external_exports.literal(2),
5088
5173
  mission: MissionSchema,
5089
5174
  role_briefings: external_exports.record(RoleSchema, RoleBriefingSchema),
5175
+ /**
5176
+ * Roles a REST caller may author under (spec §3.1). A plain array of names:
5177
+ * the role name IS the identifier, so there is nothing per-role to store.
5178
+ * Not a seat, not a membership — no invariant here constrains it, because a
5179
+ * role is a label and several speakers may share one.
5180
+ */
5181
+ rest_roles: external_exports.array(RoleSchema),
5090
5182
  anonymous: external_exports.boolean(),
5091
5183
  quiet_membership: external_exports.boolean(),
5092
5184
  membership_epoch: external_exports.number().int().nonnegative().safe(),
@@ -5175,10 +5267,17 @@ var init_contracts = __esm({
5175
5267
  }
5176
5268
  });
5177
5269
  RoomSchema = external_exports.preprocess((value) => {
5178
- if (typeof value !== "object" || value === null || Object.hasOwn(value, "room_name")) return value;
5179
- const roomId = value.room_id;
5180
- if (typeof roomId !== "string" || !LowerCrockfordUlidSchema.safeParse(roomId).success) return value;
5181
- return { ...value, room_name: defaultRoomName(roomId) };
5270
+ if (typeof value !== "object" || value === null) return value;
5271
+ const patch = {};
5272
+ if (!Object.hasOwn(value, "room_name")) {
5273
+ const roomId = value.room_id;
5274
+ if (typeof roomId === "string" && LowerCrockfordUlidSchema.safeParse(roomId).success) {
5275
+ patch.room_name = defaultRoomName(roomId);
5276
+ }
5277
+ }
5278
+ if (!Object.hasOwn(value, "rest_roles")) patch.rest_roles = [];
5279
+ if (Object.keys(patch).length === 0) return value;
5280
+ return { ...value, ...patch };
5182
5281
  }, CurrentRoomSchema);
5183
5282
  CreateRoomInputSchema = external_exports.object({
5184
5283
  name: RoomNameSchema.optional(),
@@ -5204,6 +5303,13 @@ var init_contracts = __esm({
5204
5303
  PostMessageInputSchema = external_exports.object({
5205
5304
  text: MessageTextSchema
5206
5305
  }).strict();
5306
+ PostAsRoleInputSchema = external_exports.object({
5307
+ role: RoleSchema,
5308
+ text: MessageTextSchema
5309
+ }).strict();
5310
+ RestRoleInputSchema = external_exports.object({
5311
+ role: RoleSchema
5312
+ }).strict();
5207
5313
  ContainerIdSchema = external_exports.string().regex(/^[0-9a-f]{64}$/i, "must be a 64-character hexadecimal CID").transform((value) => value.toUpperCase());
5208
5314
  AcceptExternalInviteInputSchema = external_exports.object({
5209
5315
  role: RoleSchema,
@@ -5241,10 +5347,6 @@ var init_contracts = __esm({
5241
5347
  participant_id: LowerCrockfordUlidSchema,
5242
5348
  alias: NonEmptyStringSchema
5243
5349
  }).strict();
5244
- ReplyReferenceSchema = external_exports.object({
5245
- wire_id: NonEmptyStringSchema,
5246
- sentence: PositiveSafeIntegerSchema.optional()
5247
- }).strict();
5248
5350
  MessageShape = {
5249
5351
  kind: external_exports.literal("message"),
5250
5352
  message_id: LowerCrockfordUlidSchema,
@@ -5269,8 +5371,7 @@ var init_contracts = __esm({
5269
5371
  }
5270
5372
  }),
5271
5373
  source_msg_id: external_exports.number().int().nonnegative().safe().optional(),
5272
- source_wire_id: NonEmptyStringSchema.optional(),
5273
- source_reply_to: ReplyReferenceSchema.optional()
5374
+ source_wire_id: NonEmptyStringSchema.optional()
5274
5375
  };
5275
5376
  RelayIntentShape = {
5276
5377
  kind: external_exports.literal("relay_intent"),
@@ -5313,8 +5414,7 @@ var init_contracts = __esm({
5313
5414
  }
5314
5415
  }),
5315
5416
  source_file_id: external_exports.number().int().nonnegative().safe(),
5316
- source_wire_id: NonEmptyStringSchema.optional(),
5317
- source_reply_to: ReplyReferenceSchema.optional()
5417
+ source_wire_id: NonEmptyStringSchema.optional()
5318
5418
  };
5319
5419
  MembershipIntentShape = {
5320
5420
  kind: external_exports.literal("membership_intent"),
@@ -5396,93 +5496,217 @@ var init_contracts = __esm({
5396
5496
  });
5397
5497
 
5398
5498
  // src/packets.ts
5399
- import { createHash as createHash2 } from "node:crypto";
5400
- import * as fs2 from "node:fs";
5401
- import { join as join3 } from "node:path";
5402
- import { brotliCompressSync, brotliDecompressSync, constants as zlibConstants } from "node:zlib";
5403
- function sendResult(result) {
5404
- if (result.kind === "refused" || result.kind === "migrating") return { status: "send_failed" };
5405
- if (result.kind === "introduced") return { status: "queued" };
5406
- return { status: "queued", wire_id: result.wireId };
5407
- }
5408
- async function fileItem(file, bytes) {
5409
- return {
5410
- file_id: file.file_id,
5411
- sender_id: file.from.id,
5412
- sender_name: file.from.name,
5413
- filename: file.filename,
5414
- mime: file.mime,
5415
- data: Buffer.from(bytes),
5416
- date: normalizeDate(file.date),
5417
- wire_id: file.wire_id,
5418
- reply_to: cloneReply(file.reply_to)
5419
- };
5499
+ import { randomBytes } from "node:crypto";
5500
+ import * as nodeFs2 from "node:fs";
5501
+ import { dirname as dirname3, join as join3 } from "node:path";
5502
+ function atomicWriteFileSync(target, bytes, ops = nodeFs2) {
5503
+ const temp = `${target}.tmp-${process.pid}-${temporarySequence++}`;
5504
+ let fileFd;
5505
+ let directoryFd;
5506
+ try {
5507
+ fileFd = ops.openSync(temp, nodeFs2.constants.O_CREAT | nodeFs2.constants.O_TRUNC | nodeFs2.constants.O_WRONLY, 384);
5508
+ ops.fchmodSync(fileFd, 384);
5509
+ let offset = 0;
5510
+ while (offset < bytes.byteLength) {
5511
+ const written = ops.writeSync(fileFd, bytes, offset, bytes.byteLength - offset, null);
5512
+ if (written <= 0) throw new Error(`short write while persisting ${target}`);
5513
+ offset += written;
5514
+ }
5515
+ ops.fsyncSync(fileFd);
5516
+ ops.closeSync(fileFd);
5517
+ fileFd = void 0;
5518
+ ops.renameSync(temp, target);
5519
+ ops.chmodSync(target, 384);
5520
+ directoryFd = ops.openSync(dirname3(target), nodeFs2.constants.O_RDONLY);
5521
+ ops.fsyncSync(directoryFd);
5522
+ ops.closeSync(directoryFd);
5523
+ directoryFd = void 0;
5524
+ } catch (error) {
5525
+ if (fileFd !== void 0) {
5526
+ try {
5527
+ ops.closeSync(fileFd);
5528
+ } catch {
5529
+ }
5530
+ }
5531
+ if (directoryFd !== void 0) {
5532
+ try {
5533
+ ops.closeSync(directoryFd);
5534
+ } catch {
5535
+ }
5536
+ }
5537
+ try {
5538
+ ops.rmSync(temp, { force: true });
5539
+ } catch {
5540
+ }
5541
+ throw new PacketPersistenceError(`failed to durably persist ${target}`, { cause: error });
5542
+ }
5420
5543
  }
5421
- function cloneReply(reply) {
5422
- return reply === null ? null : {
5423
- wire_id: reply.wire_id,
5424
- ...reply.sentence === void 0 ? {} : { sentence: reply.sentence }
5425
- };
5544
+ function renderInbox(value) {
5545
+ const output = [];
5546
+ if (value.IsNil()) return output;
5547
+ for (let index = 0; ; index += 1) {
5548
+ const message = value.Reduce(index);
5549
+ if (message.IsNil()) break;
5550
+ output.push({
5551
+ msg_id: Number(message.Reduce("msg_id").Visualize()),
5552
+ sender_id: message.Reduce("sender_id").Visualize(),
5553
+ sender_name: message.Reduce("sender_name").Visualize(),
5554
+ text: message.Reduce("text").Visualize(),
5555
+ date: adaptTimeToRfc3339(message.Reduce("date").Visualize()),
5556
+ status: message.Reduce("status").Visualize(),
5557
+ wire_id: message.Reduce("wire_id").Visualize()
5558
+ });
5559
+ }
5560
+ return output;
5426
5561
  }
5427
- function normalizeDate(value) {
5428
- const date = new Date(value);
5429
- if (Number.isNaN(date.valueOf())) throw new Error(`SDK returned an invalid message timestamp: ${value}`);
5430
- return date.toISOString();
5562
+ function renderFileInbox(value) {
5563
+ const output = [];
5564
+ if (value.IsNil()) return output;
5565
+ for (let index = 0; ; index += 1) {
5566
+ const file = value.Reduce(index);
5567
+ if (file.IsNil()) break;
5568
+ output.push({
5569
+ file_id: Number(file.Reduce("file_id").Visualize()),
5570
+ sender_id: file.Reduce("sender_id").Visualize(),
5571
+ sender_name: file.Reduce("sender_name").Visualize(),
5572
+ filename: file.Reduce("filename").Visualize(),
5573
+ mime: file.Reduce("mime").Visualize(),
5574
+ data: Buffer.from(file.Reduce("data").GetBinary()),
5575
+ date: adaptTimeToRfc3339(file.Reduce("date").Visualize()),
5576
+ status: file.Reduce("status").Visualize(),
5577
+ wire_id: file.Reduce("wire_id").Visualize()
5578
+ });
5579
+ }
5580
+ return output;
5431
5581
  }
5432
- function hasOursCode(error, code) {
5433
- return error instanceof Error && "code" in error && error.code === code;
5582
+ function renderIntegerArray(value) {
5583
+ const output = [];
5584
+ if (value.IsNil()) return output;
5585
+ for (let index = 0; ; index += 1) {
5586
+ const item = value.Reduce(index);
5587
+ if (item.IsNil()) break;
5588
+ output.push(Number(item.Visualize()));
5589
+ }
5590
+ return output;
5434
5591
  }
5435
- function validateRoomId(roomId) {
5436
- if (!/^[0-7][0-9a-hjkmnp-tv-z]{25}$/.test(roomId)) throw new Error(`invalid room id "${roomId}"`);
5592
+ function dictionaryEntries(value) {
5593
+ if (value.IsNil()) return [];
5594
+ return value.GetKeys().map((key) => [key.Visualize(), value.Reduce(key)]);
5437
5595
  }
5438
- function unpackInvite(encoded, maximumBytes) {
5439
- const normalized = encoded.replace(/\s+/g, "");
5440
- if (maximumBytes !== void 0 && Buffer.byteLength(encoded, "utf8") > maximumBytes || normalized.length === 0 || !/^[A-Za-z0-9_-]+$/.test(normalized)) {
5441
- throw new Error("the invite blob is empty, oversized, or invalid base64url");
5596
+ function booleanValue(value) {
5597
+ if (value.IsNil()) return false;
5598
+ try {
5599
+ return value.GetBoolean();
5600
+ } catch {
5601
+ return /true/i.test(value.Visualize());
5442
5602
  }
5443
- const compressed = Buffer.from(normalized, "base64url");
5444
- if (compressed.length === 0) throw new Error("the invite blob is empty or invalid base64url");
5445
- return Buffer.from(maximumBytes === void 0 ? brotliDecompressSync(compressed) : brotliDecompressSync(compressed, { maxOutputLength: maximumBytes }));
5446
5603
  }
5447
- var LegacyCoworkStateError, PacketRegistry, SdkRoomPacket;
5604
+ function strictBooleanValue(value, label) {
5605
+ if (value.IsNil()) throw new Error(`${label} must be a boolean`);
5606
+ let decoded;
5607
+ try {
5608
+ decoded = value.GetBoolean();
5609
+ } catch (error) {
5610
+ throw new Error(`${label} must be a boolean`, { cause: error });
5611
+ }
5612
+ if (typeof decoded !== "boolean") throw new Error(`${label} must be a boolean`);
5613
+ return decoded;
5614
+ }
5615
+ function nilString(value) {
5616
+ return value.IsNil() ? "" : value.Visualize();
5617
+ }
5618
+ function adaptTimeToRfc3339(value) {
5619
+ const rfc3339 = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(?:Z|([+-])(\d{2}):(\d{2}))$/.exec(value);
5620
+ if (rfc3339) {
5621
+ const [, year2, month2, day2, hour2, minute2, second2, fraction2 = "", sign2, offsetHour2 = "0", offsetMinute = "0"] = rfc3339;
5622
+ if (sign2 === "-" && offsetHour2 === "00" && offsetMinute === "00") return invalidAdaptTime(value);
5623
+ return canonicalUtcTime(value, year2, month2, day2, hour2, minute2, second2, fraction2, sign2, offsetHour2, offsetMinute);
5624
+ }
5625
+ const native = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))? \(UTC(?:([+-])(0|[1-9]|1\d|2[0-3]))?\)$/.exec(value);
5626
+ if (!native) return invalidAdaptTime(value);
5627
+ const [, year, month, day, hour, minute, second, fraction = "", sign, offsetHour = "0"] = native;
5628
+ if (sign === "-" && offsetHour === "0") return invalidAdaptTime(value);
5629
+ return canonicalUtcTime(value, year, month, day, hour, minute, second, fraction, sign, offsetHour, "0");
5630
+ }
5631
+ function canonicalUtcTime(source, yearText, monthText, dayText, hourText, minuteText, secondText, fraction, offsetSign, offsetHourText, offsetMinuteText) {
5632
+ const year = Number(yearText);
5633
+ const month = Number(monthText);
5634
+ const day = Number(dayText);
5635
+ const hour = Number(hourText);
5636
+ const minute = Number(minuteText);
5637
+ const second = Number(secondText);
5638
+ const offsetHour = Number(offsetHourText);
5639
+ const offsetMinute = Number(offsetMinuteText);
5640
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
5641
+ const monthDays = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
5642
+ if (month < 1 || month > 12 || day < 1 || day > monthDays[month - 1] || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59) return invalidAdaptTime(source);
5643
+ const milliseconds = Number(fraction.slice(0, 3).padEnd(3, "0"));
5644
+ const local = /* @__PURE__ */ new Date(0);
5645
+ local.setUTCFullYear(year, month - 1, day);
5646
+ local.setUTCHours(hour, minute, second, milliseconds);
5647
+ if (local.getUTCFullYear() !== year || local.getUTCMonth() !== month - 1 || local.getUTCDate() !== day || local.getUTCHours() !== hour || local.getUTCMinutes() !== minute || local.getUTCSeconds() !== second) {
5648
+ return invalidAdaptTime(source);
5649
+ }
5650
+ const direction = offsetSign === "-" ? -1 : 1;
5651
+ const offsetMilliseconds = direction * (offsetHour * 60 + offsetMinute) * 6e4;
5652
+ const canonical = new Date(local.getTime() - offsetMilliseconds).toISOString();
5653
+ if (!/^\d{4}-/.test(canonical)) return invalidAdaptTime(source);
5654
+ return canonical;
5655
+ }
5656
+ function invalidAdaptTime(value) {
5657
+ throw new Error(`unexpected ADAPT time visualization: ${value}`);
5658
+ }
5659
+ function inviteMode(value) {
5660
+ const normalized = value.replace(/^\$/, "");
5661
+ if (normalized === "one_time" || normalized === "public") return normalized;
5662
+ throw new Error(`unexpected invite mode: ${value}`);
5663
+ }
5664
+ function exportSigningSecret(packet) {
5665
+ return withScope((lifetime) => Buffer.from(packet.readonlyTx("::actor::export_signing_secret", lifetime).Serialize()).toString("hex"));
5666
+ }
5667
+ function validateRoomId(roomId) {
5668
+ if (!/^[A-Za-z0-9_-]{1,128}$/.test(roomId)) throw new Error(`invalid room id: ${roomId}`);
5669
+ }
5670
+ var PacketPersistenceError, temporarySequence, PacketRegistry, HostedRoomPacket;
5448
5671
  var init_packets = __esm({
5449
- "src/packets.ts"() {
5672
+ async "src/packets.ts"() {
5450
5673
  "use strict";
5674
+ await init_adapt();
5451
5675
  init_contracts();
5452
- LegacyCoworkStateError = class extends Error {
5453
- constructor(roomId) {
5454
- super(
5455
- `room "${roomId}" uses the pre-1.0 custom packet format and cannot be opened by the standard ours SDK runtime; back it up with the old release, recreate the room for cowork 1.0, and re-invite its participants`
5456
- );
5457
- this.name = "LegacyCoworkStateError";
5676
+ PacketPersistenceError = class extends Error {
5677
+ constructor(message, options) {
5678
+ super(message, options);
5679
+ this.name = "PacketPersistenceError";
5458
5680
  }
5459
5681
  };
5682
+ temporarySequence = 0;
5460
5683
  PacketRegistry = class {
5461
5684
  packets = /* @__PURE__ */ new Map();
5462
- trackers = /* @__PURE__ */ new Map();
5463
5685
  host;
5464
5686
  stateDir;
5465
5687
  fs;
5688
+ persistence;
5466
5689
  log;
5690
+ seed;
5691
+ beforeExpose;
5467
5692
  onNotify;
5468
- unsubscribe;
5693
+ provisioningCheckpoint;
5694
+ stagingName;
5469
5695
  constructor(host, stateDir, options = {}) {
5470
5696
  this.host = host;
5471
5697
  this.stateDir = stateDir;
5472
- this.fs = options.fs ?? fs2;
5698
+ this.fs = options.fs ?? nodeFs2;
5699
+ this.persistence = options.persistence ?? this.fs;
5473
5700
  this.log = options.log ?? (() => {
5474
5701
  });
5702
+ this.seed = options.seed ?? (() => randomBytes(24).toString("hex"));
5703
+ this.beforeExpose = options.beforeExpose ?? (() => {
5704
+ });
5475
5705
  this.onNotify = options.onNotify ?? (() => {
5476
5706
  });
5477
- this.unsubscribe = host.onIdentityNotify((name) => {
5478
- const found = [...this.packets.entries()].find(([, packet2]) => packet2.name === name);
5479
- if (!found) return;
5480
- const [roomId, packet] = found;
5481
- void packet.refresh().then(
5482
- () => this.onNotify(roomId, "message_received"),
5483
- (error) => this.log(`[${name}] failed to refresh SDK state after notification:`, error)
5484
- );
5707
+ this.provisioningCheckpoint = options.provisioningCheckpoint ?? (() => {
5485
5708
  });
5709
+ this.stagingName = options.stagingName ?? (() => `live.staging-${randomBytes(16).toString("hex")}`);
5486
5710
  }
5487
5711
  get size() {
5488
5712
  return this.packets.size;
@@ -5490,253 +5714,563 @@ var init_packets = __esm({
5490
5714
  get(roomId) {
5491
5715
  return this.packets.get(roomId);
5492
5716
  }
5493
- async create(roomId, identityName = `ours-cowork-${roomId}`, bio = `ours-cowork mission room ${roomId}`) {
5717
+ async create(roomId, identityName = `cowork-room-${roomId}`, bio = `ours-cowork mission room ${roomId}`) {
5494
5718
  validateRoomId(roomId);
5495
- this.assertStandardIdentity(roomId, identityName);
5496
- this.assertNoLegacyState(roomId);
5497
- if (this.packets.has(roomId)) throw new Error(`room identity "${roomId}" is already hosted`);
5498
- const client = this.host.createClient();
5499
- try {
5500
- let cid;
5719
+ if (this.packets.has(roomId)) throw new Error(`room packet "${roomId}" is already hosted`);
5720
+ const liveDir = this.liveDir(roomId);
5721
+ if (this.hasRestorableState(roomId)) {
5501
5722
  try {
5502
- cid = (await client.chooseIdentity({ name: identityName, force: false })).cid;
5723
+ return await this.restore(roomId, void 0, identityName, bio);
5503
5724
  } catch (error) {
5504
- if (!hasOursCode(error, "NO_SUCH_IDENTITY")) throw error;
5505
- const created = await client.createIdentity({
5506
- name: identityName,
5507
- bio,
5508
- exposeLocal: false,
5509
- localAutoAccept: true
5510
- });
5511
- cid = created.info.cid;
5725
+ this.log(`[${this.packetName(roomId)}] pending packet restore failed; reprovisioning:`, error);
5512
5726
  }
5513
- const packet = new SdkRoomPacket(identityName, cid, client);
5514
- await packet.refresh();
5515
- this.packets.set(roomId, packet);
5516
- this.track(roomId, identityName);
5517
- return packet;
5727
+ }
5728
+ this.prepareProvisioningDirectory(roomId);
5729
+ let native;
5730
+ try {
5731
+ native = await this.host.createPacket(this.packetName(roomId), this.seed());
5732
+ let room;
5733
+ room = new HostedRoomPacket(
5734
+ native,
5735
+ () => this.saveState(native, liveDir),
5736
+ this.log,
5737
+ (event) => this.onNotify(roomId, event),
5738
+ () => {
5739
+ if (this.packets.get(roomId) === room) this.packets.delete(roomId);
5740
+ }
5741
+ );
5742
+ atomicWriteFileSync(this.identityPath(roomId), Buffer.from(exportSigningSecret(native), "utf8"), this.persistence);
5743
+ this.provisioningCheckpoint("identity");
5744
+ this.saveState(native, liveDir);
5745
+ this.provisioningCheckpoint("state");
5746
+ this.packets.set(roomId, room);
5747
+ await room.setIdentity(identityName, bio);
5748
+ this.provisioningCheckpoint("identity_applied");
5749
+ return room;
5518
5750
  } catch (error) {
5519
- await client.releaseLease().catch(() => {
5520
- });
5521
- throw new Error(`failed to provision standard SDK identity for room "${roomId}"`, { cause: error });
5751
+ if (native) {
5752
+ try {
5753
+ this.host.removePacket(native.cid);
5754
+ } catch {
5755
+ }
5756
+ }
5757
+ this.packets.delete(roomId);
5758
+ throw error;
5522
5759
  }
5523
5760
  }
5524
- async restore(roomId, expectedCid, identityName = `ours-cowork-${roomId}`) {
5761
+ async restore(roomId, expectedCid, identityName, bio) {
5525
5762
  validateRoomId(roomId);
5526
- this.assertStandardIdentity(roomId, identityName);
5527
- this.assertNoLegacyState(roomId);
5528
- if (this.packets.has(roomId)) throw new Error(`room identity "${roomId}" is already hosted`);
5529
- const client = this.host.createClient();
5763
+ if (this.packets.has(roomId)) throw new Error(`room packet "${roomId}" is already hosted`);
5764
+ const liveDir = this.liveDir(roomId);
5765
+ const secret = this.fs.readFileSync(this.identityPath(roomId), "utf8").trim();
5766
+ if (!/^[0-9a-f]+$/i.test(secret) || secret.length % 2 !== 0) {
5767
+ throw new Error(`invalid signing secret for room "${roomId}"`);
5768
+ }
5769
+ const stateBytes = this.fs.readFileSync(this.statePath(roomId));
5770
+ if (stateBytes.length === 0) throw new Error(`empty packet state for room "${roomId}"`);
5771
+ const native = await this.host.createPacket(
5772
+ this.packetName(roomId),
5773
+ this.seed(),
5774
+ secret,
5775
+ { deferredExposure: true }
5776
+ );
5777
+ if (expectedCid !== void 0 && native.cid !== expectedCid) {
5778
+ try {
5779
+ this.host.removePacket(native.cid);
5780
+ } catch {
5781
+ }
5782
+ throw new Error(
5783
+ `restored room packet CID mismatch for "${roomId}": expected "${expectedCid}", found "${native.cid}"`
5784
+ );
5785
+ }
5786
+ let room;
5787
+ room = new HostedRoomPacket(
5788
+ native,
5789
+ () => this.saveState(native, liveDir),
5790
+ this.log,
5791
+ (event) => this.onNotify(roomId, event),
5792
+ () => {
5793
+ if (this.packets.get(roomId) === room) this.packets.delete(roomId);
5794
+ }
5795
+ );
5530
5796
  try {
5531
- const bound = await client.chooseIdentity({ name: identityName, force: false });
5532
- if (expectedCid !== void 0 && bound.cid !== expectedCid) {
5533
- throw new Error(`restored room identity CID mismatch: expected "${expectedCid}", found "${bound.cid}"`);
5534
- }
5535
- const packet = new SdkRoomPacket(identityName, bound.cid, client);
5536
- await packet.refresh();
5537
- this.packets.set(roomId, packet);
5538
- this.track(roomId, identityName);
5539
- return packet;
5540
- } catch (error) {
5541
- await client.releaseLease().catch(() => {
5797
+ await withScopeAsync(async (lifetime) => {
5798
+ const state = native.pw.packet.ParseValue(new Uint8Array(stateBytes)).Attach(lifetime);
5799
+ await native.mutatingTx("::actor::import_state", state, lifetime);
5542
5800
  });
5801
+ native.pw.refresh_identity_proof_document();
5802
+ if (identityName !== void 0 && bio !== void 0) {
5803
+ await room.setIdentity(identityName, bio);
5804
+ this.provisioningCheckpoint("identity_applied");
5805
+ }
5806
+ atomicWriteFileSync(
5807
+ this.ownershipPath(roomId),
5808
+ Buffer.from(`${roomId}
5809
+ `, "utf8"),
5810
+ this.persistence
5811
+ );
5812
+ await this.beforeExpose(room);
5813
+ this.host.exposePacket(native.cid);
5814
+ this.packets.set(roomId, room);
5815
+ return room;
5816
+ } catch (error) {
5817
+ try {
5818
+ this.host.removePacket(native.cid);
5819
+ } catch {
5820
+ }
5543
5821
  throw error;
5544
5822
  }
5545
5823
  }
5546
5824
  async destroy(roomId) {
5547
5825
  validateRoomId(roomId);
5548
- const packet = this.packets.get(roomId);
5549
- if (!packet) return [];
5550
- try {
5551
- await packet.destroy();
5826
+ const room = this.packets.get(roomId);
5827
+ if (room) {
5828
+ this.host.removePacket(room.cid);
5552
5829
  this.packets.delete(roomId);
5553
- return [];
5830
+ }
5831
+ const liveDir = this.liveDir(roomId);
5832
+ let removalFailure;
5833
+ try {
5834
+ this.assertSafeRoomDirectory(roomId);
5835
+ this.fs.rmSync(liveDir, { recursive: true, force: true });
5836
+ this.fsyncDirectory(this.roomDir(roomId));
5554
5837
  } catch (error) {
5555
- throw new Error(`failed to remove standard SDK identity for room "${roomId}"`, { cause: error });
5556
- } finally {
5557
- this.untrack(roomId);
5838
+ this.log(`[${room?.name ?? this.packetName(roomId)}] live-state removal failed:`, error);
5839
+ removalFailure = error;
5840
+ }
5841
+ const residue = this.residue(roomId);
5842
+ if (removalFailure !== void 0 && residue.length === 0) {
5843
+ throw new PacketPersistenceError(
5844
+ `live-state removal durability is uncertain for room "${roomId}"`,
5845
+ { cause: removalFailure }
5846
+ );
5558
5847
  }
5848
+ return residue;
5559
5849
  }
5850
+ /** Unhost runtime packets while retaining every byte required for restart. */
5560
5851
  async unhostAll() {
5561
- this.unsubscribe();
5562
- for (const roomId of [...this.trackers.keys()]) this.untrack(roomId);
5563
5852
  const errors = [];
5564
- for (const [roomId, packet] of [...this.packets]) {
5853
+ for (const [roomId, room] of [...this.packets]) {
5565
5854
  try {
5566
- await packet.close();
5855
+ this.host.removePacket(room.cid, new Error("cowork daemon is shutting down"));
5856
+ this.packets.delete(roomId);
5567
5857
  } catch (error) {
5568
5858
  errors.push(error);
5569
5859
  }
5570
- this.packets.delete(roomId);
5571
5860
  }
5572
- if (errors.length) throw new AggregateError(errors, "failed to release room SDK leases");
5861
+ if (errors.length > 0) throw new AggregateError(errors, "failed to unhost room packets");
5573
5862
  }
5574
- /**
5575
- * Hosts that watch the runtime from outside need to be told which identities
5576
- * exist; an embedded host omits `trackIdentity` and keeps its own callback.
5577
- */
5578
- track(roomId, identityName) {
5579
- const dispose = this.host.trackIdentity?.(identityName);
5580
- if (dispose) this.trackers.set(roomId, dispose);
5581
- }
5582
- untrack(roomId) {
5583
- const dispose = this.trackers.get(roomId);
5584
- if (!dispose) return;
5585
- this.trackers.delete(roomId);
5863
+ saveState(packet, liveDir) {
5586
5864
  try {
5587
- dispose();
5865
+ const bytes = withScope((lifetime) => Buffer.from(packet.readonlyTx("::actor::export_state", lifetime).Serialize()));
5866
+ atomicWriteFileSync(join3(liveDir, "state_data.bin"), bytes, this.persistence);
5588
5867
  } catch (error) {
5589
- this.log(`failed to stop the notification watch for room "${roomId}":`, error);
5868
+ if (error instanceof PacketPersistenceError) throw error;
5869
+ throw new PacketPersistenceError(`failed to export state for packet "${packet.name}"`, { cause: error });
5590
5870
  }
5591
5871
  }
5592
- assertNoLegacyState(roomId) {
5593
- const live = join3(this.stateDir, "rooms", roomId, "live");
5594
- if (this.fs.existsSync(join3(live, "identity.key")) || this.fs.existsSync(join3(live, "state_data.bin"))) {
5595
- throw new LegacyCoworkStateError(roomId);
5872
+ residue(roomId) {
5873
+ const liveDir = this.liveDir(roomId);
5874
+ try {
5875
+ this.fs.lstatSync(liveDir);
5876
+ return [liveDir];
5877
+ } catch (error) {
5878
+ if (error.code === "ENOENT") return [];
5879
+ throw error;
5596
5880
  }
5597
5881
  }
5598
- assertStandardIdentity(roomId, identityName) {
5599
- if (identityName !== `ours-cowork-${roomId}`) throw new LegacyCoworkStateError(roomId);
5882
+ packetName(roomId) {
5883
+ return `cowork-room-${roomId}`;
5600
5884
  }
5601
- };
5602
- SdkRoomPacket = class {
5603
- name;
5604
- cid;
5605
- client;
5606
- contacts = [];
5607
- invites = [];
5608
- inbox = [];
5609
- fileInbox = [];
5610
- refreshWork;
5611
- constructor(name, cid, client) {
5612
- this.name = name;
5613
- this.cid = cid;
5614
- this.client = client;
5615
- }
5616
- refresh() {
5617
- this.refreshWork ??= this.refreshUnlocked().finally(() => {
5618
- this.refreshWork = void 0;
5619
- });
5620
- return this.refreshWork;
5621
- }
5622
- async refreshUnlocked() {
5623
- const contacts = await this.client.listContacts();
5624
- this.contacts = contacts.contacts.map((contact) => ({ ...contact }));
5625
- this.invites = (await this.client.listInvites()).flatMap((invite) => invite.mode === "one_time" || invite.mode === "public" ? [{ invite_id: invite.invite_id, mode: invite.mode }] : []);
5626
- this.inbox = (await this.client.listIncomingMessages()).filter((message) => message.status === "unread").map((message) => ({
5627
- msg_id: message.msg_id,
5628
- sender_id: message.sender_id,
5629
- sender_name: message.sender_name,
5630
- text: message.text,
5631
- date: normalizeDate(message.date),
5632
- wire_id: message.wire_id,
5633
- reply_to: cloneReply(message.reply_to)
5634
- }));
5635
- await this.refreshFiles();
5885
+ roomDir(roomId) {
5886
+ return join3(this.stateDir, "rooms", roomId);
5636
5887
  }
5637
- async refreshFiles() {
5638
- const unread = (await this.client.listIncomingFiles()).filter((file) => file.status === "unread");
5639
- if (unread.length === 0) {
5640
- this.fileInbox = [];
5641
- return;
5888
+ liveDir(roomId) {
5889
+ return join3(this.roomDir(roomId), "live");
5890
+ }
5891
+ identityPath(roomId) {
5892
+ return join3(this.liveDir(roomId), "identity.key");
5893
+ }
5894
+ statePath(roomId) {
5895
+ return join3(this.liveDir(roomId), "state_data.bin");
5896
+ }
5897
+ ownershipPath(roomId) {
5898
+ return join3(this.liveDir(roomId), ".cowork-provisioning-v1");
5899
+ }
5900
+ stagingJournalPath(roomId) {
5901
+ return join3(this.roomDir(roomId), ".cowork-provisioning-stage");
5902
+ }
5903
+ residuePath(roomId) {
5904
+ return join3(this.roomDir(roomId), "provisioning-residue");
5905
+ }
5906
+ hasRestorableState(roomId) {
5907
+ try {
5908
+ const secret = this.fs.readFileSync(this.identityPath(roomId), "utf8").trim();
5909
+ return /^[0-9a-f]+$/i.test(secret) && secret.length % 2 === 0 && this.fs.readFileSync(this.statePath(roomId)).length > 0;
5910
+ } catch {
5911
+ return false;
5912
+ }
5913
+ }
5914
+ prepareProvisioningDirectory(roomId) {
5915
+ const roomDir = this.roomDir(roomId);
5916
+ const liveDir = this.liveDir(roomId);
5917
+ if (!this.fs.existsSync(roomDir)) {
5918
+ this.fs.mkdirSync(roomDir, { recursive: true, mode: 448 });
5919
+ this.fs.chmodSync(roomDir, 448);
5642
5920
  }
5643
- const ids = unread.map((file) => file.file_id);
5644
- const wires = unread.map((file) => file.wire_id);
5921
+ if (this.fs.existsSync(liveDir)) {
5922
+ let owned = false;
5923
+ try {
5924
+ owned = this.fs.readFileSync(this.ownershipPath(roomId), "utf8") === `${roomId}
5925
+ `;
5926
+ } catch {
5927
+ }
5928
+ if (owned) {
5929
+ this.assertSafeRoomDirectory(roomId);
5930
+ this.fs.rmSync(liveDir, { recursive: true, force: true });
5931
+ this.fsyncDirectory(roomDir);
5932
+ } else {
5933
+ this.assertSafeRoomDirectory(roomId);
5934
+ const residue = this.residuePath(roomId);
5935
+ if (this.fs.existsSync(residue)) {
5936
+ throw new PacketPersistenceError(
5937
+ `room "${roomId}" has unknown live state and an existing provisioning residue; inspect both before retrying`
5938
+ );
5939
+ }
5940
+ this.fs.renameSync(liveDir, residue);
5941
+ this.fs.chmodSync(residue, 448);
5942
+ this.fsyncDirectory(roomDir);
5943
+ }
5944
+ }
5945
+ this.cleanupOwnedStaging(roomId);
5946
+ const stagingName = this.stagingName();
5947
+ if (!/^live\.staging-[0-9a-f]{32}$/.test(stagingName)) {
5948
+ throw new PacketPersistenceError(`invalid provisioning staging name for room "${roomId}"`);
5949
+ }
5950
+ this.createStagingJournal(roomId, stagingName);
5951
+ const stagingDir = join3(roomDir, stagingName);
5952
+ try {
5953
+ this.fs.mkdirSync(stagingDir, { recursive: false, mode: 448 });
5954
+ } catch (error) {
5955
+ try {
5956
+ this.fs.unlinkSync(this.stagingJournalPath(roomId));
5957
+ this.fsyncDirectory(roomDir);
5958
+ } catch (cleanupError) {
5959
+ throw new AggregateError([error, cleanupError], `provisioning staging collision cleanup failed for room "${roomId}"`);
5960
+ }
5961
+ throw new PacketPersistenceError(`provisioning staging collision for room "${roomId}"`, { cause: error });
5962
+ }
5963
+ this.fs.chmodSync(stagingDir, 448);
5964
+ atomicWriteFileSync(
5965
+ join3(stagingDir, ".cowork-provisioning-v1"),
5966
+ Buffer.from(`${roomId}
5967
+ `, "utf8"),
5968
+ this.persistence
5969
+ );
5970
+ this.provisioningCheckpoint("mkdir");
5971
+ this.fs.renameSync(stagingDir, liveDir);
5972
+ this.fsyncDirectory(roomDir);
5973
+ this.fs.unlinkSync(this.stagingJournalPath(roomId));
5974
+ this.fsyncDirectory(roomDir);
5975
+ }
5976
+ cleanupOwnedStaging(roomId) {
5977
+ const journal = this.stagingJournalPath(roomId);
5978
+ let journalFd;
5645
5979
  try {
5646
- await this.client.getFiles({ wire_ids: wires });
5647
- this.fileInbox = await Promise.all(unread.map(async (file) => fileItem(file, await this.client.fetchFile(file.wire_id))));
5980
+ try {
5981
+ journalFd = this.fs.openSync(
5982
+ journal,
5983
+ nodeFs2.constants.O_RDONLY | (nodeFs2.constants.O_NOFOLLOW ?? 0)
5984
+ );
5985
+ } catch (error) {
5986
+ if (error.code === "ENOENT") return;
5987
+ throw error;
5988
+ }
5989
+ const journalStat = this.fs.fstatSync(journalFd);
5990
+ if (!journalStat.isFile() || journalStat.size > 128) {
5991
+ throw new PacketPersistenceError(`unsafe provisioning staging journal for room "${roomId}"`);
5992
+ }
5993
+ const stagingName = this.fs.readFileSync(journalFd, "utf8").trim();
5994
+ if (!/^live\.staging-[0-9a-f]{32}$/.test(stagingName)) {
5995
+ throw new PacketPersistenceError(`invalid provisioning staging journal for room "${roomId}"`);
5996
+ }
5997
+ const roomDir = this.roomDir(roomId);
5998
+ const stagingDir = join3(roomDir, stagingName);
5999
+ if (this.fs.existsSync(stagingDir)) {
6000
+ const stat = this.fs.lstatSync(stagingDir);
6001
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
6002
+ throw new PacketPersistenceError(`unsafe provisioning staging path for room "${roomId}"`);
6003
+ }
6004
+ let owned = false;
6005
+ try {
6006
+ owned = this.fs.readFileSync(join3(stagingDir, ".cowork-provisioning-v1"), "utf8") === `${roomId}
6007
+ `;
6008
+ } catch {
6009
+ }
6010
+ if (owned) {
6011
+ this.fs.rmSync(stagingDir, { recursive: true, force: true });
6012
+ this.fsyncDirectory(roomDir);
6013
+ } else {
6014
+ const residue = this.residuePath(roomId);
6015
+ if (this.fs.existsSync(residue)) {
6016
+ throw new PacketPersistenceError(
6017
+ `provisioning staging recovery for room "${roomId}" found an existing provisioning residue; preserving both`
6018
+ );
6019
+ }
6020
+ this.fs.renameSync(stagingDir, residue);
6021
+ this.fs.chmodSync(residue, 448);
6022
+ this.fsyncDirectory(roomDir);
6023
+ }
6024
+ }
6025
+ const currentJournal = this.fs.lstatSync(journal);
6026
+ if (!currentJournal.isFile() || currentJournal.dev !== journalStat.dev || currentJournal.ino !== journalStat.ino) {
6027
+ throw new PacketPersistenceError(
6028
+ `provisioning staging journal changed during recovery for room "${roomId}"; replacement preserved`
6029
+ );
6030
+ }
6031
+ this.fs.unlinkSync(journal);
6032
+ this.fsyncDirectory(roomDir);
5648
6033
  } finally {
5649
- await this.client.deferFiles({ file_ids: ids }).catch(() => {
5650
- });
6034
+ if (journalFd !== void 0) this.fs.closeSync(journalFd);
5651
6035
  }
5652
6036
  }
6037
+ createStagingJournal(roomId, stagingName) {
6038
+ const journal = this.stagingJournalPath(roomId);
6039
+ const bytes = Buffer.from(`${stagingName}
6040
+ `, "utf8");
6041
+ let fd;
6042
+ let created = false;
6043
+ try {
6044
+ fd = this.persistence.openSync(
6045
+ journal,
6046
+ nodeFs2.constants.O_CREAT | nodeFs2.constants.O_EXCL | nodeFs2.constants.O_WRONLY | (nodeFs2.constants.O_NOFOLLOW ?? 0),
6047
+ 384
6048
+ );
6049
+ created = true;
6050
+ this.persistence.fchmodSync(fd, 384);
6051
+ let offset = 0;
6052
+ while (offset < bytes.byteLength) {
6053
+ const written = this.persistence.writeSync(fd, bytes, offset, bytes.byteLength - offset, null);
6054
+ if (written <= 0) throw new Error(`short write while persisting ${journal}`);
6055
+ offset += written;
6056
+ }
6057
+ this.persistence.fsyncSync(fd);
6058
+ this.persistence.closeSync(fd);
6059
+ fd = void 0;
6060
+ this.fsyncDirectory(this.roomDir(roomId));
6061
+ } catch (error) {
6062
+ if (fd !== void 0) {
6063
+ try {
6064
+ this.persistence.closeSync(fd);
6065
+ } catch {
6066
+ }
6067
+ }
6068
+ if (created) {
6069
+ try {
6070
+ this.fs.unlinkSync(journal);
6071
+ this.fsyncDirectory(this.roomDir(roomId));
6072
+ } catch {
6073
+ }
6074
+ }
6075
+ throw new PacketPersistenceError(`failed to establish staging ownership for room "${roomId}"`, { cause: error });
6076
+ }
6077
+ }
6078
+ assertSafeRoomDirectory(roomId) {
6079
+ const roomDir = this.roomDir(roomId);
6080
+ const stat = this.fs.lstatSync(roomDir);
6081
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
6082
+ throw new Error(`room directory for "${roomId}" is not a safe directory`);
6083
+ }
6084
+ }
6085
+ fsyncDirectory(path) {
6086
+ let fd;
6087
+ try {
6088
+ fd = this.fs.openSync(path, nodeFs2.constants.O_RDONLY | (nodeFs2.constants.O_NOFOLLOW ?? 0));
6089
+ this.fs.fsyncSync(fd);
6090
+ } finally {
6091
+ if (fd !== void 0) this.fs.closeSync(fd);
6092
+ }
6093
+ }
6094
+ };
6095
+ HostedRoomPacket = class {
6096
+ name;
6097
+ cid;
6098
+ packet;
6099
+ constructor(packet, saveState, log, onNotify = () => {
6100
+ }, onTerminal = () => {
6101
+ }) {
6102
+ this.packet = packet;
6103
+ this.name = packet.name;
6104
+ this.cid = packet.cid;
6105
+ wireHandlers(packet, { onSaveState: saveState, onNotify: (event) => onNotify(event) }, log);
6106
+ packet.onTerminalClose?.(onTerminal);
6107
+ }
6108
+ async setIdentity(identityName, bio) {
6109
+ await withScopeAsync((lifetime) => this.packet.mutatingTx("::a2a_messaging::set_my_name", { name: identityName }, lifetime));
6110
+ await withScopeAsync((lifetime) => this.packet.mutatingTx("::a2a_messaging::set_my_bio", { bio }, lifetime));
6111
+ }
5653
6112
  async mintInvite(mode) {
5654
- const result = await this.client.generateInvite({ mode });
5655
- await this.refresh();
5656
- return { blob: result.blob, invite_id: result.inviteId, reusable: mode === "public" };
6113
+ return withScopeAsync(async (lifetime) => {
6114
+ const result = await this.packet.mutatingTx("::a2a_messaging::generate_invite", { mode }, lifetime);
6115
+ return {
6116
+ blob: packInvite(Buffer.from(result.Reduce("invite").GetBinary())),
6117
+ invite_id: result.Reduce("invite_id").Visualize(),
6118
+ reusable: booleanValue(result.Reduce("reusable"))
6119
+ };
6120
+ });
5657
6121
  }
5658
6122
  async addContact(invite) {
5659
- const decoded = unpackInvite(invite, MAX_EXTERNAL_INVITE_BYTES);
5660
- const result = await this.client.addContact({ invite });
5661
- await this.refresh();
5662
- return {
5663
- invite_id: createHash2("sha256").update(decoded).digest("hex"),
5664
- container_id: result.cid,
5665
- inviter_name: result.display,
5666
- pending_name: result.display
5667
- };
6123
+ let decoded;
6124
+ try {
6125
+ decoded = unpackInvite(invite, MAX_EXTERNAL_INVITE_BYTES);
6126
+ } catch (error) {
6127
+ throw new Error("external invite could not be decoded within the 48 KiB limit", { cause: error });
6128
+ }
6129
+ return withScopeAsync(async (lifetime) => {
6130
+ const result = await this.packet.mutatingTx("::a2a_messaging::add_contact", {
6131
+ invite: this.packet.newBinary(decoded, lifetime)
6132
+ }, lifetime);
6133
+ return {
6134
+ invite_id: result.Reduce("invite_id").Visualize(),
6135
+ container_id: result.Reduce("container_id").Visualize(),
6136
+ inviter_name: result.Reduce("inviter_name").Visualize(),
6137
+ pending_name: result.Reduce("pending").Visualize()
6138
+ };
6139
+ });
5668
6140
  }
5669
6141
  async revokeInvite(inviteId) {
5670
- const result = await this.client.revokeInvite({ invite_id: inviteId });
5671
- await this.refresh();
5672
- return { revoked: result.revoked };
6142
+ return withScopeAsync(async (lifetime) => {
6143
+ const result = await this.packet.mutatingTx("::a2a_messaging::revoke_invite", { invite_id: inviteId }, lifetime);
6144
+ return { revoked: booleanValue(result.Reduce("revoked")) };
6145
+ });
5673
6146
  }
5674
6147
  listInvites() {
5675
- return this.invites.map((invite) => ({ ...invite }));
6148
+ return withScope((lifetime) => {
6149
+ const value = this.packet.readonlyTx("::a2a_messaging::list_invites", lifetime);
6150
+ return dictionaryEntries(value).map(([inviteId, invite]) => ({
6151
+ invite_id: inviteId,
6152
+ mode: inviteMode(invite.Reduce("mode").Visualize())
6153
+ }));
6154
+ });
5676
6155
  }
5677
6156
  listContacts() {
5678
- return this.contacts.map((contact) => ({ ...contact }));
6157
+ return withScope((lifetime) => {
6158
+ const value = this.packet.readonlyTx("::a2a_messaging::list_contacts", lifetime);
6159
+ return dictionaryEntries(value).map(([, contact]) => ({
6160
+ name: contact.Reduce("name").Visualize(),
6161
+ container_id: contact.Reduce("container_id").Visualize()
6162
+ }));
6163
+ });
5679
6164
  }
5680
- peekInbox() {
5681
- return this.inbox.map((item) => ({ ...item }));
6165
+ listContactOrigins() {
6166
+ return withScope((lifetime) => {
6167
+ const value = this.packet.readonlyTx("::a2a_messaging::list_contact_origins", lifetime);
6168
+ return Object.fromEntries(dictionaryEntries(value).map(([cid, origin]) => [cid, {
6169
+ via: origin.Reduce("via").Visualize(),
6170
+ invite_id: nilString(origin.Reduce("invite_id")),
6171
+ at: adaptTimeToRfc3339(origin.Reduce("at").Visualize())
6172
+ }]));
6173
+ });
5682
6174
  }
5683
- peekFileInbox() {
5684
- return this.fileInbox.map((item) => ({ ...item, data: Buffer.from(item.data) }));
6175
+ peekInbox() {
6176
+ return withScope((lifetime) => renderInbox(this.packet.readonlyTx("::actor::list_incoming_messages", lifetime)).filter((message) => message.status === "unread").map(({ status: _status, ...message }) => message));
5685
6177
  }
5686
6178
  async consumeInbox(expectedIds) {
5687
- if (expectedIds.length === 0) return { consumed: [], deferred: [] };
5688
- const pulled = await this.client.getMessages();
5689
- const expected = new Set(expectedIds);
5690
- const consumed = pulled.messages.filter((message) => expected.has(message.msg_id)).map((message) => message.msg_id);
5691
- const deferred = pulled.messages.filter((message) => !expected.has(message.msg_id)).map((message) => message.msg_id);
5692
- if (deferred.length) await this.client.deferMessages({ msg_ids: deferred });
5693
- await this.refresh();
5694
- return { consumed, deferred };
6179
+ return withScopeAsync(async (lifetime) => {
6180
+ const result = await this.packet.mutatingTx(
6181
+ "::actor::consume_messages",
6182
+ { expected_ids: expectedIds },
6183
+ lifetime
6184
+ );
6185
+ return {
6186
+ consumed: renderIntegerArray(result.Reduce("consumed")),
6187
+ deferred: renderIntegerArray(result.Reduce("deferred"))
6188
+ };
6189
+ });
6190
+ }
6191
+ peekFileInbox() {
6192
+ return withScope((lifetime) => renderFileInbox(
6193
+ this.packet.readonlyTx("::actor::list_incoming_files", lifetime)
6194
+ ).filter((file) => file.status === "unread").map(({ status: _status, ...file }) => file));
5695
6195
  }
5696
6196
  async consumeFileInbox(expectedIds) {
5697
- const expected = new Set(expectedIds);
5698
- const selected = this.fileInbox.filter((file) => expected.has(file.file_id));
5699
- if (selected.length) await this.client.getFiles({ wire_ids: selected.map((file) => file.wire_id) });
5700
- this.fileInbox = this.fileInbox.filter((file) => !expected.has(file.file_id));
5701
- return { consumed: selected.map((file) => file.file_id), deferred: [] };
5702
- }
5703
- async send(contactCid, body, replyTo) {
5704
- return sendResult(await this.client.sendMessage({
5705
- contact: contactCid,
5706
- text: body,
5707
- ...replyTo === void 0 ? {} : {
5708
- reply_to_wire_id: replyTo.wire_id,
5709
- ...replyTo.sentence === void 0 ? {} : { reply_to_sentence: replyTo.sentence }
5710
- }
5711
- }));
6197
+ return withScopeAsync(async (lifetime) => {
6198
+ const result = await this.packet.mutatingTx(
6199
+ "::actor::consume_files",
6200
+ { expected_ids: expectedIds },
6201
+ lifetime
6202
+ );
6203
+ return {
6204
+ consumed: renderIntegerArray(result.Reduce("consumed")),
6205
+ deferred: renderIntegerArray(result.Reduce("deferred"))
6206
+ };
6207
+ });
6208
+ }
6209
+ async send(contactCid, body) {
6210
+ return withScopeAsync(async (lifetime) => {
6211
+ const result = await this.packet.mutatingTx(
6212
+ "::a2a_messaging::send_message",
6213
+ { contact: contactCid, text: body },
6214
+ lifetime
6215
+ );
6216
+ const refused = !result.Reduce("downgrade_refused").IsNil();
6217
+ return refused ? { status: "send_failed" } : { status: "queued", wire_id: nilString(result.Reduce("wire_id")) || void 0 };
6218
+ });
5712
6219
  }
5713
- async sendFile(contactCid, filename, mime, data, replyTo) {
6220
+ async sendFile(contactCid, filename, mime, data) {
5714
6221
  const validName = FileNameSchema.parse(filename);
5715
6222
  const validMime = FileMimeSchema.parse(mime);
5716
- if (data.length > MAX_FILE_BYTES) throw new RangeError(`room files must be at most ${MAX_FILE_BYTES} bytes (2 MiB)`);
5717
- return sendResult(await this.client.sendFile({
5718
- contact: contactCid,
5719
- data_base64: data.toString("base64"),
5720
- filename: validName,
5721
- mime: validMime,
5722
- ...replyTo === void 0 ? {} : {
5723
- reply_to_wire_id: replyTo.wire_id,
5724
- ...replyTo.sentence === void 0 ? {} : { reply_to_sentence: replyTo.sentence }
5725
- }
5726
- }));
6223
+ if (data.length > MAX_FILE_BYTES) {
6224
+ throw new RangeError(`room files must be at most ${MAX_FILE_BYTES} bytes (2 MiB)`);
6225
+ }
6226
+ return withScopeAsync(async (lifetime) => {
6227
+ const result = await this.packet.mutatingTx(
6228
+ "::a2a_messaging::send_file",
6229
+ {
6230
+ contact: contactCid,
6231
+ filename: validName,
6232
+ mime: validMime,
6233
+ // The core contract takes bytes. A filesystem path here would make
6234
+ // recovery depend on staging ownership and is deliberately forbidden.
6235
+ data: this.packet.newBinary(data, lifetime)
6236
+ },
6237
+ lifetime
6238
+ );
6239
+ const refused = !result.Reduce("downgrade_refused").IsNil() || !result.Reduce("migrating").IsNil();
6240
+ return refused ? { status: "send_failed" } : { status: "queued", wire_id: nilString(result.Reduce("wire_id")) || void 0 };
6241
+ });
5727
6242
  }
5728
6243
  async removeContact(contactCid) {
5729
- const result = await this.client.removeContact({ contact: contactCid });
5730
- const notified = result.notified === true;
5731
- await this.refresh();
5732
- return { status: notified ? "queued" : "send_failed", notified, key_material_retained: true };
5733
- }
5734
- async close() {
5735
- await this.client.releaseLease();
6244
+ return withScopeAsync(async (lifetime) => {
6245
+ const result = await this.packet.mutatingTx(
6246
+ "::a2a_messaging::remove_contact",
6247
+ { contact: contactCid },
6248
+ lifetime
6249
+ );
6250
+ const notified = strictBooleanValue(result.Reduce("notified"), "remove_contact notified");
6251
+ const keyMaterialRetained = strictBooleanValue(
6252
+ result.Reduce("key_material_retained"),
6253
+ "remove_contact key_material_retained"
6254
+ );
6255
+ if (!keyMaterialRetained) {
6256
+ throw new Error("remove_contact key_material_retained must be true");
6257
+ }
6258
+ return {
6259
+ status: notified ? "queued" : "send_failed",
6260
+ notified,
6261
+ key_material_retained: true
6262
+ };
6263
+ });
5736
6264
  }
5737
- async destroy() {
5738
- await this.client.removeIdentity({ name: this.name });
5739
- await this.client.releaseLease();
6265
+ async sign(canonicalJson2) {
6266
+ return withScopeAsync(async (lifetime) => {
6267
+ const result = await this.packet.mutatingTx(
6268
+ "::actor::sign_app_envelope",
6269
+ { canonical_json: canonicalJson2 },
6270
+ lifetime
6271
+ );
6272
+ return Buffer.from(result.Reduce("signature").GetBinary()).toString("base64url");
6273
+ });
5740
6274
  }
5741
6275
  };
5742
6276
  }
@@ -5774,18 +6308,15 @@ var init_ulid = __esm({
5774
6308
  });
5775
6309
 
5776
6310
  // src/intake.ts
5777
- import { createHash as createHash3 } from "node:crypto";
6311
+ import { createHash as createHash2 } from "node:crypto";
5778
6312
  function canonicalJson(value) {
5779
6313
  const encoded = JSON.stringify(canonicalValue(value));
5780
6314
  if (encoded === void 0) throw new TypeError("canonical JSON value is not serializable");
5781
6315
  return encoded;
5782
6316
  }
5783
- async function sendRoomBody(packet, recipientIdentity, unsigned) {
5784
- return packet.send(recipientIdentity, canonicalJson(unsigned));
5785
- }
5786
- function sameReply(stored, observed) {
5787
- if (stored === void 0 || observed == null) return stored === void 0 && observed == null;
5788
- return stored.wire_id === observed.wire_id && stored.sentence === observed.sentence;
6317
+ async function sendSignedBody(packet, recipientIdentity, unsigned) {
6318
+ const signature = await packet.sign(canonicalJson(unsigned));
6319
+ return packet.send(recipientIdentity, canonicalJson({ ...unsigned, signature }));
5789
6320
  }
5790
6321
  function canonicalValue(value) {
5791
6322
  if (Array.isArray(value)) return value.map(canonicalValue);
@@ -5940,12 +6471,11 @@ var init_intake = __esm({
5940
6471
  filename: parsedName.data,
5941
6472
  mime: parsedMime.data,
5942
6473
  size: bytes.length,
5943
- sha256: createHash3("sha256").update(bytes).digest("hex"),
6474
+ sha256: createHash2("sha256").update(bytes).digest("hex"),
5944
6475
  data_base64: bytes.toString("base64"),
5945
6476
  recipient_identities: recipientIdentities,
5946
6477
  source_file_id: item.file_id,
5947
- ...item.wire_id === "" ? {} : { source_wire_id: item.wire_id },
5948
- ...item.reply_to == null ? {} : { source_reply_to: item.reply_to }
6478
+ ...item.wire_id === "" ? {} : { source_wire_id: item.wire_id }
5949
6479
  });
5950
6480
  if (appended.kind !== "file") throw new Error("storage returned the wrong participant file kind");
5951
6481
  file = appended;
@@ -5985,8 +6515,7 @@ var init_intake = __esm({
5985
6515
  text: item.text,
5986
6516
  recipient_identities: recipientIdentities,
5987
6517
  source_msg_id: item.msg_id,
5988
- ...item.wire_id === "" ? {} : { source_wire_id: item.wire_id },
5989
- ...item.reply_to == null ? {} : { source_reply_to: item.reply_to }
6518
+ ...item.wire_id === "" ? {} : { source_wire_id: item.wire_id }
5990
6519
  });
5991
6520
  if (appended.kind !== "message") throw new Error("storage returned the wrong participant message kind");
5992
6521
  message = appended;
@@ -6011,7 +6540,7 @@ var init_intake = __esm({
6011
6540
  await this.store.save(RoomSchema.parse({ ...room, seats }));
6012
6541
  try {
6013
6542
  const unsigned = { version: 1, kind: "room_not_member", room_id: roomId };
6014
- await sendRoomBody(packet, item.sender_id, unsigned);
6543
+ await sendSignedBody(packet, item.sender_id, unsigned);
6015
6544
  } catch {
6016
6545
  }
6017
6546
  }
@@ -6095,7 +6624,7 @@ var init_intake = __esm({
6095
6624
  display_name: file.author_alias.alias,
6096
6625
  role: file.author.role
6097
6626
  };
6098
- const metadata = await sendRoomBody(packet, intent.recipient_identity, {
6627
+ const metadata = await sendSignedBody(packet, intent.recipient_identity, {
6099
6628
  version: 1,
6100
6629
  kind: "room_file",
6101
6630
  room_id: roomId,
@@ -6161,7 +6690,7 @@ var init_intake = __esm({
6161
6690
  ...message.briefing_version === void 0 ? {} : { briefing_version: message.briefing_version },
6162
6691
  ...message.membership === void 0 ? {} : { membership: message.membership }
6163
6692
  };
6164
- const outcome = await sendRoomBody(packet, intent.recipient_identity, unsigned);
6693
+ const outcome = await sendSignedBody(packet, intent.recipient_identity, unsigned);
6165
6694
  const appended = await this.store.append(roomId, {
6166
6695
  version: 1,
6167
6696
  kind: "relay_result",
@@ -6181,7 +6710,7 @@ var init_intake = __esm({
6181
6710
  const message = records.find((record) => record.kind === "message" && record.source_msg_id === item.msg_id);
6182
6711
  if (!message) return void 0;
6183
6712
  const observedWireId = item.wire_id === "" ? void 0 : item.wire_id;
6184
- if (message.source_wire_id !== observedWireId || !sameReply(message.source_reply_to, item.reply_to) || message.author.identity !== item.sender_id || message.text !== item.text || message.at !== item.date) {
6713
+ if (message.source_wire_id !== observedWireId || message.author.identity !== item.sender_id || message.text !== item.text || message.at !== item.date) {
6185
6714
  throw new Error(`inbox source ${item.msg_id} does not match its durable room message`);
6186
6715
  }
6187
6716
  return message;
@@ -6191,7 +6720,7 @@ var init_intake = __esm({
6191
6720
  if (!file) return void 0;
6192
6721
  const observedWireId = item.wire_id === "" ? void 0 : item.wire_id;
6193
6722
  const bytes = Buffer.from(item.data);
6194
- if (file.source_wire_id !== observedWireId || !sameReply(file.source_reply_to, item.reply_to) || file.author.identity !== item.sender_id || file.filename !== item.filename || file.mime !== item.mime || file.at !== item.date || file.size !== bytes.length || file.data_base64 !== bytes.toString("base64")) {
6723
+ if (file.source_wire_id !== observedWireId || file.author.identity !== item.sender_id || file.filename !== item.filename || file.mime !== item.mime || file.at !== item.date || file.size !== bytes.length || file.data_base64 !== bytes.toString("base64")) {
6195
6724
  throw new Error(`file inbox source ${item.file_id} does not match its durable room file`);
6196
6725
  }
6197
6726
  return file;
@@ -6212,7 +6741,7 @@ var init_intake = __esm({
6212
6741
  });
6213
6742
 
6214
6743
  // src/service.ts
6215
- import { createHash as createHash4 } from "node:crypto";
6744
+ import { createHash as createHash3 } from "node:crypto";
6216
6745
  function byteBoundedHistoryPage(records) {
6217
6746
  const page = [];
6218
6747
  let bytes = 2;
@@ -6247,11 +6776,11 @@ function currentContactIdentities(packet) {
6247
6776
  }
6248
6777
  var ROOM_ROLE, CreateInviteInputSchema, HistoryOptionsSchema, DeleteRoomInputSchema, RemoveParticipantInputSchema, ReplaceParticipantInputSchema, RoomServiceError, RoomService;
6249
6778
  var init_service = __esm({
6250
- "src/service.ts"() {
6779
+ async "src/service.ts"() {
6251
6780
  "use strict";
6252
6781
  init_zod();
6253
6782
  init_contracts();
6254
- init_packets();
6783
+ await init_adapt();
6255
6784
  init_intake();
6256
6785
  init_ulid();
6257
6786
  ROOM_ROLE = "room";
@@ -6317,7 +6846,7 @@ var init_service = __esm({
6317
6846
  const settings = CreateRoomInputSchema.parse(input);
6318
6847
  const roomId = LowerCrockfordUlidSchema.parse(this.nextRoomId());
6319
6848
  const roomName = settings.name ?? defaultRoomName(roomId);
6320
- const identityName = roomIdentityName(roomId);
6849
+ const identityName = roomIdentityName(roomName);
6321
6850
  return this.lock(roomId, async () => {
6322
6851
  const provisional = RoomSchema.parse({
6323
6852
  version: 2,
@@ -6330,6 +6859,7 @@ var init_service = __esm({
6330
6859
  identity_cid: "",
6331
6860
  mission: { goal: settings.goal, briefing: settings.briefing, briefing_version: 1 },
6332
6861
  role_briefings: {},
6862
+ rest_roles: [],
6333
6863
  anonymous: settings.anonymous ?? false,
6334
6864
  quiet_membership: settings.quiet_membership ?? false,
6335
6865
  membership_epoch: 0,
@@ -6400,7 +6930,7 @@ var init_service = __esm({
6400
6930
  throw new RoomServiceError(`room packet "${id}" with established CID must be restored, not created`);
6401
6931
  }
6402
6932
  try {
6403
- packet = await this.packets.restore(id, room.identity_cid, room.identity_name);
6933
+ packet = await this.packets.restore(id, room.identity_cid);
6404
6934
  } catch (error) {
6405
6935
  throw new RoomServiceError(
6406
6936
  `failed to restore established room packet "${id}": ${error instanceof Error ? error.message : String(error)}`,
@@ -6499,11 +7029,6 @@ var init_service = __esm({
6499
7029
  return this.lock(id, async () => {
6500
7030
  const room = await this.store.load(id);
6501
7031
  this.assertMutable(room, "create an invite for");
6502
- if (room.invites.some((invite) => invite.state === "live" || invite.state === "receipt_pending")) {
6503
- throw new RoomServiceError(
6504
- "standard SDK rooms permit one live invitation at a time so every accepted contact has unambiguous room admission metadata"
6505
- );
6506
- }
6507
7032
  return this.mintInviteUnlocked(room, {
6508
7033
  mode: request.mode,
6509
7034
  role: request.role ?? DEFAULT_ROLE,
@@ -6521,7 +7046,7 @@ var init_service = __esm({
6521
7046
  } catch {
6522
7047
  throw new RoomServiceError("external invite is invalid or exceeds the 48 KiB decoded limit");
6523
7048
  }
6524
- const digest = createHash4("sha256").update(decoded).digest("hex");
7049
+ const digest = createHash3("sha256").update(decoded).digest("hex");
6525
7050
  const receipt = await this.lock(id, async () => {
6526
7051
  const room = await this.store.load(id);
6527
7052
  this.assertMutable(room, "accept an external invite for");
@@ -6587,11 +7112,6 @@ var init_service = __esm({
6587
7112
  return receipt;
6588
7113
  }
6589
7114
  async mintInviteUnlocked(room, request) {
6590
- if (room.invites.some((invite2) => invite2.state === "live" || invite2.state === "receipt_pending")) {
6591
- throw new RoomServiceError(
6592
- "standard SDK rooms permit one live invitation at a time; revoke or consume the current invitation first"
6593
- );
6594
- }
6595
7115
  const packet = this.packet(room.room_id);
6596
7116
  const minted = await packet.mintInvite(request.mode);
6597
7117
  const invite = RoomInviteSchema.parse({
@@ -6961,7 +7481,7 @@ var init_service = __esm({
6961
7481
  }
6962
7482
  const coreInvite = this.packet(id).listInvites().find((invite) => invite.invite_id === replacementId);
6963
7483
  if (!coreInvite || coreInvite.mode !== replacement.mode) {
6964
- throw new RoomServiceError("recovered invite is no longer present in SDK identity state");
7484
+ throw new RoomServiceError("recovered invite is no longer present in packet state");
6965
7485
  }
6966
7486
  const confirmed2 = {
6967
7487
  invite_id: replacement.invite_id,
@@ -7085,38 +7605,107 @@ var init_service = __esm({
7085
7605
  const id = LowerCrockfordUlidSchema.parse(roomId);
7086
7606
  return this.lock(id, async () => {
7087
7607
  const room = await this.store.load(id);
7088
- if (room.state !== "active") {
7089
- throw new RoomServiceError(`cannot post a room message while room "${id}" is not active`);
7608
+ return this.appendChatUnlocked(id, room, {
7609
+ identity: room.identity_cid,
7610
+ display_name: room.identity_name,
7611
+ role: ROOM_ROLE
7612
+ }, request.text);
7613
+ });
7614
+ }
7615
+ /**
7616
+ * Register a role a REST caller may author under (spec §3.2). Idempotent, like
7617
+ * revokeInvite. Registration does not consult seats: a role is a label, not a
7618
+ * channel, so a role may be held by a seat and be REST-addressable at once.
7619
+ */
7620
+ async addRestRole(roomId, input) {
7621
+ const id = LowerCrockfordUlidSchema.parse(roomId);
7622
+ const request = RestRoleInputSchema.parse(input);
7623
+ if (request.role === ROOM_ROLE) {
7624
+ throw new RoomServiceError(`role "${ROOM_ROLE}" is reserved for the room's own voice`);
7625
+ }
7626
+ return this.lock(id, async () => {
7627
+ const room = await this.store.load(id);
7628
+ this.assertMutable(room, "register a REST role for");
7629
+ if (room.rest_roles.includes(request.role)) return room;
7630
+ return this.store.save(RoomSchema.parse({
7631
+ ...room,
7632
+ rest_roles: [...room.rest_roles, request.role]
7633
+ }));
7634
+ });
7635
+ }
7636
+ /**
7637
+ * Unregister a REST-addressable role. Messages it already authored are left
7638
+ * exactly as posted and no tombstone is kept: there is no secret whose
7639
+ * exposure window an auditor would need to reconstruct (spec §3.2).
7640
+ */
7641
+ async removeRestRole(roomId, input) {
7642
+ const id = LowerCrockfordUlidSchema.parse(roomId);
7643
+ const request = RestRoleInputSchema.parse(input);
7644
+ return this.lock(id, async () => {
7645
+ const room = await this.store.load(id);
7646
+ this.assertMutable(room, "remove a REST role from");
7647
+ if (!room.rest_roles.includes(request.role)) return room;
7648
+ return this.store.save(RoomSchema.parse({
7649
+ ...room,
7650
+ rest_roles: room.rest_roles.filter((role) => role !== request.role)
7651
+ }));
7652
+ });
7653
+ }
7654
+ /**
7655
+ * Author a message under a registered REST role (spec §4). Room-side
7656
+ * authorship: the room packet signs the bytes, so `identity` is the room's CID
7657
+ * and the role name doubles as `display_name` — using the room's own name there
7658
+ * would render as the room impersonating a foreign role.
7659
+ */
7660
+ async postAsRole(roomId, input) {
7661
+ const request = PostAsRoleInputSchema.parse(input);
7662
+ const id = LowerCrockfordUlidSchema.parse(roomId);
7663
+ return this.lock(id, async () => {
7664
+ const room = await this.store.load(id);
7665
+ this.assertPostable(id, room);
7666
+ if (!room.rest_roles.includes(request.role)) {
7667
+ throw new RoomServiceError(`role "${request.role}" is not registered for REST authorship in room "${id}"`);
7090
7668
  }
7091
- const appended = await this.store.append(id, {
7669
+ return this.appendChatUnlocked(id, room, {
7670
+ identity: room.identity_cid,
7671
+ display_name: request.role,
7672
+ role: request.role
7673
+ }, request.text);
7674
+ });
7675
+ }
7676
+ /**
7677
+ * The one operator-side chat append: archive the message, then journal one
7678
+ * relay intent per active seat and resume the pump. Shared by postMessage and
7679
+ * postAsRole so the two authorship paths cannot drift apart — only the author
7680
+ * block differs between them. Caller holds the room lock and supplies the room
7681
+ * it loaded inside that lock.
7682
+ */
7683
+ async appendChatUnlocked(id, room, author, text) {
7684
+ this.assertPostable(id, room);
7685
+ const appended = await this.store.append(id, {
7686
+ version: 1,
7687
+ kind: "message",
7688
+ room_id: id,
7689
+ at: this.now(),
7690
+ message_id: LowerCrockfordUlidSchema.parse(this.nextMessageId()),
7691
+ author,
7692
+ category: "chat",
7693
+ text,
7694
+ recipient_identities: uniqueIdentities(activeSeats(room).map((seat) => seat.identity))
7695
+ });
7696
+ if (appended.kind !== "message") throw new RoomServiceError("storage returned the wrong room message kind");
7697
+ for (const recipientIdentity of appended.recipient_identities) {
7698
+ await this.store.append(id, {
7092
7699
  version: 1,
7093
- kind: "message",
7700
+ kind: "relay_intent",
7094
7701
  room_id: id,
7095
7702
  at: this.now(),
7096
- message_id: LowerCrockfordUlidSchema.parse(this.nextMessageId()),
7097
- author: {
7098
- identity: room.identity_cid,
7099
- display_name: room.identity_name,
7100
- role: ROOM_ROLE
7101
- },
7102
- category: "chat",
7103
- text: request.text,
7104
- recipient_identities: uniqueIdentities(activeSeats(room).map((seat) => seat.identity))
7703
+ message_id: appended.message_id,
7704
+ recipient_identity: recipientIdentity
7105
7705
  });
7106
- if (appended.kind !== "message") throw new RoomServiceError("storage returned the wrong room message kind");
7107
- for (const recipientIdentity of appended.recipient_identities) {
7108
- await this.store.append(id, {
7109
- version: 1,
7110
- kind: "relay_intent",
7111
- room_id: id,
7112
- at: this.now(),
7113
- message_id: appended.message_id,
7114
- recipient_identity: recipientIdentity
7115
- });
7116
- }
7117
- await this.intake.resumePending(id);
7118
- return appended;
7119
- });
7706
+ }
7707
+ await this.intake.resumePending(id);
7708
+ return appended;
7120
7709
  }
7121
7710
  async reconcileUnlocked(room, packet) {
7122
7711
  if (room.state === "closed" || room.state === "closing") return room;
@@ -7124,6 +7713,7 @@ var init_service = __esm({
7124
7713
  for (const contact of packet.listContacts()) {
7125
7714
  if (!contactsByCid.has(contact.container_id)) contactsByCid.set(contact.container_id, contact.name);
7126
7715
  }
7716
+ const origins = packet.listContactOrigins();
7127
7717
  const inviteById = new Map(room.invites.map((invite) => [invite.invite_id, invite]));
7128
7718
  const existingCids = new Set(room.seats.filter((seat) => seat.state === "active" || seat.state === "pending").map((seat) => seat.identity));
7129
7719
  const lastRemovedAt = /* @__PURE__ */ new Map();
@@ -7139,26 +7729,37 @@ var init_service = __esm({
7139
7729
  for (const [seatIndex, seat] of room.seats.entries()) {
7140
7730
  if (seat.state !== "pending") continue;
7141
7731
  const displayName = contactsByCid.get(seat.identity);
7142
- if (displayName === void 0) continue;
7732
+ const origin = origins[seat.identity];
7733
+ if (displayName === void 0 || origin === void 0) continue;
7734
+ const completedExternalRedemption = origin.via === "invite_redeemed" && origin.invite_id === seat.invite_id;
7735
+ const fallbackInvite = origin.via === "invite_one_time" || origin.via === "invite_public" ? inviteById.get(origin.invite_id) : void 0;
7736
+ const confirmedFallback = fallbackInvite !== void 0 && fallbackInvite.state !== "receipt_pending" && (fallbackInvite.recovery_of === void 0 || fallbackInvite.recovery_confirmed === true);
7737
+ if (!completedExternalRedemption && !confirmedFallback) continue;
7738
+ const removedAt = lastRemovedAt.get(seat.identity);
7739
+ if (fallbackInvite !== void 0 && removedAt !== void 0 && fallbackInvite.created_at <= removedAt) continue;
7740
+ const predecessor = fallbackInvite?.replaces_seat === void 0 ? void 0 : room.seats.find((candidate) => candidate.participant_id === fallbackInvite.replaces_seat && candidate.state === "removed");
7741
+ if (fallbackInvite?.replaces_seat !== void 0 && predecessor === void 0) continue;
7143
7742
  const { alias: _alias, replaces_seat: _replacesSeat, ...base } = seat;
7144
7743
  const activated = {
7145
7744
  ...base,
7146
7745
  state: "active",
7147
7746
  display_name: displayName,
7148
- accepted_at: this.now(),
7149
- ..._replacesSeat === void 0 ? {} : { replaces_seat: _replacesSeat },
7150
- ...room.anonymous ? { alias: _alias } : {}
7747
+ accepted_at: origin.at,
7748
+ role: fallbackInvite?.role ?? seat.role,
7749
+ invite_id: fallbackInvite?.invite_id ?? seat.invite_id,
7750
+ ...fallbackInvite === void 0 ? _replacesSeat === void 0 ? {} : { replaces_seat: _replacesSeat } : predecessor === void 0 ? {} : { replaces_seat: predecessor.participant_id },
7751
+ ...room.anonymous ? { alias: fallbackInvite === void 0 ? _alias : predecessor?.alias ?? (fallbackInvite.role === seat.role ? _alias : mintAlias(seatsWithActivations, fallbackInvite.role)) } : {}
7151
7752
  };
7152
7753
  activatedPending.push(activated);
7153
7754
  seatsWithActivations[seatIndex] = activated;
7154
7755
  }
7155
7756
  const newSeats = [];
7156
- const eligibleInvites = room.invites.filter((invite) => invite.state === "live" && (invite.recovery_of === void 0 || invite.recovery_confirmed === true));
7157
- const admissionInvite = eligibleInvites.length === 1 ? eligibleInvites[0] : void 0;
7158
7757
  for (const [cid, displayName] of contactsByCid) {
7159
7758
  if (existingCids.has(cid)) continue;
7160
- const invite = admissionInvite;
7161
- if (!invite) continue;
7759
+ const origin = origins[cid];
7760
+ if (!origin || origin.via !== "invite_one_time" && origin.via !== "invite_public") continue;
7761
+ const invite = inviteById.get(origin.invite_id);
7762
+ if (!invite || invite.state === "receipt_pending" || invite.recovery_of !== void 0 && invite.recovery_confirmed !== true) continue;
7162
7763
  const removedAt = lastRemovedAt.get(cid);
7163
7764
  if (removedAt !== void 0 && invite.created_at <= removedAt) continue;
7164
7765
  const seated = [...seatsWithActivations, ...newSeats];
@@ -7169,7 +7770,7 @@ var init_service = __esm({
7169
7770
  display_name: displayName,
7170
7771
  role: invite.role,
7171
7772
  invite_id: invite.invite_id,
7172
- accepted_at: this.now(),
7773
+ accepted_at: origin.at,
7173
7774
  participant_id: LowerCrockfordUlidSchema.parse(generateUlid()),
7174
7775
  state: "active",
7175
7776
  ...predecessor === void 0 ? {} : { replaces_seat: predecessor.participant_id },
@@ -7420,13 +8021,18 @@ var init_service = __esm({
7420
8021
  if (!packet) throw new RoomServiceError(`room packet "${roomId}" is not hosted`);
7421
8022
  return packet;
7422
8023
  }
8024
+ assertPostable(id, room) {
8025
+ if (room.state !== "active") {
8026
+ throw new RoomServiceError(`cannot post a room message while room "${id}" is not active`);
8027
+ }
8028
+ }
7423
8029
  assertMutable(room, action) {
7424
8030
  if (room.state === "closing" || room.state === "closed") {
7425
8031
  throw new RoomServiceError(`cannot ${action} room "${room.room_id}" while it is ${room.state}`);
7426
8032
  }
7427
8033
  }
7428
8034
  isPacketPending(room) {
7429
- return room.identity_cid === "" && room.state === "provisioning" && room.status === "packet_pending" && room.identity_name === roomIdentityName(room.room_id);
8035
+ return room.identity_cid === "" && room.state === "provisioning" && room.status === "packet_pending" && (room.identity_name === legacyRoomIdentityName(room.room_id) || room.identity_name === roomIdentityName(room.room_name));
7430
8036
  }
7431
8037
  now() {
7432
8038
  return external_exports.string().datetime({ offset: true }).parse(this.nowValue());
@@ -7437,9 +8043,9 @@ var init_service = __esm({
7437
8043
 
7438
8044
  // src/storage.ts
7439
8045
  import { randomBytes as randomBytes3 } from "node:crypto";
7440
- import * as nodeFs2 from "node:fs";
8046
+ import * as nodeFs3 from "node:fs";
7441
8047
  import { AsyncLocalStorage } from "node:async_hooks";
7442
- import { dirname as dirname2, join as join4 } from "node:path";
8048
+ import { dirname as dirname4, join as join4 } from "node:path";
7443
8049
  var DIRECTORY_MODE2, FILE_MODE2, NO_FOLLOW2, utf8Decoder, CoworkStorageError, RoomQueue, CoworkStore;
7444
8050
  var init_storage = __esm({
7445
8051
  "src/storage.ts"() {
@@ -7448,7 +8054,7 @@ var init_storage = __esm({
7448
8054
  init_ulid();
7449
8055
  DIRECTORY_MODE2 = 448;
7450
8056
  FILE_MODE2 = 384;
7451
- NO_FOLLOW2 = nodeFs2.constants.O_NOFOLLOW ?? 0;
8057
+ NO_FOLLOW2 = nodeFs3.constants.O_NOFOLLOW ?? 0;
7452
8058
  utf8Decoder = new TextDecoder("utf-8", { fatal: true });
7453
8059
  CoworkStorageError = class extends Error {
7454
8060
  constructor(message, options) {
@@ -7484,7 +8090,7 @@ var init_storage = __esm({
7484
8090
  constructor(stateDir, options = {}) {
7485
8091
  if (!stateDir) throw new CoworkStorageError("state directory is required");
7486
8092
  this.stateDir = stateDir;
7487
- this.fs = options.fs ?? nodeFs2;
8093
+ this.fs = options.fs ?? nodeFs3;
7488
8094
  }
7489
8095
  mutex(roomId, work) {
7490
8096
  const validRoomId = this.roomId(roomId);
@@ -7617,7 +8223,7 @@ var init_storage = __esm({
7617
8223
  try {
7618
8224
  fd = this.fs.openSync(
7619
8225
  archivePath,
7620
- nodeFs2.constants.O_WRONLY | nodeFs2.constants.O_APPEND | NO_FOLLOW2
8226
+ nodeFs3.constants.O_WRONLY | nodeFs3.constants.O_APPEND | NO_FOLLOW2
7621
8227
  );
7622
8228
  const opened = this.validateOpenPath(fd, archivePath, "room archive", "file", true);
7623
8229
  originalSize = opened.size;
@@ -7832,7 +8438,7 @@ var init_storage = __esm({
7832
8438
  try {
7833
8439
  fd = this.fs.openSync(
7834
8440
  temp,
7835
- nodeFs2.constants.O_CREAT | nodeFs2.constants.O_EXCL | nodeFs2.constants.O_WRONLY | NO_FOLLOW2,
8441
+ nodeFs3.constants.O_CREAT | nodeFs3.constants.O_EXCL | nodeFs3.constants.O_WRONLY | NO_FOLLOW2,
7836
8442
  FILE_MODE2
7837
8443
  );
7838
8444
  this.validateOpenPath(fd, temp, `temporary ${label}`, "file", true);
@@ -7842,7 +8448,7 @@ var init_storage = __esm({
7842
8448
  this.fs.closeSync(fd);
7843
8449
  fd = void 0;
7844
8450
  this.fs.renameSync(temp, path);
7845
- this.fsyncDirectory(dirname2(path));
8451
+ this.fsyncDirectory(dirname4(path));
7846
8452
  } catch (error) {
7847
8453
  if (fd !== void 0) {
7848
8454
  try {
@@ -7938,7 +8544,7 @@ var init_storage = __esm({
7938
8544
  break;
7939
8545
  }
7940
8546
  missing.push(cursor);
7941
- const parent = dirname2(cursor);
8547
+ const parent = dirname4(cursor);
7942
8548
  if (parent === cursor) throw new CoworkStorageError(`cannot find existing parent for ${label}`);
7943
8549
  cursor = parent;
7944
8550
  }
@@ -7949,13 +8555,13 @@ var init_storage = __esm({
7949
8555
  created.push(directory);
7950
8556
  this.fs.chmodSync(directory, DIRECTORY_MODE2);
7951
8557
  this.fsyncDirectory(directory);
7952
- this.fsyncDirectory(dirname2(directory));
8558
+ this.fsyncDirectory(dirname4(directory));
7953
8559
  }
7954
8560
  } catch (error) {
7955
8561
  for (const directory of created.reverse()) {
7956
8562
  try {
7957
8563
  this.fs.rmdirSync(directory);
7958
- this.fsyncDirectory(dirname2(directory));
8564
+ this.fsyncDirectory(dirname4(directory));
7959
8565
  } catch {
7960
8566
  }
7961
8567
  }
@@ -8005,7 +8611,7 @@ var init_storage = __esm({
8005
8611
  try {
8006
8612
  fd = this.fs.openSync(
8007
8613
  path,
8008
- nodeFs2.constants.O_CREAT | nodeFs2.constants.O_EXCL | nodeFs2.constants.O_WRONLY | NO_FOLLOW2,
8614
+ nodeFs3.constants.O_CREAT | nodeFs3.constants.O_EXCL | nodeFs3.constants.O_WRONLY | NO_FOLLOW2,
8009
8615
  FILE_MODE2
8010
8616
  );
8011
8617
  this.validateOpenPath(fd, path, "room archive", "file", true);
@@ -8014,7 +8620,7 @@ var init_storage = __esm({
8014
8620
  } finally {
8015
8621
  if (fd !== void 0) this.fs.closeSync(fd);
8016
8622
  }
8017
- this.fsyncDirectory(dirname2(path));
8623
+ this.fsyncDirectory(dirname4(path));
8018
8624
  }
8019
8625
  atomicMetadataWrite(path, room) {
8020
8626
  if (this.lstatIfPresent(path)) this.assertRegularFile(path, "room metadata");
@@ -8025,7 +8631,7 @@ var init_storage = __esm({
8025
8631
  try {
8026
8632
  fd = this.fs.openSync(
8027
8633
  temp,
8028
- nodeFs2.constants.O_CREAT | nodeFs2.constants.O_EXCL | nodeFs2.constants.O_WRONLY | NO_FOLLOW2,
8634
+ nodeFs3.constants.O_CREAT | nodeFs3.constants.O_EXCL | nodeFs3.constants.O_WRONLY | NO_FOLLOW2,
8029
8635
  FILE_MODE2
8030
8636
  );
8031
8637
  this.validateOpenPath(fd, temp, "temporary room metadata", "file", true);
@@ -8035,7 +8641,7 @@ var init_storage = __esm({
8035
8641
  this.fs.closeSync(fd);
8036
8642
  fd = void 0;
8037
8643
  this.fs.renameSync(temp, path);
8038
- this.fsyncDirectory(dirname2(path));
8644
+ this.fsyncDirectory(dirname4(path));
8039
8645
  } catch (error) {
8040
8646
  if (fd !== void 0) {
8041
8647
  try {
@@ -8061,7 +8667,7 @@ var init_storage = __esm({
8061
8667
  readFileNoFollow(path, label) {
8062
8668
  let fd;
8063
8669
  try {
8064
- fd = this.fs.openSync(path, nodeFs2.constants.O_RDONLY | NO_FOLLOW2);
8670
+ fd = this.fs.openSync(path, nodeFs3.constants.O_RDONLY | NO_FOLLOW2);
8065
8671
  this.validateOpenPath(fd, path, label, "file", true);
8066
8672
  return this.fs.readFileSync(fd);
8067
8673
  } finally {
@@ -8071,7 +8677,7 @@ var init_storage = __esm({
8071
8677
  fsyncDirectory(path) {
8072
8678
  let fd;
8073
8679
  try {
8074
- fd = this.fs.openSync(path, nodeFs2.constants.O_RDONLY | NO_FOLLOW2);
8680
+ fd = this.fs.openSync(path, nodeFs3.constants.O_RDONLY | NO_FOLLOW2);
8075
8681
  this.validateOpenPath(fd, path, "directory fsync target", "directory", false);
8076
8682
  this.fs.fsyncSync(fd);
8077
8683
  } finally {
@@ -8102,33 +8708,775 @@ var init_storage = __esm({
8102
8708
  }
8103
8709
  });
8104
8710
 
8711
+ // src/openapi.ts
8712
+ function params(properties, required) {
8713
+ return { type: "object", additionalProperties: false, properties, required: [...required] };
8714
+ }
8715
+ function schemaBaseName(method) {
8716
+ return method.split(".").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
8717
+ }
8718
+ function errorResponseSpec(description, codes) {
8719
+ return {
8720
+ description,
8721
+ content: {
8722
+ "application/json": {
8723
+ schema: { $ref: "#/components/schemas/RpcErrorResponse" },
8724
+ example: {
8725
+ version: 1,
8726
+ id: "req-1",
8727
+ error: { code: codes[0], message: "see the daemon response for detail" }
8728
+ }
8729
+ }
8730
+ }
8731
+ };
8732
+ }
8733
+ function documentResponseSpec(description, mediaType) {
8734
+ return { description, content: { [mediaType]: { schema: { type: "string" } } } };
8735
+ }
8736
+ function buildOpenApiDocument() {
8737
+ const schemas = {
8738
+ RpcId: {
8739
+ description: "Caller-chosen correlation id echoed in the response.",
8740
+ oneOf: [
8741
+ { type: "string", minLength: 1, maxLength: 256 },
8742
+ { type: "integer", minimum: 0 }
8743
+ ]
8744
+ },
8745
+ RpcError: {
8746
+ type: "object",
8747
+ additionalProperties: false,
8748
+ required: ["code", "message"],
8749
+ properties: {
8750
+ code: {
8751
+ type: "string",
8752
+ enum: RPC_ERROR_CODES.map(([code]) => code),
8753
+ description: RPC_ERROR_CODES.map(([code, text]) => `\`${code}\`: ${text}`).join(" ")
8754
+ },
8755
+ message: { type: "string", description: "Human-readable detail. Not machine-parsed." }
8756
+ }
8757
+ },
8758
+ RpcSuccessResponse: {
8759
+ type: "object",
8760
+ additionalProperties: false,
8761
+ required: ["version", "id", "result"],
8762
+ properties: {
8763
+ version: { type: "integer", const: 1 },
8764
+ id: { $ref: "#/components/schemas/RpcId" },
8765
+ result: {
8766
+ description: "Method-specific result. See the individual method descriptions."
8767
+ }
8768
+ }
8769
+ },
8770
+ RpcErrorResponse: {
8771
+ type: "object",
8772
+ additionalProperties: false,
8773
+ required: ["version", "id", "error"],
8774
+ properties: {
8775
+ version: { type: "integer", const: 1 },
8776
+ id: {
8777
+ description: "The request id, or null when it could not be recovered from the body.",
8778
+ oneOf: [{ $ref: "#/components/schemas/RpcId" }, { type: "null" }]
8779
+ },
8780
+ error: { $ref: "#/components/schemas/RpcError" }
8781
+ }
8782
+ }
8783
+ };
8784
+ const requestSchemaNames = [];
8785
+ const discriminatorMapping = {};
8786
+ for (const entry of ROOM_RPC_METHODS) {
8787
+ const base = schemaBaseName(entry.method);
8788
+ const paramsName = `${base}Params`;
8789
+ const requestName = `${base}Request`;
8790
+ schemas[paramsName] = { ...entry.params, description: `Parameters for \`${entry.method}\`.` };
8791
+ schemas[requestName] = {
8792
+ type: "object",
8793
+ additionalProperties: false,
8794
+ required: ["version", "id", "method", "params"],
8795
+ title: entry.summary,
8796
+ description: `${entry.description}
8797
+
8798
+ Result: ${entry.result}`,
8799
+ properties: {
8800
+ version: { type: "integer", const: 1 },
8801
+ id: { $ref: "#/components/schemas/RpcId" },
8802
+ method: { type: "string", const: entry.method },
8803
+ params: { $ref: `#/components/schemas/${paramsName}` }
8804
+ },
8805
+ examples: [{ version: 1, id: "req-1", method: entry.method, params: entry.example }]
8806
+ };
8807
+ requestSchemaNames.push(requestName);
8808
+ discriminatorMapping[entry.method] = `#/components/schemas/${requestName}`;
8809
+ }
8810
+ return {
8811
+ openapi: "3.1.0",
8812
+ info: {
8813
+ title: "ours-cowork room management API",
8814
+ version: API_VERSION,
8815
+ summary: "Loopback REST control surface for cowork mission rooms.",
8816
+ description: [
8817
+ 'Every room-management operation is carried by a single REST route, `POST /rpc`, using the versioned RPC envelope `{ "version": 1, "id": ..., "method": ..., "params": ... }`. The `method` field selects the operation; the request schemas below are discriminated on it.',
8818
+ "",
8819
+ "The listener binds `127.0.0.1` only and has no authentication, so it accepts requests whose `Host` header is `127.0.0.1:<port>` or `localhost:<port>`, rejects cross-site fetches, and must not be exposed to other hosts through forwarding or a proxy.",
8820
+ "",
8821
+ "Requests must declare `Content-Type: application/json` and stay within 1 MiB. Operations that need an invite secret are not served here: they are reachable only over the daemon's private Unix socket."
8822
+ ].join("\n"),
8823
+ license: { name: "FSL-1.1-Apache-2.0" }
8824
+ },
8825
+ servers: [{ url: "/", description: "This cowork daemon, on its loopback REST port." }],
8826
+ tags: [
8827
+ { name: "rooms", description: "Room management over the RPC envelope." },
8828
+ { name: "documentation", description: "The API description and its browser UI." }
8829
+ ],
8830
+ paths: {
8831
+ [RPC_PATH]: {
8832
+ post: {
8833
+ tags: ["rooms"],
8834
+ operationId: "roomManagementRpc",
8835
+ summary: "Invoke a room-management method",
8836
+ description: "Dispatches one room-management method. The HTTP status reflects the envelope outcome: 200 on success, 404 when the method is not served over REST, 500 on an internal failure, and 400 for every other error code \u2014 including `not_found`.",
8837
+ requestBody: {
8838
+ required: true,
8839
+ content: {
8840
+ "application/json": {
8841
+ schema: {
8842
+ oneOf: requestSchemaNames.map((name) => ({ $ref: `#/components/schemas/${name}` })),
8843
+ discriminator: { propertyName: "method", mapping: discriminatorMapping }
8844
+ }
8845
+ }
8846
+ }
8847
+ },
8848
+ responses: {
8849
+ 200: {
8850
+ description: "The method completed. `result` is method-specific.",
8851
+ content: {
8852
+ "application/json": {
8853
+ schema: { $ref: "#/components/schemas/RpcSuccessResponse" }
8854
+ }
8855
+ }
8856
+ },
8857
+ 400: errorResponseSpec(
8858
+ "The envelope, parameters, or room state rejected the call.",
8859
+ [
8860
+ "invalid_params",
8861
+ "invalid_json",
8862
+ "invalid_request",
8863
+ "not_found",
8864
+ "invalid_state",
8865
+ "shutting_down"
8866
+ ]
8867
+ ),
8868
+ 403: errorResponseSpec(
8869
+ "The Host header was not a loopback console authority, the Origin did not match it, or the fetch was cross-site.",
8870
+ ["forbidden"]
8871
+ ),
8872
+ 404: errorResponseSpec(
8873
+ "The method is not part of the REST route table.",
8874
+ ["method_not_found"]
8875
+ ),
8876
+ 413: errorResponseSpec("The request body exceeded 1 MiB.", ["request_too_large"]),
8877
+ 415: errorResponseSpec(
8878
+ "The request did not declare `application/json`.",
8879
+ ["unsupported_media_type"]
8880
+ ),
8881
+ 500: errorResponseSpec("The daemon failed to complete the operation.", ["internal"])
8882
+ }
8883
+ }
8884
+ },
8885
+ [OPENAPI_DOCUMENT_PATH]: {
8886
+ get: {
8887
+ tags: ["documentation"],
8888
+ operationId: "getOpenApiDocument",
8889
+ summary: "Fetch this OpenAPI document",
8890
+ description: "Returns the OpenAPI 3.1 description of the room-management REST API.",
8891
+ responses: {
8892
+ 200: {
8893
+ description: "The OpenAPI document.",
8894
+ content: { "application/json": { schema: { type: "object" } } }
8895
+ }
8896
+ }
8897
+ }
8898
+ },
8899
+ [API_DOCS_PATH]: {
8900
+ get: {
8901
+ tags: ["documentation"],
8902
+ operationId: "getApiDocsPage",
8903
+ summary: "Open the API documentation UI",
8904
+ description: `Serves the browser UI that renders \`${OPENAPI_DOCUMENT_PATH}\`.`,
8905
+ responses: { 200: documentResponseSpec("The documentation page.", "text/html") }
8906
+ }
8907
+ },
8908
+ [API_DOCS_SCRIPT_PATH]: {
8909
+ get: {
8910
+ tags: ["documentation"],
8911
+ operationId: "getApiDocsScript",
8912
+ summary: "Fetch the documentation UI script",
8913
+ responses: { 200: documentResponseSpec("The UI script.", "text/javascript") }
8914
+ }
8915
+ },
8916
+ [API_DOCS_STYLESHEET_PATH]: {
8917
+ get: {
8918
+ tags: ["documentation"],
8919
+ operationId: "getApiDocsStylesheet",
8920
+ summary: "Fetch the documentation UI stylesheet",
8921
+ responses: { 200: documentResponseSpec("The UI stylesheet.", "text/css") }
8922
+ }
8923
+ }
8924
+ },
8925
+ components: { schemas }
8926
+ };
8927
+ }
8928
+ function apiDocsAsset(pathname) {
8929
+ switch (pathname) {
8930
+ case OPENAPI_DOCUMENT_PATH:
8931
+ return {
8932
+ body: `${JSON.stringify(buildOpenApiDocument(), null, 2)}
8933
+ `,
8934
+ contentType: "application/json; charset=utf-8"
8935
+ };
8936
+ case API_DOCS_PATH:
8937
+ return { body: DOCS_PAGE, contentType: "text/html; charset=utf-8" };
8938
+ case API_DOCS_SCRIPT_PATH:
8939
+ return { body: DOCS_SCRIPT, contentType: "text/javascript; charset=utf-8" };
8940
+ case API_DOCS_STYLESHEET_PATH:
8941
+ return { body: DOCS_STYLESHEET, contentType: "text/css; charset=utf-8" };
8942
+ default:
8943
+ return void 0;
8944
+ }
8945
+ }
8946
+ var RPC_PATH, OPENAPI_DOCUMENT_PATH, API_DOCS_PATH, API_DOCS_SCRIPT_PATH, API_DOCS_STYLESHEET_PATH, API_VERSION, EXAMPLE_ROOM_ID, roomIdProperty, roleProperty, restRoleProperty, missionTextProperty, roomNameProperty, inviteModeProperty, notifyProperty, ROOM_RPC_METHODS, RPC_ERROR_CODES, DOCS_PAGE, DOCS_STYLESHEET, DOCS_SCRIPT;
8947
+ var init_openapi = __esm({
8948
+ "src/openapi.ts"() {
8949
+ "use strict";
8950
+ RPC_PATH = "/rpc";
8951
+ OPENAPI_DOCUMENT_PATH = "/openapi.json";
8952
+ API_DOCS_PATH = "/docs";
8953
+ API_DOCS_SCRIPT_PATH = "/docs/ui.js";
8954
+ API_DOCS_STYLESHEET_PATH = "/docs/ui.css";
8955
+ API_VERSION = "1";
8956
+ EXAMPLE_ROOM_ID = "01jd7q4h9m2v8xk3znbc5regty";
8957
+ roomIdProperty = {
8958
+ type: "string",
8959
+ pattern: "^[0-7][0-9a-hjkmnp-tv-z]{25}$",
8960
+ description: "26-character lowercase Crockford ULID identifying the room."
8961
+ };
8962
+ roleProperty = {
8963
+ type: "string",
8964
+ description: "Invite role. At most 256 UTF-8 bytes."
8965
+ };
8966
+ restRoleProperty = {
8967
+ type: "string",
8968
+ description: "REST-addressable role name, matched by exact bytes. At most 256 UTF-8 bytes. The name `room` is reserved for the room's own voice."
8969
+ };
8970
+ missionTextProperty = (what) => ({
8971
+ type: "string",
8972
+ description: `${what}. At most 262144 UTF-8 bytes.`
8973
+ });
8974
+ roomNameProperty = {
8975
+ type: "string",
8976
+ description: "Friendly room name: 1-64 Unicode characters after trimming and NFC normalization, with no Unicode control or format characters. Duplicates are allowed."
8977
+ };
8978
+ inviteModeProperty = {
8979
+ type: "string",
8980
+ enum: ["one_time", "public"],
8981
+ description: "Invitation mode. `one_time` requires `min_accepts` to be 1."
8982
+ };
8983
+ notifyProperty = {
8984
+ type: "boolean",
8985
+ description: "Announce the membership change to the room. Defaults to the room's quiet setting."
8986
+ };
8987
+ ROOM_RPC_METHODS = [
8988
+ {
8989
+ method: "room.create",
8990
+ summary: "Create a room",
8991
+ description: "Provisions a room, its durable metadata, and its room packet. The room stays in the provisioning state until its invitation requirements are satisfied.",
8992
+ params: params({
8993
+ name: roomNameProperty,
8994
+ goal: missionTextProperty("Mission goal"),
8995
+ briefing: missionTextProperty("Mission briefing"),
8996
+ anonymous: {
8997
+ type: "boolean",
8998
+ description: "Project participants under aliases instead of contact identities."
8999
+ },
9000
+ quiet_membership: {
9001
+ type: "boolean",
9002
+ description: "Suppress membership announcements for this room by default."
9003
+ }
9004
+ }, ["goal", "briefing"]),
9005
+ result: "The created room record, including `room_id`, `room_name`, `state`, mission, invites, and seats.",
9006
+ example: { goal: "Ship the release", briefing: "Coordinate the 1.0 cut.", name: "Release" }
9007
+ },
9008
+ {
9009
+ method: "room.settings",
9010
+ summary: "Update room settings",
9011
+ description: "Updates mutable room settings. At least one setting besides `room_id` is required; omitted settings are left unchanged.",
9012
+ params: params({
9013
+ room_id: roomIdProperty,
9014
+ name: roomNameProperty,
9015
+ goal: missionTextProperty("Replacement mission goal"),
9016
+ briefing: missionTextProperty("Replacement mission briefing"),
9017
+ status: { type: "string", minLength: 1, description: "Operator status line." },
9018
+ quiet_membership: {
9019
+ type: "boolean",
9020
+ description: "Suppress membership announcements for this room."
9021
+ }
9022
+ }, ["room_id"]),
9023
+ result: "The updated room record.",
9024
+ example: { room_id: EXAMPLE_ROOM_ID, name: "Release cut" }
9025
+ },
9026
+ {
9027
+ method: "room.briefing.role.set",
9028
+ summary: "Set a role briefing",
9029
+ description: "Creates or replaces the briefing for one role and bumps its briefing version.",
9030
+ params: params({
9031
+ room_id: roomIdProperty,
9032
+ role: roleProperty,
9033
+ text: missionTextProperty("Role briefing text")
9034
+ }, ["room_id", "role", "text"]),
9035
+ result: "The updated room record with the stored role briefing.",
9036
+ example: { room_id: EXAMPLE_ROOM_ID, role: "Reviewer", text: "Review every merge request." }
9037
+ },
9038
+ {
9039
+ method: "room.briefing.role.delete",
9040
+ summary: "Delete a role briefing",
9041
+ description: "Removes the briefing for one role.",
9042
+ params: params({ room_id: roomIdProperty, role: roleProperty }, ["room_id", "role"]),
9043
+ result: "The updated room record without the removed role briefing.",
9044
+ example: { room_id: EXAMPLE_ROOM_ID, role: "Reviewer" }
9045
+ },
9046
+ {
9047
+ method: "room.invite",
9048
+ summary: "Mint an invitation requirement",
9049
+ description: "Mints one invitation requirement and returns its receipt. The invite secret is returned once in `blob` and is never stored in room metadata or replayed by any later call.",
9050
+ params: params({
9051
+ room_id: roomIdProperty,
9052
+ mode: inviteModeProperty,
9053
+ role: roleProperty,
9054
+ min_accepts: {
9055
+ type: "integer",
9056
+ minimum: 1,
9057
+ description: "Acceptances required before the requirement is satisfied. Must be 1 for `one_time` invites."
9058
+ }
9059
+ }, ["room_id", "mode", "min_accepts"]),
9060
+ result: "An invite receipt: `room_id`, the recorded `invite`, the one-shot `blob` secret, and `reusable`.",
9061
+ example: { room_id: EXAMPLE_ROOM_ID, mode: "one_time", role: "Reviewer", min_accepts: 1 }
9062
+ },
9063
+ {
9064
+ method: "room.participant.remove",
9065
+ summary: "Remove a participant",
9066
+ description: "Removes one participant seat, bumps the membership epoch, and severs the contact. The receipt reports what actually happened, including retained key material.",
9067
+ params: params({
9068
+ room_id: roomIdProperty,
9069
+ participant: {
9070
+ type: "string",
9071
+ minLength: 1,
9072
+ description: "Participant id, identity, display name, or alias."
9073
+ },
9074
+ notify: notifyProperty
9075
+ }, ["room_id", "participant"]),
9076
+ result: "A removal receipt with `participant_id`, `epoch`, `status`, `notified`, and `key_material_retained`.",
9077
+ example: { room_id: EXAMPLE_ROOM_ID, participant: "Reviewer-1", notify: true }
9078
+ },
9079
+ {
9080
+ method: "room.participant.replace",
9081
+ summary: "Replace a participant",
9082
+ description: "Removes one participant seat and mints a replacement invitation requirement in the same operation.",
9083
+ params: params({
9084
+ room_id: roomIdProperty,
9085
+ participant: {
9086
+ type: "string",
9087
+ minLength: 1,
9088
+ description: "Participant id, identity, display name, or alias."
9089
+ },
9090
+ notify: notifyProperty,
9091
+ mode: inviteModeProperty,
9092
+ min_accepts: {
9093
+ type: "integer",
9094
+ minimum: 1,
9095
+ description: "Acceptances required for the replacement invitation requirement."
9096
+ }
9097
+ }, ["room_id", "participant"]),
9098
+ result: "An invite receipt for the replacement, extended with the `removal` receipt.",
9099
+ example: { room_id: EXAMPLE_ROOM_ID, participant: "Reviewer-1", mode: "one_time" }
9100
+ },
9101
+ {
9102
+ method: "room.revoke",
9103
+ summary: "Revoke an invitation requirement",
9104
+ description: "Revokes one live invitation requirement so it can no longer admit a seat.",
9105
+ params: params({
9106
+ room_id: roomIdProperty,
9107
+ invite_id: { type: "string", minLength: 1, description: "Recorded invite identifier." }
9108
+ }, ["room_id", "invite_id"]),
9109
+ result: "The updated room record with the invite marked revoked.",
9110
+ example: { room_id: EXAMPLE_ROOM_ID, invite_id: "inv-01" }
9111
+ },
9112
+ {
9113
+ method: "room.recover",
9114
+ summary: "Recover unusable invitations",
9115
+ description: "Mints recovery invitations for requirements whose secret can no longer admit a seat. Each recovery must be confirmed before it replaces the original.",
9116
+ params: params({ room_id: roomIdProperty }, ["room_id"]),
9117
+ result: "The list of recovery invite receipts, each carrying `recovery_of`.",
9118
+ example: { room_id: EXAMPLE_ROOM_ID }
9119
+ },
9120
+ {
9121
+ method: "room.recover.confirm",
9122
+ summary: "Confirm a recovery invitation",
9123
+ description: "Confirms that a recovery invitation was handed over, retiring the invitation requirement it recovers.",
9124
+ params: params({
9125
+ room_id: roomIdProperty,
9126
+ recovery_of: { type: "string", minLength: 1, description: "Invite id being recovered." },
9127
+ invite_id: { type: "string", minLength: 1, description: "Recovery invite id to confirm." }
9128
+ }, ["room_id", "recovery_of", "invite_id"]),
9129
+ result: "The updated room record with the confirmed recovery.",
9130
+ example: { room_id: EXAMPLE_ROOM_ID, recovery_of: "inv-01", invite_id: "inv-02" }
9131
+ },
9132
+ {
9133
+ method: "room.list",
9134
+ summary: "List rooms",
9135
+ description: "Lists every room held on this host. Takes no parameters.",
9136
+ params: params({}, []),
9137
+ result: "An array of room records.",
9138
+ example: {}
9139
+ },
9140
+ {
9141
+ method: "room.show",
9142
+ summary: "Show one room",
9143
+ description: "Returns the complete durable record for one room.",
9144
+ params: params({ room_id: roomIdProperty }, ["room_id"]),
9145
+ result: "The room record.",
9146
+ example: { room_id: EXAMPLE_ROOM_ID }
9147
+ },
9148
+ {
9149
+ method: "room.participants",
9150
+ summary: "List participants",
9151
+ description: "Returns the room's seats, including pending, active, and removed states.",
9152
+ params: params({ room_id: roomIdProperty }, ["room_id"]),
9153
+ result: "An array of seat records.",
9154
+ example: { room_id: EXAMPLE_ROOM_ID }
9155
+ },
9156
+ {
9157
+ method: "room.history",
9158
+ summary: "Read room history",
9159
+ description: "Reads one page of the room's ordered communication archive. The operator view returns every record kind; the participant view returns message records only, with routing identities removed and authors reduced to alias form in anonymous rooms.",
9160
+ params: params({
9161
+ room_id: roomIdProperty,
9162
+ after: {
9163
+ type: "integer",
9164
+ minimum: 0,
9165
+ description: "Return records with a sequence number greater than this value."
9166
+ },
9167
+ limit: { type: "integer", minimum: 1, description: "Maximum number of records to return." },
9168
+ view: {
9169
+ type: "string",
9170
+ enum: ["operator", "participant"],
9171
+ description: "History projection. Defaults to the operator view."
9172
+ }
9173
+ }, ["room_id"]),
9174
+ result: "An array of communication records, byte-bounded to one page.",
9175
+ example: { room_id: EXAMPLE_ROOM_ID, after: 0, limit: 50, view: "operator" }
9176
+ },
9177
+ {
9178
+ method: "room.message",
9179
+ summary: "Post an operator message",
9180
+ description: "Appends an operator message to the room archive and relays it to active seats. Authorship is assigned by the daemon.",
9181
+ params: params({
9182
+ room_id: roomIdProperty,
9183
+ text: missionTextProperty("Message text")
9184
+ }, ["room_id", "text"]),
9185
+ result: "The appended message record with its `seq` and `record_id`.",
9186
+ example: { room_id: EXAMPLE_ROOM_ID, text: "Standup at 10:00." }
9187
+ },
9188
+ {
9189
+ method: "room.say",
9190
+ summary: "Post a message as a registered role",
9191
+ description: "Appends a message authored under one of the room's REST-addressable roles and relays it to active seats. The message carries the room's identity and the role as its label; the role must already be registered with `room.role.rest.add`. A room with no active seats accepts the message and relays it to nobody.",
9192
+ params: params({
9193
+ room_id: roomIdProperty,
9194
+ role: restRoleProperty,
9195
+ text: missionTextProperty("Message text")
9196
+ }, ["room_id", "role", "text"]),
9197
+ result: "The appended message record with its `seq` and `record_id`.",
9198
+ example: { room_id: EXAMPLE_ROOM_ID, role: "Reviewer", text: "Reviewed the release diff." }
9199
+ },
9200
+ {
9201
+ method: "room.role.rest.add",
9202
+ summary: "Register a REST-addressable role",
9203
+ description: "Makes a role available to `room.say`. Registering an already-registered role changes nothing. The name `room` is reserved for the room's own voice. A role may also be held by a participant: a role is a display label, not a channel.",
9204
+ params: params({ room_id: roomIdProperty, role: restRoleProperty }, ["room_id", "role"]),
9205
+ result: "The updated room record, including `rest_roles`.",
9206
+ example: { room_id: EXAMPLE_ROOM_ID, role: "Reviewer" }
9207
+ },
9208
+ {
9209
+ method: "room.role.rest.remove",
9210
+ summary: "Unregister a REST-addressable role",
9211
+ description: "Stops accepting `room.say` for this role. Removing a role that is not registered changes nothing, and messages the role already authored are left untouched.",
9212
+ params: params({ room_id: roomIdProperty, role: restRoleProperty }, ["room_id", "role"]),
9213
+ result: "The updated room record, including `rest_roles`.",
9214
+ example: { room_id: EXAMPLE_ROOM_ID, role: "Reviewer" }
9215
+ },
9216
+ {
9217
+ method: "room.close",
9218
+ summary: "Close a room",
9219
+ description: "Closes the room forward-only. The plaintext local archive is left in place.",
9220
+ params: params({ room_id: roomIdProperty }, ["room_id"]),
9221
+ result: "The closed room record.",
9222
+ example: { room_id: EXAMPLE_ROOM_ID }
9223
+ },
9224
+ {
9225
+ method: "room.delete",
9226
+ summary: "Delete a room",
9227
+ description: "Removes this host's local room state after the room is closed. The scope is this host only.",
9228
+ params: params({
9229
+ room_id: roomIdProperty,
9230
+ confirm: { type: "boolean", const: true, description: "Must be `true`." }
9231
+ }, ["room_id", "confirm"]),
9232
+ result: 'A delete receipt: `{ "version": 1, "room_id": "...", "deleted": true, "scope": "this_host" }`.',
9233
+ example: { room_id: EXAMPLE_ROOM_ID, confirm: true }
9234
+ }
9235
+ ];
9236
+ RPC_ERROR_CODES = [
9237
+ ["invalid_json", "The request body was not valid JSON."],
9238
+ ["invalid_request", "The RPC envelope did not match the strict envelope schema."],
9239
+ ["invalid_params", "The `params` object failed method validation."],
9240
+ ["not_found", "The referenced room or record does not exist."],
9241
+ ["invalid_state", "The room is not in a state that permits the operation."],
9242
+ ["shutting_down", "The daemon stopped accepting work."],
9243
+ ["method_not_found", "The method is not served over REST."],
9244
+ ["unsupported_media_type", "The request did not declare `application/json`."],
9245
+ ["forbidden", "The request origin or Host header was not the loopback console origin."],
9246
+ ["request_too_large", "The request body exceeded 1 MiB."],
9247
+ ["internal", "The daemon failed to complete the operation."]
9248
+ ];
9249
+ DOCS_PAGE = `<!DOCTYPE html>
9250
+ <html lang="en">
9251
+ <head>
9252
+ <meta charset="utf-8">
9253
+ <meta name="viewport" content="width=device-width, initial-scale=1">
9254
+ <title>ours-cowork room management API</title>
9255
+ <link rel="stylesheet" href="${API_DOCS_STYLESHEET_PATH}">
9256
+ </head>
9257
+ <body>
9258
+ <main id="api-docs" data-document="${OPENAPI_DOCUMENT_PATH}" data-rpc="${RPC_PATH}">
9259
+ <p class="loading">Loading <code>${OPENAPI_DOCUMENT_PATH}</code>&hellip;</p>
9260
+ </main>
9261
+ <script src="${API_DOCS_SCRIPT_PATH}"></script>
9262
+ </body>
9263
+ </html>
9264
+ `;
9265
+ DOCS_STYLESHEET = `:root { color-scheme: light dark; --line: #8883; --accent: #2563eb; }
9266
+ * { box-sizing: border-box; }
9267
+ body { margin: 0; font: 15px/1.5 ui-sans-serif, system-ui, sans-serif; }
9268
+ main { margin: 0 auto; max-width: 60rem; padding: 2rem 1.25rem 4rem; }
9269
+ h1 { font-size: 1.6rem; margin: 0 0 .25rem; }
9270
+ h2 { font-size: 1.15rem; margin: 2rem 0 .5rem; }
9271
+ code, pre, textarea { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .86em; }
9272
+ pre { background: #8881; border-radius: 6px; margin: 0; overflow-x: auto; padding: .6rem .75rem; }
9273
+ .subtitle { color: #8889; margin: 0 0 1rem; }
9274
+ .intro { border-left: 3px solid var(--accent); padding-left: .9rem; white-space: pre-wrap; }
9275
+ .route { border: 1px solid var(--line); border-radius: 8px; margin-bottom: .5rem; padding: .6rem .8rem; }
9276
+ .verb { background: var(--accent); border-radius: 4px; color: #fff; font-size: .72rem;
9277
+ font-weight: 700; letter-spacing: .04em; margin-right: .5rem; padding: .12rem .4rem; }
9278
+ details.method { border: 1px solid var(--line); border-radius: 8px; margin-bottom: .5rem; }
9279
+ details.method > summary { cursor: pointer; padding: .6rem .8rem; }
9280
+ details.method[open] > summary { border-bottom: 1px solid var(--line); }
9281
+ summary .name { font-family: ui-monospace, monospace; font-weight: 600; }
9282
+ summary .summary-text { color: #8889; margin-left: .6rem; }
9283
+ .body { padding: .8rem; }
9284
+ .description { margin: 0 0 .8rem; white-space: pre-wrap; }
9285
+ table { border-collapse: collapse; margin-bottom: .8rem; width: 100%; }
9286
+ th, td { border-bottom: 1px solid var(--line); padding: .35rem .5rem; text-align: left;
9287
+ vertical-align: top; }
9288
+ th { font-size: .78rem; letter-spacing: .04em; text-transform: uppercase; }
9289
+ td.param { font-family: ui-monospace, monospace; white-space: nowrap; }
9290
+ .required { color: #b91c1c; font-size: .75rem; margin-left: .3rem; }
9291
+ textarea { border: 1px solid var(--line); border-radius: 6px; padding: .5rem; width: 100%; }
9292
+ button { background: var(--accent); border: 0; border-radius: 6px; color: #fff; cursor: pointer;
9293
+ font: inherit; margin: .5rem 0; padding: .4rem 1rem; }
9294
+ .status { font-weight: 600; margin: .5rem 0 .35rem; }
9295
+ .status.failed { color: #b91c1c; }
9296
+ .error { color: #b91c1c; }
9297
+ `;
9298
+ DOCS_SCRIPT = `'use strict';
9299
+ (function () {
9300
+ var root = document.getElementById('api-docs');
9301
+ var documentUrl = root.dataset.document;
9302
+ var rpcUrl = root.dataset.rpc;
9303
+
9304
+ function element(tag, className, text) {
9305
+ var node = document.createElement(tag);
9306
+ if (className) node.className = className;
9307
+ if (text !== undefined) node.textContent = text;
9308
+ return node;
9309
+ }
9310
+
9311
+ function resolveRef(spec, ref) {
9312
+ return ref.replace(/^#\\//, '').split('/').reduce(function (node, part) {
9313
+ return node === undefined ? undefined : node[part];
9314
+ }, spec);
9315
+ }
9316
+
9317
+ function typeLabel(schema) {
9318
+ if (!schema) return 'any';
9319
+ if (schema.const !== undefined) return JSON.stringify(schema.const);
9320
+ if (schema.enum) return schema.enum.map(function (value) { return JSON.stringify(value); }).join(' | ');
9321
+ return schema.type || 'any';
9322
+ }
9323
+
9324
+ function parameterTable(schema) {
9325
+ var properties = (schema && schema.properties) || {};
9326
+ var names = Object.keys(properties);
9327
+ if (names.length === 0) return element('p', 'description', 'No parameters.');
9328
+ var required = (schema && schema.required) || [];
9329
+ var table = document.createElement('table');
9330
+ var head = table.insertRow();
9331
+ ['Parameter', 'Type', 'Description'].forEach(function (label) {
9332
+ head.appendChild(element('th', null, label));
9333
+ });
9334
+ names.forEach(function (name) {
9335
+ var property = properties[name];
9336
+ var row = table.insertRow();
9337
+ var cell = element('td', 'param', name);
9338
+ if (required.indexOf(name) >= 0) cell.appendChild(element('span', 'required', 'required'));
9339
+ row.appendChild(cell);
9340
+ row.appendChild(element('td', null, typeLabel(property)));
9341
+ row.appendChild(element('td', null, property.description || ''));
9342
+ });
9343
+ return table;
9344
+ }
9345
+
9346
+ function tryItPanel(example) {
9347
+ var panel = document.createElement('div');
9348
+ var input = document.createElement('textarea');
9349
+ input.rows = Math.min(14, JSON.stringify(example, null, 2).split('\\n').length + 1);
9350
+ input.value = JSON.stringify(example, null, 2);
9351
+ input.setAttribute('aria-label', 'Request body');
9352
+ var send = element('button', null, 'Send request');
9353
+ var output = document.createElement('div');
9354
+ send.addEventListener('click', function () {
9355
+ var body = input.value;
9356
+ try { JSON.parse(body); } catch (error) {
9357
+ output.textContent = '';
9358
+ output.appendChild(element('p', 'status failed', 'Request body is not valid JSON.'));
9359
+ return;
9360
+ }
9361
+ send.disabled = true;
9362
+ output.textContent = '';
9363
+ output.appendChild(element('p', 'status', 'Sending\\u2026'));
9364
+ fetch(rpcUrl, {
9365
+ method: 'POST',
9366
+ headers: { 'content-type': 'application/json' },
9367
+ body: body,
9368
+ }).then(function (response) {
9369
+ return response.text().then(function (text) { return { status: response.status, text: text }; });
9370
+ }).then(function (result) {
9371
+ var pretty = result.text;
9372
+ try { pretty = JSON.stringify(JSON.parse(result.text), null, 2); } catch (error) { /* raw */ }
9373
+ output.textContent = '';
9374
+ output.appendChild(element(
9375
+ 'p',
9376
+ result.status === 200 ? 'status' : 'status failed',
9377
+ 'HTTP ' + result.status
9378
+ ));
9379
+ output.appendChild(element('pre', null, pretty));
9380
+ }).catch(function (error) {
9381
+ output.textContent = '';
9382
+ output.appendChild(element('p', 'status failed', 'Request failed: ' + error));
9383
+ }).then(function () { send.disabled = false; });
9384
+ });
9385
+ panel.appendChild(element('h3', null, 'Try it'));
9386
+ panel.appendChild(input);
9387
+ panel.appendChild(send);
9388
+ panel.appendChild(output);
9389
+ return panel;
9390
+ }
9391
+
9392
+ function methodPanel(spec, ref) {
9393
+ var schema = resolveRef(spec, ref);
9394
+ var method = schema.properties.method.const;
9395
+ var panel = element('details', 'method');
9396
+ var summary = document.createElement('summary');
9397
+ summary.appendChild(element('span', 'name', method));
9398
+ summary.appendChild(element('span', 'summary-text', schema.title || ''));
9399
+ panel.appendChild(summary);
9400
+ var body = element('div', 'body');
9401
+ body.appendChild(element('p', 'description', schema.description || ''));
9402
+ body.appendChild(parameterTable(resolveRef(spec, schema.properties.params.$ref)));
9403
+ body.appendChild(tryItPanel((schema.examples && schema.examples[0]) || {
9404
+ version: 1, id: 'req-1', method: method, params: {},
9405
+ }));
9406
+ panel.appendChild(body);
9407
+ return panel;
9408
+ }
9409
+
9410
+ function renderRoutes(spec) {
9411
+ var section = document.createDocumentFragment();
9412
+ Object.keys(spec.paths).forEach(function (path) {
9413
+ Object.keys(spec.paths[path]).forEach(function (verb) {
9414
+ var operation = spec.paths[path][verb];
9415
+ var route = element('div', 'route');
9416
+ var heading = document.createElement('div');
9417
+ heading.appendChild(element('span', 'verb', verb.toUpperCase()));
9418
+ heading.appendChild(element('code', null, path));
9419
+ route.appendChild(heading);
9420
+ route.appendChild(element('p', 'description', operation.summary || ''));
9421
+ section.appendChild(route);
9422
+ });
9423
+ });
9424
+ return section;
9425
+ }
9426
+
9427
+ function render(spec) {
9428
+ root.textContent = '';
9429
+ root.appendChild(element('h1', null, spec.info.title));
9430
+ root.appendChild(element('p', 'subtitle', 'API version ' + spec.info.version + ' \\u00b7 OpenAPI ' + spec.openapi));
9431
+ root.appendChild(element('p', 'intro', spec.info.description || ''));
9432
+ root.appendChild(element('h2', null, 'Routes'));
9433
+ root.appendChild(renderRoutes(spec));
9434
+ root.appendChild(element('h2', null, 'Room management methods'));
9435
+ var alternatives = spec.paths[rpcUrl].post.requestBody.content['application/json'].schema.oneOf;
9436
+ alternatives.forEach(function (alternative) {
9437
+ root.appendChild(methodPanel(spec, alternative.$ref));
9438
+ });
9439
+ }
9440
+
9441
+ fetch(documentUrl).then(function (response) {
9442
+ if (!response.ok) throw new Error('HTTP ' + response.status);
9443
+ return response.json();
9444
+ }).then(render).catch(function (error) {
9445
+ root.textContent = '';
9446
+ root.appendChild(element('p', 'error', 'Could not load ' + documentUrl + ': ' + error));
9447
+ });
9448
+ }());
9449
+ `;
9450
+ }
9451
+ });
9452
+
8105
9453
  // src/web.ts
8106
- import * as nodeFs3 from "node:fs";
9454
+ import * as nodeFs4 from "node:fs";
8107
9455
  import { extname, join as join5 } from "node:path";
8108
- function loadWebAssets(root, fs3 = nodeFs3) {
9456
+ function loadWebAssets(root, fs2 = nodeFs4) {
8109
9457
  let rootIdentity;
8110
9458
  try {
8111
- rootIdentity = observeDirectory(fs3, root, "web asset root");
9459
+ rootIdentity = observeDirectory(fs2, root, "web asset root");
8112
9460
  } catch (error) {
8113
9461
  if (error.code === "ENOENT") return /* @__PURE__ */ new Map();
8114
9462
  throw error;
8115
9463
  }
8116
9464
  const manifest = /* @__PURE__ */ new Map();
8117
- manifest.set("/", loadAsset(fs3, join5(root, "index.html"), false, [rootIdentity]));
9465
+ manifest.set("/", loadAsset(fs2, join5(root, "index.html"), false, [rootIdentity]));
8118
9466
  const assetsDirectory = join5(root, "assets");
8119
- assertDirectoryIdentity(fs3, rootIdentity);
8120
- const assetsIdentity = observeDirectory(fs3, assetsDirectory, "web assets directory");
8121
- assertDirectoryIdentity(fs3, rootIdentity);
9467
+ assertDirectoryIdentity(fs2, rootIdentity);
9468
+ const assetsIdentity = observeDirectory(fs2, assetsDirectory, "web assets directory");
9469
+ assertDirectoryIdentity(fs2, rootIdentity);
8122
9470
  const directories = [rootIdentity, assetsIdentity];
8123
- assertDirectoryIdentities(fs3, directories);
8124
- const assetEntries = fs3.readdirSync(assetsDirectory, { withFileTypes: true });
8125
- assertDirectoryIdentities(fs3, directories);
9471
+ assertDirectoryIdentities(fs2, directories);
9472
+ const assetEntries = fs2.readdirSync(assetsDirectory, { withFileTypes: true });
9473
+ assertDirectoryIdentities(fs2, directories);
8126
9474
  for (const entry of assetEntries) {
8127
9475
  if (!entry.isFile() || entry.isSymbolicLink()) continue;
8128
9476
  const path = join5(assetsDirectory, entry.name);
8129
- manifest.set(`/assets/${entry.name}`, loadAsset(fs3, path, true, directories));
9477
+ manifest.set(`/assets/${entry.name}`, loadAsset(fs2, path, true, directories));
8130
9478
  }
8131
- assertDirectoryIdentities(fs3, directories);
9479
+ assertDirectoryIdentities(fs2, directories);
8132
9480
  return manifest;
8133
9481
  }
8134
9482
  function createStaticWebHandler(assets) {
@@ -8151,44 +9499,44 @@ function createStaticWebHandler(assets) {
8151
9499
  return true;
8152
9500
  };
8153
9501
  }
8154
- function loadAsset(fs3, path, immutable, directories) {
8155
- assertDirectoryIdentities(fs3, directories);
8156
- const observed = fs3.lstatSync(path);
8157
- assertDirectoryIdentities(fs3, directories);
9502
+ function loadAsset(fs2, path, immutable, directories) {
9503
+ assertDirectoryIdentities(fs2, directories);
9504
+ const observed = fs2.lstatSync(path);
9505
+ assertDirectoryIdentities(fs2, directories);
8158
9506
  if (observed.isSymbolicLink() || !observed.isFile() || observed.nlink !== 1) {
8159
9507
  throw new Error(`web asset must be a non-symbolic-link regular file: ${path}`);
8160
9508
  }
8161
9509
  let fd;
8162
9510
  try {
8163
- fd = fs3.openSync(path, nodeFs3.constants.O_RDONLY | NO_FOLLOW3);
8164
- const opened = fs3.fstatSync(fd);
8165
- assertDirectoryIdentities(fs3, directories);
9511
+ fd = fs2.openSync(path, nodeFs4.constants.O_RDONLY | NO_FOLLOW3);
9512
+ const opened = fs2.fstatSync(fd);
9513
+ assertDirectoryIdentities(fs2, directories);
8166
9514
  if (!opened.isFile() || opened.nlink !== 1 || opened.dev !== observed.dev || opened.ino !== observed.ino) {
8167
9515
  throw new Error(`web asset changed while opening: ${path}`);
8168
9516
  }
8169
- const bytes = fs3.readFileSync(fd);
8170
- assertDirectoryIdentities(fs3, directories);
9517
+ const bytes = fs2.readFileSync(fd);
9518
+ assertDirectoryIdentities(fs2, directories);
8171
9519
  return {
8172
9520
  bytes,
8173
9521
  contentType: CONTENT_TYPES.get(extname(path).toLowerCase()) ?? "application/octet-stream",
8174
9522
  immutable
8175
9523
  };
8176
9524
  } finally {
8177
- if (fd !== void 0) fs3.closeSync(fd);
9525
+ if (fd !== void 0) fs2.closeSync(fd);
8178
9526
  }
8179
9527
  }
8180
- function observeDirectory(fs3, path, label) {
8181
- const stat = fs3.lstatSync(path);
9528
+ function observeDirectory(fs2, path, label) {
9529
+ const stat = fs2.lstatSync(path);
8182
9530
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
8183
9531
  throw new Error(`${label} must be a non-symbolic-link directory`);
8184
9532
  }
8185
9533
  return { path, label, dev: stat.dev, ino: stat.ino };
8186
9534
  }
8187
- function assertDirectoryIdentities(fs3, directories) {
8188
- for (const directory of directories) assertDirectoryIdentity(fs3, directory);
9535
+ function assertDirectoryIdentities(fs2, directories) {
9536
+ for (const directory of directories) assertDirectoryIdentity(fs2, directory);
8189
9537
  }
8190
- function assertDirectoryIdentity(fs3, expected) {
8191
- const current = fs3.lstatSync(expected.path);
9538
+ function assertDirectoryIdentity(fs2, expected) {
9539
+ const current = fs2.lstatSync(expected.path);
8192
9540
  if (current.isSymbolicLink() || !current.isDirectory() || current.dev !== expected.dev || current.ino !== expected.ino) {
8193
9541
  throw new Error(`${expected.label} changed during web asset loading`);
8194
9542
  }
@@ -8227,7 +9575,7 @@ var NO_FOLLOW3, CONTENT_SECURITY_POLICY, CONTENT_TYPES;
8227
9575
  var init_web = __esm({
8228
9576
  "src/web.ts"() {
8229
9577
  "use strict";
8230
- NO_FOLLOW3 = nodeFs3.constants.O_NOFOLLOW ?? 0;
9578
+ NO_FOLLOW3 = nodeFs4.constants.O_NOFOLLOW ?? 0;
8231
9579
  CONTENT_SECURITY_POLICY = [
8232
9580
  "default-src 'self'",
8233
9581
  "script-src 'self'",
@@ -8264,12 +9612,12 @@ var init_web = __esm({
8264
9612
  import { randomBytes as randomBytes4 } from "node:crypto";
8265
9613
  import * as http from "node:http";
8266
9614
  import * as net from "node:net";
8267
- import * as nodeFs4 from "node:fs";
8268
- import { basename, dirname as dirname3, join as join6 } from "node:path";
9615
+ import * as nodeFs5 from "node:fs";
9616
+ import { basename, dirname as dirname5, join as join6 } from "node:path";
8269
9617
  function createServiceRoutes(service) {
8270
9618
  return {
8271
- "room.create": { auth: true, run: (params) => service.createRoom(params) },
8272
- "room.settings": { auth: true, run: (params) => {
9619
+ "room.create": { auth: true, run: (params2) => service.createRoom(params2) },
9620
+ "room.settings": { auth: true, run: (params2) => {
8273
9621
  const { room_id, ...input } = external_exports.object({
8274
9622
  room_id: external_exports.string(),
8275
9623
  name: external_exports.unknown().optional(),
@@ -8277,68 +9625,86 @@ function createServiceRoutes(service) {
8277
9625
  briefing: external_exports.unknown().optional(),
8278
9626
  status: external_exports.unknown().optional(),
8279
9627
  quiet_membership: external_exports.unknown().optional()
8280
- }).strict().parse(params);
9628
+ }).strict().parse(params2);
8281
9629
  return service.updateRoom(room_id, input);
8282
9630
  } },
8283
- "room.briefing.role.set": { auth: true, run: (params) => {
8284
- const { room_id, ...input } = RoleBriefingSetParams.parse(params);
9631
+ "room.briefing.role.set": { auth: true, run: (params2) => {
9632
+ const { room_id, ...input } = RoleBriefingSetParams.parse(params2);
8285
9633
  return service.setRoleBriefing(room_id, input);
8286
9634
  } },
8287
- "room.briefing.role.delete": { auth: true, run: (params) => {
8288
- const { room_id, ...input } = RoleBriefingDeleteParams.parse(params);
9635
+ "room.briefing.role.delete": { auth: true, run: (params2) => {
9636
+ const { room_id, ...input } = RoleBriefingDeleteParams.parse(params2);
8289
9637
  return service.deleteRoleBriefing(room_id, input);
8290
9638
  } },
8291
- "room.invite": { auth: true, run: (params) => {
9639
+ "room.invite": { auth: true, run: (params2) => {
8292
9640
  const { room_id, ...input } = external_exports.object({
8293
9641
  room_id: external_exports.string(),
8294
9642
  mode: external_exports.unknown(),
8295
9643
  role: external_exports.unknown().optional(),
8296
9644
  min_accepts: external_exports.unknown()
8297
- }).strict().parse(params);
9645
+ }).strict().parse(params2);
8298
9646
  return service.createInvite(room_id, input);
8299
9647
  } },
8300
- "room.participant.remove": { auth: true, run: (params) => {
8301
- const { room_id, ...input } = ParticipantRemoveParams.parse(params);
9648
+ "room.participant.remove": { auth: true, run: (params2) => {
9649
+ const { room_id, ...input } = ParticipantRemoveParams.parse(params2);
8302
9650
  return service.removeParticipant(room_id, input);
8303
9651
  } },
8304
- "room.participant.replace": { auth: true, run: (params) => {
8305
- const { room_id, ...input } = ParticipantReplaceParams.parse(params);
9652
+ "room.participant.replace": { auth: true, run: (params2) => {
9653
+ const { room_id, ...input } = ParticipantReplaceParams.parse(params2);
8306
9654
  return service.replaceParticipant(room_id, input);
8307
9655
  } },
8308
- "room.revoke": { auth: true, run: (params) => {
8309
- const value = InviteRevokeParams.parse(params);
9656
+ "room.revoke": { auth: true, run: (params2) => {
9657
+ const value = InviteRevokeParams.parse(params2);
8310
9658
  return service.revokeInvite(value.room_id, value.invite_id);
8311
9659
  } },
8312
- "room.recover": { auth: true, run: (params) => service.recoverInvites(RoomIdParams.parse(params).room_id) },
8313
- "room.recover.confirm": { auth: true, run: (params) => {
8314
- const value = RecoverConfirmParams.parse(params);
9660
+ "room.recover": { auth: true, run: (params2) => service.recoverInvites(RoomIdParams.parse(params2).room_id) },
9661
+ "room.recover.confirm": { auth: true, run: (params2) => {
9662
+ const value = RecoverConfirmParams.parse(params2);
8315
9663
  return service.confirmRecoveredInvite(value.room_id, value.recovery_of, value.invite_id);
8316
9664
  } },
8317
- "room.list": { auth: true, run: (params) => {
8318
- external_exports.object({}).strict().parse(params);
9665
+ "room.list": { auth: true, run: (params2) => {
9666
+ external_exports.object({}).strict().parse(params2);
8319
9667
  return service.listRooms();
8320
9668
  } },
8321
- "room.show": { auth: true, run: (params) => service.showRoom(RoomIdParams.parse(params).room_id) },
8322
- "room.participants": { auth: true, run: (params) => service.participants(RoomIdParams.parse(params).room_id) },
8323
- "room.history": { auth: true, run: (params) => {
8324
- const { room_id, ...options } = HistoryParams.parse(params);
9669
+ "room.show": { auth: true, run: (params2) => service.showRoom(RoomIdParams.parse(params2).room_id) },
9670
+ "room.participants": { auth: true, run: (params2) => service.participants(RoomIdParams.parse(params2).room_id) },
9671
+ "room.history": { auth: true, run: (params2) => {
9672
+ const { room_id, ...options } = HistoryParams.parse(params2);
8325
9673
  return service.history(room_id, options);
8326
9674
  } },
8327
- "room.message": { auth: true, run: (params) => {
8328
- const { room_id, ...input } = external_exports.object({ room_id: external_exports.string(), text: external_exports.unknown() }).strict().parse(params);
9675
+ "room.message": { auth: true, run: (params2) => {
9676
+ const { room_id, ...input } = external_exports.object({ room_id: external_exports.string(), text: external_exports.unknown() }).strict().parse(params2);
8329
9677
  return service.postMessage(room_id, input);
8330
9678
  } },
8331
- "room.close": { auth: true, run: (params) => service.closeRoom(RoomIdParams.parse(params).room_id) },
8332
- "room.delete": { auth: true, run: (params) => {
8333
- const { room_id, ...input } = external_exports.object({ room_id: external_exports.string(), confirm: external_exports.unknown() }).strict().parse(params);
9679
+ // Role authorship: served over REST, which is the point of the feature. No
9680
+ // secret is ingested in either direction, so the Unix-only rule does not apply.
9681
+ "room.say": { auth: true, run: (params2) => {
9682
+ const { room_id, ...input } = external_exports.object({
9683
+ room_id: external_exports.string(),
9684
+ role: external_exports.unknown(),
9685
+ text: external_exports.unknown()
9686
+ }).strict().parse(params2);
9687
+ return service.postAsRole(room_id, input);
9688
+ } },
9689
+ "room.role.rest.add": { auth: true, run: (params2) => {
9690
+ const { room_id, ...input } = RestRoleParams.parse(params2);
9691
+ return service.addRestRole(room_id, input);
9692
+ } },
9693
+ "room.role.rest.remove": { auth: true, run: (params2) => {
9694
+ const { room_id, ...input } = RestRoleParams.parse(params2);
9695
+ return service.removeRestRole(room_id, input);
9696
+ } },
9697
+ "room.close": { auth: true, run: (params2) => service.closeRoom(RoomIdParams.parse(params2).room_id) },
9698
+ "room.delete": { auth: true, run: (params2) => {
9699
+ const { room_id, ...input } = external_exports.object({ room_id: external_exports.string(), confirm: external_exports.unknown() }).strict().parse(params2);
8334
9700
  return service.deleteRoom(room_id, input);
8335
9701
  } }
8336
9702
  };
8337
9703
  }
8338
9704
  function createPrivateServiceRoutes(service) {
8339
9705
  return {
8340
- "room.accept": { auth: true, run: (params) => {
8341
- const { room_id, ...input } = ExternalInviteAcceptParams.parse(params);
9706
+ "room.accept": { auth: true, run: (params2) => {
9707
+ const { room_id, ...input } = ExternalInviteAcceptParams.parse(params2);
8342
9708
  return service.acceptExternalInvite(room_id, input);
8343
9709
  } }
8344
9710
  };
@@ -8358,6 +9724,23 @@ function isJsonMediaType(value) {
8358
9724
  const mediaType = value.split(";", 1)[0]?.trim().toLowerCase();
8359
9725
  return mediaType === "application/json" || mediaType?.startsWith("application/") === true && mediaType.endsWith("+json");
8360
9726
  }
9727
+ async function serveApiDocs(request, response) {
9728
+ const pathname = safePathname(request.url);
9729
+ if (pathname === void 0) return false;
9730
+ const asset = apiDocsAsset(pathname);
9731
+ if (!asset) return false;
9732
+ const body = Buffer.from(asset.body, "utf8");
9733
+ response.writeHead(200, {
9734
+ "content-type": asset.contentType,
9735
+ "content-length": body.length,
9736
+ "cache-control": "no-cache",
9737
+ "content-security-policy": CONTENT_SECURITY_POLICY,
9738
+ "x-content-type-options": "nosniff"
9739
+ });
9740
+ response.end(body);
9741
+ await responseFinished2(response);
9742
+ return true;
9743
+ }
8361
9744
  function sendJson(response, status, value) {
8362
9745
  const body = Buffer.from(JSON.stringify(value), "utf8");
8363
9746
  response.writeHead(status, {
@@ -8470,11 +9853,12 @@ function responseFinished2(response) {
8470
9853
  response.once("error", rejectResponse);
8471
9854
  });
8472
9855
  }
8473
- var MAX_REQUEST_BYTES, HTTP_HEADERS_TIMEOUT_MS, HTTP_REQUEST_TIMEOUT_MS, HTTP_BODY_IDLE_TIMEOUT_MS, HTTP_KEEP_ALIVE_TIMEOUT_MS, MAX_STALE_PRIVATE_SOCKET_CLEANUP, RpcIdSchema, RpcRequestSchema, RpcDispatcher, RoomIdParams, InviteRevokeParams, RecoverConfirmParams, HistoryParams, RoleBriefingSetParams, RoleBriefingDeleteParams, ParticipantRemoveParams, ParticipantReplaceParams, ExternalInviteAcceptParams, TransportServer;
9856
+ var MAX_REQUEST_BYTES, HTTP_HEADERS_TIMEOUT_MS, HTTP_REQUEST_TIMEOUT_MS, HTTP_BODY_IDLE_TIMEOUT_MS, HTTP_KEEP_ALIVE_TIMEOUT_MS, MAX_STALE_PRIVATE_SOCKET_CLEANUP, RpcIdSchema, RpcRequestSchema, RpcDispatcher, RoomIdParams, InviteRevokeParams, RecoverConfirmParams, HistoryParams, RoleBriefingSetParams, RoleBriefingDeleteParams, RestRoleParams, ParticipantRemoveParams, ParticipantReplaceParams, ExternalInviteAcceptParams, TransportServer;
8474
9857
  var init_transports = __esm({
8475
9858
  "src/transports.ts"() {
8476
9859
  "use strict";
8477
9860
  init_zod();
9861
+ init_openapi();
8478
9862
  init_web();
8479
9863
  MAX_REQUEST_BYTES = 1024 * 1024;
8480
9864
  HTTP_HEADERS_TIMEOUT_MS = 5e3;
@@ -8557,6 +9941,10 @@ var init_transports = __esm({
8557
9941
  room_id: external_exports.string(),
8558
9942
  role: external_exports.string()
8559
9943
  }).strict();
9944
+ RestRoleParams = external_exports.object({
9945
+ room_id: external_exports.string(),
9946
+ role: external_exports.string()
9947
+ }).strict();
8560
9948
  ParticipantRemoveParams = external_exports.object({
8561
9949
  room_id: external_exports.string(),
8562
9950
  participant: external_exports.string(),
@@ -8594,7 +9982,7 @@ var init_transports = __esm({
8594
9982
  protectedPrivateReplacement;
8595
9983
  constructor(options) {
8596
9984
  this.options = options;
8597
- this.fs = options.fs ?? nodeFs4;
9985
+ this.fs = options.fs ?? nodeFs5;
8598
9986
  this.staticHandler = options.staticHandler ?? createStaticWebHandler(/* @__PURE__ */ new Map());
8599
9987
  }
8600
9988
  get restAddress() {
@@ -8741,6 +10129,7 @@ var init_transports = __esm({
8741
10129
  if (request.url !== "/rpc") {
8742
10130
  if (request.method === "GET") {
8743
10131
  activateResponse();
10132
+ if (await serveApiDocs(request, response)) return;
8744
10133
  if (await this.staticHandler(request, response)) return;
8745
10134
  }
8746
10135
  activateResponse();
@@ -8889,7 +10278,7 @@ var init_transports = __esm({
8889
10278
  throw new Error(`private management socket changed during removal; replacement protected at ${quarantined.path}`);
8890
10279
  }
8891
10280
  async cleanupStalePrivateSockets() {
8892
- const directory = dirname3(this.options.socketPath);
10281
+ const directory = dirname5(this.options.socketPath);
8893
10282
  const prefix = `${basename(this.options.socketPath)}.private-`;
8894
10283
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
8895
10284
  const candidates = this.fs.readdirSync(directory).filter((name) => name.startsWith(prefix)).sort().slice(0, MAX_STALE_PRIVATE_SOCKET_CLEANUP);
@@ -8997,24 +10386,24 @@ __export(daemon_runtime_exports, {
8997
10386
  removeDaemonPid: () => removeDaemonPid,
8998
10387
  writeDaemonPid: () => writeDaemonPid
8999
10388
  });
9000
- import * as nodeFs5 from "node:fs";
10389
+ import * as nodeFs6 from "node:fs";
9001
10390
  import { join as join7 } from "node:path";
9002
- import { fileURLToPath } from "node:url";
10391
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
9003
10392
  function isIntakeNotification(event) {
9004
10393
  return event === "message_received" || event === "file_received" || event === "contact_accepted" || event === "contact_added";
9005
10394
  }
9006
10395
  function createDaemonControlRoutes(control) {
9007
10396
  if (!/^[0-9a-f]{32}$/.test(control.session)) throw new TypeError("invalid daemon control session");
9008
- const requireExact = (params, keys) => {
9009
- if (Object.keys(params).length !== keys.length || keys.some((key) => !Object.hasOwn(params, key))) {
10397
+ const requireExact = (params2, keys) => {
10398
+ if (Object.keys(params2).length !== keys.length || keys.some((key) => !Object.hasOwn(params2, key))) {
9010
10399
  throw new TypeError("invalid daemon control parameters");
9011
10400
  }
9012
10401
  };
9013
10402
  return {
9014
10403
  "daemon.status": {
9015
10404
  auth: true,
9016
- run(params) {
9017
- requireExact(params, []);
10405
+ run(params2) {
10406
+ requireExact(params2, []);
9018
10407
  return {
9019
10408
  version: 1,
9020
10409
  protocol: "cowork-supervisor-control",
@@ -9025,9 +10414,9 @@ function createDaemonControlRoutes(control) {
9025
10414
  },
9026
10415
  "daemon.shutdown": {
9027
10416
  auth: true,
9028
- async run(params) {
9029
- requireExact(params, ["session"]);
9030
- if (params.session !== control.session) throw new TypeError("daemon control session changed");
10417
+ async run(params2) {
10418
+ requireExact(params2, ["session"]);
10419
+ if (params2.session !== control.session) throw new TypeError("daemon control session changed");
9031
10420
  if (!await control.requestSupervisorShutdown()) throw new Error("supervisor IPC rejected shutdown request");
9032
10421
  return { accepted: true, session: control.session };
9033
10422
  }
@@ -9035,7 +10424,7 @@ function createDaemonControlRoutes(control) {
9035
10424
  };
9036
10425
  }
9037
10426
  function acquireDaemonLock(stateDir, options = {}) {
9038
- const fs3 = options.fs ?? nodeFs5;
10427
+ const fs2 = options.fs ?? nodeFs6;
9039
10428
  const pid = options.pid ?? process.pid;
9040
10429
  const alive = options.isProcessAlive ?? isProcessAlive;
9041
10430
  const path = join7(stateDir, "daemon.lock");
@@ -9043,22 +10432,22 @@ function acquireDaemonLock(stateDir, options = {}) {
9043
10432
  let fd;
9044
10433
  let created = false;
9045
10434
  try {
9046
- fd = fs3.openSync(path, nodeFs5.constants.O_CREAT | nodeFs5.constants.O_EXCL | nodeFs5.constants.O_WRONLY | NO_FOLLOW4, FILE_MODE3);
10435
+ fd = fs2.openSync(path, nodeFs6.constants.O_CREAT | nodeFs6.constants.O_EXCL | nodeFs6.constants.O_WRONLY | NO_FOLLOW4, FILE_MODE3);
9047
10436
  created = true;
9048
- fs3.fchmodSync(fd, FILE_MODE3);
9049
- writeAll(fs3, fd, Buffer.from(`${pid}
10437
+ fs2.fchmodSync(fd, FILE_MODE3);
10438
+ writeAll(fs2, fd, Buffer.from(`${pid}
9050
10439
  `, "ascii"));
9051
- fs3.fsyncSync(fd);
9052
- const owned = fs3.fstatSync(fd);
9053
- fs3.closeSync(fd);
10440
+ fs2.fsyncSync(fd);
10441
+ const owned = fs2.fstatSync(fd);
10442
+ fs2.closeSync(fd);
9054
10443
  fd = void 0;
9055
- fsyncDirectory2(fs3, stateDir);
10444
+ fsyncDirectory2(fs2, stateDir);
9056
10445
  const pidPath = join7(stateDir, "daemon.pid");
9057
- if (lstatIfPresent2(fs3, pidPath)) {
9058
- const pidOwner = readSecurePid(fs3, pidPath, "daemon PID");
10446
+ if (lstatIfPresent2(fs2, pidPath)) {
10447
+ const pidOwner = readSecurePid(fs2, pidPath, "daemon PID");
9059
10448
  if (alive(pidOwner)) {
9060
- fs3.unlinkSync(path);
9061
- fsyncDirectory2(fs3, stateDir);
10449
+ fs2.unlinkSync(path);
10450
+ fsyncDirectory2(fs2, stateDir);
9062
10451
  throw new Error(`cowork daemon is already running with PID ${pidOwner}`);
9063
10452
  }
9064
10453
  }
@@ -9067,90 +10456,90 @@ function acquireDaemonLock(stateDir, options = {}) {
9067
10456
  release() {
9068
10457
  if (released) return;
9069
10458
  released = true;
9070
- const current = lstatIfPresent2(fs3, path);
10459
+ const current = lstatIfPresent2(fs2, path);
9071
10460
  if (!current || current.dev !== owned.dev || current.ino !== owned.ino) return;
9072
- const content = readSecurePid(fs3, path, "daemon lock");
10461
+ const content = readSecurePid(fs2, path, "daemon lock");
9073
10462
  if (content !== pid) return;
9074
- fs3.unlinkSync(path);
9075
- fsyncDirectory2(fs3, stateDir);
10463
+ fs2.unlinkSync(path);
10464
+ fsyncDirectory2(fs2, stateDir);
9076
10465
  }
9077
10466
  };
9078
10467
  } catch (error) {
9079
10468
  if (fd !== void 0) try {
9080
- fs3.closeSync(fd);
10469
+ fs2.closeSync(fd);
9081
10470
  } catch {
9082
10471
  }
9083
10472
  if (error.code !== "EEXIST") {
9084
10473
  if (created) {
9085
10474
  try {
9086
- fs3.unlinkSync(path);
9087
- fsyncDirectory2(fs3, stateDir);
10475
+ fs2.unlinkSync(path);
10476
+ fsyncDirectory2(fs2, stateDir);
9088
10477
  } catch {
9089
10478
  }
9090
10479
  }
9091
10480
  throw error;
9092
10481
  }
9093
- const observed = fs3.lstatSync(path);
9094
- const owner = readSecurePid(fs3, path, "daemon lock");
10482
+ const observed = fs2.lstatSync(path);
10483
+ const owner = readSecurePid(fs2, path, "daemon lock");
9095
10484
  if (alive(owner)) throw new Error(`cowork daemon is already running with PID ${owner}`);
9096
- const current = fs3.lstatSync(path);
10485
+ const current = fs2.lstatSync(path);
9097
10486
  if (current.dev !== observed.dev || current.ino !== observed.ino) continue;
9098
- fs3.unlinkSync(path);
9099
- fsyncDirectory2(fs3, stateDir);
10487
+ fs2.unlinkSync(path);
10488
+ fsyncDirectory2(fs2, stateDir);
9100
10489
  }
9101
10490
  }
9102
10491
  throw new Error("daemon lock changed repeatedly while acquiring it");
9103
10492
  }
9104
- function writeDaemonPid(stateDir, fs3 = nodeFs5, pid = process.pid) {
10493
+ function writeDaemonPid(stateDir, fs2 = nodeFs6, pid = process.pid) {
9105
10494
  const path = join7(stateDir, "daemon.pid");
9106
- const existing = lstatIfPresent2(fs3, path);
10495
+ const existing = lstatIfPresent2(fs2, path);
9107
10496
  if (existing) {
9108
- const owner = readSecurePid(fs3, path, "daemon PID");
10497
+ const owner = readSecurePid(fs2, path, "daemon PID");
9109
10498
  if (isProcessAlive(owner) && owner !== pid) throw new Error(`cowork daemon PID file belongs to live PID ${owner}`);
9110
- const current = fs3.lstatSync(path);
10499
+ const current = fs2.lstatSync(path);
9111
10500
  if (current.dev !== existing.dev || current.ino !== existing.ino) throw new Error("daemon PID file changed during stale-owner check");
9112
- fs3.unlinkSync(path);
10501
+ fs2.unlinkSync(path);
9113
10502
  }
9114
10503
  let fd;
9115
10504
  let created = false;
9116
10505
  try {
9117
- fd = fs3.openSync(path, nodeFs5.constants.O_CREAT | nodeFs5.constants.O_EXCL | nodeFs5.constants.O_WRONLY | NO_FOLLOW4, FILE_MODE3);
10506
+ fd = fs2.openSync(path, nodeFs6.constants.O_CREAT | nodeFs6.constants.O_EXCL | nodeFs6.constants.O_WRONLY | NO_FOLLOW4, FILE_MODE3);
9118
10507
  created = true;
9119
- fs3.fchmodSync(fd, FILE_MODE3);
9120
- writeAll(fs3, fd, Buffer.from(`${pid}
10508
+ fs2.fchmodSync(fd, FILE_MODE3);
10509
+ writeAll(fs2, fd, Buffer.from(`${pid}
9121
10510
  `, "ascii"));
9122
- fs3.fsyncSync(fd);
10511
+ fs2.fsyncSync(fd);
9123
10512
  } catch (error) {
9124
10513
  if (fd !== void 0) {
9125
10514
  try {
9126
- fs3.closeSync(fd);
10515
+ fs2.closeSync(fd);
9127
10516
  } catch {
9128
10517
  }
9129
10518
  fd = void 0;
9130
10519
  }
9131
10520
  if (created) try {
9132
- fs3.unlinkSync(path);
10521
+ fs2.unlinkSync(path);
9133
10522
  } catch {
9134
10523
  }
9135
10524
  throw error;
9136
10525
  } finally {
9137
- if (fd !== void 0) fs3.closeSync(fd);
10526
+ if (fd !== void 0) fs2.closeSync(fd);
9138
10527
  }
9139
- fsyncDirectory2(fs3, stateDir);
10528
+ fsyncDirectory2(fs2, stateDir);
9140
10529
  }
9141
- function removeDaemonPid(stateDir, fs3 = nodeFs5, pid = process.pid) {
10530
+ function removeDaemonPid(stateDir, fs2 = nodeFs6, pid = process.pid) {
9142
10531
  const path = join7(stateDir, "daemon.pid");
9143
- const observed = lstatIfPresent2(fs3, path);
10532
+ const observed = lstatIfPresent2(fs2, path);
9144
10533
  if (!observed) return;
9145
- const owner = readSecurePid(fs3, path, "daemon PID");
10534
+ const owner = readSecurePid(fs2, path, "daemon PID");
9146
10535
  if (owner !== pid) return;
9147
- const current = fs3.lstatSync(path);
10536
+ const current = fs2.lstatSync(path);
9148
10537
  if (current.dev !== observed.dev || current.ino !== observed.ino) return;
9149
- fs3.unlinkSync(path);
9150
- fsyncDirectory2(fs3, stateDir);
10538
+ fs2.unlinkSync(path);
10539
+ fsyncDirectory2(fs2, stateDir);
9151
10540
  }
9152
- function readSecurePid(fs3, path, label) {
9153
- const stat = fs3.lstatSync(path);
10541
+ function readSecurePid(fs2, path, label) {
10542
+ const stat = fs2.lstatSync(path);
9154
10543
  if (stat.isSymbolicLink() || !stat.isFile() || stat.nlink !== 1 || (stat.mode & 511) !== FILE_MODE3) {
9155
10544
  throw new Error(`${label} must be a 0600 single-link regular file`);
9156
10545
  }
@@ -9160,15 +10549,15 @@ function readSecurePid(fs3, path, label) {
9160
10549
  let fd;
9161
10550
  let text;
9162
10551
  try {
9163
- fd = fs3.openSync(path, nodeFs5.constants.O_RDONLY | NO_FOLLOW4);
9164
- const opened = fs3.fstatSync(fd);
9165
- const current = fs3.lstatSync(path);
10552
+ fd = fs2.openSync(path, nodeFs6.constants.O_RDONLY | NO_FOLLOW4);
10553
+ const opened = fs2.fstatSync(fd);
10554
+ const current = fs2.lstatSync(path);
9166
10555
  if (opened.dev !== stat.dev || opened.ino !== stat.ino || current.dev !== opened.dev || current.ino !== opened.ino) {
9167
10556
  throw new Error(`${label} changed while opening`);
9168
10557
  }
9169
- text = fs3.readFileSync(fd, "utf8");
10558
+ text = fs2.readFileSync(fd, "utf8");
9170
10559
  } finally {
9171
- if (fd !== void 0) fs3.closeSync(fd);
10560
+ if (fd !== void 0) fs2.closeSync(fd);
9172
10561
  }
9173
10562
  if (!/^[1-9][0-9]*\n$/.test(text)) throw new Error(`${label} contains an invalid PID`);
9174
10563
  const pid = Number(text.trim());
@@ -9183,26 +10572,26 @@ function isProcessAlive(pid) {
9183
10572
  return error.code === "EPERM";
9184
10573
  }
9185
10574
  }
9186
- function writeAll(fs3, fd, bytes) {
10575
+ function writeAll(fs2, fd, bytes) {
9187
10576
  let offset = 0;
9188
10577
  while (offset < bytes.length) {
9189
- const written = fs3.writeSync(fd, bytes, offset, bytes.length - offset, null);
10578
+ const written = fs2.writeSync(fd, bytes, offset, bytes.length - offset, null);
9190
10579
  if (written <= 0) throw new Error("write made no progress");
9191
10580
  offset += written;
9192
10581
  }
9193
10582
  }
9194
- function fsyncDirectory2(fs3, path) {
10583
+ function fsyncDirectory2(fs2, path) {
9195
10584
  let fd;
9196
10585
  try {
9197
- fd = fs3.openSync(path, nodeFs5.constants.O_RDONLY | NO_FOLLOW4);
9198
- fs3.fsyncSync(fd);
10586
+ fd = fs2.openSync(path, nodeFs6.constants.O_RDONLY | NO_FOLLOW4);
10587
+ fs2.fsyncSync(fd);
9199
10588
  } finally {
9200
- if (fd !== void 0) fs3.closeSync(fd);
10589
+ if (fd !== void 0) fs2.closeSync(fd);
9201
10590
  }
9202
10591
  }
9203
- function lstatIfPresent2(fs3, path) {
10592
+ function lstatIfPresent2(fs2, path) {
9204
10593
  try {
9205
- return fs3.lstatSync(path);
10594
+ return fs2.lstatSync(path);
9206
10595
  } catch (error) {
9207
10596
  if (error.code === "ENOENT") return void 0;
9208
10597
  throw error;
@@ -9213,18 +10602,18 @@ function requiresExitFrom(error) {
9213
10602
  }
9214
10603
  var FILE_MODE3, NO_FOLLOW4, DaemonBootCancelledError, DaemonShutdownError, CoworkDaemon;
9215
10604
  var init_daemon_runtime = __esm({
9216
- "src/daemon-runtime.ts"() {
10605
+ async "src/daemon-runtime.ts"() {
9217
10606
  "use strict";
10607
+ await init_adapt();
9218
10608
  init_config();
9219
10609
  init_config();
9220
- init_ours_runtime();
9221
- init_packets();
9222
- init_service();
10610
+ await init_packets();
10611
+ await init_service();
9223
10612
  init_storage();
9224
10613
  init_transports();
9225
10614
  init_web();
9226
10615
  FILE_MODE3 = 384;
9227
- NO_FOLLOW4 = nodeFs5.constants.O_NOFOLLOW ?? 0;
10616
+ NO_FOLLOW4 = nodeFs6.constants.O_NOFOLLOW ?? 0;
9228
10617
  DaemonBootCancelledError = class extends Error {
9229
10618
  constructor() {
9230
10619
  super("cowork daemon boot cancelled by shutdown");
@@ -9276,7 +10665,7 @@ var init_daemon_runtime = __esm({
9276
10665
  this.options.onStage?.("post-lock");
9277
10666
  this.checkpoint();
9278
10667
  try {
9279
- this.host = this.options.host ?? createOursHost(config, this.options.log);
10668
+ this.host = this.options.host ?? new AdaptHost(config.brokerUrl, this.options.log);
9280
10669
  this.store = this.options.store ?? new CoworkStore(config.stateDir);
9281
10670
  let serviceRef = this.options.service;
9282
10671
  this.registry = this.options.registry ?? new PacketRegistry(
@@ -9327,7 +10716,7 @@ var init_daemon_runtime = __esm({
9327
10716
  const unixDispatcher = new RpcDispatcher(unixRoutes);
9328
10717
  const restDispatcher = new RpcDispatcher(serviceRoutes);
9329
10718
  const staticHandler = createStaticWebHandler(loadWebAssets(
9330
- fileURLToPath(new URL("./web/", import.meta.url))
10719
+ fileURLToPath2(new URL("./web/", import.meta.url))
9331
10720
  ));
9332
10721
  this.transports = this.options.transports ?? new TransportServer({
9333
10722
  socketPath: runtime.socketPath,
@@ -9570,7 +10959,7 @@ async function runWorker() {
9570
10959
  await acknowledge();
9571
10960
  return shutdownComplete;
9572
10961
  }
9573
- const runtime = await Promise.resolve().then(() => (init_daemon_runtime(), daemon_runtime_exports));
10962
+ const runtime = await init_daemon_runtime().then(() => daemon_runtime_exports);
9574
10963
  if (shutdownRequested) {
9575
10964
  await acknowledge();
9576
10965
  return shutdownComplete;
@@ -9665,13 +11054,13 @@ var init_daemon_process = __esm({
9665
11054
  import { fork } from "node:child_process";
9666
11055
  import { randomBytes as randomBytes6 } from "node:crypto";
9667
11056
  import { resolve as resolve3 } from "node:path";
9668
- import { fileURLToPath as fileURLToPath2 } from "node:url";
11057
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
9669
11058
  async function runSupervisor(options = {}) {
9670
11059
  const workerEnv = { ...process.env };
9671
11060
  delete workerEnv.NODE_OPTIONS;
9672
11061
  workerEnv.OURS_COWORK_DAEMON_WORKER = "1";
9673
11062
  workerEnv.OURS_COWORK_SUPERVISOR_PID = String(process.pid);
9674
- const child = fork(fileURLToPath2(import.meta.url), [], {
11063
+ const child = fork(fileURLToPath3(import.meta.url), [], {
9675
11064
  env: workerEnv,
9676
11065
  stdio: options.quiet ? ["ignore", "ignore", "ignore", "ipc"] : ["inherit", "inherit", "inherit", "ipc"],
9677
11066
  execArgv: [],
@@ -9847,7 +11236,7 @@ var init_daemon = __esm({
9847
11236
  }
9848
11237
  };
9849
11238
  invokedPath = process.argv[1] ? resolve3(process.argv[1]) : void 0;
9850
- if (invokedPath === fileURLToPath2(import.meta.url)) {
11239
+ if (invokedPath === fileURLToPath3(import.meta.url)) {
9851
11240
  void Promise.resolve().then(() => (init_daemon_process(), daemon_process_exports)).then(({ runDaemonProcess: runDaemonProcess2 }) => runDaemonProcess2()).catch((error) => {
9852
11241
  console.error(error instanceof Error ? error.stack ?? error.message : String(error));
9853
11242
  process.exitCode = 1;