@ours.network/cowork 0.2.0 → 0.3.1
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 +6 -0
- package/dist/cli.js +685 -19
- package/dist/daemon.js +1551 -474
- package/dist/mufl_code/{9D2DA4CD06758ABFBC52B9C5692A2E30249A3C12F48B6F45F193B22E447F0342.muflo → BBAE58CF78DEE59692F456EAFFA9A6109835B66846FC3989F6D201B8F4523A55.muflo} +0 -0
- package/dist/web/assets/app.js +12 -12
- package/docs/07-messaging-history.md +10 -2
- package/docs/10-limitations.md +3 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4271,6 +4271,591 @@ 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
|
+
function utf8Bounded(label, maximumBytes) {
|
|
4284
|
+
return external_exports.string().refine((value) => Buffer.byteLength(value, "utf8") >= 1, `${label} must be at least 1 UTF-8 byte`).refine(
|
|
4285
|
+
(value) => Buffer.byteLength(value, "utf8") <= maximumBytes,
|
|
4286
|
+
`${label} must be at most ${maximumBytes} UTF-8 bytes`
|
|
4287
|
+
);
|
|
4288
|
+
}
|
|
4289
|
+
var NonEmptyStringSchema = external_exports.string().min(1);
|
|
4290
|
+
var PositiveSafeIntegerSchema = external_exports.number().int().positive().safe();
|
|
4291
|
+
var LowerCrockfordUlidSchema = external_exports.string().regex(
|
|
4292
|
+
/^[0-7][0-9a-hjkmnp-tv-z]{25}$/,
|
|
4293
|
+
"must be a 26-character lowercase Crockford ULID"
|
|
4294
|
+
);
|
|
4295
|
+
function isStrictRfc3339(value) {
|
|
4296
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-](\d{2}):(\d{2}))$/.exec(value);
|
|
4297
|
+
if (!match) return false;
|
|
4298
|
+
const [, yearText, monthText, dayText, hourText, minuteText, secondText, offsetHourText, offsetMinuteText] = match;
|
|
4299
|
+
const year = Number(yearText);
|
|
4300
|
+
const month = Number(monthText);
|
|
4301
|
+
const day = Number(dayText);
|
|
4302
|
+
const hour = Number(hourText);
|
|
4303
|
+
const minute = Number(minuteText);
|
|
4304
|
+
const second = Number(secondText);
|
|
4305
|
+
const offsetHour = offsetHourText === void 0 ? 0 : Number(offsetHourText);
|
|
4306
|
+
const offsetMinute = offsetMinuteText === void 0 ? 0 : Number(offsetMinuteText);
|
|
4307
|
+
if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59) return false;
|
|
4308
|
+
if (offsetHour > 23 || offsetMinute > 59) return false;
|
|
4309
|
+
const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
4310
|
+
const days = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
4311
|
+
return day >= 1 && day <= days[month - 1];
|
|
4312
|
+
}
|
|
4313
|
+
var Rfc3339Schema = external_exports.string().refine(isStrictRfc3339, "must be a valid RFC3339 timestamp");
|
|
4314
|
+
var RoleSchema = utf8Bounded("role", MAX_ROLE_BYTES);
|
|
4315
|
+
var MissionTextSchema = utf8Bounded("mission text", MAX_TEXT_BYTES);
|
|
4316
|
+
var MessageTextSchema = utf8Bounded("message text", MAX_TEXT_BYTES);
|
|
4317
|
+
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");
|
|
4318
|
+
var FileMimeSchema = external_exports.string().refine(
|
|
4319
|
+
(value) => Buffer.byteLength(value, "utf8") <= MAX_MIME_BYTES,
|
|
4320
|
+
`file MIME metadata must be at most ${MAX_MIME_BYTES} UTF-8 bytes`
|
|
4321
|
+
);
|
|
4322
|
+
var RoomStateSchema = external_exports.enum(["provisioning", "active", "closing", "closed"]);
|
|
4323
|
+
var SeatStateSchema = external_exports.enum(["active", "removed"]);
|
|
4324
|
+
var InviteModeSchema = external_exports.enum(["one_time", "public"]);
|
|
4325
|
+
var InviteStateSchema = external_exports.enum([
|
|
4326
|
+
"live",
|
|
4327
|
+
"consumed",
|
|
4328
|
+
"revoked",
|
|
4329
|
+
"replacement_required",
|
|
4330
|
+
"receipt_pending"
|
|
4331
|
+
]);
|
|
4332
|
+
var RelayStatusSchema = external_exports.enum(["queued", "send_failed"]);
|
|
4333
|
+
var SeatV1Schema = external_exports.object({
|
|
4334
|
+
identity: NonEmptyStringSchema,
|
|
4335
|
+
display_name: NonEmptyStringSchema,
|
|
4336
|
+
role: RoleSchema,
|
|
4337
|
+
invite_id: NonEmptyStringSchema,
|
|
4338
|
+
accepted_at: Rfc3339Schema
|
|
4339
|
+
}).strict();
|
|
4340
|
+
var SeatSchema = external_exports.object({
|
|
4341
|
+
identity: NonEmptyStringSchema,
|
|
4342
|
+
display_name: NonEmptyStringSchema,
|
|
4343
|
+
role: RoleSchema,
|
|
4344
|
+
invite_id: NonEmptyStringSchema,
|
|
4345
|
+
accepted_at: Rfc3339Schema,
|
|
4346
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
4347
|
+
state: SeatStateSchema,
|
|
4348
|
+
alias: NonEmptyStringSchema.optional(),
|
|
4349
|
+
removed_at: Rfc3339Schema.optional(),
|
|
4350
|
+
removed_epoch: external_exports.number().int().nonnegative().safe().optional(),
|
|
4351
|
+
replaces_seat: LowerCrockfordUlidSchema.optional(),
|
|
4352
|
+
bounced_at: Rfc3339Schema.optional()
|
|
4353
|
+
}).strict().superRefine((seat, context) => {
|
|
4354
|
+
if (seat.state === "removed") {
|
|
4355
|
+
if (seat.removed_at === void 0) {
|
|
4356
|
+
context.addIssue({
|
|
4357
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4358
|
+
path: ["removed_at"],
|
|
4359
|
+
message: "removed seats require removed_at"
|
|
4360
|
+
});
|
|
4361
|
+
}
|
|
4362
|
+
if (seat.removed_epoch === void 0) {
|
|
4363
|
+
context.addIssue({
|
|
4364
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4365
|
+
path: ["removed_epoch"],
|
|
4366
|
+
message: "removed seats require removed_epoch"
|
|
4367
|
+
});
|
|
4368
|
+
}
|
|
4369
|
+
} else {
|
|
4370
|
+
for (const field of ["removed_at", "removed_epoch", "bounced_at"]) {
|
|
4371
|
+
if (seat[field] !== void 0) {
|
|
4372
|
+
context.addIssue({
|
|
4373
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4374
|
+
path: [field],
|
|
4375
|
+
message: `${field} is reserved for removed seats`
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
});
|
|
4381
|
+
var RoomInviteSchema = external_exports.object({
|
|
4382
|
+
invite_id: NonEmptyStringSchema,
|
|
4383
|
+
mode: InviteModeSchema,
|
|
4384
|
+
role: RoleSchema,
|
|
4385
|
+
min_accepts: PositiveSafeIntegerSchema,
|
|
4386
|
+
accepted_cids: external_exports.array(NonEmptyStringSchema),
|
|
4387
|
+
state: InviteStateSchema,
|
|
4388
|
+
recovery_of: NonEmptyStringSchema.optional(),
|
|
4389
|
+
recovery_confirmed: external_exports.boolean().optional(),
|
|
4390
|
+
created_at: Rfc3339Schema,
|
|
4391
|
+
replaces_seat: LowerCrockfordUlidSchema.optional()
|
|
4392
|
+
}).strict().superRefine((invite, context) => {
|
|
4393
|
+
if (invite.mode === "one_time" && invite.min_accepts !== 1) {
|
|
4394
|
+
context.addIssue({
|
|
4395
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4396
|
+
path: ["min_accepts"],
|
|
4397
|
+
message: "one_time invites require min_accepts === 1"
|
|
4398
|
+
});
|
|
4399
|
+
}
|
|
4400
|
+
if (invite.state === "receipt_pending" && invite.recovery_of === void 0) {
|
|
4401
|
+
context.addIssue({
|
|
4402
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4403
|
+
path: ["recovery_of"],
|
|
4404
|
+
message: "receipt_pending invites require recovery_of"
|
|
4405
|
+
});
|
|
4406
|
+
}
|
|
4407
|
+
if (invite.recovery_of === void 0 && invite.recovery_confirmed !== void 0) {
|
|
4408
|
+
context.addIssue({
|
|
4409
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4410
|
+
path: ["recovery_confirmed"],
|
|
4411
|
+
message: "recovery_confirmed is forbidden without recovery_of"
|
|
4412
|
+
});
|
|
4413
|
+
}
|
|
4414
|
+
if (invite.recovery_of !== void 0 && invite.recovery_confirmed === void 0) {
|
|
4415
|
+
context.addIssue({
|
|
4416
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4417
|
+
path: ["recovery_confirmed"],
|
|
4418
|
+
message: "recovery_confirmed is required with recovery_of"
|
|
4419
|
+
});
|
|
4420
|
+
}
|
|
4421
|
+
if (invite.state === "receipt_pending" && invite.recovery_confirmed !== false) {
|
|
4422
|
+
context.addIssue({
|
|
4423
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4424
|
+
path: ["recovery_confirmed"],
|
|
4425
|
+
message: "receipt_pending recovery lineage must be unconfirmed"
|
|
4426
|
+
});
|
|
4427
|
+
}
|
|
4428
|
+
if (invite.recovery_of !== void 0 && (invite.state === "live" || invite.state === "consumed" || invite.state === "replacement_required") && invite.recovery_confirmed !== true) {
|
|
4429
|
+
context.addIssue({
|
|
4430
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4431
|
+
path: ["recovery_confirmed"],
|
|
4432
|
+
message: "live, consumed, and replacement_required recovery lineage must be confirmed"
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
if (invite.state === "receipt_pending" && invite.accepted_cids.length > 0) {
|
|
4436
|
+
context.addIssue({
|
|
4437
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4438
|
+
path: ["accepted_cids"],
|
|
4439
|
+
message: "receipt_pending invites cannot have accepted CIDs"
|
|
4440
|
+
});
|
|
4441
|
+
}
|
|
4442
|
+
});
|
|
4443
|
+
var MissionV1Schema = external_exports.object({
|
|
4444
|
+
goal: MissionTextSchema,
|
|
4445
|
+
briefing: MissionTextSchema
|
|
4446
|
+
}).strict();
|
|
4447
|
+
var MissionSchema = external_exports.object({
|
|
4448
|
+
goal: MissionTextSchema,
|
|
4449
|
+
briefing: MissionTextSchema,
|
|
4450
|
+
briefing_version: PositiveSafeIntegerSchema
|
|
4451
|
+
}).strict();
|
|
4452
|
+
var RoleBriefingSchema = external_exports.object({
|
|
4453
|
+
text: MissionTextSchema,
|
|
4454
|
+
version: PositiveSafeIntegerSchema,
|
|
4455
|
+
updated_at: Rfc3339Schema
|
|
4456
|
+
}).strict();
|
|
4457
|
+
function refineRoomLineage(room, context) {
|
|
4458
|
+
const pendingIdentityName = `cowork-room-${room.room_id}`;
|
|
4459
|
+
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;
|
|
4460
|
+
if (room.identity_cid === "" && !exactPacketPending) {
|
|
4461
|
+
context.addIssue({
|
|
4462
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4463
|
+
path: ["identity_cid"],
|
|
4464
|
+
message: "empty identity_cid is reserved for the exact packet_pending provisioning sentinel"
|
|
4465
|
+
});
|
|
4466
|
+
}
|
|
4467
|
+
if (room.identity_cid !== "" && room.status === "packet_pending") {
|
|
4468
|
+
context.addIssue({
|
|
4469
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4470
|
+
path: ["status"],
|
|
4471
|
+
message: "packet_pending status requires an empty identity_cid"
|
|
4472
|
+
});
|
|
4473
|
+
}
|
|
4474
|
+
const pendingByRecovery = /* @__PURE__ */ new Map();
|
|
4475
|
+
for (const [index, invite] of room.invites.entries()) {
|
|
4476
|
+
if (invite.recovery_of === void 0) continue;
|
|
4477
|
+
const recoveryOf = invite.recovery_of;
|
|
4478
|
+
const source = room.invites.find((candidate) => candidate.invite_id === recoveryOf);
|
|
4479
|
+
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;
|
|
4480
|
+
if (!source || source.invite_id === invite.invite_id || !validSourceState) {
|
|
4481
|
+
context.addIssue({
|
|
4482
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4483
|
+
path: ["invites", index, "recovery_of"],
|
|
4484
|
+
message: "recovery_of must point to a source invite in the state required by this recovery lineage"
|
|
4485
|
+
});
|
|
4486
|
+
} else if (invite.mode !== source.mode || invite.role !== source.role || invite.min_accepts !== source.min_accepts) {
|
|
4487
|
+
context.addIssue({
|
|
4488
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4489
|
+
path: ["invites", index],
|
|
4490
|
+
message: "receipt_pending descriptor must copy source mode, role, and min_accepts"
|
|
4491
|
+
});
|
|
4492
|
+
}
|
|
4493
|
+
if (invite.state === "receipt_pending") {
|
|
4494
|
+
const count = (pendingByRecovery.get(recoveryOf) ?? 0) + 1;
|
|
4495
|
+
pendingByRecovery.set(recoveryOf, count);
|
|
4496
|
+
if (count > 1) {
|
|
4497
|
+
context.addIssue({
|
|
4498
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4499
|
+
path: ["invites", index, "recovery_of"],
|
|
4500
|
+
message: "only one receipt_pending invite may exist per recovery_of pointer"
|
|
4501
|
+
});
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
var RoomCommonShape = {
|
|
4507
|
+
room_id: LowerCrockfordUlidSchema,
|
|
4508
|
+
identity_name: NonEmptyStringSchema,
|
|
4509
|
+
identity_cid: external_exports.string(),
|
|
4510
|
+
state: RoomStateSchema,
|
|
4511
|
+
status: NonEmptyStringSchema.optional(),
|
|
4512
|
+
invites: external_exports.array(RoomInviteSchema),
|
|
4513
|
+
created_at: Rfc3339Schema,
|
|
4514
|
+
activated_at: Rfc3339Schema.optional(),
|
|
4515
|
+
closed_at: Rfc3339Schema.optional()
|
|
4516
|
+
};
|
|
4517
|
+
var RoomV1Schema = external_exports.object({
|
|
4518
|
+
...RoomCommonShape,
|
|
4519
|
+
version: external_exports.literal(1),
|
|
4520
|
+
mission: MissionV1Schema,
|
|
4521
|
+
seats: external_exports.array(SeatV1Schema)
|
|
4522
|
+
}).strict().superRefine(refineRoomLineage);
|
|
4523
|
+
var RoomSchema = external_exports.object({
|
|
4524
|
+
...RoomCommonShape,
|
|
4525
|
+
version: external_exports.literal(2),
|
|
4526
|
+
mission: MissionSchema,
|
|
4527
|
+
role_briefings: external_exports.record(RoleSchema, RoleBriefingSchema),
|
|
4528
|
+
anonymous: external_exports.boolean(),
|
|
4529
|
+
quiet_membership: external_exports.boolean(),
|
|
4530
|
+
membership_epoch: external_exports.number().int().nonnegative().safe(),
|
|
4531
|
+
seats: external_exports.array(SeatSchema)
|
|
4532
|
+
}).strict().superRefine((room, context) => {
|
|
4533
|
+
refineRoomLineage(room, context);
|
|
4534
|
+
const byParticipant = /* @__PURE__ */ new Map();
|
|
4535
|
+
const activeAliases = /* @__PURE__ */ new Set();
|
|
4536
|
+
for (const [index, seat] of room.seats.entries()) {
|
|
4537
|
+
if (byParticipant.has(seat.participant_id)) {
|
|
4538
|
+
context.addIssue({
|
|
4539
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4540
|
+
path: ["seats", index, "participant_id"],
|
|
4541
|
+
message: "participant_id must be unique within the room"
|
|
4542
|
+
});
|
|
4543
|
+
}
|
|
4544
|
+
byParticipant.set(seat.participant_id, seat);
|
|
4545
|
+
if (room.anonymous && seat.alias === void 0) {
|
|
4546
|
+
context.addIssue({
|
|
4547
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4548
|
+
path: ["seats", index, "alias"],
|
|
4549
|
+
message: "anonymous rooms require an alias on every seat"
|
|
4550
|
+
});
|
|
4551
|
+
}
|
|
4552
|
+
if (!room.anonymous && seat.alias !== void 0) {
|
|
4553
|
+
context.addIssue({
|
|
4554
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4555
|
+
path: ["seats", index, "alias"],
|
|
4556
|
+
message: "aliases are reserved for anonymous rooms"
|
|
4557
|
+
});
|
|
4558
|
+
}
|
|
4559
|
+
if (seat.state === "active" && seat.alias !== void 0) {
|
|
4560
|
+
if (activeAliases.has(seat.alias)) {
|
|
4561
|
+
context.addIssue({
|
|
4562
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4563
|
+
path: ["seats", index, "alias"],
|
|
4564
|
+
message: "active seats must hold distinct aliases"
|
|
4565
|
+
});
|
|
4566
|
+
}
|
|
4567
|
+
activeAliases.add(seat.alias);
|
|
4568
|
+
}
|
|
4569
|
+
if (seat.removed_epoch !== void 0 && seat.removed_epoch > room.membership_epoch) {
|
|
4570
|
+
context.addIssue({
|
|
4571
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4572
|
+
path: ["seats", index, "removed_epoch"],
|
|
4573
|
+
message: "removed_epoch cannot exceed the room membership_epoch"
|
|
4574
|
+
});
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
for (const [index, seat] of room.seats.entries()) {
|
|
4578
|
+
if (seat.replaces_seat === void 0) continue;
|
|
4579
|
+
const predecessor = byParticipant.get(seat.replaces_seat);
|
|
4580
|
+
if (!predecessor || predecessor === seat || predecessor.state !== "removed") {
|
|
4581
|
+
context.addIssue({
|
|
4582
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4583
|
+
path: ["seats", index, "replaces_seat"],
|
|
4584
|
+
message: "replaces_seat must reference a removed seat in this room"
|
|
4585
|
+
});
|
|
4586
|
+
continue;
|
|
4587
|
+
}
|
|
4588
|
+
if (predecessor.role !== seat.role) {
|
|
4589
|
+
context.addIssue({
|
|
4590
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4591
|
+
path: ["seats", index, "role"],
|
|
4592
|
+
message: "a replacement seat must inherit the predecessor role"
|
|
4593
|
+
});
|
|
4594
|
+
}
|
|
4595
|
+
if (room.anonymous && seat.alias !== predecessor.alias) {
|
|
4596
|
+
context.addIssue({
|
|
4597
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4598
|
+
path: ["seats", index, "alias"],
|
|
4599
|
+
message: "an anonymous replacement seat must inherit the predecessor alias"
|
|
4600
|
+
});
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
});
|
|
4604
|
+
var CreateRoomInputSchema = external_exports.object({
|
|
4605
|
+
goal: MissionTextSchema,
|
|
4606
|
+
briefing: MissionTextSchema,
|
|
4607
|
+
anonymous: external_exports.boolean().optional(),
|
|
4608
|
+
quiet_membership: external_exports.boolean().optional()
|
|
4609
|
+
}).strict();
|
|
4610
|
+
var UpdateRoomInputSchema = external_exports.object({
|
|
4611
|
+
goal: MissionTextSchema.optional(),
|
|
4612
|
+
briefing: MissionTextSchema.optional(),
|
|
4613
|
+
status: NonEmptyStringSchema.optional(),
|
|
4614
|
+
quiet_membership: external_exports.boolean().optional()
|
|
4615
|
+
}).strict().refine((input) => Object.keys(input).length > 0, "at least one setting is required");
|
|
4616
|
+
var RoleBriefingSetInputSchema = external_exports.object({
|
|
4617
|
+
role: RoleSchema,
|
|
4618
|
+
text: MissionTextSchema
|
|
4619
|
+
}).strict();
|
|
4620
|
+
var RoleBriefingDeleteInputSchema = external_exports.object({
|
|
4621
|
+
role: RoleSchema
|
|
4622
|
+
}).strict();
|
|
4623
|
+
var PostMessageInputSchema = external_exports.object({
|
|
4624
|
+
text: MessageTextSchema
|
|
4625
|
+
}).strict();
|
|
4626
|
+
var AuthorSnapshotSchema = external_exports.object({
|
|
4627
|
+
identity: NonEmptyStringSchema,
|
|
4628
|
+
display_name: NonEmptyStringSchema,
|
|
4629
|
+
role: RoleSchema
|
|
4630
|
+
}).strict();
|
|
4631
|
+
var RecordCommonShape = {
|
|
4632
|
+
version: external_exports.literal(1),
|
|
4633
|
+
room_id: LowerCrockfordUlidSchema,
|
|
4634
|
+
seq: PositiveSafeIntegerSchema,
|
|
4635
|
+
record_id: NonEmptyStringSchema,
|
|
4636
|
+
at: Rfc3339Schema
|
|
4637
|
+
};
|
|
4638
|
+
var AppendCommonShape = {
|
|
4639
|
+
version: external_exports.literal(1),
|
|
4640
|
+
room_id: LowerCrockfordUlidSchema,
|
|
4641
|
+
at: Rfc3339Schema
|
|
4642
|
+
};
|
|
4643
|
+
var MembershipNoticeSchema = external_exports.object({
|
|
4644
|
+
action: external_exports.enum(["remove"]),
|
|
4645
|
+
alias: NonEmptyStringSchema.optional(),
|
|
4646
|
+
role: RoleSchema.optional(),
|
|
4647
|
+
epoch: external_exports.number().int().nonnegative().safe()
|
|
4648
|
+
}).strict();
|
|
4649
|
+
var AuthorAliasSchema = external_exports.object({
|
|
4650
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
4651
|
+
alias: NonEmptyStringSchema
|
|
4652
|
+
}).strict();
|
|
4653
|
+
var MessageShape = {
|
|
4654
|
+
kind: external_exports.literal("message"),
|
|
4655
|
+
message_id: LowerCrockfordUlidSchema,
|
|
4656
|
+
author: AuthorSnapshotSchema,
|
|
4657
|
+
author_alias: AuthorAliasSchema.optional(),
|
|
4658
|
+
category: external_exports.enum(["briefing", "role_briefing", "chat", "membership"]),
|
|
4659
|
+
briefing_role: RoleSchema.optional(),
|
|
4660
|
+
briefing_version: PositiveSafeIntegerSchema.optional(),
|
|
4661
|
+
membership: MembershipNoticeSchema.optional(),
|
|
4662
|
+
text: MessageTextSchema,
|
|
4663
|
+
recipient_identities: external_exports.array(NonEmptyStringSchema).superRefine((identities, context) => {
|
|
4664
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4665
|
+
for (const [index, identity] of identities.entries()) {
|
|
4666
|
+
if (seen.has(identity)) {
|
|
4667
|
+
context.addIssue({
|
|
4668
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4669
|
+
path: [index],
|
|
4670
|
+
message: "recipient identities must be unique"
|
|
4671
|
+
});
|
|
4672
|
+
}
|
|
4673
|
+
seen.add(identity);
|
|
4674
|
+
}
|
|
4675
|
+
}),
|
|
4676
|
+
source_msg_id: external_exports.number().int().nonnegative().safe().optional(),
|
|
4677
|
+
source_wire_id: NonEmptyStringSchema.optional()
|
|
4678
|
+
};
|
|
4679
|
+
var RelayIntentShape = {
|
|
4680
|
+
kind: external_exports.literal("relay_intent"),
|
|
4681
|
+
message_id: LowerCrockfordUlidSchema.optional(),
|
|
4682
|
+
file_id: LowerCrockfordUlidSchema.optional(),
|
|
4683
|
+
recipient_identity: NonEmptyStringSchema
|
|
4684
|
+
};
|
|
4685
|
+
var RelayResultStatusSchema = external_exports.enum(["queued", "send_failed", "skipped_removed"]);
|
|
4686
|
+
var RelayResultShape = {
|
|
4687
|
+
kind: external_exports.literal("relay_result"),
|
|
4688
|
+
intent_record_id: NonEmptyStringSchema,
|
|
4689
|
+
message_id: LowerCrockfordUlidSchema.optional(),
|
|
4690
|
+
file_id: LowerCrockfordUlidSchema.optional(),
|
|
4691
|
+
recipient_identity: NonEmptyStringSchema,
|
|
4692
|
+
status: RelayResultStatusSchema,
|
|
4693
|
+
wire_id: NonEmptyStringSchema.optional(),
|
|
4694
|
+
metadata_wire_id: NonEmptyStringSchema.optional()
|
|
4695
|
+
};
|
|
4696
|
+
var FileShape = {
|
|
4697
|
+
kind: external_exports.literal("file"),
|
|
4698
|
+
file_id: LowerCrockfordUlidSchema,
|
|
4699
|
+
author: AuthorSnapshotSchema,
|
|
4700
|
+
author_alias: AuthorAliasSchema.optional(),
|
|
4701
|
+
filename: FileNameSchema,
|
|
4702
|
+
mime: FileMimeSchema,
|
|
4703
|
+
size: external_exports.number().int().nonnegative().max(MAX_FILE_BYTES),
|
|
4704
|
+
sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
4705
|
+
data_base64: external_exports.string(),
|
|
4706
|
+
recipient_identities: external_exports.array(NonEmptyStringSchema).superRefine((identities, context) => {
|
|
4707
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4708
|
+
for (const [index, identity] of identities.entries()) {
|
|
4709
|
+
if (seen.has(identity)) {
|
|
4710
|
+
context.addIssue({
|
|
4711
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4712
|
+
path: [index],
|
|
4713
|
+
message: "recipient identities must be unique"
|
|
4714
|
+
});
|
|
4715
|
+
}
|
|
4716
|
+
seen.add(identity);
|
|
4717
|
+
}
|
|
4718
|
+
}),
|
|
4719
|
+
source_file_id: external_exports.number().int().nonnegative().safe(),
|
|
4720
|
+
source_wire_id: NonEmptyStringSchema.optional()
|
|
4721
|
+
};
|
|
4722
|
+
var MembershipIntentShape = {
|
|
4723
|
+
kind: external_exports.literal("membership_intent"),
|
|
4724
|
+
action: external_exports.enum(["remove"]),
|
|
4725
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
4726
|
+
recipient_identity: NonEmptyStringSchema,
|
|
4727
|
+
role: RoleSchema,
|
|
4728
|
+
alias: NonEmptyStringSchema.optional(),
|
|
4729
|
+
epoch: PositiveSafeIntegerSchema,
|
|
4730
|
+
notify: external_exports.boolean()
|
|
4731
|
+
};
|
|
4732
|
+
var MembershipResultShape = {
|
|
4733
|
+
kind: external_exports.literal("membership_result"),
|
|
4734
|
+
intent_record_id: NonEmptyStringSchema,
|
|
4735
|
+
participant_id: LowerCrockfordUlidSchema,
|
|
4736
|
+
status: RelayStatusSchema,
|
|
4737
|
+
notified: external_exports.boolean(),
|
|
4738
|
+
key_material_retained: external_exports.literal(true),
|
|
4739
|
+
uncertain_after_restart: external_exports.literal(true).optional()
|
|
4740
|
+
};
|
|
4741
|
+
var CloseNoticeIntentShape = {
|
|
4742
|
+
kind: external_exports.literal("close_notice_intent"),
|
|
4743
|
+
recipient_identity: NonEmptyStringSchema
|
|
4744
|
+
};
|
|
4745
|
+
var CloseNoticeResultShape = {
|
|
4746
|
+
kind: external_exports.literal("close_notice_result"),
|
|
4747
|
+
intent_record_id: NonEmptyStringSchema,
|
|
4748
|
+
recipient_identity: NonEmptyStringSchema,
|
|
4749
|
+
status: RelayStatusSchema,
|
|
4750
|
+
notified: external_exports.boolean(),
|
|
4751
|
+
key_material_retained: external_exports.literal(true),
|
|
4752
|
+
uncertain_after_restart: external_exports.literal(true).optional()
|
|
4753
|
+
};
|
|
4754
|
+
var MessageRecordSchema = external_exports.object({ ...RecordCommonShape, ...MessageShape }).strict();
|
|
4755
|
+
var FileRecordSchema = external_exports.object({ ...RecordCommonShape, ...FileShape }).strict();
|
|
4756
|
+
var RelayIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...RelayIntentShape }).strict();
|
|
4757
|
+
var RelayResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...RelayResultShape }).strict();
|
|
4758
|
+
var MembershipIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...MembershipIntentShape }).strict();
|
|
4759
|
+
var MembershipResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...MembershipResultShape }).strict();
|
|
4760
|
+
var CloseNoticeIntentRecordSchema = external_exports.object({ ...RecordCommonShape, ...CloseNoticeIntentShape }).strict();
|
|
4761
|
+
var CloseNoticeResultRecordSchema = external_exports.object({ ...RecordCommonShape, ...CloseNoticeResultShape }).strict();
|
|
4762
|
+
var RawCommunicationRecordSchema = external_exports.discriminatedUnion("kind", [
|
|
4763
|
+
MessageRecordSchema,
|
|
4764
|
+
FileRecordSchema,
|
|
4765
|
+
RelayIntentRecordSchema,
|
|
4766
|
+
RelayResultRecordSchema,
|
|
4767
|
+
MembershipIntentRecordSchema,
|
|
4768
|
+
MembershipResultRecordSchema,
|
|
4769
|
+
CloseNoticeIntentRecordSchema,
|
|
4770
|
+
CloseNoticeResultRecordSchema
|
|
4771
|
+
]);
|
|
4772
|
+
function refineRelaySubject(record, context) {
|
|
4773
|
+
if (record.kind !== "relay_intent" && record.kind !== "relay_result") return;
|
|
4774
|
+
if (record.message_id === void 0 === (record.file_id === void 0)) {
|
|
4775
|
+
context.addIssue({
|
|
4776
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4777
|
+
path: ["message_id"],
|
|
4778
|
+
message: "relay records require exactly one of message_id or file_id"
|
|
4779
|
+
});
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
function refineFileRecord(record, context) {
|
|
4783
|
+
if (record.kind !== "file" || record.data_base64 === void 0) return;
|
|
4784
|
+
const bytes = Buffer.from(record.data_base64, "base64");
|
|
4785
|
+
if (bytes.toString("base64") !== record.data_base64) {
|
|
4786
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["data_base64"], message: "file bytes must use canonical base64" });
|
|
4787
|
+
}
|
|
4788
|
+
if (bytes.length !== record.size) {
|
|
4789
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["size"], message: "file size must match decoded bytes" });
|
|
4790
|
+
}
|
|
4791
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
4792
|
+
if (digest !== record.sha256) {
|
|
4793
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["sha256"], message: "file sha256 must match decoded bytes" });
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
function refineMessageCategory(message, context) {
|
|
4797
|
+
const requires = (field, present) => {
|
|
4798
|
+
if (present && message[field] === void 0) {
|
|
4799
|
+
context.addIssue({
|
|
4800
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4801
|
+
path: [field],
|
|
4802
|
+
message: `${message.category} messages require ${field}`
|
|
4803
|
+
});
|
|
4804
|
+
}
|
|
4805
|
+
if (!present && message[field] !== void 0) {
|
|
4806
|
+
context.addIssue({
|
|
4807
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4808
|
+
path: [field],
|
|
4809
|
+
message: `${field} is forbidden on ${message.category} messages`
|
|
4810
|
+
});
|
|
4811
|
+
}
|
|
4812
|
+
};
|
|
4813
|
+
requires("briefing_role", message.category === "role_briefing");
|
|
4814
|
+
requires("membership", message.category === "membership");
|
|
4815
|
+
if (message.category === "role_briefing" && message.briefing_version === void 0) {
|
|
4816
|
+
context.addIssue({
|
|
4817
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4818
|
+
path: ["briefing_version"],
|
|
4819
|
+
message: "role_briefing messages require briefing_version"
|
|
4820
|
+
});
|
|
4821
|
+
}
|
|
4822
|
+
if (message.category === "chat" || message.category === "membership") {
|
|
4823
|
+
if (message.briefing_version !== void 0) {
|
|
4824
|
+
context.addIssue({
|
|
4825
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4826
|
+
path: ["briefing_version"],
|
|
4827
|
+
message: `briefing_version is forbidden on ${message.category} messages`
|
|
4828
|
+
});
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
var CommunicationRecordSchema = RawCommunicationRecordSchema.superRefine((record, context) => {
|
|
4833
|
+
if (record.record_id !== `${record.room_id}:${record.seq}`) {
|
|
4834
|
+
context.addIssue({
|
|
4835
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4836
|
+
path: ["record_id"],
|
|
4837
|
+
message: 'record_id must equal room_id + ":" + seq'
|
|
4838
|
+
});
|
|
4839
|
+
}
|
|
4840
|
+
if (record.kind === "message") refineMessageCategory(record, context);
|
|
4841
|
+
refineRelaySubject(record, context);
|
|
4842
|
+
refineFileRecord(record, context);
|
|
4843
|
+
});
|
|
4844
|
+
var AppendRecordSchema = external_exports.discriminatedUnion("kind", [
|
|
4845
|
+
external_exports.object({ ...AppendCommonShape, ...MessageShape }).strict(),
|
|
4846
|
+
external_exports.object({ ...AppendCommonShape, ...FileShape }).strict(),
|
|
4847
|
+
external_exports.object({ ...AppendCommonShape, ...RelayIntentShape }).strict(),
|
|
4848
|
+
external_exports.object({ ...AppendCommonShape, ...RelayResultShape }).strict(),
|
|
4849
|
+
external_exports.object({ ...AppendCommonShape, ...MembershipIntentShape }).strict(),
|
|
4850
|
+
external_exports.object({ ...AppendCommonShape, ...MembershipResultShape }).strict(),
|
|
4851
|
+
external_exports.object({ ...AppendCommonShape, ...CloseNoticeIntentShape }).strict(),
|
|
4852
|
+
external_exports.object({ ...AppendCommonShape, ...CloseNoticeResultShape }).strict()
|
|
4853
|
+
]).superRefine((record, context) => {
|
|
4854
|
+
if (record.kind === "message") refineMessageCategory(record, context);
|
|
4855
|
+
refineRelaySubject(record, context);
|
|
4856
|
+
refineFileRecord(record, context);
|
|
4857
|
+
});
|
|
4858
|
+
|
|
4274
4859
|
// src/cli.ts
|
|
4275
4860
|
var EXIT = {
|
|
4276
4861
|
success: 0,
|
|
@@ -4287,7 +4872,6 @@ var NATIVE_RPC_TIMEOUT_MS = 12e4;
|
|
|
4287
4872
|
var START_TIMEOUT_MS = 3e4;
|
|
4288
4873
|
var WEB_READY_TIMEOUT_MS = 3e4;
|
|
4289
4874
|
var STOP_TIMEOUT_MS = 12e3;
|
|
4290
|
-
var MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
4291
4875
|
var SELF = fileURLToPath(import.meta.url);
|
|
4292
4876
|
var SYSTEMD_UNIT = "ours-cowork.service";
|
|
4293
4877
|
var LAUNCHD_LABEL = "network.ours.cowork";
|
|
@@ -4316,7 +4900,9 @@ var NATIVE_MUTATION_METHODS = /* @__PURE__ */ new Set([
|
|
|
4316
4900
|
"room.message",
|
|
4317
4901
|
"room.close",
|
|
4318
4902
|
"room.recover",
|
|
4319
|
-
"room.recover.confirm"
|
|
4903
|
+
"room.recover.confirm",
|
|
4904
|
+
"room.participant.remove",
|
|
4905
|
+
"room.participant.replace"
|
|
4320
4906
|
]);
|
|
4321
4907
|
function rpcTimeoutForMethod(method) {
|
|
4322
4908
|
return NATIVE_MUTATION_METHODS.has(method) ? NATIVE_RPC_TIMEOUT_MS : RPC_TIMEOUT_MS;
|
|
@@ -4349,14 +4935,17 @@ Usage:
|
|
|
4349
4935
|
ours-cowork [--json] docs [topic]
|
|
4350
4936
|
|
|
4351
4937
|
Room commands:
|
|
4352
|
-
create --goal <text> --briefing <text>
|
|
4353
|
-
settings <room-id> [--goal <text>] [--briefing <text>] [--status <text>]
|
|
4354
|
-
|
|
4938
|
+
create --goal <text> --briefing <text> [--anonymous] [--quiet-membership]
|
|
4939
|
+
settings <room-id> [--goal <text>] [--briefing <text>] [--status <text>] [--quiet-membership true|false]
|
|
4940
|
+
role-briefing <room-id> --role <label> (--text <text> | --delete)
|
|
4941
|
+
invite <room-id> [--role <label>] [--mode one_time|public] [--min-accepts <n>]
|
|
4355
4942
|
revoke <room-id> <invite-id>
|
|
4943
|
+
remove <room-id> <participant> [--silent]
|
|
4944
|
+
replace <room-id> <participant> [--mode one_time|public] [--min-accepts <n>] [--silent]
|
|
4356
4945
|
list
|
|
4357
4946
|
show <room-id>
|
|
4358
4947
|
participants <room-id>
|
|
4359
|
-
history <room-id> [--after <seq>] [--limit <n>]
|
|
4948
|
+
history <room-id> [--after <seq>] [--limit <n>] [--view operator|participant]
|
|
4360
4949
|
message <room-id> --text <text>
|
|
4361
4950
|
close <room-id>
|
|
4362
4951
|
delete <room-id> --yes
|
|
@@ -4436,23 +5025,44 @@ function roomRequest(command, args) {
|
|
|
4436
5025
|
if (!command) usageError("room requires a command");
|
|
4437
5026
|
switch (command) {
|
|
4438
5027
|
case "create": {
|
|
4439
|
-
const parsed = parseOptions(args, ["--goal", "--briefing"]);
|
|
5028
|
+
const parsed = parseOptions(args, ["--goal", "--briefing"], ["--anonymous", "--quiet-membership"]);
|
|
4440
5029
|
exactPositionals(parsed, 0, "room create");
|
|
4441
5030
|
return { method: "room.create", params: {
|
|
4442
5031
|
goal: requiredFlag(parsed, "--goal", "room create"),
|
|
4443
|
-
briefing: requiredFlag(parsed, "--briefing", "room create")
|
|
5032
|
+
briefing: requiredFlag(parsed, "--briefing", "room create"),
|
|
5033
|
+
...parsed.booleans.has("--anonymous") ? { anonymous: true } : {},
|
|
5034
|
+
...parsed.booleans.has("--quiet-membership") ? { quiet_membership: true } : {}
|
|
4444
5035
|
} };
|
|
4445
5036
|
}
|
|
4446
5037
|
case "settings": {
|
|
4447
|
-
const parsed = parseOptions(args, ["--goal", "--briefing", "--status"]);
|
|
5038
|
+
const parsed = parseOptions(args, ["--goal", "--briefing", "--status", "--quiet-membership"]);
|
|
4448
5039
|
const [roomId] = exactPositionals(parsed, 1, "room settings");
|
|
4449
5040
|
const params = { room_id: roomId };
|
|
4450
5041
|
for (const [flag, key] of [["--goal", "goal"], ["--briefing", "briefing"], ["--status", "status"]]) {
|
|
4451
5042
|
if (parsed.values[flag] !== void 0) params[key] = parsed.values[flag];
|
|
4452
5043
|
}
|
|
5044
|
+
const quiet = parsed.values["--quiet-membership"];
|
|
5045
|
+
if (quiet !== void 0) {
|
|
5046
|
+
if (quiet !== "true" && quiet !== "false") usageError("--quiet-membership must be true or false");
|
|
5047
|
+
params.quiet_membership = quiet === "true";
|
|
5048
|
+
}
|
|
4453
5049
|
if (Object.keys(params).length === 1) usageError("room settings requires at least one setting option");
|
|
4454
5050
|
return { method: "room.settings", params };
|
|
4455
5051
|
}
|
|
5052
|
+
case "role-briefing": {
|
|
5053
|
+
const parsed = parseOptions(args, ["--role", "--text"], ["--delete"]);
|
|
5054
|
+
const [roomId] = exactPositionals(parsed, 1, "room role-briefing");
|
|
5055
|
+
const role = requiredFlag(parsed, "--role", "room role-briefing");
|
|
5056
|
+
if (parsed.booleans.has("--delete")) {
|
|
5057
|
+
if (parsed.values["--text"] !== void 0) usageError("room role-briefing takes either --text or --delete");
|
|
5058
|
+
return { method: "room.briefing.role.delete", params: { room_id: roomId, role } };
|
|
5059
|
+
}
|
|
5060
|
+
return { method: "room.briefing.role.set", params: {
|
|
5061
|
+
room_id: roomId,
|
|
5062
|
+
role,
|
|
5063
|
+
text: requiredFlag(parsed, "--text", "room role-briefing")
|
|
5064
|
+
} };
|
|
5065
|
+
}
|
|
4456
5066
|
case "invite": {
|
|
4457
5067
|
const parsed = parseOptions(args, ["--mode", "--role", "--min-accepts"]);
|
|
4458
5068
|
const [roomId] = exactPositionals(parsed, 1, "room invite");
|
|
@@ -4463,10 +5073,33 @@ function roomRequest(command, args) {
|
|
|
4463
5073
|
return { method: "room.invite", params: {
|
|
4464
5074
|
room_id: roomId,
|
|
4465
5075
|
mode,
|
|
4466
|
-
role
|
|
5076
|
+
// Omitted role = the built-in "Participant" role (daemon-side default).
|
|
5077
|
+
...parsed.values["--role"] === void 0 ? {} : { role: parsed.values["--role"] },
|
|
4467
5078
|
min_accepts: minimum
|
|
4468
5079
|
} };
|
|
4469
5080
|
}
|
|
5081
|
+
case "remove": {
|
|
5082
|
+
const parsed = parseOptions(args, [], ["--silent"]);
|
|
5083
|
+
const [roomId, participant] = exactPositionals(parsed, 2, "room remove");
|
|
5084
|
+
return { method: "room.participant.remove", params: {
|
|
5085
|
+
room_id: roomId,
|
|
5086
|
+
participant,
|
|
5087
|
+
...parsed.booleans.has("--silent") ? { notify: false } : {}
|
|
5088
|
+
} };
|
|
5089
|
+
}
|
|
5090
|
+
case "replace": {
|
|
5091
|
+
const parsed = parseOptions(args, ["--mode", "--min-accepts"], ["--silent"]);
|
|
5092
|
+
const [roomId, participant] = exactPositionals(parsed, 2, "room replace");
|
|
5093
|
+
const mode = parsed.values["--mode"];
|
|
5094
|
+
if (mode !== void 0 && mode !== "one_time" && mode !== "public") usageError("--mode must be one_time or public");
|
|
5095
|
+
return { method: "room.participant.replace", params: {
|
|
5096
|
+
room_id: roomId,
|
|
5097
|
+
participant,
|
|
5098
|
+
...mode === void 0 ? {} : { mode },
|
|
5099
|
+
...parsed.values["--min-accepts"] === void 0 ? {} : { min_accepts: parseInteger(parsed.values["--min-accepts"], "--min-accepts", 1) },
|
|
5100
|
+
...parsed.booleans.has("--silent") ? { notify: false } : {}
|
|
5101
|
+
} };
|
|
5102
|
+
}
|
|
4470
5103
|
case "revoke": {
|
|
4471
5104
|
const parsed = parseOptions(args);
|
|
4472
5105
|
const [roomId, inviteId] = exactPositionals(parsed, 2, "room revoke");
|
|
@@ -4483,11 +5116,16 @@ function roomRequest(command, args) {
|
|
|
4483
5116
|
return { method: `room.${command}`, params: { room_id: roomId } };
|
|
4484
5117
|
}
|
|
4485
5118
|
case "history": {
|
|
4486
|
-
const parsed = parseOptions(args, ["--after", "--limit"]);
|
|
5119
|
+
const parsed = parseOptions(args, ["--after", "--limit", "--view"]);
|
|
4487
5120
|
const [roomId] = exactPositionals(parsed, 1, "room history");
|
|
4488
5121
|
const params = { room_id: roomId };
|
|
4489
5122
|
if (parsed.values["--after"] !== void 0) params.after = parseInteger(parsed.values["--after"], "--after", 0);
|
|
4490
5123
|
if (parsed.values["--limit"] !== void 0) params.limit = parseInteger(parsed.values["--limit"], "--limit", 1);
|
|
5124
|
+
const view = parsed.values["--view"];
|
|
5125
|
+
if (view !== void 0) {
|
|
5126
|
+
if (view !== "operator" && view !== "participant") usageError("--view must be operator or participant");
|
|
5127
|
+
params.view = view;
|
|
5128
|
+
}
|
|
4491
5129
|
return { method: "room.history", params };
|
|
4492
5130
|
}
|
|
4493
5131
|
case "message": {
|
|
@@ -4550,8 +5188,8 @@ function rpcCall(socketPath, method, params, timeoutMs = RPC_TIMEOUT_MS) {
|
|
|
4550
5188
|
socket.on("data", (chunk) => {
|
|
4551
5189
|
if (settled) return;
|
|
4552
5190
|
size += Buffer.byteLength(chunk, "utf8");
|
|
4553
|
-
if (size >
|
|
4554
|
-
finishError(new CliError(EXIT.internal, "internal", "daemon response exceeded
|
|
5191
|
+
if (size > MAX_MANAGEMENT_RESPONSE_BYTES) {
|
|
5192
|
+
finishError(new CliError(EXIT.internal, "internal", "daemon response exceeded 4 MiB"));
|
|
4555
5193
|
return;
|
|
4556
5194
|
}
|
|
4557
5195
|
bytes += chunk;
|
|
@@ -4687,13 +5325,11 @@ function httpGetReady(url, timeoutMs) {
|
|
|
4687
5325
|
if (deadline) clearTimeout(deadline);
|
|
4688
5326
|
deadline = void 0;
|
|
4689
5327
|
request.removeListener("response", onResponse);
|
|
4690
|
-
request.removeListener("error", onRequestError);
|
|
4691
5328
|
request.removeListener("timeout", onRequestTimeout);
|
|
4692
5329
|
request.removeListener("close", onRequestClose);
|
|
4693
5330
|
request.setTimeout(0);
|
|
4694
5331
|
if (response) {
|
|
4695
5332
|
response.removeListener("end", onResponseEnd);
|
|
4696
|
-
response.removeListener("error", onResponseError);
|
|
4697
5333
|
response.removeListener("aborted", onResponseAborted);
|
|
4698
5334
|
response.removeListener("close", onResponseClose);
|
|
4699
5335
|
if (!response.destroyed) response.destroy();
|
|
@@ -4717,14 +5353,14 @@ function httpGetReady(url, timeoutMs) {
|
|
|
4717
5353
|
}
|
|
4718
5354
|
response = incoming;
|
|
4719
5355
|
incoming.once("end", onResponseEnd);
|
|
4720
|
-
incoming.
|
|
5356
|
+
incoming.on("error", onResponseError);
|
|
4721
5357
|
incoming.once("aborted", onResponseAborted);
|
|
4722
5358
|
incoming.once("close", onResponseClose);
|
|
4723
5359
|
incoming.resume();
|
|
4724
5360
|
};
|
|
4725
5361
|
const request = http.get(url);
|
|
4726
5362
|
request.once("response", onResponse);
|
|
4727
|
-
request.
|
|
5363
|
+
request.on("error", onRequestError);
|
|
4728
5364
|
request.once("timeout", onRequestTimeout);
|
|
4729
5365
|
request.once("close", onRequestClose);
|
|
4730
5366
|
request.setTimeout(timeoutMs);
|
|
@@ -4940,6 +5576,35 @@ function readDocs(topic) {
|
|
|
4940
5576
|
throw new CliError(EXIT.internal, "internal", `offline documentation is missing: ${file}`, { cause: error });
|
|
4941
5577
|
}
|
|
4942
5578
|
}
|
|
5579
|
+
async function readHistoryPages(socketPath, params) {
|
|
5580
|
+
const requested = typeof params.limit === "number" ? params.limit : Number.MAX_SAFE_INTEGER;
|
|
5581
|
+
let after = typeof params.after === "number" ? params.after : 0;
|
|
5582
|
+
const records = [];
|
|
5583
|
+
while (records.length < requested) {
|
|
5584
|
+
const remaining = requested - records.length;
|
|
5585
|
+
const pageParams = { ...params, after, ...remaining < Number.MAX_SAFE_INTEGER ? { limit: remaining } : {} };
|
|
5586
|
+
const page = await rpcCall(
|
|
5587
|
+
socketPath,
|
|
5588
|
+
"room.history",
|
|
5589
|
+
pageParams,
|
|
5590
|
+
rpcTimeoutForMethod("room.history")
|
|
5591
|
+
);
|
|
5592
|
+
if (!Array.isArray(page)) {
|
|
5593
|
+
throw new CliError(EXIT.internal, "internal", "daemon returned an invalid history page");
|
|
5594
|
+
}
|
|
5595
|
+
if (page.length === 0) break;
|
|
5596
|
+
for (const record of page) {
|
|
5597
|
+
const seq = record !== null && typeof record === "object" ? record.seq : void 0;
|
|
5598
|
+
if (typeof seq !== "number" || !Number.isSafeInteger(seq) || seq <= after) {
|
|
5599
|
+
throw new CliError(EXIT.internal, "internal", "daemon returned a non-progressing history page");
|
|
5600
|
+
}
|
|
5601
|
+
after = seq;
|
|
5602
|
+
records.push(record);
|
|
5603
|
+
if (records.length === requested) break;
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
return records;
|
|
5607
|
+
}
|
|
4943
5608
|
async function execute(args, output) {
|
|
4944
5609
|
const command = args[0] ?? "help";
|
|
4945
5610
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
@@ -4956,8 +5621,9 @@ async function execute(args, output) {
|
|
|
4956
5621
|
if (command === "room") {
|
|
4957
5622
|
const request = roomRequest(args[1], args.slice(2));
|
|
4958
5623
|
const config2 = loadCliConfig();
|
|
4959
|
-
const
|
|
4960
|
-
|
|
5624
|
+
const socketPath = join2(config2.stateDir, "management.sock");
|
|
5625
|
+
const result = request.method === "room.history" ? await readHistoryPages(socketPath, request.params) : await rpcCall(
|
|
5626
|
+
socketPath,
|
|
4961
5627
|
request.method,
|
|
4962
5628
|
request.params,
|
|
4963
5629
|
rpcTimeoutForMethod(request.method)
|