zooid 0.7.1 → 0.7.3

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.
@@ -24181,6 +24181,39 @@ function parseTransport(name, raw, processEnv) {
24181
24181
  }
24182
24182
  return { type: "http", port };
24183
24183
  }
24184
+ function parseRoomBinding(path, raw, serverName) {
24185
+ function normalizeAlias(alias) {
24186
+ if (alias.length === 0) {
24187
+ throw new Error(`${path}: must be a non-empty alias`);
24188
+ }
24189
+ if (!MATRIX_ROOM_IDENT_RE.test(alias)) {
24190
+ throw new Error(
24191
+ `${path}: must start with '#' or '!' (got ${JSON.stringify(alias)})`
24192
+ );
24193
+ }
24194
+ return alias.includes(":") ? alias : `${alias}:${serverName}`;
24195
+ }
24196
+ if (typeof raw === "string") {
24197
+ return { alias: normalizeAlias(raw) };
24198
+ }
24199
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
24200
+ throw new Error(`${path}: must be a string or { alias, power_level } object`);
24201
+ }
24202
+ const r = raw;
24203
+ if (typeof r.alias !== "string" || r.alias.length === 0) {
24204
+ throw new Error(`${path}.alias: must be a non-empty string`);
24205
+ }
24206
+ const out = { alias: normalizeAlias(r.alias) };
24207
+ if (r.power_level !== void 0) {
24208
+ if (typeof r.power_level !== "number" || !Number.isInteger(r.power_level)) {
24209
+ throw new Error(
24210
+ `${path}.power_level: must be an integer (got ${JSON.stringify(r.power_level)})`
24211
+ );
24212
+ }
24213
+ out.powerLevel = r.power_level;
24214
+ }
24215
+ return out;
24216
+ }
24184
24217
  function parseTransportBinding(name, entry, transports) {
24185
24218
  const present = TRANSPORT_KINDS.filter(
24186
24219
  (k) => entry[k] !== void 0 && entry[k] !== null
@@ -24250,16 +24283,8 @@ function parseTransportBinding(name, entry, transports) {
24250
24283
  throw new Error(`agents.${name}.matrix.rooms is required and must be a non-empty array`);
24251
24284
  }
24252
24285
  const rooms = [];
24253
- for (const r of block.rooms) {
24254
- if (typeof r !== "string" || r.length === 0) {
24255
- throw new Error(`agents.${name}.matrix.rooms[] must be a non-empty string`);
24256
- }
24257
- if (!MATRIX_ROOM_IDENT_RE.test(r)) {
24258
- throw new Error(
24259
- `agents.${name}.matrix.rooms[] must start with '#' or '!' (got ${JSON.stringify(r)})`
24260
- );
24261
- }
24262
- rooms.push(r.includes(":") ? r : `${r}:${serverName}`);
24286
+ for (let i = 0; i < block.rooms.length; i++) {
24287
+ rooms.push(parseRoomBinding(`agents.${name}.matrix.rooms[${i}]`, block.rooms[i], serverName));
24263
24288
  }
24264
24289
  let displayName;
24265
24290
  if (block.display_name !== void 0) {
@@ -24785,6 +24810,22 @@ var MatrixClient = class {
24785
24810
  preset: opts.preset ?? "public_chat"
24786
24811
  };
24787
24812
  if (opts.name !== void 0) body.name = opts.name;
24813
+ if (opts.roomVersion !== void 0) body.room_version = opts.roomVersion;
24814
+ if (opts.restrictedToSpaceId !== void 0) {
24815
+ body.initial_state = [
24816
+ {
24817
+ type: "m.room.join_rules",
24818
+ state_key: "",
24819
+ content: {
24820
+ join_rule: "restricted",
24821
+ allow: [{ type: "m.room_membership", room_id: opts.restrictedToSpaceId }]
24822
+ }
24823
+ }
24824
+ ];
24825
+ }
24826
+ if (opts.userPowerLevels && Object.keys(opts.userPowerLevels).length > 0) {
24827
+ body.power_level_content_override = { users: opts.userPowerLevels };
24828
+ }
24788
24829
  const r = await this.fetch(
24789
24830
  `${this.homeserver}/_matrix/client/v3/createRoom?user_id=${encodeURIComponent(opts.senderUserId)}`,
24790
24831
  {
@@ -24830,6 +24871,31 @@ var MatrixClient = class {
24830
24871
  if (!r.ok) throw new Error(`sendStateEvent ${opts.eventType} failed: ${r.status}`);
24831
24872
  return await r.json();
24832
24873
  }
24874
+ /**
24875
+ * Invite a user to a room. Sent as the inviter (`asUserId`) — that user
24876
+ * needs invite power in the room. Tolerates the "already in room /
24877
+ * already invited" responses idempotently so bootstrap can run on a
24878
+ * fresh AND a populated homeserver without branching.
24879
+ */
24880
+ async invite(opts) {
24881
+ const url2 = `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(opts.roomId)}/invite?user_id=${encodeURIComponent(opts.asUserId)}`;
24882
+ const r = await this.fetch(url2, {
24883
+ method: "POST",
24884
+ headers: {
24885
+ Authorization: `Bearer ${this.asToken}`,
24886
+ "content-type": "application/json"
24887
+ },
24888
+ body: JSON.stringify({ user_id: opts.targetUserId })
24889
+ });
24890
+ if (r.ok) return;
24891
+ if (r.status === 403) {
24892
+ const body = await r.text();
24893
+ const idempotent = /already (in the room|invited|a member|joined)/i.test(body) || /user that is joined/i.test(body);
24894
+ if (idempotent) return;
24895
+ throw new Error(`invite(${opts.targetUserId}) failed: 403 ${body}`);
24896
+ }
24897
+ throw new Error(`invite(${opts.targetUserId}) failed: ${r.status}`);
24898
+ }
24833
24899
  async joinRoom(roomIdOrAlias, asUserId) {
24834
24900
  const url2 = `${this.homeserver}/_matrix/client/v3/join/${encodeURIComponent(roomIdOrAlias)}?user_id=${encodeURIComponent(asUserId)}`;
24835
24901
  const r = await this.fetch(url2, {
@@ -25158,7 +25224,7 @@ function route(event, agents, threadStates) {
25158
25224
  const threadState = threadRoot ? threadStates?.get(threadRoot) : void 0;
25159
25225
  for (const a of agents) {
25160
25226
  if (event.sender === a.userId) continue;
25161
- if (!a.rooms.includes(event.room_id ?? "")) continue;
25227
+ if (!a.rooms.some((r) => r.alias === event.room_id)) continue;
25162
25228
  if (a.trigger === "any") {
25163
25229
  matches.push(a);
25164
25230
  continue;
@@ -25185,15 +25251,50 @@ async function ensureWorkforceSpace(opts) {
25185
25251
  const existing = await opts.client.resolveAlias(alias);
25186
25252
  if (existing) return existing;
25187
25253
  const display = opts.spaceLocalpart.charAt(0).toUpperCase() + opts.spaceLocalpart.slice(1);
25188
- return opts.client.createRoomRaw({
25254
+ const body = {
25255
+ room_alias_name: opts.spaceLocalpart,
25256
+ name: display,
25257
+ preset: opts.preset,
25258
+ creation_content: { type: "m.space" },
25259
+ // A workspace is joined by invitation, not self-service. Pin the space's
25260
+ // join rule to invite regardless of preset so it can't be walked into
25261
+ // (which would otherwise satisfy every restricted child room's allow).
25262
+ initial_state: [{ type: "m.room.join_rules", state_key: "", content: { join_rule: "invite" } }]
25263
+ };
25264
+ if (opts.admins && opts.admins.length > 0) {
25265
+ body.invite = opts.admins;
25266
+ const users = { [opts.asUserId]: 100 };
25267
+ for (const a of opts.admins) users[a] = 100;
25268
+ body.power_level_content_override = { users };
25269
+ }
25270
+ return opts.client.createRoomRaw({ asUserId: opts.asUserId, body });
25271
+ }
25272
+ async function ensureDefaultChannel(opts) {
25273
+ const localpart2 = opts.channelLocalpart ?? "general";
25274
+ const alias = `#${localpart2}:${opts.serverName}`;
25275
+ const existing = await opts.client.resolveAlias(alias);
25276
+ if (existing) return existing;
25277
+ let userPowerLevels;
25278
+ if (opts.admins && opts.admins.length > 0) {
25279
+ userPowerLevels = { [opts.asUserId]: 100 };
25280
+ for (const a of opts.admins) userPowerLevels[a] = 100;
25281
+ }
25282
+ const roomId = await opts.client.createRoom({
25283
+ roomAliasName: localpart2,
25284
+ invite: [],
25285
+ senderUserId: opts.asUserId,
25286
+ name: localpart2.charAt(0).toUpperCase() + localpart2.slice(1),
25287
+ restrictedToSpaceId: opts.spaceId,
25288
+ ...userPowerLevels ? { userPowerLevels } : {}
25289
+ });
25290
+ await opts.client.sendStateEvent({
25291
+ roomId: opts.spaceId,
25189
25292
  asUserId: opts.asUserId,
25190
- body: {
25191
- room_alias_name: opts.spaceLocalpart,
25192
- name: display,
25193
- preset: opts.preset,
25194
- creation_content: { type: "m.space" }
25195
- }
25293
+ eventType: "m.space.child",
25294
+ stateKey: roomId,
25295
+ content: { via: [opts.serverName] }
25196
25296
  });
25297
+ return roomId;
25197
25298
  }
25198
25299
  function serverNameFromMxid(mxid) {
25199
25300
  const colon = mxid.indexOf(":");
@@ -25224,8 +25325,23 @@ var BotPool = class {
25224
25325
  } catch (err) {
25225
25326
  console.warn(`[matrix] setDisplayName(${a.userId}) failed: ${err.message}`);
25226
25327
  }
25328
+ if (opts.spaceRoomId && opts.asUserId) {
25329
+ try {
25330
+ await this.client.invite({
25331
+ roomId: opts.spaceRoomId,
25332
+ asUserId: opts.asUserId,
25333
+ targetUserId: a.userId
25334
+ });
25335
+ await this.client.joinRoom(opts.spaceRoomId, a.userId);
25336
+ } catch (err) {
25337
+ console.warn(
25338
+ `[matrix] space membership for ${a.userId} failed: ${err.message}`
25339
+ );
25340
+ }
25341
+ }
25227
25342
  for (let i = 0; i < a.rooms.length; i++) {
25228
- const room = a.rooms[i];
25343
+ const binding = a.rooms[i];
25344
+ const room = binding.alias;
25229
25345
  try {
25230
25346
  let resolved = room;
25231
25347
  if (room.startsWith("#")) {
@@ -25240,17 +25356,25 @@ var BotPool = class {
25240
25356
  const colon = room.indexOf(":");
25241
25357
  const aliasLocalpart = colon > 1 ? room.slice(1, colon) : room.slice(1);
25242
25358
  const sender = opts.adminUserId ?? a.userId;
25359
+ const userPowerLevels = buildUserPowerLevels(
25360
+ opts.asUserId,
25361
+ opts.adminUserIds,
25362
+ this.agents,
25363
+ room
25364
+ );
25243
25365
  resolved = await this.client.createRoom({
25244
25366
  roomAliasName: aliasLocalpart,
25245
25367
  invite: opts.adminUserId ? [opts.adminUserId] : [],
25246
25368
  senderUserId: sender,
25247
- name: aliasLocalpart
25369
+ name: aliasLocalpart,
25370
+ ...opts.spaceRoomId ? { restrictedToSpaceId: opts.spaceRoomId } : {},
25371
+ ...userPowerLevels ? { userPowerLevels } : {}
25248
25372
  });
25249
25373
  }
25250
25374
  aliasToId.set(room, resolved);
25251
25375
  }
25252
25376
  }
25253
- a.rooms[i] = resolved;
25377
+ binding.alias = resolved;
25254
25378
  await this.client.joinRoom(resolved, a.userId);
25255
25379
  if (opts.spaceRoomId && opts.asUserId && !attachedToSpace.has(resolved)) {
25256
25380
  attachedToSpace.add(resolved);
@@ -25289,6 +25413,18 @@ function localpart(userId) {
25289
25413
  if (!m) throw new Error(`bad user id: ${userId}`);
25290
25414
  return m[1];
25291
25415
  }
25416
+ function buildUserPowerLevels(asUserId, admins, agents, roomAlias) {
25417
+ const users = {};
25418
+ if (asUserId) users[asUserId] = 100;
25419
+ if (admins) for (const a of admins) users[a] = 100;
25420
+ for (const a of agents) {
25421
+ for (const r of a.rooms) {
25422
+ if (r.alias !== roomAlias) continue;
25423
+ if (r.powerLevel !== void 0) users[a.userId] = r.powerLevel;
25424
+ }
25425
+ }
25426
+ return Object.keys(users).length > 0 ? users : void 0;
25427
+ }
25292
25428
 
25293
25429
  // ../transport-matrix/src/transport.ts
25294
25430
  import { Hono } from "hono";
@@ -25439,12 +25575,17 @@ function toMatrixHtml(markdown) {
25439
25575
  // ../transport-matrix/src/transport.ts
25440
25576
  var STARTUP_GRACE_MS = 5e3;
25441
25577
  var SEEN_EVENT_CAP = 5e3;
25578
+ var DRAIN_QUIET_MS = 300;
25579
+ var DRAIN_MAX_MS = 3e4;
25580
+ var delay = (ms) => new Promise((r) => setTimeout(r, ms));
25442
25581
  function inboundThreadRoot2(evt) {
25443
25582
  const r = evt.content?.["m.relates_to"];
25444
25583
  return r?.rel_type === "m.thread" && r.event_id ? r.event_id : void 0;
25445
25584
  }
25446
25585
  function createMatrixTransport(opts) {
25447
25586
  const { agents, approvals, client, bindings, hsToken, adminUserId } = opts;
25587
+ const drainQuietMs = opts.drainQuietMs ?? DRAIN_QUIET_MS;
25588
+ const drainMaxMs = opts.drainMaxMs ?? DRAIN_MAX_MS;
25448
25589
  const pool = new BotPool(client, bindings);
25449
25590
  const sessions = /* @__PURE__ */ new Map();
25450
25591
  const buffers = /* @__PURE__ */ new Map();
@@ -25726,6 +25867,14 @@ function createMatrixTransport(opts) {
25726
25867
  channelId: evt.room_id,
25727
25868
  content: [{ type: "text", text: promptText }]
25728
25869
  });
25870
+ const drainStart = Date.now();
25871
+ let drained = buffers.get(sessionId) ?? "";
25872
+ while (drainQuietMs > 0 && Date.now() - drainStart < drainMaxMs) {
25873
+ await delay(drainQuietMs);
25874
+ const next = buffers.get(sessionId) ?? "";
25875
+ if (next === drained && next.length > 0) break;
25876
+ drained = next;
25877
+ }
25729
25878
  const text = buffers.get(sessionId) ?? "";
25730
25879
  if (text.length > 0) {
25731
25880
  const html = toMatrixHtml(text);
@@ -25777,7 +25926,7 @@ function createMatrixTransport(opts) {
25777
25926
  }
25778
25927
  async function rebuildThreadState(client, roomId, rootEventId, bindings) {
25779
25928
  const state = { participants: [], rootMentions: [] };
25780
- const asUser = (bindings.find((b) => b.rooms.includes(roomId)) ?? bindings[0])?.userId;
25929
+ const asUser = (bindings.find((b) => b.rooms.some((r) => r.alias === roomId)) ?? bindings[0])?.userId;
25781
25930
  if (!asUser) return state;
25782
25931
  const root = await client.fetchEvent(roomId, rootEventId, asUser);
25783
25932
  if (root) {
@@ -25832,7 +25981,11 @@ function truncate(s, n) {
25832
25981
  function buildWorkforceRoster(agents) {
25833
25982
  return {
25834
25983
  version: 1,
25835
- agents: agents.map((a) => ({ user_id: a.userId, name: a.name, rooms: a.rooms }))
25984
+ agents: agents.map((a) => ({
25985
+ user_id: a.userId,
25986
+ name: a.name,
25987
+ rooms: a.rooms.map((r) => r.alias)
25988
+ }))
25836
25989
  };
25837
25990
  }
25838
25991
  async function publishWorkforce(opts) {
@@ -31425,10 +31578,11 @@ export {
31425
31578
  MatrixClient,
31426
31579
  renderRegistration,
31427
31580
  ensureWorkforceSpace,
31581
+ ensureDefaultChannel,
31428
31582
  createMatrixTransport,
31429
31583
  startWorkforcePublisher,
31430
31584
  SpawnRegistry,
31431
31585
  startDaemonSocketServer,
31432
31586
  buildAcpRegistry
31433
31587
  };
31434
- //# sourceMappingURL=chunk-SUPTPSN3.js.map
31588
+ //# sourceMappingURL=chunk-3IKBBKGI.js.map