@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/README.md +7 -7
- package/dist/cli.js +53 -116
- package/dist/daemon.js +2410 -1021
- package/dist/mufl_code/BBAE58CF78DEE59692F456EAFFA9A6109835B66846FC3989F6D201B8F4523A55.muflo +0 -0
- package/dist/web/assets/app.js +3 -3
- package/docs/01-prerequisites.md +1 -1
- package/docs/02-installation.md +1 -1
- package/docs/03-configuration.md +1 -31
- package/docs/04-daemon-lifecycle.md +0 -4
- package/docs/05-room-workflow.md +2 -2
- package/docs/06-invites.md +0 -2
- package/docs/07-messaging-history.md +22 -2
- package/docs/08-backup-restore.md +2 -2
- package/docs/09-service-management.md +2 -4
- package/docs/10-limitations.md +2 -3
- package/docs/11-web-console.md +12 -1
- package/package.json +6 -3
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 = (
|
|
425
|
-
const { data, path, errorMaps, issueData } =
|
|
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(
|
|
535
|
-
if (!
|
|
974
|
+
function processCreateParams(params2) {
|
|
975
|
+
if (!params2)
|
|
536
976
|
return {};
|
|
537
|
-
const { errorMap: errorMap2, invalid_type_error, required_error, description } =
|
|
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 } =
|
|
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,
|
|
1131
|
+
function createZodEnum(values, params2) {
|
|
692
1132
|
return new ZodEnum({
|
|
693
1133
|
values,
|
|
694
1134
|
typeName: ZodFirstPartyTypeKind.ZodEnum,
|
|
695
|
-
...processCreateParams(
|
|
1135
|
+
...processCreateParams(params2)
|
|
696
1136
|
});
|
|
697
1137
|
}
|
|
698
|
-
function cleanParams(
|
|
699
|
-
const p = typeof
|
|
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
|
|
711
|
-
const _fatal =
|
|
712
|
-
ctx.addIssue({ code: "custom", ...
|
|
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
|
|
718
|
-
const _fatal =
|
|
719
|
-
ctx.addIssue({ code: "custom", ...
|
|
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,
|
|
813
|
-
const result = this.safeParse(data,
|
|
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,
|
|
1258
|
+
safeParse(data, params2) {
|
|
819
1259
|
const ctx = {
|
|
820
1260
|
common: {
|
|
821
1261
|
issues: [],
|
|
822
|
-
async:
|
|
823
|
-
contextualErrorMap:
|
|
1262
|
+
async: params2?.async ?? false,
|
|
1263
|
+
contextualErrorMap: params2?.errorMap
|
|
824
1264
|
},
|
|
825
|
-
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,
|
|
871
|
-
const result = await this.safeParseAsync(data,
|
|
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,
|
|
1316
|
+
async safeParseAsync(data, params2) {
|
|
877
1317
|
const ctx = {
|
|
878
1318
|
common: {
|
|
879
1319
|
issues: [],
|
|
880
|
-
contextualErrorMap:
|
|
1320
|
+
contextualErrorMap: params2?.errorMap,
|
|
881
1321
|
async: true
|
|
882
1322
|
},
|
|
883
|
-
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 = (
|
|
2049
|
+
ZodString.create = (params2) => {
|
|
1610
2050
|
return new ZodString({
|
|
1611
2051
|
checks: [],
|
|
1612
2052
|
typeName: ZodFirstPartyTypeKind.ZodString,
|
|
1613
|
-
coerce:
|
|
1614
|
-
...processCreateParams(
|
|
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 = (
|
|
2281
|
+
ZodNumber.create = (params2) => {
|
|
1842
2282
|
return new ZodNumber({
|
|
1843
2283
|
checks: [],
|
|
1844
2284
|
typeName: ZodFirstPartyTypeKind.ZodNumber,
|
|
1845
|
-
coerce:
|
|
1846
|
-
...processCreateParams(
|
|
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 = (
|
|
2453
|
+
ZodBigInt.create = (params2) => {
|
|
2014
2454
|
return new ZodBigInt({
|
|
2015
2455
|
checks: [],
|
|
2016
2456
|
typeName: ZodFirstPartyTypeKind.ZodBigInt,
|
|
2017
|
-
coerce:
|
|
2018
|
-
...processCreateParams(
|
|
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 = (
|
|
2479
|
+
ZodBoolean.create = (params2) => {
|
|
2040
2480
|
return new ZodBoolean({
|
|
2041
2481
|
typeName: ZodFirstPartyTypeKind.ZodBoolean,
|
|
2042
|
-
coerce:
|
|
2043
|
-
...processCreateParams(
|
|
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 = (
|
|
2587
|
+
ZodDate.create = (params2) => {
|
|
2148
2588
|
return new ZodDate({
|
|
2149
2589
|
checks: [],
|
|
2150
|
-
coerce:
|
|
2590
|
+
coerce: params2?.coerce || false,
|
|
2151
2591
|
typeName: ZodFirstPartyTypeKind.ZodDate,
|
|
2152
|
-
...processCreateParams(
|
|
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 = (
|
|
2610
|
+
ZodSymbol.create = (params2) => {
|
|
2171
2611
|
return new ZodSymbol({
|
|
2172
2612
|
typeName: ZodFirstPartyTypeKind.ZodSymbol,
|
|
2173
|
-
...processCreateParams(
|
|
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 = (
|
|
2631
|
+
ZodUndefined.create = (params2) => {
|
|
2192
2632
|
return new ZodUndefined({
|
|
2193
2633
|
typeName: ZodFirstPartyTypeKind.ZodUndefined,
|
|
2194
|
-
...processCreateParams(
|
|
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 = (
|
|
2652
|
+
ZodNull.create = (params2) => {
|
|
2213
2653
|
return new ZodNull({
|
|
2214
2654
|
typeName: ZodFirstPartyTypeKind.ZodNull,
|
|
2215
|
-
...processCreateParams(
|
|
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 = (
|
|
2667
|
+
ZodAny.create = (params2) => {
|
|
2228
2668
|
return new ZodAny({
|
|
2229
2669
|
typeName: ZodFirstPartyTypeKind.ZodAny,
|
|
2230
|
-
...processCreateParams(
|
|
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 = (
|
|
2682
|
+
ZodUnknown.create = (params2) => {
|
|
2243
2683
|
return new ZodUnknown({
|
|
2244
2684
|
typeName: ZodFirstPartyTypeKind.ZodUnknown,
|
|
2245
|
-
...processCreateParams(
|
|
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 = (
|
|
2699
|
+
ZodNever.create = (params2) => {
|
|
2260
2700
|
return new ZodNever({
|
|
2261
2701
|
typeName: ZodFirstPartyTypeKind.ZodNever,
|
|
2262
|
-
...processCreateParams(
|
|
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 = (
|
|
2720
|
+
ZodVoid.create = (params2) => {
|
|
2281
2721
|
return new ZodVoid({
|
|
2282
2722
|
typeName: ZodFirstPartyTypeKind.ZodVoid,
|
|
2283
|
-
...processCreateParams(
|
|
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,
|
|
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(
|
|
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,
|
|
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(
|
|
3144
|
+
...processCreateParams(params2)
|
|
2705
3145
|
});
|
|
2706
3146
|
};
|
|
2707
|
-
ZodObject.strictCreate = (shape,
|
|
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(
|
|
3153
|
+
...processCreateParams(params2)
|
|
2714
3154
|
});
|
|
2715
3155
|
};
|
|
2716
|
-
ZodObject.lazycreate = (shape,
|
|
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(
|
|
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,
|
|
3249
|
+
ZodUnion.create = (types, params2) => {
|
|
2810
3250
|
return new ZodUnion({
|
|
2811
3251
|
options: types,
|
|
2812
3252
|
typeName: ZodFirstPartyTypeKind.ZodUnion,
|
|
2813
|
-
...processCreateParams(
|
|
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,
|
|
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(
|
|
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,
|
|
3408
|
+
ZodIntersection.create = (left, right, params2) => {
|
|
2969
3409
|
return new ZodIntersection({
|
|
2970
3410
|
left,
|
|
2971
3411
|
right,
|
|
2972
3412
|
typeName: ZodFirstPartyTypeKind.ZodIntersection,
|
|
2973
|
-
...processCreateParams(
|
|
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,
|
|
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(
|
|
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,
|
|
3594
|
+
ZodMap.create = (keyType, valueType, params2) => {
|
|
3155
3595
|
return new ZodMap({
|
|
3156
3596
|
valueType,
|
|
3157
3597
|
keyType,
|
|
3158
3598
|
typeName: ZodFirstPartyTypeKind.ZodMap,
|
|
3159
|
-
...processCreateParams(
|
|
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,
|
|
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(
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
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,
|
|
3802
|
+
ZodLazy.create = (getter, params2) => {
|
|
3363
3803
|
return new ZodLazy({
|
|
3364
3804
|
getter,
|
|
3365
3805
|
typeName: ZodFirstPartyTypeKind.ZodLazy,
|
|
3366
|
-
...processCreateParams(
|
|
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,
|
|
3826
|
+
ZodLiteral.create = (value, params2) => {
|
|
3387
3827
|
return new ZodLiteral({
|
|
3388
3828
|
value,
|
|
3389
3829
|
typeName: ZodFirstPartyTypeKind.ZodLiteral,
|
|
3390
|
-
...processCreateParams(
|
|
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,
|
|
3929
|
+
ZodNativeEnum.create = (values, params2) => {
|
|
3490
3930
|
return new ZodNativeEnum({
|
|
3491
3931
|
values,
|
|
3492
3932
|
typeName: ZodFirstPartyTypeKind.ZodNativeEnum,
|
|
3493
|
-
...processCreateParams(
|
|
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,
|
|
3959
|
+
ZodPromise.create = (schema, params2) => {
|
|
3520
3960
|
return new ZodPromise({
|
|
3521
3961
|
type: schema,
|
|
3522
3962
|
typeName: ZodFirstPartyTypeKind.ZodPromise,
|
|
3523
|
-
...processCreateParams(
|
|
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,
|
|
4089
|
+
ZodEffects.create = (schema, effect, params2) => {
|
|
3650
4090
|
return new ZodEffects({
|
|
3651
4091
|
schema,
|
|
3652
4092
|
typeName: ZodFirstPartyTypeKind.ZodEffects,
|
|
3653
4093
|
effect,
|
|
3654
|
-
...processCreateParams(
|
|
4094
|
+
...processCreateParams(params2)
|
|
3655
4095
|
});
|
|
3656
4096
|
};
|
|
3657
|
-
ZodEffects.createWithPreprocess = (preprocess, schema,
|
|
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(
|
|
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,
|
|
4117
|
+
ZodOptional.create = (type, params2) => {
|
|
3678
4118
|
return new ZodOptional({
|
|
3679
4119
|
innerType: type,
|
|
3680
4120
|
typeName: ZodFirstPartyTypeKind.ZodOptional,
|
|
3681
|
-
...processCreateParams(
|
|
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,
|
|
4136
|
+
ZodNullable.create = (type, params2) => {
|
|
3697
4137
|
return new ZodNullable({
|
|
3698
4138
|
innerType: type,
|
|
3699
4139
|
typeName: ZodFirstPartyTypeKind.ZodNullable,
|
|
3700
|
-
...processCreateParams(
|
|
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,
|
|
4160
|
+
ZodDefault.create = (type, params2) => {
|
|
3721
4161
|
return new ZodDefault({
|
|
3722
4162
|
innerType: type,
|
|
3723
4163
|
typeName: ZodFirstPartyTypeKind.ZodDefault,
|
|
3724
|
-
defaultValue: typeof
|
|
3725
|
-
...processCreateParams(
|
|
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,
|
|
4213
|
+
ZodCatch.create = (type, params2) => {
|
|
3774
4214
|
return new ZodCatch({
|
|
3775
4215
|
innerType: type,
|
|
3776
4216
|
typeName: ZodFirstPartyTypeKind.ZodCatch,
|
|
3777
|
-
catchValue: typeof
|
|
3778
|
-
...processCreateParams(
|
|
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 = (
|
|
4236
|
+
ZodNaN.create = (params2) => {
|
|
3797
4237
|
return new ZodNaN({
|
|
3798
4238
|
typeName: ZodFirstPartyTypeKind.ZodNaN,
|
|
3799
|
-
...processCreateParams(
|
|
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,
|
|
4327
|
+
ZodReadonly.create = (type, params2) => {
|
|
3888
4328
|
return new ZodReadonly({
|
|
3889
4329
|
innerType: type,
|
|
3890
4330
|
typeName: ZodFirstPartyTypeKind.ZodReadonly,
|
|
3891
|
-
...processCreateParams(
|
|
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,
|
|
4375
|
+
instanceOfType = (cls, params2 = {
|
|
3936
4376
|
message: `Input not instance of ${cls.name}`
|
|
3937
|
-
}) => custom((data) => data instanceof cls,
|
|
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:
|
|
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
|
|
4572
|
+
const fs2 = io.fs ?? nodeFs;
|
|
4136
4573
|
const defaults = defaultConfig(io.home);
|
|
4137
|
-
const configPath =
|
|
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(
|
|
4576
|
+
const stat = lstatIfPresent(fs2, configPath);
|
|
4140
4577
|
if (stat) {
|
|
4141
|
-
assertSecureFile(
|
|
4578
|
+
assertSecureFile(fs2, configPath, "config file");
|
|
4142
4579
|
let parsed;
|
|
4143
4580
|
try {
|
|
4144
|
-
parsed = JSON.parse(readSecureFile(
|
|
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:
|
|
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
|
|
4232
|
-
const stateDir =
|
|
4233
|
-
assertSecureAncestors(
|
|
4234
|
-
const existing = lstatIfPresent(
|
|
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(
|
|
4615
|
+
createSecureDirectoryTree(fs2, stateDir, "state directory");
|
|
4237
4616
|
}
|
|
4238
|
-
assertSecureDirectory(
|
|
4239
|
-
const roomsPath =
|
|
4240
|
-
const rooms = lstatIfPresent(
|
|
4617
|
+
assertSecureDirectory(fs2, stateDir, "state directory");
|
|
4618
|
+
const roomsPath = join2(stateDir, "rooms");
|
|
4619
|
+
const rooms = lstatIfPresent(fs2, roomsPath);
|
|
4241
4620
|
if (!rooms) {
|
|
4242
|
-
|
|
4243
|
-
secureOpenedDirectory(
|
|
4244
|
-
fsyncDirectory(
|
|
4621
|
+
fs2.mkdirSync(roomsPath, { mode: DIRECTORY_MODE });
|
|
4622
|
+
secureOpenedDirectory(fs2, roomsPath, "rooms directory");
|
|
4623
|
+
fsyncDirectory(fs2, stateDir);
|
|
4245
4624
|
}
|
|
4246
|
-
assertSecureDirectory(
|
|
4625
|
+
assertSecureDirectory(fs2, roomsPath, "rooms directory");
|
|
4247
4626
|
return {
|
|
4248
|
-
socketPath:
|
|
4249
|
-
pidPath:
|
|
4250
|
-
lockPath:
|
|
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(
|
|
4262
|
-
const absolute = isAbsolute(path) ? 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 =
|
|
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 =
|
|
4269
|
-
const stat = lstatIfPresent(
|
|
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(
|
|
4667
|
+
function createSecureDirectoryTree(fs2, path, label) {
|
|
4289
4668
|
const missing = [];
|
|
4290
4669
|
let cursor = path;
|
|
4291
|
-
while (!lstatIfPresent(
|
|
4670
|
+
while (!lstatIfPresent(fs2, cursor)) {
|
|
4292
4671
|
missing.push(cursor);
|
|
4293
|
-
const parent =
|
|
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(
|
|
4676
|
+
assertSecureAncestors(fs2, path, label);
|
|
4298
4677
|
for (const directory of missing.reverse()) {
|
|
4299
|
-
|
|
4300
|
-
secureOpenedDirectory(
|
|
4301
|
-
fsyncDirectory(
|
|
4678
|
+
fs2.mkdirSync(directory, { mode: DIRECTORY_MODE });
|
|
4679
|
+
secureOpenedDirectory(fs2, directory, label);
|
|
4680
|
+
fsyncDirectory(fs2, dirname2(directory));
|
|
4302
4681
|
}
|
|
4303
4682
|
}
|
|
4304
|
-
function secureOpenedDirectory(
|
|
4683
|
+
function secureOpenedDirectory(fs2, path, label) {
|
|
4305
4684
|
let fd;
|
|
4306
4685
|
try {
|
|
4307
|
-
fd =
|
|
4308
|
-
const opened =
|
|
4309
|
-
const current =
|
|
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
|
-
|
|
4314
|
-
|
|
4692
|
+
fs2.fchmodSync(fd, DIRECTORY_MODE);
|
|
4693
|
+
fs2.fsyncSync(fd);
|
|
4315
4694
|
} finally {
|
|
4316
|
-
if (fd !== void 0)
|
|
4695
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
4317
4696
|
}
|
|
4318
4697
|
}
|
|
4319
|
-
function assertSecureDirectory(
|
|
4320
|
-
const stat =
|
|
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(
|
|
4329
|
-
const stat =
|
|
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(
|
|
4719
|
+
function readSecureFile(fs2, path, label) {
|
|
4341
4720
|
let fd;
|
|
4342
4721
|
try {
|
|
4343
|
-
fd =
|
|
4344
|
-
const opened =
|
|
4345
|
-
const current =
|
|
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
|
|
4728
|
+
return fs2.readFileSync(fd);
|
|
4350
4729
|
} finally {
|
|
4351
|
-
if (fd !== void 0)
|
|
4730
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
4352
4731
|
}
|
|
4353
4732
|
}
|
|
4354
|
-
function fsyncDirectory(
|
|
4733
|
+
function fsyncDirectory(fs2, path) {
|
|
4355
4734
|
let fd;
|
|
4356
4735
|
try {
|
|
4357
|
-
fd =
|
|
4358
|
-
|
|
4736
|
+
fd = fs2.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
|
|
4737
|
+
fs2.fsyncSync(fd);
|
|
4359
4738
|
} finally {
|
|
4360
|
-
if (fd !== void 0)
|
|
4739
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
4361
4740
|
}
|
|
4362
4741
|
}
|
|
4363
|
-
function lstatIfPresent(
|
|
4742
|
+
function lstatIfPresent(fs2, path) {
|
|
4364
4743
|
try {
|
|
4365
|
-
return
|
|
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,
|
|
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(
|
|
4725
|
-
return `${ROOM_IDENTITY_PREFIX}${
|
|
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.
|
|
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,
|
|
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
|
|
5179
|
-
const
|
|
5180
|
-
if (
|
|
5181
|
-
|
|
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 {
|
|
5400
|
-
import * as
|
|
5401
|
-
import { join as join3 } from "node:path";
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
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
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
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
|
|
5428
|
-
const
|
|
5429
|
-
if (
|
|
5430
|
-
|
|
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
|
|
5433
|
-
|
|
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
|
|
5436
|
-
if (
|
|
5592
|
+
function dictionaryEntries(value) {
|
|
5593
|
+
if (value.IsNil()) return [];
|
|
5594
|
+
return value.GetKeys().map((key) => [key.Visualize(), value.Reduce(key)]);
|
|
5437
5595
|
}
|
|
5438
|
-
function
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5453
|
-
constructor(
|
|
5454
|
-
super(
|
|
5455
|
-
|
|
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
|
-
|
|
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 ??
|
|
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.
|
|
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 = `
|
|
5717
|
+
async create(roomId, identityName = `cowork-room-${roomId}`, bio = `ours-cowork mission room ${roomId}`) {
|
|
5494
5718
|
validateRoomId(roomId);
|
|
5495
|
-
this.
|
|
5496
|
-
this.
|
|
5497
|
-
if (this.
|
|
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
|
-
|
|
5723
|
+
return await this.restore(roomId, void 0, identityName, bio);
|
|
5503
5724
|
} catch (error) {
|
|
5504
|
-
|
|
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
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
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
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
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
|
|
5761
|
+
async restore(roomId, expectedCid, identityName, bio) {
|
|
5525
5762
|
validateRoomId(roomId);
|
|
5526
|
-
this.
|
|
5527
|
-
this.
|
|
5528
|
-
|
|
5529
|
-
|
|
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
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
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
|
|
5549
|
-
if (
|
|
5550
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
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,
|
|
5853
|
+
for (const [roomId, room] of [...this.packets]) {
|
|
5565
5854
|
try {
|
|
5566
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5593
|
-
const
|
|
5594
|
-
|
|
5595
|
-
|
|
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
|
-
|
|
5599
|
-
|
|
5882
|
+
packetName(roomId) {
|
|
5883
|
+
return `cowork-room-${roomId}`;
|
|
5600
5884
|
}
|
|
5601
|
-
|
|
5602
|
-
|
|
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
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
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
|
-
|
|
5644
|
-
|
|
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
|
-
|
|
5647
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
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
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
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
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
5681
|
-
return
|
|
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
|
-
|
|
5684
|
-
return this.
|
|
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
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
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
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
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
|
|
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)
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
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
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
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
|
|
5738
|
-
|
|
5739
|
-
|
|
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
|
|
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
|
|
5784
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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 ||
|
|
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 ||
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
7089
|
-
|
|
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
|
-
|
|
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: "
|
|
7700
|
+
kind: "relay_intent",
|
|
7094
7701
|
room_id: id,
|
|
7095
7702
|
at: this.now(),
|
|
7096
|
-
message_id:
|
|
7097
|
-
|
|
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
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
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
|
-
|
|
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:
|
|
7149
|
-
|
|
7150
|
-
|
|
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
|
|
7161
|
-
if (!
|
|
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:
|
|
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 ===
|
|
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
|
|
8046
|
+
import * as nodeFs3 from "node:fs";
|
|
7441
8047
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7442
|
-
import { dirname as
|
|
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 =
|
|
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 ??
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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,
|
|
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>…</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
|
|
9454
|
+
import * as nodeFs4 from "node:fs";
|
|
8107
9455
|
import { extname, join as join5 } from "node:path";
|
|
8108
|
-
function loadWebAssets(root,
|
|
9456
|
+
function loadWebAssets(root, fs2 = nodeFs4) {
|
|
8109
9457
|
let rootIdentity;
|
|
8110
9458
|
try {
|
|
8111
|
-
rootIdentity = observeDirectory(
|
|
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(
|
|
9465
|
+
manifest.set("/", loadAsset(fs2, join5(root, "index.html"), false, [rootIdentity]));
|
|
8118
9466
|
const assetsDirectory = join5(root, "assets");
|
|
8119
|
-
assertDirectoryIdentity(
|
|
8120
|
-
const assetsIdentity = observeDirectory(
|
|
8121
|
-
assertDirectoryIdentity(
|
|
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(
|
|
8124
|
-
const assetEntries =
|
|
8125
|
-
assertDirectoryIdentities(
|
|
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(
|
|
9477
|
+
manifest.set(`/assets/${entry.name}`, loadAsset(fs2, path, true, directories));
|
|
8130
9478
|
}
|
|
8131
|
-
assertDirectoryIdentities(
|
|
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(
|
|
8155
|
-
assertDirectoryIdentities(
|
|
8156
|
-
const observed =
|
|
8157
|
-
assertDirectoryIdentities(
|
|
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 =
|
|
8164
|
-
const opened =
|
|
8165
|
-
assertDirectoryIdentities(
|
|
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 =
|
|
8170
|
-
assertDirectoryIdentities(
|
|
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)
|
|
9525
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
8178
9526
|
}
|
|
8179
9527
|
}
|
|
8180
|
-
function observeDirectory(
|
|
8181
|
-
const stat =
|
|
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(
|
|
8188
|
-
for (const directory of directories) assertDirectoryIdentity(
|
|
9535
|
+
function assertDirectoryIdentities(fs2, directories) {
|
|
9536
|
+
for (const directory of directories) assertDirectoryIdentity(fs2, directory);
|
|
8189
9537
|
}
|
|
8190
|
-
function assertDirectoryIdentity(
|
|
8191
|
-
const current =
|
|
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 =
|
|
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
|
|
8268
|
-
import { basename, dirname as
|
|
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: (
|
|
8272
|
-
"room.settings": { auth: true, run: (
|
|
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(
|
|
9628
|
+
}).strict().parse(params2);
|
|
8281
9629
|
return service.updateRoom(room_id, input);
|
|
8282
9630
|
} },
|
|
8283
|
-
"room.briefing.role.set": { auth: true, run: (
|
|
8284
|
-
const { room_id, ...input } = RoleBriefingSetParams.parse(
|
|
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: (
|
|
8288
|
-
const { room_id, ...input } = RoleBriefingDeleteParams.parse(
|
|
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: (
|
|
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(
|
|
9645
|
+
}).strict().parse(params2);
|
|
8298
9646
|
return service.createInvite(room_id, input);
|
|
8299
9647
|
} },
|
|
8300
|
-
"room.participant.remove": { auth: true, run: (
|
|
8301
|
-
const { room_id, ...input } = ParticipantRemoveParams.parse(
|
|
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: (
|
|
8305
|
-
const { room_id, ...input } = ParticipantReplaceParams.parse(
|
|
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: (
|
|
8309
|
-
const value = InviteRevokeParams.parse(
|
|
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: (
|
|
8313
|
-
"room.recover.confirm": { auth: true, run: (
|
|
8314
|
-
const value = RecoverConfirmParams.parse(
|
|
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: (
|
|
8318
|
-
external_exports.object({}).strict().parse(
|
|
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: (
|
|
8322
|
-
"room.participants": { auth: true, run: (
|
|
8323
|
-
"room.history": { auth: true, run: (
|
|
8324
|
-
const { room_id, ...options } = HistoryParams.parse(
|
|
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: (
|
|
8328
|
-
const { room_id, ...input } = external_exports.object({ room_id: external_exports.string(), text: external_exports.unknown() }).strict().parse(
|
|
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
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
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: (
|
|
8341
|
-
const { room_id, ...input } = ExternalInviteAcceptParams.parse(
|
|
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 ??
|
|
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 =
|
|
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
|
|
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 = (
|
|
9009
|
-
if (Object.keys(
|
|
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(
|
|
9017
|
-
requireExact(
|
|
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(
|
|
9029
|
-
requireExact(
|
|
9030
|
-
if (
|
|
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
|
|
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 =
|
|
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
|
-
|
|
9049
|
-
writeAll(
|
|
10437
|
+
fs2.fchmodSync(fd, FILE_MODE3);
|
|
10438
|
+
writeAll(fs2, fd, Buffer.from(`${pid}
|
|
9050
10439
|
`, "ascii"));
|
|
9051
|
-
|
|
9052
|
-
const owned =
|
|
9053
|
-
|
|
10440
|
+
fs2.fsyncSync(fd);
|
|
10441
|
+
const owned = fs2.fstatSync(fd);
|
|
10442
|
+
fs2.closeSync(fd);
|
|
9054
10443
|
fd = void 0;
|
|
9055
|
-
fsyncDirectory2(
|
|
10444
|
+
fsyncDirectory2(fs2, stateDir);
|
|
9056
10445
|
const pidPath = join7(stateDir, "daemon.pid");
|
|
9057
|
-
if (lstatIfPresent2(
|
|
9058
|
-
const pidOwner = readSecurePid(
|
|
10446
|
+
if (lstatIfPresent2(fs2, pidPath)) {
|
|
10447
|
+
const pidOwner = readSecurePid(fs2, pidPath, "daemon PID");
|
|
9059
10448
|
if (alive(pidOwner)) {
|
|
9060
|
-
|
|
9061
|
-
fsyncDirectory2(
|
|
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(
|
|
10459
|
+
const current = lstatIfPresent2(fs2, path);
|
|
9071
10460
|
if (!current || current.dev !== owned.dev || current.ino !== owned.ino) return;
|
|
9072
|
-
const content = readSecurePid(
|
|
10461
|
+
const content = readSecurePid(fs2, path, "daemon lock");
|
|
9073
10462
|
if (content !== pid) return;
|
|
9074
|
-
|
|
9075
|
-
fsyncDirectory2(
|
|
10463
|
+
fs2.unlinkSync(path);
|
|
10464
|
+
fsyncDirectory2(fs2, stateDir);
|
|
9076
10465
|
}
|
|
9077
10466
|
};
|
|
9078
10467
|
} catch (error) {
|
|
9079
10468
|
if (fd !== void 0) try {
|
|
9080
|
-
|
|
10469
|
+
fs2.closeSync(fd);
|
|
9081
10470
|
} catch {
|
|
9082
10471
|
}
|
|
9083
10472
|
if (error.code !== "EEXIST") {
|
|
9084
10473
|
if (created) {
|
|
9085
10474
|
try {
|
|
9086
|
-
|
|
9087
|
-
fsyncDirectory2(
|
|
10475
|
+
fs2.unlinkSync(path);
|
|
10476
|
+
fsyncDirectory2(fs2, stateDir);
|
|
9088
10477
|
} catch {
|
|
9089
10478
|
}
|
|
9090
10479
|
}
|
|
9091
10480
|
throw error;
|
|
9092
10481
|
}
|
|
9093
|
-
const observed =
|
|
9094
|
-
const owner = readSecurePid(
|
|
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 =
|
|
10485
|
+
const current = fs2.lstatSync(path);
|
|
9097
10486
|
if (current.dev !== observed.dev || current.ino !== observed.ino) continue;
|
|
9098
|
-
|
|
9099
|
-
fsyncDirectory2(
|
|
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,
|
|
10493
|
+
function writeDaemonPid(stateDir, fs2 = nodeFs6, pid = process.pid) {
|
|
9105
10494
|
const path = join7(stateDir, "daemon.pid");
|
|
9106
|
-
const existing = lstatIfPresent2(
|
|
10495
|
+
const existing = lstatIfPresent2(fs2, path);
|
|
9107
10496
|
if (existing) {
|
|
9108
|
-
const owner = readSecurePid(
|
|
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 =
|
|
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
|
-
|
|
10501
|
+
fs2.unlinkSync(path);
|
|
9113
10502
|
}
|
|
9114
10503
|
let fd;
|
|
9115
10504
|
let created = false;
|
|
9116
10505
|
try {
|
|
9117
|
-
fd =
|
|
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
|
-
|
|
9120
|
-
writeAll(
|
|
10508
|
+
fs2.fchmodSync(fd, FILE_MODE3);
|
|
10509
|
+
writeAll(fs2, fd, Buffer.from(`${pid}
|
|
9121
10510
|
`, "ascii"));
|
|
9122
|
-
|
|
10511
|
+
fs2.fsyncSync(fd);
|
|
9123
10512
|
} catch (error) {
|
|
9124
10513
|
if (fd !== void 0) {
|
|
9125
10514
|
try {
|
|
9126
|
-
|
|
10515
|
+
fs2.closeSync(fd);
|
|
9127
10516
|
} catch {
|
|
9128
10517
|
}
|
|
9129
10518
|
fd = void 0;
|
|
9130
10519
|
}
|
|
9131
10520
|
if (created) try {
|
|
9132
|
-
|
|
10521
|
+
fs2.unlinkSync(path);
|
|
9133
10522
|
} catch {
|
|
9134
10523
|
}
|
|
9135
10524
|
throw error;
|
|
9136
10525
|
} finally {
|
|
9137
|
-
if (fd !== void 0)
|
|
10526
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
9138
10527
|
}
|
|
9139
|
-
fsyncDirectory2(
|
|
10528
|
+
fsyncDirectory2(fs2, stateDir);
|
|
9140
10529
|
}
|
|
9141
|
-
function removeDaemonPid(stateDir,
|
|
10530
|
+
function removeDaemonPid(stateDir, fs2 = nodeFs6, pid = process.pid) {
|
|
9142
10531
|
const path = join7(stateDir, "daemon.pid");
|
|
9143
|
-
const observed = lstatIfPresent2(
|
|
10532
|
+
const observed = lstatIfPresent2(fs2, path);
|
|
9144
10533
|
if (!observed) return;
|
|
9145
|
-
const owner = readSecurePid(
|
|
10534
|
+
const owner = readSecurePid(fs2, path, "daemon PID");
|
|
9146
10535
|
if (owner !== pid) return;
|
|
9147
|
-
const current =
|
|
10536
|
+
const current = fs2.lstatSync(path);
|
|
9148
10537
|
if (current.dev !== observed.dev || current.ino !== observed.ino) return;
|
|
9149
|
-
|
|
9150
|
-
fsyncDirectory2(
|
|
10538
|
+
fs2.unlinkSync(path);
|
|
10539
|
+
fsyncDirectory2(fs2, stateDir);
|
|
9151
10540
|
}
|
|
9152
|
-
function readSecurePid(
|
|
9153
|
-
const stat =
|
|
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 =
|
|
9164
|
-
const opened =
|
|
9165
|
-
const current =
|
|
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 =
|
|
10558
|
+
text = fs2.readFileSync(fd, "utf8");
|
|
9170
10559
|
} finally {
|
|
9171
|
-
if (fd !== void 0)
|
|
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(
|
|
10575
|
+
function writeAll(fs2, fd, bytes) {
|
|
9187
10576
|
let offset = 0;
|
|
9188
10577
|
while (offset < bytes.length) {
|
|
9189
|
-
const written =
|
|
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(
|
|
10583
|
+
function fsyncDirectory2(fs2, path) {
|
|
9195
10584
|
let fd;
|
|
9196
10585
|
try {
|
|
9197
|
-
fd =
|
|
9198
|
-
|
|
10586
|
+
fd = fs2.openSync(path, nodeFs6.constants.O_RDONLY | NO_FOLLOW4);
|
|
10587
|
+
fs2.fsyncSync(fd);
|
|
9199
10588
|
} finally {
|
|
9200
|
-
if (fd !== void 0)
|
|
10589
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
9201
10590
|
}
|
|
9202
10591
|
}
|
|
9203
|
-
function lstatIfPresent2(
|
|
10592
|
+
function lstatIfPresent2(fs2, path) {
|
|
9204
10593
|
try {
|
|
9205
|
-
return
|
|
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
|
-
|
|
9221
|
-
|
|
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 =
|
|
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 ??
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 ===
|
|
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;
|