@zooid/transport-matrix 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.
- package/dist/index.d.ts +78 -4
- package/dist/index.js +142 -13
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/bot-pool.test.ts +200 -13
- package/src/bot-pool.ts +73 -5
- package/src/index.ts +2 -2
- package/src/matrix-client.test.ts +184 -0
- package/src/matrix-client.ts +75 -0
- package/src/router.test.ts +3 -3
- package/src/router.ts +11 -2
- package/src/space-provisioner.test.ts +191 -1
- package/src/space-provisioner.ts +77 -7
- package/src/transport.test.ts +58 -2
- package/src/transport.ts +56 -2
- package/src/workforce-publisher.test.ts +12 -2
- package/src/workforce-publisher.ts +5 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TransportContextProvider, HistoryOptions, HistoryPage, ThreadOverviewPage, Member, ChannelInfo, AcpRegistry, ApprovalCorrelator } from '@zooid/core';
|
|
1
|
+
import { TransportContextProvider, HistoryOptions, HistoryPage, ThreadOverviewPage, Member, ChannelInfo, RoomBinding, AcpRegistry, ApprovalCorrelator } from '@zooid/core';
|
|
2
2
|
import * as hono_types from 'hono/types';
|
|
3
3
|
import { Hono } from 'hono';
|
|
4
4
|
|
|
@@ -53,6 +53,20 @@ declare class MatrixClient {
|
|
|
53
53
|
/** Optional `m.room.name`. When set, sent in the createRoom body so the
|
|
54
54
|
* room has a display name from the moment it exists. */
|
|
55
55
|
name?: string;
|
|
56
|
+
/** When set, the room is created with a `restricted` join rule whose allow
|
|
57
|
+
* condition references this space room ID — i.e. joinable by space members
|
|
58
|
+
* only, rather than the whole homeserver. */
|
|
59
|
+
restrictedToSpaceId?: string;
|
|
60
|
+
/** Explicit room version. Restricted join rules require v8+; omit to use the
|
|
61
|
+
* homeserver default (modern Tuwunel defaults to v10/v11). */
|
|
62
|
+
roomVersion?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Seeds `m.room.power_levels.users` at creation via
|
|
65
|
+
* `power_level_content_override.users`. The caller owns the full map —
|
|
66
|
+
* typically the AS bot at 100, plus operator and any agents with
|
|
67
|
+
* declared PLs. Empty/absent → no override (the preset's defaults apply).
|
|
68
|
+
*/
|
|
69
|
+
userPowerLevels?: Record<string, number>;
|
|
56
70
|
}): Promise<string>;
|
|
57
71
|
createRoomRaw(opts: {
|
|
58
72
|
asUserId: string;
|
|
@@ -67,6 +81,17 @@ declare class MatrixClient {
|
|
|
67
81
|
}): Promise<{
|
|
68
82
|
event_id: string;
|
|
69
83
|
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Invite a user to a room. Sent as the inviter (`asUserId`) — that user
|
|
86
|
+
* needs invite power in the room. Tolerates the "already in room /
|
|
87
|
+
* already invited" responses idempotently so bootstrap can run on a
|
|
88
|
+
* fresh AND a populated homeserver without branching.
|
|
89
|
+
*/
|
|
90
|
+
invite(opts: {
|
|
91
|
+
roomId: string;
|
|
92
|
+
asUserId: string;
|
|
93
|
+
targetUserId: string;
|
|
94
|
+
}): Promise<void>;
|
|
70
95
|
joinRoom(roomIdOrAlias: string, asUserId: string): Promise<void>;
|
|
71
96
|
sendMessage(input: SendMessageInput): Promise<{
|
|
72
97
|
event_id: string;
|
|
@@ -188,7 +213,13 @@ interface AgentBinding {
|
|
|
188
213
|
userId: string;
|
|
189
214
|
/** Optional human-readable display name. Falls back to the user_id localpart. */
|
|
190
215
|
displayName?: string;
|
|
191
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Rooms this agent is bound to. Each entry's `alias` starts out as the
|
|
218
|
+
* configured `#alias` (or `!id`) and is rewritten to the canonical room
|
|
219
|
+
* ID by `BotPool.bootstrap`. Optional `powerLevel` is seeded into the
|
|
220
|
+
* room's `m.room.power_levels.users` at room creation only.
|
|
221
|
+
*/
|
|
222
|
+
rooms: RoomBinding[];
|
|
192
223
|
trigger: 'mention' | 'any';
|
|
193
224
|
}
|
|
194
225
|
interface ThreadState {
|
|
@@ -219,11 +250,17 @@ interface BootstrapOpts {
|
|
|
219
250
|
spaceRoomId?: string;
|
|
220
251
|
/** AS bot user ID. Required when spaceRoomId is set; sender of the m.space.child write. */
|
|
221
252
|
asUserId?: string;
|
|
253
|
+
/**
|
|
254
|
+
* Operator MXIDs seeded at PL 100 in every agent room this pool creates.
|
|
255
|
+
* Applied via `power_level_content_override.users` at room creation only —
|
|
256
|
+
* never reconciled. Empty/absent = no operator entries.
|
|
257
|
+
*/
|
|
258
|
+
adminUserIds?: string[];
|
|
222
259
|
}
|
|
223
260
|
declare class BotPool {
|
|
224
261
|
private readonly client;
|
|
225
262
|
private readonly agents;
|
|
226
|
-
constructor(client: Pick<MatrixClient, 'registerBot' | 'joinRoom' | 'resolveAlias' | 'createRoom' | 'sendStateEvent' | 'setDisplayName'>, agents: AgentBinding[]);
|
|
263
|
+
constructor(client: Pick<MatrixClient, 'registerBot' | 'invite' | 'joinRoom' | 'resolveAlias' | 'createRoom' | 'sendStateEvent' | 'setDisplayName'>, agents: AgentBinding[]);
|
|
227
264
|
bootstrap(opts?: BootstrapOpts): Promise<void>;
|
|
228
265
|
findByUserId(userId: string): AgentBinding | undefined;
|
|
229
266
|
findByName(name: string): AgentBinding | undefined;
|
|
@@ -237,12 +274,19 @@ interface CreateMatrixTransportOptions {
|
|
|
237
274
|
hsToken: string;
|
|
238
275
|
/** Admin Matrix user ID. When set, BotPool.bootstrap invites this user into rooms it creates. */
|
|
239
276
|
adminUserId?: string;
|
|
277
|
+
/** Post-turn drain: keep collecting trailing `agent_message_chunk`s until the
|
|
278
|
+
* buffer is quiet for this long before flushing. Defaults to `DRAIN_QUIET_MS`.
|
|
279
|
+
* Set to 0 to disable the drain (e.g. in tests). */
|
|
280
|
+
drainQuietMs?: number;
|
|
281
|
+
/** Hard cap on the post-turn drain. Defaults to `DRAIN_MAX_MS`. */
|
|
282
|
+
drainMaxMs?: number;
|
|
240
283
|
}
|
|
241
284
|
declare function createMatrixTransport(opts: CreateMatrixTransportOptions): {
|
|
242
285
|
app: Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
243
286
|
bootstrap: (bootstrapOpts?: {
|
|
244
287
|
spaceRoomId?: string;
|
|
245
288
|
asUserId?: string;
|
|
289
|
+
adminUserIds?: string[];
|
|
246
290
|
}) => Promise<void>;
|
|
247
291
|
pool: BotPool;
|
|
248
292
|
};
|
|
@@ -253,8 +297,38 @@ interface EnsureSpaceOpts {
|
|
|
253
297
|
serverName: string;
|
|
254
298
|
spaceLocalpart: string;
|
|
255
299
|
preset: 'public_chat' | 'private_chat';
|
|
300
|
+
/**
|
|
301
|
+
* Operator MXIDs to seed at PL 100 in the space's `m.room.power_levels`
|
|
302
|
+
* at creation. The AS bot is always included. Empty/absent → no override
|
|
303
|
+
* (the preset's PL defaults apply). Only consulted on first creation —
|
|
304
|
+
* if the alias already resolves we return the existing room untouched.
|
|
305
|
+
*/
|
|
306
|
+
admins?: string[];
|
|
256
307
|
}
|
|
257
308
|
declare function ensureWorkforceSpace(opts: EnsureSpaceOpts): Promise<string>;
|
|
309
|
+
interface EnsureDefaultChannelOpts {
|
|
310
|
+
client: MatrixClient;
|
|
311
|
+
asUserId: string;
|
|
312
|
+
serverName: string;
|
|
313
|
+
spaceId: string;
|
|
314
|
+
/** Localpart of the default channel; defaults to `general`. */
|
|
315
|
+
channelLocalpart?: string;
|
|
316
|
+
/**
|
|
317
|
+
* Operator MXIDs to seed at PL 100 in the channel's `m.room.power_levels`
|
|
318
|
+
* at creation. The AS bot is always included. Empty/absent → no override
|
|
319
|
+
* (the preset's PL defaults apply). Only consulted on first creation —
|
|
320
|
+
* if the alias already resolves we return the existing room untouched.
|
|
321
|
+
*/
|
|
322
|
+
admins?: string[];
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Ensure a space has a default channel (`#general` by default), restricted to
|
|
326
|
+
* the space's members and attached as an `m.space.child`. Idempotent: returns
|
|
327
|
+
* the existing room if the alias already resolves. Has no agent — it's the
|
|
328
|
+
* human landing room, so it is created here at provisioning time rather than
|
|
329
|
+
* via the agent-room path.
|
|
330
|
+
*/
|
|
331
|
+
declare function ensureDefaultChannel(opts: EnsureDefaultChannelOpts): Promise<string>;
|
|
258
332
|
declare function serverNameFromMxid(mxid: string): string;
|
|
259
333
|
|
|
260
334
|
interface WorkforceRoster {
|
|
@@ -285,4 +359,4 @@ interface StartOpts {
|
|
|
285
359
|
}
|
|
286
360
|
declare function startWorkforcePublisher(opts: StartOpts): Promise<PublisherHandle>;
|
|
287
361
|
|
|
288
|
-
export { type AgentBinding, BotPool, type CreateMatrixTransportOptions, type EnsureSpaceOpts, MatrixClient, type MatrixClientOptions, MatrixContextProvider, type MatrixContextProviderOpts, type MatrixTransportConfig, type MaybeMessage, type PublishOpts, type PublisherHandle, type RouteMatch, type SendCustomEventInput, type SendMessageInput, type StartOpts as StartWorkforcePublisherOpts, type WorkforceRoster, buildWorkforceRoster, createMatrixTransport, ensureWorkforceSpace, extractMentions, publishWorkforce, renderRegistration, route, serverNameFromMxid, startWorkforcePublisher };
|
|
362
|
+
export { type AgentBinding, BotPool, type CreateMatrixTransportOptions, type EnsureDefaultChannelOpts, type EnsureSpaceOpts, MatrixClient, type MatrixClientOptions, MatrixContextProvider, type MatrixContextProviderOpts, type MatrixTransportConfig, type MaybeMessage, type PublishOpts, type PublisherHandle, type RouteMatch, type SendCustomEventInput, type SendMessageInput, type StartOpts as StartWorkforcePublisherOpts, type WorkforceRoster, buildWorkforceRoster, createMatrixTransport, ensureDefaultChannel, ensureWorkforceSpace, extractMentions, publishWorkforce, renderRegistration, route, serverNameFromMxid, startWorkforcePublisher };
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,22 @@ var MatrixClient = class {
|
|
|
39
39
|
preset: opts.preset ?? "public_chat"
|
|
40
40
|
};
|
|
41
41
|
if (opts.name !== void 0) body.name = opts.name;
|
|
42
|
+
if (opts.roomVersion !== void 0) body.room_version = opts.roomVersion;
|
|
43
|
+
if (opts.restrictedToSpaceId !== void 0) {
|
|
44
|
+
body.initial_state = [
|
|
45
|
+
{
|
|
46
|
+
type: "m.room.join_rules",
|
|
47
|
+
state_key: "",
|
|
48
|
+
content: {
|
|
49
|
+
join_rule: "restricted",
|
|
50
|
+
allow: [{ type: "m.room_membership", room_id: opts.restrictedToSpaceId }]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
if (opts.userPowerLevels && Object.keys(opts.userPowerLevels).length > 0) {
|
|
56
|
+
body.power_level_content_override = { users: opts.userPowerLevels };
|
|
57
|
+
}
|
|
42
58
|
const r = await this.fetch(
|
|
43
59
|
`${this.homeserver}/_matrix/client/v3/createRoom?user_id=${encodeURIComponent(opts.senderUserId)}`,
|
|
44
60
|
{
|
|
@@ -84,6 +100,31 @@ var MatrixClient = class {
|
|
|
84
100
|
if (!r.ok) throw new Error(`sendStateEvent ${opts.eventType} failed: ${r.status}`);
|
|
85
101
|
return await r.json();
|
|
86
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Invite a user to a room. Sent as the inviter (`asUserId`) — that user
|
|
105
|
+
* needs invite power in the room. Tolerates the "already in room /
|
|
106
|
+
* already invited" responses idempotently so bootstrap can run on a
|
|
107
|
+
* fresh AND a populated homeserver without branching.
|
|
108
|
+
*/
|
|
109
|
+
async invite(opts) {
|
|
110
|
+
const url = `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(opts.roomId)}/invite?user_id=${encodeURIComponent(opts.asUserId)}`;
|
|
111
|
+
const r = await this.fetch(url, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: {
|
|
114
|
+
Authorization: `Bearer ${this.asToken}`,
|
|
115
|
+
"content-type": "application/json"
|
|
116
|
+
},
|
|
117
|
+
body: JSON.stringify({ user_id: opts.targetUserId })
|
|
118
|
+
});
|
|
119
|
+
if (r.ok) return;
|
|
120
|
+
if (r.status === 403) {
|
|
121
|
+
const body = await r.text();
|
|
122
|
+
const idempotent = /already (in the room|invited|a member|joined)/i.test(body) || /user that is joined/i.test(body);
|
|
123
|
+
if (idempotent) return;
|
|
124
|
+
throw new Error(`invite(${opts.targetUserId}) failed: 403 ${body}`);
|
|
125
|
+
}
|
|
126
|
+
throw new Error(`invite(${opts.targetUserId}) failed: ${r.status}`);
|
|
127
|
+
}
|
|
87
128
|
async joinRoom(roomIdOrAlias, asUserId) {
|
|
88
129
|
const url = `${this.homeserver}/_matrix/client/v3/join/${encodeURIComponent(roomIdOrAlias)}?user_id=${encodeURIComponent(asUserId)}`;
|
|
89
130
|
const r = await this.fetch(url, {
|
|
@@ -412,7 +453,7 @@ function route(event, agents, threadStates) {
|
|
|
412
453
|
const threadState = threadRoot ? threadStates?.get(threadRoot) : void 0;
|
|
413
454
|
for (const a of agents) {
|
|
414
455
|
if (event.sender === a.userId) continue;
|
|
415
|
-
if (!a.rooms.
|
|
456
|
+
if (!a.rooms.some((r) => r.alias === event.room_id)) continue;
|
|
416
457
|
if (a.trigger === "any") {
|
|
417
458
|
matches.push(a);
|
|
418
459
|
continue;
|
|
@@ -439,15 +480,50 @@ async function ensureWorkforceSpace(opts) {
|
|
|
439
480
|
const existing = await opts.client.resolveAlias(alias);
|
|
440
481
|
if (existing) return existing;
|
|
441
482
|
const display = opts.spaceLocalpart.charAt(0).toUpperCase() + opts.spaceLocalpart.slice(1);
|
|
442
|
-
|
|
483
|
+
const body = {
|
|
484
|
+
room_alias_name: opts.spaceLocalpart,
|
|
485
|
+
name: display,
|
|
486
|
+
preset: opts.preset,
|
|
487
|
+
creation_content: { type: "m.space" },
|
|
488
|
+
// A workspace is joined by invitation, not self-service. Pin the space's
|
|
489
|
+
// join rule to invite regardless of preset so it can't be walked into
|
|
490
|
+
// (which would otherwise satisfy every restricted child room's allow).
|
|
491
|
+
initial_state: [{ type: "m.room.join_rules", state_key: "", content: { join_rule: "invite" } }]
|
|
492
|
+
};
|
|
493
|
+
if (opts.admins && opts.admins.length > 0) {
|
|
494
|
+
body.invite = opts.admins;
|
|
495
|
+
const users = { [opts.asUserId]: 100 };
|
|
496
|
+
for (const a of opts.admins) users[a] = 100;
|
|
497
|
+
body.power_level_content_override = { users };
|
|
498
|
+
}
|
|
499
|
+
return opts.client.createRoomRaw({ asUserId: opts.asUserId, body });
|
|
500
|
+
}
|
|
501
|
+
async function ensureDefaultChannel(opts) {
|
|
502
|
+
const localpart2 = opts.channelLocalpart ?? "general";
|
|
503
|
+
const alias = `#${localpart2}:${opts.serverName}`;
|
|
504
|
+
const existing = await opts.client.resolveAlias(alias);
|
|
505
|
+
if (existing) return existing;
|
|
506
|
+
let userPowerLevels;
|
|
507
|
+
if (opts.admins && opts.admins.length > 0) {
|
|
508
|
+
userPowerLevels = { [opts.asUserId]: 100 };
|
|
509
|
+
for (const a of opts.admins) userPowerLevels[a] = 100;
|
|
510
|
+
}
|
|
511
|
+
const roomId = await opts.client.createRoom({
|
|
512
|
+
roomAliasName: localpart2,
|
|
513
|
+
invite: [],
|
|
514
|
+
senderUserId: opts.asUserId,
|
|
515
|
+
name: localpart2.charAt(0).toUpperCase() + localpart2.slice(1),
|
|
516
|
+
restrictedToSpaceId: opts.spaceId,
|
|
517
|
+
...userPowerLevels ? { userPowerLevels } : {}
|
|
518
|
+
});
|
|
519
|
+
await opts.client.sendStateEvent({
|
|
520
|
+
roomId: opts.spaceId,
|
|
443
521
|
asUserId: opts.asUserId,
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
preset: opts.preset,
|
|
448
|
-
creation_content: { type: "m.space" }
|
|
449
|
-
}
|
|
522
|
+
eventType: "m.space.child",
|
|
523
|
+
stateKey: roomId,
|
|
524
|
+
content: { via: [opts.serverName] }
|
|
450
525
|
});
|
|
526
|
+
return roomId;
|
|
451
527
|
}
|
|
452
528
|
function serverNameFromMxid(mxid) {
|
|
453
529
|
const colon = mxid.indexOf(":");
|
|
@@ -478,8 +554,23 @@ var BotPool = class {
|
|
|
478
554
|
} catch (err) {
|
|
479
555
|
console.warn(`[matrix] setDisplayName(${a.userId}) failed: ${err.message}`);
|
|
480
556
|
}
|
|
557
|
+
if (opts.spaceRoomId && opts.asUserId) {
|
|
558
|
+
try {
|
|
559
|
+
await this.client.invite({
|
|
560
|
+
roomId: opts.spaceRoomId,
|
|
561
|
+
asUserId: opts.asUserId,
|
|
562
|
+
targetUserId: a.userId
|
|
563
|
+
});
|
|
564
|
+
await this.client.joinRoom(opts.spaceRoomId, a.userId);
|
|
565
|
+
} catch (err) {
|
|
566
|
+
console.warn(
|
|
567
|
+
`[matrix] space membership for ${a.userId} failed: ${err.message}`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
481
571
|
for (let i = 0; i < a.rooms.length; i++) {
|
|
482
|
-
const
|
|
572
|
+
const binding = a.rooms[i];
|
|
573
|
+
const room = binding.alias;
|
|
483
574
|
try {
|
|
484
575
|
let resolved = room;
|
|
485
576
|
if (room.startsWith("#")) {
|
|
@@ -494,17 +585,25 @@ var BotPool = class {
|
|
|
494
585
|
const colon = room.indexOf(":");
|
|
495
586
|
const aliasLocalpart = colon > 1 ? room.slice(1, colon) : room.slice(1);
|
|
496
587
|
const sender = opts.adminUserId ?? a.userId;
|
|
588
|
+
const userPowerLevels = buildUserPowerLevels(
|
|
589
|
+
opts.asUserId,
|
|
590
|
+
opts.adminUserIds,
|
|
591
|
+
this.agents,
|
|
592
|
+
room
|
|
593
|
+
);
|
|
497
594
|
resolved = await this.client.createRoom({
|
|
498
595
|
roomAliasName: aliasLocalpart,
|
|
499
596
|
invite: opts.adminUserId ? [opts.adminUserId] : [],
|
|
500
597
|
senderUserId: sender,
|
|
501
|
-
name: aliasLocalpart
|
|
598
|
+
name: aliasLocalpart,
|
|
599
|
+
...opts.spaceRoomId ? { restrictedToSpaceId: opts.spaceRoomId } : {},
|
|
600
|
+
...userPowerLevels ? { userPowerLevels } : {}
|
|
502
601
|
});
|
|
503
602
|
}
|
|
504
603
|
aliasToId.set(room, resolved);
|
|
505
604
|
}
|
|
506
605
|
}
|
|
507
|
-
|
|
606
|
+
binding.alias = resolved;
|
|
508
607
|
await this.client.joinRoom(resolved, a.userId);
|
|
509
608
|
if (opts.spaceRoomId && opts.asUserId && !attachedToSpace.has(resolved)) {
|
|
510
609
|
attachedToSpace.add(resolved);
|
|
@@ -543,6 +642,18 @@ function localpart(userId) {
|
|
|
543
642
|
if (!m) throw new Error(`bad user id: ${userId}`);
|
|
544
643
|
return m[1];
|
|
545
644
|
}
|
|
645
|
+
function buildUserPowerLevels(asUserId, admins, agents, roomAlias) {
|
|
646
|
+
const users = {};
|
|
647
|
+
if (asUserId) users[asUserId] = 100;
|
|
648
|
+
if (admins) for (const a of admins) users[a] = 100;
|
|
649
|
+
for (const a of agents) {
|
|
650
|
+
for (const r of a.rooms) {
|
|
651
|
+
if (r.alias !== roomAlias) continue;
|
|
652
|
+
if (r.powerLevel !== void 0) users[a.userId] = r.powerLevel;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return Object.keys(users).length > 0 ? users : void 0;
|
|
656
|
+
}
|
|
546
657
|
|
|
547
658
|
// src/transport.ts
|
|
548
659
|
import { Hono } from "hono";
|
|
@@ -696,12 +807,17 @@ function toMatrixHtml(markdown) {
|
|
|
696
807
|
// src/transport.ts
|
|
697
808
|
var STARTUP_GRACE_MS = 5e3;
|
|
698
809
|
var SEEN_EVENT_CAP = 5e3;
|
|
810
|
+
var DRAIN_QUIET_MS = 300;
|
|
811
|
+
var DRAIN_MAX_MS = 3e4;
|
|
812
|
+
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
699
813
|
function inboundThreadRoot2(evt) {
|
|
700
814
|
const r = evt.content?.["m.relates_to"];
|
|
701
815
|
return r?.rel_type === "m.thread" && r.event_id ? r.event_id : void 0;
|
|
702
816
|
}
|
|
703
817
|
function createMatrixTransport(opts) {
|
|
704
818
|
const { agents, approvals, client, bindings, hsToken, adminUserId } = opts;
|
|
819
|
+
const drainQuietMs = opts.drainQuietMs ?? DRAIN_QUIET_MS;
|
|
820
|
+
const drainMaxMs = opts.drainMaxMs ?? DRAIN_MAX_MS;
|
|
705
821
|
const pool = new BotPool(client, bindings);
|
|
706
822
|
const sessions = /* @__PURE__ */ new Map();
|
|
707
823
|
const buffers = /* @__PURE__ */ new Map();
|
|
@@ -983,6 +1099,14 @@ function createMatrixTransport(opts) {
|
|
|
983
1099
|
channelId: evt.room_id,
|
|
984
1100
|
content: [{ type: "text", text: promptText }]
|
|
985
1101
|
});
|
|
1102
|
+
const drainStart = Date.now();
|
|
1103
|
+
let drained = buffers.get(sessionId) ?? "";
|
|
1104
|
+
while (drainQuietMs > 0 && Date.now() - drainStart < drainMaxMs) {
|
|
1105
|
+
await delay(drainQuietMs);
|
|
1106
|
+
const next = buffers.get(sessionId) ?? "";
|
|
1107
|
+
if (next === drained && next.length > 0) break;
|
|
1108
|
+
drained = next;
|
|
1109
|
+
}
|
|
986
1110
|
const text = buffers.get(sessionId) ?? "";
|
|
987
1111
|
if (text.length > 0) {
|
|
988
1112
|
const html = toMatrixHtml(text);
|
|
@@ -1034,7 +1158,7 @@ function createMatrixTransport(opts) {
|
|
|
1034
1158
|
}
|
|
1035
1159
|
async function rebuildThreadState(client, roomId, rootEventId, bindings) {
|
|
1036
1160
|
const state = { participants: [], rootMentions: [] };
|
|
1037
|
-
const asUser = (bindings.find((b) => b.rooms.
|
|
1161
|
+
const asUser = (bindings.find((b) => b.rooms.some((r) => r.alias === roomId)) ?? bindings[0])?.userId;
|
|
1038
1162
|
if (!asUser) return state;
|
|
1039
1163
|
const root = await client.fetchEvent(roomId, rootEventId, asUser);
|
|
1040
1164
|
if (root) {
|
|
@@ -1089,7 +1213,11 @@ function truncate(s, n) {
|
|
|
1089
1213
|
function buildWorkforceRoster(agents) {
|
|
1090
1214
|
return {
|
|
1091
1215
|
version: 1,
|
|
1092
|
-
agents: agents.map((a) => ({
|
|
1216
|
+
agents: agents.map((a) => ({
|
|
1217
|
+
user_id: a.userId,
|
|
1218
|
+
name: a.name,
|
|
1219
|
+
rooms: a.rooms.map((r) => r.alias)
|
|
1220
|
+
}))
|
|
1093
1221
|
};
|
|
1094
1222
|
}
|
|
1095
1223
|
async function publishWorkforce(opts) {
|
|
@@ -1117,6 +1245,7 @@ export {
|
|
|
1117
1245
|
MatrixContextProvider,
|
|
1118
1246
|
buildWorkforceRoster,
|
|
1119
1247
|
createMatrixTransport,
|
|
1248
|
+
ensureDefaultChannel,
|
|
1120
1249
|
ensureWorkforceSpace,
|
|
1121
1250
|
extractMentions,
|
|
1122
1251
|
publishWorkforce,
|