@ours.network/cowork 0.4.0 → 0.4.1-nightly.20260816.4aaf940
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 +112 -10
- package/dist/daemon.js +988 -2226
- package/docs/01-prerequisites.md +1 -1
- package/docs/02-installation.md +1 -1
- package/docs/03-configuration.md +31 -1
- package/docs/04-daemon-lifecycle.md +4 -0
- package/docs/05-room-workflow.md +2 -2
- package/docs/06-invites.md +2 -0
- package/docs/07-messaging-history.md +2 -2
- package/docs/08-backup-restore.md +2 -2
- package/docs/09-service-management.md +4 -2
- package/docs/10-limitations.md +3 -2
- package/docs/11-web-console.md +1 -12
- package/package.json +3 -6
- package/dist/mufl_code/BBAE58CF78DEE59692F456EAFFA9A6109835B66846FC3989F6D201B8F4523A55.muflo +0 -0
package/dist/daemon.js
CHANGED
|
@@ -10,446 +10,6 @@ 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
|
-
|
|
453
13
|
// node_modules/zod/v3/helpers/util.js
|
|
454
14
|
var util, objectUtil, ZodParsedType, getParsedType;
|
|
455
15
|
var init_util = __esm({
|
|
@@ -861,8 +421,8 @@ var init_parseUtil = __esm({
|
|
|
861
421
|
"node_modules/zod/v3/helpers/parseUtil.js"() {
|
|
862
422
|
init_errors();
|
|
863
423
|
init_en();
|
|
864
|
-
makeIssue = (
|
|
865
|
-
const { data, path, errorMaps, issueData } =
|
|
424
|
+
makeIssue = (params) => {
|
|
425
|
+
const { data, path, errorMaps, issueData } = params;
|
|
866
426
|
const fullPath = [...path, ...issueData.path || []];
|
|
867
427
|
const fullIssue = {
|
|
868
428
|
...issueData,
|
|
@@ -971,17 +531,17 @@ var init_errorUtil = __esm({
|
|
|
971
531
|
});
|
|
972
532
|
|
|
973
533
|
// node_modules/zod/v3/types.js
|
|
974
|
-
function processCreateParams(
|
|
975
|
-
if (!
|
|
534
|
+
function processCreateParams(params) {
|
|
535
|
+
if (!params)
|
|
976
536
|
return {};
|
|
977
|
-
const { errorMap: errorMap2, invalid_type_error, required_error, description } =
|
|
537
|
+
const { errorMap: errorMap2, invalid_type_error, required_error, description } = params;
|
|
978
538
|
if (errorMap2 && (invalid_type_error || required_error)) {
|
|
979
539
|
throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);
|
|
980
540
|
}
|
|
981
541
|
if (errorMap2)
|
|
982
542
|
return { errorMap: errorMap2, description };
|
|
983
543
|
const customMap = (iss, ctx) => {
|
|
984
|
-
const { message } =
|
|
544
|
+
const { message } = params;
|
|
985
545
|
if (iss.code === "invalid_enum_value") {
|
|
986
546
|
return { message: message ?? ctx.defaultError };
|
|
987
547
|
}
|
|
@@ -1128,15 +688,15 @@ function mergeValues(a, b) {
|
|
|
1128
688
|
return { valid: false };
|
|
1129
689
|
}
|
|
1130
690
|
}
|
|
1131
|
-
function createZodEnum(values,
|
|
691
|
+
function createZodEnum(values, params) {
|
|
1132
692
|
return new ZodEnum({
|
|
1133
693
|
values,
|
|
1134
694
|
typeName: ZodFirstPartyTypeKind.ZodEnum,
|
|
1135
|
-
...processCreateParams(
|
|
695
|
+
...processCreateParams(params)
|
|
1136
696
|
});
|
|
1137
697
|
}
|
|
1138
|
-
function cleanParams(
|
|
1139
|
-
const p = typeof
|
|
698
|
+
function cleanParams(params, data) {
|
|
699
|
+
const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params;
|
|
1140
700
|
const p2 = typeof p === "string" ? { message: p } : p;
|
|
1141
701
|
return p2;
|
|
1142
702
|
}
|
|
@@ -1147,16 +707,16 @@ function custom(check, _params = {}, fatal) {
|
|
|
1147
707
|
if (r instanceof Promise) {
|
|
1148
708
|
return r.then((r2) => {
|
|
1149
709
|
if (!r2) {
|
|
1150
|
-
const
|
|
1151
|
-
const _fatal =
|
|
1152
|
-
ctx.addIssue({ code: "custom", ...
|
|
710
|
+
const params = cleanParams(_params, data);
|
|
711
|
+
const _fatal = params.fatal ?? fatal ?? true;
|
|
712
|
+
ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
|
|
1153
713
|
}
|
|
1154
714
|
});
|
|
1155
715
|
}
|
|
1156
716
|
if (!r) {
|
|
1157
|
-
const
|
|
1158
|
-
const _fatal =
|
|
1159
|
-
ctx.addIssue({ code: "custom", ...
|
|
717
|
+
const params = cleanParams(_params, data);
|
|
718
|
+
const _fatal = params.fatal ?? fatal ?? true;
|
|
719
|
+
ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
|
|
1160
720
|
}
|
|
1161
721
|
return;
|
|
1162
722
|
});
|
|
@@ -1249,20 +809,20 @@ var init_types = __esm({
|
|
|
1249
809
|
const result = this._parse(input);
|
|
1250
810
|
return Promise.resolve(result);
|
|
1251
811
|
}
|
|
1252
|
-
parse(data,
|
|
1253
|
-
const result = this.safeParse(data,
|
|
812
|
+
parse(data, params) {
|
|
813
|
+
const result = this.safeParse(data, params);
|
|
1254
814
|
if (result.success)
|
|
1255
815
|
return result.data;
|
|
1256
816
|
throw result.error;
|
|
1257
817
|
}
|
|
1258
|
-
safeParse(data,
|
|
818
|
+
safeParse(data, params) {
|
|
1259
819
|
const ctx = {
|
|
1260
820
|
common: {
|
|
1261
821
|
issues: [],
|
|
1262
|
-
async:
|
|
1263
|
-
contextualErrorMap:
|
|
822
|
+
async: params?.async ?? false,
|
|
823
|
+
contextualErrorMap: params?.errorMap
|
|
1264
824
|
},
|
|
1265
|
-
path:
|
|
825
|
+
path: params?.path || [],
|
|
1266
826
|
schemaErrorMap: this._def.errorMap,
|
|
1267
827
|
parent: null,
|
|
1268
828
|
data,
|
|
@@ -1307,20 +867,20 @@ var init_types = __esm({
|
|
|
1307
867
|
issues: ctx.common.issues
|
|
1308
868
|
});
|
|
1309
869
|
}
|
|
1310
|
-
async parseAsync(data,
|
|
1311
|
-
const result = await this.safeParseAsync(data,
|
|
870
|
+
async parseAsync(data, params) {
|
|
871
|
+
const result = await this.safeParseAsync(data, params);
|
|
1312
872
|
if (result.success)
|
|
1313
873
|
return result.data;
|
|
1314
874
|
throw result.error;
|
|
1315
875
|
}
|
|
1316
|
-
async safeParseAsync(data,
|
|
876
|
+
async safeParseAsync(data, params) {
|
|
1317
877
|
const ctx = {
|
|
1318
878
|
common: {
|
|
1319
879
|
issues: [],
|
|
1320
|
-
contextualErrorMap:
|
|
880
|
+
contextualErrorMap: params?.errorMap,
|
|
1321
881
|
async: true
|
|
1322
882
|
},
|
|
1323
|
-
path:
|
|
883
|
+
path: params?.path || [],
|
|
1324
884
|
schemaErrorMap: this._def.errorMap,
|
|
1325
885
|
parent: null,
|
|
1326
886
|
data,
|
|
@@ -2046,12 +1606,12 @@ var init_types = __esm({
|
|
|
2046
1606
|
return max;
|
|
2047
1607
|
}
|
|
2048
1608
|
};
|
|
2049
|
-
ZodString.create = (
|
|
1609
|
+
ZodString.create = (params) => {
|
|
2050
1610
|
return new ZodString({
|
|
2051
1611
|
checks: [],
|
|
2052
1612
|
typeName: ZodFirstPartyTypeKind.ZodString,
|
|
2053
|
-
coerce:
|
|
2054
|
-
...processCreateParams(
|
|
1613
|
+
coerce: params?.coerce ?? false,
|
|
1614
|
+
...processCreateParams(params)
|
|
2055
1615
|
});
|
|
2056
1616
|
};
|
|
2057
1617
|
ZodNumber = class _ZodNumber extends ZodType {
|
|
@@ -2278,12 +1838,12 @@ var init_types = __esm({
|
|
|
2278
1838
|
return Number.isFinite(min) && Number.isFinite(max);
|
|
2279
1839
|
}
|
|
2280
1840
|
};
|
|
2281
|
-
ZodNumber.create = (
|
|
1841
|
+
ZodNumber.create = (params) => {
|
|
2282
1842
|
return new ZodNumber({
|
|
2283
1843
|
checks: [],
|
|
2284
1844
|
typeName: ZodFirstPartyTypeKind.ZodNumber,
|
|
2285
|
-
coerce:
|
|
2286
|
-
...processCreateParams(
|
|
1845
|
+
coerce: params?.coerce || false,
|
|
1846
|
+
...processCreateParams(params)
|
|
2287
1847
|
});
|
|
2288
1848
|
};
|
|
2289
1849
|
ZodBigInt = class _ZodBigInt extends ZodType {
|
|
@@ -2450,12 +2010,12 @@ var init_types = __esm({
|
|
|
2450
2010
|
return max;
|
|
2451
2011
|
}
|
|
2452
2012
|
};
|
|
2453
|
-
ZodBigInt.create = (
|
|
2013
|
+
ZodBigInt.create = (params) => {
|
|
2454
2014
|
return new ZodBigInt({
|
|
2455
2015
|
checks: [],
|
|
2456
2016
|
typeName: ZodFirstPartyTypeKind.ZodBigInt,
|
|
2457
|
-
coerce:
|
|
2458
|
-
...processCreateParams(
|
|
2017
|
+
coerce: params?.coerce ?? false,
|
|
2018
|
+
...processCreateParams(params)
|
|
2459
2019
|
});
|
|
2460
2020
|
};
|
|
2461
2021
|
ZodBoolean = class extends ZodType {
|
|
@@ -2476,11 +2036,11 @@ var init_types = __esm({
|
|
|
2476
2036
|
return OK(input.data);
|
|
2477
2037
|
}
|
|
2478
2038
|
};
|
|
2479
|
-
ZodBoolean.create = (
|
|
2039
|
+
ZodBoolean.create = (params) => {
|
|
2480
2040
|
return new ZodBoolean({
|
|
2481
2041
|
typeName: ZodFirstPartyTypeKind.ZodBoolean,
|
|
2482
|
-
coerce:
|
|
2483
|
-
...processCreateParams(
|
|
2042
|
+
coerce: params?.coerce || false,
|
|
2043
|
+
...processCreateParams(params)
|
|
2484
2044
|
});
|
|
2485
2045
|
};
|
|
2486
2046
|
ZodDate = class _ZodDate extends ZodType {
|
|
@@ -2584,12 +2144,12 @@ var init_types = __esm({
|
|
|
2584
2144
|
return max != null ? new Date(max) : null;
|
|
2585
2145
|
}
|
|
2586
2146
|
};
|
|
2587
|
-
ZodDate.create = (
|
|
2147
|
+
ZodDate.create = (params) => {
|
|
2588
2148
|
return new ZodDate({
|
|
2589
2149
|
checks: [],
|
|
2590
|
-
coerce:
|
|
2150
|
+
coerce: params?.coerce || false,
|
|
2591
2151
|
typeName: ZodFirstPartyTypeKind.ZodDate,
|
|
2592
|
-
...processCreateParams(
|
|
2152
|
+
...processCreateParams(params)
|
|
2593
2153
|
});
|
|
2594
2154
|
};
|
|
2595
2155
|
ZodSymbol = class extends ZodType {
|
|
@@ -2607,10 +2167,10 @@ var init_types = __esm({
|
|
|
2607
2167
|
return OK(input.data);
|
|
2608
2168
|
}
|
|
2609
2169
|
};
|
|
2610
|
-
ZodSymbol.create = (
|
|
2170
|
+
ZodSymbol.create = (params) => {
|
|
2611
2171
|
return new ZodSymbol({
|
|
2612
2172
|
typeName: ZodFirstPartyTypeKind.ZodSymbol,
|
|
2613
|
-
...processCreateParams(
|
|
2173
|
+
...processCreateParams(params)
|
|
2614
2174
|
});
|
|
2615
2175
|
};
|
|
2616
2176
|
ZodUndefined = class extends ZodType {
|
|
@@ -2628,10 +2188,10 @@ var init_types = __esm({
|
|
|
2628
2188
|
return OK(input.data);
|
|
2629
2189
|
}
|
|
2630
2190
|
};
|
|
2631
|
-
ZodUndefined.create = (
|
|
2191
|
+
ZodUndefined.create = (params) => {
|
|
2632
2192
|
return new ZodUndefined({
|
|
2633
2193
|
typeName: ZodFirstPartyTypeKind.ZodUndefined,
|
|
2634
|
-
...processCreateParams(
|
|
2194
|
+
...processCreateParams(params)
|
|
2635
2195
|
});
|
|
2636
2196
|
};
|
|
2637
2197
|
ZodNull = class extends ZodType {
|
|
@@ -2649,10 +2209,10 @@ var init_types = __esm({
|
|
|
2649
2209
|
return OK(input.data);
|
|
2650
2210
|
}
|
|
2651
2211
|
};
|
|
2652
|
-
ZodNull.create = (
|
|
2212
|
+
ZodNull.create = (params) => {
|
|
2653
2213
|
return new ZodNull({
|
|
2654
2214
|
typeName: ZodFirstPartyTypeKind.ZodNull,
|
|
2655
|
-
...processCreateParams(
|
|
2215
|
+
...processCreateParams(params)
|
|
2656
2216
|
});
|
|
2657
2217
|
};
|
|
2658
2218
|
ZodAny = class extends ZodType {
|
|
@@ -2664,10 +2224,10 @@ var init_types = __esm({
|
|
|
2664
2224
|
return OK(input.data);
|
|
2665
2225
|
}
|
|
2666
2226
|
};
|
|
2667
|
-
ZodAny.create = (
|
|
2227
|
+
ZodAny.create = (params) => {
|
|
2668
2228
|
return new ZodAny({
|
|
2669
2229
|
typeName: ZodFirstPartyTypeKind.ZodAny,
|
|
2670
|
-
...processCreateParams(
|
|
2230
|
+
...processCreateParams(params)
|
|
2671
2231
|
});
|
|
2672
2232
|
};
|
|
2673
2233
|
ZodUnknown = class extends ZodType {
|
|
@@ -2679,10 +2239,10 @@ var init_types = __esm({
|
|
|
2679
2239
|
return OK(input.data);
|
|
2680
2240
|
}
|
|
2681
2241
|
};
|
|
2682
|
-
ZodUnknown.create = (
|
|
2242
|
+
ZodUnknown.create = (params) => {
|
|
2683
2243
|
return new ZodUnknown({
|
|
2684
2244
|
typeName: ZodFirstPartyTypeKind.ZodUnknown,
|
|
2685
|
-
...processCreateParams(
|
|
2245
|
+
...processCreateParams(params)
|
|
2686
2246
|
});
|
|
2687
2247
|
};
|
|
2688
2248
|
ZodNever = class extends ZodType {
|
|
@@ -2696,10 +2256,10 @@ var init_types = __esm({
|
|
|
2696
2256
|
return INVALID;
|
|
2697
2257
|
}
|
|
2698
2258
|
};
|
|
2699
|
-
ZodNever.create = (
|
|
2259
|
+
ZodNever.create = (params) => {
|
|
2700
2260
|
return new ZodNever({
|
|
2701
2261
|
typeName: ZodFirstPartyTypeKind.ZodNever,
|
|
2702
|
-
...processCreateParams(
|
|
2262
|
+
...processCreateParams(params)
|
|
2703
2263
|
});
|
|
2704
2264
|
};
|
|
2705
2265
|
ZodVoid = class extends ZodType {
|
|
@@ -2717,10 +2277,10 @@ var init_types = __esm({
|
|
|
2717
2277
|
return OK(input.data);
|
|
2718
2278
|
}
|
|
2719
2279
|
};
|
|
2720
|
-
ZodVoid.create = (
|
|
2280
|
+
ZodVoid.create = (params) => {
|
|
2721
2281
|
return new ZodVoid({
|
|
2722
2282
|
typeName: ZodFirstPartyTypeKind.ZodVoid,
|
|
2723
|
-
...processCreateParams(
|
|
2283
|
+
...processCreateParams(params)
|
|
2724
2284
|
});
|
|
2725
2285
|
};
|
|
2726
2286
|
ZodArray = class _ZodArray extends ZodType {
|
|
@@ -2814,14 +2374,14 @@ var init_types = __esm({
|
|
|
2814
2374
|
return this.min(1, message);
|
|
2815
2375
|
}
|
|
2816
2376
|
};
|
|
2817
|
-
ZodArray.create = (schema,
|
|
2377
|
+
ZodArray.create = (schema, params) => {
|
|
2818
2378
|
return new ZodArray({
|
|
2819
2379
|
type: schema,
|
|
2820
2380
|
minLength: null,
|
|
2821
2381
|
maxLength: null,
|
|
2822
2382
|
exactLength: null,
|
|
2823
2383
|
typeName: ZodFirstPartyTypeKind.ZodArray,
|
|
2824
|
-
...processCreateParams(
|
|
2384
|
+
...processCreateParams(params)
|
|
2825
2385
|
});
|
|
2826
2386
|
};
|
|
2827
2387
|
ZodObject = class _ZodObject extends ZodType {
|
|
@@ -3135,31 +2695,31 @@ var init_types = __esm({
|
|
|
3135
2695
|
return createZodEnum(util.objectKeys(this.shape));
|
|
3136
2696
|
}
|
|
3137
2697
|
};
|
|
3138
|
-
ZodObject.create = (shape,
|
|
2698
|
+
ZodObject.create = (shape, params) => {
|
|
3139
2699
|
return new ZodObject({
|
|
3140
2700
|
shape: () => shape,
|
|
3141
2701
|
unknownKeys: "strip",
|
|
3142
2702
|
catchall: ZodNever.create(),
|
|
3143
2703
|
typeName: ZodFirstPartyTypeKind.ZodObject,
|
|
3144
|
-
...processCreateParams(
|
|
2704
|
+
...processCreateParams(params)
|
|
3145
2705
|
});
|
|
3146
2706
|
};
|
|
3147
|
-
ZodObject.strictCreate = (shape,
|
|
2707
|
+
ZodObject.strictCreate = (shape, params) => {
|
|
3148
2708
|
return new ZodObject({
|
|
3149
2709
|
shape: () => shape,
|
|
3150
2710
|
unknownKeys: "strict",
|
|
3151
2711
|
catchall: ZodNever.create(),
|
|
3152
2712
|
typeName: ZodFirstPartyTypeKind.ZodObject,
|
|
3153
|
-
...processCreateParams(
|
|
2713
|
+
...processCreateParams(params)
|
|
3154
2714
|
});
|
|
3155
2715
|
};
|
|
3156
|
-
ZodObject.lazycreate = (shape,
|
|
2716
|
+
ZodObject.lazycreate = (shape, params) => {
|
|
3157
2717
|
return new ZodObject({
|
|
3158
2718
|
shape,
|
|
3159
2719
|
unknownKeys: "strip",
|
|
3160
2720
|
catchall: ZodNever.create(),
|
|
3161
2721
|
typeName: ZodFirstPartyTypeKind.ZodObject,
|
|
3162
|
-
...processCreateParams(
|
|
2722
|
+
...processCreateParams(params)
|
|
3163
2723
|
});
|
|
3164
2724
|
};
|
|
3165
2725
|
ZodUnion = class extends ZodType {
|
|
@@ -3246,11 +2806,11 @@ var init_types = __esm({
|
|
|
3246
2806
|
return this._def.options;
|
|
3247
2807
|
}
|
|
3248
2808
|
};
|
|
3249
|
-
ZodUnion.create = (types,
|
|
2809
|
+
ZodUnion.create = (types, params) => {
|
|
3250
2810
|
return new ZodUnion({
|
|
3251
2811
|
options: types,
|
|
3252
2812
|
typeName: ZodFirstPartyTypeKind.ZodUnion,
|
|
3253
|
-
...processCreateParams(
|
|
2813
|
+
...processCreateParams(params)
|
|
3254
2814
|
});
|
|
3255
2815
|
};
|
|
3256
2816
|
getDiscriminator = (type) => {
|
|
@@ -3337,7 +2897,7 @@ var init_types = __esm({
|
|
|
3337
2897
|
* @param types an array of object schemas
|
|
3338
2898
|
* @param params
|
|
3339
2899
|
*/
|
|
3340
|
-
static create(discriminator, options,
|
|
2900
|
+
static create(discriminator, options, params) {
|
|
3341
2901
|
const optionsMap = /* @__PURE__ */ new Map();
|
|
3342
2902
|
for (const type of options) {
|
|
3343
2903
|
const discriminatorValues = getDiscriminator(type.shape[discriminator]);
|
|
@@ -3356,7 +2916,7 @@ var init_types = __esm({
|
|
|
3356
2916
|
discriminator,
|
|
3357
2917
|
options,
|
|
3358
2918
|
optionsMap,
|
|
3359
|
-
...processCreateParams(
|
|
2919
|
+
...processCreateParams(params)
|
|
3360
2920
|
});
|
|
3361
2921
|
}
|
|
3362
2922
|
};
|
|
@@ -3405,12 +2965,12 @@ var init_types = __esm({
|
|
|
3405
2965
|
}
|
|
3406
2966
|
}
|
|
3407
2967
|
};
|
|
3408
|
-
ZodIntersection.create = (left, right,
|
|
2968
|
+
ZodIntersection.create = (left, right, params) => {
|
|
3409
2969
|
return new ZodIntersection({
|
|
3410
2970
|
left,
|
|
3411
2971
|
right,
|
|
3412
2972
|
typeName: ZodFirstPartyTypeKind.ZodIntersection,
|
|
3413
|
-
...processCreateParams(
|
|
2973
|
+
...processCreateParams(params)
|
|
3414
2974
|
});
|
|
3415
2975
|
};
|
|
3416
2976
|
ZodTuple = class _ZodTuple extends ZodType {
|
|
@@ -3469,7 +3029,7 @@ var init_types = __esm({
|
|
|
3469
3029
|
});
|
|
3470
3030
|
}
|
|
3471
3031
|
};
|
|
3472
|
-
ZodTuple.create = (schemas,
|
|
3032
|
+
ZodTuple.create = (schemas, params) => {
|
|
3473
3033
|
if (!Array.isArray(schemas)) {
|
|
3474
3034
|
throw new Error("You must pass an array of schemas to z.tuple([ ... ])");
|
|
3475
3035
|
}
|
|
@@ -3477,7 +3037,7 @@ var init_types = __esm({
|
|
|
3477
3037
|
items: schemas,
|
|
3478
3038
|
typeName: ZodFirstPartyTypeKind.ZodTuple,
|
|
3479
3039
|
rest: null,
|
|
3480
|
-
...processCreateParams(
|
|
3040
|
+
...processCreateParams(params)
|
|
3481
3041
|
});
|
|
3482
3042
|
};
|
|
3483
3043
|
ZodRecord = class _ZodRecord extends ZodType {
|
|
@@ -3591,12 +3151,12 @@ var init_types = __esm({
|
|
|
3591
3151
|
}
|
|
3592
3152
|
}
|
|
3593
3153
|
};
|
|
3594
|
-
ZodMap.create = (keyType, valueType,
|
|
3154
|
+
ZodMap.create = (keyType, valueType, params) => {
|
|
3595
3155
|
return new ZodMap({
|
|
3596
3156
|
valueType,
|
|
3597
3157
|
keyType,
|
|
3598
3158
|
typeName: ZodFirstPartyTypeKind.ZodMap,
|
|
3599
|
-
...processCreateParams(
|
|
3159
|
+
...processCreateParams(params)
|
|
3600
3160
|
});
|
|
3601
3161
|
};
|
|
3602
3162
|
ZodSet = class _ZodSet extends ZodType {
|
|
@@ -3675,13 +3235,13 @@ var init_types = __esm({
|
|
|
3675
3235
|
return this.min(1, message);
|
|
3676
3236
|
}
|
|
3677
3237
|
};
|
|
3678
|
-
ZodSet.create = (valueType,
|
|
3238
|
+
ZodSet.create = (valueType, params) => {
|
|
3679
3239
|
return new ZodSet({
|
|
3680
3240
|
valueType,
|
|
3681
3241
|
minSize: null,
|
|
3682
3242
|
maxSize: null,
|
|
3683
3243
|
typeName: ZodFirstPartyTypeKind.ZodSet,
|
|
3684
|
-
...processCreateParams(
|
|
3244
|
+
...processCreateParams(params)
|
|
3685
3245
|
});
|
|
3686
3246
|
};
|
|
3687
3247
|
ZodFunction = class _ZodFunction extends ZodType {
|
|
@@ -3721,18 +3281,18 @@ var init_types = __esm({
|
|
|
3721
3281
|
}
|
|
3722
3282
|
});
|
|
3723
3283
|
}
|
|
3724
|
-
const
|
|
3284
|
+
const params = { errorMap: ctx.common.contextualErrorMap };
|
|
3725
3285
|
const fn = ctx.data;
|
|
3726
3286
|
if (this._def.returns instanceof ZodPromise) {
|
|
3727
3287
|
const me = this;
|
|
3728
3288
|
return OK(async function(...args) {
|
|
3729
3289
|
const error = new ZodError([]);
|
|
3730
|
-
const parsedArgs = await me._def.args.parseAsync(args,
|
|
3290
|
+
const parsedArgs = await me._def.args.parseAsync(args, params).catch((e) => {
|
|
3731
3291
|
error.addIssue(makeArgsIssue(args, e));
|
|
3732
3292
|
throw error;
|
|
3733
3293
|
});
|
|
3734
3294
|
const result = await Reflect.apply(fn, this, parsedArgs);
|
|
3735
|
-
const parsedReturns = await me._def.returns._def.type.parseAsync(result,
|
|
3295
|
+
const parsedReturns = await me._def.returns._def.type.parseAsync(result, params).catch((e) => {
|
|
3736
3296
|
error.addIssue(makeReturnsIssue(result, e));
|
|
3737
3297
|
throw error;
|
|
3738
3298
|
});
|
|
@@ -3741,12 +3301,12 @@ var init_types = __esm({
|
|
|
3741
3301
|
} else {
|
|
3742
3302
|
const me = this;
|
|
3743
3303
|
return OK(function(...args) {
|
|
3744
|
-
const parsedArgs = me._def.args.safeParse(args,
|
|
3304
|
+
const parsedArgs = me._def.args.safeParse(args, params);
|
|
3745
3305
|
if (!parsedArgs.success) {
|
|
3746
3306
|
throw new ZodError([makeArgsIssue(args, parsedArgs.error)]);
|
|
3747
3307
|
}
|
|
3748
3308
|
const result = Reflect.apply(fn, this, parsedArgs.data);
|
|
3749
|
-
const parsedReturns = me._def.returns.safeParse(result,
|
|
3309
|
+
const parsedReturns = me._def.returns.safeParse(result, params);
|
|
3750
3310
|
if (!parsedReturns.success) {
|
|
3751
3311
|
throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]);
|
|
3752
3312
|
}
|
|
@@ -3780,12 +3340,12 @@ var init_types = __esm({
|
|
|
3780
3340
|
const validatedFunc = this.parse(func);
|
|
3781
3341
|
return validatedFunc;
|
|
3782
3342
|
}
|
|
3783
|
-
static create(args, returns,
|
|
3343
|
+
static create(args, returns, params) {
|
|
3784
3344
|
return new _ZodFunction({
|
|
3785
3345
|
args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()),
|
|
3786
3346
|
returns: returns || ZodUnknown.create(),
|
|
3787
3347
|
typeName: ZodFirstPartyTypeKind.ZodFunction,
|
|
3788
|
-
...processCreateParams(
|
|
3348
|
+
...processCreateParams(params)
|
|
3789
3349
|
});
|
|
3790
3350
|
}
|
|
3791
3351
|
};
|
|
@@ -3799,11 +3359,11 @@ var init_types = __esm({
|
|
|
3799
3359
|
return lazySchema._parse({ data: ctx.data, path: ctx.path, parent: ctx });
|
|
3800
3360
|
}
|
|
3801
3361
|
};
|
|
3802
|
-
ZodLazy.create = (getter,
|
|
3362
|
+
ZodLazy.create = (getter, params) => {
|
|
3803
3363
|
return new ZodLazy({
|
|
3804
3364
|
getter,
|
|
3805
3365
|
typeName: ZodFirstPartyTypeKind.ZodLazy,
|
|
3806
|
-
...processCreateParams(
|
|
3366
|
+
...processCreateParams(params)
|
|
3807
3367
|
});
|
|
3808
3368
|
};
|
|
3809
3369
|
ZodLiteral = class extends ZodType {
|
|
@@ -3823,11 +3383,11 @@ var init_types = __esm({
|
|
|
3823
3383
|
return this._def.value;
|
|
3824
3384
|
}
|
|
3825
3385
|
};
|
|
3826
|
-
ZodLiteral.create = (value,
|
|
3386
|
+
ZodLiteral.create = (value, params) => {
|
|
3827
3387
|
return new ZodLiteral({
|
|
3828
3388
|
value,
|
|
3829
3389
|
typeName: ZodFirstPartyTypeKind.ZodLiteral,
|
|
3830
|
-
...processCreateParams(
|
|
3390
|
+
...processCreateParams(params)
|
|
3831
3391
|
});
|
|
3832
3392
|
};
|
|
3833
3393
|
ZodEnum = class _ZodEnum extends ZodType {
|
|
@@ -3926,11 +3486,11 @@ var init_types = __esm({
|
|
|
3926
3486
|
return this._def.values;
|
|
3927
3487
|
}
|
|
3928
3488
|
};
|
|
3929
|
-
ZodNativeEnum.create = (values,
|
|
3489
|
+
ZodNativeEnum.create = (values, params) => {
|
|
3930
3490
|
return new ZodNativeEnum({
|
|
3931
3491
|
values,
|
|
3932
3492
|
typeName: ZodFirstPartyTypeKind.ZodNativeEnum,
|
|
3933
|
-
...processCreateParams(
|
|
3493
|
+
...processCreateParams(params)
|
|
3934
3494
|
});
|
|
3935
3495
|
};
|
|
3936
3496
|
ZodPromise = class extends ZodType {
|
|
@@ -3956,11 +3516,11 @@ var init_types = __esm({
|
|
|
3956
3516
|
}));
|
|
3957
3517
|
}
|
|
3958
3518
|
};
|
|
3959
|
-
ZodPromise.create = (schema,
|
|
3519
|
+
ZodPromise.create = (schema, params) => {
|
|
3960
3520
|
return new ZodPromise({
|
|
3961
3521
|
type: schema,
|
|
3962
3522
|
typeName: ZodFirstPartyTypeKind.ZodPromise,
|
|
3963
|
-
...processCreateParams(
|
|
3523
|
+
...processCreateParams(params)
|
|
3964
3524
|
});
|
|
3965
3525
|
};
|
|
3966
3526
|
ZodEffects = class extends ZodType {
|
|
@@ -4086,20 +3646,20 @@ var init_types = __esm({
|
|
|
4086
3646
|
util.assertNever(effect);
|
|
4087
3647
|
}
|
|
4088
3648
|
};
|
|
4089
|
-
ZodEffects.create = (schema, effect,
|
|
3649
|
+
ZodEffects.create = (schema, effect, params) => {
|
|
4090
3650
|
return new ZodEffects({
|
|
4091
3651
|
schema,
|
|
4092
3652
|
typeName: ZodFirstPartyTypeKind.ZodEffects,
|
|
4093
3653
|
effect,
|
|
4094
|
-
...processCreateParams(
|
|
3654
|
+
...processCreateParams(params)
|
|
4095
3655
|
});
|
|
4096
3656
|
};
|
|
4097
|
-
ZodEffects.createWithPreprocess = (preprocess, schema,
|
|
3657
|
+
ZodEffects.createWithPreprocess = (preprocess, schema, params) => {
|
|
4098
3658
|
return new ZodEffects({
|
|
4099
3659
|
schema,
|
|
4100
3660
|
effect: { type: "preprocess", transform: preprocess },
|
|
4101
3661
|
typeName: ZodFirstPartyTypeKind.ZodEffects,
|
|
4102
|
-
...processCreateParams(
|
|
3662
|
+
...processCreateParams(params)
|
|
4103
3663
|
});
|
|
4104
3664
|
};
|
|
4105
3665
|
ZodOptional = class extends ZodType {
|
|
@@ -4114,11 +3674,11 @@ var init_types = __esm({
|
|
|
4114
3674
|
return this._def.innerType;
|
|
4115
3675
|
}
|
|
4116
3676
|
};
|
|
4117
|
-
ZodOptional.create = (type,
|
|
3677
|
+
ZodOptional.create = (type, params) => {
|
|
4118
3678
|
return new ZodOptional({
|
|
4119
3679
|
innerType: type,
|
|
4120
3680
|
typeName: ZodFirstPartyTypeKind.ZodOptional,
|
|
4121
|
-
...processCreateParams(
|
|
3681
|
+
...processCreateParams(params)
|
|
4122
3682
|
});
|
|
4123
3683
|
};
|
|
4124
3684
|
ZodNullable = class extends ZodType {
|
|
@@ -4133,11 +3693,11 @@ var init_types = __esm({
|
|
|
4133
3693
|
return this._def.innerType;
|
|
4134
3694
|
}
|
|
4135
3695
|
};
|
|
4136
|
-
ZodNullable.create = (type,
|
|
3696
|
+
ZodNullable.create = (type, params) => {
|
|
4137
3697
|
return new ZodNullable({
|
|
4138
3698
|
innerType: type,
|
|
4139
3699
|
typeName: ZodFirstPartyTypeKind.ZodNullable,
|
|
4140
|
-
...processCreateParams(
|
|
3700
|
+
...processCreateParams(params)
|
|
4141
3701
|
});
|
|
4142
3702
|
};
|
|
4143
3703
|
ZodDefault = class extends ZodType {
|
|
@@ -4157,12 +3717,12 @@ var init_types = __esm({
|
|
|
4157
3717
|
return this._def.innerType;
|
|
4158
3718
|
}
|
|
4159
3719
|
};
|
|
4160
|
-
ZodDefault.create = (type,
|
|
3720
|
+
ZodDefault.create = (type, params) => {
|
|
4161
3721
|
return new ZodDefault({
|
|
4162
3722
|
innerType: type,
|
|
4163
3723
|
typeName: ZodFirstPartyTypeKind.ZodDefault,
|
|
4164
|
-
defaultValue: typeof
|
|
4165
|
-
...processCreateParams(
|
|
3724
|
+
defaultValue: typeof params.default === "function" ? params.default : () => params.default,
|
|
3725
|
+
...processCreateParams(params)
|
|
4166
3726
|
});
|
|
4167
3727
|
};
|
|
4168
3728
|
ZodCatch = class extends ZodType {
|
|
@@ -4210,12 +3770,12 @@ var init_types = __esm({
|
|
|
4210
3770
|
return this._def.innerType;
|
|
4211
3771
|
}
|
|
4212
3772
|
};
|
|
4213
|
-
ZodCatch.create = (type,
|
|
3773
|
+
ZodCatch.create = (type, params) => {
|
|
4214
3774
|
return new ZodCatch({
|
|
4215
3775
|
innerType: type,
|
|
4216
3776
|
typeName: ZodFirstPartyTypeKind.ZodCatch,
|
|
4217
|
-
catchValue: typeof
|
|
4218
|
-
...processCreateParams(
|
|
3777
|
+
catchValue: typeof params.catch === "function" ? params.catch : () => params.catch,
|
|
3778
|
+
...processCreateParams(params)
|
|
4219
3779
|
});
|
|
4220
3780
|
};
|
|
4221
3781
|
ZodNaN = class extends ZodType {
|
|
@@ -4233,10 +3793,10 @@ var init_types = __esm({
|
|
|
4233
3793
|
return { status: "valid", value: input.data };
|
|
4234
3794
|
}
|
|
4235
3795
|
};
|
|
4236
|
-
ZodNaN.create = (
|
|
3796
|
+
ZodNaN.create = (params) => {
|
|
4237
3797
|
return new ZodNaN({
|
|
4238
3798
|
typeName: ZodFirstPartyTypeKind.ZodNaN,
|
|
4239
|
-
...processCreateParams(
|
|
3799
|
+
...processCreateParams(params)
|
|
4240
3800
|
});
|
|
4241
3801
|
};
|
|
4242
3802
|
BRAND = Symbol("zod_brand");
|
|
@@ -4324,11 +3884,11 @@ var init_types = __esm({
|
|
|
4324
3884
|
return this._def.innerType;
|
|
4325
3885
|
}
|
|
4326
3886
|
};
|
|
4327
|
-
ZodReadonly.create = (type,
|
|
3887
|
+
ZodReadonly.create = (type, params) => {
|
|
4328
3888
|
return new ZodReadonly({
|
|
4329
3889
|
innerType: type,
|
|
4330
3890
|
typeName: ZodFirstPartyTypeKind.ZodReadonly,
|
|
4331
|
-
...processCreateParams(
|
|
3891
|
+
...processCreateParams(params)
|
|
4332
3892
|
});
|
|
4333
3893
|
};
|
|
4334
3894
|
late = {
|
|
@@ -4372,9 +3932,9 @@ var init_types = __esm({
|
|
|
4372
3932
|
ZodFirstPartyTypeKind2["ZodPipeline"] = "ZodPipeline";
|
|
4373
3933
|
ZodFirstPartyTypeKind2["ZodReadonly"] = "ZodReadonly";
|
|
4374
3934
|
})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
|
|
4375
|
-
instanceOfType = (cls,
|
|
3935
|
+
instanceOfType = (cls, params = {
|
|
4376
3936
|
message: `Input not instance of ${cls.name}`
|
|
4377
|
-
}) => custom((data) => data instanceof cls,
|
|
3937
|
+
}) => custom((data) => data instanceof cls, params);
|
|
4378
3938
|
stringType = ZodString.create;
|
|
4379
3939
|
numberType = ZodNumber.create;
|
|
4380
3940
|
nanType = ZodNaN.create;
|
|
@@ -4559,26 +4119,29 @@ var init_zod = __esm({
|
|
|
4559
4119
|
// src/config.ts
|
|
4560
4120
|
import * as nodeFs from "node:fs";
|
|
4561
4121
|
import { homedir } from "node:os";
|
|
4562
|
-
import { dirname
|
|
4122
|
+
import { dirname, isAbsolute, join, parse, resolve } from "node:path";
|
|
4123
|
+
function daemonMode(config) {
|
|
4124
|
+
return config.daemon?.mode ?? "embedded";
|
|
4125
|
+
}
|
|
4563
4126
|
function defaultConfig(home = homedir()) {
|
|
4564
4127
|
return {
|
|
4565
4128
|
version: 1,
|
|
4566
4129
|
brokerUrl: "wss://broker1.ours.network",
|
|
4567
|
-
stateDir:
|
|
4130
|
+
stateDir: resolve(home, ".ours-cowork"),
|
|
4568
4131
|
rest: { enabled: true, port: 3052 }
|
|
4569
4132
|
};
|
|
4570
4133
|
}
|
|
4571
4134
|
function loadConfig(env = process.env, io = {}) {
|
|
4572
|
-
const
|
|
4135
|
+
const fs3 = io.fs ?? nodeFs;
|
|
4573
4136
|
const defaults = defaultConfig(io.home);
|
|
4574
|
-
const configPath =
|
|
4137
|
+
const configPath = resolve(env.OURS_COWORK_CONFIG ?? join(io.home ?? homedir(), ".ours-cowork", "config.json"));
|
|
4575
4138
|
let file = defaults;
|
|
4576
|
-
const stat = lstatIfPresent(
|
|
4139
|
+
const stat = lstatIfPresent(fs3, configPath);
|
|
4577
4140
|
if (stat) {
|
|
4578
|
-
assertSecureFile(
|
|
4141
|
+
assertSecureFile(fs3, configPath, "config file");
|
|
4579
4142
|
let parsed;
|
|
4580
4143
|
try {
|
|
4581
|
-
parsed = JSON.parse(readSecureFile(
|
|
4144
|
+
parsed = JSON.parse(readSecureFile(fs3, configPath, "config file").toString("utf8"));
|
|
4582
4145
|
} catch (error) {
|
|
4583
4146
|
throw new CoworkConfigError(`malformed cowork config at ${configPath}`, { cause: error });
|
|
4584
4147
|
}
|
|
@@ -4591,42 +4154,100 @@ function loadConfig(env = process.env, io = {}) {
|
|
|
4591
4154
|
throw new CoworkConfigError(`configured cowork config does not exist: ${configPath}`);
|
|
4592
4155
|
}
|
|
4593
4156
|
const restPort = env.OURS_COWORK_REST_PORT === void 0 ? void 0 : parsePort(env.OURS_COWORK_REST_PORT);
|
|
4157
|
+
const daemon = mergeDaemonSelection(env, file.daemon);
|
|
4594
4158
|
try {
|
|
4595
4159
|
return CoworkConfigSchema.parse({
|
|
4596
4160
|
version: 1,
|
|
4597
4161
|
brokerUrl: env.OURS_COWORK_BROKER_URL ?? file.brokerUrl,
|
|
4598
|
-
stateDir:
|
|
4162
|
+
stateDir: resolve(env.OURS_COWORK_STATE_DIR ?? file.stateDir),
|
|
4599
4163
|
rest: {
|
|
4600
4164
|
enabled: restPort === void 0 ? file.rest.enabled : true,
|
|
4601
4165
|
port: restPort ?? file.rest.port
|
|
4602
|
-
}
|
|
4166
|
+
},
|
|
4167
|
+
...daemon === void 0 ? {} : { daemon }
|
|
4603
4168
|
});
|
|
4604
4169
|
} catch (error) {
|
|
4605
4170
|
throw new CoworkConfigError("invalid effective cowork config", { cause: error });
|
|
4606
4171
|
}
|
|
4607
4172
|
}
|
|
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
|
+
}
|
|
4608
4229
|
function ensureRuntimeState(config, io = {}) {
|
|
4609
4230
|
const parsed = CoworkConfigSchema.parse(config);
|
|
4610
|
-
const
|
|
4611
|
-
const stateDir =
|
|
4612
|
-
assertSecureAncestors(
|
|
4613
|
-
const existing = lstatIfPresent(
|
|
4231
|
+
const fs3 = io.fs ?? nodeFs;
|
|
4232
|
+
const stateDir = resolve(parsed.stateDir);
|
|
4233
|
+
assertSecureAncestors(fs3, stateDir, "state directory");
|
|
4234
|
+
const existing = lstatIfPresent(fs3, stateDir);
|
|
4614
4235
|
if (!existing) {
|
|
4615
|
-
createSecureDirectoryTree(
|
|
4236
|
+
createSecureDirectoryTree(fs3, stateDir, "state directory");
|
|
4616
4237
|
}
|
|
4617
|
-
assertSecureDirectory(
|
|
4618
|
-
const roomsPath =
|
|
4619
|
-
const rooms = lstatIfPresent(
|
|
4238
|
+
assertSecureDirectory(fs3, stateDir, "state directory");
|
|
4239
|
+
const roomsPath = join(stateDir, "rooms");
|
|
4240
|
+
const rooms = lstatIfPresent(fs3, roomsPath);
|
|
4620
4241
|
if (!rooms) {
|
|
4621
|
-
|
|
4622
|
-
secureOpenedDirectory(
|
|
4623
|
-
fsyncDirectory(
|
|
4242
|
+
fs3.mkdirSync(roomsPath, { mode: DIRECTORY_MODE });
|
|
4243
|
+
secureOpenedDirectory(fs3, roomsPath, "rooms directory");
|
|
4244
|
+
fsyncDirectory(fs3, stateDir);
|
|
4624
4245
|
}
|
|
4625
|
-
assertSecureDirectory(
|
|
4246
|
+
assertSecureDirectory(fs3, roomsPath, "rooms directory");
|
|
4626
4247
|
return {
|
|
4627
|
-
socketPath:
|
|
4628
|
-
pidPath:
|
|
4629
|
-
lockPath:
|
|
4248
|
+
socketPath: join(stateDir, "management.sock"),
|
|
4249
|
+
pidPath: join(stateDir, "daemon.pid"),
|
|
4250
|
+
lockPath: join(stateDir, "daemon.lock")
|
|
4630
4251
|
};
|
|
4631
4252
|
}
|
|
4632
4253
|
function parsePort(value) {
|
|
@@ -4637,15 +4258,15 @@ function parsePort(value) {
|
|
|
4637
4258
|
if (port > 65535) throw new CoworkConfigError("OURS_COWORK_REST_PORT must be from 1 to 65535");
|
|
4638
4259
|
return port;
|
|
4639
4260
|
}
|
|
4640
|
-
function assertSecureAncestors(
|
|
4641
|
-
const absolute = isAbsolute(path) ? path :
|
|
4261
|
+
function assertSecureAncestors(fs3, path, label) {
|
|
4262
|
+
const absolute = isAbsolute(path) ? path : resolve(path);
|
|
4642
4263
|
const root = parse(absolute).root;
|
|
4643
|
-
const rootOwner =
|
|
4264
|
+
const rootOwner = fs3.lstatSync(root).uid;
|
|
4644
4265
|
let cursor = root;
|
|
4645
4266
|
const components = absolute.slice(root.length).split("/").filter(Boolean);
|
|
4646
4267
|
for (const [index, component] of components.entries()) {
|
|
4647
|
-
cursor =
|
|
4648
|
-
const stat = lstatIfPresent(
|
|
4268
|
+
cursor = join(cursor, component);
|
|
4269
|
+
const stat = lstatIfPresent(fs3, cursor);
|
|
4649
4270
|
if (stat?.isSymbolicLink()) throw new CoworkConfigError(`${label} must not traverse a symbolic link (symlink): ${cursor}`);
|
|
4650
4271
|
if (!stat) break;
|
|
4651
4272
|
if (!stat.isDirectory()) {
|
|
@@ -4664,39 +4285,39 @@ function assertTrustedAncestor(stat, rootOwner, path, label) {
|
|
|
4664
4285
|
throw new CoworkConfigError(`${label} has an unsafe writable ancestor: ${path}`);
|
|
4665
4286
|
}
|
|
4666
4287
|
}
|
|
4667
|
-
function createSecureDirectoryTree(
|
|
4288
|
+
function createSecureDirectoryTree(fs3, path, label) {
|
|
4668
4289
|
const missing = [];
|
|
4669
4290
|
let cursor = path;
|
|
4670
|
-
while (!lstatIfPresent(
|
|
4291
|
+
while (!lstatIfPresent(fs3, cursor)) {
|
|
4671
4292
|
missing.push(cursor);
|
|
4672
|
-
const parent =
|
|
4293
|
+
const parent = dirname(cursor);
|
|
4673
4294
|
if (parent === cursor) throw new CoworkConfigError(`cannot locate an existing ancestor for ${label}`);
|
|
4674
4295
|
cursor = parent;
|
|
4675
4296
|
}
|
|
4676
|
-
assertSecureAncestors(
|
|
4297
|
+
assertSecureAncestors(fs3, path, label);
|
|
4677
4298
|
for (const directory of missing.reverse()) {
|
|
4678
|
-
|
|
4679
|
-
secureOpenedDirectory(
|
|
4680
|
-
fsyncDirectory(
|
|
4299
|
+
fs3.mkdirSync(directory, { mode: DIRECTORY_MODE });
|
|
4300
|
+
secureOpenedDirectory(fs3, directory, label);
|
|
4301
|
+
fsyncDirectory(fs3, dirname(directory));
|
|
4681
4302
|
}
|
|
4682
4303
|
}
|
|
4683
|
-
function secureOpenedDirectory(
|
|
4304
|
+
function secureOpenedDirectory(fs3, path, label) {
|
|
4684
4305
|
let fd;
|
|
4685
4306
|
try {
|
|
4686
|
-
fd =
|
|
4687
|
-
const opened =
|
|
4688
|
-
const current =
|
|
4307
|
+
fd = fs3.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
|
|
4308
|
+
const opened = fs3.fstatSync(fd);
|
|
4309
|
+
const current = fs3.lstatSync(path);
|
|
4689
4310
|
if (!opened.isDirectory() || current.isSymbolicLink() || opened.dev !== current.dev || opened.ino !== current.ino) {
|
|
4690
4311
|
throw new CoworkConfigError(`${label} changed while opening`);
|
|
4691
4312
|
}
|
|
4692
|
-
|
|
4693
|
-
|
|
4313
|
+
fs3.fchmodSync(fd, DIRECTORY_MODE);
|
|
4314
|
+
fs3.fsyncSync(fd);
|
|
4694
4315
|
} finally {
|
|
4695
|
-
if (fd !== void 0)
|
|
4316
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
4696
4317
|
}
|
|
4697
4318
|
}
|
|
4698
|
-
function assertSecureDirectory(
|
|
4699
|
-
const stat =
|
|
4319
|
+
function assertSecureDirectory(fs3, path, label) {
|
|
4320
|
+
const stat = fs3.lstatSync(path);
|
|
4700
4321
|
if (stat.isSymbolicLink()) throw new CoworkConfigError(`${label} must not be a symbolic link (symlink)`);
|
|
4701
4322
|
if (!stat.isDirectory()) throw new CoworkConfigError(`${label} must be a directory`);
|
|
4702
4323
|
if ((stat.mode & 511) !== DIRECTORY_MODE) {
|
|
@@ -4704,8 +4325,8 @@ function assertSecureDirectory(fs2, path, label) {
|
|
|
4704
4325
|
}
|
|
4705
4326
|
assertOwner(stat, label);
|
|
4706
4327
|
}
|
|
4707
|
-
function assertSecureFile(
|
|
4708
|
-
const stat =
|
|
4328
|
+
function assertSecureFile(fs3, path, label) {
|
|
4329
|
+
const stat = fs3.lstatSync(path);
|
|
4709
4330
|
if (stat.isSymbolicLink()) throw new CoworkConfigError(`${label} must not be a symbolic link (symlink)`);
|
|
4710
4331
|
if (!stat.isFile() || stat.nlink !== 1) throw new CoworkConfigError(`${label} must be a single-link regular file`);
|
|
4711
4332
|
if ((stat.mode & 511) !== FILE_MODE) throw new CoworkConfigError(`${label} mode must be 0600`);
|
|
@@ -4716,38 +4337,38 @@ function assertOwner(stat, label) {
|
|
|
4716
4337
|
throw new CoworkConfigError(`${label} must be owned by the current user`);
|
|
4717
4338
|
}
|
|
4718
4339
|
}
|
|
4719
|
-
function readSecureFile(
|
|
4340
|
+
function readSecureFile(fs3, path, label) {
|
|
4720
4341
|
let fd;
|
|
4721
4342
|
try {
|
|
4722
|
-
fd =
|
|
4723
|
-
const opened =
|
|
4724
|
-
const current =
|
|
4343
|
+
fd = fs3.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
|
|
4344
|
+
const opened = fs3.fstatSync(fd);
|
|
4345
|
+
const current = fs3.lstatSync(path);
|
|
4725
4346
|
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) {
|
|
4726
4347
|
throw new CoworkConfigError(`${label} changed while opening`);
|
|
4727
4348
|
}
|
|
4728
|
-
return
|
|
4349
|
+
return fs3.readFileSync(fd);
|
|
4729
4350
|
} finally {
|
|
4730
|
-
if (fd !== void 0)
|
|
4351
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
4731
4352
|
}
|
|
4732
4353
|
}
|
|
4733
|
-
function fsyncDirectory(
|
|
4354
|
+
function fsyncDirectory(fs3, path) {
|
|
4734
4355
|
let fd;
|
|
4735
4356
|
try {
|
|
4736
|
-
fd =
|
|
4737
|
-
|
|
4357
|
+
fd = fs3.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
|
|
4358
|
+
fs3.fsyncSync(fd);
|
|
4738
4359
|
} finally {
|
|
4739
|
-
if (fd !== void 0)
|
|
4360
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
4740
4361
|
}
|
|
4741
4362
|
}
|
|
4742
|
-
function lstatIfPresent(
|
|
4363
|
+
function lstatIfPresent(fs3, path) {
|
|
4743
4364
|
try {
|
|
4744
|
-
return
|
|
4365
|
+
return fs3.lstatSync(path);
|
|
4745
4366
|
} catch (error) {
|
|
4746
4367
|
if (error.code === "ENOENT") return void 0;
|
|
4747
4368
|
throw error;
|
|
4748
4369
|
}
|
|
4749
4370
|
}
|
|
4750
|
-
var DIRECTORY_MODE, FILE_MODE, NO_FOLLOW, CoworkConfigSchema, CoworkConfigError;
|
|
4371
|
+
var DIRECTORY_MODE, FILE_MODE, NO_FOLLOW, CoworkDaemonSchema, CoworkConfigSchema, CoworkConfigError;
|
|
4751
4372
|
var init_config = __esm({
|
|
4752
4373
|
"src/config.ts"() {
|
|
4753
4374
|
"use strict";
|
|
@@ -4755,6 +4376,33 @@ var init_config = __esm({
|
|
|
4755
4376
|
DIRECTORY_MODE = 448;
|
|
4756
4377
|
FILE_MODE = 384;
|
|
4757
4378
|
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
|
+
});
|
|
4758
4406
|
CoworkConfigSchema = external_exports.object({
|
|
4759
4407
|
version: external_exports.literal(1),
|
|
4760
4408
|
brokerUrl: external_exports.string().url().refine((value) => {
|
|
@@ -4765,7 +4413,10 @@ var init_config = __esm({
|
|
|
4765
4413
|
rest: external_exports.object({
|
|
4766
4414
|
enabled: external_exports.boolean(),
|
|
4767
4415
|
port: external_exports.number().int().min(1).max(65535)
|
|
4768
|
-
}).strict()
|
|
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()
|
|
4769
4420
|
}).strict();
|
|
4770
4421
|
CoworkConfigError = class extends Error {
|
|
4771
4422
|
constructor(message, options) {
|
|
@@ -4776,6 +4427,271 @@ var init_config = __esm({
|
|
|
4776
4427
|
}
|
|
4777
4428
|
});
|
|
4778
4429
|
|
|
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
|
+
|
|
4779
4695
|
// src/contracts.ts
|
|
4780
4696
|
import { createHash } from "node:crypto";
|
|
4781
4697
|
function utf8Bounded(label, maximumBytes) {
|
|
@@ -4805,15 +4721,15 @@ function isStrictRfc3339(value) {
|
|
|
4805
4721
|
function normalizeRoomName(value) {
|
|
4806
4722
|
return value.trim().normalize("NFC");
|
|
4807
4723
|
}
|
|
4808
|
-
function roomIdentityName(
|
|
4809
|
-
return `${ROOM_IDENTITY_PREFIX}${
|
|
4724
|
+
function roomIdentityName(roomId) {
|
|
4725
|
+
return `${ROOM_IDENTITY_PREFIX}${LowerCrockfordUlidSchema.parse(roomId)}`;
|
|
4810
4726
|
}
|
|
4811
4727
|
function legacyRoomIdentityName(roomId) {
|
|
4812
4728
|
return `cowork-room-${LowerCrockfordUlidSchema.parse(roomId)}`;
|
|
4813
4729
|
}
|
|
4814
4730
|
function refineRoomLineage(room, context) {
|
|
4815
4731
|
const pendingIdentityName = legacyRoomIdentityName(room.room_id);
|
|
4816
|
-
const currentPendingIdentityName =
|
|
4732
|
+
const currentPendingIdentityName = roomIdentityName(room.room_id);
|
|
4817
4733
|
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;
|
|
4818
4734
|
if (room.identity_cid === "" && !exactPacketPending) {
|
|
4819
4735
|
context.addIssue({
|
|
@@ -4940,7 +4856,7 @@ function refineMessageCategory(message, context) {
|
|
|
4940
4856
|
}
|
|
4941
4857
|
}
|
|
4942
4858
|
}
|
|
4943
|
-
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, MessageShape, RelayIntentShape, RelayResultStatusSchema, RelayResultShape, FileShape, MembershipIntentShape, MembershipResultShape, CloseNoticeIntentShape, CloseNoticeResultShape, MessageRecordSchema, FileRecordSchema, RelayIntentRecordSchema, RelayResultRecordSchema, MembershipIntentRecordSchema, MembershipResultRecordSchema, CloseNoticeIntentRecordSchema, CloseNoticeResultRecordSchema, RawCommunicationRecordSchema, CommunicationRecordSchema, AppendRecordSchema;
|
|
4859
|
+
var MAX_TEXT_BYTES, MAX_FILE_BYTES, MAX_HISTORY_PAGE_BYTES, MAX_MANAGEMENT_RESPONSE_BYTES, MAX_EXTERNAL_INVITE_BYTES, MAX_FILE_NAME_BYTES, MAX_MIME_BYTES, MAX_ROLE_BYTES, MAX_ROOM_NAME_CHARACTERS, NonEmptyStringSchema, PositiveSafeIntegerSchema, LowerCrockfordUlidSchema, Rfc3339Schema, RoomNameSchema, ROOM_IDENTITY_PREFIX, RoleSchema, MissionTextSchema, MessageTextSchema, FileNameSchema, FileMimeSchema, RoomStateSchema, SeatStateSchema, InviteModeSchema, DEFAULT_ROLE, InviteStateSchema, RelayStatusSchema, SeatV1Schema, SeatSchema, RoomInviteSchema, MissionV1Schema, MissionSchema, RoleBriefingSchema, RoomCommonShape, RoomV1Schema, CurrentRoomSchema, RoomSchema, CreateRoomInputSchema, UpdateRoomInputSchema, RoleBriefingSetInputSchema, RoleBriefingDeleteInputSchema, PostMessageInputSchema, ContainerIdSchema, AcceptExternalInviteInputSchema, AuthorSnapshotSchema, RecordCommonShape, AppendCommonShape, MembershipNoticeSchema, AuthorAliasSchema, ReplyReferenceSchema, MessageShape, RelayIntentShape, RelayResultStatusSchema, RelayResultShape, FileShape, MembershipIntentShape, MembershipResultShape, CloseNoticeIntentShape, CloseNoticeResultShape, MessageRecordSchema, FileRecordSchema, RelayIntentRecordSchema, RelayResultRecordSchema, MembershipIntentRecordSchema, MembershipResultRecordSchema, CloseNoticeIntentRecordSchema, CloseNoticeResultRecordSchema, RawCommunicationRecordSchema, CommunicationRecordSchema, AppendRecordSchema;
|
|
4944
4860
|
var init_contracts = __esm({
|
|
4945
4861
|
"src/contracts.ts"() {
|
|
4946
4862
|
"use strict";
|
|
@@ -4973,7 +4889,7 @@ var init_contracts = __esm({
|
|
|
4973
4889
|
});
|
|
4974
4890
|
}
|
|
4975
4891
|
});
|
|
4976
|
-
ROOM_IDENTITY_PREFIX = "ours-cowork-
|
|
4892
|
+
ROOM_IDENTITY_PREFIX = "ours-cowork-";
|
|
4977
4893
|
RoleSchema = utf8Bounded("role", MAX_ROLE_BYTES);
|
|
4978
4894
|
MissionTextSchema = utf8Bounded("mission text", MAX_TEXT_BYTES);
|
|
4979
4895
|
MessageTextSchema = utf8Bounded("message text", MAX_TEXT_BYTES);
|
|
@@ -5325,6 +5241,10 @@ var init_contracts = __esm({
|
|
|
5325
5241
|
participant_id: LowerCrockfordUlidSchema,
|
|
5326
5242
|
alias: NonEmptyStringSchema
|
|
5327
5243
|
}).strict();
|
|
5244
|
+
ReplyReferenceSchema = external_exports.object({
|
|
5245
|
+
wire_id: NonEmptyStringSchema,
|
|
5246
|
+
sentence: PositiveSafeIntegerSchema.optional()
|
|
5247
|
+
}).strict();
|
|
5328
5248
|
MessageShape = {
|
|
5329
5249
|
kind: external_exports.literal("message"),
|
|
5330
5250
|
message_id: LowerCrockfordUlidSchema,
|
|
@@ -5349,7 +5269,8 @@ var init_contracts = __esm({
|
|
|
5349
5269
|
}
|
|
5350
5270
|
}),
|
|
5351
5271
|
source_msg_id: external_exports.number().int().nonnegative().safe().optional(),
|
|
5352
|
-
source_wire_id: NonEmptyStringSchema.optional()
|
|
5272
|
+
source_wire_id: NonEmptyStringSchema.optional(),
|
|
5273
|
+
source_reply_to: ReplyReferenceSchema.optional()
|
|
5353
5274
|
};
|
|
5354
5275
|
RelayIntentShape = {
|
|
5355
5276
|
kind: external_exports.literal("relay_intent"),
|
|
@@ -5392,7 +5313,8 @@ var init_contracts = __esm({
|
|
|
5392
5313
|
}
|
|
5393
5314
|
}),
|
|
5394
5315
|
source_file_id: external_exports.number().int().nonnegative().safe(),
|
|
5395
|
-
source_wire_id: NonEmptyStringSchema.optional()
|
|
5316
|
+
source_wire_id: NonEmptyStringSchema.optional(),
|
|
5317
|
+
source_reply_to: ReplyReferenceSchema.optional()
|
|
5396
5318
|
};
|
|
5397
5319
|
MembershipIntentShape = {
|
|
5398
5320
|
kind: external_exports.literal("membership_intent"),
|
|
@@ -5474,217 +5396,93 @@ var init_contracts = __esm({
|
|
|
5474
5396
|
});
|
|
5475
5397
|
|
|
5476
5398
|
// src/packets.ts
|
|
5477
|
-
import {
|
|
5478
|
-
import * as
|
|
5479
|
-
import {
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
fileFd = ops.openSync(temp, nodeFs2.constants.O_CREAT | nodeFs2.constants.O_TRUNC | nodeFs2.constants.O_WRONLY, 384);
|
|
5486
|
-
ops.fchmodSync(fileFd, 384);
|
|
5487
|
-
let offset = 0;
|
|
5488
|
-
while (offset < bytes.byteLength) {
|
|
5489
|
-
const written = ops.writeSync(fileFd, bytes, offset, bytes.byteLength - offset, null);
|
|
5490
|
-
if (written <= 0) throw new Error(`short write while persisting ${target}`);
|
|
5491
|
-
offset += written;
|
|
5492
|
-
}
|
|
5493
|
-
ops.fsyncSync(fileFd);
|
|
5494
|
-
ops.closeSync(fileFd);
|
|
5495
|
-
fileFd = void 0;
|
|
5496
|
-
ops.renameSync(temp, target);
|
|
5497
|
-
ops.chmodSync(target, 384);
|
|
5498
|
-
directoryFd = ops.openSync(dirname3(target), nodeFs2.constants.O_RDONLY);
|
|
5499
|
-
ops.fsyncSync(directoryFd);
|
|
5500
|
-
ops.closeSync(directoryFd);
|
|
5501
|
-
directoryFd = void 0;
|
|
5502
|
-
} catch (error) {
|
|
5503
|
-
if (fileFd !== void 0) {
|
|
5504
|
-
try {
|
|
5505
|
-
ops.closeSync(fileFd);
|
|
5506
|
-
} catch {
|
|
5507
|
-
}
|
|
5508
|
-
}
|
|
5509
|
-
if (directoryFd !== void 0) {
|
|
5510
|
-
try {
|
|
5511
|
-
ops.closeSync(directoryFd);
|
|
5512
|
-
} catch {
|
|
5513
|
-
}
|
|
5514
|
-
}
|
|
5515
|
-
try {
|
|
5516
|
-
ops.rmSync(temp, { force: true });
|
|
5517
|
-
} catch {
|
|
5518
|
-
}
|
|
5519
|
-
throw new PacketPersistenceError(`failed to durably persist ${target}`, { cause: error });
|
|
5520
|
-
}
|
|
5521
|
-
}
|
|
5522
|
-
function renderInbox(value) {
|
|
5523
|
-
const output = [];
|
|
5524
|
-
if (value.IsNil()) return output;
|
|
5525
|
-
for (let index = 0; ; index += 1) {
|
|
5526
|
-
const message = value.Reduce(index);
|
|
5527
|
-
if (message.IsNil()) break;
|
|
5528
|
-
output.push({
|
|
5529
|
-
msg_id: Number(message.Reduce("msg_id").Visualize()),
|
|
5530
|
-
sender_id: message.Reduce("sender_id").Visualize(),
|
|
5531
|
-
sender_name: message.Reduce("sender_name").Visualize(),
|
|
5532
|
-
text: message.Reduce("text").Visualize(),
|
|
5533
|
-
date: adaptTimeToRfc3339(message.Reduce("date").Visualize()),
|
|
5534
|
-
status: message.Reduce("status").Visualize(),
|
|
5535
|
-
wire_id: message.Reduce("wire_id").Visualize()
|
|
5536
|
-
});
|
|
5537
|
-
}
|
|
5538
|
-
return output;
|
|
5539
|
-
}
|
|
5540
|
-
function renderFileInbox(value) {
|
|
5541
|
-
const output = [];
|
|
5542
|
-
if (value.IsNil()) return output;
|
|
5543
|
-
for (let index = 0; ; index += 1) {
|
|
5544
|
-
const file = value.Reduce(index);
|
|
5545
|
-
if (file.IsNil()) break;
|
|
5546
|
-
output.push({
|
|
5547
|
-
file_id: Number(file.Reduce("file_id").Visualize()),
|
|
5548
|
-
sender_id: file.Reduce("sender_id").Visualize(),
|
|
5549
|
-
sender_name: file.Reduce("sender_name").Visualize(),
|
|
5550
|
-
filename: file.Reduce("filename").Visualize(),
|
|
5551
|
-
mime: file.Reduce("mime").Visualize(),
|
|
5552
|
-
data: Buffer.from(file.Reduce("data").GetBinary()),
|
|
5553
|
-
date: adaptTimeToRfc3339(file.Reduce("date").Visualize()),
|
|
5554
|
-
status: file.Reduce("status").Visualize(),
|
|
5555
|
-
wire_id: file.Reduce("wire_id").Visualize()
|
|
5556
|
-
});
|
|
5557
|
-
}
|
|
5558
|
-
return output;
|
|
5559
|
-
}
|
|
5560
|
-
function renderIntegerArray(value) {
|
|
5561
|
-
const output = [];
|
|
5562
|
-
if (value.IsNil()) return output;
|
|
5563
|
-
for (let index = 0; ; index += 1) {
|
|
5564
|
-
const item = value.Reduce(index);
|
|
5565
|
-
if (item.IsNil()) break;
|
|
5566
|
-
output.push(Number(item.Visualize()));
|
|
5567
|
-
}
|
|
5568
|
-
return output;
|
|
5399
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
5400
|
+
import * as fs2 from "node:fs";
|
|
5401
|
+
import { join as join3 } from "node:path";
|
|
5402
|
+
import { brotliCompressSync, brotliDecompressSync, constants as zlibConstants } from "node:zlib";
|
|
5403
|
+
function sendResult(result) {
|
|
5404
|
+
if (result.kind === "refused" || result.kind === "migrating") return { status: "send_failed" };
|
|
5405
|
+
if (result.kind === "introduced") return { status: "queued" };
|
|
5406
|
+
return { status: "queued", wire_id: result.wireId };
|
|
5569
5407
|
}
|
|
5570
|
-
function
|
|
5571
|
-
|
|
5572
|
-
|
|
5408
|
+
async function fileItem(file, bytes) {
|
|
5409
|
+
return {
|
|
5410
|
+
file_id: file.file_id,
|
|
5411
|
+
sender_id: file.from.id,
|
|
5412
|
+
sender_name: file.from.name,
|
|
5413
|
+
filename: file.filename,
|
|
5414
|
+
mime: file.mime,
|
|
5415
|
+
data: Buffer.from(bytes),
|
|
5416
|
+
date: normalizeDate(file.date),
|
|
5417
|
+
wire_id: file.wire_id,
|
|
5418
|
+
reply_to: cloneReply(file.reply_to)
|
|
5419
|
+
};
|
|
5573
5420
|
}
|
|
5574
|
-
function
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
}
|
|
5579
|
-
return /true/i.test(value.Visualize());
|
|
5580
|
-
}
|
|
5421
|
+
function cloneReply(reply) {
|
|
5422
|
+
return reply === null ? null : {
|
|
5423
|
+
wire_id: reply.wire_id,
|
|
5424
|
+
...reply.sentence === void 0 ? {} : { sentence: reply.sentence }
|
|
5425
|
+
};
|
|
5581
5426
|
}
|
|
5582
|
-
function
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
decoded = value.GetBoolean();
|
|
5587
|
-
} catch (error) {
|
|
5588
|
-
throw new Error(`${label} must be a boolean`, { cause: error });
|
|
5589
|
-
}
|
|
5590
|
-
if (typeof decoded !== "boolean") throw new Error(`${label} must be a boolean`);
|
|
5591
|
-
return decoded;
|
|
5427
|
+
function normalizeDate(value) {
|
|
5428
|
+
const date = new Date(value);
|
|
5429
|
+
if (Number.isNaN(date.valueOf())) throw new Error(`SDK returned an invalid message timestamp: ${value}`);
|
|
5430
|
+
return date.toISOString();
|
|
5592
5431
|
}
|
|
5593
|
-
function
|
|
5594
|
-
return
|
|
5432
|
+
function hasOursCode(error, code) {
|
|
5433
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
5595
5434
|
}
|
|
5596
|
-
function
|
|
5597
|
-
|
|
5598
|
-
if (rfc3339) {
|
|
5599
|
-
const [, year2, month2, day2, hour2, minute2, second2, fraction2 = "", sign2, offsetHour2 = "0", offsetMinute = "0"] = rfc3339;
|
|
5600
|
-
if (sign2 === "-" && offsetHour2 === "00" && offsetMinute === "00") return invalidAdaptTime(value);
|
|
5601
|
-
return canonicalUtcTime(value, year2, month2, day2, hour2, minute2, second2, fraction2, sign2, offsetHour2, offsetMinute);
|
|
5602
|
-
}
|
|
5603
|
-
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);
|
|
5604
|
-
if (!native) return invalidAdaptTime(value);
|
|
5605
|
-
const [, year, month, day, hour, minute, second, fraction = "", sign, offsetHour = "0"] = native;
|
|
5606
|
-
if (sign === "-" && offsetHour === "0") return invalidAdaptTime(value);
|
|
5607
|
-
return canonicalUtcTime(value, year, month, day, hour, minute, second, fraction, sign, offsetHour, "0");
|
|
5435
|
+
function validateRoomId(roomId) {
|
|
5436
|
+
if (!/^[0-7][0-9a-hjkmnp-tv-z]{25}$/.test(roomId)) throw new Error(`invalid room id "${roomId}"`);
|
|
5608
5437
|
}
|
|
5609
|
-
function
|
|
5610
|
-
const
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
const hour = Number(hourText);
|
|
5614
|
-
const minute = Number(minuteText);
|
|
5615
|
-
const second = Number(secondText);
|
|
5616
|
-
const offsetHour = Number(offsetHourText);
|
|
5617
|
-
const offsetMinute = Number(offsetMinuteText);
|
|
5618
|
-
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
5619
|
-
const monthDays = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
5620
|
-
if (month < 1 || month > 12 || day < 1 || day > monthDays[month - 1] || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59) return invalidAdaptTime(source);
|
|
5621
|
-
const milliseconds = Number(fraction.slice(0, 3).padEnd(3, "0"));
|
|
5622
|
-
const local = /* @__PURE__ */ new Date(0);
|
|
5623
|
-
local.setUTCFullYear(year, month - 1, day);
|
|
5624
|
-
local.setUTCHours(hour, minute, second, milliseconds);
|
|
5625
|
-
if (local.getUTCFullYear() !== year || local.getUTCMonth() !== month - 1 || local.getUTCDate() !== day || local.getUTCHours() !== hour || local.getUTCMinutes() !== minute || local.getUTCSeconds() !== second) {
|
|
5626
|
-
return invalidAdaptTime(source);
|
|
5438
|
+
function unpackInvite(encoded, maximumBytes) {
|
|
5439
|
+
const normalized = encoded.replace(/\s+/g, "");
|
|
5440
|
+
if (maximumBytes !== void 0 && Buffer.byteLength(encoded, "utf8") > maximumBytes || normalized.length === 0 || !/^[A-Za-z0-9_-]+$/.test(normalized)) {
|
|
5441
|
+
throw new Error("the invite blob is empty, oversized, or invalid base64url");
|
|
5627
5442
|
}
|
|
5628
|
-
const
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
if (!/^\d{4}-/.test(canonical)) return invalidAdaptTime(source);
|
|
5632
|
-
return canonical;
|
|
5633
|
-
}
|
|
5634
|
-
function invalidAdaptTime(value) {
|
|
5635
|
-
throw new Error(`unexpected ADAPT time visualization: ${value}`);
|
|
5636
|
-
}
|
|
5637
|
-
function inviteMode(value) {
|
|
5638
|
-
const normalized = value.replace(/^\$/, "");
|
|
5639
|
-
if (normalized === "one_time" || normalized === "public") return normalized;
|
|
5640
|
-
throw new Error(`unexpected invite mode: ${value}`);
|
|
5641
|
-
}
|
|
5642
|
-
function exportSigningSecret(packet) {
|
|
5643
|
-
return withScope((lifetime) => Buffer.from(packet.readonlyTx("::actor::export_signing_secret", lifetime).Serialize()).toString("hex"));
|
|
5644
|
-
}
|
|
5645
|
-
function validateRoomId(roomId) {
|
|
5646
|
-
if (!/^[A-Za-z0-9_-]{1,128}$/.test(roomId)) throw new Error(`invalid room id: ${roomId}`);
|
|
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 }));
|
|
5647
5446
|
}
|
|
5648
|
-
var
|
|
5447
|
+
var LegacyCoworkStateError, PacketRegistry, SdkRoomPacket;
|
|
5649
5448
|
var init_packets = __esm({
|
|
5650
|
-
|
|
5449
|
+
"src/packets.ts"() {
|
|
5651
5450
|
"use strict";
|
|
5652
|
-
await init_adapt();
|
|
5653
5451
|
init_contracts();
|
|
5654
|
-
|
|
5655
|
-
constructor(
|
|
5656
|
-
super(
|
|
5657
|
-
|
|
5452
|
+
LegacyCoworkStateError = class extends Error {
|
|
5453
|
+
constructor(roomId) {
|
|
5454
|
+
super(
|
|
5455
|
+
`room "${roomId}" uses the pre-1.0 custom packet format and cannot be opened by the standard ours SDK runtime; back it up with the old release, recreate the room for cowork 1.0, and re-invite its participants`
|
|
5456
|
+
);
|
|
5457
|
+
this.name = "LegacyCoworkStateError";
|
|
5658
5458
|
}
|
|
5659
5459
|
};
|
|
5660
|
-
temporarySequence = 0;
|
|
5661
5460
|
PacketRegistry = class {
|
|
5662
5461
|
packets = /* @__PURE__ */ new Map();
|
|
5462
|
+
trackers = /* @__PURE__ */ new Map();
|
|
5663
5463
|
host;
|
|
5664
5464
|
stateDir;
|
|
5665
5465
|
fs;
|
|
5666
|
-
persistence;
|
|
5667
5466
|
log;
|
|
5668
|
-
seed;
|
|
5669
|
-
beforeExpose;
|
|
5670
5467
|
onNotify;
|
|
5671
|
-
|
|
5672
|
-
stagingName;
|
|
5468
|
+
unsubscribe;
|
|
5673
5469
|
constructor(host, stateDir, options = {}) {
|
|
5674
5470
|
this.host = host;
|
|
5675
5471
|
this.stateDir = stateDir;
|
|
5676
|
-
this.fs = options.fs ??
|
|
5677
|
-
this.persistence = options.persistence ?? this.fs;
|
|
5472
|
+
this.fs = options.fs ?? fs2;
|
|
5678
5473
|
this.log = options.log ?? (() => {
|
|
5679
5474
|
});
|
|
5680
|
-
this.seed = options.seed ?? (() => randomBytes(24).toString("hex"));
|
|
5681
|
-
this.beforeExpose = options.beforeExpose ?? (() => {
|
|
5682
|
-
});
|
|
5683
5475
|
this.onNotify = options.onNotify ?? (() => {
|
|
5684
5476
|
});
|
|
5685
|
-
this.
|
|
5477
|
+
this.unsubscribe = host.onIdentityNotify((name) => {
|
|
5478
|
+
const found = [...this.packets.entries()].find(([, packet2]) => packet2.name === name);
|
|
5479
|
+
if (!found) return;
|
|
5480
|
+
const [roomId, packet] = found;
|
|
5481
|
+
void packet.refresh().then(
|
|
5482
|
+
() => this.onNotify(roomId, "message_received"),
|
|
5483
|
+
(error) => this.log(`[${name}] failed to refresh SDK state after notification:`, error)
|
|
5484
|
+
);
|
|
5686
5485
|
});
|
|
5687
|
-
this.stagingName = options.stagingName ?? (() => `live.staging-${randomBytes(16).toString("hex")}`);
|
|
5688
5486
|
}
|
|
5689
5487
|
get size() {
|
|
5690
5488
|
return this.packets.size;
|
|
@@ -5692,563 +5490,253 @@ var init_packets = __esm({
|
|
|
5692
5490
|
get(roomId) {
|
|
5693
5491
|
return this.packets.get(roomId);
|
|
5694
5492
|
}
|
|
5695
|
-
async create(roomId, identityName = `cowork
|
|
5493
|
+
async create(roomId, identityName = `ours-cowork-${roomId}`, bio = `ours-cowork mission room ${roomId}`) {
|
|
5696
5494
|
validateRoomId(roomId);
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
if (this.
|
|
5495
|
+
this.assertStandardIdentity(roomId, identityName);
|
|
5496
|
+
this.assertNoLegacyState(roomId);
|
|
5497
|
+
if (this.packets.has(roomId)) throw new Error(`room identity "${roomId}" is already hosted`);
|
|
5498
|
+
const client = this.host.createClient();
|
|
5499
|
+
try {
|
|
5500
|
+
let cid;
|
|
5700
5501
|
try {
|
|
5701
|
-
|
|
5502
|
+
cid = (await client.chooseIdentity({ name: identityName, force: false })).cid;
|
|
5702
5503
|
} catch (error) {
|
|
5703
|
-
|
|
5504
|
+
if (!hasOursCode(error, "NO_SUCH_IDENTITY")) throw error;
|
|
5505
|
+
const created = await client.createIdentity({
|
|
5506
|
+
name: identityName,
|
|
5507
|
+
bio,
|
|
5508
|
+
exposeLocal: false,
|
|
5509
|
+
localAutoAccept: true
|
|
5510
|
+
});
|
|
5511
|
+
cid = created.info.cid;
|
|
5704
5512
|
}
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
let room;
|
|
5711
|
-
room = new HostedRoomPacket(
|
|
5712
|
-
native,
|
|
5713
|
-
() => this.saveState(native, liveDir),
|
|
5714
|
-
this.log,
|
|
5715
|
-
(event) => this.onNotify(roomId, event),
|
|
5716
|
-
() => {
|
|
5717
|
-
if (this.packets.get(roomId) === room) this.packets.delete(roomId);
|
|
5718
|
-
}
|
|
5719
|
-
);
|
|
5720
|
-
atomicWriteFileSync(this.identityPath(roomId), Buffer.from(exportSigningSecret(native), "utf8"), this.persistence);
|
|
5721
|
-
this.provisioningCheckpoint("identity");
|
|
5722
|
-
this.saveState(native, liveDir);
|
|
5723
|
-
this.provisioningCheckpoint("state");
|
|
5724
|
-
this.packets.set(roomId, room);
|
|
5725
|
-
await room.setIdentity(identityName, bio);
|
|
5726
|
-
this.provisioningCheckpoint("identity_applied");
|
|
5727
|
-
return room;
|
|
5513
|
+
const packet = new SdkRoomPacket(identityName, cid, client);
|
|
5514
|
+
await packet.refresh();
|
|
5515
|
+
this.packets.set(roomId, packet);
|
|
5516
|
+
this.track(roomId, identityName);
|
|
5517
|
+
return packet;
|
|
5728
5518
|
} catch (error) {
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
} catch {
|
|
5733
|
-
}
|
|
5734
|
-
}
|
|
5735
|
-
this.packets.delete(roomId);
|
|
5736
|
-
throw error;
|
|
5519
|
+
await client.releaseLease().catch(() => {
|
|
5520
|
+
});
|
|
5521
|
+
throw new Error(`failed to provision standard SDK identity for room "${roomId}"`, { cause: error });
|
|
5737
5522
|
}
|
|
5738
5523
|
}
|
|
5739
|
-
async restore(roomId, expectedCid, identityName
|
|
5524
|
+
async restore(roomId, expectedCid, identityName = `ours-cowork-${roomId}`) {
|
|
5740
5525
|
validateRoomId(roomId);
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
throw new Error(`invalid signing secret for room "${roomId}"`);
|
|
5746
|
-
}
|
|
5747
|
-
const stateBytes = this.fs.readFileSync(this.statePath(roomId));
|
|
5748
|
-
if (stateBytes.length === 0) throw new Error(`empty packet state for room "${roomId}"`);
|
|
5749
|
-
const native = await this.host.createPacket(
|
|
5750
|
-
this.packetName(roomId),
|
|
5751
|
-
this.seed(),
|
|
5752
|
-
secret,
|
|
5753
|
-
{ deferredExposure: true }
|
|
5754
|
-
);
|
|
5755
|
-
if (expectedCid !== void 0 && native.cid !== expectedCid) {
|
|
5756
|
-
try {
|
|
5757
|
-
this.host.removePacket(native.cid);
|
|
5758
|
-
} catch {
|
|
5759
|
-
}
|
|
5760
|
-
throw new Error(
|
|
5761
|
-
`restored room packet CID mismatch for "${roomId}": expected "${expectedCid}", found "${native.cid}"`
|
|
5762
|
-
);
|
|
5763
|
-
}
|
|
5764
|
-
let room;
|
|
5765
|
-
room = new HostedRoomPacket(
|
|
5766
|
-
native,
|
|
5767
|
-
() => this.saveState(native, liveDir),
|
|
5768
|
-
this.log,
|
|
5769
|
-
(event) => this.onNotify(roomId, event),
|
|
5770
|
-
() => {
|
|
5771
|
-
if (this.packets.get(roomId) === room) this.packets.delete(roomId);
|
|
5772
|
-
}
|
|
5773
|
-
);
|
|
5526
|
+
this.assertStandardIdentity(roomId, identityName);
|
|
5527
|
+
this.assertNoLegacyState(roomId);
|
|
5528
|
+
if (this.packets.has(roomId)) throw new Error(`room identity "${roomId}" is already hosted`);
|
|
5529
|
+
const client = this.host.createClient();
|
|
5774
5530
|
try {
|
|
5775
|
-
await
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
}
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
atomicWriteFileSync(
|
|
5785
|
-
this.ownershipPath(roomId),
|
|
5786
|
-
Buffer.from(`${roomId}
|
|
5787
|
-
`, "utf8"),
|
|
5788
|
-
this.persistence
|
|
5789
|
-
);
|
|
5790
|
-
await this.beforeExpose(room);
|
|
5791
|
-
this.host.exposePacket(native.cid);
|
|
5792
|
-
this.packets.set(roomId, room);
|
|
5793
|
-
return room;
|
|
5531
|
+
const bound = await client.chooseIdentity({ name: identityName, force: false });
|
|
5532
|
+
if (expectedCid !== void 0 && bound.cid !== expectedCid) {
|
|
5533
|
+
throw new Error(`restored room identity CID mismatch: expected "${expectedCid}", found "${bound.cid}"`);
|
|
5534
|
+
}
|
|
5535
|
+
const packet = new SdkRoomPacket(identityName, bound.cid, client);
|
|
5536
|
+
await packet.refresh();
|
|
5537
|
+
this.packets.set(roomId, packet);
|
|
5538
|
+
this.track(roomId, identityName);
|
|
5539
|
+
return packet;
|
|
5794
5540
|
} catch (error) {
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
} catch {
|
|
5798
|
-
}
|
|
5541
|
+
await client.releaseLease().catch(() => {
|
|
5542
|
+
});
|
|
5799
5543
|
throw error;
|
|
5800
5544
|
}
|
|
5801
5545
|
}
|
|
5802
5546
|
async destroy(roomId) {
|
|
5803
5547
|
validateRoomId(roomId);
|
|
5804
|
-
const
|
|
5805
|
-
if (
|
|
5806
|
-
this.host.removePacket(room.cid);
|
|
5807
|
-
this.packets.delete(roomId);
|
|
5808
|
-
}
|
|
5809
|
-
const liveDir = this.liveDir(roomId);
|
|
5810
|
-
let removalFailure;
|
|
5548
|
+
const packet = this.packets.get(roomId);
|
|
5549
|
+
if (!packet) return [];
|
|
5811
5550
|
try {
|
|
5812
|
-
|
|
5813
|
-
this.
|
|
5814
|
-
|
|
5551
|
+
await packet.destroy();
|
|
5552
|
+
this.packets.delete(roomId);
|
|
5553
|
+
return [];
|
|
5815
5554
|
} catch (error) {
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
const residue = this.residue(roomId);
|
|
5820
|
-
if (removalFailure !== void 0 && residue.length === 0) {
|
|
5821
|
-
throw new PacketPersistenceError(
|
|
5822
|
-
`live-state removal durability is uncertain for room "${roomId}"`,
|
|
5823
|
-
{ cause: removalFailure }
|
|
5824
|
-
);
|
|
5555
|
+
throw new Error(`failed to remove standard SDK identity for room "${roomId}"`, { cause: error });
|
|
5556
|
+
} finally {
|
|
5557
|
+
this.untrack(roomId);
|
|
5825
5558
|
}
|
|
5826
|
-
return residue;
|
|
5827
5559
|
}
|
|
5828
|
-
/** Unhost runtime packets while retaining every byte required for restart. */
|
|
5829
5560
|
async unhostAll() {
|
|
5561
|
+
this.unsubscribe();
|
|
5562
|
+
for (const roomId of [...this.trackers.keys()]) this.untrack(roomId);
|
|
5830
5563
|
const errors = [];
|
|
5831
|
-
for (const [roomId,
|
|
5564
|
+
for (const [roomId, packet] of [...this.packets]) {
|
|
5832
5565
|
try {
|
|
5833
|
-
|
|
5834
|
-
this.packets.delete(roomId);
|
|
5566
|
+
await packet.close();
|
|
5835
5567
|
} catch (error) {
|
|
5836
5568
|
errors.push(error);
|
|
5837
5569
|
}
|
|
5570
|
+
this.packets.delete(roomId);
|
|
5838
5571
|
}
|
|
5839
|
-
if (errors.length
|
|
5840
|
-
}
|
|
5841
|
-
saveState(packet, liveDir) {
|
|
5842
|
-
try {
|
|
5843
|
-
const bytes = withScope((lifetime) => Buffer.from(packet.readonlyTx("::actor::export_state", lifetime).Serialize()));
|
|
5844
|
-
atomicWriteFileSync(join3(liveDir, "state_data.bin"), bytes, this.persistence);
|
|
5845
|
-
} catch (error) {
|
|
5846
|
-
if (error instanceof PacketPersistenceError) throw error;
|
|
5847
|
-
throw new PacketPersistenceError(`failed to export state for packet "${packet.name}"`, { cause: error });
|
|
5848
|
-
}
|
|
5572
|
+
if (errors.length) throw new AggregateError(errors, "failed to release room SDK leases");
|
|
5849
5573
|
}
|
|
5850
|
-
|
|
5851
|
-
|
|
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);
|
|
5852
5586
|
try {
|
|
5853
|
-
|
|
5854
|
-
return [liveDir];
|
|
5587
|
+
dispose();
|
|
5855
5588
|
} catch (error) {
|
|
5856
|
-
|
|
5857
|
-
throw error;
|
|
5589
|
+
this.log(`failed to stop the notification watch for room "${roomId}":`, error);
|
|
5858
5590
|
}
|
|
5859
5591
|
}
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
return join3(this.stateDir, "rooms", roomId);
|
|
5865
|
-
}
|
|
5866
|
-
liveDir(roomId) {
|
|
5867
|
-
return join3(this.roomDir(roomId), "live");
|
|
5868
|
-
}
|
|
5869
|
-
identityPath(roomId) {
|
|
5870
|
-
return join3(this.liveDir(roomId), "identity.key");
|
|
5871
|
-
}
|
|
5872
|
-
statePath(roomId) {
|
|
5873
|
-
return join3(this.liveDir(roomId), "state_data.bin");
|
|
5874
|
-
}
|
|
5875
|
-
ownershipPath(roomId) {
|
|
5876
|
-
return join3(this.liveDir(roomId), ".cowork-provisioning-v1");
|
|
5877
|
-
}
|
|
5878
|
-
stagingJournalPath(roomId) {
|
|
5879
|
-
return join3(this.roomDir(roomId), ".cowork-provisioning-stage");
|
|
5880
|
-
}
|
|
5881
|
-
residuePath(roomId) {
|
|
5882
|
-
return join3(this.roomDir(roomId), "provisioning-residue");
|
|
5883
|
-
}
|
|
5884
|
-
hasRestorableState(roomId) {
|
|
5885
|
-
try {
|
|
5886
|
-
const secret = this.fs.readFileSync(this.identityPath(roomId), "utf8").trim();
|
|
5887
|
-
return /^[0-9a-f]+$/i.test(secret) && secret.length % 2 === 0 && this.fs.readFileSync(this.statePath(roomId)).length > 0;
|
|
5888
|
-
} catch {
|
|
5889
|
-
return false;
|
|
5592
|
+
assertNoLegacyState(roomId) {
|
|
5593
|
+
const live = join3(this.stateDir, "rooms", roomId, "live");
|
|
5594
|
+
if (this.fs.existsSync(join3(live, "identity.key")) || this.fs.existsSync(join3(live, "state_data.bin"))) {
|
|
5595
|
+
throw new LegacyCoworkStateError(roomId);
|
|
5890
5596
|
}
|
|
5891
5597
|
}
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
const liveDir = this.liveDir(roomId);
|
|
5895
|
-
if (!this.fs.existsSync(roomDir)) {
|
|
5896
|
-
this.fs.mkdirSync(roomDir, { recursive: true, mode: 448 });
|
|
5897
|
-
this.fs.chmodSync(roomDir, 448);
|
|
5898
|
-
}
|
|
5899
|
-
if (this.fs.existsSync(liveDir)) {
|
|
5900
|
-
let owned = false;
|
|
5901
|
-
try {
|
|
5902
|
-
owned = this.fs.readFileSync(this.ownershipPath(roomId), "utf8") === `${roomId}
|
|
5903
|
-
`;
|
|
5904
|
-
} catch {
|
|
5905
|
-
}
|
|
5906
|
-
if (owned) {
|
|
5907
|
-
this.assertSafeRoomDirectory(roomId);
|
|
5908
|
-
this.fs.rmSync(liveDir, { recursive: true, force: true });
|
|
5909
|
-
this.fsyncDirectory(roomDir);
|
|
5910
|
-
} else {
|
|
5911
|
-
this.assertSafeRoomDirectory(roomId);
|
|
5912
|
-
const residue = this.residuePath(roomId);
|
|
5913
|
-
if (this.fs.existsSync(residue)) {
|
|
5914
|
-
throw new PacketPersistenceError(
|
|
5915
|
-
`room "${roomId}" has unknown live state and an existing provisioning residue; inspect both before retrying`
|
|
5916
|
-
);
|
|
5917
|
-
}
|
|
5918
|
-
this.fs.renameSync(liveDir, residue);
|
|
5919
|
-
this.fs.chmodSync(residue, 448);
|
|
5920
|
-
this.fsyncDirectory(roomDir);
|
|
5921
|
-
}
|
|
5922
|
-
}
|
|
5923
|
-
this.cleanupOwnedStaging(roomId);
|
|
5924
|
-
const stagingName = this.stagingName();
|
|
5925
|
-
if (!/^live\.staging-[0-9a-f]{32}$/.test(stagingName)) {
|
|
5926
|
-
throw new PacketPersistenceError(`invalid provisioning staging name for room "${roomId}"`);
|
|
5927
|
-
}
|
|
5928
|
-
this.createStagingJournal(roomId, stagingName);
|
|
5929
|
-
const stagingDir = join3(roomDir, stagingName);
|
|
5930
|
-
try {
|
|
5931
|
-
this.fs.mkdirSync(stagingDir, { recursive: false, mode: 448 });
|
|
5932
|
-
} catch (error) {
|
|
5933
|
-
try {
|
|
5934
|
-
this.fs.unlinkSync(this.stagingJournalPath(roomId));
|
|
5935
|
-
this.fsyncDirectory(roomDir);
|
|
5936
|
-
} catch (cleanupError) {
|
|
5937
|
-
throw new AggregateError([error, cleanupError], `provisioning staging collision cleanup failed for room "${roomId}"`);
|
|
5938
|
-
}
|
|
5939
|
-
throw new PacketPersistenceError(`provisioning staging collision for room "${roomId}"`, { cause: error });
|
|
5940
|
-
}
|
|
5941
|
-
this.fs.chmodSync(stagingDir, 448);
|
|
5942
|
-
atomicWriteFileSync(
|
|
5943
|
-
join3(stagingDir, ".cowork-provisioning-v1"),
|
|
5944
|
-
Buffer.from(`${roomId}
|
|
5945
|
-
`, "utf8"),
|
|
5946
|
-
this.persistence
|
|
5947
|
-
);
|
|
5948
|
-
this.provisioningCheckpoint("mkdir");
|
|
5949
|
-
this.fs.renameSync(stagingDir, liveDir);
|
|
5950
|
-
this.fsyncDirectory(roomDir);
|
|
5951
|
-
this.fs.unlinkSync(this.stagingJournalPath(roomId));
|
|
5952
|
-
this.fsyncDirectory(roomDir);
|
|
5953
|
-
}
|
|
5954
|
-
cleanupOwnedStaging(roomId) {
|
|
5955
|
-
const journal = this.stagingJournalPath(roomId);
|
|
5956
|
-
let journalFd;
|
|
5957
|
-
try {
|
|
5958
|
-
try {
|
|
5959
|
-
journalFd = this.fs.openSync(
|
|
5960
|
-
journal,
|
|
5961
|
-
nodeFs2.constants.O_RDONLY | (nodeFs2.constants.O_NOFOLLOW ?? 0)
|
|
5962
|
-
);
|
|
5963
|
-
} catch (error) {
|
|
5964
|
-
if (error.code === "ENOENT") return;
|
|
5965
|
-
throw error;
|
|
5966
|
-
}
|
|
5967
|
-
const journalStat = this.fs.fstatSync(journalFd);
|
|
5968
|
-
if (!journalStat.isFile() || journalStat.size > 128) {
|
|
5969
|
-
throw new PacketPersistenceError(`unsafe provisioning staging journal for room "${roomId}"`);
|
|
5970
|
-
}
|
|
5971
|
-
const stagingName = this.fs.readFileSync(journalFd, "utf8").trim();
|
|
5972
|
-
if (!/^live\.staging-[0-9a-f]{32}$/.test(stagingName)) {
|
|
5973
|
-
throw new PacketPersistenceError(`invalid provisioning staging journal for room "${roomId}"`);
|
|
5974
|
-
}
|
|
5975
|
-
const roomDir = this.roomDir(roomId);
|
|
5976
|
-
const stagingDir = join3(roomDir, stagingName);
|
|
5977
|
-
if (this.fs.existsSync(stagingDir)) {
|
|
5978
|
-
const stat = this.fs.lstatSync(stagingDir);
|
|
5979
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
5980
|
-
throw new PacketPersistenceError(`unsafe provisioning staging path for room "${roomId}"`);
|
|
5981
|
-
}
|
|
5982
|
-
let owned = false;
|
|
5983
|
-
try {
|
|
5984
|
-
owned = this.fs.readFileSync(join3(stagingDir, ".cowork-provisioning-v1"), "utf8") === `${roomId}
|
|
5985
|
-
`;
|
|
5986
|
-
} catch {
|
|
5987
|
-
}
|
|
5988
|
-
if (owned) {
|
|
5989
|
-
this.fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
5990
|
-
this.fsyncDirectory(roomDir);
|
|
5991
|
-
} else {
|
|
5992
|
-
const residue = this.residuePath(roomId);
|
|
5993
|
-
if (this.fs.existsSync(residue)) {
|
|
5994
|
-
throw new PacketPersistenceError(
|
|
5995
|
-
`provisioning staging recovery for room "${roomId}" found an existing provisioning residue; preserving both`
|
|
5996
|
-
);
|
|
5997
|
-
}
|
|
5998
|
-
this.fs.renameSync(stagingDir, residue);
|
|
5999
|
-
this.fs.chmodSync(residue, 448);
|
|
6000
|
-
this.fsyncDirectory(roomDir);
|
|
6001
|
-
}
|
|
6002
|
-
}
|
|
6003
|
-
const currentJournal = this.fs.lstatSync(journal);
|
|
6004
|
-
if (!currentJournal.isFile() || currentJournal.dev !== journalStat.dev || currentJournal.ino !== journalStat.ino) {
|
|
6005
|
-
throw new PacketPersistenceError(
|
|
6006
|
-
`provisioning staging journal changed during recovery for room "${roomId}"; replacement preserved`
|
|
6007
|
-
);
|
|
6008
|
-
}
|
|
6009
|
-
this.fs.unlinkSync(journal);
|
|
6010
|
-
this.fsyncDirectory(roomDir);
|
|
6011
|
-
} finally {
|
|
6012
|
-
if (journalFd !== void 0) this.fs.closeSync(journalFd);
|
|
6013
|
-
}
|
|
5598
|
+
assertStandardIdentity(roomId, identityName) {
|
|
5599
|
+
if (identityName !== `ours-cowork-${roomId}`) throw new LegacyCoworkStateError(roomId);
|
|
6014
5600
|
}
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
} catch {
|
|
6051
|
-
}
|
|
6052
|
-
}
|
|
6053
|
-
throw new PacketPersistenceError(`failed to establish staging ownership for room "${roomId}"`, { cause: error });
|
|
6054
|
-
}
|
|
5601
|
+
};
|
|
5602
|
+
SdkRoomPacket = class {
|
|
5603
|
+
name;
|
|
5604
|
+
cid;
|
|
5605
|
+
client;
|
|
5606
|
+
contacts = [];
|
|
5607
|
+
invites = [];
|
|
5608
|
+
inbox = [];
|
|
5609
|
+
fileInbox = [];
|
|
5610
|
+
refreshWork;
|
|
5611
|
+
constructor(name, cid, client) {
|
|
5612
|
+
this.name = name;
|
|
5613
|
+
this.cid = cid;
|
|
5614
|
+
this.client = client;
|
|
5615
|
+
}
|
|
5616
|
+
refresh() {
|
|
5617
|
+
this.refreshWork ??= this.refreshUnlocked().finally(() => {
|
|
5618
|
+
this.refreshWork = void 0;
|
|
5619
|
+
});
|
|
5620
|
+
return this.refreshWork;
|
|
5621
|
+
}
|
|
5622
|
+
async refreshUnlocked() {
|
|
5623
|
+
const contacts = await this.client.listContacts();
|
|
5624
|
+
this.contacts = contacts.contacts.map((contact) => ({ ...contact }));
|
|
5625
|
+
this.invites = (await this.client.listInvites()).flatMap((invite) => invite.mode === "one_time" || invite.mode === "public" ? [{ invite_id: invite.invite_id, mode: invite.mode }] : []);
|
|
5626
|
+
this.inbox = (await this.client.listIncomingMessages()).filter((message) => message.status === "unread").map((message) => ({
|
|
5627
|
+
msg_id: message.msg_id,
|
|
5628
|
+
sender_id: message.sender_id,
|
|
5629
|
+
sender_name: message.sender_name,
|
|
5630
|
+
text: message.text,
|
|
5631
|
+
date: normalizeDate(message.date),
|
|
5632
|
+
wire_id: message.wire_id,
|
|
5633
|
+
reply_to: cloneReply(message.reply_to)
|
|
5634
|
+
}));
|
|
5635
|
+
await this.refreshFiles();
|
|
6055
5636
|
}
|
|
6056
|
-
|
|
6057
|
-
const
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
5637
|
+
async refreshFiles() {
|
|
5638
|
+
const unread = (await this.client.listIncomingFiles()).filter((file) => file.status === "unread");
|
|
5639
|
+
if (unread.length === 0) {
|
|
5640
|
+
this.fileInbox = [];
|
|
5641
|
+
return;
|
|
6061
5642
|
}
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
let fd;
|
|
5643
|
+
const ids = unread.map((file) => file.file_id);
|
|
5644
|
+
const wires = unread.map((file) => file.wire_id);
|
|
6065
5645
|
try {
|
|
6066
|
-
|
|
6067
|
-
this.
|
|
5646
|
+
await this.client.getFiles({ wire_ids: wires });
|
|
5647
|
+
this.fileInbox = await Promise.all(unread.map(async (file) => fileItem(file, await this.client.fetchFile(file.wire_id))));
|
|
6068
5648
|
} finally {
|
|
6069
|
-
|
|
5649
|
+
await this.client.deferFiles({ file_ids: ids }).catch(() => {
|
|
5650
|
+
});
|
|
6070
5651
|
}
|
|
6071
5652
|
}
|
|
6072
|
-
};
|
|
6073
|
-
HostedRoomPacket = class {
|
|
6074
|
-
name;
|
|
6075
|
-
cid;
|
|
6076
|
-
packet;
|
|
6077
|
-
constructor(packet, saveState, log, onNotify = () => {
|
|
6078
|
-
}, onTerminal = () => {
|
|
6079
|
-
}) {
|
|
6080
|
-
this.packet = packet;
|
|
6081
|
-
this.name = packet.name;
|
|
6082
|
-
this.cid = packet.cid;
|
|
6083
|
-
wireHandlers(packet, { onSaveState: saveState, onNotify: (event) => onNotify(event) }, log);
|
|
6084
|
-
packet.onTerminalClose?.(onTerminal);
|
|
6085
|
-
}
|
|
6086
|
-
async setIdentity(identityName, bio) {
|
|
6087
|
-
await withScopeAsync((lifetime) => this.packet.mutatingTx("::a2a_messaging::set_my_name", { name: identityName }, lifetime));
|
|
6088
|
-
await withScopeAsync((lifetime) => this.packet.mutatingTx("::a2a_messaging::set_my_bio", { bio }, lifetime));
|
|
6089
|
-
}
|
|
6090
5653
|
async mintInvite(mode) {
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
blob: packInvite(Buffer.from(result.Reduce("invite").GetBinary())),
|
|
6095
|
-
invite_id: result.Reduce("invite_id").Visualize(),
|
|
6096
|
-
reusable: booleanValue(result.Reduce("reusable"))
|
|
6097
|
-
};
|
|
6098
|
-
});
|
|
5654
|
+
const result = await this.client.generateInvite({ mode });
|
|
5655
|
+
await this.refresh();
|
|
5656
|
+
return { blob: result.blob, invite_id: result.inviteId, reusable: mode === "public" };
|
|
6099
5657
|
}
|
|
6100
5658
|
async addContact(invite) {
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
}, lifetime);
|
|
6111
|
-
return {
|
|
6112
|
-
invite_id: result.Reduce("invite_id").Visualize(),
|
|
6113
|
-
container_id: result.Reduce("container_id").Visualize(),
|
|
6114
|
-
inviter_name: result.Reduce("inviter_name").Visualize(),
|
|
6115
|
-
pending_name: result.Reduce("pending").Visualize()
|
|
6116
|
-
};
|
|
6117
|
-
});
|
|
5659
|
+
const decoded = unpackInvite(invite, MAX_EXTERNAL_INVITE_BYTES);
|
|
5660
|
+
const result = await this.client.addContact({ invite });
|
|
5661
|
+
await this.refresh();
|
|
5662
|
+
return {
|
|
5663
|
+
invite_id: createHash2("sha256").update(decoded).digest("hex"),
|
|
5664
|
+
container_id: result.cid,
|
|
5665
|
+
inviter_name: result.display,
|
|
5666
|
+
pending_name: result.display
|
|
5667
|
+
};
|
|
6118
5668
|
}
|
|
6119
5669
|
async revokeInvite(inviteId) {
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
});
|
|
5670
|
+
const result = await this.client.revokeInvite({ invite_id: inviteId });
|
|
5671
|
+
await this.refresh();
|
|
5672
|
+
return { revoked: result.revoked };
|
|
6124
5673
|
}
|
|
6125
5674
|
listInvites() {
|
|
6126
|
-
return
|
|
6127
|
-
const value = this.packet.readonlyTx("::a2a_messaging::list_invites", lifetime);
|
|
6128
|
-
return dictionaryEntries(value).map(([inviteId, invite]) => ({
|
|
6129
|
-
invite_id: inviteId,
|
|
6130
|
-
mode: inviteMode(invite.Reduce("mode").Visualize())
|
|
6131
|
-
}));
|
|
6132
|
-
});
|
|
5675
|
+
return this.invites.map((invite) => ({ ...invite }));
|
|
6133
5676
|
}
|
|
6134
5677
|
listContacts() {
|
|
6135
|
-
return
|
|
6136
|
-
const value = this.packet.readonlyTx("::a2a_messaging::list_contacts", lifetime);
|
|
6137
|
-
return dictionaryEntries(value).map(([, contact]) => ({
|
|
6138
|
-
name: contact.Reduce("name").Visualize(),
|
|
6139
|
-
container_id: contact.Reduce("container_id").Visualize()
|
|
6140
|
-
}));
|
|
6141
|
-
});
|
|
6142
|
-
}
|
|
6143
|
-
listContactOrigins() {
|
|
6144
|
-
return withScope((lifetime) => {
|
|
6145
|
-
const value = this.packet.readonlyTx("::a2a_messaging::list_contact_origins", lifetime);
|
|
6146
|
-
return Object.fromEntries(dictionaryEntries(value).map(([cid, origin]) => [cid, {
|
|
6147
|
-
via: origin.Reduce("via").Visualize(),
|
|
6148
|
-
invite_id: nilString(origin.Reduce("invite_id")),
|
|
6149
|
-
at: adaptTimeToRfc3339(origin.Reduce("at").Visualize())
|
|
6150
|
-
}]));
|
|
6151
|
-
});
|
|
5678
|
+
return this.contacts.map((contact) => ({ ...contact }));
|
|
6152
5679
|
}
|
|
6153
5680
|
peekInbox() {
|
|
6154
|
-
return
|
|
6155
|
-
}
|
|
6156
|
-
async consumeInbox(expectedIds) {
|
|
6157
|
-
return withScopeAsync(async (lifetime) => {
|
|
6158
|
-
const result = await this.packet.mutatingTx(
|
|
6159
|
-
"::actor::consume_messages",
|
|
6160
|
-
{ expected_ids: expectedIds },
|
|
6161
|
-
lifetime
|
|
6162
|
-
);
|
|
6163
|
-
return {
|
|
6164
|
-
consumed: renderIntegerArray(result.Reduce("consumed")),
|
|
6165
|
-
deferred: renderIntegerArray(result.Reduce("deferred"))
|
|
6166
|
-
};
|
|
6167
|
-
});
|
|
5681
|
+
return this.inbox.map((item) => ({ ...item }));
|
|
6168
5682
|
}
|
|
6169
5683
|
peekFileInbox() {
|
|
6170
|
-
return
|
|
6171
|
-
this.packet.readonlyTx("::actor::list_incoming_files", lifetime)
|
|
6172
|
-
).filter((file) => file.status === "unread").map(({ status: _status, ...file }) => file));
|
|
5684
|
+
return this.fileInbox.map((item) => ({ ...item, data: Buffer.from(item.data) }));
|
|
6173
5685
|
}
|
|
6174
|
-
async
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
deferred: renderIntegerArray(result.Reduce("deferred"))
|
|
6184
|
-
};
|
|
6185
|
-
});
|
|
5686
|
+
async consumeInbox(expectedIds) {
|
|
5687
|
+
if (expectedIds.length === 0) return { consumed: [], deferred: [] };
|
|
5688
|
+
const pulled = await this.client.getMessages();
|
|
5689
|
+
const expected = new Set(expectedIds);
|
|
5690
|
+
const consumed = pulled.messages.filter((message) => expected.has(message.msg_id)).map((message) => message.msg_id);
|
|
5691
|
+
const deferred = pulled.messages.filter((message) => !expected.has(message.msg_id)).map((message) => message.msg_id);
|
|
5692
|
+
if (deferred.length) await this.client.deferMessages({ msg_ids: deferred });
|
|
5693
|
+
await this.refresh();
|
|
5694
|
+
return { consumed, deferred };
|
|
6186
5695
|
}
|
|
6187
|
-
async
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
5696
|
+
async consumeFileInbox(expectedIds) {
|
|
5697
|
+
const expected = new Set(expectedIds);
|
|
5698
|
+
const selected = this.fileInbox.filter((file) => expected.has(file.file_id));
|
|
5699
|
+
if (selected.length) await this.client.getFiles({ wire_ids: selected.map((file) => file.wire_id) });
|
|
5700
|
+
this.fileInbox = this.fileInbox.filter((file) => !expected.has(file.file_id));
|
|
5701
|
+
return { consumed: selected.map((file) => file.file_id), deferred: [] };
|
|
5702
|
+
}
|
|
5703
|
+
async send(contactCid, body, replyTo) {
|
|
5704
|
+
return sendResult(await this.client.sendMessage({
|
|
5705
|
+
contact: contactCid,
|
|
5706
|
+
text: body,
|
|
5707
|
+
...replyTo === void 0 ? {} : {
|
|
5708
|
+
reply_to_wire_id: replyTo.wire_id,
|
|
5709
|
+
...replyTo.sentence === void 0 ? {} : { reply_to_sentence: replyTo.sentence }
|
|
5710
|
+
}
|
|
5711
|
+
}));
|
|
6197
5712
|
}
|
|
6198
|
-
async sendFile(contactCid, filename, mime, data) {
|
|
5713
|
+
async sendFile(contactCid, filename, mime, data, replyTo) {
|
|
6199
5714
|
const validName = FileNameSchema.parse(filename);
|
|
6200
5715
|
const validMime = FileMimeSchema.parse(mime);
|
|
6201
|
-
if (data.length > MAX_FILE_BYTES) {
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
// recovery depend on staging ownership and is deliberately forbidden.
|
|
6213
|
-
data: this.packet.newBinary(data, lifetime)
|
|
6214
|
-
},
|
|
6215
|
-
lifetime
|
|
6216
|
-
);
|
|
6217
|
-
const refused = !result.Reduce("downgrade_refused").IsNil() || !result.Reduce("migrating").IsNil();
|
|
6218
|
-
return refused ? { status: "send_failed" } : { status: "queued", wire_id: nilString(result.Reduce("wire_id")) || void 0 };
|
|
6219
|
-
});
|
|
5716
|
+
if (data.length > MAX_FILE_BYTES) throw new RangeError(`room files must be at most ${MAX_FILE_BYTES} bytes (2 MiB)`);
|
|
5717
|
+
return sendResult(await this.client.sendFile({
|
|
5718
|
+
contact: contactCid,
|
|
5719
|
+
data_base64: data.toString("base64"),
|
|
5720
|
+
filename: validName,
|
|
5721
|
+
mime: validMime,
|
|
5722
|
+
...replyTo === void 0 ? {} : {
|
|
5723
|
+
reply_to_wire_id: replyTo.wire_id,
|
|
5724
|
+
...replyTo.sentence === void 0 ? {} : { reply_to_sentence: replyTo.sentence }
|
|
5725
|
+
}
|
|
5726
|
+
}));
|
|
6220
5727
|
}
|
|
6221
5728
|
async removeContact(contactCid) {
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
lifetime
|
|
6227
|
-
);
|
|
6228
|
-
const notified = strictBooleanValue(result.Reduce("notified"), "remove_contact notified");
|
|
6229
|
-
const keyMaterialRetained = strictBooleanValue(
|
|
6230
|
-
result.Reduce("key_material_retained"),
|
|
6231
|
-
"remove_contact key_material_retained"
|
|
6232
|
-
);
|
|
6233
|
-
if (!keyMaterialRetained) {
|
|
6234
|
-
throw new Error("remove_contact key_material_retained must be true");
|
|
6235
|
-
}
|
|
6236
|
-
return {
|
|
6237
|
-
status: notified ? "queued" : "send_failed",
|
|
6238
|
-
notified,
|
|
6239
|
-
key_material_retained: true
|
|
6240
|
-
};
|
|
6241
|
-
});
|
|
5729
|
+
const result = await this.client.removeContact({ contact: contactCid });
|
|
5730
|
+
const notified = result.notified === true;
|
|
5731
|
+
await this.refresh();
|
|
5732
|
+
return { status: notified ? "queued" : "send_failed", notified, key_material_retained: true };
|
|
6242
5733
|
}
|
|
6243
|
-
async
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
);
|
|
6250
|
-
return Buffer.from(result.Reduce("signature").GetBinary()).toString("base64url");
|
|
6251
|
-
});
|
|
5734
|
+
async close() {
|
|
5735
|
+
await this.client.releaseLease();
|
|
5736
|
+
}
|
|
5737
|
+
async destroy() {
|
|
5738
|
+
await this.client.removeIdentity({ name: this.name });
|
|
5739
|
+
await this.client.releaseLease();
|
|
6252
5740
|
}
|
|
6253
5741
|
};
|
|
6254
5742
|
}
|
|
@@ -6286,15 +5774,18 @@ var init_ulid = __esm({
|
|
|
6286
5774
|
});
|
|
6287
5775
|
|
|
6288
5776
|
// src/intake.ts
|
|
6289
|
-
import { createHash as
|
|
5777
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
6290
5778
|
function canonicalJson(value) {
|
|
6291
5779
|
const encoded = JSON.stringify(canonicalValue(value));
|
|
6292
5780
|
if (encoded === void 0) throw new TypeError("canonical JSON value is not serializable");
|
|
6293
5781
|
return encoded;
|
|
6294
5782
|
}
|
|
6295
|
-
async function
|
|
6296
|
-
|
|
6297
|
-
|
|
5783
|
+
async function sendRoomBody(packet, recipientIdentity, unsigned) {
|
|
5784
|
+
return packet.send(recipientIdentity, canonicalJson(unsigned));
|
|
5785
|
+
}
|
|
5786
|
+
function sameReply(stored, observed) {
|
|
5787
|
+
if (stored === void 0 || observed == null) return stored === void 0 && observed == null;
|
|
5788
|
+
return stored.wire_id === observed.wire_id && stored.sentence === observed.sentence;
|
|
6298
5789
|
}
|
|
6299
5790
|
function canonicalValue(value) {
|
|
6300
5791
|
if (Array.isArray(value)) return value.map(canonicalValue);
|
|
@@ -6449,11 +5940,12 @@ var init_intake = __esm({
|
|
|
6449
5940
|
filename: parsedName.data,
|
|
6450
5941
|
mime: parsedMime.data,
|
|
6451
5942
|
size: bytes.length,
|
|
6452
|
-
sha256:
|
|
5943
|
+
sha256: createHash3("sha256").update(bytes).digest("hex"),
|
|
6453
5944
|
data_base64: bytes.toString("base64"),
|
|
6454
5945
|
recipient_identities: recipientIdentities,
|
|
6455
5946
|
source_file_id: item.file_id,
|
|
6456
|
-
...item.wire_id === "" ? {} : { source_wire_id: item.wire_id }
|
|
5947
|
+
...item.wire_id === "" ? {} : { source_wire_id: item.wire_id },
|
|
5948
|
+
...item.reply_to == null ? {} : { source_reply_to: item.reply_to }
|
|
6457
5949
|
});
|
|
6458
5950
|
if (appended.kind !== "file") throw new Error("storage returned the wrong participant file kind");
|
|
6459
5951
|
file = appended;
|
|
@@ -6493,7 +5985,8 @@ var init_intake = __esm({
|
|
|
6493
5985
|
text: item.text,
|
|
6494
5986
|
recipient_identities: recipientIdentities,
|
|
6495
5987
|
source_msg_id: item.msg_id,
|
|
6496
|
-
...item.wire_id === "" ? {} : { source_wire_id: item.wire_id }
|
|
5988
|
+
...item.wire_id === "" ? {} : { source_wire_id: item.wire_id },
|
|
5989
|
+
...item.reply_to == null ? {} : { source_reply_to: item.reply_to }
|
|
6497
5990
|
});
|
|
6498
5991
|
if (appended.kind !== "message") throw new Error("storage returned the wrong participant message kind");
|
|
6499
5992
|
message = appended;
|
|
@@ -6518,7 +6011,7 @@ var init_intake = __esm({
|
|
|
6518
6011
|
await this.store.save(RoomSchema.parse({ ...room, seats }));
|
|
6519
6012
|
try {
|
|
6520
6013
|
const unsigned = { version: 1, kind: "room_not_member", room_id: roomId };
|
|
6521
|
-
await
|
|
6014
|
+
await sendRoomBody(packet, item.sender_id, unsigned);
|
|
6522
6015
|
} catch {
|
|
6523
6016
|
}
|
|
6524
6017
|
}
|
|
@@ -6602,7 +6095,7 @@ var init_intake = __esm({
|
|
|
6602
6095
|
display_name: file.author_alias.alias,
|
|
6603
6096
|
role: file.author.role
|
|
6604
6097
|
};
|
|
6605
|
-
const metadata = await
|
|
6098
|
+
const metadata = await sendRoomBody(packet, intent.recipient_identity, {
|
|
6606
6099
|
version: 1,
|
|
6607
6100
|
kind: "room_file",
|
|
6608
6101
|
room_id: roomId,
|
|
@@ -6668,7 +6161,7 @@ var init_intake = __esm({
|
|
|
6668
6161
|
...message.briefing_version === void 0 ? {} : { briefing_version: message.briefing_version },
|
|
6669
6162
|
...message.membership === void 0 ? {} : { membership: message.membership }
|
|
6670
6163
|
};
|
|
6671
|
-
const outcome = await
|
|
6164
|
+
const outcome = await sendRoomBody(packet, intent.recipient_identity, unsigned);
|
|
6672
6165
|
const appended = await this.store.append(roomId, {
|
|
6673
6166
|
version: 1,
|
|
6674
6167
|
kind: "relay_result",
|
|
@@ -6688,7 +6181,7 @@ var init_intake = __esm({
|
|
|
6688
6181
|
const message = records.find((record) => record.kind === "message" && record.source_msg_id === item.msg_id);
|
|
6689
6182
|
if (!message) return void 0;
|
|
6690
6183
|
const observedWireId = item.wire_id === "" ? void 0 : item.wire_id;
|
|
6691
|
-
if (message.source_wire_id !== observedWireId || message.author.identity !== item.sender_id || message.text !== item.text || message.at !== item.date) {
|
|
6184
|
+
if (message.source_wire_id !== observedWireId || !sameReply(message.source_reply_to, item.reply_to) || message.author.identity !== item.sender_id || message.text !== item.text || message.at !== item.date) {
|
|
6692
6185
|
throw new Error(`inbox source ${item.msg_id} does not match its durable room message`);
|
|
6693
6186
|
}
|
|
6694
6187
|
return message;
|
|
@@ -6698,7 +6191,7 @@ var init_intake = __esm({
|
|
|
6698
6191
|
if (!file) return void 0;
|
|
6699
6192
|
const observedWireId = item.wire_id === "" ? void 0 : item.wire_id;
|
|
6700
6193
|
const bytes = Buffer.from(item.data);
|
|
6701
|
-
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")) {
|
|
6194
|
+
if (file.source_wire_id !== observedWireId || !sameReply(file.source_reply_to, item.reply_to) || file.author.identity !== item.sender_id || file.filename !== item.filename || file.mime !== item.mime || file.at !== item.date || file.size !== bytes.length || file.data_base64 !== bytes.toString("base64")) {
|
|
6702
6195
|
throw new Error(`file inbox source ${item.file_id} does not match its durable room file`);
|
|
6703
6196
|
}
|
|
6704
6197
|
return file;
|
|
@@ -6719,7 +6212,7 @@ var init_intake = __esm({
|
|
|
6719
6212
|
});
|
|
6720
6213
|
|
|
6721
6214
|
// src/service.ts
|
|
6722
|
-
import { createHash as
|
|
6215
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
6723
6216
|
function byteBoundedHistoryPage(records) {
|
|
6724
6217
|
const page = [];
|
|
6725
6218
|
let bytes = 2;
|
|
@@ -6754,11 +6247,11 @@ function currentContactIdentities(packet) {
|
|
|
6754
6247
|
}
|
|
6755
6248
|
var ROOM_ROLE, CreateInviteInputSchema, HistoryOptionsSchema, DeleteRoomInputSchema, RemoveParticipantInputSchema, ReplaceParticipantInputSchema, RoomServiceError, RoomService;
|
|
6756
6249
|
var init_service = __esm({
|
|
6757
|
-
|
|
6250
|
+
"src/service.ts"() {
|
|
6758
6251
|
"use strict";
|
|
6759
6252
|
init_zod();
|
|
6760
6253
|
init_contracts();
|
|
6761
|
-
|
|
6254
|
+
init_packets();
|
|
6762
6255
|
init_intake();
|
|
6763
6256
|
init_ulid();
|
|
6764
6257
|
ROOM_ROLE = "room";
|
|
@@ -6824,7 +6317,7 @@ var init_service = __esm({
|
|
|
6824
6317
|
const settings = CreateRoomInputSchema.parse(input);
|
|
6825
6318
|
const roomId = LowerCrockfordUlidSchema.parse(this.nextRoomId());
|
|
6826
6319
|
const roomName = settings.name ?? defaultRoomName(roomId);
|
|
6827
|
-
const identityName = roomIdentityName(
|
|
6320
|
+
const identityName = roomIdentityName(roomId);
|
|
6828
6321
|
return this.lock(roomId, async () => {
|
|
6829
6322
|
const provisional = RoomSchema.parse({
|
|
6830
6323
|
version: 2,
|
|
@@ -6907,7 +6400,7 @@ var init_service = __esm({
|
|
|
6907
6400
|
throw new RoomServiceError(`room packet "${id}" with established CID must be restored, not created`);
|
|
6908
6401
|
}
|
|
6909
6402
|
try {
|
|
6910
|
-
packet = await this.packets.restore(id, room.identity_cid);
|
|
6403
|
+
packet = await this.packets.restore(id, room.identity_cid, room.identity_name);
|
|
6911
6404
|
} catch (error) {
|
|
6912
6405
|
throw new RoomServiceError(
|
|
6913
6406
|
`failed to restore established room packet "${id}": ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -7006,6 +6499,11 @@ var init_service = __esm({
|
|
|
7006
6499
|
return this.lock(id, async () => {
|
|
7007
6500
|
const room = await this.store.load(id);
|
|
7008
6501
|
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
|
+
}
|
|
7009
6507
|
return this.mintInviteUnlocked(room, {
|
|
7010
6508
|
mode: request.mode,
|
|
7011
6509
|
role: request.role ?? DEFAULT_ROLE,
|
|
@@ -7023,7 +6521,7 @@ var init_service = __esm({
|
|
|
7023
6521
|
} catch {
|
|
7024
6522
|
throw new RoomServiceError("external invite is invalid or exceeds the 48 KiB decoded limit");
|
|
7025
6523
|
}
|
|
7026
|
-
const digest =
|
|
6524
|
+
const digest = createHash4("sha256").update(decoded).digest("hex");
|
|
7027
6525
|
const receipt = await this.lock(id, async () => {
|
|
7028
6526
|
const room = await this.store.load(id);
|
|
7029
6527
|
this.assertMutable(room, "accept an external invite for");
|
|
@@ -7089,6 +6587,11 @@ var init_service = __esm({
|
|
|
7089
6587
|
return receipt;
|
|
7090
6588
|
}
|
|
7091
6589
|
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
|
+
}
|
|
7092
6595
|
const packet = this.packet(room.room_id);
|
|
7093
6596
|
const minted = await packet.mintInvite(request.mode);
|
|
7094
6597
|
const invite = RoomInviteSchema.parse({
|
|
@@ -7458,7 +6961,7 @@ var init_service = __esm({
|
|
|
7458
6961
|
}
|
|
7459
6962
|
const coreInvite = this.packet(id).listInvites().find((invite) => invite.invite_id === replacementId);
|
|
7460
6963
|
if (!coreInvite || coreInvite.mode !== replacement.mode) {
|
|
7461
|
-
throw new RoomServiceError("recovered invite is no longer present in
|
|
6964
|
+
throw new RoomServiceError("recovered invite is no longer present in SDK identity state");
|
|
7462
6965
|
}
|
|
7463
6966
|
const confirmed2 = {
|
|
7464
6967
|
invite_id: replacement.invite_id,
|
|
@@ -7621,7 +7124,6 @@ var init_service = __esm({
|
|
|
7621
7124
|
for (const contact of packet.listContacts()) {
|
|
7622
7125
|
if (!contactsByCid.has(contact.container_id)) contactsByCid.set(contact.container_id, contact.name);
|
|
7623
7126
|
}
|
|
7624
|
-
const origins = packet.listContactOrigins();
|
|
7625
7127
|
const inviteById = new Map(room.invites.map((invite) => [invite.invite_id, invite]));
|
|
7626
7128
|
const existingCids = new Set(room.seats.filter((seat) => seat.state === "active" || seat.state === "pending").map((seat) => seat.identity));
|
|
7627
7129
|
const lastRemovedAt = /* @__PURE__ */ new Map();
|
|
@@ -7637,37 +7139,26 @@ var init_service = __esm({
|
|
|
7637
7139
|
for (const [seatIndex, seat] of room.seats.entries()) {
|
|
7638
7140
|
if (seat.state !== "pending") continue;
|
|
7639
7141
|
const displayName = contactsByCid.get(seat.identity);
|
|
7640
|
-
|
|
7641
|
-
if (displayName === void 0 || origin === void 0) continue;
|
|
7642
|
-
const completedExternalRedemption = origin.via === "invite_redeemed" && origin.invite_id === seat.invite_id;
|
|
7643
|
-
const fallbackInvite = origin.via === "invite_one_time" || origin.via === "invite_public" ? inviteById.get(origin.invite_id) : void 0;
|
|
7644
|
-
const confirmedFallback = fallbackInvite !== void 0 && fallbackInvite.state !== "receipt_pending" && (fallbackInvite.recovery_of === void 0 || fallbackInvite.recovery_confirmed === true);
|
|
7645
|
-
if (!completedExternalRedemption && !confirmedFallback) continue;
|
|
7646
|
-
const removedAt = lastRemovedAt.get(seat.identity);
|
|
7647
|
-
if (fallbackInvite !== void 0 && removedAt !== void 0 && fallbackInvite.created_at <= removedAt) continue;
|
|
7648
|
-
const predecessor = fallbackInvite?.replaces_seat === void 0 ? void 0 : room.seats.find((candidate) => candidate.participant_id === fallbackInvite.replaces_seat && candidate.state === "removed");
|
|
7649
|
-
if (fallbackInvite?.replaces_seat !== void 0 && predecessor === void 0) continue;
|
|
7142
|
+
if (displayName === void 0) continue;
|
|
7650
7143
|
const { alias: _alias, replaces_seat: _replacesSeat, ...base } = seat;
|
|
7651
7144
|
const activated = {
|
|
7652
7145
|
...base,
|
|
7653
7146
|
state: "active",
|
|
7654
7147
|
display_name: displayName,
|
|
7655
|
-
accepted_at:
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
...fallbackInvite === void 0 ? _replacesSeat === void 0 ? {} : { replaces_seat: _replacesSeat } : predecessor === void 0 ? {} : { replaces_seat: predecessor.participant_id },
|
|
7659
|
-
...room.anonymous ? { alias: fallbackInvite === void 0 ? _alias : predecessor?.alias ?? (fallbackInvite.role === seat.role ? _alias : mintAlias(seatsWithActivations, fallbackInvite.role)) } : {}
|
|
7148
|
+
accepted_at: this.now(),
|
|
7149
|
+
..._replacesSeat === void 0 ? {} : { replaces_seat: _replacesSeat },
|
|
7150
|
+
...room.anonymous ? { alias: _alias } : {}
|
|
7660
7151
|
};
|
|
7661
7152
|
activatedPending.push(activated);
|
|
7662
7153
|
seatsWithActivations[seatIndex] = activated;
|
|
7663
7154
|
}
|
|
7664
7155
|
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;
|
|
7665
7158
|
for (const [cid, displayName] of contactsByCid) {
|
|
7666
7159
|
if (existingCids.has(cid)) continue;
|
|
7667
|
-
const
|
|
7668
|
-
if (!
|
|
7669
|
-
const invite = inviteById.get(origin.invite_id);
|
|
7670
|
-
if (!invite || invite.state === "receipt_pending" || invite.recovery_of !== void 0 && invite.recovery_confirmed !== true) continue;
|
|
7160
|
+
const invite = admissionInvite;
|
|
7161
|
+
if (!invite) continue;
|
|
7671
7162
|
const removedAt = lastRemovedAt.get(cid);
|
|
7672
7163
|
if (removedAt !== void 0 && invite.created_at <= removedAt) continue;
|
|
7673
7164
|
const seated = [...seatsWithActivations, ...newSeats];
|
|
@@ -7678,7 +7169,7 @@ var init_service = __esm({
|
|
|
7678
7169
|
display_name: displayName,
|
|
7679
7170
|
role: invite.role,
|
|
7680
7171
|
invite_id: invite.invite_id,
|
|
7681
|
-
accepted_at:
|
|
7172
|
+
accepted_at: this.now(),
|
|
7682
7173
|
participant_id: LowerCrockfordUlidSchema.parse(generateUlid()),
|
|
7683
7174
|
state: "active",
|
|
7684
7175
|
...predecessor === void 0 ? {} : { replaces_seat: predecessor.participant_id },
|
|
@@ -7935,7 +7426,7 @@ var init_service = __esm({
|
|
|
7935
7426
|
}
|
|
7936
7427
|
}
|
|
7937
7428
|
isPacketPending(room) {
|
|
7938
|
-
return room.identity_cid === "" && room.state === "provisioning" && room.status === "packet_pending" &&
|
|
7429
|
+
return room.identity_cid === "" && room.state === "provisioning" && room.status === "packet_pending" && room.identity_name === roomIdentityName(room.room_id);
|
|
7939
7430
|
}
|
|
7940
7431
|
now() {
|
|
7941
7432
|
return external_exports.string().datetime({ offset: true }).parse(this.nowValue());
|
|
@@ -7946,9 +7437,9 @@ var init_service = __esm({
|
|
|
7946
7437
|
|
|
7947
7438
|
// src/storage.ts
|
|
7948
7439
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
7949
|
-
import * as
|
|
7440
|
+
import * as nodeFs2 from "node:fs";
|
|
7950
7441
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7951
|
-
import { dirname as
|
|
7442
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
7952
7443
|
var DIRECTORY_MODE2, FILE_MODE2, NO_FOLLOW2, utf8Decoder, CoworkStorageError, RoomQueue, CoworkStore;
|
|
7953
7444
|
var init_storage = __esm({
|
|
7954
7445
|
"src/storage.ts"() {
|
|
@@ -7957,7 +7448,7 @@ var init_storage = __esm({
|
|
|
7957
7448
|
init_ulid();
|
|
7958
7449
|
DIRECTORY_MODE2 = 448;
|
|
7959
7450
|
FILE_MODE2 = 384;
|
|
7960
|
-
NO_FOLLOW2 =
|
|
7451
|
+
NO_FOLLOW2 = nodeFs2.constants.O_NOFOLLOW ?? 0;
|
|
7961
7452
|
utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
7962
7453
|
CoworkStorageError = class extends Error {
|
|
7963
7454
|
constructor(message, options) {
|
|
@@ -7993,7 +7484,7 @@ var init_storage = __esm({
|
|
|
7993
7484
|
constructor(stateDir, options = {}) {
|
|
7994
7485
|
if (!stateDir) throw new CoworkStorageError("state directory is required");
|
|
7995
7486
|
this.stateDir = stateDir;
|
|
7996
|
-
this.fs = options.fs ??
|
|
7487
|
+
this.fs = options.fs ?? nodeFs2;
|
|
7997
7488
|
}
|
|
7998
7489
|
mutex(roomId, work) {
|
|
7999
7490
|
const validRoomId = this.roomId(roomId);
|
|
@@ -8126,7 +7617,7 @@ var init_storage = __esm({
|
|
|
8126
7617
|
try {
|
|
8127
7618
|
fd = this.fs.openSync(
|
|
8128
7619
|
archivePath,
|
|
8129
|
-
|
|
7620
|
+
nodeFs2.constants.O_WRONLY | nodeFs2.constants.O_APPEND | NO_FOLLOW2
|
|
8130
7621
|
);
|
|
8131
7622
|
const opened = this.validateOpenPath(fd, archivePath, "room archive", "file", true);
|
|
8132
7623
|
originalSize = opened.size;
|
|
@@ -8341,7 +7832,7 @@ var init_storage = __esm({
|
|
|
8341
7832
|
try {
|
|
8342
7833
|
fd = this.fs.openSync(
|
|
8343
7834
|
temp,
|
|
8344
|
-
|
|
7835
|
+
nodeFs2.constants.O_CREAT | nodeFs2.constants.O_EXCL | nodeFs2.constants.O_WRONLY | NO_FOLLOW2,
|
|
8345
7836
|
FILE_MODE2
|
|
8346
7837
|
);
|
|
8347
7838
|
this.validateOpenPath(fd, temp, `temporary ${label}`, "file", true);
|
|
@@ -8351,7 +7842,7 @@ var init_storage = __esm({
|
|
|
8351
7842
|
this.fs.closeSync(fd);
|
|
8352
7843
|
fd = void 0;
|
|
8353
7844
|
this.fs.renameSync(temp, path);
|
|
8354
|
-
this.fsyncDirectory(
|
|
7845
|
+
this.fsyncDirectory(dirname2(path));
|
|
8355
7846
|
} catch (error) {
|
|
8356
7847
|
if (fd !== void 0) {
|
|
8357
7848
|
try {
|
|
@@ -8447,7 +7938,7 @@ var init_storage = __esm({
|
|
|
8447
7938
|
break;
|
|
8448
7939
|
}
|
|
8449
7940
|
missing.push(cursor);
|
|
8450
|
-
const parent =
|
|
7941
|
+
const parent = dirname2(cursor);
|
|
8451
7942
|
if (parent === cursor) throw new CoworkStorageError(`cannot find existing parent for ${label}`);
|
|
8452
7943
|
cursor = parent;
|
|
8453
7944
|
}
|
|
@@ -8458,13 +7949,13 @@ var init_storage = __esm({
|
|
|
8458
7949
|
created.push(directory);
|
|
8459
7950
|
this.fs.chmodSync(directory, DIRECTORY_MODE2);
|
|
8460
7951
|
this.fsyncDirectory(directory);
|
|
8461
|
-
this.fsyncDirectory(
|
|
7952
|
+
this.fsyncDirectory(dirname2(directory));
|
|
8462
7953
|
}
|
|
8463
7954
|
} catch (error) {
|
|
8464
7955
|
for (const directory of created.reverse()) {
|
|
8465
7956
|
try {
|
|
8466
7957
|
this.fs.rmdirSync(directory);
|
|
8467
|
-
this.fsyncDirectory(
|
|
7958
|
+
this.fsyncDirectory(dirname2(directory));
|
|
8468
7959
|
} catch {
|
|
8469
7960
|
}
|
|
8470
7961
|
}
|
|
@@ -8514,7 +8005,7 @@ var init_storage = __esm({
|
|
|
8514
8005
|
try {
|
|
8515
8006
|
fd = this.fs.openSync(
|
|
8516
8007
|
path,
|
|
8517
|
-
|
|
8008
|
+
nodeFs2.constants.O_CREAT | nodeFs2.constants.O_EXCL | nodeFs2.constants.O_WRONLY | NO_FOLLOW2,
|
|
8518
8009
|
FILE_MODE2
|
|
8519
8010
|
);
|
|
8520
8011
|
this.validateOpenPath(fd, path, "room archive", "file", true);
|
|
@@ -8523,7 +8014,7 @@ var init_storage = __esm({
|
|
|
8523
8014
|
} finally {
|
|
8524
8015
|
if (fd !== void 0) this.fs.closeSync(fd);
|
|
8525
8016
|
}
|
|
8526
|
-
this.fsyncDirectory(
|
|
8017
|
+
this.fsyncDirectory(dirname2(path));
|
|
8527
8018
|
}
|
|
8528
8019
|
atomicMetadataWrite(path, room) {
|
|
8529
8020
|
if (this.lstatIfPresent(path)) this.assertRegularFile(path, "room metadata");
|
|
@@ -8534,7 +8025,7 @@ var init_storage = __esm({
|
|
|
8534
8025
|
try {
|
|
8535
8026
|
fd = this.fs.openSync(
|
|
8536
8027
|
temp,
|
|
8537
|
-
|
|
8028
|
+
nodeFs2.constants.O_CREAT | nodeFs2.constants.O_EXCL | nodeFs2.constants.O_WRONLY | NO_FOLLOW2,
|
|
8538
8029
|
FILE_MODE2
|
|
8539
8030
|
);
|
|
8540
8031
|
this.validateOpenPath(fd, temp, "temporary room metadata", "file", true);
|
|
@@ -8544,7 +8035,7 @@ var init_storage = __esm({
|
|
|
8544
8035
|
this.fs.closeSync(fd);
|
|
8545
8036
|
fd = void 0;
|
|
8546
8037
|
this.fs.renameSync(temp, path);
|
|
8547
|
-
this.fsyncDirectory(
|
|
8038
|
+
this.fsyncDirectory(dirname2(path));
|
|
8548
8039
|
} catch (error) {
|
|
8549
8040
|
if (fd !== void 0) {
|
|
8550
8041
|
try {
|
|
@@ -8570,7 +8061,7 @@ var init_storage = __esm({
|
|
|
8570
8061
|
readFileNoFollow(path, label) {
|
|
8571
8062
|
let fd;
|
|
8572
8063
|
try {
|
|
8573
|
-
fd = this.fs.openSync(path,
|
|
8064
|
+
fd = this.fs.openSync(path, nodeFs2.constants.O_RDONLY | NO_FOLLOW2);
|
|
8574
8065
|
this.validateOpenPath(fd, path, label, "file", true);
|
|
8575
8066
|
return this.fs.readFileSync(fd);
|
|
8576
8067
|
} finally {
|
|
@@ -8580,7 +8071,7 @@ var init_storage = __esm({
|
|
|
8580
8071
|
fsyncDirectory(path) {
|
|
8581
8072
|
let fd;
|
|
8582
8073
|
try {
|
|
8583
|
-
fd = this.fs.openSync(path,
|
|
8074
|
+
fd = this.fs.openSync(path, nodeFs2.constants.O_RDONLY | NO_FOLLOW2);
|
|
8584
8075
|
this.validateOpenPath(fd, path, "directory fsync target", "directory", false);
|
|
8585
8076
|
this.fs.fsyncSync(fd);
|
|
8586
8077
|
} finally {
|
|
@@ -8611,743 +8102,33 @@ var init_storage = __esm({
|
|
|
8611
8102
|
}
|
|
8612
8103
|
});
|
|
8613
8104
|
|
|
8614
|
-
// src/openapi.ts
|
|
8615
|
-
function params(properties, required) {
|
|
8616
|
-
return { type: "object", additionalProperties: false, properties, required: [...required] };
|
|
8617
|
-
}
|
|
8618
|
-
function schemaBaseName(method) {
|
|
8619
|
-
return method.split(".").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
8620
|
-
}
|
|
8621
|
-
function errorResponseSpec(description, codes) {
|
|
8622
|
-
return {
|
|
8623
|
-
description,
|
|
8624
|
-
content: {
|
|
8625
|
-
"application/json": {
|
|
8626
|
-
schema: { $ref: "#/components/schemas/RpcErrorResponse" },
|
|
8627
|
-
example: {
|
|
8628
|
-
version: 1,
|
|
8629
|
-
id: "req-1",
|
|
8630
|
-
error: { code: codes[0], message: "see the daemon response for detail" }
|
|
8631
|
-
}
|
|
8632
|
-
}
|
|
8633
|
-
}
|
|
8634
|
-
};
|
|
8635
|
-
}
|
|
8636
|
-
function documentResponseSpec(description, mediaType) {
|
|
8637
|
-
return { description, content: { [mediaType]: { schema: { type: "string" } } } };
|
|
8638
|
-
}
|
|
8639
|
-
function buildOpenApiDocument() {
|
|
8640
|
-
const schemas = {
|
|
8641
|
-
RpcId: {
|
|
8642
|
-
description: "Caller-chosen correlation id echoed in the response.",
|
|
8643
|
-
oneOf: [
|
|
8644
|
-
{ type: "string", minLength: 1, maxLength: 256 },
|
|
8645
|
-
{ type: "integer", minimum: 0 }
|
|
8646
|
-
]
|
|
8647
|
-
},
|
|
8648
|
-
RpcError: {
|
|
8649
|
-
type: "object",
|
|
8650
|
-
additionalProperties: false,
|
|
8651
|
-
required: ["code", "message"],
|
|
8652
|
-
properties: {
|
|
8653
|
-
code: {
|
|
8654
|
-
type: "string",
|
|
8655
|
-
enum: RPC_ERROR_CODES.map(([code]) => code),
|
|
8656
|
-
description: RPC_ERROR_CODES.map(([code, text]) => `\`${code}\`: ${text}`).join(" ")
|
|
8657
|
-
},
|
|
8658
|
-
message: { type: "string", description: "Human-readable detail. Not machine-parsed." }
|
|
8659
|
-
}
|
|
8660
|
-
},
|
|
8661
|
-
RpcSuccessResponse: {
|
|
8662
|
-
type: "object",
|
|
8663
|
-
additionalProperties: false,
|
|
8664
|
-
required: ["version", "id", "result"],
|
|
8665
|
-
properties: {
|
|
8666
|
-
version: { type: "integer", const: 1 },
|
|
8667
|
-
id: { $ref: "#/components/schemas/RpcId" },
|
|
8668
|
-
result: {
|
|
8669
|
-
description: "Method-specific result. See the individual method descriptions."
|
|
8670
|
-
}
|
|
8671
|
-
}
|
|
8672
|
-
},
|
|
8673
|
-
RpcErrorResponse: {
|
|
8674
|
-
type: "object",
|
|
8675
|
-
additionalProperties: false,
|
|
8676
|
-
required: ["version", "id", "error"],
|
|
8677
|
-
properties: {
|
|
8678
|
-
version: { type: "integer", const: 1 },
|
|
8679
|
-
id: {
|
|
8680
|
-
description: "The request id, or null when it could not be recovered from the body.",
|
|
8681
|
-
oneOf: [{ $ref: "#/components/schemas/RpcId" }, { type: "null" }]
|
|
8682
|
-
},
|
|
8683
|
-
error: { $ref: "#/components/schemas/RpcError" }
|
|
8684
|
-
}
|
|
8685
|
-
}
|
|
8686
|
-
};
|
|
8687
|
-
const requestSchemaNames = [];
|
|
8688
|
-
const discriminatorMapping = {};
|
|
8689
|
-
for (const entry of ROOM_RPC_METHODS) {
|
|
8690
|
-
const base = schemaBaseName(entry.method);
|
|
8691
|
-
const paramsName = `${base}Params`;
|
|
8692
|
-
const requestName = `${base}Request`;
|
|
8693
|
-
schemas[paramsName] = { ...entry.params, description: `Parameters for \`${entry.method}\`.` };
|
|
8694
|
-
schemas[requestName] = {
|
|
8695
|
-
type: "object",
|
|
8696
|
-
additionalProperties: false,
|
|
8697
|
-
required: ["version", "id", "method", "params"],
|
|
8698
|
-
title: entry.summary,
|
|
8699
|
-
description: `${entry.description}
|
|
8700
|
-
|
|
8701
|
-
Result: ${entry.result}`,
|
|
8702
|
-
properties: {
|
|
8703
|
-
version: { type: "integer", const: 1 },
|
|
8704
|
-
id: { $ref: "#/components/schemas/RpcId" },
|
|
8705
|
-
method: { type: "string", const: entry.method },
|
|
8706
|
-
params: { $ref: `#/components/schemas/${paramsName}` }
|
|
8707
|
-
},
|
|
8708
|
-
examples: [{ version: 1, id: "req-1", method: entry.method, params: entry.example }]
|
|
8709
|
-
};
|
|
8710
|
-
requestSchemaNames.push(requestName);
|
|
8711
|
-
discriminatorMapping[entry.method] = `#/components/schemas/${requestName}`;
|
|
8712
|
-
}
|
|
8713
|
-
return {
|
|
8714
|
-
openapi: "3.1.0",
|
|
8715
|
-
info: {
|
|
8716
|
-
title: "ours-cowork room management API",
|
|
8717
|
-
version: API_VERSION,
|
|
8718
|
-
summary: "Loopback REST control surface for cowork mission rooms.",
|
|
8719
|
-
description: [
|
|
8720
|
-
'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.',
|
|
8721
|
-
"",
|
|
8722
|
-
"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.",
|
|
8723
|
-
"",
|
|
8724
|
-
"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."
|
|
8725
|
-
].join("\n"),
|
|
8726
|
-
license: { name: "FSL-1.1-Apache-2.0" }
|
|
8727
|
-
},
|
|
8728
|
-
servers: [{ url: "/", description: "This cowork daemon, on its loopback REST port." }],
|
|
8729
|
-
tags: [
|
|
8730
|
-
{ name: "rooms", description: "Room management over the RPC envelope." },
|
|
8731
|
-
{ name: "documentation", description: "The API description and its browser UI." }
|
|
8732
|
-
],
|
|
8733
|
-
paths: {
|
|
8734
|
-
[RPC_PATH]: {
|
|
8735
|
-
post: {
|
|
8736
|
-
tags: ["rooms"],
|
|
8737
|
-
operationId: "roomManagementRpc",
|
|
8738
|
-
summary: "Invoke a room-management method",
|
|
8739
|
-
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`.",
|
|
8740
|
-
requestBody: {
|
|
8741
|
-
required: true,
|
|
8742
|
-
content: {
|
|
8743
|
-
"application/json": {
|
|
8744
|
-
schema: {
|
|
8745
|
-
oneOf: requestSchemaNames.map((name) => ({ $ref: `#/components/schemas/${name}` })),
|
|
8746
|
-
discriminator: { propertyName: "method", mapping: discriminatorMapping }
|
|
8747
|
-
}
|
|
8748
|
-
}
|
|
8749
|
-
}
|
|
8750
|
-
},
|
|
8751
|
-
responses: {
|
|
8752
|
-
200: {
|
|
8753
|
-
description: "The method completed. `result` is method-specific.",
|
|
8754
|
-
content: {
|
|
8755
|
-
"application/json": {
|
|
8756
|
-
schema: { $ref: "#/components/schemas/RpcSuccessResponse" }
|
|
8757
|
-
}
|
|
8758
|
-
}
|
|
8759
|
-
},
|
|
8760
|
-
400: errorResponseSpec(
|
|
8761
|
-
"The envelope, parameters, or room state rejected the call.",
|
|
8762
|
-
[
|
|
8763
|
-
"invalid_params",
|
|
8764
|
-
"invalid_json",
|
|
8765
|
-
"invalid_request",
|
|
8766
|
-
"not_found",
|
|
8767
|
-
"invalid_state",
|
|
8768
|
-
"shutting_down"
|
|
8769
|
-
]
|
|
8770
|
-
),
|
|
8771
|
-
403: errorResponseSpec(
|
|
8772
|
-
"The Host header was not a loopback console authority, the Origin did not match it, or the fetch was cross-site.",
|
|
8773
|
-
["forbidden"]
|
|
8774
|
-
),
|
|
8775
|
-
404: errorResponseSpec(
|
|
8776
|
-
"The method is not part of the REST route table.",
|
|
8777
|
-
["method_not_found"]
|
|
8778
|
-
),
|
|
8779
|
-
413: errorResponseSpec("The request body exceeded 1 MiB.", ["request_too_large"]),
|
|
8780
|
-
415: errorResponseSpec(
|
|
8781
|
-
"The request did not declare `application/json`.",
|
|
8782
|
-
["unsupported_media_type"]
|
|
8783
|
-
),
|
|
8784
|
-
500: errorResponseSpec("The daemon failed to complete the operation.", ["internal"])
|
|
8785
|
-
}
|
|
8786
|
-
}
|
|
8787
|
-
},
|
|
8788
|
-
[OPENAPI_DOCUMENT_PATH]: {
|
|
8789
|
-
get: {
|
|
8790
|
-
tags: ["documentation"],
|
|
8791
|
-
operationId: "getOpenApiDocument",
|
|
8792
|
-
summary: "Fetch this OpenAPI document",
|
|
8793
|
-
description: "Returns the OpenAPI 3.1 description of the room-management REST API.",
|
|
8794
|
-
responses: {
|
|
8795
|
-
200: {
|
|
8796
|
-
description: "The OpenAPI document.",
|
|
8797
|
-
content: { "application/json": { schema: { type: "object" } } }
|
|
8798
|
-
}
|
|
8799
|
-
}
|
|
8800
|
-
}
|
|
8801
|
-
},
|
|
8802
|
-
[API_DOCS_PATH]: {
|
|
8803
|
-
get: {
|
|
8804
|
-
tags: ["documentation"],
|
|
8805
|
-
operationId: "getApiDocsPage",
|
|
8806
|
-
summary: "Open the API documentation UI",
|
|
8807
|
-
description: `Serves the browser UI that renders \`${OPENAPI_DOCUMENT_PATH}\`.`,
|
|
8808
|
-
responses: { 200: documentResponseSpec("The documentation page.", "text/html") }
|
|
8809
|
-
}
|
|
8810
|
-
},
|
|
8811
|
-
[API_DOCS_SCRIPT_PATH]: {
|
|
8812
|
-
get: {
|
|
8813
|
-
tags: ["documentation"],
|
|
8814
|
-
operationId: "getApiDocsScript",
|
|
8815
|
-
summary: "Fetch the documentation UI script",
|
|
8816
|
-
responses: { 200: documentResponseSpec("The UI script.", "text/javascript") }
|
|
8817
|
-
}
|
|
8818
|
-
},
|
|
8819
|
-
[API_DOCS_STYLESHEET_PATH]: {
|
|
8820
|
-
get: {
|
|
8821
|
-
tags: ["documentation"],
|
|
8822
|
-
operationId: "getApiDocsStylesheet",
|
|
8823
|
-
summary: "Fetch the documentation UI stylesheet",
|
|
8824
|
-
responses: { 200: documentResponseSpec("The UI stylesheet.", "text/css") }
|
|
8825
|
-
}
|
|
8826
|
-
}
|
|
8827
|
-
},
|
|
8828
|
-
components: { schemas }
|
|
8829
|
-
};
|
|
8830
|
-
}
|
|
8831
|
-
function apiDocsAsset(pathname) {
|
|
8832
|
-
switch (pathname) {
|
|
8833
|
-
case OPENAPI_DOCUMENT_PATH:
|
|
8834
|
-
return {
|
|
8835
|
-
body: `${JSON.stringify(buildOpenApiDocument(), null, 2)}
|
|
8836
|
-
`,
|
|
8837
|
-
contentType: "application/json; charset=utf-8"
|
|
8838
|
-
};
|
|
8839
|
-
case API_DOCS_PATH:
|
|
8840
|
-
return { body: DOCS_PAGE, contentType: "text/html; charset=utf-8" };
|
|
8841
|
-
case API_DOCS_SCRIPT_PATH:
|
|
8842
|
-
return { body: DOCS_SCRIPT, contentType: "text/javascript; charset=utf-8" };
|
|
8843
|
-
case API_DOCS_STYLESHEET_PATH:
|
|
8844
|
-
return { body: DOCS_STYLESHEET, contentType: "text/css; charset=utf-8" };
|
|
8845
|
-
default:
|
|
8846
|
-
return void 0;
|
|
8847
|
-
}
|
|
8848
|
-
}
|
|
8849
|
-
var RPC_PATH, OPENAPI_DOCUMENT_PATH, API_DOCS_PATH, API_DOCS_SCRIPT_PATH, API_DOCS_STYLESHEET_PATH, API_VERSION, EXAMPLE_ROOM_ID, roomIdProperty, roleProperty, missionTextProperty, roomNameProperty, inviteModeProperty, notifyProperty, ROOM_RPC_METHODS, RPC_ERROR_CODES, DOCS_PAGE, DOCS_STYLESHEET, DOCS_SCRIPT;
|
|
8850
|
-
var init_openapi = __esm({
|
|
8851
|
-
"src/openapi.ts"() {
|
|
8852
|
-
"use strict";
|
|
8853
|
-
RPC_PATH = "/rpc";
|
|
8854
|
-
OPENAPI_DOCUMENT_PATH = "/openapi.json";
|
|
8855
|
-
API_DOCS_PATH = "/docs";
|
|
8856
|
-
API_DOCS_SCRIPT_PATH = "/docs/ui.js";
|
|
8857
|
-
API_DOCS_STYLESHEET_PATH = "/docs/ui.css";
|
|
8858
|
-
API_VERSION = "1";
|
|
8859
|
-
EXAMPLE_ROOM_ID = "01jd7q4h9m2v8xk3znbc5regty";
|
|
8860
|
-
roomIdProperty = {
|
|
8861
|
-
type: "string",
|
|
8862
|
-
pattern: "^[0-7][0-9a-hjkmnp-tv-z]{25}$",
|
|
8863
|
-
description: "26-character lowercase Crockford ULID identifying the room."
|
|
8864
|
-
};
|
|
8865
|
-
roleProperty = {
|
|
8866
|
-
type: "string",
|
|
8867
|
-
description: "Invite role. At most 256 UTF-8 bytes."
|
|
8868
|
-
};
|
|
8869
|
-
missionTextProperty = (what) => ({
|
|
8870
|
-
type: "string",
|
|
8871
|
-
description: `${what}. At most 262144 UTF-8 bytes.`
|
|
8872
|
-
});
|
|
8873
|
-
roomNameProperty = {
|
|
8874
|
-
type: "string",
|
|
8875
|
-
description: "Friendly room name: 1-64 Unicode characters after trimming and NFC normalization, with no Unicode control or format characters. Duplicates are allowed."
|
|
8876
|
-
};
|
|
8877
|
-
inviteModeProperty = {
|
|
8878
|
-
type: "string",
|
|
8879
|
-
enum: ["one_time", "public"],
|
|
8880
|
-
description: "Invitation mode. `one_time` requires `min_accepts` to be 1."
|
|
8881
|
-
};
|
|
8882
|
-
notifyProperty = {
|
|
8883
|
-
type: "boolean",
|
|
8884
|
-
description: "Announce the membership change to the room. Defaults to the room's quiet setting."
|
|
8885
|
-
};
|
|
8886
|
-
ROOM_RPC_METHODS = [
|
|
8887
|
-
{
|
|
8888
|
-
method: "room.create",
|
|
8889
|
-
summary: "Create a room",
|
|
8890
|
-
description: "Provisions a room, its durable metadata, and its room packet. The room stays in the provisioning state until its invitation requirements are satisfied.",
|
|
8891
|
-
params: params({
|
|
8892
|
-
name: roomNameProperty,
|
|
8893
|
-
goal: missionTextProperty("Mission goal"),
|
|
8894
|
-
briefing: missionTextProperty("Mission briefing"),
|
|
8895
|
-
anonymous: {
|
|
8896
|
-
type: "boolean",
|
|
8897
|
-
description: "Project participants under aliases instead of contact identities."
|
|
8898
|
-
},
|
|
8899
|
-
quiet_membership: {
|
|
8900
|
-
type: "boolean",
|
|
8901
|
-
description: "Suppress membership announcements for this room by default."
|
|
8902
|
-
}
|
|
8903
|
-
}, ["goal", "briefing"]),
|
|
8904
|
-
result: "The created room record, including `room_id`, `room_name`, `state`, mission, invites, and seats.",
|
|
8905
|
-
example: { goal: "Ship the release", briefing: "Coordinate the 1.0 cut.", name: "Release" }
|
|
8906
|
-
},
|
|
8907
|
-
{
|
|
8908
|
-
method: "room.settings",
|
|
8909
|
-
summary: "Update room settings",
|
|
8910
|
-
description: "Updates mutable room settings. At least one setting besides `room_id` is required; omitted settings are left unchanged.",
|
|
8911
|
-
params: params({
|
|
8912
|
-
room_id: roomIdProperty,
|
|
8913
|
-
name: roomNameProperty,
|
|
8914
|
-
goal: missionTextProperty("Replacement mission goal"),
|
|
8915
|
-
briefing: missionTextProperty("Replacement mission briefing"),
|
|
8916
|
-
status: { type: "string", minLength: 1, description: "Operator status line." },
|
|
8917
|
-
quiet_membership: {
|
|
8918
|
-
type: "boolean",
|
|
8919
|
-
description: "Suppress membership announcements for this room."
|
|
8920
|
-
}
|
|
8921
|
-
}, ["room_id"]),
|
|
8922
|
-
result: "The updated room record.",
|
|
8923
|
-
example: { room_id: EXAMPLE_ROOM_ID, name: "Release cut" }
|
|
8924
|
-
},
|
|
8925
|
-
{
|
|
8926
|
-
method: "room.briefing.role.set",
|
|
8927
|
-
summary: "Set a role briefing",
|
|
8928
|
-
description: "Creates or replaces the briefing for one role and bumps its briefing version.",
|
|
8929
|
-
params: params({
|
|
8930
|
-
room_id: roomIdProperty,
|
|
8931
|
-
role: roleProperty,
|
|
8932
|
-
text: missionTextProperty("Role briefing text")
|
|
8933
|
-
}, ["room_id", "role", "text"]),
|
|
8934
|
-
result: "The updated room record with the stored role briefing.",
|
|
8935
|
-
example: { room_id: EXAMPLE_ROOM_ID, role: "Reviewer", text: "Review every merge request." }
|
|
8936
|
-
},
|
|
8937
|
-
{
|
|
8938
|
-
method: "room.briefing.role.delete",
|
|
8939
|
-
summary: "Delete a role briefing",
|
|
8940
|
-
description: "Removes the briefing for one role.",
|
|
8941
|
-
params: params({ room_id: roomIdProperty, role: roleProperty }, ["room_id", "role"]),
|
|
8942
|
-
result: "The updated room record without the removed role briefing.",
|
|
8943
|
-
example: { room_id: EXAMPLE_ROOM_ID, role: "Reviewer" }
|
|
8944
|
-
},
|
|
8945
|
-
{
|
|
8946
|
-
method: "room.invite",
|
|
8947
|
-
summary: "Mint an invitation requirement",
|
|
8948
|
-
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.",
|
|
8949
|
-
params: params({
|
|
8950
|
-
room_id: roomIdProperty,
|
|
8951
|
-
mode: inviteModeProperty,
|
|
8952
|
-
role: roleProperty,
|
|
8953
|
-
min_accepts: {
|
|
8954
|
-
type: "integer",
|
|
8955
|
-
minimum: 1,
|
|
8956
|
-
description: "Acceptances required before the requirement is satisfied. Must be 1 for `one_time` invites."
|
|
8957
|
-
}
|
|
8958
|
-
}, ["room_id", "mode", "min_accepts"]),
|
|
8959
|
-
result: "An invite receipt: `room_id`, the recorded `invite`, the one-shot `blob` secret, and `reusable`.",
|
|
8960
|
-
example: { room_id: EXAMPLE_ROOM_ID, mode: "one_time", role: "Reviewer", min_accepts: 1 }
|
|
8961
|
-
},
|
|
8962
|
-
{
|
|
8963
|
-
method: "room.participant.remove",
|
|
8964
|
-
summary: "Remove a participant",
|
|
8965
|
-
description: "Removes one participant seat, bumps the membership epoch, and severs the contact. The receipt reports what actually happened, including retained key material.",
|
|
8966
|
-
params: params({
|
|
8967
|
-
room_id: roomIdProperty,
|
|
8968
|
-
participant: {
|
|
8969
|
-
type: "string",
|
|
8970
|
-
minLength: 1,
|
|
8971
|
-
description: "Participant id, identity, display name, or alias."
|
|
8972
|
-
},
|
|
8973
|
-
notify: notifyProperty
|
|
8974
|
-
}, ["room_id", "participant"]),
|
|
8975
|
-
result: "A removal receipt with `participant_id`, `epoch`, `status`, `notified`, and `key_material_retained`.",
|
|
8976
|
-
example: { room_id: EXAMPLE_ROOM_ID, participant: "Reviewer-1", notify: true }
|
|
8977
|
-
},
|
|
8978
|
-
{
|
|
8979
|
-
method: "room.participant.replace",
|
|
8980
|
-
summary: "Replace a participant",
|
|
8981
|
-
description: "Removes one participant seat and mints a replacement invitation requirement in the same operation.",
|
|
8982
|
-
params: params({
|
|
8983
|
-
room_id: roomIdProperty,
|
|
8984
|
-
participant: {
|
|
8985
|
-
type: "string",
|
|
8986
|
-
minLength: 1,
|
|
8987
|
-
description: "Participant id, identity, display name, or alias."
|
|
8988
|
-
},
|
|
8989
|
-
notify: notifyProperty,
|
|
8990
|
-
mode: inviteModeProperty,
|
|
8991
|
-
min_accepts: {
|
|
8992
|
-
type: "integer",
|
|
8993
|
-
minimum: 1,
|
|
8994
|
-
description: "Acceptances required for the replacement invitation requirement."
|
|
8995
|
-
}
|
|
8996
|
-
}, ["room_id", "participant"]),
|
|
8997
|
-
result: "An invite receipt for the replacement, extended with the `removal` receipt.",
|
|
8998
|
-
example: { room_id: EXAMPLE_ROOM_ID, participant: "Reviewer-1", mode: "one_time" }
|
|
8999
|
-
},
|
|
9000
|
-
{
|
|
9001
|
-
method: "room.revoke",
|
|
9002
|
-
summary: "Revoke an invitation requirement",
|
|
9003
|
-
description: "Revokes one live invitation requirement so it can no longer admit a seat.",
|
|
9004
|
-
params: params({
|
|
9005
|
-
room_id: roomIdProperty,
|
|
9006
|
-
invite_id: { type: "string", minLength: 1, description: "Recorded invite identifier." }
|
|
9007
|
-
}, ["room_id", "invite_id"]),
|
|
9008
|
-
result: "The updated room record with the invite marked revoked.",
|
|
9009
|
-
example: { room_id: EXAMPLE_ROOM_ID, invite_id: "inv-01" }
|
|
9010
|
-
},
|
|
9011
|
-
{
|
|
9012
|
-
method: "room.recover",
|
|
9013
|
-
summary: "Recover unusable invitations",
|
|
9014
|
-
description: "Mints recovery invitations for requirements whose secret can no longer admit a seat. Each recovery must be confirmed before it replaces the original.",
|
|
9015
|
-
params: params({ room_id: roomIdProperty }, ["room_id"]),
|
|
9016
|
-
result: "The list of recovery invite receipts, each carrying `recovery_of`.",
|
|
9017
|
-
example: { room_id: EXAMPLE_ROOM_ID }
|
|
9018
|
-
},
|
|
9019
|
-
{
|
|
9020
|
-
method: "room.recover.confirm",
|
|
9021
|
-
summary: "Confirm a recovery invitation",
|
|
9022
|
-
description: "Confirms that a recovery invitation was handed over, retiring the invitation requirement it recovers.",
|
|
9023
|
-
params: params({
|
|
9024
|
-
room_id: roomIdProperty,
|
|
9025
|
-
recovery_of: { type: "string", minLength: 1, description: "Invite id being recovered." },
|
|
9026
|
-
invite_id: { type: "string", minLength: 1, description: "Recovery invite id to confirm." }
|
|
9027
|
-
}, ["room_id", "recovery_of", "invite_id"]),
|
|
9028
|
-
result: "The updated room record with the confirmed recovery.",
|
|
9029
|
-
example: { room_id: EXAMPLE_ROOM_ID, recovery_of: "inv-01", invite_id: "inv-02" }
|
|
9030
|
-
},
|
|
9031
|
-
{
|
|
9032
|
-
method: "room.list",
|
|
9033
|
-
summary: "List rooms",
|
|
9034
|
-
description: "Lists every room held on this host. Takes no parameters.",
|
|
9035
|
-
params: params({}, []),
|
|
9036
|
-
result: "An array of room records.",
|
|
9037
|
-
example: {}
|
|
9038
|
-
},
|
|
9039
|
-
{
|
|
9040
|
-
method: "room.show",
|
|
9041
|
-
summary: "Show one room",
|
|
9042
|
-
description: "Returns the complete durable record for one room.",
|
|
9043
|
-
params: params({ room_id: roomIdProperty }, ["room_id"]),
|
|
9044
|
-
result: "The room record.",
|
|
9045
|
-
example: { room_id: EXAMPLE_ROOM_ID }
|
|
9046
|
-
},
|
|
9047
|
-
{
|
|
9048
|
-
method: "room.participants",
|
|
9049
|
-
summary: "List participants",
|
|
9050
|
-
description: "Returns the room's seats, including pending, active, and removed states.",
|
|
9051
|
-
params: params({ room_id: roomIdProperty }, ["room_id"]),
|
|
9052
|
-
result: "An array of seat records.",
|
|
9053
|
-
example: { room_id: EXAMPLE_ROOM_ID }
|
|
9054
|
-
},
|
|
9055
|
-
{
|
|
9056
|
-
method: "room.history",
|
|
9057
|
-
summary: "Read room history",
|
|
9058
|
-
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.",
|
|
9059
|
-
params: params({
|
|
9060
|
-
room_id: roomIdProperty,
|
|
9061
|
-
after: {
|
|
9062
|
-
type: "integer",
|
|
9063
|
-
minimum: 0,
|
|
9064
|
-
description: "Return records with a sequence number greater than this value."
|
|
9065
|
-
},
|
|
9066
|
-
limit: { type: "integer", minimum: 1, description: "Maximum number of records to return." },
|
|
9067
|
-
view: {
|
|
9068
|
-
type: "string",
|
|
9069
|
-
enum: ["operator", "participant"],
|
|
9070
|
-
description: "History projection. Defaults to the operator view."
|
|
9071
|
-
}
|
|
9072
|
-
}, ["room_id"]),
|
|
9073
|
-
result: "An array of communication records, byte-bounded to one page.",
|
|
9074
|
-
example: { room_id: EXAMPLE_ROOM_ID, after: 0, limit: 50, view: "operator" }
|
|
9075
|
-
},
|
|
9076
|
-
{
|
|
9077
|
-
method: "room.message",
|
|
9078
|
-
summary: "Post an operator message",
|
|
9079
|
-
description: "Appends an operator message to the room archive and relays it to active seats. Authorship is assigned by the daemon.",
|
|
9080
|
-
params: params({
|
|
9081
|
-
room_id: roomIdProperty,
|
|
9082
|
-
text: missionTextProperty("Message text")
|
|
9083
|
-
}, ["room_id", "text"]),
|
|
9084
|
-
result: "The appended message record with its `seq` and `record_id`.",
|
|
9085
|
-
example: { room_id: EXAMPLE_ROOM_ID, text: "Standup at 10:00." }
|
|
9086
|
-
},
|
|
9087
|
-
{
|
|
9088
|
-
method: "room.close",
|
|
9089
|
-
summary: "Close a room",
|
|
9090
|
-
description: "Closes the room forward-only. The plaintext local archive is left in place.",
|
|
9091
|
-
params: params({ room_id: roomIdProperty }, ["room_id"]),
|
|
9092
|
-
result: "The closed room record.",
|
|
9093
|
-
example: { room_id: EXAMPLE_ROOM_ID }
|
|
9094
|
-
},
|
|
9095
|
-
{
|
|
9096
|
-
method: "room.delete",
|
|
9097
|
-
summary: "Delete a room",
|
|
9098
|
-
description: "Removes this host's local room state after the room is closed. The scope is this host only.",
|
|
9099
|
-
params: params({
|
|
9100
|
-
room_id: roomIdProperty,
|
|
9101
|
-
confirm: { type: "boolean", const: true, description: "Must be `true`." }
|
|
9102
|
-
}, ["room_id", "confirm"]),
|
|
9103
|
-
result: 'A delete receipt: `{ "version": 1, "room_id": "...", "deleted": true, "scope": "this_host" }`.',
|
|
9104
|
-
example: { room_id: EXAMPLE_ROOM_ID, confirm: true }
|
|
9105
|
-
}
|
|
9106
|
-
];
|
|
9107
|
-
RPC_ERROR_CODES = [
|
|
9108
|
-
["invalid_json", "The request body was not valid JSON."],
|
|
9109
|
-
["invalid_request", "The RPC envelope did not match the strict envelope schema."],
|
|
9110
|
-
["invalid_params", "The `params` object failed method validation."],
|
|
9111
|
-
["not_found", "The referenced room or record does not exist."],
|
|
9112
|
-
["invalid_state", "The room is not in a state that permits the operation."],
|
|
9113
|
-
["shutting_down", "The daemon stopped accepting work."],
|
|
9114
|
-
["method_not_found", "The method is not served over REST."],
|
|
9115
|
-
["unsupported_media_type", "The request did not declare `application/json`."],
|
|
9116
|
-
["forbidden", "The request origin or Host header was not the loopback console origin."],
|
|
9117
|
-
["request_too_large", "The request body exceeded 1 MiB."],
|
|
9118
|
-
["internal", "The daemon failed to complete the operation."]
|
|
9119
|
-
];
|
|
9120
|
-
DOCS_PAGE = `<!DOCTYPE html>
|
|
9121
|
-
<html lang="en">
|
|
9122
|
-
<head>
|
|
9123
|
-
<meta charset="utf-8">
|
|
9124
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
9125
|
-
<title>ours-cowork room management API</title>
|
|
9126
|
-
<link rel="stylesheet" href="${API_DOCS_STYLESHEET_PATH}">
|
|
9127
|
-
</head>
|
|
9128
|
-
<body>
|
|
9129
|
-
<main id="api-docs" data-document="${OPENAPI_DOCUMENT_PATH}" data-rpc="${RPC_PATH}">
|
|
9130
|
-
<p class="loading">Loading <code>${OPENAPI_DOCUMENT_PATH}</code>…</p>
|
|
9131
|
-
</main>
|
|
9132
|
-
<script src="${API_DOCS_SCRIPT_PATH}"></script>
|
|
9133
|
-
</body>
|
|
9134
|
-
</html>
|
|
9135
|
-
`;
|
|
9136
|
-
DOCS_STYLESHEET = `:root { color-scheme: light dark; --line: #8883; --accent: #2563eb; }
|
|
9137
|
-
* { box-sizing: border-box; }
|
|
9138
|
-
body { margin: 0; font: 15px/1.5 ui-sans-serif, system-ui, sans-serif; }
|
|
9139
|
-
main { margin: 0 auto; max-width: 60rem; padding: 2rem 1.25rem 4rem; }
|
|
9140
|
-
h1 { font-size: 1.6rem; margin: 0 0 .25rem; }
|
|
9141
|
-
h2 { font-size: 1.15rem; margin: 2rem 0 .5rem; }
|
|
9142
|
-
code, pre, textarea { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .86em; }
|
|
9143
|
-
pre { background: #8881; border-radius: 6px; margin: 0; overflow-x: auto; padding: .6rem .75rem; }
|
|
9144
|
-
.subtitle { color: #8889; margin: 0 0 1rem; }
|
|
9145
|
-
.intro { border-left: 3px solid var(--accent); padding-left: .9rem; white-space: pre-wrap; }
|
|
9146
|
-
.route { border: 1px solid var(--line); border-radius: 8px; margin-bottom: .5rem; padding: .6rem .8rem; }
|
|
9147
|
-
.verb { background: var(--accent); border-radius: 4px; color: #fff; font-size: .72rem;
|
|
9148
|
-
font-weight: 700; letter-spacing: .04em; margin-right: .5rem; padding: .12rem .4rem; }
|
|
9149
|
-
details.method { border: 1px solid var(--line); border-radius: 8px; margin-bottom: .5rem; }
|
|
9150
|
-
details.method > summary { cursor: pointer; padding: .6rem .8rem; }
|
|
9151
|
-
details.method[open] > summary { border-bottom: 1px solid var(--line); }
|
|
9152
|
-
summary .name { font-family: ui-monospace, monospace; font-weight: 600; }
|
|
9153
|
-
summary .summary-text { color: #8889; margin-left: .6rem; }
|
|
9154
|
-
.body { padding: .8rem; }
|
|
9155
|
-
.description { margin: 0 0 .8rem; white-space: pre-wrap; }
|
|
9156
|
-
table { border-collapse: collapse; margin-bottom: .8rem; width: 100%; }
|
|
9157
|
-
th, td { border-bottom: 1px solid var(--line); padding: .35rem .5rem; text-align: left;
|
|
9158
|
-
vertical-align: top; }
|
|
9159
|
-
th { font-size: .78rem; letter-spacing: .04em; text-transform: uppercase; }
|
|
9160
|
-
td.param { font-family: ui-monospace, monospace; white-space: nowrap; }
|
|
9161
|
-
.required { color: #b91c1c; font-size: .75rem; margin-left: .3rem; }
|
|
9162
|
-
textarea { border: 1px solid var(--line); border-radius: 6px; padding: .5rem; width: 100%; }
|
|
9163
|
-
button { background: var(--accent); border: 0; border-radius: 6px; color: #fff; cursor: pointer;
|
|
9164
|
-
font: inherit; margin: .5rem 0; padding: .4rem 1rem; }
|
|
9165
|
-
.status { font-weight: 600; margin: .5rem 0 .35rem; }
|
|
9166
|
-
.status.failed { color: #b91c1c; }
|
|
9167
|
-
.error { color: #b91c1c; }
|
|
9168
|
-
`;
|
|
9169
|
-
DOCS_SCRIPT = `'use strict';
|
|
9170
|
-
(function () {
|
|
9171
|
-
var root = document.getElementById('api-docs');
|
|
9172
|
-
var documentUrl = root.dataset.document;
|
|
9173
|
-
var rpcUrl = root.dataset.rpc;
|
|
9174
|
-
|
|
9175
|
-
function element(tag, className, text) {
|
|
9176
|
-
var node = document.createElement(tag);
|
|
9177
|
-
if (className) node.className = className;
|
|
9178
|
-
if (text !== undefined) node.textContent = text;
|
|
9179
|
-
return node;
|
|
9180
|
-
}
|
|
9181
|
-
|
|
9182
|
-
function resolveRef(spec, ref) {
|
|
9183
|
-
return ref.replace(/^#\\//, '').split('/').reduce(function (node, part) {
|
|
9184
|
-
return node === undefined ? undefined : node[part];
|
|
9185
|
-
}, spec);
|
|
9186
|
-
}
|
|
9187
|
-
|
|
9188
|
-
function typeLabel(schema) {
|
|
9189
|
-
if (!schema) return 'any';
|
|
9190
|
-
if (schema.const !== undefined) return JSON.stringify(schema.const);
|
|
9191
|
-
if (schema.enum) return schema.enum.map(function (value) { return JSON.stringify(value); }).join(' | ');
|
|
9192
|
-
return schema.type || 'any';
|
|
9193
|
-
}
|
|
9194
|
-
|
|
9195
|
-
function parameterTable(schema) {
|
|
9196
|
-
var properties = (schema && schema.properties) || {};
|
|
9197
|
-
var names = Object.keys(properties);
|
|
9198
|
-
if (names.length === 0) return element('p', 'description', 'No parameters.');
|
|
9199
|
-
var required = (schema && schema.required) || [];
|
|
9200
|
-
var table = document.createElement('table');
|
|
9201
|
-
var head = table.insertRow();
|
|
9202
|
-
['Parameter', 'Type', 'Description'].forEach(function (label) {
|
|
9203
|
-
head.appendChild(element('th', null, label));
|
|
9204
|
-
});
|
|
9205
|
-
names.forEach(function (name) {
|
|
9206
|
-
var property = properties[name];
|
|
9207
|
-
var row = table.insertRow();
|
|
9208
|
-
var cell = element('td', 'param', name);
|
|
9209
|
-
if (required.indexOf(name) >= 0) cell.appendChild(element('span', 'required', 'required'));
|
|
9210
|
-
row.appendChild(cell);
|
|
9211
|
-
row.appendChild(element('td', null, typeLabel(property)));
|
|
9212
|
-
row.appendChild(element('td', null, property.description || ''));
|
|
9213
|
-
});
|
|
9214
|
-
return table;
|
|
9215
|
-
}
|
|
9216
|
-
|
|
9217
|
-
function tryItPanel(example) {
|
|
9218
|
-
var panel = document.createElement('div');
|
|
9219
|
-
var input = document.createElement('textarea');
|
|
9220
|
-
input.rows = Math.min(14, JSON.stringify(example, null, 2).split('\\n').length + 1);
|
|
9221
|
-
input.value = JSON.stringify(example, null, 2);
|
|
9222
|
-
input.setAttribute('aria-label', 'Request body');
|
|
9223
|
-
var send = element('button', null, 'Send request');
|
|
9224
|
-
var output = document.createElement('div');
|
|
9225
|
-
send.addEventListener('click', function () {
|
|
9226
|
-
var body = input.value;
|
|
9227
|
-
try { JSON.parse(body); } catch (error) {
|
|
9228
|
-
output.textContent = '';
|
|
9229
|
-
output.appendChild(element('p', 'status failed', 'Request body is not valid JSON.'));
|
|
9230
|
-
return;
|
|
9231
|
-
}
|
|
9232
|
-
send.disabled = true;
|
|
9233
|
-
output.textContent = '';
|
|
9234
|
-
output.appendChild(element('p', 'status', 'Sending\\u2026'));
|
|
9235
|
-
fetch(rpcUrl, {
|
|
9236
|
-
method: 'POST',
|
|
9237
|
-
headers: { 'content-type': 'application/json' },
|
|
9238
|
-
body: body,
|
|
9239
|
-
}).then(function (response) {
|
|
9240
|
-
return response.text().then(function (text) { return { status: response.status, text: text }; });
|
|
9241
|
-
}).then(function (result) {
|
|
9242
|
-
var pretty = result.text;
|
|
9243
|
-
try { pretty = JSON.stringify(JSON.parse(result.text), null, 2); } catch (error) { /* raw */ }
|
|
9244
|
-
output.textContent = '';
|
|
9245
|
-
output.appendChild(element(
|
|
9246
|
-
'p',
|
|
9247
|
-
result.status === 200 ? 'status' : 'status failed',
|
|
9248
|
-
'HTTP ' + result.status
|
|
9249
|
-
));
|
|
9250
|
-
output.appendChild(element('pre', null, pretty));
|
|
9251
|
-
}).catch(function (error) {
|
|
9252
|
-
output.textContent = '';
|
|
9253
|
-
output.appendChild(element('p', 'status failed', 'Request failed: ' + error));
|
|
9254
|
-
}).then(function () { send.disabled = false; });
|
|
9255
|
-
});
|
|
9256
|
-
panel.appendChild(element('h3', null, 'Try it'));
|
|
9257
|
-
panel.appendChild(input);
|
|
9258
|
-
panel.appendChild(send);
|
|
9259
|
-
panel.appendChild(output);
|
|
9260
|
-
return panel;
|
|
9261
|
-
}
|
|
9262
|
-
|
|
9263
|
-
function methodPanel(spec, ref) {
|
|
9264
|
-
var schema = resolveRef(spec, ref);
|
|
9265
|
-
var method = schema.properties.method.const;
|
|
9266
|
-
var panel = element('details', 'method');
|
|
9267
|
-
var summary = document.createElement('summary');
|
|
9268
|
-
summary.appendChild(element('span', 'name', method));
|
|
9269
|
-
summary.appendChild(element('span', 'summary-text', schema.title || ''));
|
|
9270
|
-
panel.appendChild(summary);
|
|
9271
|
-
var body = element('div', 'body');
|
|
9272
|
-
body.appendChild(element('p', 'description', schema.description || ''));
|
|
9273
|
-
body.appendChild(parameterTable(resolveRef(spec, schema.properties.params.$ref)));
|
|
9274
|
-
body.appendChild(tryItPanel((schema.examples && schema.examples[0]) || {
|
|
9275
|
-
version: 1, id: 'req-1', method: method, params: {},
|
|
9276
|
-
}));
|
|
9277
|
-
panel.appendChild(body);
|
|
9278
|
-
return panel;
|
|
9279
|
-
}
|
|
9280
|
-
|
|
9281
|
-
function renderRoutes(spec) {
|
|
9282
|
-
var section = document.createDocumentFragment();
|
|
9283
|
-
Object.keys(spec.paths).forEach(function (path) {
|
|
9284
|
-
Object.keys(spec.paths[path]).forEach(function (verb) {
|
|
9285
|
-
var operation = spec.paths[path][verb];
|
|
9286
|
-
var route = element('div', 'route');
|
|
9287
|
-
var heading = document.createElement('div');
|
|
9288
|
-
heading.appendChild(element('span', 'verb', verb.toUpperCase()));
|
|
9289
|
-
heading.appendChild(element('code', null, path));
|
|
9290
|
-
route.appendChild(heading);
|
|
9291
|
-
route.appendChild(element('p', 'description', operation.summary || ''));
|
|
9292
|
-
section.appendChild(route);
|
|
9293
|
-
});
|
|
9294
|
-
});
|
|
9295
|
-
return section;
|
|
9296
|
-
}
|
|
9297
|
-
|
|
9298
|
-
function render(spec) {
|
|
9299
|
-
root.textContent = '';
|
|
9300
|
-
root.appendChild(element('h1', null, spec.info.title));
|
|
9301
|
-
root.appendChild(element('p', 'subtitle', 'API version ' + spec.info.version + ' \\u00b7 OpenAPI ' + spec.openapi));
|
|
9302
|
-
root.appendChild(element('p', 'intro', spec.info.description || ''));
|
|
9303
|
-
root.appendChild(element('h2', null, 'Routes'));
|
|
9304
|
-
root.appendChild(renderRoutes(spec));
|
|
9305
|
-
root.appendChild(element('h2', null, 'Room management methods'));
|
|
9306
|
-
var alternatives = spec.paths[rpcUrl].post.requestBody.content['application/json'].schema.oneOf;
|
|
9307
|
-
alternatives.forEach(function (alternative) {
|
|
9308
|
-
root.appendChild(methodPanel(spec, alternative.$ref));
|
|
9309
|
-
});
|
|
9310
|
-
}
|
|
9311
|
-
|
|
9312
|
-
fetch(documentUrl).then(function (response) {
|
|
9313
|
-
if (!response.ok) throw new Error('HTTP ' + response.status);
|
|
9314
|
-
return response.json();
|
|
9315
|
-
}).then(render).catch(function (error) {
|
|
9316
|
-
root.textContent = '';
|
|
9317
|
-
root.appendChild(element('p', 'error', 'Could not load ' + documentUrl + ': ' + error));
|
|
9318
|
-
});
|
|
9319
|
-
}());
|
|
9320
|
-
`;
|
|
9321
|
-
}
|
|
9322
|
-
});
|
|
9323
|
-
|
|
9324
8105
|
// src/web.ts
|
|
9325
|
-
import * as
|
|
8106
|
+
import * as nodeFs3 from "node:fs";
|
|
9326
8107
|
import { extname, join as join5 } from "node:path";
|
|
9327
|
-
function loadWebAssets(root,
|
|
8108
|
+
function loadWebAssets(root, fs3 = nodeFs3) {
|
|
9328
8109
|
let rootIdentity;
|
|
9329
8110
|
try {
|
|
9330
|
-
rootIdentity = observeDirectory(
|
|
8111
|
+
rootIdentity = observeDirectory(fs3, root, "web asset root");
|
|
9331
8112
|
} catch (error) {
|
|
9332
8113
|
if (error.code === "ENOENT") return /* @__PURE__ */ new Map();
|
|
9333
8114
|
throw error;
|
|
9334
8115
|
}
|
|
9335
8116
|
const manifest = /* @__PURE__ */ new Map();
|
|
9336
|
-
manifest.set("/", loadAsset(
|
|
8117
|
+
manifest.set("/", loadAsset(fs3, join5(root, "index.html"), false, [rootIdentity]));
|
|
9337
8118
|
const assetsDirectory = join5(root, "assets");
|
|
9338
|
-
assertDirectoryIdentity(
|
|
9339
|
-
const assetsIdentity = observeDirectory(
|
|
9340
|
-
assertDirectoryIdentity(
|
|
8119
|
+
assertDirectoryIdentity(fs3, rootIdentity);
|
|
8120
|
+
const assetsIdentity = observeDirectory(fs3, assetsDirectory, "web assets directory");
|
|
8121
|
+
assertDirectoryIdentity(fs3, rootIdentity);
|
|
9341
8122
|
const directories = [rootIdentity, assetsIdentity];
|
|
9342
|
-
assertDirectoryIdentities(
|
|
9343
|
-
const assetEntries =
|
|
9344
|
-
assertDirectoryIdentities(
|
|
8123
|
+
assertDirectoryIdentities(fs3, directories);
|
|
8124
|
+
const assetEntries = fs3.readdirSync(assetsDirectory, { withFileTypes: true });
|
|
8125
|
+
assertDirectoryIdentities(fs3, directories);
|
|
9345
8126
|
for (const entry of assetEntries) {
|
|
9346
8127
|
if (!entry.isFile() || entry.isSymbolicLink()) continue;
|
|
9347
8128
|
const path = join5(assetsDirectory, entry.name);
|
|
9348
|
-
manifest.set(`/assets/${entry.name}`, loadAsset(
|
|
8129
|
+
manifest.set(`/assets/${entry.name}`, loadAsset(fs3, path, true, directories));
|
|
9349
8130
|
}
|
|
9350
|
-
assertDirectoryIdentities(
|
|
8131
|
+
assertDirectoryIdentities(fs3, directories);
|
|
9351
8132
|
return manifest;
|
|
9352
8133
|
}
|
|
9353
8134
|
function createStaticWebHandler(assets) {
|
|
@@ -9370,44 +8151,44 @@ function createStaticWebHandler(assets) {
|
|
|
9370
8151
|
return true;
|
|
9371
8152
|
};
|
|
9372
8153
|
}
|
|
9373
|
-
function loadAsset(
|
|
9374
|
-
assertDirectoryIdentities(
|
|
9375
|
-
const observed =
|
|
9376
|
-
assertDirectoryIdentities(
|
|
8154
|
+
function loadAsset(fs3, path, immutable, directories) {
|
|
8155
|
+
assertDirectoryIdentities(fs3, directories);
|
|
8156
|
+
const observed = fs3.lstatSync(path);
|
|
8157
|
+
assertDirectoryIdentities(fs3, directories);
|
|
9377
8158
|
if (observed.isSymbolicLink() || !observed.isFile() || observed.nlink !== 1) {
|
|
9378
8159
|
throw new Error(`web asset must be a non-symbolic-link regular file: ${path}`);
|
|
9379
8160
|
}
|
|
9380
8161
|
let fd;
|
|
9381
8162
|
try {
|
|
9382
|
-
fd =
|
|
9383
|
-
const opened =
|
|
9384
|
-
assertDirectoryIdentities(
|
|
8163
|
+
fd = fs3.openSync(path, nodeFs3.constants.O_RDONLY | NO_FOLLOW3);
|
|
8164
|
+
const opened = fs3.fstatSync(fd);
|
|
8165
|
+
assertDirectoryIdentities(fs3, directories);
|
|
9385
8166
|
if (!opened.isFile() || opened.nlink !== 1 || opened.dev !== observed.dev || opened.ino !== observed.ino) {
|
|
9386
8167
|
throw new Error(`web asset changed while opening: ${path}`);
|
|
9387
8168
|
}
|
|
9388
|
-
const bytes =
|
|
9389
|
-
assertDirectoryIdentities(
|
|
8169
|
+
const bytes = fs3.readFileSync(fd);
|
|
8170
|
+
assertDirectoryIdentities(fs3, directories);
|
|
9390
8171
|
return {
|
|
9391
8172
|
bytes,
|
|
9392
8173
|
contentType: CONTENT_TYPES.get(extname(path).toLowerCase()) ?? "application/octet-stream",
|
|
9393
8174
|
immutable
|
|
9394
8175
|
};
|
|
9395
8176
|
} finally {
|
|
9396
|
-
if (fd !== void 0)
|
|
8177
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
9397
8178
|
}
|
|
9398
8179
|
}
|
|
9399
|
-
function observeDirectory(
|
|
9400
|
-
const stat =
|
|
8180
|
+
function observeDirectory(fs3, path, label) {
|
|
8181
|
+
const stat = fs3.lstatSync(path);
|
|
9401
8182
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
9402
8183
|
throw new Error(`${label} must be a non-symbolic-link directory`);
|
|
9403
8184
|
}
|
|
9404
8185
|
return { path, label, dev: stat.dev, ino: stat.ino };
|
|
9405
8186
|
}
|
|
9406
|
-
function assertDirectoryIdentities(
|
|
9407
|
-
for (const directory of directories) assertDirectoryIdentity(
|
|
8187
|
+
function assertDirectoryIdentities(fs3, directories) {
|
|
8188
|
+
for (const directory of directories) assertDirectoryIdentity(fs3, directory);
|
|
9408
8189
|
}
|
|
9409
|
-
function assertDirectoryIdentity(
|
|
9410
|
-
const current =
|
|
8190
|
+
function assertDirectoryIdentity(fs3, expected) {
|
|
8191
|
+
const current = fs3.lstatSync(expected.path);
|
|
9411
8192
|
if (current.isSymbolicLink() || !current.isDirectory() || current.dev !== expected.dev || current.ino !== expected.ino) {
|
|
9412
8193
|
throw new Error(`${expected.label} changed during web asset loading`);
|
|
9413
8194
|
}
|
|
@@ -9446,7 +8227,7 @@ var NO_FOLLOW3, CONTENT_SECURITY_POLICY, CONTENT_TYPES;
|
|
|
9446
8227
|
var init_web = __esm({
|
|
9447
8228
|
"src/web.ts"() {
|
|
9448
8229
|
"use strict";
|
|
9449
|
-
NO_FOLLOW3 =
|
|
8230
|
+
NO_FOLLOW3 = nodeFs3.constants.O_NOFOLLOW ?? 0;
|
|
9450
8231
|
CONTENT_SECURITY_POLICY = [
|
|
9451
8232
|
"default-src 'self'",
|
|
9452
8233
|
"script-src 'self'",
|
|
@@ -9483,12 +8264,12 @@ var init_web = __esm({
|
|
|
9483
8264
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
9484
8265
|
import * as http from "node:http";
|
|
9485
8266
|
import * as net from "node:net";
|
|
9486
|
-
import * as
|
|
9487
|
-
import { basename, dirname as
|
|
8267
|
+
import * as nodeFs4 from "node:fs";
|
|
8268
|
+
import { basename, dirname as dirname3, join as join6 } from "node:path";
|
|
9488
8269
|
function createServiceRoutes(service) {
|
|
9489
8270
|
return {
|
|
9490
|
-
"room.create": { auth: true, run: (
|
|
9491
|
-
"room.settings": { auth: true, run: (
|
|
8271
|
+
"room.create": { auth: true, run: (params) => service.createRoom(params) },
|
|
8272
|
+
"room.settings": { auth: true, run: (params) => {
|
|
9492
8273
|
const { room_id, ...input } = external_exports.object({
|
|
9493
8274
|
room_id: external_exports.string(),
|
|
9494
8275
|
name: external_exports.unknown().optional(),
|
|
@@ -9496,68 +8277,68 @@ function createServiceRoutes(service) {
|
|
|
9496
8277
|
briefing: external_exports.unknown().optional(),
|
|
9497
8278
|
status: external_exports.unknown().optional(),
|
|
9498
8279
|
quiet_membership: external_exports.unknown().optional()
|
|
9499
|
-
}).strict().parse(
|
|
8280
|
+
}).strict().parse(params);
|
|
9500
8281
|
return service.updateRoom(room_id, input);
|
|
9501
8282
|
} },
|
|
9502
|
-
"room.briefing.role.set": { auth: true, run: (
|
|
9503
|
-
const { room_id, ...input } = RoleBriefingSetParams.parse(
|
|
8283
|
+
"room.briefing.role.set": { auth: true, run: (params) => {
|
|
8284
|
+
const { room_id, ...input } = RoleBriefingSetParams.parse(params);
|
|
9504
8285
|
return service.setRoleBriefing(room_id, input);
|
|
9505
8286
|
} },
|
|
9506
|
-
"room.briefing.role.delete": { auth: true, run: (
|
|
9507
|
-
const { room_id, ...input } = RoleBriefingDeleteParams.parse(
|
|
8287
|
+
"room.briefing.role.delete": { auth: true, run: (params) => {
|
|
8288
|
+
const { room_id, ...input } = RoleBriefingDeleteParams.parse(params);
|
|
9508
8289
|
return service.deleteRoleBriefing(room_id, input);
|
|
9509
8290
|
} },
|
|
9510
|
-
"room.invite": { auth: true, run: (
|
|
8291
|
+
"room.invite": { auth: true, run: (params) => {
|
|
9511
8292
|
const { room_id, ...input } = external_exports.object({
|
|
9512
8293
|
room_id: external_exports.string(),
|
|
9513
8294
|
mode: external_exports.unknown(),
|
|
9514
8295
|
role: external_exports.unknown().optional(),
|
|
9515
8296
|
min_accepts: external_exports.unknown()
|
|
9516
|
-
}).strict().parse(
|
|
8297
|
+
}).strict().parse(params);
|
|
9517
8298
|
return service.createInvite(room_id, input);
|
|
9518
8299
|
} },
|
|
9519
|
-
"room.participant.remove": { auth: true, run: (
|
|
9520
|
-
const { room_id, ...input } = ParticipantRemoveParams.parse(
|
|
8300
|
+
"room.participant.remove": { auth: true, run: (params) => {
|
|
8301
|
+
const { room_id, ...input } = ParticipantRemoveParams.parse(params);
|
|
9521
8302
|
return service.removeParticipant(room_id, input);
|
|
9522
8303
|
} },
|
|
9523
|
-
"room.participant.replace": { auth: true, run: (
|
|
9524
|
-
const { room_id, ...input } = ParticipantReplaceParams.parse(
|
|
8304
|
+
"room.participant.replace": { auth: true, run: (params) => {
|
|
8305
|
+
const { room_id, ...input } = ParticipantReplaceParams.parse(params);
|
|
9525
8306
|
return service.replaceParticipant(room_id, input);
|
|
9526
8307
|
} },
|
|
9527
|
-
"room.revoke": { auth: true, run: (
|
|
9528
|
-
const value = InviteRevokeParams.parse(
|
|
8308
|
+
"room.revoke": { auth: true, run: (params) => {
|
|
8309
|
+
const value = InviteRevokeParams.parse(params);
|
|
9529
8310
|
return service.revokeInvite(value.room_id, value.invite_id);
|
|
9530
8311
|
} },
|
|
9531
|
-
"room.recover": { auth: true, run: (
|
|
9532
|
-
"room.recover.confirm": { auth: true, run: (
|
|
9533
|
-
const value = RecoverConfirmParams.parse(
|
|
8312
|
+
"room.recover": { auth: true, run: (params) => service.recoverInvites(RoomIdParams.parse(params).room_id) },
|
|
8313
|
+
"room.recover.confirm": { auth: true, run: (params) => {
|
|
8314
|
+
const value = RecoverConfirmParams.parse(params);
|
|
9534
8315
|
return service.confirmRecoveredInvite(value.room_id, value.recovery_of, value.invite_id);
|
|
9535
8316
|
} },
|
|
9536
|
-
"room.list": { auth: true, run: (
|
|
9537
|
-
external_exports.object({}).strict().parse(
|
|
8317
|
+
"room.list": { auth: true, run: (params) => {
|
|
8318
|
+
external_exports.object({}).strict().parse(params);
|
|
9538
8319
|
return service.listRooms();
|
|
9539
8320
|
} },
|
|
9540
|
-
"room.show": { auth: true, run: (
|
|
9541
|
-
"room.participants": { auth: true, run: (
|
|
9542
|
-
"room.history": { auth: true, run: (
|
|
9543
|
-
const { room_id, ...options } = HistoryParams.parse(
|
|
8321
|
+
"room.show": { auth: true, run: (params) => service.showRoom(RoomIdParams.parse(params).room_id) },
|
|
8322
|
+
"room.participants": { auth: true, run: (params) => service.participants(RoomIdParams.parse(params).room_id) },
|
|
8323
|
+
"room.history": { auth: true, run: (params) => {
|
|
8324
|
+
const { room_id, ...options } = HistoryParams.parse(params);
|
|
9544
8325
|
return service.history(room_id, options);
|
|
9545
8326
|
} },
|
|
9546
|
-
"room.message": { auth: true, run: (
|
|
9547
|
-
const { room_id, ...input } = external_exports.object({ room_id: external_exports.string(), text: external_exports.unknown() }).strict().parse(
|
|
8327
|
+
"room.message": { auth: true, run: (params) => {
|
|
8328
|
+
const { room_id, ...input } = external_exports.object({ room_id: external_exports.string(), text: external_exports.unknown() }).strict().parse(params);
|
|
9548
8329
|
return service.postMessage(room_id, input);
|
|
9549
8330
|
} },
|
|
9550
|
-
"room.close": { auth: true, run: (
|
|
9551
|
-
"room.delete": { auth: true, run: (
|
|
9552
|
-
const { room_id, ...input } = external_exports.object({ room_id: external_exports.string(), confirm: external_exports.unknown() }).strict().parse(
|
|
8331
|
+
"room.close": { auth: true, run: (params) => service.closeRoom(RoomIdParams.parse(params).room_id) },
|
|
8332
|
+
"room.delete": { auth: true, run: (params) => {
|
|
8333
|
+
const { room_id, ...input } = external_exports.object({ room_id: external_exports.string(), confirm: external_exports.unknown() }).strict().parse(params);
|
|
9553
8334
|
return service.deleteRoom(room_id, input);
|
|
9554
8335
|
} }
|
|
9555
8336
|
};
|
|
9556
8337
|
}
|
|
9557
8338
|
function createPrivateServiceRoutes(service) {
|
|
9558
8339
|
return {
|
|
9559
|
-
"room.accept": { auth: true, run: (
|
|
9560
|
-
const { room_id, ...input } = ExternalInviteAcceptParams.parse(
|
|
8340
|
+
"room.accept": { auth: true, run: (params) => {
|
|
8341
|
+
const { room_id, ...input } = ExternalInviteAcceptParams.parse(params);
|
|
9561
8342
|
return service.acceptExternalInvite(room_id, input);
|
|
9562
8343
|
} }
|
|
9563
8344
|
};
|
|
@@ -9577,23 +8358,6 @@ function isJsonMediaType(value) {
|
|
|
9577
8358
|
const mediaType = value.split(";", 1)[0]?.trim().toLowerCase();
|
|
9578
8359
|
return mediaType === "application/json" || mediaType?.startsWith("application/") === true && mediaType.endsWith("+json");
|
|
9579
8360
|
}
|
|
9580
|
-
async function serveApiDocs(request, response) {
|
|
9581
|
-
const pathname = safePathname(request.url);
|
|
9582
|
-
if (pathname === void 0) return false;
|
|
9583
|
-
const asset = apiDocsAsset(pathname);
|
|
9584
|
-
if (!asset) return false;
|
|
9585
|
-
const body = Buffer.from(asset.body, "utf8");
|
|
9586
|
-
response.writeHead(200, {
|
|
9587
|
-
"content-type": asset.contentType,
|
|
9588
|
-
"content-length": body.length,
|
|
9589
|
-
"cache-control": "no-cache",
|
|
9590
|
-
"content-security-policy": CONTENT_SECURITY_POLICY,
|
|
9591
|
-
"x-content-type-options": "nosniff"
|
|
9592
|
-
});
|
|
9593
|
-
response.end(body);
|
|
9594
|
-
await responseFinished2(response);
|
|
9595
|
-
return true;
|
|
9596
|
-
}
|
|
9597
8361
|
function sendJson(response, status, value) {
|
|
9598
8362
|
const body = Buffer.from(JSON.stringify(value), "utf8");
|
|
9599
8363
|
response.writeHead(status, {
|
|
@@ -9711,7 +8475,6 @@ var init_transports = __esm({
|
|
|
9711
8475
|
"src/transports.ts"() {
|
|
9712
8476
|
"use strict";
|
|
9713
8477
|
init_zod();
|
|
9714
|
-
init_openapi();
|
|
9715
8478
|
init_web();
|
|
9716
8479
|
MAX_REQUEST_BYTES = 1024 * 1024;
|
|
9717
8480
|
HTTP_HEADERS_TIMEOUT_MS = 5e3;
|
|
@@ -9831,7 +8594,7 @@ var init_transports = __esm({
|
|
|
9831
8594
|
protectedPrivateReplacement;
|
|
9832
8595
|
constructor(options) {
|
|
9833
8596
|
this.options = options;
|
|
9834
|
-
this.fs = options.fs ??
|
|
8597
|
+
this.fs = options.fs ?? nodeFs4;
|
|
9835
8598
|
this.staticHandler = options.staticHandler ?? createStaticWebHandler(/* @__PURE__ */ new Map());
|
|
9836
8599
|
}
|
|
9837
8600
|
get restAddress() {
|
|
@@ -9978,7 +8741,6 @@ var init_transports = __esm({
|
|
|
9978
8741
|
if (request.url !== "/rpc") {
|
|
9979
8742
|
if (request.method === "GET") {
|
|
9980
8743
|
activateResponse();
|
|
9981
|
-
if (await serveApiDocs(request, response)) return;
|
|
9982
8744
|
if (await this.staticHandler(request, response)) return;
|
|
9983
8745
|
}
|
|
9984
8746
|
activateResponse();
|
|
@@ -10127,7 +8889,7 @@ var init_transports = __esm({
|
|
|
10127
8889
|
throw new Error(`private management socket changed during removal; replacement protected at ${quarantined.path}`);
|
|
10128
8890
|
}
|
|
10129
8891
|
async cleanupStalePrivateSockets() {
|
|
10130
|
-
const directory =
|
|
8892
|
+
const directory = dirname3(this.options.socketPath);
|
|
10131
8893
|
const prefix = `${basename(this.options.socketPath)}.private-`;
|
|
10132
8894
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
10133
8895
|
const candidates = this.fs.readdirSync(directory).filter((name) => name.startsWith(prefix)).sort().slice(0, MAX_STALE_PRIVATE_SOCKET_CLEANUP);
|
|
@@ -10235,24 +8997,24 @@ __export(daemon_runtime_exports, {
|
|
|
10235
8997
|
removeDaemonPid: () => removeDaemonPid,
|
|
10236
8998
|
writeDaemonPid: () => writeDaemonPid
|
|
10237
8999
|
});
|
|
10238
|
-
import * as
|
|
9000
|
+
import * as nodeFs5 from "node:fs";
|
|
10239
9001
|
import { join as join7 } from "node:path";
|
|
10240
|
-
import { fileURLToPath
|
|
9002
|
+
import { fileURLToPath } from "node:url";
|
|
10241
9003
|
function isIntakeNotification(event) {
|
|
10242
9004
|
return event === "message_received" || event === "file_received" || event === "contact_accepted" || event === "contact_added";
|
|
10243
9005
|
}
|
|
10244
9006
|
function createDaemonControlRoutes(control) {
|
|
10245
9007
|
if (!/^[0-9a-f]{32}$/.test(control.session)) throw new TypeError("invalid daemon control session");
|
|
10246
|
-
const requireExact = (
|
|
10247
|
-
if (Object.keys(
|
|
9008
|
+
const requireExact = (params, keys) => {
|
|
9009
|
+
if (Object.keys(params).length !== keys.length || keys.some((key) => !Object.hasOwn(params, key))) {
|
|
10248
9010
|
throw new TypeError("invalid daemon control parameters");
|
|
10249
9011
|
}
|
|
10250
9012
|
};
|
|
10251
9013
|
return {
|
|
10252
9014
|
"daemon.status": {
|
|
10253
9015
|
auth: true,
|
|
10254
|
-
run(
|
|
10255
|
-
requireExact(
|
|
9016
|
+
run(params) {
|
|
9017
|
+
requireExact(params, []);
|
|
10256
9018
|
return {
|
|
10257
9019
|
version: 1,
|
|
10258
9020
|
protocol: "cowork-supervisor-control",
|
|
@@ -10263,9 +9025,9 @@ function createDaemonControlRoutes(control) {
|
|
|
10263
9025
|
},
|
|
10264
9026
|
"daemon.shutdown": {
|
|
10265
9027
|
auth: true,
|
|
10266
|
-
async run(
|
|
10267
|
-
requireExact(
|
|
10268
|
-
if (
|
|
9028
|
+
async run(params) {
|
|
9029
|
+
requireExact(params, ["session"]);
|
|
9030
|
+
if (params.session !== control.session) throw new TypeError("daemon control session changed");
|
|
10269
9031
|
if (!await control.requestSupervisorShutdown()) throw new Error("supervisor IPC rejected shutdown request");
|
|
10270
9032
|
return { accepted: true, session: control.session };
|
|
10271
9033
|
}
|
|
@@ -10273,7 +9035,7 @@ function createDaemonControlRoutes(control) {
|
|
|
10273
9035
|
};
|
|
10274
9036
|
}
|
|
10275
9037
|
function acquireDaemonLock(stateDir, options = {}) {
|
|
10276
|
-
const
|
|
9038
|
+
const fs3 = options.fs ?? nodeFs5;
|
|
10277
9039
|
const pid = options.pid ?? process.pid;
|
|
10278
9040
|
const alive = options.isProcessAlive ?? isProcessAlive;
|
|
10279
9041
|
const path = join7(stateDir, "daemon.lock");
|
|
@@ -10281,22 +9043,22 @@ function acquireDaemonLock(stateDir, options = {}) {
|
|
|
10281
9043
|
let fd;
|
|
10282
9044
|
let created = false;
|
|
10283
9045
|
try {
|
|
10284
|
-
fd =
|
|
9046
|
+
fd = fs3.openSync(path, nodeFs5.constants.O_CREAT | nodeFs5.constants.O_EXCL | nodeFs5.constants.O_WRONLY | NO_FOLLOW4, FILE_MODE3);
|
|
10285
9047
|
created = true;
|
|
10286
|
-
|
|
10287
|
-
writeAll(
|
|
9048
|
+
fs3.fchmodSync(fd, FILE_MODE3);
|
|
9049
|
+
writeAll(fs3, fd, Buffer.from(`${pid}
|
|
10288
9050
|
`, "ascii"));
|
|
10289
|
-
|
|
10290
|
-
const owned =
|
|
10291
|
-
|
|
9051
|
+
fs3.fsyncSync(fd);
|
|
9052
|
+
const owned = fs3.fstatSync(fd);
|
|
9053
|
+
fs3.closeSync(fd);
|
|
10292
9054
|
fd = void 0;
|
|
10293
|
-
fsyncDirectory2(
|
|
9055
|
+
fsyncDirectory2(fs3, stateDir);
|
|
10294
9056
|
const pidPath = join7(stateDir, "daemon.pid");
|
|
10295
|
-
if (lstatIfPresent2(
|
|
10296
|
-
const pidOwner = readSecurePid(
|
|
9057
|
+
if (lstatIfPresent2(fs3, pidPath)) {
|
|
9058
|
+
const pidOwner = readSecurePid(fs3, pidPath, "daemon PID");
|
|
10297
9059
|
if (alive(pidOwner)) {
|
|
10298
|
-
|
|
10299
|
-
fsyncDirectory2(
|
|
9060
|
+
fs3.unlinkSync(path);
|
|
9061
|
+
fsyncDirectory2(fs3, stateDir);
|
|
10300
9062
|
throw new Error(`cowork daemon is already running with PID ${pidOwner}`);
|
|
10301
9063
|
}
|
|
10302
9064
|
}
|
|
@@ -10305,90 +9067,90 @@ function acquireDaemonLock(stateDir, options = {}) {
|
|
|
10305
9067
|
release() {
|
|
10306
9068
|
if (released) return;
|
|
10307
9069
|
released = true;
|
|
10308
|
-
const current = lstatIfPresent2(
|
|
9070
|
+
const current = lstatIfPresent2(fs3, path);
|
|
10309
9071
|
if (!current || current.dev !== owned.dev || current.ino !== owned.ino) return;
|
|
10310
|
-
const content = readSecurePid(
|
|
9072
|
+
const content = readSecurePid(fs3, path, "daemon lock");
|
|
10311
9073
|
if (content !== pid) return;
|
|
10312
|
-
|
|
10313
|
-
fsyncDirectory2(
|
|
9074
|
+
fs3.unlinkSync(path);
|
|
9075
|
+
fsyncDirectory2(fs3, stateDir);
|
|
10314
9076
|
}
|
|
10315
9077
|
};
|
|
10316
9078
|
} catch (error) {
|
|
10317
9079
|
if (fd !== void 0) try {
|
|
10318
|
-
|
|
9080
|
+
fs3.closeSync(fd);
|
|
10319
9081
|
} catch {
|
|
10320
9082
|
}
|
|
10321
9083
|
if (error.code !== "EEXIST") {
|
|
10322
9084
|
if (created) {
|
|
10323
9085
|
try {
|
|
10324
|
-
|
|
10325
|
-
fsyncDirectory2(
|
|
9086
|
+
fs3.unlinkSync(path);
|
|
9087
|
+
fsyncDirectory2(fs3, stateDir);
|
|
10326
9088
|
} catch {
|
|
10327
9089
|
}
|
|
10328
9090
|
}
|
|
10329
9091
|
throw error;
|
|
10330
9092
|
}
|
|
10331
|
-
const observed =
|
|
10332
|
-
const owner = readSecurePid(
|
|
9093
|
+
const observed = fs3.lstatSync(path);
|
|
9094
|
+
const owner = readSecurePid(fs3, path, "daemon lock");
|
|
10333
9095
|
if (alive(owner)) throw new Error(`cowork daemon is already running with PID ${owner}`);
|
|
10334
|
-
const current =
|
|
9096
|
+
const current = fs3.lstatSync(path);
|
|
10335
9097
|
if (current.dev !== observed.dev || current.ino !== observed.ino) continue;
|
|
10336
|
-
|
|
10337
|
-
fsyncDirectory2(
|
|
9098
|
+
fs3.unlinkSync(path);
|
|
9099
|
+
fsyncDirectory2(fs3, stateDir);
|
|
10338
9100
|
}
|
|
10339
9101
|
}
|
|
10340
9102
|
throw new Error("daemon lock changed repeatedly while acquiring it");
|
|
10341
9103
|
}
|
|
10342
|
-
function writeDaemonPid(stateDir,
|
|
9104
|
+
function writeDaemonPid(stateDir, fs3 = nodeFs5, pid = process.pid) {
|
|
10343
9105
|
const path = join7(stateDir, "daemon.pid");
|
|
10344
|
-
const existing = lstatIfPresent2(
|
|
9106
|
+
const existing = lstatIfPresent2(fs3, path);
|
|
10345
9107
|
if (existing) {
|
|
10346
|
-
const owner = readSecurePid(
|
|
9108
|
+
const owner = readSecurePid(fs3, path, "daemon PID");
|
|
10347
9109
|
if (isProcessAlive(owner) && owner !== pid) throw new Error(`cowork daemon PID file belongs to live PID ${owner}`);
|
|
10348
|
-
const current =
|
|
9110
|
+
const current = fs3.lstatSync(path);
|
|
10349
9111
|
if (current.dev !== existing.dev || current.ino !== existing.ino) throw new Error("daemon PID file changed during stale-owner check");
|
|
10350
|
-
|
|
9112
|
+
fs3.unlinkSync(path);
|
|
10351
9113
|
}
|
|
10352
9114
|
let fd;
|
|
10353
9115
|
let created = false;
|
|
10354
9116
|
try {
|
|
10355
|
-
fd =
|
|
9117
|
+
fd = fs3.openSync(path, nodeFs5.constants.O_CREAT | nodeFs5.constants.O_EXCL | nodeFs5.constants.O_WRONLY | NO_FOLLOW4, FILE_MODE3);
|
|
10356
9118
|
created = true;
|
|
10357
|
-
|
|
10358
|
-
writeAll(
|
|
9119
|
+
fs3.fchmodSync(fd, FILE_MODE3);
|
|
9120
|
+
writeAll(fs3, fd, Buffer.from(`${pid}
|
|
10359
9121
|
`, "ascii"));
|
|
10360
|
-
|
|
9122
|
+
fs3.fsyncSync(fd);
|
|
10361
9123
|
} catch (error) {
|
|
10362
9124
|
if (fd !== void 0) {
|
|
10363
9125
|
try {
|
|
10364
|
-
|
|
9126
|
+
fs3.closeSync(fd);
|
|
10365
9127
|
} catch {
|
|
10366
9128
|
}
|
|
10367
9129
|
fd = void 0;
|
|
10368
9130
|
}
|
|
10369
9131
|
if (created) try {
|
|
10370
|
-
|
|
9132
|
+
fs3.unlinkSync(path);
|
|
10371
9133
|
} catch {
|
|
10372
9134
|
}
|
|
10373
9135
|
throw error;
|
|
10374
9136
|
} finally {
|
|
10375
|
-
if (fd !== void 0)
|
|
9137
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
10376
9138
|
}
|
|
10377
|
-
fsyncDirectory2(
|
|
9139
|
+
fsyncDirectory2(fs3, stateDir);
|
|
10378
9140
|
}
|
|
10379
|
-
function removeDaemonPid(stateDir,
|
|
9141
|
+
function removeDaemonPid(stateDir, fs3 = nodeFs5, pid = process.pid) {
|
|
10380
9142
|
const path = join7(stateDir, "daemon.pid");
|
|
10381
|
-
const observed = lstatIfPresent2(
|
|
9143
|
+
const observed = lstatIfPresent2(fs3, path);
|
|
10382
9144
|
if (!observed) return;
|
|
10383
|
-
const owner = readSecurePid(
|
|
9145
|
+
const owner = readSecurePid(fs3, path, "daemon PID");
|
|
10384
9146
|
if (owner !== pid) return;
|
|
10385
|
-
const current =
|
|
9147
|
+
const current = fs3.lstatSync(path);
|
|
10386
9148
|
if (current.dev !== observed.dev || current.ino !== observed.ino) return;
|
|
10387
|
-
|
|
10388
|
-
fsyncDirectory2(
|
|
9149
|
+
fs3.unlinkSync(path);
|
|
9150
|
+
fsyncDirectory2(fs3, stateDir);
|
|
10389
9151
|
}
|
|
10390
|
-
function readSecurePid(
|
|
10391
|
-
const stat =
|
|
9152
|
+
function readSecurePid(fs3, path, label) {
|
|
9153
|
+
const stat = fs3.lstatSync(path);
|
|
10392
9154
|
if (stat.isSymbolicLink() || !stat.isFile() || stat.nlink !== 1 || (stat.mode & 511) !== FILE_MODE3) {
|
|
10393
9155
|
throw new Error(`${label} must be a 0600 single-link regular file`);
|
|
10394
9156
|
}
|
|
@@ -10398,15 +9160,15 @@ function readSecurePid(fs2, path, label) {
|
|
|
10398
9160
|
let fd;
|
|
10399
9161
|
let text;
|
|
10400
9162
|
try {
|
|
10401
|
-
fd =
|
|
10402
|
-
const opened =
|
|
10403
|
-
const current =
|
|
9163
|
+
fd = fs3.openSync(path, nodeFs5.constants.O_RDONLY | NO_FOLLOW4);
|
|
9164
|
+
const opened = fs3.fstatSync(fd);
|
|
9165
|
+
const current = fs3.lstatSync(path);
|
|
10404
9166
|
if (opened.dev !== stat.dev || opened.ino !== stat.ino || current.dev !== opened.dev || current.ino !== opened.ino) {
|
|
10405
9167
|
throw new Error(`${label} changed while opening`);
|
|
10406
9168
|
}
|
|
10407
|
-
text =
|
|
9169
|
+
text = fs3.readFileSync(fd, "utf8");
|
|
10408
9170
|
} finally {
|
|
10409
|
-
if (fd !== void 0)
|
|
9171
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
10410
9172
|
}
|
|
10411
9173
|
if (!/^[1-9][0-9]*\n$/.test(text)) throw new Error(`${label} contains an invalid PID`);
|
|
10412
9174
|
const pid = Number(text.trim());
|
|
@@ -10421,26 +9183,26 @@ function isProcessAlive(pid) {
|
|
|
10421
9183
|
return error.code === "EPERM";
|
|
10422
9184
|
}
|
|
10423
9185
|
}
|
|
10424
|
-
function writeAll(
|
|
9186
|
+
function writeAll(fs3, fd, bytes) {
|
|
10425
9187
|
let offset = 0;
|
|
10426
9188
|
while (offset < bytes.length) {
|
|
10427
|
-
const written =
|
|
9189
|
+
const written = fs3.writeSync(fd, bytes, offset, bytes.length - offset, null);
|
|
10428
9190
|
if (written <= 0) throw new Error("write made no progress");
|
|
10429
9191
|
offset += written;
|
|
10430
9192
|
}
|
|
10431
9193
|
}
|
|
10432
|
-
function fsyncDirectory2(
|
|
9194
|
+
function fsyncDirectory2(fs3, path) {
|
|
10433
9195
|
let fd;
|
|
10434
9196
|
try {
|
|
10435
|
-
fd =
|
|
10436
|
-
|
|
9197
|
+
fd = fs3.openSync(path, nodeFs5.constants.O_RDONLY | NO_FOLLOW4);
|
|
9198
|
+
fs3.fsyncSync(fd);
|
|
10437
9199
|
} finally {
|
|
10438
|
-
if (fd !== void 0)
|
|
9200
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
10439
9201
|
}
|
|
10440
9202
|
}
|
|
10441
|
-
function lstatIfPresent2(
|
|
9203
|
+
function lstatIfPresent2(fs3, path) {
|
|
10442
9204
|
try {
|
|
10443
|
-
return
|
|
9205
|
+
return fs3.lstatSync(path);
|
|
10444
9206
|
} catch (error) {
|
|
10445
9207
|
if (error.code === "ENOENT") return void 0;
|
|
10446
9208
|
throw error;
|
|
@@ -10451,18 +9213,18 @@ function requiresExitFrom(error) {
|
|
|
10451
9213
|
}
|
|
10452
9214
|
var FILE_MODE3, NO_FOLLOW4, DaemonBootCancelledError, DaemonShutdownError, CoworkDaemon;
|
|
10453
9215
|
var init_daemon_runtime = __esm({
|
|
10454
|
-
|
|
9216
|
+
"src/daemon-runtime.ts"() {
|
|
10455
9217
|
"use strict";
|
|
10456
|
-
await init_adapt();
|
|
10457
9218
|
init_config();
|
|
10458
9219
|
init_config();
|
|
10459
|
-
|
|
10460
|
-
|
|
9220
|
+
init_ours_runtime();
|
|
9221
|
+
init_packets();
|
|
9222
|
+
init_service();
|
|
10461
9223
|
init_storage();
|
|
10462
9224
|
init_transports();
|
|
10463
9225
|
init_web();
|
|
10464
9226
|
FILE_MODE3 = 384;
|
|
10465
|
-
NO_FOLLOW4 =
|
|
9227
|
+
NO_FOLLOW4 = nodeFs5.constants.O_NOFOLLOW ?? 0;
|
|
10466
9228
|
DaemonBootCancelledError = class extends Error {
|
|
10467
9229
|
constructor() {
|
|
10468
9230
|
super("cowork daemon boot cancelled by shutdown");
|
|
@@ -10514,7 +9276,7 @@ var init_daemon_runtime = __esm({
|
|
|
10514
9276
|
this.options.onStage?.("post-lock");
|
|
10515
9277
|
this.checkpoint();
|
|
10516
9278
|
try {
|
|
10517
|
-
this.host = this.options.host ??
|
|
9279
|
+
this.host = this.options.host ?? createOursHost(config, this.options.log);
|
|
10518
9280
|
this.store = this.options.store ?? new CoworkStore(config.stateDir);
|
|
10519
9281
|
let serviceRef = this.options.service;
|
|
10520
9282
|
this.registry = this.options.registry ?? new PacketRegistry(
|
|
@@ -10565,7 +9327,7 @@ var init_daemon_runtime = __esm({
|
|
|
10565
9327
|
const unixDispatcher = new RpcDispatcher(unixRoutes);
|
|
10566
9328
|
const restDispatcher = new RpcDispatcher(serviceRoutes);
|
|
10567
9329
|
const staticHandler = createStaticWebHandler(loadWebAssets(
|
|
10568
|
-
|
|
9330
|
+
fileURLToPath(new URL("./web/", import.meta.url))
|
|
10569
9331
|
));
|
|
10570
9332
|
this.transports = this.options.transports ?? new TransportServer({
|
|
10571
9333
|
socketPath: runtime.socketPath,
|
|
@@ -10808,7 +9570,7 @@ async function runWorker() {
|
|
|
10808
9570
|
await acknowledge();
|
|
10809
9571
|
return shutdownComplete;
|
|
10810
9572
|
}
|
|
10811
|
-
const runtime = await
|
|
9573
|
+
const runtime = await Promise.resolve().then(() => (init_daemon_runtime(), daemon_runtime_exports));
|
|
10812
9574
|
if (shutdownRequested) {
|
|
10813
9575
|
await acknowledge();
|
|
10814
9576
|
return shutdownComplete;
|
|
@@ -10903,13 +9665,13 @@ var init_daemon_process = __esm({
|
|
|
10903
9665
|
import { fork } from "node:child_process";
|
|
10904
9666
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
10905
9667
|
import { resolve as resolve3 } from "node:path";
|
|
10906
|
-
import { fileURLToPath as
|
|
9668
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10907
9669
|
async function runSupervisor(options = {}) {
|
|
10908
9670
|
const workerEnv = { ...process.env };
|
|
10909
9671
|
delete workerEnv.NODE_OPTIONS;
|
|
10910
9672
|
workerEnv.OURS_COWORK_DAEMON_WORKER = "1";
|
|
10911
9673
|
workerEnv.OURS_COWORK_SUPERVISOR_PID = String(process.pid);
|
|
10912
|
-
const child = fork(
|
|
9674
|
+
const child = fork(fileURLToPath2(import.meta.url), [], {
|
|
10913
9675
|
env: workerEnv,
|
|
10914
9676
|
stdio: options.quiet ? ["ignore", "ignore", "ignore", "ipc"] : ["inherit", "inherit", "inherit", "ipc"],
|
|
10915
9677
|
execArgv: [],
|
|
@@ -11085,7 +9847,7 @@ var init_daemon = __esm({
|
|
|
11085
9847
|
}
|
|
11086
9848
|
};
|
|
11087
9849
|
invokedPath = process.argv[1] ? resolve3(process.argv[1]) : void 0;
|
|
11088
|
-
if (invokedPath ===
|
|
9850
|
+
if (invokedPath === fileURLToPath2(import.meta.url)) {
|
|
11089
9851
|
void Promise.resolve().then(() => (init_daemon_process(), daemon_process_exports)).then(({ runDaemonProcess: runDaemonProcess2 }) => runDaemonProcess2()).catch((error) => {
|
|
11090
9852
|
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
11091
9853
|
process.exitCode = 1;
|