@ours.network/cowork 1.0.0 → 1.0.2
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 +3 -3
- package/dist/cli.js +232 -250
- package/dist/daemon.js +1107 -1120
- package/dist/web/assets/app.js +1 -1
- package/docs/01-prerequisites.md +1 -1
- package/docs/02-installation.md +1 -1
- package/docs/03-configuration.md +1 -4
- package/docs/05-room-workflow.md +2 -2
- package/docs/06-invites.md +13 -1
- package/docs/07-messaging-history.md +7 -0
- package/docs/08-backup-restore.md +2 -2
- package/docs/10-limitations.md +1 -1
- package/docs/11-web-console.md +2 -2
- package/package.json +3 -3
package/dist/daemon.js
CHANGED
|
@@ -4116,1181 +4116,1149 @@ var init_zod = __esm({
|
|
|
4116
4116
|
}
|
|
4117
4117
|
});
|
|
4118
4118
|
|
|
4119
|
-
// src/
|
|
4120
|
-
import
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
if (!match) return false;
|
|
4130
|
-
const [, yearText, monthText, dayText, hourText, minuteText, secondText, offsetHourText, offsetMinuteText] = match;
|
|
4131
|
-
const year = Number(yearText);
|
|
4132
|
-
const month = Number(monthText);
|
|
4133
|
-
const day = Number(dayText);
|
|
4134
|
-
const hour = Number(hourText);
|
|
4135
|
-
const minute = Number(minuteText);
|
|
4136
|
-
const second = Number(secondText);
|
|
4137
|
-
const offsetHour = offsetHourText === void 0 ? 0 : Number(offsetHourText);
|
|
4138
|
-
const offsetMinute = offsetMinuteText === void 0 ? 0 : Number(offsetMinuteText);
|
|
4139
|
-
if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59) return false;
|
|
4140
|
-
if (offsetHour > 23 || offsetMinute > 59) return false;
|
|
4141
|
-
const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
4142
|
-
const days = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
4143
|
-
return day >= 1 && day <= days[month - 1];
|
|
4119
|
+
// src/config.ts
|
|
4120
|
+
import * as nodeFs from "node:fs";
|
|
4121
|
+
import { homedir } from "node:os";
|
|
4122
|
+
import { dirname, isAbsolute, join, parse, resolve } from "node:path";
|
|
4123
|
+
function defaultConfig(home = homedir()) {
|
|
4124
|
+
return {
|
|
4125
|
+
version: 1,
|
|
4126
|
+
stateDir: resolve(home, ".ours-cowork"),
|
|
4127
|
+
rest: { enabled: true, port: 3052 }
|
|
4128
|
+
};
|
|
4144
4129
|
}
|
|
4145
|
-
function
|
|
4146
|
-
|
|
4130
|
+
function loadConfig(env = process.env, io = {}) {
|
|
4131
|
+
rejectRemovedEnvironment(env);
|
|
4132
|
+
const fs2 = io.fs ?? nodeFs;
|
|
4133
|
+
const defaults = defaultConfig(io.home);
|
|
4134
|
+
const configPath = resolve(env.OURS_COWORK_CONFIG ?? join(io.home ?? homedir(), ".ours-cowork", "config.json"));
|
|
4135
|
+
let file = defaults;
|
|
4136
|
+
const stat = lstatIfPresent(fs2, configPath);
|
|
4137
|
+
if (stat) {
|
|
4138
|
+
assertSecureFile(fs2, configPath, "config file");
|
|
4139
|
+
let parsed;
|
|
4140
|
+
try {
|
|
4141
|
+
parsed = JSON.parse(readSecureFile(fs2, configPath, "config file").toString("utf8"));
|
|
4142
|
+
} catch (error) {
|
|
4143
|
+
throw new CoworkConfigError(`malformed cowork config at ${configPath}`, { cause: error });
|
|
4144
|
+
}
|
|
4145
|
+
rejectRemovedConfig(parsed, configPath);
|
|
4146
|
+
try {
|
|
4147
|
+
file = CoworkConfigSchema.parse(parsed);
|
|
4148
|
+
} catch (error) {
|
|
4149
|
+
throw new CoworkConfigError(`invalid cowork config at ${configPath}`, { cause: error });
|
|
4150
|
+
}
|
|
4151
|
+
} else if (env.OURS_COWORK_CONFIG !== void 0) {
|
|
4152
|
+
throw new CoworkConfigError(`configured cowork config does not exist: ${configPath}`);
|
|
4153
|
+
}
|
|
4154
|
+
const restPort = env.OURS_COWORK_REST_PORT === void 0 ? void 0 : parsePort(env.OURS_COWORK_REST_PORT);
|
|
4155
|
+
try {
|
|
4156
|
+
return CoworkConfigSchema.parse({
|
|
4157
|
+
version: 1,
|
|
4158
|
+
stateDir: resolve(env.OURS_COWORK_STATE_DIR ?? file.stateDir),
|
|
4159
|
+
rest: {
|
|
4160
|
+
enabled: restPort === void 0 ? file.rest.enabled : true,
|
|
4161
|
+
port: restPort ?? file.rest.port
|
|
4162
|
+
}
|
|
4163
|
+
});
|
|
4164
|
+
} catch (error) {
|
|
4165
|
+
throw new CoworkConfigError("invalid effective cowork config", { cause: error });
|
|
4166
|
+
}
|
|
4147
4167
|
}
|
|
4148
|
-
function
|
|
4149
|
-
|
|
4168
|
+
function rejectRemovedEnvironment(env) {
|
|
4169
|
+
const removed = [
|
|
4170
|
+
"OURS_COWORK_BROKER_URL",
|
|
4171
|
+
"OURS_COWORK_DAEMON_MODE",
|
|
4172
|
+
"OURS_COWORK_DAEMON_ENDPOINT",
|
|
4173
|
+
"OURS_COWORK_DAEMON_STATE_DIR"
|
|
4174
|
+
].filter((name) => env[name] !== void 0);
|
|
4175
|
+
if (removed.length === 0) return;
|
|
4176
|
+
throw new CoworkConfigError(
|
|
4177
|
+
`${removed.join(", ")} ${removed.length === 1 ? "was" : "were"} removed: ours-cowork now attaches only to the shared ours daemon. Configure that daemon with @ours.network/cli and select it through the standard OURS_CONFIG, OURS_PORT, and OURS_STATE_DIR inputs.`
|
|
4178
|
+
);
|
|
4150
4179
|
}
|
|
4151
|
-
function
|
|
4152
|
-
|
|
4153
|
-
const
|
|
4154
|
-
|
|
4180
|
+
function rejectRemovedConfig(value, path) {
|
|
4181
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
4182
|
+
const removed = ["brokerUrl", "daemon"].filter((key) => Object.hasOwn(value, key));
|
|
4183
|
+
if (removed.length === 0) return;
|
|
4184
|
+
throw new CoworkConfigError(
|
|
4185
|
+
`cowork config at ${path} contains removed ${removed.join(" and ")} ${removed.length === 1 ? "key" : "keys"}: ours-cowork now attaches only to the shared ours daemon. Remove those keys and configure the daemon with @ours.network/cli.`
|
|
4186
|
+
);
|
|
4155
4187
|
}
|
|
4156
|
-
function
|
|
4157
|
-
const
|
|
4158
|
-
const
|
|
4159
|
-
|
|
4160
|
-
|
|
4188
|
+
function ensureRuntimeState(config, io = {}) {
|
|
4189
|
+
const parsed = CoworkConfigSchema.parse(config);
|
|
4190
|
+
const fs2 = io.fs ?? nodeFs;
|
|
4191
|
+
const stateDir = resolve(parsed.stateDir);
|
|
4192
|
+
assertSecureAncestors(fs2, stateDir, "state directory");
|
|
4193
|
+
const existing = lstatIfPresent(fs2, stateDir);
|
|
4194
|
+
if (!existing) {
|
|
4195
|
+
createSecureDirectoryTree(fs2, stateDir, "state directory");
|
|
4161
4196
|
}
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4197
|
+
assertSecureDirectory(fs2, stateDir, "state directory");
|
|
4198
|
+
const roomsPath = join(stateDir, "rooms");
|
|
4199
|
+
const rooms = lstatIfPresent(fs2, roomsPath);
|
|
4200
|
+
if (!rooms) {
|
|
4201
|
+
fs2.mkdirSync(roomsPath, { mode: DIRECTORY_MODE });
|
|
4202
|
+
secureOpenedDirectory(fs2, roomsPath, "rooms directory");
|
|
4203
|
+
fsyncDirectory(fs2, stateDir);
|
|
4204
|
+
}
|
|
4205
|
+
assertSecureDirectory(fs2, roomsPath, "rooms directory");
|
|
4206
|
+
return {
|
|
4207
|
+
socketPath: join(stateDir, "management.sock"),
|
|
4208
|
+
pidPath: join(stateDir, "daemon.pid"),
|
|
4209
|
+
lockPath: join(stateDir, "daemon.lock")
|
|
4210
|
+
};
|
|
4166
4211
|
}
|
|
4167
|
-
function
|
|
4168
|
-
|
|
4212
|
+
function parsePort(value) {
|
|
4213
|
+
if (!/^[1-9][0-9]{0,4}$/.test(value)) {
|
|
4214
|
+
throw new CoworkConfigError("OURS_COWORK_REST_PORT must be a decimal port from 1 to 65535");
|
|
4215
|
+
}
|
|
4216
|
+
const port = Number(value);
|
|
4217
|
+
if (port > 65535) throw new CoworkConfigError("OURS_COWORK_REST_PORT must be from 1 to 65535");
|
|
4218
|
+
return port;
|
|
4169
4219
|
}
|
|
4170
|
-
function
|
|
4171
|
-
const
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
const
|
|
4177
|
-
|
|
4220
|
+
function assertSecureAncestors(fs2, path, label) {
|
|
4221
|
+
const absolute = isAbsolute(path) ? path : resolve(path);
|
|
4222
|
+
const root = parse(absolute).root;
|
|
4223
|
+
const rootOwner = fs2.lstatSync(root).uid;
|
|
4224
|
+
let cursor = root;
|
|
4225
|
+
const components = absolute.slice(root.length).split("/").filter(Boolean);
|
|
4226
|
+
for (const [index, component] of components.entries()) {
|
|
4227
|
+
cursor = join(cursor, component);
|
|
4228
|
+
const stat = lstatIfPresent(fs2, cursor);
|
|
4229
|
+
if (stat?.isSymbolicLink()) throw new CoworkConfigError(`${label} must not traverse a symbolic link (symlink): ${cursor}`);
|
|
4230
|
+
if (!stat) break;
|
|
4231
|
+
if (!stat.isDirectory()) {
|
|
4232
|
+
if (index === components.length - 1) return;
|
|
4233
|
+
throw new CoworkConfigError(`${label} ancestor is not a directory: ${cursor}`);
|
|
4234
|
+
}
|
|
4235
|
+
if (index < components.length - 1) assertTrustedAncestor(stat, rootOwner, cursor, label);
|
|
4236
|
+
}
|
|
4178
4237
|
}
|
|
4179
|
-
function
|
|
4180
|
-
|
|
4238
|
+
function assertTrustedAncestor(stat, rootOwner, path, label) {
|
|
4239
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : stat.uid;
|
|
4240
|
+
const trustedStickyDirectory = (stat.mode & 512) !== 0;
|
|
4241
|
+
const writableByOthers = (stat.mode & 18) !== 0;
|
|
4242
|
+
const trustedOwner = stat.uid === uid || stat.uid === 0 || stat.uid === rootOwner;
|
|
4243
|
+
if (writableByOthers && (!trustedStickyDirectory || !trustedOwner)) {
|
|
4244
|
+
throw new CoworkConfigError(`${label} has an unsafe writable ancestor: ${path}`);
|
|
4245
|
+
}
|
|
4181
4246
|
}
|
|
4182
|
-
function
|
|
4183
|
-
const
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4247
|
+
function createSecureDirectoryTree(fs2, path, label) {
|
|
4248
|
+
const missing = [];
|
|
4249
|
+
let cursor = path;
|
|
4250
|
+
while (!lstatIfPresent(fs2, cursor)) {
|
|
4251
|
+
missing.push(cursor);
|
|
4252
|
+
const parent = dirname(cursor);
|
|
4253
|
+
if (parent === cursor) throw new CoworkConfigError(`cannot locate an existing ancestor for ${label}`);
|
|
4254
|
+
cursor = parent;
|
|
4190
4255
|
}
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
});
|
|
4256
|
+
assertSecureAncestors(fs2, path, label);
|
|
4257
|
+
for (const directory of missing.reverse()) {
|
|
4258
|
+
fs2.mkdirSync(directory, { mode: DIRECTORY_MODE });
|
|
4259
|
+
secureOpenedDirectory(fs2, directory, label);
|
|
4260
|
+
fsyncDirectory(fs2, dirname(directory));
|
|
4197
4261
|
}
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
const
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
path: ["invites", index, "recovery_of"],
|
|
4208
|
-
message: "recovery_of must point to a source invite in the state required by this recovery lineage"
|
|
4209
|
-
});
|
|
4210
|
-
} else if (invite.mode !== source.mode || invite.role !== source.role || invite.min_accepts !== source.min_accepts) {
|
|
4211
|
-
context.addIssue({
|
|
4212
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4213
|
-
path: ["invites", index],
|
|
4214
|
-
message: "receipt_pending descriptor must copy source mode, role, and min_accepts"
|
|
4215
|
-
});
|
|
4216
|
-
}
|
|
4217
|
-
if (invite.state === "receipt_pending") {
|
|
4218
|
-
const count = (pendingByRecovery.get(recoveryOf) ?? 0) + 1;
|
|
4219
|
-
pendingByRecovery.set(recoveryOf, count);
|
|
4220
|
-
if (count > 1) {
|
|
4221
|
-
context.addIssue({
|
|
4222
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4223
|
-
path: ["invites", index, "recovery_of"],
|
|
4224
|
-
message: "only one receipt_pending invite may exist per recovery_of pointer"
|
|
4225
|
-
});
|
|
4226
|
-
}
|
|
4262
|
+
}
|
|
4263
|
+
function secureOpenedDirectory(fs2, path, label) {
|
|
4264
|
+
let fd;
|
|
4265
|
+
try {
|
|
4266
|
+
fd = fs2.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
|
|
4267
|
+
const opened = fs2.fstatSync(fd);
|
|
4268
|
+
const current = fs2.lstatSync(path);
|
|
4269
|
+
if (!opened.isDirectory() || current.isSymbolicLink() || opened.dev !== current.dev || opened.ino !== current.ino) {
|
|
4270
|
+
throw new CoworkConfigError(`${label} changed while opening`);
|
|
4227
4271
|
}
|
|
4272
|
+
fs2.fchmodSync(fd, DIRECTORY_MODE);
|
|
4273
|
+
fs2.fsyncSync(fd);
|
|
4274
|
+
} finally {
|
|
4275
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
4228
4276
|
}
|
|
4229
4277
|
}
|
|
4230
|
-
function
|
|
4231
|
-
|
|
4232
|
-
}
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
version: 2,
|
|
4237
|
-
mission: { ...room.mission, briefing_version: 1 },
|
|
4238
|
-
role_briefings: {},
|
|
4239
|
-
rest_roles: [],
|
|
4240
|
-
anonymous: false,
|
|
4241
|
-
quiet_membership: false,
|
|
4242
|
-
membership_epoch: 0,
|
|
4243
|
-
seats: room.seats.map((seat) => ({
|
|
4244
|
-
...seat,
|
|
4245
|
-
participant_id: LowerCrockfordUlidSchema.parse(mintParticipantId()),
|
|
4246
|
-
state: "active"
|
|
4247
|
-
}))
|
|
4248
|
-
});
|
|
4249
|
-
}
|
|
4250
|
-
function refineRelaySubject(record, context) {
|
|
4251
|
-
if (record.kind !== "relay_intent" && record.kind !== "relay_result") return;
|
|
4252
|
-
if (record.message_id === void 0 === (record.file_id === void 0)) {
|
|
4253
|
-
context.addIssue({
|
|
4254
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4255
|
-
path: ["message_id"],
|
|
4256
|
-
message: "relay records require exactly one of message_id or file_id"
|
|
4257
|
-
});
|
|
4278
|
+
function assertSecureDirectory(fs2, path, label) {
|
|
4279
|
+
const stat = fs2.lstatSync(path);
|
|
4280
|
+
if (stat.isSymbolicLink()) throw new CoworkConfigError(`${label} must not be a symbolic link (symlink)`);
|
|
4281
|
+
if (!stat.isDirectory()) throw new CoworkConfigError(`${label} must be a directory`);
|
|
4282
|
+
if ((stat.mode & 511) !== DIRECTORY_MODE) {
|
|
4283
|
+
throw new CoworkConfigError(`${label} mode must be 0700`);
|
|
4258
4284
|
}
|
|
4285
|
+
assertOwner(stat, label);
|
|
4259
4286
|
}
|
|
4260
|
-
function
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
if (
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
if (digest !== record.sha256) {
|
|
4271
|
-
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["sha256"], message: "file sha256 must match decoded bytes" });
|
|
4287
|
+
function assertSecureFile(fs2, path, label) {
|
|
4288
|
+
const stat = fs2.lstatSync(path);
|
|
4289
|
+
if (stat.isSymbolicLink()) throw new CoworkConfigError(`${label} must not be a symbolic link (symlink)`);
|
|
4290
|
+
if (!stat.isFile() || stat.nlink !== 1) throw new CoworkConfigError(`${label} must be a single-link regular file`);
|
|
4291
|
+
if ((stat.mode & 511) !== FILE_MODE) throw new CoworkConfigError(`${label} mode must be 0600`);
|
|
4292
|
+
assertOwner(stat, label);
|
|
4293
|
+
}
|
|
4294
|
+
function assertOwner(stat, label) {
|
|
4295
|
+
if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
|
|
4296
|
+
throw new CoworkConfigError(`${label} must be owned by the current user`);
|
|
4272
4297
|
}
|
|
4273
4298
|
}
|
|
4274
|
-
function
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
});
|
|
4282
|
-
}
|
|
4283
|
-
if (!present && message[field] !== void 0) {
|
|
4284
|
-
context.addIssue({
|
|
4285
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4286
|
-
path: [field],
|
|
4287
|
-
message: `${field} is forbidden on ${message.category} messages`
|
|
4288
|
-
});
|
|
4299
|
+
function readSecureFile(fs2, path, label) {
|
|
4300
|
+
let fd;
|
|
4301
|
+
try {
|
|
4302
|
+
fd = fs2.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
|
|
4303
|
+
const opened = fs2.fstatSync(fd);
|
|
4304
|
+
const current = fs2.lstatSync(path);
|
|
4305
|
+
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) {
|
|
4306
|
+
throw new CoworkConfigError(`${label} changed while opening`);
|
|
4289
4307
|
}
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
if (message.category === "role_briefing" && message.briefing_version === void 0) {
|
|
4294
|
-
context.addIssue({
|
|
4295
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4296
|
-
path: ["briefing_version"],
|
|
4297
|
-
message: "role_briefing messages require briefing_version"
|
|
4298
|
-
});
|
|
4308
|
+
return fs2.readFileSync(fd);
|
|
4309
|
+
} finally {
|
|
4310
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
4299
4311
|
}
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4312
|
+
}
|
|
4313
|
+
function fsyncDirectory(fs2, path) {
|
|
4314
|
+
let fd;
|
|
4315
|
+
try {
|
|
4316
|
+
fd = fs2.openSync(path, nodeFs.constants.O_RDONLY | NO_FOLLOW);
|
|
4317
|
+
fs2.fsyncSync(fd);
|
|
4318
|
+
} finally {
|
|
4319
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
4308
4320
|
}
|
|
4309
4321
|
}
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4322
|
+
function lstatIfPresent(fs2, path) {
|
|
4323
|
+
try {
|
|
4324
|
+
return fs2.lstatSync(path);
|
|
4325
|
+
} catch (error) {
|
|
4326
|
+
if (error.code === "ENOENT") return void 0;
|
|
4327
|
+
throw error;
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
var DIRECTORY_MODE, FILE_MODE, NO_FOLLOW, CoworkConfigSchema, CoworkConfigError;
|
|
4331
|
+
var init_config = __esm({
|
|
4332
|
+
"src/config.ts"() {
|
|
4313
4333
|
"use strict";
|
|
4314
4334
|
init_zod();
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
MAX_FRIENDLY_IDENTITY_SLUG_CHARACTERS = 25;
|
|
4326
|
-
NonEmptyStringSchema = external_exports.string().min(1);
|
|
4327
|
-
PositiveSafeIntegerSchema = external_exports.number().int().positive().safe();
|
|
4328
|
-
LowerCrockfordUlidSchema = external_exports.string().regex(
|
|
4329
|
-
/^[0-7][0-9a-hjkmnp-tv-z]{25}$/,
|
|
4330
|
-
"must be a 26-character lowercase Crockford ULID"
|
|
4331
|
-
);
|
|
4332
|
-
Rfc3339Schema = external_exports.string().refine(isStrictRfc3339, "must be a valid RFC3339 timestamp");
|
|
4333
|
-
RoomNameSchema = external_exports.string().refine(
|
|
4334
|
-
(value) => !/[\p{Cc}\p{Cf}]/u.test(value),
|
|
4335
|
-
"room name must not contain Unicode control or format characters"
|
|
4336
|
-
).transform(normalizeRoomName).superRefine((value, context) => {
|
|
4337
|
-
const length = Array.from(value).length;
|
|
4338
|
-
if (length < 1 || length > MAX_ROOM_NAME_CHARACTERS) {
|
|
4339
|
-
context.addIssue({
|
|
4340
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4341
|
-
message: `room name must contain 1-${MAX_ROOM_NAME_CHARACTERS} Unicode characters after normalization`
|
|
4342
|
-
});
|
|
4343
|
-
}
|
|
4344
|
-
});
|
|
4345
|
-
RoomIdentityNameModeSchema = external_exports.enum(["stable_id", "friendly"]);
|
|
4346
|
-
ROOM_IDENTITY_PREFIX = "ours-cowork-";
|
|
4347
|
-
SDK_IDENTITY_NAME_PATTERN = /^[A-Za-z0-9 _.@-]{1,64}$/;
|
|
4348
|
-
FRIENDLY_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4349
|
-
RoleSchema = utf8Bounded("role", MAX_ROLE_BYTES);
|
|
4350
|
-
ROOM_ROLE = "room";
|
|
4351
|
-
MissionTextSchema = utf8Bounded("mission text", MAX_TEXT_BYTES);
|
|
4352
|
-
MessageTextSchema = utf8Bounded("message text", MAX_TEXT_BYTES);
|
|
4353
|
-
FileNameSchema = utf8Bounded("file name", MAX_FILE_NAME_BYTES).refine((value) => value !== "." && value !== "..", "file name must not be a relative path token").refine((value) => !/[\x00/\\]/.test(value), "file name must be a single path-free name");
|
|
4354
|
-
FileMimeSchema = external_exports.string().refine(
|
|
4355
|
-
(value) => Buffer.byteLength(value, "utf8") <= MAX_MIME_BYTES,
|
|
4356
|
-
`file MIME metadata must be at most ${MAX_MIME_BYTES} UTF-8 bytes`
|
|
4357
|
-
);
|
|
4358
|
-
RoomStateSchema = external_exports.enum(["provisioning", "active", "closing", "closed"]);
|
|
4359
|
-
SeatStateSchema = external_exports.enum(["pending", "active", "removed"]);
|
|
4360
|
-
InviteModeSchema = external_exports.enum(["one_time", "public"]);
|
|
4361
|
-
DEFAULT_ROLE = "Participant";
|
|
4362
|
-
InviteStateSchema = external_exports.enum([
|
|
4363
|
-
"live",
|
|
4364
|
-
"consumed",
|
|
4365
|
-
"revoked",
|
|
4366
|
-
"replacement_required",
|
|
4367
|
-
"receipt_pending"
|
|
4368
|
-
]);
|
|
4369
|
-
RelayStatusSchema = external_exports.enum(["queued", "send_failed"]);
|
|
4370
|
-
SeatV1Schema = external_exports.object({
|
|
4371
|
-
identity: NonEmptyStringSchema,
|
|
4372
|
-
display_name: NonEmptyStringSchema,
|
|
4373
|
-
role: RoleSchema,
|
|
4374
|
-
invite_id: NonEmptyStringSchema,
|
|
4375
|
-
accepted_at: Rfc3339Schema
|
|
4335
|
+
DIRECTORY_MODE = 448;
|
|
4336
|
+
FILE_MODE = 384;
|
|
4337
|
+
NO_FOLLOW = nodeFs.constants.O_NOFOLLOW ?? 0;
|
|
4338
|
+
CoworkConfigSchema = external_exports.object({
|
|
4339
|
+
version: external_exports.literal(1),
|
|
4340
|
+
stateDir: external_exports.string().min(1),
|
|
4341
|
+
rest: external_exports.object({
|
|
4342
|
+
enabled: external_exports.boolean(),
|
|
4343
|
+
port: external_exports.number().int().min(1).max(65535)
|
|
4344
|
+
}).strict()
|
|
4376
4345
|
}).strict();
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
invite_id: NonEmptyStringSchema,
|
|
4382
|
-
accepted_at: Rfc3339Schema.optional(),
|
|
4383
|
-
requested_at: Rfc3339Schema.optional(),
|
|
4384
|
-
invite_sha256: external_exports.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
4385
|
-
participant_id: LowerCrockfordUlidSchema,
|
|
4386
|
-
state: SeatStateSchema,
|
|
4387
|
-
alias: NonEmptyStringSchema.optional(),
|
|
4388
|
-
removed_at: Rfc3339Schema.optional(),
|
|
4389
|
-
removed_epoch: external_exports.number().int().nonnegative().safe().optional(),
|
|
4390
|
-
replaces_seat: LowerCrockfordUlidSchema.optional(),
|
|
4391
|
-
bounced_at: Rfc3339Schema.optional()
|
|
4392
|
-
}).strict().superRefine((seat, context) => {
|
|
4393
|
-
if (seat.state === "pending") {
|
|
4394
|
-
for (const field of ["requested_at", "invite_sha256"]) {
|
|
4395
|
-
if (seat[field] === void 0) {
|
|
4396
|
-
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: [field], message: `pending seats require ${field}` });
|
|
4397
|
-
}
|
|
4398
|
-
}
|
|
4399
|
-
for (const field of ["accepted_at", "removed_at", "removed_epoch", "bounced_at"]) {
|
|
4400
|
-
if (seat[field] !== void 0) {
|
|
4401
|
-
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: [field], message: `${field} is forbidden on pending seats` });
|
|
4402
|
-
}
|
|
4403
|
-
}
|
|
4404
|
-
} else if (seat.state === "removed") {
|
|
4405
|
-
if (seat.accepted_at === void 0 && seat.requested_at === void 0) {
|
|
4406
|
-
context.addIssue({
|
|
4407
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4408
|
-
path: ["accepted_at"],
|
|
4409
|
-
message: "removed seats require accepted_at unless they are cancelled external admissions"
|
|
4410
|
-
});
|
|
4411
|
-
}
|
|
4412
|
-
if (seat.removed_at === void 0) {
|
|
4413
|
-
context.addIssue({
|
|
4414
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4415
|
-
path: ["removed_at"],
|
|
4416
|
-
message: "removed seats require removed_at"
|
|
4417
|
-
});
|
|
4418
|
-
}
|
|
4419
|
-
if (seat.removed_epoch === void 0) {
|
|
4420
|
-
context.addIssue({
|
|
4421
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4422
|
-
path: ["removed_epoch"],
|
|
4423
|
-
message: "removed seats require removed_epoch"
|
|
4424
|
-
});
|
|
4425
|
-
}
|
|
4426
|
-
} else {
|
|
4427
|
-
if (seat.accepted_at === void 0) {
|
|
4428
|
-
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["accepted_at"], message: "active seats require accepted_at" });
|
|
4429
|
-
}
|
|
4430
|
-
for (const field of ["removed_at", "removed_epoch", "bounced_at"]) {
|
|
4431
|
-
if (seat[field] !== void 0) {
|
|
4432
|
-
context.addIssue({
|
|
4433
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4434
|
-
path: [field],
|
|
4435
|
-
message: `${field} is reserved for removed seats`
|
|
4436
|
-
});
|
|
4437
|
-
}
|
|
4438
|
-
}
|
|
4439
|
-
}
|
|
4440
|
-
if (seat.requested_at === void 0 !== (seat.invite_sha256 === void 0)) {
|
|
4441
|
-
context.addIssue({
|
|
4442
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4443
|
-
path: ["invite_sha256"],
|
|
4444
|
-
message: "external admission metadata requires both requested_at and invite_sha256"
|
|
4445
|
-
});
|
|
4446
|
-
}
|
|
4447
|
-
});
|
|
4448
|
-
RoomInviteSchema = external_exports.object({
|
|
4449
|
-
invite_id: NonEmptyStringSchema,
|
|
4450
|
-
mode: InviteModeSchema,
|
|
4451
|
-
role: RoleSchema,
|
|
4452
|
-
min_accepts: PositiveSafeIntegerSchema,
|
|
4453
|
-
accepted_cids: external_exports.array(NonEmptyStringSchema),
|
|
4454
|
-
state: InviteStateSchema,
|
|
4455
|
-
recovery_of: NonEmptyStringSchema.optional(),
|
|
4456
|
-
recovery_confirmed: external_exports.boolean().optional(),
|
|
4457
|
-
created_at: Rfc3339Schema,
|
|
4458
|
-
replaces_seat: LowerCrockfordUlidSchema.optional()
|
|
4459
|
-
}).strict().superRefine((invite, context) => {
|
|
4460
|
-
if (invite.mode === "one_time" && invite.min_accepts !== 1) {
|
|
4461
|
-
context.addIssue({
|
|
4462
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4463
|
-
path: ["min_accepts"],
|
|
4464
|
-
message: "one_time invites require min_accepts === 1"
|
|
4465
|
-
});
|
|
4466
|
-
}
|
|
4467
|
-
if (invite.state === "receipt_pending" && invite.recovery_of === void 0) {
|
|
4468
|
-
context.addIssue({
|
|
4469
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4470
|
-
path: ["recovery_of"],
|
|
4471
|
-
message: "receipt_pending invites require recovery_of"
|
|
4472
|
-
});
|
|
4473
|
-
}
|
|
4474
|
-
if (invite.recovery_of === void 0 && invite.recovery_confirmed !== void 0) {
|
|
4475
|
-
context.addIssue({
|
|
4476
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4477
|
-
path: ["recovery_confirmed"],
|
|
4478
|
-
message: "recovery_confirmed is forbidden without recovery_of"
|
|
4479
|
-
});
|
|
4346
|
+
CoworkConfigError = class extends Error {
|
|
4347
|
+
constructor(message, options) {
|
|
4348
|
+
super(message, options);
|
|
4349
|
+
this.name = "CoworkConfigError";
|
|
4480
4350
|
}
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4351
|
+
};
|
|
4352
|
+
}
|
|
4353
|
+
});
|
|
4354
|
+
|
|
4355
|
+
// src/ours-runtime.ts
|
|
4356
|
+
import { randomBytes } from "node:crypto";
|
|
4357
|
+
function createOursHost(_config, log = () => {
|
|
4358
|
+
}) {
|
|
4359
|
+
return new SharedOursHost(log);
|
|
4360
|
+
}
|
|
4361
|
+
function sleep(ms, signal) {
|
|
4362
|
+
return new Promise((resolveSleep) => {
|
|
4363
|
+
const timer = setTimeout(finish, ms);
|
|
4364
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
4365
|
+
function finish() {
|
|
4366
|
+
clearTimeout(timer);
|
|
4367
|
+
signal.removeEventListener("abort", finish);
|
|
4368
|
+
resolveSleep();
|
|
4369
|
+
}
|
|
4370
|
+
});
|
|
4371
|
+
}
|
|
4372
|
+
var WATCH_RETRY_MIN_MS, WATCH_RETRY_MAX_MS, STATE_RESYNC_INTERVAL_MS, attachSharedClient, SharedOursHost;
|
|
4373
|
+
var init_ours_runtime = __esm({
|
|
4374
|
+
"src/ours-runtime.ts"() {
|
|
4375
|
+
"use strict";
|
|
4376
|
+
WATCH_RETRY_MIN_MS = 500;
|
|
4377
|
+
WATCH_RETRY_MAX_MS = 3e4;
|
|
4378
|
+
STATE_RESYNC_INTERVAL_MS = 2e3;
|
|
4379
|
+
attachSharedClient = async (options) => {
|
|
4380
|
+
const { attachOursClient } = await import("@ours.network/sdk");
|
|
4381
|
+
return attachOursClient(options);
|
|
4382
|
+
};
|
|
4383
|
+
SharedOursHost = class {
|
|
4384
|
+
log;
|
|
4385
|
+
attach;
|
|
4386
|
+
listeners = /* @__PURE__ */ new Set();
|
|
4387
|
+
watchers = /* @__PURE__ */ new Map();
|
|
4388
|
+
watchLeaseToken = `cowork-watch-${randomBytes(16).toString("hex")}`;
|
|
4389
|
+
watchClient;
|
|
4390
|
+
resyncTimer;
|
|
4391
|
+
closed = false;
|
|
4392
|
+
constructor(log = () => {
|
|
4393
|
+
}, attach = attachSharedClient) {
|
|
4394
|
+
this.log = log;
|
|
4395
|
+
this.attach = attach;
|
|
4487
4396
|
}
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
message: "receipt_pending recovery lineage must be unconfirmed"
|
|
4493
|
-
});
|
|
4397
|
+
async boot() {
|
|
4398
|
+
if (this.watchClient) return;
|
|
4399
|
+
if (this.closed) throw new Error("shared ours daemon host cannot restart in the same process");
|
|
4400
|
+
this.watchClient = await this.attach({ leaseToken: this.watchLeaseToken });
|
|
4494
4401
|
}
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
path: ["recovery_confirmed"],
|
|
4499
|
-
message: "live, consumed, and replacement_required recovery lineage must be confirmed"
|
|
4500
|
-
});
|
|
4402
|
+
async createClient(leaseToken = `cowork-${randomBytes(16).toString("hex")}`) {
|
|
4403
|
+
if (!this.watchClient) throw new Error("shared ours daemon host is not booted");
|
|
4404
|
+
return this.attach({ leaseToken });
|
|
4501
4405
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4406
|
+
async listIdentityNames(localNames) {
|
|
4407
|
+
if (!this.watchClient) throw new Error("shared ours daemon host is not booted");
|
|
4408
|
+
const rows = await this.watchClient.identities();
|
|
4409
|
+
return new Set(rows.flatMap((row) => localNames.has(row.name) ? [row.name] : []));
|
|
4410
|
+
}
|
|
4411
|
+
onIdentityNotify(listener) {
|
|
4412
|
+
this.listeners.add(listener);
|
|
4413
|
+
return () => this.listeners.delete(listener);
|
|
4414
|
+
}
|
|
4415
|
+
trackIdentity(identityName) {
|
|
4416
|
+
if (!this.watchClient) throw new Error("shared ours daemon host is not booted");
|
|
4417
|
+
const existing = this.watchers.get(identityName);
|
|
4418
|
+
if (existing) return () => this.untrack(identityName, existing);
|
|
4419
|
+
const controller = new AbortController();
|
|
4420
|
+
const watcher = { controller, work: Promise.resolve() };
|
|
4421
|
+
watcher.work = this.follow(identityName, controller.signal);
|
|
4422
|
+
this.watchers.set(identityName, watcher);
|
|
4423
|
+
this.announce(identityName);
|
|
4424
|
+
this.ensureStateResync();
|
|
4425
|
+
return () => this.untrack(identityName, watcher);
|
|
4426
|
+
}
|
|
4427
|
+
close() {
|
|
4428
|
+
}
|
|
4429
|
+
async shutdown() {
|
|
4430
|
+
if (this.closed) return { requiresProcessExit: false };
|
|
4431
|
+
this.closed = true;
|
|
4432
|
+
this.listeners.clear();
|
|
4433
|
+
this.stopStateResync();
|
|
4434
|
+
const watchers = [...this.watchers.values()];
|
|
4435
|
+
this.watchers.clear();
|
|
4436
|
+
for (const watcher of watchers) watcher.controller.abort();
|
|
4437
|
+
await Promise.allSettled(watchers.map((watcher) => watcher.work));
|
|
4438
|
+
const client = this.watchClient;
|
|
4439
|
+
this.watchClient = void 0;
|
|
4440
|
+
if (client) await client.releaseLease();
|
|
4441
|
+
return { requiresProcessExit: false };
|
|
4442
|
+
}
|
|
4443
|
+
untrack(identityName, watcher) {
|
|
4444
|
+
if (this.watchers.get(identityName) !== watcher) return;
|
|
4445
|
+
this.watchers.delete(identityName);
|
|
4446
|
+
watcher.controller.abort();
|
|
4447
|
+
if (this.watchers.size === 0) this.stopStateResync();
|
|
4448
|
+
}
|
|
4449
|
+
ensureStateResync() {
|
|
4450
|
+
if (this.resyncTimer) return;
|
|
4451
|
+
this.resyncTimer = setInterval(() => {
|
|
4452
|
+
for (const identityName of this.watchers.keys()) this.announce(identityName);
|
|
4453
|
+
}, STATE_RESYNC_INTERVAL_MS);
|
|
4454
|
+
this.resyncTimer.unref();
|
|
4455
|
+
}
|
|
4456
|
+
stopStateResync() {
|
|
4457
|
+
if (!this.resyncTimer) return;
|
|
4458
|
+
clearInterval(this.resyncTimer);
|
|
4459
|
+
this.resyncTimer = void 0;
|
|
4508
4460
|
}
|
|
4509
|
-
});
|
|
4510
|
-
MissionV1Schema = external_exports.object({
|
|
4511
|
-
goal: MissionTextSchema,
|
|
4512
|
-
briefing: MissionTextSchema
|
|
4513
|
-
}).strict();
|
|
4514
|
-
MissionSchema = external_exports.object({
|
|
4515
|
-
goal: MissionTextSchema,
|
|
4516
|
-
briefing: MissionTextSchema,
|
|
4517
|
-
briefing_version: PositiveSafeIntegerSchema
|
|
4518
|
-
}).strict();
|
|
4519
|
-
RoleBriefingSchema = external_exports.object({
|
|
4520
|
-
text: MissionTextSchema,
|
|
4521
|
-
version: PositiveSafeIntegerSchema,
|
|
4522
|
-
updated_at: Rfc3339Schema
|
|
4523
|
-
}).strict();
|
|
4524
|
-
RoomCommonShape = {
|
|
4525
|
-
room_id: LowerCrockfordUlidSchema,
|
|
4526
|
-
identity_name: NonEmptyStringSchema,
|
|
4527
|
-
identity_cid: external_exports.string(),
|
|
4528
|
-
state: RoomStateSchema,
|
|
4529
|
-
status: NonEmptyStringSchema.optional(),
|
|
4530
|
-
invites: external_exports.array(RoomInviteSchema),
|
|
4531
|
-
created_at: Rfc3339Schema,
|
|
4532
|
-
activated_at: Rfc3339Schema.optional(),
|
|
4533
|
-
closed_at: Rfc3339Schema.optional()
|
|
4534
|
-
};
|
|
4535
|
-
RoomV1Schema = external_exports.object({
|
|
4536
|
-
...RoomCommonShape,
|
|
4537
|
-
version: external_exports.literal(1),
|
|
4538
|
-
mission: MissionV1Schema,
|
|
4539
|
-
seats: external_exports.array(SeatV1Schema)
|
|
4540
|
-
}).strict().superRefine(refineRoomLineage);
|
|
4541
|
-
CurrentRoomSchema = external_exports.object({
|
|
4542
|
-
...RoomCommonShape,
|
|
4543
|
-
room_name: RoomNameSchema,
|
|
4544
|
-
version: external_exports.literal(2),
|
|
4545
|
-
mission: MissionSchema,
|
|
4546
|
-
role_briefings: external_exports.record(RoleSchema, RoleBriefingSchema),
|
|
4547
4461
|
/**
|
|
4548
|
-
*
|
|
4549
|
-
*
|
|
4550
|
-
*
|
|
4551
|
-
*
|
|
4552
|
-
*
|
|
4462
|
+
* Long-poll one identity forever, reconnecting with bounded backoff.
|
|
4463
|
+
*
|
|
4464
|
+
* A replacement stream primes at the daemon tip, so each reconnection also
|
|
4465
|
+
* requests a full state resync. The periodic resync above covers the small
|
|
4466
|
+
* request-prime race and daemon transitions that are not in the structured
|
|
4467
|
+
* notification log; reconciliation is idempotent and self-coalescing.
|
|
4553
4468
|
*/
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
const byParticipant = /* @__PURE__ */ new Map();
|
|
4581
|
-
const activeAliases = /* @__PURE__ */ new Set();
|
|
4582
|
-
const authorizedCids = /* @__PURE__ */ new Set();
|
|
4583
|
-
for (const [index, seat] of room.seats.entries()) {
|
|
4584
|
-
if (byParticipant.has(seat.participant_id)) {
|
|
4585
|
-
context.addIssue({
|
|
4586
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4587
|
-
path: ["seats", index, "participant_id"],
|
|
4588
|
-
message: "participant_id must be unique within the room"
|
|
4589
|
-
});
|
|
4590
|
-
}
|
|
4591
|
-
byParticipant.set(seat.participant_id, seat);
|
|
4592
|
-
if (seat.state === "pending" || seat.state === "active") {
|
|
4593
|
-
if (authorizedCids.has(seat.identity)) {
|
|
4594
|
-
context.addIssue({
|
|
4595
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4596
|
-
path: ["seats", index, "identity"],
|
|
4597
|
-
message: "at most one pending or active seat may exist per CID"
|
|
4598
|
-
});
|
|
4469
|
+
async follow(identityName, signal) {
|
|
4470
|
+
let backoffMs = WATCH_RETRY_MIN_MS;
|
|
4471
|
+
let resyncPending = false;
|
|
4472
|
+
while (!signal.aborted) {
|
|
4473
|
+
const client = this.watchClient;
|
|
4474
|
+
if (!client) return;
|
|
4475
|
+
try {
|
|
4476
|
+
const stream = client.watchNotifications(identityName, { signal });
|
|
4477
|
+
let step = stream.next();
|
|
4478
|
+
if (resyncPending) {
|
|
4479
|
+
resyncPending = false;
|
|
4480
|
+
this.announce(identityName);
|
|
4481
|
+
}
|
|
4482
|
+
for (let settled = await step; !settled.done; settled = await step) {
|
|
4483
|
+
backoffMs = WATCH_RETRY_MIN_MS;
|
|
4484
|
+
this.announce(identityName);
|
|
4485
|
+
step = stream.next();
|
|
4486
|
+
}
|
|
4487
|
+
if (!signal.aborted) {
|
|
4488
|
+
resyncPending = true;
|
|
4489
|
+
this.log(`[${identityName}] shared ours daemon notification watch ended; reconnecting`);
|
|
4490
|
+
}
|
|
4491
|
+
} catch (error) {
|
|
4492
|
+
if (signal.aborted) return;
|
|
4493
|
+
resyncPending = true;
|
|
4494
|
+
this.log(`[${identityName}] shared ours daemon notification watch failed:`, error);
|
|
4599
4495
|
}
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
context.addIssue({
|
|
4604
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4605
|
-
path: ["seats", index, "alias"],
|
|
4606
|
-
message: "anonymous rooms require an alias on every seat"
|
|
4607
|
-
});
|
|
4608
|
-
}
|
|
4609
|
-
if (!room.anonymous && seat.alias !== void 0) {
|
|
4610
|
-
context.addIssue({
|
|
4611
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4612
|
-
path: ["seats", index, "alias"],
|
|
4613
|
-
message: "aliases are reserved for anonymous rooms"
|
|
4614
|
-
});
|
|
4615
|
-
}
|
|
4616
|
-
if (seat.state === "active" && seat.alias !== void 0) {
|
|
4617
|
-
if (activeAliases.has(seat.alias)) {
|
|
4618
|
-
context.addIssue({
|
|
4619
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4620
|
-
path: ["seats", index, "alias"],
|
|
4621
|
-
message: "active seats must hold distinct aliases"
|
|
4622
|
-
});
|
|
4623
|
-
}
|
|
4624
|
-
activeAliases.add(seat.alias);
|
|
4625
|
-
}
|
|
4626
|
-
if (seat.removed_epoch !== void 0 && seat.removed_epoch > room.membership_epoch) {
|
|
4627
|
-
context.addIssue({
|
|
4628
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4629
|
-
path: ["seats", index, "removed_epoch"],
|
|
4630
|
-
message: "removed_epoch cannot exceed the room membership_epoch"
|
|
4631
|
-
});
|
|
4632
|
-
}
|
|
4633
|
-
}
|
|
4634
|
-
for (const [index, seat] of room.seats.entries()) {
|
|
4635
|
-
if (seat.replaces_seat === void 0) continue;
|
|
4636
|
-
const predecessor = byParticipant.get(seat.replaces_seat);
|
|
4637
|
-
if (!predecessor || predecessor === seat || predecessor.state !== "removed") {
|
|
4638
|
-
context.addIssue({
|
|
4639
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4640
|
-
path: ["seats", index, "replaces_seat"],
|
|
4641
|
-
message: "replaces_seat must reference a removed seat in this room"
|
|
4642
|
-
});
|
|
4643
|
-
continue;
|
|
4644
|
-
}
|
|
4645
|
-
if (predecessor.role !== seat.role) {
|
|
4646
|
-
context.addIssue({
|
|
4647
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4648
|
-
path: ["seats", index, "role"],
|
|
4649
|
-
message: "a replacement seat must inherit the predecessor role"
|
|
4650
|
-
});
|
|
4651
|
-
}
|
|
4652
|
-
if (room.anonymous && seat.alias !== predecessor.alias) {
|
|
4653
|
-
context.addIssue({
|
|
4654
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4655
|
-
path: ["seats", index, "alias"],
|
|
4656
|
-
message: "an anonymous replacement seat must inherit the predecessor alias"
|
|
4657
|
-
});
|
|
4658
|
-
}
|
|
4659
|
-
}
|
|
4660
|
-
});
|
|
4661
|
-
RoomSchema = external_exports.preprocess((value) => {
|
|
4662
|
-
if (typeof value !== "object" || value === null) return value;
|
|
4663
|
-
const patch = {};
|
|
4664
|
-
if (!Object.hasOwn(value, "room_name")) {
|
|
4665
|
-
const roomId = value.room_id;
|
|
4666
|
-
if (typeof roomId === "string" && LowerCrockfordUlidSchema.safeParse(roomId).success) {
|
|
4667
|
-
patch.room_name = defaultRoomName(roomId);
|
|
4496
|
+
if (signal.aborted) return;
|
|
4497
|
+
await sleep(backoffMs, signal);
|
|
4498
|
+
backoffMs = Math.min(backoffMs * 2, WATCH_RETRY_MAX_MS);
|
|
4668
4499
|
}
|
|
4669
4500
|
}
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
anonymous: external_exports.boolean().optional(),
|
|
4679
|
-
quiet_membership: external_exports.boolean().optional()
|
|
4680
|
-
}).strict();
|
|
4681
|
-
UpdateRoomInputSchema = external_exports.object({
|
|
4682
|
-
name: RoomNameSchema.optional(),
|
|
4683
|
-
goal: MissionTextSchema.optional(),
|
|
4684
|
-
briefing: MissionTextSchema.optional(),
|
|
4685
|
-
status: NonEmptyStringSchema.optional(),
|
|
4686
|
-
quiet_membership: external_exports.boolean().optional()
|
|
4687
|
-
}).strict().refine((input) => Object.keys(input).length > 0, "at least one setting is required");
|
|
4688
|
-
RoleBriefingSetInputSchema = external_exports.object({
|
|
4689
|
-
role: RoleSchema,
|
|
4690
|
-
text: MissionTextSchema
|
|
4691
|
-
}).strict();
|
|
4692
|
-
RoleBriefingDeleteInputSchema = external_exports.object({
|
|
4693
|
-
role: RoleSchema
|
|
4694
|
-
}).strict();
|
|
4695
|
-
PostMessageInputSchema = external_exports.object({
|
|
4696
|
-
text: MessageTextSchema
|
|
4697
|
-
}).strict();
|
|
4698
|
-
PostAsRoleInputSchema = external_exports.object({
|
|
4699
|
-
role: RoleSchema,
|
|
4700
|
-
text: MessageTextSchema
|
|
4701
|
-
}).strict();
|
|
4702
|
-
RestRoleInputSchema = external_exports.object({
|
|
4703
|
-
role: RoleSchema
|
|
4704
|
-
}).strict();
|
|
4705
|
-
ContainerIdSchema = external_exports.string().regex(/^[0-9a-f]{64}$/i, "must be a 64-character hexadecimal CID").transform((value) => value.toUpperCase());
|
|
4706
|
-
AcceptExternalInviteInputSchema = external_exports.object({
|
|
4707
|
-
role: RoleSchema,
|
|
4708
|
-
invite: external_exports.string().refine(
|
|
4709
|
-
(value) => Buffer.byteLength(value, "utf8") <= MAX_EXTERNAL_INVITE_BYTES,
|
|
4710
|
-
`invite input must be at most ${MAX_EXTERNAL_INVITE_BYTES} UTF-8 bytes`
|
|
4711
|
-
),
|
|
4712
|
-
expected_cid: ContainerIdSchema.optional(),
|
|
4713
|
-
replaces_seat: LowerCrockfordUlidSchema.optional()
|
|
4714
|
-
}).strict();
|
|
4715
|
-
AuthorSnapshotSchema = external_exports.object({
|
|
4716
|
-
identity: NonEmptyStringSchema,
|
|
4717
|
-
display_name: NonEmptyStringSchema,
|
|
4718
|
-
role: RoleSchema
|
|
4719
|
-
}).strict();
|
|
4720
|
-
RecordCommonShape = {
|
|
4721
|
-
version: external_exports.literal(1),
|
|
4722
|
-
room_id: LowerCrockfordUlidSchema,
|
|
4723
|
-
seq: PositiveSafeIntegerSchema,
|
|
4724
|
-
record_id: NonEmptyStringSchema,
|
|
4725
|
-
at: Rfc3339Schema
|
|
4726
|
-
};
|
|
4727
|
-
AppendCommonShape = {
|
|
4728
|
-
version: external_exports.literal(1),
|
|
4729
|
-
room_id: LowerCrockfordUlidSchema,
|
|
4730
|
-
at: Rfc3339Schema
|
|
4731
|
-
};
|
|
4732
|
-
MembershipNoticeSchema = external_exports.object({
|
|
4733
|
-
action: external_exports.enum(["remove"]),
|
|
4734
|
-
alias: NonEmptyStringSchema.optional(),
|
|
4735
|
-
role: RoleSchema.optional(),
|
|
4736
|
-
epoch: external_exports.number().int().nonnegative().safe()
|
|
4737
|
-
}).strict();
|
|
4738
|
-
AuthorAliasSchema = external_exports.object({
|
|
4739
|
-
participant_id: LowerCrockfordUlidSchema,
|
|
4740
|
-
alias: NonEmptyStringSchema
|
|
4741
|
-
}).strict();
|
|
4742
|
-
ReplyReferenceSchema = external_exports.object({
|
|
4743
|
-
wire_id: NonEmptyStringSchema,
|
|
4744
|
-
sentence: PositiveSafeIntegerSchema.optional()
|
|
4745
|
-
}).strict();
|
|
4746
|
-
MessageShape = {
|
|
4747
|
-
kind: external_exports.literal("message"),
|
|
4748
|
-
message_id: LowerCrockfordUlidSchema,
|
|
4749
|
-
author: AuthorSnapshotSchema,
|
|
4750
|
-
author_alias: AuthorAliasSchema.optional(),
|
|
4751
|
-
category: external_exports.enum(["briefing", "role_briefing", "chat", "membership"]),
|
|
4752
|
-
briefing_role: RoleSchema.optional(),
|
|
4753
|
-
briefing_version: PositiveSafeIntegerSchema.optional(),
|
|
4754
|
-
membership: MembershipNoticeSchema.optional(),
|
|
4755
|
-
text: MessageTextSchema,
|
|
4756
|
-
recipient_identities: external_exports.array(NonEmptyStringSchema).superRefine((identities, context) => {
|
|
4757
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4758
|
-
for (const [index, identity] of identities.entries()) {
|
|
4759
|
-
if (seen.has(identity)) {
|
|
4760
|
-
context.addIssue({
|
|
4761
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4762
|
-
path: [index],
|
|
4763
|
-
message: "recipient identities must be unique"
|
|
4764
|
-
});
|
|
4765
|
-
}
|
|
4766
|
-
seen.add(identity);
|
|
4767
|
-
}
|
|
4768
|
-
}),
|
|
4769
|
-
source_msg_id: external_exports.number().int().nonnegative().safe().optional(),
|
|
4770
|
-
source_wire_id: NonEmptyStringSchema.optional(),
|
|
4771
|
-
source_reply_to: ReplyReferenceSchema.optional()
|
|
4772
|
-
};
|
|
4773
|
-
RelayIntentShape = {
|
|
4774
|
-
kind: external_exports.literal("relay_intent"),
|
|
4775
|
-
message_id: LowerCrockfordUlidSchema.optional(),
|
|
4776
|
-
file_id: LowerCrockfordUlidSchema.optional(),
|
|
4777
|
-
recipient_identity: NonEmptyStringSchema
|
|
4778
|
-
};
|
|
4779
|
-
RelayResultStatusSchema = external_exports.enum(["queued", "send_failed", "skipped_removed"]);
|
|
4780
|
-
RelayResultShape = {
|
|
4781
|
-
kind: external_exports.literal("relay_result"),
|
|
4782
|
-
intent_record_id: NonEmptyStringSchema,
|
|
4783
|
-
message_id: LowerCrockfordUlidSchema.optional(),
|
|
4784
|
-
file_id: LowerCrockfordUlidSchema.optional(),
|
|
4785
|
-
recipient_identity: NonEmptyStringSchema,
|
|
4786
|
-
status: RelayResultStatusSchema,
|
|
4787
|
-
wire_id: NonEmptyStringSchema.optional(),
|
|
4788
|
-
metadata_wire_id: NonEmptyStringSchema.optional()
|
|
4789
|
-
};
|
|
4790
|
-
FileShape = {
|
|
4791
|
-
kind: external_exports.literal("file"),
|
|
4792
|
-
file_id: LowerCrockfordUlidSchema,
|
|
4793
|
-
author: AuthorSnapshotSchema,
|
|
4794
|
-
author_alias: AuthorAliasSchema.optional(),
|
|
4795
|
-
filename: FileNameSchema,
|
|
4796
|
-
mime: FileMimeSchema,
|
|
4797
|
-
size: external_exports.number().int().nonnegative().max(MAX_FILE_BYTES),
|
|
4798
|
-
sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
4799
|
-
data_base64: external_exports.string(),
|
|
4800
|
-
recipient_identities: external_exports.array(NonEmptyStringSchema).superRefine((identities, context) => {
|
|
4801
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4802
|
-
for (const [index, identity] of identities.entries()) {
|
|
4803
|
-
if (seen.has(identity)) {
|
|
4804
|
-
context.addIssue({
|
|
4805
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4806
|
-
path: [index],
|
|
4807
|
-
message: "recipient identities must be unique"
|
|
4808
|
-
});
|
|
4809
|
-
}
|
|
4810
|
-
seen.add(identity);
|
|
4811
|
-
}
|
|
4812
|
-
}),
|
|
4813
|
-
source_file_id: external_exports.number().int().nonnegative().safe(),
|
|
4814
|
-
source_wire_id: NonEmptyStringSchema.optional(),
|
|
4815
|
-
source_reply_to: ReplyReferenceSchema.optional()
|
|
4816
|
-
};
|
|
4817
|
-
MembershipIntentShape = {
|
|
4818
|
-
kind: external_exports.literal("membership_intent"),
|
|
4819
|
-
action: external_exports.enum(["remove"]),
|
|
4820
|
-
participant_id: LowerCrockfordUlidSchema,
|
|
4821
|
-
recipient_identity: NonEmptyStringSchema,
|
|
4822
|
-
role: RoleSchema,
|
|
4823
|
-
alias: NonEmptyStringSchema.optional(),
|
|
4824
|
-
epoch: PositiveSafeIntegerSchema,
|
|
4825
|
-
notify: external_exports.boolean()
|
|
4826
|
-
};
|
|
4827
|
-
MembershipResultShape = {
|
|
4828
|
-
kind: external_exports.literal("membership_result"),
|
|
4829
|
-
intent_record_id: NonEmptyStringSchema,
|
|
4830
|
-
participant_id: LowerCrockfordUlidSchema,
|
|
4831
|
-
status: RelayStatusSchema,
|
|
4832
|
-
notified: external_exports.boolean(),
|
|
4833
|
-
key_material_retained: external_exports.literal(true),
|
|
4834
|
-
uncertain_after_restart: external_exports.literal(true).optional()
|
|
4835
|
-
};
|
|
4836
|
-
CloseNoticeIntentShape = {
|
|
4837
|
-
kind: external_exports.literal("close_notice_intent"),
|
|
4838
|
-
recipient_identity: NonEmptyStringSchema
|
|
4839
|
-
};
|
|
4840
|
-
CloseNoticeResultShape = {
|
|
4841
|
-
kind: external_exports.literal("close_notice_result"),
|
|
4842
|
-
intent_record_id: NonEmptyStringSchema,
|
|
4843
|
-
recipient_identity: NonEmptyStringSchema,
|
|
4844
|
-
status: RelayStatusSchema,
|
|
4845
|
-
notified: external_exports.boolean(),
|
|
4846
|
-
key_material_retained: external_exports.literal(true),
|
|
4847
|
-
uncertain_after_restart: external_exports.literal(true).optional()
|
|
4848
|
-
};
|
|
4849
|
-
MessageRecordSchema = external_exports.object({ ...RecordCommonShape, ...MessageShape }).strict();
|
|
4850
|
-
FileRecordSchema = external_exports.object({ ...RecordCommonShape, ...FileShape }).strict();
|
|
4851
|
-
RelayIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...RelayIntentShape }).strict();
|
|
4852
|
-
RelayResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...RelayResultShape }).strict();
|
|
4853
|
-
MembershipIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...MembershipIntentShape }).strict();
|
|
4854
|
-
MembershipResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...MembershipResultShape }).strict();
|
|
4855
|
-
CloseNoticeIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...CloseNoticeIntentShape }).strict();
|
|
4856
|
-
CloseNoticeResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...CloseNoticeResultShape }).strict();
|
|
4857
|
-
RawCommunicationRecordSchema = external_exports.discriminatedUnion("kind", [
|
|
4858
|
-
MessageRecordSchema,
|
|
4859
|
-
FileRecordSchema,
|
|
4860
|
-
RelayIntentRecordSchema,
|
|
4861
|
-
RelayResultRecordSchema,
|
|
4862
|
-
MembershipIntentRecordSchema,
|
|
4863
|
-
MembershipResultRecordSchema,
|
|
4864
|
-
CloseNoticeIntentRecordSchema,
|
|
4865
|
-
CloseNoticeResultRecordSchema
|
|
4866
|
-
]);
|
|
4867
|
-
CommunicationRecordSchema = RawCommunicationRecordSchema.superRefine((record, context) => {
|
|
4868
|
-
if (record.record_id !== `${record.room_id}:${record.seq}`) {
|
|
4869
|
-
context.addIssue({
|
|
4870
|
-
code: external_exports.ZodIssueCode.custom,
|
|
4871
|
-
path: ["record_id"],
|
|
4872
|
-
message: 'record_id must equal room_id + ":" + seq'
|
|
4873
|
-
});
|
|
4874
|
-
}
|
|
4875
|
-
if (record.kind === "message") refineMessageCategory(record, context);
|
|
4876
|
-
refineRelaySubject(record, context);
|
|
4877
|
-
refineFileRecord(record, context);
|
|
4878
|
-
});
|
|
4879
|
-
AppendRecordSchema = external_exports.discriminatedUnion("kind", [
|
|
4880
|
-
external_exports.object({ ...AppendCommonShape, ...MessageShape }).strict(),
|
|
4881
|
-
external_exports.object({ ...AppendCommonShape, ...FileShape }).strict(),
|
|
4882
|
-
external_exports.object({ ...AppendCommonShape, ...RelayIntentShape }).strict(),
|
|
4883
|
-
external_exports.object({ ...AppendCommonShape, ...RelayResultShape }).strict(),
|
|
4884
|
-
external_exports.object({ ...AppendCommonShape, ...MembershipIntentShape }).strict(),
|
|
4885
|
-
external_exports.object({ ...AppendCommonShape, ...MembershipResultShape }).strict(),
|
|
4886
|
-
external_exports.object({ ...AppendCommonShape, ...CloseNoticeIntentShape }).strict(),
|
|
4887
|
-
external_exports.object({ ...AppendCommonShape, ...CloseNoticeResultShape }).strict()
|
|
4888
|
-
]).superRefine((record, context) => {
|
|
4889
|
-
if (record.kind === "message") refineMessageCategory(record, context);
|
|
4890
|
-
refineRelaySubject(record, context);
|
|
4891
|
-
refineFileRecord(record, context);
|
|
4892
|
-
});
|
|
4893
|
-
}
|
|
4894
|
-
});
|
|
4895
|
-
|
|
4896
|
-
// src/config.ts
|
|
4897
|
-
import * as nodeFs from "node:fs";
|
|
4898
|
-
import { homedir } from "node:os";
|
|
4899
|
-
import { dirname, isAbsolute, join, parse, resolve } from "node:path";
|
|
4900
|
-
function defaultConfig(home = homedir()) {
|
|
4901
|
-
return {
|
|
4902
|
-
version: 1,
|
|
4903
|
-
stateDir: resolve(home, ".ours-cowork"),
|
|
4904
|
-
roomIdentity: { nameMode: "stable_id" },
|
|
4905
|
-
rest: { enabled: true, port: 3052 }
|
|
4906
|
-
};
|
|
4907
|
-
}
|
|
4908
|
-
function loadConfig(env = process.env, io = {}) {
|
|
4909
|
-
rejectRemovedEnvironment(env);
|
|
4910
|
-
const fs2 = io.fs ?? nodeFs;
|
|
4911
|
-
const defaults = defaultConfig(io.home);
|
|
4912
|
-
const configPath = resolve(env.OURS_COWORK_CONFIG ?? join(io.home ?? homedir(), ".ours-cowork", "config.json"));
|
|
4913
|
-
let file = defaults;
|
|
4914
|
-
const stat = lstatIfPresent(fs2, configPath);
|
|
4915
|
-
if (stat) {
|
|
4916
|
-
assertSecureFile(fs2, configPath, "config file");
|
|
4917
|
-
let parsed;
|
|
4918
|
-
try {
|
|
4919
|
-
parsed = JSON.parse(readSecureFile(fs2, configPath, "config file").toString("utf8"));
|
|
4920
|
-
} catch (error) {
|
|
4921
|
-
throw new CoworkConfigError(`malformed cowork config at ${configPath}`, { cause: error });
|
|
4922
|
-
}
|
|
4923
|
-
rejectRemovedConfig(parsed, configPath);
|
|
4924
|
-
try {
|
|
4925
|
-
file = CoworkConfigSchema.parse(parsed);
|
|
4926
|
-
} catch (error) {
|
|
4927
|
-
throw new CoworkConfigError(`invalid cowork config at ${configPath}`, { cause: error });
|
|
4928
|
-
}
|
|
4929
|
-
} else if (env.OURS_COWORK_CONFIG !== void 0) {
|
|
4930
|
-
throw new CoworkConfigError(`configured cowork config does not exist: ${configPath}`);
|
|
4931
|
-
}
|
|
4932
|
-
const restPort = env.OURS_COWORK_REST_PORT === void 0 ? void 0 : parsePort(env.OURS_COWORK_REST_PORT);
|
|
4933
|
-
try {
|
|
4934
|
-
return CoworkConfigSchema.parse({
|
|
4935
|
-
version: 1,
|
|
4936
|
-
stateDir: resolve(env.OURS_COWORK_STATE_DIR ?? file.stateDir),
|
|
4937
|
-
roomIdentity: file.roomIdentity,
|
|
4938
|
-
rest: {
|
|
4939
|
-
enabled: restPort === void 0 ? file.rest.enabled : true,
|
|
4940
|
-
port: restPort ?? file.rest.port
|
|
4501
|
+
announce(identityName) {
|
|
4502
|
+
for (const listener of this.listeners) {
|
|
4503
|
+
try {
|
|
4504
|
+
listener(identityName);
|
|
4505
|
+
} catch (error) {
|
|
4506
|
+
this.log(`cowork SDK notification listener failed for ${identityName}:`, error);
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4941
4509
|
}
|
|
4942
|
-
}
|
|
4943
|
-
} catch (error) {
|
|
4944
|
-
throw new CoworkConfigError("invalid effective cowork config", { cause: error });
|
|
4510
|
+
};
|
|
4945
4511
|
}
|
|
4946
|
-
}
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
"
|
|
4953
|
-
|
|
4954
|
-
if (removed.length === 0) return;
|
|
4955
|
-
throw new CoworkConfigError(
|
|
4956
|
-
`${removed.join(", ")} ${removed.length === 1 ? "was" : "were"} removed: ours-cowork now attaches only to the shared ours daemon. Configure that daemon with @ours.network/cli and select it through the standard OURS_CONFIG, OURS_PORT, and OURS_STATE_DIR inputs.`
|
|
4512
|
+
});
|
|
4513
|
+
|
|
4514
|
+
// src/contracts.ts
|
|
4515
|
+
import { createHash } from "node:crypto";
|
|
4516
|
+
function utf8Bounded(label, maximumBytes) {
|
|
4517
|
+
return external_exports.string().refine((value) => Buffer.byteLength(value, "utf8") >= 1, `${label} must be at least 1 UTF-8 byte`).refine(
|
|
4518
|
+
(value) => Buffer.byteLength(value, "utf8") <= maximumBytes,
|
|
4519
|
+
`${label} must be at most ${maximumBytes} UTF-8 bytes`
|
|
4957
4520
|
);
|
|
4958
4521
|
}
|
|
4959
|
-
function
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
);
|
|
4522
|
+
function isStrictRfc3339(value) {
|
|
4523
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-](\d{2}):(\d{2}))$/.exec(value);
|
|
4524
|
+
if (!match) return false;
|
|
4525
|
+
const [, yearText, monthText, dayText, hourText, minuteText, secondText, offsetHourText, offsetMinuteText] = match;
|
|
4526
|
+
const year = Number(yearText);
|
|
4527
|
+
const month = Number(monthText);
|
|
4528
|
+
const day = Number(dayText);
|
|
4529
|
+
const hour = Number(hourText);
|
|
4530
|
+
const minute = Number(minuteText);
|
|
4531
|
+
const second = Number(secondText);
|
|
4532
|
+
const offsetHour = offsetHourText === void 0 ? 0 : Number(offsetHourText);
|
|
4533
|
+
const offsetMinute = offsetMinuteText === void 0 ? 0 : Number(offsetMinuteText);
|
|
4534
|
+
if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59) return false;
|
|
4535
|
+
if (offsetHour > 23 || offsetMinute > 59) return false;
|
|
4536
|
+
const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
4537
|
+
const days = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
4538
|
+
return day >= 1 && day <= days[month - 1];
|
|
4966
4539
|
}
|
|
4967
|
-
function
|
|
4968
|
-
|
|
4969
|
-
const fs2 = io.fs ?? nodeFs;
|
|
4970
|
-
const stateDir = resolve(parsed.stateDir);
|
|
4971
|
-
assertSecureAncestors(fs2, stateDir, "state directory");
|
|
4972
|
-
const existing = lstatIfPresent(fs2, stateDir);
|
|
4973
|
-
if (!existing) {
|
|
4974
|
-
createSecureDirectoryTree(fs2, stateDir, "state directory");
|
|
4975
|
-
}
|
|
4976
|
-
assertSecureDirectory(fs2, stateDir, "state directory");
|
|
4977
|
-
const roomsPath = join(stateDir, "rooms");
|
|
4978
|
-
const rooms = lstatIfPresent(fs2, roomsPath);
|
|
4979
|
-
if (!rooms) {
|
|
4980
|
-
fs2.mkdirSync(roomsPath, { mode: DIRECTORY_MODE });
|
|
4981
|
-
secureOpenedDirectory(fs2, roomsPath, "rooms directory");
|
|
4982
|
-
fsyncDirectory(fs2, stateDir);
|
|
4983
|
-
}
|
|
4984
|
-
assertSecureDirectory(fs2, roomsPath, "rooms directory");
|
|
4985
|
-
return {
|
|
4986
|
-
socketPath: join(stateDir, "management.sock"),
|
|
4987
|
-
pidPath: join(stateDir, "daemon.pid"),
|
|
4988
|
-
lockPath: join(stateDir, "daemon.lock")
|
|
4989
|
-
};
|
|
4540
|
+
function normalizeRoomName(value) {
|
|
4541
|
+
return value.trim().normalize("NFC");
|
|
4990
4542
|
}
|
|
4991
|
-
function
|
|
4992
|
-
|
|
4993
|
-
throw new CoworkConfigError("OURS_COWORK_REST_PORT must be a decimal port from 1 to 65535");
|
|
4994
|
-
}
|
|
4995
|
-
const port = Number(value);
|
|
4996
|
-
if (port > 65535) throw new CoworkConfigError("OURS_COWORK_REST_PORT must be from 1 to 65535");
|
|
4997
|
-
return port;
|
|
4543
|
+
function roomIdentityName(roomName) {
|
|
4544
|
+
return `${ROOM_IDENTITY_PREFIX}${RoomNameSchema.parse(roomName)}`;
|
|
4998
4545
|
}
|
|
4999
|
-
function
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
const rootOwner = fs2.lstatSync(root).uid;
|
|
5003
|
-
let cursor = root;
|
|
5004
|
-
const components = absolute.slice(root.length).split("/").filter(Boolean);
|
|
5005
|
-
for (const [index, component] of components.entries()) {
|
|
5006
|
-
cursor = join(cursor, component);
|
|
5007
|
-
const stat = lstatIfPresent(fs2, cursor);
|
|
5008
|
-
if (stat?.isSymbolicLink()) throw new CoworkConfigError(`${label} must not traverse a symbolic link (symlink): ${cursor}`);
|
|
5009
|
-
if (!stat) break;
|
|
5010
|
-
if (!stat.isDirectory()) {
|
|
5011
|
-
if (index === components.length - 1) return;
|
|
5012
|
-
throw new CoworkConfigError(`${label} ancestor is not a directory: ${cursor}`);
|
|
5013
|
-
}
|
|
5014
|
-
if (index < components.length - 1) assertTrustedAncestor(stat, rootOwner, cursor, label);
|
|
4546
|
+
function isPersistedRoomIdentityName(roomId, identityName) {
|
|
4547
|
+
if (!LowerCrockfordUlidSchema.safeParse(roomId).success || !identityName.startsWith(ROOM_IDENTITY_PREFIX)) {
|
|
4548
|
+
return false;
|
|
5015
4549
|
}
|
|
4550
|
+
const parsed = RoomNameSchema.safeParse(identityName.slice(ROOM_IDENTITY_PREFIX.length));
|
|
4551
|
+
return parsed.success && identityName === `${ROOM_IDENTITY_PREFIX}${parsed.data}`;
|
|
5016
4552
|
}
|
|
5017
|
-
function
|
|
5018
|
-
|
|
5019
|
-
const trustedStickyDirectory = (stat.mode & 512) !== 0;
|
|
5020
|
-
const writableByOthers = (stat.mode & 18) !== 0;
|
|
5021
|
-
const trustedOwner = stat.uid === uid || stat.uid === 0 || stat.uid === rootOwner;
|
|
5022
|
-
if (writableByOthers && (!trustedStickyDirectory || !trustedOwner)) {
|
|
5023
|
-
throw new CoworkConfigError(`${label} has an unsafe writable ancestor: ${path}`);
|
|
5024
|
-
}
|
|
4553
|
+
function isStandardRoomIdentityName(roomId, identityName) {
|
|
4554
|
+
return isPersistedRoomIdentityName(roomId, identityName);
|
|
5025
4555
|
}
|
|
5026
|
-
function
|
|
5027
|
-
const
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
4556
|
+
function refineRoomLineage(room, context) {
|
|
4557
|
+
const exactPacketPending = room.state === "provisioning" && room.status === "packet_pending" && isPersistedRoomIdentityName(room.room_id, room.identity_name) && room.invites.length === 0 && room.seats.length === 0 && room.activated_at === void 0 && room.closed_at === void 0;
|
|
4558
|
+
if (room.identity_cid === "" && !exactPacketPending) {
|
|
4559
|
+
context.addIssue({
|
|
4560
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4561
|
+
path: ["identity_cid"],
|
|
4562
|
+
message: "empty identity_cid is reserved for the exact packet_pending provisioning sentinel"
|
|
4563
|
+
});
|
|
5034
4564
|
}
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
4565
|
+
if (room.identity_cid !== "" && room.status === "packet_pending") {
|
|
4566
|
+
context.addIssue({
|
|
4567
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4568
|
+
path: ["status"],
|
|
4569
|
+
message: "packet_pending status requires an empty identity_cid"
|
|
4570
|
+
});
|
|
5040
4571
|
}
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
const
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
4572
|
+
const pendingByRecovery = /* @__PURE__ */ new Map();
|
|
4573
|
+
for (const [index, invite] of room.invites.entries()) {
|
|
4574
|
+
if (invite.recovery_of === void 0) continue;
|
|
4575
|
+
const recoveryOf = invite.recovery_of;
|
|
4576
|
+
const source = room.invites.find((candidate) => candidate.invite_id === recoveryOf);
|
|
4577
|
+
const validSourceState = invite.state === "receipt_pending" ? source?.state === "replacement_required" : invite.state === "live" || invite.state === "consumed" || invite.state === "replacement_required" ? source?.state === "revoked" : invite.state === "revoked" ? invite.recovery_confirmed === true ? source?.state === "revoked" : source?.state === "replacement_required" || source?.state === "revoked" : false;
|
|
4578
|
+
if (!source || source.invite_id === invite.invite_id || !validSourceState) {
|
|
4579
|
+
context.addIssue({
|
|
4580
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4581
|
+
path: ["invites", index, "recovery_of"],
|
|
4582
|
+
message: "recovery_of must point to a source invite in the state required by this recovery lineage"
|
|
4583
|
+
});
|
|
4584
|
+
} else if (invite.mode !== source.mode || invite.role !== source.role || invite.min_accepts !== source.min_accepts) {
|
|
4585
|
+
context.addIssue({
|
|
4586
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4587
|
+
path: ["invites", index],
|
|
4588
|
+
message: "receipt_pending descriptor must copy source mode, role, and min_accepts"
|
|
4589
|
+
});
|
|
4590
|
+
}
|
|
4591
|
+
if (invite.state === "receipt_pending") {
|
|
4592
|
+
const count = (pendingByRecovery.get(recoveryOf) ?? 0) + 1;
|
|
4593
|
+
pendingByRecovery.set(recoveryOf, count);
|
|
4594
|
+
if (count > 1) {
|
|
4595
|
+
context.addIssue({
|
|
4596
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4597
|
+
path: ["invites", index, "recovery_of"],
|
|
4598
|
+
message: "only one receipt_pending invite may exist per recovery_of pointer"
|
|
4599
|
+
});
|
|
4600
|
+
}
|
|
5050
4601
|
}
|
|
5051
|
-
fs2.fchmodSync(fd, DIRECTORY_MODE);
|
|
5052
|
-
fs2.fsyncSync(fd);
|
|
5053
|
-
} finally {
|
|
5054
|
-
if (fd !== void 0) fs2.closeSync(fd);
|
|
5055
|
-
}
|
|
5056
|
-
}
|
|
5057
|
-
function assertSecureDirectory(fs2, path, label) {
|
|
5058
|
-
const stat = fs2.lstatSync(path);
|
|
5059
|
-
if (stat.isSymbolicLink()) throw new CoworkConfigError(`${label} must not be a symbolic link (symlink)`);
|
|
5060
|
-
if (!stat.isDirectory()) throw new CoworkConfigError(`${label} must be a directory`);
|
|
5061
|
-
if ((stat.mode & 511) !== DIRECTORY_MODE) {
|
|
5062
|
-
throw new CoworkConfigError(`${label} mode must be 0700`);
|
|
5063
4602
|
}
|
|
5064
|
-
assertOwner(stat, label);
|
|
5065
|
-
}
|
|
5066
|
-
function assertSecureFile(fs2, path, label) {
|
|
5067
|
-
const stat = fs2.lstatSync(path);
|
|
5068
|
-
if (stat.isSymbolicLink()) throw new CoworkConfigError(`${label} must not be a symbolic link (symlink)`);
|
|
5069
|
-
if (!stat.isFile() || stat.nlink !== 1) throw new CoworkConfigError(`${label} must be a single-link regular file`);
|
|
5070
|
-
if ((stat.mode & 511) !== FILE_MODE) throw new CoworkConfigError(`${label} mode must be 0600`);
|
|
5071
|
-
assertOwner(stat, label);
|
|
5072
4603
|
}
|
|
5073
|
-
function
|
|
5074
|
-
|
|
5075
|
-
throw new CoworkConfigError(`${label} must be owned by the current user`);
|
|
5076
|
-
}
|
|
4604
|
+
function defaultRoomName(roomId) {
|
|
4605
|
+
return `Room ${LowerCrockfordUlidSchema.parse(roomId).slice(0, 8)}`;
|
|
5077
4606
|
}
|
|
5078
|
-
function
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
4607
|
+
function migrateRoomV1(room, mintParticipantId) {
|
|
4608
|
+
return RoomSchema.parse({
|
|
4609
|
+
...room,
|
|
4610
|
+
version: 2,
|
|
4611
|
+
mission: { ...room.mission, briefing_version: 1 },
|
|
4612
|
+
role_briefings: {},
|
|
4613
|
+
rest_roles: [],
|
|
4614
|
+
anonymous: false,
|
|
4615
|
+
quiet_membership: false,
|
|
4616
|
+
membership_epoch: 0,
|
|
4617
|
+
seats: room.seats.map((seat) => ({
|
|
4618
|
+
...seat,
|
|
4619
|
+
participant_id: LowerCrockfordUlidSchema.parse(mintParticipantId()),
|
|
4620
|
+
state: "active"
|
|
4621
|
+
}))
|
|
4622
|
+
});
|
|
5091
4623
|
}
|
|
5092
|
-
function
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
4624
|
+
function refineRelaySubject(record, context) {
|
|
4625
|
+
if (record.kind !== "relay_intent" && record.kind !== "relay_result") return;
|
|
4626
|
+
if (record.message_id === void 0 === (record.file_id === void 0)) {
|
|
4627
|
+
context.addIssue({
|
|
4628
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4629
|
+
path: ["message_id"],
|
|
4630
|
+
message: "relay records require exactly one of message_id or file_id"
|
|
4631
|
+
});
|
|
5099
4632
|
}
|
|
5100
4633
|
}
|
|
5101
|
-
function
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
throw error;
|
|
4634
|
+
function refineFileRecord(record, context) {
|
|
4635
|
+
if (record.kind !== "file" || record.data_base64 === void 0) return;
|
|
4636
|
+
const bytes = Buffer.from(record.data_base64, "base64");
|
|
4637
|
+
if (bytes.toString("base64") !== record.data_base64) {
|
|
4638
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["data_base64"], message: "file bytes must use canonical base64" });
|
|
5107
4639
|
}
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
"
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
init_contracts();
|
|
5115
|
-
DIRECTORY_MODE = 448;
|
|
5116
|
-
FILE_MODE = 384;
|
|
5117
|
-
NO_FOLLOW = nodeFs.constants.O_NOFOLLOW ?? 0;
|
|
5118
|
-
CoworkConfigSchema = external_exports.object({
|
|
5119
|
-
version: external_exports.literal(1),
|
|
5120
|
-
stateDir: external_exports.string().min(1),
|
|
5121
|
-
roomIdentity: external_exports.object({
|
|
5122
|
-
nameMode: RoomIdentityNameModeSchema
|
|
5123
|
-
}).strict().default({ nameMode: "stable_id" }),
|
|
5124
|
-
rest: external_exports.object({
|
|
5125
|
-
enabled: external_exports.boolean(),
|
|
5126
|
-
port: external_exports.number().int().min(1).max(65535)
|
|
5127
|
-
}).strict()
|
|
5128
|
-
}).strict();
|
|
5129
|
-
CoworkConfigError = class extends Error {
|
|
5130
|
-
constructor(message, options) {
|
|
5131
|
-
super(message, options);
|
|
5132
|
-
this.name = "CoworkConfigError";
|
|
5133
|
-
}
|
|
5134
|
-
};
|
|
4640
|
+
if (bytes.length !== record.size) {
|
|
4641
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["size"], message: "file size must match decoded bytes" });
|
|
4642
|
+
}
|
|
4643
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
4644
|
+
if (digest !== record.sha256) {
|
|
4645
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["sha256"], message: "file sha256 must match decoded bytes" });
|
|
5135
4646
|
}
|
|
5136
|
-
});
|
|
5137
|
-
|
|
5138
|
-
// src/ours-runtime.ts
|
|
5139
|
-
import { randomBytes } from "node:crypto";
|
|
5140
|
-
function createOursHost(_config, log = () => {
|
|
5141
|
-
}) {
|
|
5142
|
-
return new SharedOursHost(log);
|
|
5143
4647
|
}
|
|
5144
|
-
function
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
4648
|
+
function refineMessageCategory(message, context) {
|
|
4649
|
+
const requires = (field, present) => {
|
|
4650
|
+
if (present && message[field] === void 0) {
|
|
4651
|
+
context.addIssue({
|
|
4652
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4653
|
+
path: [field],
|
|
4654
|
+
message: `${message.category} messages require ${field}`
|
|
4655
|
+
});
|
|
5152
4656
|
}
|
|
5153
|
-
|
|
4657
|
+
if (!present && message[field] !== void 0) {
|
|
4658
|
+
context.addIssue({
|
|
4659
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4660
|
+
path: [field],
|
|
4661
|
+
message: `${field} is forbidden on ${message.category} messages`
|
|
4662
|
+
});
|
|
4663
|
+
}
|
|
4664
|
+
};
|
|
4665
|
+
requires("briefing_role", message.category === "role_briefing");
|
|
4666
|
+
requires("membership", message.category === "membership");
|
|
4667
|
+
if (message.category === "role_briefing" && message.briefing_version === void 0) {
|
|
4668
|
+
context.addIssue({
|
|
4669
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4670
|
+
path: ["briefing_version"],
|
|
4671
|
+
message: "role_briefing messages require briefing_version"
|
|
4672
|
+
});
|
|
4673
|
+
}
|
|
4674
|
+
if (message.category === "chat" || message.category === "membership") {
|
|
4675
|
+
if (message.briefing_version !== void 0) {
|
|
4676
|
+
context.addIssue({
|
|
4677
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4678
|
+
path: ["briefing_version"],
|
|
4679
|
+
message: `briefing_version is forbidden on ${message.category} messages`
|
|
4680
|
+
});
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
5154
4683
|
}
|
|
5155
|
-
var
|
|
5156
|
-
var
|
|
5157
|
-
"src/
|
|
4684
|
+
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, ROOM_ROLE, MissionTextSchema, MessageTextSchema, FileNameSchema, FileMimeSchema, RoomStateSchema, SeatStateSchema, InviteModeSchema, DEFAULT_ROLE, InviteStateSchema, RelayStatusSchema, SeatV1Schema, SeatSchema, RoomInviteSchema, MissionV1Schema, MissionSchema, RoleBriefingSchema, RoomCommonShape, RoomV1Schema, CurrentRoomSchema, RoomSchema, CreateRoomInputSchema, UpdateRoomInputSchema, RoleBriefingSetInputSchema, RoleBriefingDeleteInputSchema, PostMessageInputSchema, PostAsRoleInputSchema, RestRoleInputSchema, ContainerIdSchema, AcceptExternalInviteInputSchema, AuthorSnapshotSchema, RecordCommonShape, AppendCommonShape, MembershipNoticeSchema, AuthorAliasSchema, ReplyReferenceSchema, MessageShape, RelayIntentShape, RelayResultStatusSchema, RelayResultShape, FileShape, MembershipIntentShape, MembershipResultShape, CloseNoticeIntentShape, CloseNoticeResultShape, MessageRecordSchema, FileRecordSchema, RelayIntentRecordSchema, RelayResultRecordSchema, MembershipIntentRecordSchema, MembershipResultRecordSchema, CloseNoticeIntentRecordSchema, CloseNoticeResultRecordSchema, RawCommunicationRecordSchema, CommunicationRecordSchema, AppendRecordSchema;
|
|
4685
|
+
var init_contracts = __esm({
|
|
4686
|
+
"src/contracts.ts"() {
|
|
5158
4687
|
"use strict";
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
4688
|
+
init_zod();
|
|
4689
|
+
MAX_TEXT_BYTES = 262144;
|
|
4690
|
+
MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
4691
|
+
MAX_HISTORY_PAGE_BYTES = 3 * 1024 * 1024;
|
|
4692
|
+
MAX_MANAGEMENT_RESPONSE_BYTES = MAX_HISTORY_PAGE_BYTES + 1024 * 1024;
|
|
4693
|
+
MAX_EXTERNAL_INVITE_BYTES = 48 * 1024;
|
|
4694
|
+
MAX_FILE_NAME_BYTES = 255;
|
|
4695
|
+
MAX_MIME_BYTES = 255;
|
|
4696
|
+
MAX_ROLE_BYTES = 256;
|
|
4697
|
+
MAX_ROOM_NAME_CHARACTERS = 64;
|
|
4698
|
+
NonEmptyStringSchema = external_exports.string().min(1);
|
|
4699
|
+
PositiveSafeIntegerSchema = external_exports.number().int().positive().safe();
|
|
4700
|
+
LowerCrockfordUlidSchema = external_exports.string().regex(
|
|
4701
|
+
/^[0-7][0-9a-hjkmnp-tv-z]{25}$/,
|
|
4702
|
+
"must be a 26-character lowercase Crockford ULID"
|
|
4703
|
+
);
|
|
4704
|
+
Rfc3339Schema = external_exports.string().refine(isStrictRfc3339, "must be a valid RFC3339 timestamp");
|
|
4705
|
+
RoomNameSchema = external_exports.string().refine(
|
|
4706
|
+
(value) => !/[\p{Cc}\p{Cf}]/u.test(value),
|
|
4707
|
+
"room name must not contain Unicode control or format characters"
|
|
4708
|
+
).transform(normalizeRoomName).superRefine((value, context) => {
|
|
4709
|
+
const length = Array.from(value).length;
|
|
4710
|
+
if (length < 1 || length > MAX_ROOM_NAME_CHARACTERS) {
|
|
4711
|
+
context.addIssue({
|
|
4712
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4713
|
+
message: `room name must contain 1-${MAX_ROOM_NAME_CHARACTERS} Unicode characters after normalization`
|
|
4714
|
+
});
|
|
5184
4715
|
}
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
4716
|
+
});
|
|
4717
|
+
ROOM_IDENTITY_PREFIX = "ours-cowork:";
|
|
4718
|
+
RoleSchema = utf8Bounded("role", MAX_ROLE_BYTES);
|
|
4719
|
+
ROOM_ROLE = "room";
|
|
4720
|
+
MissionTextSchema = utf8Bounded("mission text", MAX_TEXT_BYTES);
|
|
4721
|
+
MessageTextSchema = utf8Bounded("message text", MAX_TEXT_BYTES);
|
|
4722
|
+
FileNameSchema = utf8Bounded("file name", MAX_FILE_NAME_BYTES).refine((value) => value !== "." && value !== "..", "file name must not be a relative path token").refine((value) => !/[\x00/\\]/.test(value), "file name must be a single path-free name");
|
|
4723
|
+
FileMimeSchema = external_exports.string().refine(
|
|
4724
|
+
(value) => Buffer.byteLength(value, "utf8") <= MAX_MIME_BYTES,
|
|
4725
|
+
`file MIME metadata must be at most ${MAX_MIME_BYTES} UTF-8 bytes`
|
|
4726
|
+
);
|
|
4727
|
+
RoomStateSchema = external_exports.enum(["provisioning", "active", "closing", "closed"]);
|
|
4728
|
+
SeatStateSchema = external_exports.enum(["pending", "active", "removed"]);
|
|
4729
|
+
InviteModeSchema = external_exports.enum(["one_time", "public"]);
|
|
4730
|
+
DEFAULT_ROLE = "Participant";
|
|
4731
|
+
InviteStateSchema = external_exports.enum([
|
|
4732
|
+
"live",
|
|
4733
|
+
"consumed",
|
|
4734
|
+
"revoked",
|
|
4735
|
+
"replacement_required",
|
|
4736
|
+
"receipt_pending"
|
|
4737
|
+
]);
|
|
4738
|
+
RelayStatusSchema = external_exports.enum(["queued", "send_failed"]);
|
|
4739
|
+
SeatV1Schema = external_exports.object({
|
|
4740
|
+
identity: NonEmptyStringSchema,
|
|
4741
|
+
display_name: NonEmptyStringSchema,
|
|
4742
|
+
role: RoleSchema,
|
|
4743
|
+
invite_id: NonEmptyStringSchema,
|
|
4744
|
+
accepted_at: Rfc3339Schema
|
|
4745
|
+
}).strict();
|
|
4746
|
+
SeatSchema = external_exports.object({
|
|
4747
|
+
identity: NonEmptyStringSchema,
|
|
4748
|
+
display_name: NonEmptyStringSchema,
|
|
4749
|
+
role: RoleSchema,
|
|
4750
|
+
invite_id: NonEmptyStringSchema,
|
|
4751
|
+
accepted_at: Rfc3339Schema.optional(),
|
|
4752
|
+
requested_at: Rfc3339Schema.optional(),
|
|
4753
|
+
invite_sha256: external_exports.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
4754
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
4755
|
+
state: SeatStateSchema,
|
|
4756
|
+
alias: NonEmptyStringSchema.optional(),
|
|
4757
|
+
removed_at: Rfc3339Schema.optional(),
|
|
4758
|
+
removed_epoch: external_exports.number().int().nonnegative().safe().optional(),
|
|
4759
|
+
replaces_seat: LowerCrockfordUlidSchema.optional(),
|
|
4760
|
+
bounced_at: Rfc3339Schema.optional()
|
|
4761
|
+
}).strict().superRefine((seat, context) => {
|
|
4762
|
+
if (seat.state === "pending") {
|
|
4763
|
+
for (const field of ["requested_at", "invite_sha256"]) {
|
|
4764
|
+
if (seat[field] === void 0) {
|
|
4765
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: [field], message: `pending seats require ${field}` });
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
for (const field of ["accepted_at", "removed_at", "removed_epoch", "bounced_at"]) {
|
|
4769
|
+
if (seat[field] !== void 0) {
|
|
4770
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: [field], message: `${field} is forbidden on pending seats` });
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
} else if (seat.state === "removed") {
|
|
4774
|
+
if (seat.accepted_at === void 0 && seat.requested_at === void 0) {
|
|
4775
|
+
context.addIssue({
|
|
4776
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4777
|
+
path: ["accepted_at"],
|
|
4778
|
+
message: "removed seats require accepted_at unless they are cancelled external admissions"
|
|
4779
|
+
});
|
|
4780
|
+
}
|
|
4781
|
+
if (seat.removed_at === void 0) {
|
|
4782
|
+
context.addIssue({
|
|
4783
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4784
|
+
path: ["removed_at"],
|
|
4785
|
+
message: "removed seats require removed_at"
|
|
4786
|
+
});
|
|
4787
|
+
}
|
|
4788
|
+
if (seat.removed_epoch === void 0) {
|
|
4789
|
+
context.addIssue({
|
|
4790
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4791
|
+
path: ["removed_epoch"],
|
|
4792
|
+
message: "removed seats require removed_epoch"
|
|
4793
|
+
});
|
|
4794
|
+
}
|
|
4795
|
+
} else {
|
|
4796
|
+
if (seat.accepted_at === void 0) {
|
|
4797
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["accepted_at"], message: "active seats require accepted_at" });
|
|
4798
|
+
}
|
|
4799
|
+
for (const field of ["removed_at", "removed_epoch", "bounced_at"]) {
|
|
4800
|
+
if (seat[field] !== void 0) {
|
|
4801
|
+
context.addIssue({
|
|
4802
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4803
|
+
path: [field],
|
|
4804
|
+
message: `${field} is reserved for removed seats`
|
|
4805
|
+
});
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
5188
4808
|
}
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
4809
|
+
if (seat.requested_at === void 0 !== (seat.invite_sha256 === void 0)) {
|
|
4810
|
+
context.addIssue({
|
|
4811
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4812
|
+
path: ["invite_sha256"],
|
|
4813
|
+
message: "external admission metadata requires both requested_at and invite_sha256"
|
|
4814
|
+
});
|
|
5193
4815
|
}
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
4816
|
+
});
|
|
4817
|
+
RoomInviteSchema = external_exports.object({
|
|
4818
|
+
invite_id: NonEmptyStringSchema,
|
|
4819
|
+
mode: InviteModeSchema,
|
|
4820
|
+
role: RoleSchema,
|
|
4821
|
+
min_accepts: PositiveSafeIntegerSchema,
|
|
4822
|
+
accepted_cids: external_exports.array(NonEmptyStringSchema),
|
|
4823
|
+
state: InviteStateSchema,
|
|
4824
|
+
recovery_of: NonEmptyStringSchema.optional(),
|
|
4825
|
+
recovery_confirmed: external_exports.boolean().optional(),
|
|
4826
|
+
created_at: Rfc3339Schema,
|
|
4827
|
+
replaces_seat: LowerCrockfordUlidSchema.optional()
|
|
4828
|
+
}).strict().superRefine((invite, context) => {
|
|
4829
|
+
if (invite.mode === "one_time" && invite.min_accepts !== 1) {
|
|
4830
|
+
context.addIssue({
|
|
4831
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4832
|
+
path: ["min_accepts"],
|
|
4833
|
+
message: "one_time invites require min_accepts === 1"
|
|
4834
|
+
});
|
|
5197
4835
|
}
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
watcher.work = this.follow(identityName, controller.signal);
|
|
5205
|
-
this.watchers.set(identityName, watcher);
|
|
5206
|
-
this.announce(identityName);
|
|
5207
|
-
this.ensureStateResync();
|
|
5208
|
-
return () => this.untrack(identityName, watcher);
|
|
4836
|
+
if (invite.state === "receipt_pending" && invite.recovery_of === void 0) {
|
|
4837
|
+
context.addIssue({
|
|
4838
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4839
|
+
path: ["recovery_of"],
|
|
4840
|
+
message: "receipt_pending invites require recovery_of"
|
|
4841
|
+
});
|
|
5209
4842
|
}
|
|
5210
|
-
|
|
4843
|
+
if (invite.recovery_of === void 0 && invite.recovery_confirmed !== void 0) {
|
|
4844
|
+
context.addIssue({
|
|
4845
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4846
|
+
path: ["recovery_confirmed"],
|
|
4847
|
+
message: "recovery_confirmed is forbidden without recovery_of"
|
|
4848
|
+
});
|
|
5211
4849
|
}
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
this.watchers.clear();
|
|
5219
|
-
for (const watcher of watchers) watcher.controller.abort();
|
|
5220
|
-
await Promise.allSettled(watchers.map((watcher) => watcher.work));
|
|
5221
|
-
const client = this.watchClient;
|
|
5222
|
-
this.watchClient = void 0;
|
|
5223
|
-
if (client) await client.releaseLease();
|
|
5224
|
-
return { requiresProcessExit: false };
|
|
4850
|
+
if (invite.recovery_of !== void 0 && invite.recovery_confirmed === void 0) {
|
|
4851
|
+
context.addIssue({
|
|
4852
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4853
|
+
path: ["recovery_confirmed"],
|
|
4854
|
+
message: "recovery_confirmed is required with recovery_of"
|
|
4855
|
+
});
|
|
5225
4856
|
}
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
4857
|
+
if (invite.state === "receipt_pending" && invite.recovery_confirmed !== false) {
|
|
4858
|
+
context.addIssue({
|
|
4859
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4860
|
+
path: ["recovery_confirmed"],
|
|
4861
|
+
message: "receipt_pending recovery lineage must be unconfirmed"
|
|
4862
|
+
});
|
|
5231
4863
|
}
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
4864
|
+
if (invite.recovery_of !== void 0 && (invite.state === "live" || invite.state === "consumed" || invite.state === "replacement_required") && invite.recovery_confirmed !== true) {
|
|
4865
|
+
context.addIssue({
|
|
4866
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4867
|
+
path: ["recovery_confirmed"],
|
|
4868
|
+
message: "live, consumed, and replacement_required recovery lineage must be confirmed"
|
|
4869
|
+
});
|
|
5238
4870
|
}
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
4871
|
+
if (invite.state === "receipt_pending" && invite.accepted_cids.length > 0) {
|
|
4872
|
+
context.addIssue({
|
|
4873
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4874
|
+
path: ["accepted_cids"],
|
|
4875
|
+
message: "receipt_pending invites cannot have accepted CIDs"
|
|
4876
|
+
});
|
|
5243
4877
|
}
|
|
4878
|
+
});
|
|
4879
|
+
MissionV1Schema = external_exports.object({
|
|
4880
|
+
goal: MissionTextSchema,
|
|
4881
|
+
briefing: MissionTextSchema
|
|
4882
|
+
}).strict();
|
|
4883
|
+
MissionSchema = external_exports.object({
|
|
4884
|
+
goal: MissionTextSchema,
|
|
4885
|
+
briefing: MissionTextSchema,
|
|
4886
|
+
briefing_version: PositiveSafeIntegerSchema
|
|
4887
|
+
}).strict();
|
|
4888
|
+
RoleBriefingSchema = external_exports.object({
|
|
4889
|
+
text: MissionTextSchema,
|
|
4890
|
+
version: PositiveSafeIntegerSchema,
|
|
4891
|
+
updated_at: Rfc3339Schema
|
|
4892
|
+
}).strict();
|
|
4893
|
+
RoomCommonShape = {
|
|
4894
|
+
room_id: LowerCrockfordUlidSchema,
|
|
4895
|
+
identity_name: NonEmptyStringSchema,
|
|
4896
|
+
identity_cid: external_exports.string(),
|
|
4897
|
+
state: RoomStateSchema,
|
|
4898
|
+
status: NonEmptyStringSchema.optional(),
|
|
4899
|
+
invites: external_exports.array(RoomInviteSchema),
|
|
4900
|
+
created_at: Rfc3339Schema,
|
|
4901
|
+
activated_at: Rfc3339Schema.optional(),
|
|
4902
|
+
closed_at: Rfc3339Schema.optional()
|
|
4903
|
+
};
|
|
4904
|
+
RoomV1Schema = external_exports.object({
|
|
4905
|
+
...RoomCommonShape,
|
|
4906
|
+
version: external_exports.literal(1),
|
|
4907
|
+
mission: MissionV1Schema,
|
|
4908
|
+
seats: external_exports.array(SeatV1Schema)
|
|
4909
|
+
}).strict().superRefine(refineRoomLineage);
|
|
4910
|
+
CurrentRoomSchema = external_exports.object({
|
|
4911
|
+
...RoomCommonShape,
|
|
4912
|
+
room_name: RoomNameSchema,
|
|
4913
|
+
version: external_exports.literal(2),
|
|
4914
|
+
mission: MissionSchema,
|
|
4915
|
+
role_briefings: external_exports.record(RoleSchema, RoleBriefingSchema),
|
|
5244
4916
|
/**
|
|
5245
|
-
*
|
|
5246
|
-
*
|
|
5247
|
-
*
|
|
5248
|
-
*
|
|
5249
|
-
*
|
|
5250
|
-
* notification log; reconciliation is idempotent and self-coalescing.
|
|
4917
|
+
* Roles a REST caller may author under. A plain array of names:
|
|
4918
|
+
* the role name IS the identifier, so there is nothing per-role to store.
|
|
4919
|
+
* Not a seat, not a membership: seat and membership invariants do not apply.
|
|
4920
|
+
* The registry itself is an exact-name set, and reserves `room` for the
|
|
4921
|
+
* room's own voice.
|
|
5251
4922
|
*/
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
if (resyncPending) {
|
|
5262
|
-
resyncPending = false;
|
|
5263
|
-
this.announce(identityName);
|
|
5264
|
-
}
|
|
5265
|
-
for (let settled = await step; !settled.done; settled = await step) {
|
|
5266
|
-
backoffMs = WATCH_RETRY_MIN_MS;
|
|
5267
|
-
this.announce(identityName);
|
|
5268
|
-
step = stream.next();
|
|
5269
|
-
}
|
|
5270
|
-
if (!signal.aborted) {
|
|
5271
|
-
resyncPending = true;
|
|
5272
|
-
this.log(`[${identityName}] shared ours daemon notification watch ended; reconnecting`);
|
|
5273
|
-
}
|
|
5274
|
-
} catch (error) {
|
|
5275
|
-
if (signal.aborted) return;
|
|
5276
|
-
resyncPending = true;
|
|
5277
|
-
this.log(`[${identityName}] shared ours daemon notification watch failed:`, error);
|
|
4923
|
+
rest_roles: external_exports.array(RoleSchema).superRefine((roles, context) => {
|
|
4924
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4925
|
+
for (const [index, role] of roles.entries()) {
|
|
4926
|
+
if (role === ROOM_ROLE) {
|
|
4927
|
+
context.addIssue({
|
|
4928
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4929
|
+
path: [index],
|
|
4930
|
+
message: `role "${ROOM_ROLE}" is reserved for the room's own voice`
|
|
4931
|
+
});
|
|
5278
4932
|
}
|
|
5279
|
-
if (
|
|
5280
|
-
|
|
5281
|
-
|
|
4933
|
+
if (seen.has(role)) {
|
|
4934
|
+
context.addIssue({
|
|
4935
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4936
|
+
path: [index],
|
|
4937
|
+
message: "REST role names must be unique within the room"
|
|
4938
|
+
});
|
|
4939
|
+
}
|
|
4940
|
+
seen.add(role);
|
|
4941
|
+
}
|
|
4942
|
+
}),
|
|
4943
|
+
anonymous: external_exports.boolean(),
|
|
4944
|
+
quiet_membership: external_exports.boolean(),
|
|
4945
|
+
membership_epoch: external_exports.number().int().nonnegative().safe(),
|
|
4946
|
+
seats: external_exports.array(SeatSchema)
|
|
4947
|
+
}).strict().superRefine((room, context) => {
|
|
4948
|
+
refineRoomLineage(room, context);
|
|
4949
|
+
const byParticipant = /* @__PURE__ */ new Map();
|
|
4950
|
+
const activeAliases = /* @__PURE__ */ new Set();
|
|
4951
|
+
const authorizedCids = /* @__PURE__ */ new Set();
|
|
4952
|
+
for (const [index, seat] of room.seats.entries()) {
|
|
4953
|
+
if (byParticipant.has(seat.participant_id)) {
|
|
4954
|
+
context.addIssue({
|
|
4955
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4956
|
+
path: ["seats", index, "participant_id"],
|
|
4957
|
+
message: "participant_id must be unique within the room"
|
|
4958
|
+
});
|
|
4959
|
+
}
|
|
4960
|
+
byParticipant.set(seat.participant_id, seat);
|
|
4961
|
+
if (seat.state === "pending" || seat.state === "active") {
|
|
4962
|
+
if (authorizedCids.has(seat.identity)) {
|
|
4963
|
+
context.addIssue({
|
|
4964
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4965
|
+
path: ["seats", index, "identity"],
|
|
4966
|
+
message: "at most one pending or active seat may exist per CID"
|
|
4967
|
+
});
|
|
4968
|
+
}
|
|
4969
|
+
authorizedCids.add(seat.identity);
|
|
4970
|
+
}
|
|
4971
|
+
if (room.anonymous && seat.alias === void 0) {
|
|
4972
|
+
context.addIssue({
|
|
4973
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4974
|
+
path: ["seats", index, "alias"],
|
|
4975
|
+
message: "anonymous rooms require an alias on every seat"
|
|
4976
|
+
});
|
|
4977
|
+
}
|
|
4978
|
+
if (!room.anonymous && seat.alias !== void 0) {
|
|
4979
|
+
context.addIssue({
|
|
4980
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4981
|
+
path: ["seats", index, "alias"],
|
|
4982
|
+
message: "aliases are reserved for anonymous rooms"
|
|
4983
|
+
});
|
|
4984
|
+
}
|
|
4985
|
+
if (seat.state === "active" && seat.alias !== void 0) {
|
|
4986
|
+
if (activeAliases.has(seat.alias)) {
|
|
4987
|
+
context.addIssue({
|
|
4988
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4989
|
+
path: ["seats", index, "alias"],
|
|
4990
|
+
message: "active seats must hold distinct aliases"
|
|
4991
|
+
});
|
|
4992
|
+
}
|
|
4993
|
+
activeAliases.add(seat.alias);
|
|
4994
|
+
}
|
|
4995
|
+
if (seat.removed_epoch !== void 0 && seat.removed_epoch > room.membership_epoch) {
|
|
4996
|
+
context.addIssue({
|
|
4997
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4998
|
+
path: ["seats", index, "removed_epoch"],
|
|
4999
|
+
message: "removed_epoch cannot exceed the room membership_epoch"
|
|
5000
|
+
});
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
for (const [index, seat] of room.seats.entries()) {
|
|
5004
|
+
if (seat.replaces_seat === void 0) continue;
|
|
5005
|
+
const predecessor = byParticipant.get(seat.replaces_seat);
|
|
5006
|
+
if (!predecessor || predecessor === seat || predecessor.state !== "removed") {
|
|
5007
|
+
context.addIssue({
|
|
5008
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5009
|
+
path: ["seats", index, "replaces_seat"],
|
|
5010
|
+
message: "replaces_seat must reference a removed seat in this room"
|
|
5011
|
+
});
|
|
5012
|
+
continue;
|
|
5013
|
+
}
|
|
5014
|
+
if (predecessor.role !== seat.role) {
|
|
5015
|
+
context.addIssue({
|
|
5016
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5017
|
+
path: ["seats", index, "role"],
|
|
5018
|
+
message: "a replacement seat must inherit the predecessor role"
|
|
5019
|
+
});
|
|
5020
|
+
}
|
|
5021
|
+
if (room.anonymous && seat.alias !== predecessor.alias) {
|
|
5022
|
+
context.addIssue({
|
|
5023
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5024
|
+
path: ["seats", index, "alias"],
|
|
5025
|
+
message: "an anonymous replacement seat must inherit the predecessor alias"
|
|
5026
|
+
});
|
|
5282
5027
|
}
|
|
5283
5028
|
}
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5029
|
+
});
|
|
5030
|
+
RoomSchema = external_exports.preprocess((value) => {
|
|
5031
|
+
if (typeof value !== "object" || value === null) return value;
|
|
5032
|
+
const patch = {};
|
|
5033
|
+
if (!Object.hasOwn(value, "room_name")) {
|
|
5034
|
+
const roomId = value.room_id;
|
|
5035
|
+
if (typeof roomId === "string" && LowerCrockfordUlidSchema.safeParse(roomId).success) {
|
|
5036
|
+
patch.room_name = defaultRoomName(roomId);
|
|
5291
5037
|
}
|
|
5292
5038
|
}
|
|
5039
|
+
if (!Object.hasOwn(value, "rest_roles")) patch.rest_roles = [];
|
|
5040
|
+
if (Object.keys(patch).length === 0) return value;
|
|
5041
|
+
return { ...value, ...patch };
|
|
5042
|
+
}, CurrentRoomSchema);
|
|
5043
|
+
CreateRoomInputSchema = external_exports.object({
|
|
5044
|
+
name: RoomNameSchema.optional(),
|
|
5045
|
+
goal: MissionTextSchema,
|
|
5046
|
+
briefing: MissionTextSchema,
|
|
5047
|
+
anonymous: external_exports.boolean().optional(),
|
|
5048
|
+
quiet_membership: external_exports.boolean().optional()
|
|
5049
|
+
}).strict();
|
|
5050
|
+
UpdateRoomInputSchema = external_exports.object({
|
|
5051
|
+
name: RoomNameSchema.optional(),
|
|
5052
|
+
goal: MissionTextSchema.optional(),
|
|
5053
|
+
briefing: MissionTextSchema.optional(),
|
|
5054
|
+
status: NonEmptyStringSchema.optional(),
|
|
5055
|
+
quiet_membership: external_exports.boolean().optional()
|
|
5056
|
+
}).strict().refine((input) => Object.keys(input).length > 0, "at least one setting is required");
|
|
5057
|
+
RoleBriefingSetInputSchema = external_exports.object({
|
|
5058
|
+
role: RoleSchema,
|
|
5059
|
+
text: MissionTextSchema
|
|
5060
|
+
}).strict();
|
|
5061
|
+
RoleBriefingDeleteInputSchema = external_exports.object({
|
|
5062
|
+
role: RoleSchema
|
|
5063
|
+
}).strict();
|
|
5064
|
+
PostMessageInputSchema = external_exports.object({
|
|
5065
|
+
text: MessageTextSchema
|
|
5066
|
+
}).strict();
|
|
5067
|
+
PostAsRoleInputSchema = external_exports.object({
|
|
5068
|
+
role: RoleSchema,
|
|
5069
|
+
text: MessageTextSchema
|
|
5070
|
+
}).strict();
|
|
5071
|
+
RestRoleInputSchema = external_exports.object({
|
|
5072
|
+
role: RoleSchema
|
|
5073
|
+
}).strict();
|
|
5074
|
+
ContainerIdSchema = external_exports.string().regex(/^[0-9a-f]{64}$/i, "must be a 64-character hexadecimal CID").transform((value) => value.toUpperCase());
|
|
5075
|
+
AcceptExternalInviteInputSchema = external_exports.object({
|
|
5076
|
+
role: RoleSchema,
|
|
5077
|
+
invite: external_exports.string().refine(
|
|
5078
|
+
(value) => Buffer.byteLength(value, "utf8") <= MAX_EXTERNAL_INVITE_BYTES,
|
|
5079
|
+
`invite input must be at most ${MAX_EXTERNAL_INVITE_BYTES} UTF-8 bytes`
|
|
5080
|
+
),
|
|
5081
|
+
expected_cid: ContainerIdSchema.optional(),
|
|
5082
|
+
replaces_seat: LowerCrockfordUlidSchema.optional()
|
|
5083
|
+
}).strict();
|
|
5084
|
+
AuthorSnapshotSchema = external_exports.object({
|
|
5085
|
+
identity: NonEmptyStringSchema,
|
|
5086
|
+
display_name: NonEmptyStringSchema,
|
|
5087
|
+
role: RoleSchema
|
|
5088
|
+
}).strict();
|
|
5089
|
+
RecordCommonShape = {
|
|
5090
|
+
version: external_exports.literal(1),
|
|
5091
|
+
room_id: LowerCrockfordUlidSchema,
|
|
5092
|
+
seq: PositiveSafeIntegerSchema,
|
|
5093
|
+
record_id: NonEmptyStringSchema,
|
|
5094
|
+
at: Rfc3339Schema
|
|
5095
|
+
};
|
|
5096
|
+
AppendCommonShape = {
|
|
5097
|
+
version: external_exports.literal(1),
|
|
5098
|
+
room_id: LowerCrockfordUlidSchema,
|
|
5099
|
+
at: Rfc3339Schema
|
|
5100
|
+
};
|
|
5101
|
+
MembershipNoticeSchema = external_exports.object({
|
|
5102
|
+
action: external_exports.enum(["remove"]),
|
|
5103
|
+
alias: NonEmptyStringSchema.optional(),
|
|
5104
|
+
role: RoleSchema.optional(),
|
|
5105
|
+
epoch: external_exports.number().int().nonnegative().safe()
|
|
5106
|
+
}).strict();
|
|
5107
|
+
AuthorAliasSchema = external_exports.object({
|
|
5108
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
5109
|
+
alias: NonEmptyStringSchema
|
|
5110
|
+
}).strict();
|
|
5111
|
+
ReplyReferenceSchema = external_exports.object({
|
|
5112
|
+
wire_id: NonEmptyStringSchema,
|
|
5113
|
+
sentence: PositiveSafeIntegerSchema.optional()
|
|
5114
|
+
}).strict();
|
|
5115
|
+
MessageShape = {
|
|
5116
|
+
kind: external_exports.literal("message"),
|
|
5117
|
+
message_id: LowerCrockfordUlidSchema,
|
|
5118
|
+
author: AuthorSnapshotSchema,
|
|
5119
|
+
author_alias: AuthorAliasSchema.optional(),
|
|
5120
|
+
category: external_exports.enum(["briefing", "role_briefing", "chat", "membership"]),
|
|
5121
|
+
briefing_role: RoleSchema.optional(),
|
|
5122
|
+
briefing_version: PositiveSafeIntegerSchema.optional(),
|
|
5123
|
+
membership: MembershipNoticeSchema.optional(),
|
|
5124
|
+
text: MessageTextSchema,
|
|
5125
|
+
recipient_identities: external_exports.array(NonEmptyStringSchema).superRefine((identities, context) => {
|
|
5126
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5127
|
+
for (const [index, identity] of identities.entries()) {
|
|
5128
|
+
if (seen.has(identity)) {
|
|
5129
|
+
context.addIssue({
|
|
5130
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5131
|
+
path: [index],
|
|
5132
|
+
message: "recipient identities must be unique"
|
|
5133
|
+
});
|
|
5134
|
+
}
|
|
5135
|
+
seen.add(identity);
|
|
5136
|
+
}
|
|
5137
|
+
}),
|
|
5138
|
+
source_msg_id: external_exports.number().int().nonnegative().safe().optional(),
|
|
5139
|
+
source_wire_id: NonEmptyStringSchema.optional(),
|
|
5140
|
+
source_reply_to: ReplyReferenceSchema.optional()
|
|
5141
|
+
};
|
|
5142
|
+
RelayIntentShape = {
|
|
5143
|
+
kind: external_exports.literal("relay_intent"),
|
|
5144
|
+
message_id: LowerCrockfordUlidSchema.optional(),
|
|
5145
|
+
file_id: LowerCrockfordUlidSchema.optional(),
|
|
5146
|
+
recipient_identity: NonEmptyStringSchema
|
|
5147
|
+
};
|
|
5148
|
+
RelayResultStatusSchema = external_exports.enum(["queued", "send_failed", "skipped_removed"]);
|
|
5149
|
+
RelayResultShape = {
|
|
5150
|
+
kind: external_exports.literal("relay_result"),
|
|
5151
|
+
intent_record_id: NonEmptyStringSchema,
|
|
5152
|
+
message_id: LowerCrockfordUlidSchema.optional(),
|
|
5153
|
+
file_id: LowerCrockfordUlidSchema.optional(),
|
|
5154
|
+
recipient_identity: NonEmptyStringSchema,
|
|
5155
|
+
status: RelayResultStatusSchema,
|
|
5156
|
+
wire_id: NonEmptyStringSchema.optional(),
|
|
5157
|
+
metadata_wire_id: NonEmptyStringSchema.optional()
|
|
5158
|
+
};
|
|
5159
|
+
FileShape = {
|
|
5160
|
+
kind: external_exports.literal("file"),
|
|
5161
|
+
file_id: LowerCrockfordUlidSchema,
|
|
5162
|
+
author: AuthorSnapshotSchema,
|
|
5163
|
+
author_alias: AuthorAliasSchema.optional(),
|
|
5164
|
+
filename: FileNameSchema,
|
|
5165
|
+
mime: FileMimeSchema,
|
|
5166
|
+
size: external_exports.number().int().nonnegative().max(MAX_FILE_BYTES),
|
|
5167
|
+
sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
5168
|
+
data_base64: external_exports.string(),
|
|
5169
|
+
recipient_identities: external_exports.array(NonEmptyStringSchema).superRefine((identities, context) => {
|
|
5170
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5171
|
+
for (const [index, identity] of identities.entries()) {
|
|
5172
|
+
if (seen.has(identity)) {
|
|
5173
|
+
context.addIssue({
|
|
5174
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5175
|
+
path: [index],
|
|
5176
|
+
message: "recipient identities must be unique"
|
|
5177
|
+
});
|
|
5178
|
+
}
|
|
5179
|
+
seen.add(identity);
|
|
5180
|
+
}
|
|
5181
|
+
}),
|
|
5182
|
+
source_file_id: external_exports.number().int().nonnegative().safe(),
|
|
5183
|
+
source_wire_id: NonEmptyStringSchema.optional(),
|
|
5184
|
+
source_reply_to: ReplyReferenceSchema.optional()
|
|
5185
|
+
};
|
|
5186
|
+
MembershipIntentShape = {
|
|
5187
|
+
kind: external_exports.literal("membership_intent"),
|
|
5188
|
+
action: external_exports.enum(["remove"]),
|
|
5189
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
5190
|
+
recipient_identity: NonEmptyStringSchema,
|
|
5191
|
+
role: RoleSchema,
|
|
5192
|
+
alias: NonEmptyStringSchema.optional(),
|
|
5193
|
+
epoch: PositiveSafeIntegerSchema,
|
|
5194
|
+
notify: external_exports.boolean()
|
|
5195
|
+
};
|
|
5196
|
+
MembershipResultShape = {
|
|
5197
|
+
kind: external_exports.literal("membership_result"),
|
|
5198
|
+
intent_record_id: NonEmptyStringSchema,
|
|
5199
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
5200
|
+
status: RelayStatusSchema,
|
|
5201
|
+
notified: external_exports.boolean(),
|
|
5202
|
+
key_material_retained: external_exports.literal(true),
|
|
5203
|
+
uncertain_after_restart: external_exports.literal(true).optional()
|
|
5204
|
+
};
|
|
5205
|
+
CloseNoticeIntentShape = {
|
|
5206
|
+
kind: external_exports.literal("close_notice_intent"),
|
|
5207
|
+
recipient_identity: NonEmptyStringSchema
|
|
5208
|
+
};
|
|
5209
|
+
CloseNoticeResultShape = {
|
|
5210
|
+
kind: external_exports.literal("close_notice_result"),
|
|
5211
|
+
intent_record_id: NonEmptyStringSchema,
|
|
5212
|
+
recipient_identity: NonEmptyStringSchema,
|
|
5213
|
+
status: RelayStatusSchema,
|
|
5214
|
+
notified: external_exports.boolean(),
|
|
5215
|
+
key_material_retained: external_exports.literal(true),
|
|
5216
|
+
uncertain_after_restart: external_exports.literal(true).optional()
|
|
5293
5217
|
};
|
|
5218
|
+
MessageRecordSchema = external_exports.object({ ...RecordCommonShape, ...MessageShape }).strict();
|
|
5219
|
+
FileRecordSchema = external_exports.object({ ...RecordCommonShape, ...FileShape }).strict();
|
|
5220
|
+
RelayIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...RelayIntentShape }).strict();
|
|
5221
|
+
RelayResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...RelayResultShape }).strict();
|
|
5222
|
+
MembershipIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...MembershipIntentShape }).strict();
|
|
5223
|
+
MembershipResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...MembershipResultShape }).strict();
|
|
5224
|
+
CloseNoticeIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...CloseNoticeIntentShape }).strict();
|
|
5225
|
+
CloseNoticeResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...CloseNoticeResultShape }).strict();
|
|
5226
|
+
RawCommunicationRecordSchema = external_exports.discriminatedUnion("kind", [
|
|
5227
|
+
MessageRecordSchema,
|
|
5228
|
+
FileRecordSchema,
|
|
5229
|
+
RelayIntentRecordSchema,
|
|
5230
|
+
RelayResultRecordSchema,
|
|
5231
|
+
MembershipIntentRecordSchema,
|
|
5232
|
+
MembershipResultRecordSchema,
|
|
5233
|
+
CloseNoticeIntentRecordSchema,
|
|
5234
|
+
CloseNoticeResultRecordSchema
|
|
5235
|
+
]);
|
|
5236
|
+
CommunicationRecordSchema = RawCommunicationRecordSchema.superRefine((record, context) => {
|
|
5237
|
+
if (record.record_id !== `${record.room_id}:${record.seq}`) {
|
|
5238
|
+
context.addIssue({
|
|
5239
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5240
|
+
path: ["record_id"],
|
|
5241
|
+
message: 'record_id must equal room_id + ":" + seq'
|
|
5242
|
+
});
|
|
5243
|
+
}
|
|
5244
|
+
if (record.kind === "message") refineMessageCategory(record, context);
|
|
5245
|
+
refineRelaySubject(record, context);
|
|
5246
|
+
refineFileRecord(record, context);
|
|
5247
|
+
});
|
|
5248
|
+
AppendRecordSchema = external_exports.discriminatedUnion("kind", [
|
|
5249
|
+
external_exports.object({ ...AppendCommonShape, ...MessageShape }).strict(),
|
|
5250
|
+
external_exports.object({ ...AppendCommonShape, ...FileShape }).strict(),
|
|
5251
|
+
external_exports.object({ ...AppendCommonShape, ...RelayIntentShape }).strict(),
|
|
5252
|
+
external_exports.object({ ...AppendCommonShape, ...RelayResultShape }).strict(),
|
|
5253
|
+
external_exports.object({ ...AppendCommonShape, ...MembershipIntentShape }).strict(),
|
|
5254
|
+
external_exports.object({ ...AppendCommonShape, ...MembershipResultShape }).strict(),
|
|
5255
|
+
external_exports.object({ ...AppendCommonShape, ...CloseNoticeIntentShape }).strict(),
|
|
5256
|
+
external_exports.object({ ...AppendCommonShape, ...CloseNoticeResultShape }).strict()
|
|
5257
|
+
]).superRefine((record, context) => {
|
|
5258
|
+
if (record.kind === "message") refineMessageCategory(record, context);
|
|
5259
|
+
refineRelaySubject(record, context);
|
|
5260
|
+
refineFileRecord(record, context);
|
|
5261
|
+
});
|
|
5294
5262
|
}
|
|
5295
5263
|
});
|
|
5296
5264
|
|
|
@@ -5464,7 +5432,7 @@ var init_packets = __esm({
|
|
|
5464
5432
|
get(roomId) {
|
|
5465
5433
|
return this.packets.get(roomId);
|
|
5466
5434
|
}
|
|
5467
|
-
async create(roomId, identityName
|
|
5435
|
+
async create(roomId, identityName, bio = `ours-cowork mission room ${roomId}`) {
|
|
5468
5436
|
validateRoomId(roomId);
|
|
5469
5437
|
this.assertStandardIdentity(roomId, identityName);
|
|
5470
5438
|
this.assertNoLegacyState(roomId);
|
|
@@ -5496,7 +5464,7 @@ var init_packets = __esm({
|
|
|
5496
5464
|
throw new Error(`failed to provision standard SDK identity for room "${roomId}"`, { cause: error });
|
|
5497
5465
|
}
|
|
5498
5466
|
}
|
|
5499
|
-
async restore(roomId, expectedCid, identityName
|
|
5467
|
+
async restore(roomId, expectedCid, identityName) {
|
|
5500
5468
|
validateRoomId(roomId);
|
|
5501
5469
|
this.assertStandardIdentity(roomId, identityName);
|
|
5502
5470
|
this.assertNoLegacyState(roomId);
|
|
@@ -5582,6 +5550,7 @@ var init_packets = __esm({
|
|
|
5582
5550
|
cid;
|
|
5583
5551
|
client;
|
|
5584
5552
|
contacts = [];
|
|
5553
|
+
hasInviteProvenance = false;
|
|
5585
5554
|
invites = [];
|
|
5586
5555
|
refreshWork;
|
|
5587
5556
|
contactRefreshWork;
|
|
@@ -5607,8 +5576,12 @@ var init_packets = __esm({
|
|
|
5607
5576
|
return this.contactRefreshWork;
|
|
5608
5577
|
}
|
|
5609
5578
|
async refreshContactsUnlocked() {
|
|
5610
|
-
const
|
|
5611
|
-
this.
|
|
5579
|
+
const listed = await this.client.listContacts();
|
|
5580
|
+
this.hasInviteProvenance = listed.origins !== void 0;
|
|
5581
|
+
this.contacts = listed.contacts.map((contact) => ({
|
|
5582
|
+
...contact,
|
|
5583
|
+
...listed.origins?.[contact.container_id] === void 0 ? {} : { accepted_via_invite_id: listed.origins[contact.container_id] }
|
|
5584
|
+
}));
|
|
5612
5585
|
}
|
|
5613
5586
|
async mintInvite(mode) {
|
|
5614
5587
|
const result = await this.client.generateInvite({ mode });
|
|
@@ -5634,6 +5607,9 @@ var init_packets = __esm({
|
|
|
5634
5607
|
listInvites() {
|
|
5635
5608
|
return this.invites.map((invite) => ({ ...invite }));
|
|
5636
5609
|
}
|
|
5610
|
+
get supportsInviteProvenance() {
|
|
5611
|
+
return this.hasInviteProvenance;
|
|
5612
|
+
}
|
|
5637
5613
|
listContacts() {
|
|
5638
5614
|
return this.contacts.map((contact) => ({ ...contact }));
|
|
5639
5615
|
}
|
|
@@ -5993,7 +5969,12 @@ var init_intake = __esm({
|
|
|
5993
5969
|
const seats = room.seats.map((candidate) => candidate.participant_id === removed.participant_id ? { ...candidate, bounced_at: this.now() } : candidate);
|
|
5994
5970
|
await this.store.save(RoomSchema.parse({ ...room, seats }));
|
|
5995
5971
|
try {
|
|
5996
|
-
const unsigned = {
|
|
5972
|
+
const unsigned = {
|
|
5973
|
+
version: 1,
|
|
5974
|
+
kind: "room_not_member",
|
|
5975
|
+
room_id: roomId,
|
|
5976
|
+
room_name: room.room_name
|
|
5977
|
+
};
|
|
5997
5978
|
await sendRoomBody(packet, item.sender_id, unsigned);
|
|
5998
5979
|
} catch {
|
|
5999
5980
|
}
|
|
@@ -6082,6 +6063,7 @@ var init_intake = __esm({
|
|
|
6082
6063
|
version: 1,
|
|
6083
6064
|
kind: "room_file",
|
|
6084
6065
|
room_id: roomId,
|
|
6066
|
+
room_name: room.room_name,
|
|
6085
6067
|
file_id: file.file_id,
|
|
6086
6068
|
author,
|
|
6087
6069
|
filename: file.filename,
|
|
@@ -6131,6 +6113,7 @@ var init_intake = __esm({
|
|
|
6131
6113
|
version: 1,
|
|
6132
6114
|
kind: wireKind(message.category),
|
|
6133
6115
|
room_id: roomId,
|
|
6116
|
+
room_name: room.room_name,
|
|
6134
6117
|
message_id: message.message_id,
|
|
6135
6118
|
// An anonymous author leaves the archive only in alias form.
|
|
6136
6119
|
author: message.author_alias === void 0 ? message.author : {
|
|
@@ -6282,7 +6265,6 @@ var init_service = __esm({
|
|
|
6282
6265
|
nextMessageId;
|
|
6283
6266
|
intake;
|
|
6284
6267
|
provisioningCheckpoint;
|
|
6285
|
-
identityNameMode;
|
|
6286
6268
|
constructor(store, packets, options = {}) {
|
|
6287
6269
|
this.store = store;
|
|
6288
6270
|
this.packets = packets;
|
|
@@ -6291,7 +6273,6 @@ var init_service = __esm({
|
|
|
6291
6273
|
this.nextMessageId = options.messageId ?? generateUlid;
|
|
6292
6274
|
this.provisioningCheckpoint = options.provisioningCheckpoint ?? (() => {
|
|
6293
6275
|
});
|
|
6294
|
-
this.identityNameMode = options.identityNameMode ?? "stable_id";
|
|
6295
6276
|
this.intake = new IntakePump(store, packets, {
|
|
6296
6277
|
now: this.nowValue,
|
|
6297
6278
|
messageId: this.nextMessageId
|
|
@@ -6301,7 +6282,7 @@ var init_service = __esm({
|
|
|
6301
6282
|
const settings = CreateRoomInputSchema.parse(input);
|
|
6302
6283
|
const roomId = LowerCrockfordUlidSchema.parse(this.nextRoomId());
|
|
6303
6284
|
const roomName = settings.name ?? defaultRoomName(roomId);
|
|
6304
|
-
const identityName =
|
|
6285
|
+
const identityName = roomIdentityName(roomName);
|
|
6305
6286
|
return this.lock(roomId, async () => {
|
|
6306
6287
|
const provisional = RoomSchema.parse({
|
|
6307
6288
|
version: 2,
|
|
@@ -6472,9 +6453,9 @@ var init_service = __esm({
|
|
|
6472
6453
|
return this.lock(id, async () => {
|
|
6473
6454
|
const room = await this.store.load(id);
|
|
6474
6455
|
this.assertMutable(room, "create an invite for");
|
|
6475
|
-
if (room.invites.some((invite) => invite.state === "live" || invite.state === "receipt_pending")) {
|
|
6456
|
+
if (!this.packet(id).supportsInviteProvenance && room.invites.some((invite) => invite.state === "live" || invite.state === "receipt_pending")) {
|
|
6476
6457
|
throw new RoomServiceError(
|
|
6477
|
-
"
|
|
6458
|
+
"the installed ours SDK does not expose authenticated invite provenance; revoke or consume the current invitation before creating another"
|
|
6478
6459
|
);
|
|
6479
6460
|
}
|
|
6480
6461
|
return this.mintInviteUnlocked(room, {
|
|
@@ -6560,12 +6541,12 @@ var init_service = __esm({
|
|
|
6560
6541
|
return receipt;
|
|
6561
6542
|
}
|
|
6562
6543
|
async mintInviteUnlocked(room, request) {
|
|
6563
|
-
|
|
6544
|
+
const packet = this.packet(room.room_id);
|
|
6545
|
+
if (!packet.supportsInviteProvenance && room.invites.some((invite2) => invite2.state === "live" || invite2.state === "receipt_pending")) {
|
|
6564
6546
|
throw new RoomServiceError(
|
|
6565
|
-
"
|
|
6547
|
+
"the installed ours SDK does not expose authenticated invite provenance; revoke or consume the current invitation first"
|
|
6566
6548
|
);
|
|
6567
6549
|
}
|
|
6568
|
-
const packet = this.packet(room.room_id);
|
|
6569
6550
|
const minted = await packet.mintInvite(request.mode);
|
|
6570
6551
|
const invite = RoomInviteSchema.parse({
|
|
6571
6552
|
invite_id: minted.invite_id,
|
|
@@ -7164,7 +7145,12 @@ var init_service = __esm({
|
|
|
7164
7145
|
if (room.state === "closed" || room.state === "closing") return room;
|
|
7165
7146
|
const contactsByCid = /* @__PURE__ */ new Map();
|
|
7166
7147
|
for (const contact of packet.listContacts()) {
|
|
7167
|
-
if (
|
|
7148
|
+
if (contactsByCid.has(contact.container_id)) continue;
|
|
7149
|
+
const authenticatedInvite = contact.accepted_via_invite_id;
|
|
7150
|
+
contactsByCid.set(contact.container_id, {
|
|
7151
|
+
displayName: contact.name,
|
|
7152
|
+
...authenticatedInvite === void 0 ? {} : { inviteId: authenticatedInvite }
|
|
7153
|
+
});
|
|
7168
7154
|
}
|
|
7169
7155
|
const inviteById = new Map(room.invites.map((invite) => [invite.invite_id, invite]));
|
|
7170
7156
|
const existingCids = new Set(room.seats.filter((seat) => seat.state === "active" || seat.state === "pending").map((seat) => seat.identity));
|
|
@@ -7180,13 +7166,13 @@ var init_service = __esm({
|
|
|
7180
7166
|
const seatsWithActivations = [...room.seats];
|
|
7181
7167
|
for (const [seatIndex, seat] of room.seats.entries()) {
|
|
7182
7168
|
if (seat.state !== "pending") continue;
|
|
7183
|
-
const
|
|
7184
|
-
if (
|
|
7169
|
+
const contact = contactsByCid.get(seat.identity);
|
|
7170
|
+
if (contact === void 0) continue;
|
|
7185
7171
|
const { alias: _alias, replaces_seat: _replacesSeat, ...base } = seat;
|
|
7186
7172
|
const activated = {
|
|
7187
7173
|
...base,
|
|
7188
7174
|
state: "active",
|
|
7189
|
-
display_name: displayName,
|
|
7175
|
+
display_name: contact.displayName,
|
|
7190
7176
|
accepted_at: this.now(),
|
|
7191
7177
|
..._replacesSeat === void 0 ? {} : { replaces_seat: _replacesSeat },
|
|
7192
7178
|
...room.anonymous ? { alias: _alias } : {}
|
|
@@ -7196,11 +7182,13 @@ var init_service = __esm({
|
|
|
7196
7182
|
}
|
|
7197
7183
|
const newSeats = [];
|
|
7198
7184
|
const eligibleInvites = room.invites.filter((invite) => invite.state === "live" && (invite.recovery_of === void 0 || invite.recovery_confirmed === true));
|
|
7199
|
-
const
|
|
7200
|
-
for (const [cid,
|
|
7185
|
+
const legacyAdmissionInvite = !packet.supportsInviteProvenance && eligibleInvites.length === 1 ? eligibleInvites[0] : void 0;
|
|
7186
|
+
for (const [cid, contact] of contactsByCid) {
|
|
7201
7187
|
if (existingCids.has(cid)) continue;
|
|
7202
|
-
const invite =
|
|
7188
|
+
const invite = contact.inviteId === void 0 ? legacyAdmissionInvite : inviteById.get(contact.inviteId);
|
|
7203
7189
|
if (!invite) continue;
|
|
7190
|
+
if (invite.state !== "live") continue;
|
|
7191
|
+
if (invite.recovery_of !== void 0 && invite.recovery_confirmed !== true) continue;
|
|
7204
7192
|
const removedAt = lastRemovedAt.get(cid);
|
|
7205
7193
|
if (removedAt !== void 0 && invite.created_at <= removedAt) continue;
|
|
7206
7194
|
const seated = [...seatsWithActivations, ...newSeats];
|
|
@@ -7208,7 +7196,7 @@ var init_service = __esm({
|
|
|
7208
7196
|
if (invite.replaces_seat !== void 0 && !predecessor) continue;
|
|
7209
7197
|
newSeats.push({
|
|
7210
7198
|
identity: cid,
|
|
7211
|
-
display_name: displayName,
|
|
7199
|
+
display_name: contact.displayName,
|
|
7212
7200
|
role: invite.role,
|
|
7213
7201
|
invite_id: invite.invite_id,
|
|
7214
7202
|
accepted_at: this.now(),
|
|
@@ -10124,8 +10112,7 @@ var init_daemon_runtime = __esm({
|
|
|
10124
10112
|
);
|
|
10125
10113
|
this.service = this.options.service ?? new RoomService(
|
|
10126
10114
|
this.store,
|
|
10127
|
-
this.registry
|
|
10128
|
-
{ identityNameMode: config.roomIdentity?.nameMode ?? "stable_id" }
|
|
10115
|
+
this.registry
|
|
10129
10116
|
);
|
|
10130
10117
|
serviceRef = this.service;
|
|
10131
10118
|
this.hostStartAttempted = true;
|