@ours.network/cowork 0.3.0 → 0.3.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 +9 -1
- package/dist/cli.js +655 -14
- package/dist/daemon.js +1500 -1169
- package/dist/mufl_code/{9D2DA4CD06758ABFBC52B9C5692A2E30249A3C12F48B6F45F193B22E447F0342.muflo → BBAE58CF78DEE59692F456EAFFA9A6109835B66846FC3989F6D201B8F4523A55.muflo} +0 -0
- package/dist/web/assets/app.js +12 -12
- package/docs/05-room-workflow.md +3 -3
- package/docs/07-messaging-history.md +10 -2
- package/docs/10-limitations.md +3 -1
- package/docs/11-web-console.md +3 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4271,6 +4271,619 @@ function lstatIfPresent(fs2, path) {
|
|
|
4271
4271
|
}
|
|
4272
4272
|
}
|
|
4273
4273
|
|
|
4274
|
+
// src/contracts.ts
|
|
4275
|
+
import { createHash } from "node:crypto";
|
|
4276
|
+
var MAX_TEXT_BYTES = 262144;
|
|
4277
|
+
var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
4278
|
+
var MAX_HISTORY_PAGE_BYTES = 3 * 1024 * 1024;
|
|
4279
|
+
var MAX_MANAGEMENT_RESPONSE_BYTES = MAX_HISTORY_PAGE_BYTES + 1024 * 1024;
|
|
4280
|
+
var MAX_FILE_NAME_BYTES = 255;
|
|
4281
|
+
var MAX_MIME_BYTES = 255;
|
|
4282
|
+
var MAX_ROLE_BYTES = 256;
|
|
4283
|
+
var MAX_ROOM_NAME_CHARACTERS = 64;
|
|
4284
|
+
function utf8Bounded(label, maximumBytes) {
|
|
4285
|
+
return external_exports.string().refine((value) => Buffer.byteLength(value, "utf8") >= 1, `${label} must be at least 1 UTF-8 byte`).refine(
|
|
4286
|
+
(value) => Buffer.byteLength(value, "utf8") <= maximumBytes,
|
|
4287
|
+
`${label} must be at most ${maximumBytes} UTF-8 bytes`
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
4290
|
+
var NonEmptyStringSchema = external_exports.string().min(1);
|
|
4291
|
+
var PositiveSafeIntegerSchema = external_exports.number().int().positive().safe();
|
|
4292
|
+
var LowerCrockfordUlidSchema = external_exports.string().regex(
|
|
4293
|
+
/^[0-7][0-9a-hjkmnp-tv-z]{25}$/,
|
|
4294
|
+
"must be a 26-character lowercase Crockford ULID"
|
|
4295
|
+
);
|
|
4296
|
+
function isStrictRfc3339(value) {
|
|
4297
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-](\d{2}):(\d{2}))$/.exec(value);
|
|
4298
|
+
if (!match) return false;
|
|
4299
|
+
const [, yearText, monthText, dayText, hourText, minuteText, secondText, offsetHourText, offsetMinuteText] = match;
|
|
4300
|
+
const year = Number(yearText);
|
|
4301
|
+
const month = Number(monthText);
|
|
4302
|
+
const day = Number(dayText);
|
|
4303
|
+
const hour = Number(hourText);
|
|
4304
|
+
const minute = Number(minuteText);
|
|
4305
|
+
const second = Number(secondText);
|
|
4306
|
+
const offsetHour = offsetHourText === void 0 ? 0 : Number(offsetHourText);
|
|
4307
|
+
const offsetMinute = offsetMinuteText === void 0 ? 0 : Number(offsetMinuteText);
|
|
4308
|
+
if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59) return false;
|
|
4309
|
+
if (offsetHour > 23 || offsetMinute > 59) return false;
|
|
4310
|
+
const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
4311
|
+
const days = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
4312
|
+
return day >= 1 && day <= days[month - 1];
|
|
4313
|
+
}
|
|
4314
|
+
var Rfc3339Schema = external_exports.string().refine(isStrictRfc3339, "must be a valid RFC3339 timestamp");
|
|
4315
|
+
function normalizeRoomName(value) {
|
|
4316
|
+
return value.trim().normalize("NFC");
|
|
4317
|
+
}
|
|
4318
|
+
var RoomNameSchema = external_exports.string().refine(
|
|
4319
|
+
(value) => !/[\p{Cc}\p{Cf}]/u.test(value),
|
|
4320
|
+
"room name must not contain Unicode control or format characters"
|
|
4321
|
+
).transform(normalizeRoomName).superRefine((value, context) => {
|
|
4322
|
+
const length = Array.from(value).length;
|
|
4323
|
+
if (length < 1 || length > MAX_ROOM_NAME_CHARACTERS) {
|
|
4324
|
+
context.addIssue({
|
|
4325
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4326
|
+
message: `room name must contain 1-${MAX_ROOM_NAME_CHARACTERS} Unicode characters after normalization`
|
|
4327
|
+
});
|
|
4328
|
+
}
|
|
4329
|
+
});
|
|
4330
|
+
var RoleSchema = utf8Bounded("role", MAX_ROLE_BYTES);
|
|
4331
|
+
var MissionTextSchema = utf8Bounded("mission text", MAX_TEXT_BYTES);
|
|
4332
|
+
var MessageTextSchema = utf8Bounded("message text", MAX_TEXT_BYTES);
|
|
4333
|
+
var 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");
|
|
4334
|
+
var FileMimeSchema = external_exports.string().refine(
|
|
4335
|
+
(value) => Buffer.byteLength(value, "utf8") <= MAX_MIME_BYTES,
|
|
4336
|
+
`file MIME metadata must be at most ${MAX_MIME_BYTES} UTF-8 bytes`
|
|
4337
|
+
);
|
|
4338
|
+
var RoomStateSchema = external_exports.enum(["provisioning", "active", "closing", "closed"]);
|
|
4339
|
+
var SeatStateSchema = external_exports.enum(["active", "removed"]);
|
|
4340
|
+
var InviteModeSchema = external_exports.enum(["one_time", "public"]);
|
|
4341
|
+
var InviteStateSchema = external_exports.enum([
|
|
4342
|
+
"live",
|
|
4343
|
+
"consumed",
|
|
4344
|
+
"revoked",
|
|
4345
|
+
"replacement_required",
|
|
4346
|
+
"receipt_pending"
|
|
4347
|
+
]);
|
|
4348
|
+
var RelayStatusSchema = external_exports.enum(["queued", "send_failed"]);
|
|
4349
|
+
var SeatV1Schema = external_exports.object({
|
|
4350
|
+
identity: NonEmptyStringSchema,
|
|
4351
|
+
display_name: NonEmptyStringSchema,
|
|
4352
|
+
role: RoleSchema,
|
|
4353
|
+
invite_id: NonEmptyStringSchema,
|
|
4354
|
+
accepted_at: Rfc3339Schema
|
|
4355
|
+
}).strict();
|
|
4356
|
+
var SeatSchema = external_exports.object({
|
|
4357
|
+
identity: NonEmptyStringSchema,
|
|
4358
|
+
display_name: NonEmptyStringSchema,
|
|
4359
|
+
role: RoleSchema,
|
|
4360
|
+
invite_id: NonEmptyStringSchema,
|
|
4361
|
+
accepted_at: Rfc3339Schema,
|
|
4362
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
4363
|
+
state: SeatStateSchema,
|
|
4364
|
+
alias: NonEmptyStringSchema.optional(),
|
|
4365
|
+
removed_at: Rfc3339Schema.optional(),
|
|
4366
|
+
removed_epoch: external_exports.number().int().nonnegative().safe().optional(),
|
|
4367
|
+
replaces_seat: LowerCrockfordUlidSchema.optional(),
|
|
4368
|
+
bounced_at: Rfc3339Schema.optional()
|
|
4369
|
+
}).strict().superRefine((seat, context) => {
|
|
4370
|
+
if (seat.state === "removed") {
|
|
4371
|
+
if (seat.removed_at === void 0) {
|
|
4372
|
+
context.addIssue({
|
|
4373
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4374
|
+
path: ["removed_at"],
|
|
4375
|
+
message: "removed seats require removed_at"
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
if (seat.removed_epoch === void 0) {
|
|
4379
|
+
context.addIssue({
|
|
4380
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4381
|
+
path: ["removed_epoch"],
|
|
4382
|
+
message: "removed seats require removed_epoch"
|
|
4383
|
+
});
|
|
4384
|
+
}
|
|
4385
|
+
} else {
|
|
4386
|
+
for (const field of ["removed_at", "removed_epoch", "bounced_at"]) {
|
|
4387
|
+
if (seat[field] !== void 0) {
|
|
4388
|
+
context.addIssue({
|
|
4389
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4390
|
+
path: [field],
|
|
4391
|
+
message: `${field} is reserved for removed seats`
|
|
4392
|
+
});
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
});
|
|
4397
|
+
var RoomInviteSchema = external_exports.object({
|
|
4398
|
+
invite_id: NonEmptyStringSchema,
|
|
4399
|
+
mode: InviteModeSchema,
|
|
4400
|
+
role: RoleSchema,
|
|
4401
|
+
min_accepts: PositiveSafeIntegerSchema,
|
|
4402
|
+
accepted_cids: external_exports.array(NonEmptyStringSchema),
|
|
4403
|
+
state: InviteStateSchema,
|
|
4404
|
+
recovery_of: NonEmptyStringSchema.optional(),
|
|
4405
|
+
recovery_confirmed: external_exports.boolean().optional(),
|
|
4406
|
+
created_at: Rfc3339Schema,
|
|
4407
|
+
replaces_seat: LowerCrockfordUlidSchema.optional()
|
|
4408
|
+
}).strict().superRefine((invite, context) => {
|
|
4409
|
+
if (invite.mode === "one_time" && invite.min_accepts !== 1) {
|
|
4410
|
+
context.addIssue({
|
|
4411
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4412
|
+
path: ["min_accepts"],
|
|
4413
|
+
message: "one_time invites require min_accepts === 1"
|
|
4414
|
+
});
|
|
4415
|
+
}
|
|
4416
|
+
if (invite.state === "receipt_pending" && invite.recovery_of === void 0) {
|
|
4417
|
+
context.addIssue({
|
|
4418
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4419
|
+
path: ["recovery_of"],
|
|
4420
|
+
message: "receipt_pending invites require recovery_of"
|
|
4421
|
+
});
|
|
4422
|
+
}
|
|
4423
|
+
if (invite.recovery_of === void 0 && invite.recovery_confirmed !== void 0) {
|
|
4424
|
+
context.addIssue({
|
|
4425
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4426
|
+
path: ["recovery_confirmed"],
|
|
4427
|
+
message: "recovery_confirmed is forbidden without recovery_of"
|
|
4428
|
+
});
|
|
4429
|
+
}
|
|
4430
|
+
if (invite.recovery_of !== void 0 && invite.recovery_confirmed === void 0) {
|
|
4431
|
+
context.addIssue({
|
|
4432
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4433
|
+
path: ["recovery_confirmed"],
|
|
4434
|
+
message: "recovery_confirmed is required with recovery_of"
|
|
4435
|
+
});
|
|
4436
|
+
}
|
|
4437
|
+
if (invite.state === "receipt_pending" && invite.recovery_confirmed !== false) {
|
|
4438
|
+
context.addIssue({
|
|
4439
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4440
|
+
path: ["recovery_confirmed"],
|
|
4441
|
+
message: "receipt_pending recovery lineage must be unconfirmed"
|
|
4442
|
+
});
|
|
4443
|
+
}
|
|
4444
|
+
if (invite.recovery_of !== void 0 && (invite.state === "live" || invite.state === "consumed" || invite.state === "replacement_required") && invite.recovery_confirmed !== true) {
|
|
4445
|
+
context.addIssue({
|
|
4446
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4447
|
+
path: ["recovery_confirmed"],
|
|
4448
|
+
message: "live, consumed, and replacement_required recovery lineage must be confirmed"
|
|
4449
|
+
});
|
|
4450
|
+
}
|
|
4451
|
+
if (invite.state === "receipt_pending" && invite.accepted_cids.length > 0) {
|
|
4452
|
+
context.addIssue({
|
|
4453
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4454
|
+
path: ["accepted_cids"],
|
|
4455
|
+
message: "receipt_pending invites cannot have accepted CIDs"
|
|
4456
|
+
});
|
|
4457
|
+
}
|
|
4458
|
+
});
|
|
4459
|
+
var MissionV1Schema = external_exports.object({
|
|
4460
|
+
goal: MissionTextSchema,
|
|
4461
|
+
briefing: MissionTextSchema
|
|
4462
|
+
}).strict();
|
|
4463
|
+
var MissionSchema = external_exports.object({
|
|
4464
|
+
goal: MissionTextSchema,
|
|
4465
|
+
briefing: MissionTextSchema,
|
|
4466
|
+
briefing_version: PositiveSafeIntegerSchema
|
|
4467
|
+
}).strict();
|
|
4468
|
+
var RoleBriefingSchema = external_exports.object({
|
|
4469
|
+
text: MissionTextSchema,
|
|
4470
|
+
version: PositiveSafeIntegerSchema,
|
|
4471
|
+
updated_at: Rfc3339Schema
|
|
4472
|
+
}).strict();
|
|
4473
|
+
function refineRoomLineage(room, context) {
|
|
4474
|
+
const pendingIdentityName = `cowork-room-${room.room_id}`;
|
|
4475
|
+
const exactPacketPending = room.state === "provisioning" && room.status === "packet_pending" && room.identity_name === pendingIdentityName && room.invites.length === 0 && room.seats.length === 0 && room.activated_at === void 0 && room.closed_at === void 0;
|
|
4476
|
+
if (room.identity_cid === "" && !exactPacketPending) {
|
|
4477
|
+
context.addIssue({
|
|
4478
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4479
|
+
path: ["identity_cid"],
|
|
4480
|
+
message: "empty identity_cid is reserved for the exact packet_pending provisioning sentinel"
|
|
4481
|
+
});
|
|
4482
|
+
}
|
|
4483
|
+
if (room.identity_cid !== "" && room.status === "packet_pending") {
|
|
4484
|
+
context.addIssue({
|
|
4485
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4486
|
+
path: ["status"],
|
|
4487
|
+
message: "packet_pending status requires an empty identity_cid"
|
|
4488
|
+
});
|
|
4489
|
+
}
|
|
4490
|
+
const pendingByRecovery = /* @__PURE__ */ new Map();
|
|
4491
|
+
for (const [index, invite] of room.invites.entries()) {
|
|
4492
|
+
if (invite.recovery_of === void 0) continue;
|
|
4493
|
+
const recoveryOf = invite.recovery_of;
|
|
4494
|
+
const source = room.invites.find((candidate) => candidate.invite_id === recoveryOf);
|
|
4495
|
+
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;
|
|
4496
|
+
if (!source || source.invite_id === invite.invite_id || !validSourceState) {
|
|
4497
|
+
context.addIssue({
|
|
4498
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4499
|
+
path: ["invites", index, "recovery_of"],
|
|
4500
|
+
message: "recovery_of must point to a source invite in the state required by this recovery lineage"
|
|
4501
|
+
});
|
|
4502
|
+
} else if (invite.mode !== source.mode || invite.role !== source.role || invite.min_accepts !== source.min_accepts) {
|
|
4503
|
+
context.addIssue({
|
|
4504
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4505
|
+
path: ["invites", index],
|
|
4506
|
+
message: "receipt_pending descriptor must copy source mode, role, and min_accepts"
|
|
4507
|
+
});
|
|
4508
|
+
}
|
|
4509
|
+
if (invite.state === "receipt_pending") {
|
|
4510
|
+
const count = (pendingByRecovery.get(recoveryOf) ?? 0) + 1;
|
|
4511
|
+
pendingByRecovery.set(recoveryOf, count);
|
|
4512
|
+
if (count > 1) {
|
|
4513
|
+
context.addIssue({
|
|
4514
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4515
|
+
path: ["invites", index, "recovery_of"],
|
|
4516
|
+
message: "only one receipt_pending invite may exist per recovery_of pointer"
|
|
4517
|
+
});
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
var RoomCommonShape = {
|
|
4523
|
+
room_id: LowerCrockfordUlidSchema,
|
|
4524
|
+
identity_name: NonEmptyStringSchema,
|
|
4525
|
+
identity_cid: external_exports.string(),
|
|
4526
|
+
state: RoomStateSchema,
|
|
4527
|
+
status: NonEmptyStringSchema.optional(),
|
|
4528
|
+
invites: external_exports.array(RoomInviteSchema),
|
|
4529
|
+
created_at: Rfc3339Schema,
|
|
4530
|
+
activated_at: Rfc3339Schema.optional(),
|
|
4531
|
+
closed_at: Rfc3339Schema.optional()
|
|
4532
|
+
};
|
|
4533
|
+
var RoomV1Schema = external_exports.object({
|
|
4534
|
+
...RoomCommonShape,
|
|
4535
|
+
version: external_exports.literal(1),
|
|
4536
|
+
mission: MissionV1Schema,
|
|
4537
|
+
seats: external_exports.array(SeatV1Schema)
|
|
4538
|
+
}).strict().superRefine(refineRoomLineage);
|
|
4539
|
+
var CurrentRoomSchema = external_exports.object({
|
|
4540
|
+
...RoomCommonShape,
|
|
4541
|
+
room_name: RoomNameSchema,
|
|
4542
|
+
version: external_exports.literal(2),
|
|
4543
|
+
mission: MissionSchema,
|
|
4544
|
+
role_briefings: external_exports.record(RoleSchema, RoleBriefingSchema),
|
|
4545
|
+
anonymous: external_exports.boolean(),
|
|
4546
|
+
quiet_membership: external_exports.boolean(),
|
|
4547
|
+
membership_epoch: external_exports.number().int().nonnegative().safe(),
|
|
4548
|
+
seats: external_exports.array(SeatSchema)
|
|
4549
|
+
}).strict().superRefine((room, context) => {
|
|
4550
|
+
refineRoomLineage(room, context);
|
|
4551
|
+
const byParticipant = /* @__PURE__ */ new Map();
|
|
4552
|
+
const activeAliases = /* @__PURE__ */ new Set();
|
|
4553
|
+
for (const [index, seat] of room.seats.entries()) {
|
|
4554
|
+
if (byParticipant.has(seat.participant_id)) {
|
|
4555
|
+
context.addIssue({
|
|
4556
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4557
|
+
path: ["seats", index, "participant_id"],
|
|
4558
|
+
message: "participant_id must be unique within the room"
|
|
4559
|
+
});
|
|
4560
|
+
}
|
|
4561
|
+
byParticipant.set(seat.participant_id, seat);
|
|
4562
|
+
if (room.anonymous && seat.alias === void 0) {
|
|
4563
|
+
context.addIssue({
|
|
4564
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4565
|
+
path: ["seats", index, "alias"],
|
|
4566
|
+
message: "anonymous rooms require an alias on every seat"
|
|
4567
|
+
});
|
|
4568
|
+
}
|
|
4569
|
+
if (!room.anonymous && seat.alias !== void 0) {
|
|
4570
|
+
context.addIssue({
|
|
4571
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4572
|
+
path: ["seats", index, "alias"],
|
|
4573
|
+
message: "aliases are reserved for anonymous rooms"
|
|
4574
|
+
});
|
|
4575
|
+
}
|
|
4576
|
+
if (seat.state === "active" && seat.alias !== void 0) {
|
|
4577
|
+
if (activeAliases.has(seat.alias)) {
|
|
4578
|
+
context.addIssue({
|
|
4579
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4580
|
+
path: ["seats", index, "alias"],
|
|
4581
|
+
message: "active seats must hold distinct aliases"
|
|
4582
|
+
});
|
|
4583
|
+
}
|
|
4584
|
+
activeAliases.add(seat.alias);
|
|
4585
|
+
}
|
|
4586
|
+
if (seat.removed_epoch !== void 0 && seat.removed_epoch > room.membership_epoch) {
|
|
4587
|
+
context.addIssue({
|
|
4588
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4589
|
+
path: ["seats", index, "removed_epoch"],
|
|
4590
|
+
message: "removed_epoch cannot exceed the room membership_epoch"
|
|
4591
|
+
});
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
for (const [index, seat] of room.seats.entries()) {
|
|
4595
|
+
if (seat.replaces_seat === void 0) continue;
|
|
4596
|
+
const predecessor = byParticipant.get(seat.replaces_seat);
|
|
4597
|
+
if (!predecessor || predecessor === seat || predecessor.state !== "removed") {
|
|
4598
|
+
context.addIssue({
|
|
4599
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4600
|
+
path: ["seats", index, "replaces_seat"],
|
|
4601
|
+
message: "replaces_seat must reference a removed seat in this room"
|
|
4602
|
+
});
|
|
4603
|
+
continue;
|
|
4604
|
+
}
|
|
4605
|
+
if (predecessor.role !== seat.role) {
|
|
4606
|
+
context.addIssue({
|
|
4607
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4608
|
+
path: ["seats", index, "role"],
|
|
4609
|
+
message: "a replacement seat must inherit the predecessor role"
|
|
4610
|
+
});
|
|
4611
|
+
}
|
|
4612
|
+
if (room.anonymous && seat.alias !== predecessor.alias) {
|
|
4613
|
+
context.addIssue({
|
|
4614
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4615
|
+
path: ["seats", index, "alias"],
|
|
4616
|
+
message: "an anonymous replacement seat must inherit the predecessor alias"
|
|
4617
|
+
});
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
});
|
|
4621
|
+
function defaultRoomName(roomId) {
|
|
4622
|
+
return `Room ${LowerCrockfordUlidSchema.parse(roomId).slice(0, 8)}`;
|
|
4623
|
+
}
|
|
4624
|
+
var RoomSchema = external_exports.preprocess((value) => {
|
|
4625
|
+
if (typeof value !== "object" || value === null || Object.hasOwn(value, "room_name")) return value;
|
|
4626
|
+
const roomId = value.room_id;
|
|
4627
|
+
if (typeof roomId !== "string" || !LowerCrockfordUlidSchema.safeParse(roomId).success) return value;
|
|
4628
|
+
return { ...value, room_name: defaultRoomName(roomId) };
|
|
4629
|
+
}, CurrentRoomSchema);
|
|
4630
|
+
var CreateRoomInputSchema = external_exports.object({
|
|
4631
|
+
name: RoomNameSchema.optional(),
|
|
4632
|
+
goal: MissionTextSchema,
|
|
4633
|
+
briefing: MissionTextSchema,
|
|
4634
|
+
anonymous: external_exports.boolean().optional(),
|
|
4635
|
+
quiet_membership: external_exports.boolean().optional()
|
|
4636
|
+
}).strict();
|
|
4637
|
+
var UpdateRoomInputSchema = external_exports.object({
|
|
4638
|
+
name: RoomNameSchema.optional(),
|
|
4639
|
+
goal: MissionTextSchema.optional(),
|
|
4640
|
+
briefing: MissionTextSchema.optional(),
|
|
4641
|
+
status: NonEmptyStringSchema.optional(),
|
|
4642
|
+
quiet_membership: external_exports.boolean().optional()
|
|
4643
|
+
}).strict().refine((input) => Object.keys(input).length > 0, "at least one setting is required");
|
|
4644
|
+
var RoleBriefingSetInputSchema = external_exports.object({
|
|
4645
|
+
role: RoleSchema,
|
|
4646
|
+
text: MissionTextSchema
|
|
4647
|
+
}).strict();
|
|
4648
|
+
var RoleBriefingDeleteInputSchema = external_exports.object({
|
|
4649
|
+
role: RoleSchema
|
|
4650
|
+
}).strict();
|
|
4651
|
+
var PostMessageInputSchema = external_exports.object({
|
|
4652
|
+
text: MessageTextSchema
|
|
4653
|
+
}).strict();
|
|
4654
|
+
var AuthorSnapshotSchema = external_exports.object({
|
|
4655
|
+
identity: NonEmptyStringSchema,
|
|
4656
|
+
display_name: NonEmptyStringSchema,
|
|
4657
|
+
role: RoleSchema
|
|
4658
|
+
}).strict();
|
|
4659
|
+
var RecordCommonShape = {
|
|
4660
|
+
version: external_exports.literal(1),
|
|
4661
|
+
room_id: LowerCrockfordUlidSchema,
|
|
4662
|
+
seq: PositiveSafeIntegerSchema,
|
|
4663
|
+
record_id: NonEmptyStringSchema,
|
|
4664
|
+
at: Rfc3339Schema
|
|
4665
|
+
};
|
|
4666
|
+
var AppendCommonShape = {
|
|
4667
|
+
version: external_exports.literal(1),
|
|
4668
|
+
room_id: LowerCrockfordUlidSchema,
|
|
4669
|
+
at: Rfc3339Schema
|
|
4670
|
+
};
|
|
4671
|
+
var MembershipNoticeSchema = external_exports.object({
|
|
4672
|
+
action: external_exports.enum(["remove"]),
|
|
4673
|
+
alias: NonEmptyStringSchema.optional(),
|
|
4674
|
+
role: RoleSchema.optional(),
|
|
4675
|
+
epoch: external_exports.number().int().nonnegative().safe()
|
|
4676
|
+
}).strict();
|
|
4677
|
+
var AuthorAliasSchema = external_exports.object({
|
|
4678
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
4679
|
+
alias: NonEmptyStringSchema
|
|
4680
|
+
}).strict();
|
|
4681
|
+
var MessageShape = {
|
|
4682
|
+
kind: external_exports.literal("message"),
|
|
4683
|
+
message_id: LowerCrockfordUlidSchema,
|
|
4684
|
+
author: AuthorSnapshotSchema,
|
|
4685
|
+
author_alias: AuthorAliasSchema.optional(),
|
|
4686
|
+
category: external_exports.enum(["briefing", "role_briefing", "chat", "membership"]),
|
|
4687
|
+
briefing_role: RoleSchema.optional(),
|
|
4688
|
+
briefing_version: PositiveSafeIntegerSchema.optional(),
|
|
4689
|
+
membership: MembershipNoticeSchema.optional(),
|
|
4690
|
+
text: MessageTextSchema,
|
|
4691
|
+
recipient_identities: external_exports.array(NonEmptyStringSchema).superRefine((identities, context) => {
|
|
4692
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4693
|
+
for (const [index, identity] of identities.entries()) {
|
|
4694
|
+
if (seen.has(identity)) {
|
|
4695
|
+
context.addIssue({
|
|
4696
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4697
|
+
path: [index],
|
|
4698
|
+
message: "recipient identities must be unique"
|
|
4699
|
+
});
|
|
4700
|
+
}
|
|
4701
|
+
seen.add(identity);
|
|
4702
|
+
}
|
|
4703
|
+
}),
|
|
4704
|
+
source_msg_id: external_exports.number().int().nonnegative().safe().optional(),
|
|
4705
|
+
source_wire_id: NonEmptyStringSchema.optional()
|
|
4706
|
+
};
|
|
4707
|
+
var RelayIntentShape = {
|
|
4708
|
+
kind: external_exports.literal("relay_intent"),
|
|
4709
|
+
message_id: LowerCrockfordUlidSchema.optional(),
|
|
4710
|
+
file_id: LowerCrockfordUlidSchema.optional(),
|
|
4711
|
+
recipient_identity: NonEmptyStringSchema
|
|
4712
|
+
};
|
|
4713
|
+
var RelayResultStatusSchema = external_exports.enum(["queued", "send_failed", "skipped_removed"]);
|
|
4714
|
+
var RelayResultShape = {
|
|
4715
|
+
kind: external_exports.literal("relay_result"),
|
|
4716
|
+
intent_record_id: NonEmptyStringSchema,
|
|
4717
|
+
message_id: LowerCrockfordUlidSchema.optional(),
|
|
4718
|
+
file_id: LowerCrockfordUlidSchema.optional(),
|
|
4719
|
+
recipient_identity: NonEmptyStringSchema,
|
|
4720
|
+
status: RelayResultStatusSchema,
|
|
4721
|
+
wire_id: NonEmptyStringSchema.optional(),
|
|
4722
|
+
metadata_wire_id: NonEmptyStringSchema.optional()
|
|
4723
|
+
};
|
|
4724
|
+
var FileShape = {
|
|
4725
|
+
kind: external_exports.literal("file"),
|
|
4726
|
+
file_id: LowerCrockfordUlidSchema,
|
|
4727
|
+
author: AuthorSnapshotSchema,
|
|
4728
|
+
author_alias: AuthorAliasSchema.optional(),
|
|
4729
|
+
filename: FileNameSchema,
|
|
4730
|
+
mime: FileMimeSchema,
|
|
4731
|
+
size: external_exports.number().int().nonnegative().max(MAX_FILE_BYTES),
|
|
4732
|
+
sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
4733
|
+
data_base64: external_exports.string(),
|
|
4734
|
+
recipient_identities: external_exports.array(NonEmptyStringSchema).superRefine((identities, context) => {
|
|
4735
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4736
|
+
for (const [index, identity] of identities.entries()) {
|
|
4737
|
+
if (seen.has(identity)) {
|
|
4738
|
+
context.addIssue({
|
|
4739
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4740
|
+
path: [index],
|
|
4741
|
+
message: "recipient identities must be unique"
|
|
4742
|
+
});
|
|
4743
|
+
}
|
|
4744
|
+
seen.add(identity);
|
|
4745
|
+
}
|
|
4746
|
+
}),
|
|
4747
|
+
source_file_id: external_exports.number().int().nonnegative().safe(),
|
|
4748
|
+
source_wire_id: NonEmptyStringSchema.optional()
|
|
4749
|
+
};
|
|
4750
|
+
var MembershipIntentShape = {
|
|
4751
|
+
kind: external_exports.literal("membership_intent"),
|
|
4752
|
+
action: external_exports.enum(["remove"]),
|
|
4753
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
4754
|
+
recipient_identity: NonEmptyStringSchema,
|
|
4755
|
+
role: RoleSchema,
|
|
4756
|
+
alias: NonEmptyStringSchema.optional(),
|
|
4757
|
+
epoch: PositiveSafeIntegerSchema,
|
|
4758
|
+
notify: external_exports.boolean()
|
|
4759
|
+
};
|
|
4760
|
+
var MembershipResultShape = {
|
|
4761
|
+
kind: external_exports.literal("membership_result"),
|
|
4762
|
+
intent_record_id: NonEmptyStringSchema,
|
|
4763
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
4764
|
+
status: RelayStatusSchema,
|
|
4765
|
+
notified: external_exports.boolean(),
|
|
4766
|
+
key_material_retained: external_exports.literal(true),
|
|
4767
|
+
uncertain_after_restart: external_exports.literal(true).optional()
|
|
4768
|
+
};
|
|
4769
|
+
var CloseNoticeIntentShape = {
|
|
4770
|
+
kind: external_exports.literal("close_notice_intent"),
|
|
4771
|
+
recipient_identity: NonEmptyStringSchema
|
|
4772
|
+
};
|
|
4773
|
+
var CloseNoticeResultShape = {
|
|
4774
|
+
kind: external_exports.literal("close_notice_result"),
|
|
4775
|
+
intent_record_id: NonEmptyStringSchema,
|
|
4776
|
+
recipient_identity: NonEmptyStringSchema,
|
|
4777
|
+
status: RelayStatusSchema,
|
|
4778
|
+
notified: external_exports.boolean(),
|
|
4779
|
+
key_material_retained: external_exports.literal(true),
|
|
4780
|
+
uncertain_after_restart: external_exports.literal(true).optional()
|
|
4781
|
+
};
|
|
4782
|
+
var MessageRecordSchema = external_exports.object({ ...RecordCommonShape, ...MessageShape }).strict();
|
|
4783
|
+
var FileRecordSchema = external_exports.object({ ...RecordCommonShape, ...FileShape }).strict();
|
|
4784
|
+
var RelayIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...RelayIntentShape }).strict();
|
|
4785
|
+
var RelayResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...RelayResultShape }).strict();
|
|
4786
|
+
var MembershipIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...MembershipIntentShape }).strict();
|
|
4787
|
+
var MembershipResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...MembershipResultShape }).strict();
|
|
4788
|
+
var CloseNoticeIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...CloseNoticeIntentShape }).strict();
|
|
4789
|
+
var CloseNoticeResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...CloseNoticeResultShape }).strict();
|
|
4790
|
+
var RawCommunicationRecordSchema = external_exports.discriminatedUnion("kind", [
|
|
4791
|
+
MessageRecordSchema,
|
|
4792
|
+
FileRecordSchema,
|
|
4793
|
+
RelayIntentRecordSchema,
|
|
4794
|
+
RelayResultRecordSchema,
|
|
4795
|
+
MembershipIntentRecordSchema,
|
|
4796
|
+
MembershipResultRecordSchema,
|
|
4797
|
+
CloseNoticeIntentRecordSchema,
|
|
4798
|
+
CloseNoticeResultRecordSchema
|
|
4799
|
+
]);
|
|
4800
|
+
function refineRelaySubject(record, context) {
|
|
4801
|
+
if (record.kind !== "relay_intent" && record.kind !== "relay_result") return;
|
|
4802
|
+
if (record.message_id === void 0 === (record.file_id === void 0)) {
|
|
4803
|
+
context.addIssue({
|
|
4804
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4805
|
+
path: ["message_id"],
|
|
4806
|
+
message: "relay records require exactly one of message_id or file_id"
|
|
4807
|
+
});
|
|
4808
|
+
}
|
|
4809
|
+
}
|
|
4810
|
+
function refineFileRecord(record, context) {
|
|
4811
|
+
if (record.kind !== "file" || record.data_base64 === void 0) return;
|
|
4812
|
+
const bytes = Buffer.from(record.data_base64, "base64");
|
|
4813
|
+
if (bytes.toString("base64") !== record.data_base64) {
|
|
4814
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["data_base64"], message: "file bytes must use canonical base64" });
|
|
4815
|
+
}
|
|
4816
|
+
if (bytes.length !== record.size) {
|
|
4817
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["size"], message: "file size must match decoded bytes" });
|
|
4818
|
+
}
|
|
4819
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
4820
|
+
if (digest !== record.sha256) {
|
|
4821
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["sha256"], message: "file sha256 must match decoded bytes" });
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
function refineMessageCategory(message, context) {
|
|
4825
|
+
const requires = (field, present) => {
|
|
4826
|
+
if (present && message[field] === void 0) {
|
|
4827
|
+
context.addIssue({
|
|
4828
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4829
|
+
path: [field],
|
|
4830
|
+
message: `${message.category} messages require ${field}`
|
|
4831
|
+
});
|
|
4832
|
+
}
|
|
4833
|
+
if (!present && message[field] !== void 0) {
|
|
4834
|
+
context.addIssue({
|
|
4835
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4836
|
+
path: [field],
|
|
4837
|
+
message: `${field} is forbidden on ${message.category} messages`
|
|
4838
|
+
});
|
|
4839
|
+
}
|
|
4840
|
+
};
|
|
4841
|
+
requires("briefing_role", message.category === "role_briefing");
|
|
4842
|
+
requires("membership", message.category === "membership");
|
|
4843
|
+
if (message.category === "role_briefing" && message.briefing_version === void 0) {
|
|
4844
|
+
context.addIssue({
|
|
4845
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4846
|
+
path: ["briefing_version"],
|
|
4847
|
+
message: "role_briefing messages require briefing_version"
|
|
4848
|
+
});
|
|
4849
|
+
}
|
|
4850
|
+
if (message.category === "chat" || message.category === "membership") {
|
|
4851
|
+
if (message.briefing_version !== void 0) {
|
|
4852
|
+
context.addIssue({
|
|
4853
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4854
|
+
path: ["briefing_version"],
|
|
4855
|
+
message: `briefing_version is forbidden on ${message.category} messages`
|
|
4856
|
+
});
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
var CommunicationRecordSchema = RawCommunicationRecordSchema.superRefine((record, context) => {
|
|
4861
|
+
if (record.record_id !== `${record.room_id}:${record.seq}`) {
|
|
4862
|
+
context.addIssue({
|
|
4863
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4864
|
+
path: ["record_id"],
|
|
4865
|
+
message: 'record_id must equal room_id + ":" + seq'
|
|
4866
|
+
});
|
|
4867
|
+
}
|
|
4868
|
+
if (record.kind === "message") refineMessageCategory(record, context);
|
|
4869
|
+
refineRelaySubject(record, context);
|
|
4870
|
+
refineFileRecord(record, context);
|
|
4871
|
+
});
|
|
4872
|
+
var AppendRecordSchema = external_exports.discriminatedUnion("kind", [
|
|
4873
|
+
external_exports.object({ ...AppendCommonShape, ...MessageShape }).strict(),
|
|
4874
|
+
external_exports.object({ ...AppendCommonShape, ...FileShape }).strict(),
|
|
4875
|
+
external_exports.object({ ...AppendCommonShape, ...RelayIntentShape }).strict(),
|
|
4876
|
+
external_exports.object({ ...AppendCommonShape, ...RelayResultShape }).strict(),
|
|
4877
|
+
external_exports.object({ ...AppendCommonShape, ...MembershipIntentShape }).strict(),
|
|
4878
|
+
external_exports.object({ ...AppendCommonShape, ...MembershipResultShape }).strict(),
|
|
4879
|
+
external_exports.object({ ...AppendCommonShape, ...CloseNoticeIntentShape }).strict(),
|
|
4880
|
+
external_exports.object({ ...AppendCommonShape, ...CloseNoticeResultShape }).strict()
|
|
4881
|
+
]).superRefine((record, context) => {
|
|
4882
|
+
if (record.kind === "message") refineMessageCategory(record, context);
|
|
4883
|
+
refineRelaySubject(record, context);
|
|
4884
|
+
refineFileRecord(record, context);
|
|
4885
|
+
});
|
|
4886
|
+
|
|
4274
4887
|
// src/cli.ts
|
|
4275
4888
|
var EXIT = {
|
|
4276
4889
|
success: 0,
|
|
@@ -4287,7 +4900,6 @@ var NATIVE_RPC_TIMEOUT_MS = 12e4;
|
|
|
4287
4900
|
var START_TIMEOUT_MS = 3e4;
|
|
4288
4901
|
var WEB_READY_TIMEOUT_MS = 3e4;
|
|
4289
4902
|
var STOP_TIMEOUT_MS = 12e3;
|
|
4290
|
-
var MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
4291
4903
|
var SELF = fileURLToPath(import.meta.url);
|
|
4292
4904
|
var SYSTEMD_UNIT = "ours-cowork.service";
|
|
4293
4905
|
var LAUNCHD_LABEL = "network.ours.cowork";
|
|
@@ -4351,8 +4963,8 @@ Usage:
|
|
|
4351
4963
|
ours-cowork [--json] docs [topic]
|
|
4352
4964
|
|
|
4353
4965
|
Room commands:
|
|
4354
|
-
create --goal <text> --briefing <text> [--anonymous] [--quiet-membership]
|
|
4355
|
-
settings <room-id> [--goal <text>] [--briefing <text>] [--status <text>] [--quiet-membership true|false]
|
|
4966
|
+
create [--name <display-name>] --goal <text> --briefing <text> [--anonymous] [--quiet-membership]
|
|
4967
|
+
settings <room-id> [--name <display-name>] [--goal <text>] [--briefing <text>] [--status <text>] [--quiet-membership true|false]
|
|
4356
4968
|
role-briefing <room-id> --role <label> (--text <text> | --delete)
|
|
4357
4969
|
invite <room-id> [--role <label>] [--mode one_time|public] [--min-accepts <n>]
|
|
4358
4970
|
revoke <room-id> <invite-id>
|
|
@@ -4441,9 +5053,10 @@ function roomRequest(command, args) {
|
|
|
4441
5053
|
if (!command) usageError("room requires a command");
|
|
4442
5054
|
switch (command) {
|
|
4443
5055
|
case "create": {
|
|
4444
|
-
const parsed = parseOptions(args, ["--goal", "--briefing"], ["--anonymous", "--quiet-membership"]);
|
|
5056
|
+
const parsed = parseOptions(args, ["--name", "--goal", "--briefing"], ["--anonymous", "--quiet-membership"]);
|
|
4445
5057
|
exactPositionals(parsed, 0, "room create");
|
|
4446
5058
|
return { method: "room.create", params: {
|
|
5059
|
+
...parsed.values["--name"] === void 0 ? {} : { name: parsed.values["--name"] },
|
|
4447
5060
|
goal: requiredFlag(parsed, "--goal", "room create"),
|
|
4448
5061
|
briefing: requiredFlag(parsed, "--briefing", "room create"),
|
|
4449
5062
|
...parsed.booleans.has("--anonymous") ? { anonymous: true } : {},
|
|
@@ -4451,10 +5064,10 @@ function roomRequest(command, args) {
|
|
|
4451
5064
|
} };
|
|
4452
5065
|
}
|
|
4453
5066
|
case "settings": {
|
|
4454
|
-
const parsed = parseOptions(args, ["--goal", "--briefing", "--status", "--quiet-membership"]);
|
|
5067
|
+
const parsed = parseOptions(args, ["--name", "--goal", "--briefing", "--status", "--quiet-membership"]);
|
|
4455
5068
|
const [roomId] = exactPositionals(parsed, 1, "room settings");
|
|
4456
5069
|
const params = { room_id: roomId };
|
|
4457
|
-
for (const [flag, key] of [["--goal", "goal"], ["--briefing", "briefing"], ["--status", "status"]]) {
|
|
5070
|
+
for (const [flag, key] of [["--name", "name"], ["--goal", "goal"], ["--briefing", "briefing"], ["--status", "status"]]) {
|
|
4458
5071
|
if (parsed.values[flag] !== void 0) params[key] = parsed.values[flag];
|
|
4459
5072
|
}
|
|
4460
5073
|
const quiet = parsed.values["--quiet-membership"];
|
|
@@ -4604,8 +5217,8 @@ function rpcCall(socketPath, method, params, timeoutMs = RPC_TIMEOUT_MS) {
|
|
|
4604
5217
|
socket.on("data", (chunk) => {
|
|
4605
5218
|
if (settled) return;
|
|
4606
5219
|
size += Buffer.byteLength(chunk, "utf8");
|
|
4607
|
-
if (size >
|
|
4608
|
-
finishError(new CliError(EXIT.internal, "internal", "daemon response exceeded
|
|
5220
|
+
if (size > MAX_MANAGEMENT_RESPONSE_BYTES) {
|
|
5221
|
+
finishError(new CliError(EXIT.internal, "internal", "daemon response exceeded 4 MiB"));
|
|
4609
5222
|
return;
|
|
4610
5223
|
}
|
|
4611
5224
|
bytes += chunk;
|
|
@@ -4741,13 +5354,11 @@ function httpGetReady(url, timeoutMs) {
|
|
|
4741
5354
|
if (deadline) clearTimeout(deadline);
|
|
4742
5355
|
deadline = void 0;
|
|
4743
5356
|
request.removeListener("response", onResponse);
|
|
4744
|
-
request.removeListener("error", onRequestError);
|
|
4745
5357
|
request.removeListener("timeout", onRequestTimeout);
|
|
4746
5358
|
request.removeListener("close", onRequestClose);
|
|
4747
5359
|
request.setTimeout(0);
|
|
4748
5360
|
if (response) {
|
|
4749
5361
|
response.removeListener("end", onResponseEnd);
|
|
4750
|
-
response.removeListener("error", onResponseError);
|
|
4751
5362
|
response.removeListener("aborted", onResponseAborted);
|
|
4752
5363
|
response.removeListener("close", onResponseClose);
|
|
4753
5364
|
if (!response.destroyed) response.destroy();
|
|
@@ -4771,14 +5382,14 @@ function httpGetReady(url, timeoutMs) {
|
|
|
4771
5382
|
}
|
|
4772
5383
|
response = incoming;
|
|
4773
5384
|
incoming.once("end", onResponseEnd);
|
|
4774
|
-
incoming.
|
|
5385
|
+
incoming.on("error", onResponseError);
|
|
4775
5386
|
incoming.once("aborted", onResponseAborted);
|
|
4776
5387
|
incoming.once("close", onResponseClose);
|
|
4777
5388
|
incoming.resume();
|
|
4778
5389
|
};
|
|
4779
5390
|
const request = http.get(url);
|
|
4780
5391
|
request.once("response", onResponse);
|
|
4781
|
-
request.
|
|
5392
|
+
request.on("error", onRequestError);
|
|
4782
5393
|
request.once("timeout", onRequestTimeout);
|
|
4783
5394
|
request.once("close", onRequestClose);
|
|
4784
5395
|
request.setTimeout(timeoutMs);
|
|
@@ -4994,6 +5605,35 @@ function readDocs(topic) {
|
|
|
4994
5605
|
throw new CliError(EXIT.internal, "internal", `offline documentation is missing: ${file}`, { cause: error });
|
|
4995
5606
|
}
|
|
4996
5607
|
}
|
|
5608
|
+
async function readHistoryPages(socketPath, params) {
|
|
5609
|
+
const requested = typeof params.limit === "number" ? params.limit : Number.MAX_SAFE_INTEGER;
|
|
5610
|
+
let after = typeof params.after === "number" ? params.after : 0;
|
|
5611
|
+
const records = [];
|
|
5612
|
+
while (records.length < requested) {
|
|
5613
|
+
const remaining = requested - records.length;
|
|
5614
|
+
const pageParams = { ...params, after, ...remaining < Number.MAX_SAFE_INTEGER ? { limit: remaining } : {} };
|
|
5615
|
+
const page = await rpcCall(
|
|
5616
|
+
socketPath,
|
|
5617
|
+
"room.history",
|
|
5618
|
+
pageParams,
|
|
5619
|
+
rpcTimeoutForMethod("room.history")
|
|
5620
|
+
);
|
|
5621
|
+
if (!Array.isArray(page)) {
|
|
5622
|
+
throw new CliError(EXIT.internal, "internal", "daemon returned an invalid history page");
|
|
5623
|
+
}
|
|
5624
|
+
if (page.length === 0) break;
|
|
5625
|
+
for (const record of page) {
|
|
5626
|
+
const seq = record !== null && typeof record === "object" ? record.seq : void 0;
|
|
5627
|
+
if (typeof seq !== "number" || !Number.isSafeInteger(seq) || seq <= after) {
|
|
5628
|
+
throw new CliError(EXIT.internal, "internal", "daemon returned a non-progressing history page");
|
|
5629
|
+
}
|
|
5630
|
+
after = seq;
|
|
5631
|
+
records.push(record);
|
|
5632
|
+
if (records.length === requested) break;
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
return records;
|
|
5636
|
+
}
|
|
4997
5637
|
async function execute(args, output) {
|
|
4998
5638
|
const command = args[0] ?? "help";
|
|
4999
5639
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
@@ -5010,8 +5650,9 @@ async function execute(args, output) {
|
|
|
5010
5650
|
if (command === "room") {
|
|
5011
5651
|
const request = roomRequest(args[1], args.slice(2));
|
|
5012
5652
|
const config2 = loadCliConfig();
|
|
5013
|
-
const
|
|
5014
|
-
|
|
5653
|
+
const socketPath = join2(config2.stateDir, "management.sock");
|
|
5654
|
+
const result = request.method === "room.history" ? await readHistoryPages(socketPath, request.params) : await rpcCall(
|
|
5655
|
+
socketPath,
|
|
5015
5656
|
request.method,
|
|
5016
5657
|
request.params,
|
|
5017
5658
|
rpcTimeoutForMethod(request.method)
|