@ours.network/cowork 0.3.5 → 0.3.6
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/dist/daemon.js
CHANGED
|
@@ -107,6 +107,15 @@ function packInvite(raw) {
|
|
|
107
107
|
}
|
|
108
108
|
}).toString("base64url");
|
|
109
109
|
}
|
|
110
|
+
function unpackInvite(encoded, maximumBytes) {
|
|
111
|
+
const normalized = encoded.replace(/\s+/g, "");
|
|
112
|
+
if (maximumBytes !== void 0 && Buffer.byteLength(encoded, "utf8") > maximumBytes || normalized.length === 0 || !/^[A-Za-z0-9_-]+$/.test(normalized)) {
|
|
113
|
+
throw new Error("the invite blob is empty, oversized, or invalid base64url");
|
|
114
|
+
}
|
|
115
|
+
const compressed = Buffer.from(normalized, "base64url");
|
|
116
|
+
if (compressed.length === 0) throw new Error("the invite blob is empty or invalid base64url");
|
|
117
|
+
return Buffer.from(maximumBytes === void 0 ? brotliDecompressSync(compressed) : brotliDecompressSync(compressed, { maxOutputLength: maximumBytes }));
|
|
118
|
+
}
|
|
110
119
|
var commonSdk, executableSdk, wrapperSdk, wrappersSdk, AdaptObjectLifetime, adapt_wrapper, object_to_adapt_value, PacketWrapperConfigurator, LATE_RESULT_DRAIN_MS, Packet, AdaptHostShutdownError, AdaptHost;
|
|
111
120
|
var init_adapt = __esm({
|
|
112
121
|
async "src/adapt.ts"() {
|
|
@@ -4931,7 +4940,7 @@ function refineMessageCategory(message, context) {
|
|
|
4931
4940
|
}
|
|
4932
4941
|
}
|
|
4933
4942
|
}
|
|
4934
|
-
var MAX_TEXT_BYTES, MAX_FILE_BYTES, MAX_HISTORY_PAGE_BYTES, MAX_MANAGEMENT_RESPONSE_BYTES, MAX_FILE_NAME_BYTES, MAX_MIME_BYTES, MAX_ROLE_BYTES, MAX_ROOM_NAME_CHARACTERS, NonEmptyStringSchema, PositiveSafeIntegerSchema, LowerCrockfordUlidSchema, Rfc3339Schema, RoomNameSchema, ROOM_IDENTITY_PREFIX, RoleSchema, MissionTextSchema, MessageTextSchema, FileNameSchema, FileMimeSchema, RoomStateSchema, SeatStateSchema, InviteModeSchema, DEFAULT_ROLE, InviteStateSchema, RelayStatusSchema, SeatV1Schema, SeatSchema, RoomInviteSchema, MissionV1Schema, MissionSchema, RoleBriefingSchema, RoomCommonShape, RoomV1Schema, CurrentRoomSchema, RoomSchema, CreateRoomInputSchema, UpdateRoomInputSchema, RoleBriefingSetInputSchema, RoleBriefingDeleteInputSchema, PostMessageInputSchema, AuthorSnapshotSchema, RecordCommonShape, AppendCommonShape, MembershipNoticeSchema, AuthorAliasSchema, MessageShape, RelayIntentShape, RelayResultStatusSchema, RelayResultShape, FileShape, MembershipIntentShape, MembershipResultShape, CloseNoticeIntentShape, CloseNoticeResultShape, MessageRecordSchema, FileRecordSchema, RelayIntentRecordSchema, RelayResultRecordSchema, MembershipIntentRecordSchema, MembershipResultRecordSchema, CloseNoticeIntentRecordSchema, CloseNoticeResultRecordSchema, RawCommunicationRecordSchema, CommunicationRecordSchema, AppendRecordSchema;
|
|
4943
|
+
var MAX_TEXT_BYTES, MAX_FILE_BYTES, MAX_HISTORY_PAGE_BYTES, MAX_MANAGEMENT_RESPONSE_BYTES, MAX_EXTERNAL_INVITE_BYTES, MAX_FILE_NAME_BYTES, MAX_MIME_BYTES, MAX_ROLE_BYTES, MAX_ROOM_NAME_CHARACTERS, NonEmptyStringSchema, PositiveSafeIntegerSchema, LowerCrockfordUlidSchema, Rfc3339Schema, RoomNameSchema, ROOM_IDENTITY_PREFIX, RoleSchema, MissionTextSchema, MessageTextSchema, FileNameSchema, FileMimeSchema, RoomStateSchema, SeatStateSchema, InviteModeSchema, DEFAULT_ROLE, InviteStateSchema, RelayStatusSchema, SeatV1Schema, SeatSchema, RoomInviteSchema, MissionV1Schema, MissionSchema, RoleBriefingSchema, RoomCommonShape, RoomV1Schema, CurrentRoomSchema, RoomSchema, CreateRoomInputSchema, UpdateRoomInputSchema, RoleBriefingSetInputSchema, RoleBriefingDeleteInputSchema, PostMessageInputSchema, ContainerIdSchema, AcceptExternalInviteInputSchema, AuthorSnapshotSchema, RecordCommonShape, AppendCommonShape, MembershipNoticeSchema, AuthorAliasSchema, MessageShape, RelayIntentShape, RelayResultStatusSchema, RelayResultShape, FileShape, MembershipIntentShape, MembershipResultShape, CloseNoticeIntentShape, CloseNoticeResultShape, MessageRecordSchema, FileRecordSchema, RelayIntentRecordSchema, RelayResultRecordSchema, MembershipIntentRecordSchema, MembershipResultRecordSchema, CloseNoticeIntentRecordSchema, CloseNoticeResultRecordSchema, RawCommunicationRecordSchema, CommunicationRecordSchema, AppendRecordSchema;
|
|
4935
4944
|
var init_contracts = __esm({
|
|
4936
4945
|
"src/contracts.ts"() {
|
|
4937
4946
|
"use strict";
|
|
@@ -4940,6 +4949,7 @@ var init_contracts = __esm({
|
|
|
4940
4949
|
MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
4941
4950
|
MAX_HISTORY_PAGE_BYTES = 3 * 1024 * 1024;
|
|
4942
4951
|
MAX_MANAGEMENT_RESPONSE_BYTES = MAX_HISTORY_PAGE_BYTES + 1024 * 1024;
|
|
4952
|
+
MAX_EXTERNAL_INVITE_BYTES = 48 * 1024;
|
|
4943
4953
|
MAX_FILE_NAME_BYTES = 255;
|
|
4944
4954
|
MAX_MIME_BYTES = 255;
|
|
4945
4955
|
MAX_ROLE_BYTES = 256;
|
|
@@ -4973,7 +4983,7 @@ var init_contracts = __esm({
|
|
|
4973
4983
|
`file MIME metadata must be at most ${MAX_MIME_BYTES} UTF-8 bytes`
|
|
4974
4984
|
);
|
|
4975
4985
|
RoomStateSchema = external_exports.enum(["provisioning", "active", "closing", "closed"]);
|
|
4976
|
-
SeatStateSchema = external_exports.enum(["active", "removed"]);
|
|
4986
|
+
SeatStateSchema = external_exports.enum(["pending", "active", "removed"]);
|
|
4977
4987
|
InviteModeSchema = external_exports.enum(["one_time", "public"]);
|
|
4978
4988
|
DEFAULT_ROLE = "Participant";
|
|
4979
4989
|
InviteStateSchema = external_exports.enum([
|
|
@@ -4996,7 +5006,9 @@ var init_contracts = __esm({
|
|
|
4996
5006
|
display_name: NonEmptyStringSchema,
|
|
4997
5007
|
role: RoleSchema,
|
|
4998
5008
|
invite_id: NonEmptyStringSchema,
|
|
4999
|
-
accepted_at: Rfc3339Schema,
|
|
5009
|
+
accepted_at: Rfc3339Schema.optional(),
|
|
5010
|
+
requested_at: Rfc3339Schema.optional(),
|
|
5011
|
+
invite_sha256: external_exports.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
5000
5012
|
participant_id: LowerCrockfordUlidSchema,
|
|
5001
5013
|
state: SeatStateSchema,
|
|
5002
5014
|
alias: NonEmptyStringSchema.optional(),
|
|
@@ -5005,7 +5017,25 @@ var init_contracts = __esm({
|
|
|
5005
5017
|
replaces_seat: LowerCrockfordUlidSchema.optional(),
|
|
5006
5018
|
bounced_at: Rfc3339Schema.optional()
|
|
5007
5019
|
}).strict().superRefine((seat, context) => {
|
|
5008
|
-
if (seat.state === "
|
|
5020
|
+
if (seat.state === "pending") {
|
|
5021
|
+
for (const field of ["requested_at", "invite_sha256"]) {
|
|
5022
|
+
if (seat[field] === void 0) {
|
|
5023
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: [field], message: `pending seats require ${field}` });
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
for (const field of ["accepted_at", "removed_at", "removed_epoch", "bounced_at"]) {
|
|
5027
|
+
if (seat[field] !== void 0) {
|
|
5028
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: [field], message: `${field} is forbidden on pending seats` });
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
} else if (seat.state === "removed") {
|
|
5032
|
+
if (seat.accepted_at === void 0 && seat.requested_at === void 0) {
|
|
5033
|
+
context.addIssue({
|
|
5034
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5035
|
+
path: ["accepted_at"],
|
|
5036
|
+
message: "removed seats require accepted_at unless they are cancelled external admissions"
|
|
5037
|
+
});
|
|
5038
|
+
}
|
|
5009
5039
|
if (seat.removed_at === void 0) {
|
|
5010
5040
|
context.addIssue({
|
|
5011
5041
|
code: external_exports.ZodIssueCode.custom,
|
|
@@ -5021,6 +5051,9 @@ var init_contracts = __esm({
|
|
|
5021
5051
|
});
|
|
5022
5052
|
}
|
|
5023
5053
|
} else {
|
|
5054
|
+
if (seat.accepted_at === void 0) {
|
|
5055
|
+
context.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["accepted_at"], message: "active seats require accepted_at" });
|
|
5056
|
+
}
|
|
5024
5057
|
for (const field of ["removed_at", "removed_epoch", "bounced_at"]) {
|
|
5025
5058
|
if (seat[field] !== void 0) {
|
|
5026
5059
|
context.addIssue({
|
|
@@ -5031,6 +5064,13 @@ var init_contracts = __esm({
|
|
|
5031
5064
|
}
|
|
5032
5065
|
}
|
|
5033
5066
|
}
|
|
5067
|
+
if (seat.requested_at === void 0 !== (seat.invite_sha256 === void 0)) {
|
|
5068
|
+
context.addIssue({
|
|
5069
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5070
|
+
path: ["invite_sha256"],
|
|
5071
|
+
message: "external admission metadata requires both requested_at and invite_sha256"
|
|
5072
|
+
});
|
|
5073
|
+
}
|
|
5034
5074
|
});
|
|
5035
5075
|
RoomInviteSchema = external_exports.object({
|
|
5036
5076
|
invite_id: NonEmptyStringSchema,
|
|
@@ -5139,6 +5179,7 @@ var init_contracts = __esm({
|
|
|
5139
5179
|
refineRoomLineage(room, context);
|
|
5140
5180
|
const byParticipant = /* @__PURE__ */ new Map();
|
|
5141
5181
|
const activeAliases = /* @__PURE__ */ new Set();
|
|
5182
|
+
const authorizedCids = /* @__PURE__ */ new Set();
|
|
5142
5183
|
for (const [index, seat] of room.seats.entries()) {
|
|
5143
5184
|
if (byParticipant.has(seat.participant_id)) {
|
|
5144
5185
|
context.addIssue({
|
|
@@ -5148,6 +5189,16 @@ var init_contracts = __esm({
|
|
|
5148
5189
|
});
|
|
5149
5190
|
}
|
|
5150
5191
|
byParticipant.set(seat.participant_id, seat);
|
|
5192
|
+
if (seat.state === "pending" || seat.state === "active") {
|
|
5193
|
+
if (authorizedCids.has(seat.identity)) {
|
|
5194
|
+
context.addIssue({
|
|
5195
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5196
|
+
path: ["seats", index, "identity"],
|
|
5197
|
+
message: "at most one pending or active seat may exist per CID"
|
|
5198
|
+
});
|
|
5199
|
+
}
|
|
5200
|
+
authorizedCids.add(seat.identity);
|
|
5201
|
+
}
|
|
5151
5202
|
if (room.anonymous && seat.alias === void 0) {
|
|
5152
5203
|
context.addIssue({
|
|
5153
5204
|
code: external_exports.ZodIssueCode.custom,
|
|
@@ -5237,6 +5288,16 @@ var init_contracts = __esm({
|
|
|
5237
5288
|
PostMessageInputSchema = external_exports.object({
|
|
5238
5289
|
text: MessageTextSchema
|
|
5239
5290
|
}).strict();
|
|
5291
|
+
ContainerIdSchema = external_exports.string().regex(/^[0-9a-f]{64}$/i, "must be a 64-character hexadecimal CID").transform((value) => value.toUpperCase());
|
|
5292
|
+
AcceptExternalInviteInputSchema = external_exports.object({
|
|
5293
|
+
role: RoleSchema,
|
|
5294
|
+
invite: external_exports.string().refine(
|
|
5295
|
+
(value) => Buffer.byteLength(value, "utf8") <= MAX_EXTERNAL_INVITE_BYTES,
|
|
5296
|
+
`invite input must be at most ${MAX_EXTERNAL_INVITE_BYTES} UTF-8 bytes`
|
|
5297
|
+
),
|
|
5298
|
+
expected_cid: ContainerIdSchema.optional(),
|
|
5299
|
+
replaces_seat: LowerCrockfordUlidSchema.optional()
|
|
5300
|
+
}).strict();
|
|
5240
5301
|
AuthorSnapshotSchema = external_exports.object({
|
|
5241
5302
|
identity: NonEmptyStringSchema,
|
|
5242
5303
|
display_name: NonEmptyStringSchema,
|
|
@@ -6036,6 +6097,25 @@ var init_packets = __esm({
|
|
|
6036
6097
|
};
|
|
6037
6098
|
});
|
|
6038
6099
|
}
|
|
6100
|
+
async addContact(invite) {
|
|
6101
|
+
let decoded;
|
|
6102
|
+
try {
|
|
6103
|
+
decoded = unpackInvite(invite, MAX_EXTERNAL_INVITE_BYTES);
|
|
6104
|
+
} catch (error) {
|
|
6105
|
+
throw new Error("external invite could not be decoded within the 48 KiB limit", { cause: error });
|
|
6106
|
+
}
|
|
6107
|
+
return withScopeAsync(async (lifetime) => {
|
|
6108
|
+
const result = await this.packet.mutatingTx("::a2a_messaging::add_contact", {
|
|
6109
|
+
invite: this.packet.newBinary(decoded, lifetime)
|
|
6110
|
+
}, lifetime);
|
|
6111
|
+
return {
|
|
6112
|
+
invite_id: result.Reduce("invite_id").Visualize(),
|
|
6113
|
+
container_id: result.Reduce("container_id").Visualize(),
|
|
6114
|
+
inviter_name: result.Reduce("inviter_name").Visualize(),
|
|
6115
|
+
pending_name: result.Reduce("pending").Visualize()
|
|
6116
|
+
};
|
|
6117
|
+
});
|
|
6118
|
+
}
|
|
6039
6119
|
async revokeInvite(inviteId) {
|
|
6040
6120
|
return withScopeAsync(async (lifetime) => {
|
|
6041
6121
|
const result = await this.packet.mutatingTx("::a2a_messaging::revoke_invite", { invite_id: inviteId }, lifetime);
|
|
@@ -6639,6 +6719,7 @@ var init_intake = __esm({
|
|
|
6639
6719
|
});
|
|
6640
6720
|
|
|
6641
6721
|
// src/service.ts
|
|
6722
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
6642
6723
|
function byteBoundedHistoryPage(records) {
|
|
6643
6724
|
const page = [];
|
|
6644
6725
|
let bytes = 2;
|
|
@@ -6659,6 +6740,9 @@ function byteBoundedHistoryPage(records) {
|
|
|
6659
6740
|
function activeSeats(room) {
|
|
6660
6741
|
return room.seats.filter((seat) => seat.state === "active");
|
|
6661
6742
|
}
|
|
6743
|
+
function isCancelledExternalSeat(seat) {
|
|
6744
|
+
return seat.state === "removed" && seat.accepted_at === void 0 && seat.requested_at !== void 0 && seat.invite_sha256 !== void 0;
|
|
6745
|
+
}
|
|
6662
6746
|
function mintAlias(seated, role) {
|
|
6663
6747
|
return `${role} #${seated.filter((seat) => seat.role === role).length + 1}`;
|
|
6664
6748
|
}
|
|
@@ -6670,10 +6754,11 @@ function currentContactIdentities(packet) {
|
|
|
6670
6754
|
}
|
|
6671
6755
|
var ROOM_ROLE, CreateInviteInputSchema, HistoryOptionsSchema, DeleteRoomInputSchema, RemoveParticipantInputSchema, ReplaceParticipantInputSchema, RoomServiceError, RoomService;
|
|
6672
6756
|
var init_service = __esm({
|
|
6673
|
-
"src/service.ts"() {
|
|
6757
|
+
async "src/service.ts"() {
|
|
6674
6758
|
"use strict";
|
|
6675
6759
|
init_zod();
|
|
6676
6760
|
init_contracts();
|
|
6761
|
+
await init_adapt();
|
|
6677
6762
|
init_intake();
|
|
6678
6763
|
init_ulid();
|
|
6679
6764
|
ROOM_ROLE = "room";
|
|
@@ -6928,6 +7013,81 @@ var init_service = __esm({
|
|
|
6928
7013
|
});
|
|
6929
7014
|
});
|
|
6930
7015
|
}
|
|
7016
|
+
/** Redeem an external invite without retaining its secret-bearing bytes. */
|
|
7017
|
+
async acceptExternalInvite(roomId, input) {
|
|
7018
|
+
const id = LowerCrockfordUlidSchema.parse(roomId);
|
|
7019
|
+
const request = AcceptExternalInviteInputSchema.parse(input);
|
|
7020
|
+
let decoded;
|
|
7021
|
+
try {
|
|
7022
|
+
decoded = unpackInvite(request.invite, MAX_EXTERNAL_INVITE_BYTES);
|
|
7023
|
+
} catch {
|
|
7024
|
+
throw new RoomServiceError("external invite is invalid or exceeds the 48 KiB decoded limit");
|
|
7025
|
+
}
|
|
7026
|
+
const digest = createHash3("sha256").update(decoded).digest("hex");
|
|
7027
|
+
const receipt = await this.lock(id, async () => {
|
|
7028
|
+
const room = await this.store.load(id);
|
|
7029
|
+
this.assertMutable(room, "accept an external invite for");
|
|
7030
|
+
const contactsBeforeRedemption = new Set(
|
|
7031
|
+
this.packet(id).listContacts().map((contact) => contact.container_id)
|
|
7032
|
+
);
|
|
7033
|
+
const predecessor = request.replaces_seat === void 0 ? void 0 : room.seats.find((seat2) => seat2.participant_id === request.replaces_seat);
|
|
7034
|
+
if (request.replaces_seat !== void 0 && (!predecessor || predecessor.state !== "removed" || predecessor.role !== request.role)) {
|
|
7035
|
+
throw new RoomServiceError("external replacement must reference a removed seat with the configured role");
|
|
7036
|
+
}
|
|
7037
|
+
if (predecessor && room.seats.some((seat2) => seat2.replaces_seat === predecessor.participant_id && (seat2.state === "pending" || seat2.state === "active"))) {
|
|
7038
|
+
throw new RoomServiceError("external replacement seat already has a pending or active successor");
|
|
7039
|
+
}
|
|
7040
|
+
let added;
|
|
7041
|
+
try {
|
|
7042
|
+
added = await this.packet(id).addContact(request.invite);
|
|
7043
|
+
} catch {
|
|
7044
|
+
throw new RoomServiceError("external invite was rejected");
|
|
7045
|
+
}
|
|
7046
|
+
const cid = ContainerIdSchema.parse(added.container_id);
|
|
7047
|
+
if (request.expected_cid !== void 0 && cid !== request.expected_cid) {
|
|
7048
|
+
throw new RoomServiceError("external invite inviter CID did not match --expected-cid");
|
|
7049
|
+
}
|
|
7050
|
+
if (contactsBeforeRedemption.has(cid)) {
|
|
7051
|
+
throw new RoomServiceError("external invite inviter is already a contact; redemption provenance is ambiguous");
|
|
7052
|
+
}
|
|
7053
|
+
if (room.seats.some((seat2) => seat2.identity === cid && (seat2.state === "pending" || seat2.state === "active"))) {
|
|
7054
|
+
throw new RoomServiceError("external inviter already has a pending or active seat in this room");
|
|
7055
|
+
}
|
|
7056
|
+
const requestedAt = this.now();
|
|
7057
|
+
const participantId = LowerCrockfordUlidSchema.parse(generateUlid());
|
|
7058
|
+
const seated = room.seats;
|
|
7059
|
+
const pending = {
|
|
7060
|
+
identity: cid,
|
|
7061
|
+
display_name: added.inviter_name,
|
|
7062
|
+
role: request.role,
|
|
7063
|
+
invite_id: added.invite_id,
|
|
7064
|
+
requested_at: requestedAt,
|
|
7065
|
+
invite_sha256: digest,
|
|
7066
|
+
participant_id: participantId,
|
|
7067
|
+
state: "pending",
|
|
7068
|
+
...predecessor === void 0 ? {} : { replaces_seat: predecessor.participant_id },
|
|
7069
|
+
...room.anonymous ? { alias: predecessor?.alias ?? mintAlias(seated, request.role) } : {}
|
|
7070
|
+
};
|
|
7071
|
+
await this.store.save(RoomSchema.parse({ ...room, seats: [...room.seats, pending] }));
|
|
7072
|
+
const reconciled = await this.reconcileUnlocked(await this.store.load(id), this.packet(id));
|
|
7073
|
+
const seat = reconciled.seats.find((candidate) => candidate.participant_id === participantId);
|
|
7074
|
+
return {
|
|
7075
|
+
room_id: id,
|
|
7076
|
+
participant_id: participantId,
|
|
7077
|
+
state: seat.state,
|
|
7078
|
+
role: seat.role,
|
|
7079
|
+
identity: seat.identity,
|
|
7080
|
+
invite_id: seat.invite_id,
|
|
7081
|
+
inviter_name: added.inviter_name,
|
|
7082
|
+
pending_name: added.pending_name,
|
|
7083
|
+
requested_at: requestedAt,
|
|
7084
|
+
...seat.accepted_at === void 0 ? {} : { accepted_at: seat.accepted_at },
|
|
7085
|
+
...seat.replaces_seat === void 0 ? {} : { replaces_seat: seat.replaces_seat }
|
|
7086
|
+
};
|
|
7087
|
+
});
|
|
7088
|
+
await this.intake.resumePending(id);
|
|
7089
|
+
return receipt;
|
|
7090
|
+
}
|
|
6931
7091
|
async mintInviteUnlocked(room, request) {
|
|
6932
7092
|
const packet = this.packet(room.room_id);
|
|
6933
7093
|
const minted = await packet.mintInvite(request.mode);
|
|
@@ -6968,7 +7128,7 @@ var init_service = __esm({
|
|
|
6968
7128
|
const receipt = await this.lock(id, async () => {
|
|
6969
7129
|
const room = await this.store.load(id);
|
|
6970
7130
|
this.assertMutable(room, "remove a participant from");
|
|
6971
|
-
const seat = this.
|
|
7131
|
+
const seat = this.findSeatForRemoval(room, request.participant);
|
|
6972
7132
|
const notify = (request.notify ?? true) && !room.quiet_membership;
|
|
6973
7133
|
return this.beginRemovalUnlocked(room, seat, notify);
|
|
6974
7134
|
});
|
|
@@ -7001,6 +7161,16 @@ var init_service = __esm({
|
|
|
7001
7161
|
await this.intake.resumePending(id);
|
|
7002
7162
|
return receipt;
|
|
7003
7163
|
}
|
|
7164
|
+
findSeatForRemoval(room, participant) {
|
|
7165
|
+
const matches = (candidate) => candidate.identity === participant || candidate.participant_id === participant;
|
|
7166
|
+
const seat = room.seats.find((candidate) => candidate.state !== "removed" && matches(candidate)) ?? room.seats.find((candidate) => isCancelledExternalSeat(candidate) && matches(candidate));
|
|
7167
|
+
if (!seat) {
|
|
7168
|
+
throw new RoomServiceError(
|
|
7169
|
+
`"${participant}" is not a pending, active, or recoverable cancelled participant of room "${room.room_id}"`
|
|
7170
|
+
);
|
|
7171
|
+
}
|
|
7172
|
+
return seat;
|
|
7173
|
+
}
|
|
7004
7174
|
findActiveSeat(room, participant) {
|
|
7005
7175
|
const seat = room.seats.find((candidate) => candidate.state === "active" && (candidate.identity === participant || candidate.participant_id === participant));
|
|
7006
7176
|
if (!seat) {
|
|
@@ -7009,6 +7179,40 @@ var init_service = __esm({
|
|
|
7009
7179
|
return seat;
|
|
7010
7180
|
}
|
|
7011
7181
|
async beginRemovalUnlocked(room, seat, notify) {
|
|
7182
|
+
if (seat.state === "pending" || isCancelledExternalSeat(seat)) {
|
|
7183
|
+
let cancelled = seat;
|
|
7184
|
+
if (seat.state === "pending") {
|
|
7185
|
+
const removedAt = this.now();
|
|
7186
|
+
cancelled = {
|
|
7187
|
+
...seat,
|
|
7188
|
+
state: "removed",
|
|
7189
|
+
removed_at: removedAt,
|
|
7190
|
+
removed_epoch: room.membership_epoch
|
|
7191
|
+
};
|
|
7192
|
+
}
|
|
7193
|
+
const seats = room.seats.map((candidate) => candidate.participant_id === seat.participant_id ? cancelled : candidate);
|
|
7194
|
+
if (seat.state === "pending") await this.store.save(RoomSchema.parse({ ...room, seats }));
|
|
7195
|
+
const established = this.packet(room.room_id).listContacts().some((contact) => contact.container_id === seat.identity);
|
|
7196
|
+
if (established) {
|
|
7197
|
+
const outcome = await this.packet(room.room_id).removeContact(seat.identity);
|
|
7198
|
+
return {
|
|
7199
|
+
room_id: room.room_id,
|
|
7200
|
+
participant_id: seat.participant_id,
|
|
7201
|
+
epoch: room.membership_epoch,
|
|
7202
|
+
status: outcome.status,
|
|
7203
|
+
notified: outcome.notified,
|
|
7204
|
+
key_material_retained: true
|
|
7205
|
+
};
|
|
7206
|
+
}
|
|
7207
|
+
return {
|
|
7208
|
+
room_id: room.room_id,
|
|
7209
|
+
participant_id: seat.participant_id,
|
|
7210
|
+
epoch: room.membership_epoch,
|
|
7211
|
+
status: "cancelled_pending",
|
|
7212
|
+
notified: false,
|
|
7213
|
+
key_material_retained: true
|
|
7214
|
+
};
|
|
7215
|
+
}
|
|
7012
7216
|
const intent = await this.store.append(room.room_id, {
|
|
7013
7217
|
version: 1,
|
|
7014
7218
|
kind: "membership_intent",
|
|
@@ -7419,7 +7623,7 @@ var init_service = __esm({
|
|
|
7419
7623
|
}
|
|
7420
7624
|
const origins = packet.listContactOrigins();
|
|
7421
7625
|
const inviteById = new Map(room.invites.map((invite) => [invite.invite_id, invite]));
|
|
7422
|
-
const existingCids = new Set(room.seats.filter((seat) => seat.state === "active").map((seat) => seat.identity));
|
|
7626
|
+
const existingCids = new Set(room.seats.filter((seat) => seat.state === "active" || seat.state === "pending").map((seat) => seat.identity));
|
|
7423
7627
|
const lastRemovedAt = /* @__PURE__ */ new Map();
|
|
7424
7628
|
for (const seat of room.seats) {
|
|
7425
7629
|
if (seat.state !== "removed" || seat.removed_at === void 0) continue;
|
|
@@ -7428,6 +7632,35 @@ var init_service = __esm({
|
|
|
7428
7632
|
lastRemovedAt.set(seat.identity, seat.removed_at);
|
|
7429
7633
|
}
|
|
7430
7634
|
}
|
|
7635
|
+
const activatedPending = [];
|
|
7636
|
+
const seatsWithActivations = [...room.seats];
|
|
7637
|
+
for (const [seatIndex, seat] of room.seats.entries()) {
|
|
7638
|
+
if (seat.state !== "pending") continue;
|
|
7639
|
+
const displayName = contactsByCid.get(seat.identity);
|
|
7640
|
+
const origin = origins[seat.identity];
|
|
7641
|
+
if (displayName === void 0 || origin === void 0) continue;
|
|
7642
|
+
const completedExternalRedemption = origin.via === "invite_redeemed" && origin.invite_id === seat.invite_id;
|
|
7643
|
+
const fallbackInvite = origin.via === "invite_one_time" || origin.via === "invite_public" ? inviteById.get(origin.invite_id) : void 0;
|
|
7644
|
+
const confirmedFallback = fallbackInvite !== void 0 && fallbackInvite.state !== "receipt_pending" && (fallbackInvite.recovery_of === void 0 || fallbackInvite.recovery_confirmed === true);
|
|
7645
|
+
if (!completedExternalRedemption && !confirmedFallback) continue;
|
|
7646
|
+
const removedAt = lastRemovedAt.get(seat.identity);
|
|
7647
|
+
if (fallbackInvite !== void 0 && removedAt !== void 0 && fallbackInvite.created_at <= removedAt) continue;
|
|
7648
|
+
const predecessor = fallbackInvite?.replaces_seat === void 0 ? void 0 : room.seats.find((candidate) => candidate.participant_id === fallbackInvite.replaces_seat && candidate.state === "removed");
|
|
7649
|
+
if (fallbackInvite?.replaces_seat !== void 0 && predecessor === void 0) continue;
|
|
7650
|
+
const { alias: _alias, replaces_seat: _replacesSeat, ...base } = seat;
|
|
7651
|
+
const activated = {
|
|
7652
|
+
...base,
|
|
7653
|
+
state: "active",
|
|
7654
|
+
display_name: displayName,
|
|
7655
|
+
accepted_at: origin.at,
|
|
7656
|
+
role: fallbackInvite?.role ?? seat.role,
|
|
7657
|
+
invite_id: fallbackInvite?.invite_id ?? seat.invite_id,
|
|
7658
|
+
...fallbackInvite === void 0 ? _replacesSeat === void 0 ? {} : { replaces_seat: _replacesSeat } : predecessor === void 0 ? {} : { replaces_seat: predecessor.participant_id },
|
|
7659
|
+
...room.anonymous ? { alias: fallbackInvite === void 0 ? _alias : predecessor?.alias ?? (fallbackInvite.role === seat.role ? _alias : mintAlias(seatsWithActivations, fallbackInvite.role)) } : {}
|
|
7660
|
+
};
|
|
7661
|
+
activatedPending.push(activated);
|
|
7662
|
+
seatsWithActivations[seatIndex] = activated;
|
|
7663
|
+
}
|
|
7431
7664
|
const newSeats = [];
|
|
7432
7665
|
for (const [cid, displayName] of contactsByCid) {
|
|
7433
7666
|
if (existingCids.has(cid)) continue;
|
|
@@ -7437,7 +7670,7 @@ var init_service = __esm({
|
|
|
7437
7670
|
if (!invite || invite.state === "receipt_pending" || invite.recovery_of !== void 0 && invite.recovery_confirmed !== true) continue;
|
|
7438
7671
|
const removedAt = lastRemovedAt.get(cid);
|
|
7439
7672
|
if (removedAt !== void 0 && invite.created_at <= removedAt) continue;
|
|
7440
|
-
const seated = [...
|
|
7673
|
+
const seated = [...seatsWithActivations, ...newSeats];
|
|
7441
7674
|
const predecessor = invite.replaces_seat === void 0 ? void 0 : seated.find((seat) => seat.participant_id === invite.replaces_seat && seat.state === "removed");
|
|
7442
7675
|
if (invite.replaces_seat !== void 0 && !predecessor) continue;
|
|
7443
7676
|
newSeats.push({
|
|
@@ -7455,9 +7688,10 @@ var init_service = __esm({
|
|
|
7455
7688
|
});
|
|
7456
7689
|
existingCids.add(cid);
|
|
7457
7690
|
}
|
|
7458
|
-
const seats = [...
|
|
7691
|
+
const seats = [...seatsWithActivations, ...newSeats];
|
|
7459
7692
|
const acceptedByInvite = /* @__PURE__ */ new Map();
|
|
7460
7693
|
for (const seat of seats) {
|
|
7694
|
+
if (seat.state === "pending" || !inviteById.has(seat.invite_id)) continue;
|
|
7461
7695
|
const accepted = acceptedByInvite.get(seat.invite_id) ?? [];
|
|
7462
7696
|
if (!accepted.includes(seat.identity)) accepted.push(seat.identity);
|
|
7463
7697
|
acceptedByInvite.set(seat.invite_id, accepted);
|
|
@@ -7481,7 +7715,7 @@ var init_service = __esm({
|
|
|
7481
7715
|
...room,
|
|
7482
7716
|
seats,
|
|
7483
7717
|
invites,
|
|
7484
|
-
membership_epoch: room.membership_epoch + newSeats.length
|
|
7718
|
+
membership_epoch: room.membership_epoch + activatedPending.length + newSeats.length
|
|
7485
7719
|
});
|
|
7486
7720
|
const journal = await this.store.read(next.room_id);
|
|
7487
7721
|
const completedIntents = new Set(journal.filter((record) => record.kind === "membership_result").map((record) => record.kind === "membership_result" ? record.intent_record_id : ""));
|
|
@@ -7492,11 +7726,12 @@ var init_service = __esm({
|
|
|
7492
7726
|
({ room: next } = await this.completeRemovalUnlocked(next, intent));
|
|
7493
7727
|
}
|
|
7494
7728
|
const requirementsMet = invites.filter((invite) => invite.state !== "revoked").every((invite) => invite.accepted_cids.length >= invite.min_accepts);
|
|
7495
|
-
|
|
7729
|
+
const admitted = [...activatedPending, ...newSeats];
|
|
7730
|
+
if (next.state === "provisioning" && activeSeats(next).length > 0 && requirementsMet) {
|
|
7496
7731
|
const activationAt = await this.ensureActivationBriefing(next, activeSeats(next));
|
|
7497
7732
|
next = RoomSchema.parse({ ...next, state: "active", activated_at: activationAt });
|
|
7498
|
-
} else if (next.state === "active" &&
|
|
7499
|
-
await this.ensureActivationBriefing(next,
|
|
7733
|
+
} else if (next.state === "active" && admitted.length > 0) {
|
|
7734
|
+
await this.ensureActivationBriefing(next, admitted);
|
|
7500
7735
|
}
|
|
7501
7736
|
return this.store.save(next);
|
|
7502
7737
|
}
|
|
@@ -8609,6 +8844,14 @@ function createServiceRoutes(service) {
|
|
|
8609
8844
|
} }
|
|
8610
8845
|
};
|
|
8611
8846
|
}
|
|
8847
|
+
function createPrivateServiceRoutes(service) {
|
|
8848
|
+
return {
|
|
8849
|
+
"room.accept": { auth: true, run: (params) => {
|
|
8850
|
+
const { room_id, ...input } = ExternalInviteAcceptParams.parse(params);
|
|
8851
|
+
return service.acceptExternalInvite(room_id, input);
|
|
8852
|
+
} }
|
|
8853
|
+
};
|
|
8854
|
+
}
|
|
8612
8855
|
function errorResponse(id, code, message) {
|
|
8613
8856
|
return { version: 1, id, error: { code, message } };
|
|
8614
8857
|
}
|
|
@@ -8736,7 +8979,7 @@ function responseFinished2(response) {
|
|
|
8736
8979
|
response.once("error", rejectResponse);
|
|
8737
8980
|
});
|
|
8738
8981
|
}
|
|
8739
|
-
var MAX_REQUEST_BYTES, HTTP_HEADERS_TIMEOUT_MS, HTTP_REQUEST_TIMEOUT_MS, HTTP_BODY_IDLE_TIMEOUT_MS, HTTP_KEEP_ALIVE_TIMEOUT_MS, MAX_STALE_PRIVATE_SOCKET_CLEANUP, RpcIdSchema, RpcRequestSchema, RpcDispatcher, RoomIdParams, InviteRevokeParams, RecoverConfirmParams, HistoryParams, RoleBriefingSetParams, RoleBriefingDeleteParams, ParticipantRemoveParams, ParticipantReplaceParams, TransportServer;
|
|
8982
|
+
var MAX_REQUEST_BYTES, HTTP_HEADERS_TIMEOUT_MS, HTTP_REQUEST_TIMEOUT_MS, HTTP_BODY_IDLE_TIMEOUT_MS, HTTP_KEEP_ALIVE_TIMEOUT_MS, MAX_STALE_PRIVATE_SOCKET_CLEANUP, RpcIdSchema, RpcRequestSchema, RpcDispatcher, RoomIdParams, InviteRevokeParams, RecoverConfirmParams, HistoryParams, RoleBriefingSetParams, RoleBriefingDeleteParams, ParticipantRemoveParams, ParticipantReplaceParams, ExternalInviteAcceptParams, TransportServer;
|
|
8740
8983
|
var init_transports = __esm({
|
|
8741
8984
|
"src/transports.ts"() {
|
|
8742
8985
|
"use strict";
|
|
@@ -8835,6 +9078,13 @@ var init_transports = __esm({
|
|
|
8835
9078
|
mode: external_exports.enum(["one_time", "public"]).optional(),
|
|
8836
9079
|
min_accepts: external_exports.number().optional()
|
|
8837
9080
|
}).strict();
|
|
9081
|
+
ExternalInviteAcceptParams = external_exports.object({
|
|
9082
|
+
room_id: external_exports.string(),
|
|
9083
|
+
role: external_exports.string(),
|
|
9084
|
+
invite: external_exports.string(),
|
|
9085
|
+
expected_cid: external_exports.string().optional(),
|
|
9086
|
+
replaces_seat: external_exports.string().optional()
|
|
9087
|
+
}).strict();
|
|
8838
9088
|
TransportServer = class {
|
|
8839
9089
|
options;
|
|
8840
9090
|
fs;
|
|
@@ -9260,7 +9510,7 @@ import * as nodeFs6 from "node:fs";
|
|
|
9260
9510
|
import { join as join7 } from "node:path";
|
|
9261
9511
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9262
9512
|
function isIntakeNotification(event) {
|
|
9263
|
-
return event === "message_received" || event === "file_received" || event === "contact_accepted";
|
|
9513
|
+
return event === "message_received" || event === "file_received" || event === "contact_accepted" || event === "contact_added";
|
|
9264
9514
|
}
|
|
9265
9515
|
function createDaemonControlRoutes(control) {
|
|
9266
9516
|
if (!/^[0-9a-f]{32}$/.test(control.session)) throw new TypeError("invalid daemon control session");
|
|
@@ -9478,7 +9728,7 @@ var init_daemon_runtime = __esm({
|
|
|
9478
9728
|
init_config();
|
|
9479
9729
|
init_config();
|
|
9480
9730
|
await init_packets();
|
|
9481
|
-
init_service();
|
|
9731
|
+
await init_service();
|
|
9482
9732
|
init_storage();
|
|
9483
9733
|
init_transports();
|
|
9484
9734
|
init_web();
|
|
@@ -9582,7 +9832,7 @@ var init_daemon_runtime = __esm({
|
|
|
9582
9832
|
}
|
|
9583
9833
|
const realService = this.service;
|
|
9584
9834
|
const serviceRoutes = createServiceRoutes(realService);
|
|
9585
|
-
const unixRoutes = this.options.control ? { ...serviceRoutes, ...createDaemonControlRoutes(this.options.control) } : serviceRoutes;
|
|
9835
|
+
const unixRoutes = this.options.control ? { ...serviceRoutes, ...createPrivateServiceRoutes(realService), ...createDaemonControlRoutes(this.options.control) } : { ...serviceRoutes, ...createPrivateServiceRoutes(realService) };
|
|
9586
9836
|
const unixDispatcher = new RpcDispatcher(unixRoutes);
|
|
9587
9837
|
const restDispatcher = new RpcDispatcher(serviceRoutes);
|
|
9588
9838
|
const staticHandler = createStaticWebHandler(loadWebAssets(
|