@zooid/transport-matrix 0.7.3 → 0.7.4
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.js +9 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/transport.test.ts +94 -0
- package/src/transport.ts +25 -3
package/dist/index.js
CHANGED
|
@@ -821,6 +821,7 @@ function createMatrixTransport(opts) {
|
|
|
821
821
|
const pool = new BotPool(client, bindings);
|
|
822
822
|
const sessions = /* @__PURE__ */ new Map();
|
|
823
823
|
const buffers = /* @__PURE__ */ new Map();
|
|
824
|
+
const bufferMessageIds = /* @__PURE__ */ new Map();
|
|
824
825
|
const sendQueue = /* @__PURE__ */ new Map();
|
|
825
826
|
const threadStates = /* @__PURE__ */ new Map();
|
|
826
827
|
const cutoffTs = Date.now() - STARTUP_GRACE_MS;
|
|
@@ -835,8 +836,13 @@ function createMatrixTransport(opts) {
|
|
|
835
836
|
const block = event.content;
|
|
836
837
|
if (block.type === "text" && typeof block.text === "string") {
|
|
837
838
|
const current = buffers.get(event.sessionId) ?? "";
|
|
838
|
-
const
|
|
839
|
+
const prevMessageId = bufferMessageIds.get(event.sessionId);
|
|
840
|
+
const messageChanged = event.messageId !== void 0 && prevMessageId !== void 0 && event.messageId !== prevMessageId;
|
|
841
|
+
const needsBreak = current.length > 0 && (block.text === "" || messageChanged);
|
|
842
|
+
const prefix = needsBreak ? "\n\n" : "";
|
|
839
843
|
buffers.set(event.sessionId, current + prefix + block.text);
|
|
844
|
+
if (event.messageId !== void 0)
|
|
845
|
+
bufferMessageIds.set(event.sessionId, event.messageId);
|
|
840
846
|
} else {
|
|
841
847
|
console.warn(`[matrix:${name}] dropped chunk block type=${block.type}`, block);
|
|
842
848
|
}
|
|
@@ -1079,6 +1085,7 @@ function createMatrixTransport(opts) {
|
|
|
1079
1085
|
const sessionId = await agents.ensureSession(agent.name, sessionKey, evt.room_id);
|
|
1080
1086
|
sessions.set(sessionId, { agent, roomId: evt.room_id, threadRoot });
|
|
1081
1087
|
buffers.set(sessionId, "");
|
|
1088
|
+
bufferMessageIds.delete(sessionId);
|
|
1082
1089
|
const roomId = evt.room_id;
|
|
1083
1090
|
const TYPING_TTL_MS = 3e4;
|
|
1084
1091
|
const TYPING_REFRESH_MS = 25e3;
|
|
@@ -1139,6 +1146,7 @@ function createMatrixTransport(opts) {
|
|
|
1139
1146
|
await safeTyping(false);
|
|
1140
1147
|
await safePresence("online");
|
|
1141
1148
|
buffers.delete(sessionId);
|
|
1149
|
+
bufferMessageIds.delete(sessionId);
|
|
1142
1150
|
}
|
|
1143
1151
|
}
|
|
1144
1152
|
return {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/matrix-client.ts","../src/context-provider.ts","../src/registration.ts","../src/mentions.ts","../src/router.ts","../src/space-provisioner.ts","../src/bot-pool.ts","../src/transport.ts","../src/event-encoders.ts","../src/markdown-to-matrix-html.ts","../src/workforce-publisher.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\n\nexport interface MatrixClientOptions {\n homeserver: string\n asToken: string\n fetch?: typeof globalThis.fetch\n}\n\nexport interface SendMessageInput {\n roomId: string\n asUserId: string\n content: { msgtype: string; body: string; [k: string]: unknown }\n threadRoot?: string\n}\n\nexport interface SendCustomEventInput {\n roomId: string\n asUserId: string\n eventType: string\n content: Record<string, unknown>\n}\n\nexport interface SetTypingInput {\n roomId: string\n asUserId: string\n typing: boolean\n /** ms — homeserver expects re-PUTs before this expires. Ignored when typing=false. */\n timeoutMs?: number\n}\n\nexport interface SetPresenceInput {\n asUserId: string\n presence: 'online' | 'unavailable' | 'offline'\n statusMsg?: string\n}\n\nexport class MatrixClient {\n private readonly homeserver: string\n private readonly asToken: string\n private readonly fetch: typeof globalThis.fetch\n\n constructor(opts: MatrixClientOptions) {\n this.homeserver = opts.homeserver.replace(/\\/$/, '')\n this.asToken = opts.asToken\n this.fetch = opts.fetch ?? globalThis.fetch\n }\n\n async registerBot(\n localpart: string,\n ): Promise<{ user_id: string; device_id: string } | undefined> {\n const r = await this.fetch(`${this.homeserver}/_matrix/client/v3/register`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.asToken}` },\n body: JSON.stringify({ type: 'm.login.application_service', username: localpart }),\n })\n if (r.status === 200) return (await r.json()) as { user_id: string; device_id: string }\n if (r.status === 400) {\n const body = (await r.json()) as { errcode?: string }\n if (body.errcode === 'M_USER_IN_USE') return undefined\n }\n throw new Error(`registerBot(${localpart}) failed: ${r.status}`)\n }\n\n async resolveAlias(alias: string): Promise<string | null> {\n const r = await this.fetch(\n `${this.homeserver}/_matrix/client/v3/directory/room/${encodeURIComponent(alias)}`,\n { headers: { Authorization: `Bearer ${this.asToken}` } },\n )\n if (r.status === 404) return null\n if (!r.ok) throw new Error(`resolveAlias(${alias}) failed: ${r.status}`)\n const j = (await r.json()) as { room_id: string }\n return j.room_id\n }\n\n async createRoom(opts: {\n roomAliasName: string\n invite: string[]\n senderUserId: string\n preset?: 'public_chat' | 'private_chat' | 'trusted_private_chat'\n /** Optional `m.room.name`. When set, sent in the createRoom body so the\n * room has a display name from the moment it exists. */\n name?: string\n /** When set, the room is created with a `restricted` join rule whose allow\n * condition references this space room ID — i.e. joinable by space members\n * only, rather than the whole homeserver. */\n restrictedToSpaceId?: string\n /** Explicit room version. Restricted join rules require v8+; omit to use the\n * homeserver default (modern Tuwunel defaults to v10/v11). */\n roomVersion?: string\n /**\n * Seeds `m.room.power_levels.users` at creation via\n * `power_level_content_override.users`. The caller owns the full map —\n * typically the AS bot at 100, plus operator and any agents with\n * declared PLs. Empty/absent → no override (the preset's defaults apply).\n */\n userPowerLevels?: Record<string, number>\n }): Promise<string> {\n const body: Record<string, unknown> = {\n room_alias_name: opts.roomAliasName,\n invite: opts.invite,\n preset: opts.preset ?? 'public_chat',\n }\n if (opts.name !== undefined) body.name = opts.name\n if (opts.roomVersion !== undefined) body.room_version = opts.roomVersion\n if (opts.restrictedToSpaceId !== undefined) {\n body.initial_state = [\n {\n type: 'm.room.join_rules',\n state_key: '',\n content: {\n join_rule: 'restricted',\n allow: [{ type: 'm.room_membership', room_id: opts.restrictedToSpaceId }],\n },\n },\n ]\n }\n if (opts.userPowerLevels && Object.keys(opts.userPowerLevels).length > 0) {\n body.power_level_content_override = { users: opts.userPowerLevels }\n }\n const r = await this.fetch(\n `${this.homeserver}/_matrix/client/v3/createRoom?user_id=${encodeURIComponent(opts.senderUserId)}`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.asToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n },\n )\n if (!r.ok) {\n const body = await r.text()\n throw new Error(`createRoom(${opts.roomAliasName}) failed: ${r.status} ${body}`)\n }\n const j = (await r.json()) as { room_id: string }\n return j.room_id\n }\n\n async createRoomRaw(opts: {\n asUserId: string\n body: Record<string, unknown>\n }): Promise<string> {\n const url = `${this.homeserver}/_matrix/client/v3/createRoom?user_id=${encodeURIComponent(opts.asUserId)}`\n const r = await this.fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.asToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(opts.body),\n })\n if (!r.ok) throw new Error(`createRoomRaw failed: ${r.status}`)\n const j = (await r.json()) as { room_id: string }\n return j.room_id\n }\n\n async sendStateEvent(opts: {\n roomId: string\n asUserId: string\n eventType: string\n stateKey?: string\n content: Record<string, unknown>\n }): Promise<{ event_id: string }> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(opts.roomId)}` +\n `/state/${encodeURIComponent(opts.eventType)}/${encodeURIComponent(opts.stateKey ?? '')}` +\n `?user_id=${encodeURIComponent(opts.asUserId)}`\n const r = await this.fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${this.asToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(opts.content),\n })\n if (!r.ok) throw new Error(`sendStateEvent ${opts.eventType} failed: ${r.status}`)\n return (await r.json()) as { event_id: string }\n }\n\n /**\n * Invite a user to a room. Sent as the inviter (`asUserId`) — that user\n * needs invite power in the room. Tolerates the \"already in room /\n * already invited\" responses idempotently so bootstrap can run on a\n * fresh AND a populated homeserver without branching.\n */\n async invite(opts: {\n roomId: string\n asUserId: string\n targetUserId: string\n }): Promise<void> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(opts.roomId)}/invite` +\n `?user_id=${encodeURIComponent(opts.asUserId)}`\n const r = await this.fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.asToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({ user_id: opts.targetUserId }),\n })\n if (r.ok) return\n if (r.status === 403) {\n // Tuwunel/Synapse use M_FORBIDDEN both for permission errors AND for\n // \"already a member / already invited\". Inspect the body so we only\n // swallow the idempotent case.\n //\n // Phrasings seen in the wild:\n // - Synapse: \"is already in the room\", \"is already invited\"\n // - Tuwunel: \"cannot invite user that is joined or banned\" (one\n // string for both \"joined\" and \"banned\" — we can't tell which\n // from the body; the bot-pool's outer try-catch surfaces a real\n // ban via the subsequent joinRoom failure)\n // - Generic: \"X is already a member of the room\"\n const body = await r.text()\n const idempotent =\n /already (in the room|invited|a member|joined)/i.test(body) ||\n /user that is joined/i.test(body)\n if (idempotent) return\n throw new Error(`invite(${opts.targetUserId}) failed: 403 ${body}`)\n }\n throw new Error(`invite(${opts.targetUserId}) failed: ${r.status}`)\n }\n\n async joinRoom(roomIdOrAlias: string, asUserId: string): Promise<void> {\n const url =\n `${this.homeserver}/_matrix/client/v3/join/${encodeURIComponent(roomIdOrAlias)}` +\n `?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.asToken}` },\n body: '{}',\n })\n if (!r.ok) throw new Error(`joinRoom(${roomIdOrAlias}) failed: ${r.status}`)\n }\n\n async sendMessage(input: SendMessageInput): Promise<{ event_id: string }> {\n const content: Record<string, unknown> = { ...input.content }\n if (input.threadRoot) {\n content['m.relates_to'] = { rel_type: 'm.thread', event_id: input.threadRoot }\n }\n return this.sendEvent(input.roomId, input.asUserId, 'm.room.message', content)\n }\n\n async sendCustomEvent(input: SendCustomEventInput): Promise<{ event_id: string }> {\n return this.sendEvent(input.roomId, input.asUserId, input.eventType, input.content)\n }\n\n async setTyping(input: SetTypingInput): Promise<void> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(input.roomId)}` +\n `/typing/${encodeURIComponent(input.asUserId)}` +\n `?user_id=${encodeURIComponent(input.asUserId)}`\n const body: Record<string, unknown> = { typing: input.typing }\n if (input.typing && input.timeoutMs !== undefined) body.timeout = input.timeoutMs\n const r = await this.fetch(url, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${this.asToken}` },\n body: JSON.stringify(body),\n })\n if (!r.ok) throw new Error(`setTyping(${input.roomId}, ${input.asUserId}) failed: ${r.status}`)\n }\n\n async setDisplayName(asUserId: string, displayName: string): Promise<void> {\n const url =\n `${this.homeserver}/_matrix/client/v3/profile/${encodeURIComponent(asUserId)}/displayname` +\n `?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${this.asToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({ displayname: displayName }),\n })\n if (!r.ok) throw new Error(`setDisplayName(${asUserId}) failed: ${r.status}`)\n }\n\n async setPresence(input: SetPresenceInput): Promise<void> {\n const url =\n `${this.homeserver}/_matrix/client/v3/presence/${encodeURIComponent(input.asUserId)}/status` +\n `?user_id=${encodeURIComponent(input.asUserId)}`\n const body: Record<string, unknown> = { presence: input.presence }\n if (input.statusMsg !== undefined) body.status_msg = input.statusMsg\n const r = await this.fetch(url, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${this.asToken}` },\n body: JSON.stringify(body),\n })\n if (!r.ok) throw new Error(`setPresence(${input.asUserId}) failed: ${r.status}`)\n }\n\n /**\n * Fetch a single event from a room. Used to recover thread-root context\n * after a daemon restart wipes the in-memory threadStates cache.\n */\n async fetchEvent(\n roomId: string,\n eventId: string,\n asUserId: string,\n ): Promise<Record<string, unknown> | null> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}` +\n `/event/${encodeURIComponent(eventId)}` +\n `?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.asToken}` },\n })\n if (r.status === 404) return null\n if (!r.ok) throw new Error(`fetchEvent(${eventId}) failed: ${r.status}`)\n return (await r.json()) as Record<string, unknown>\n }\n\n /**\n * Fetch replies to a thread root via the relations endpoint, oldest-first.\n * Pass `limit` and `from` for pagination; `next_batch` echoes back when\n * there are more replies. Returns `{ chunk: [] }` when the root is unknown.\n */\n async fetchThreadRelations(opts: {\n roomId: string\n rootEventId: string\n asUserId: string\n limit?: number\n from?: string\n }): Promise<{ chunk: Array<Record<string, unknown>>; next_batch?: string }> {\n const params = new URLSearchParams({\n dir: 'f',\n limit: String(opts.limit ?? 100),\n user_id: opts.asUserId,\n })\n if (opts.from) params.set('from', opts.from)\n const url =\n `${this.homeserver}/_matrix/client/v1/rooms/${encodeURIComponent(opts.roomId)}` +\n `/relations/${encodeURIComponent(opts.rootEventId)}/m.thread?${params.toString()}`\n const r = await this.fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.asToken}` },\n })\n if (r.status === 404) return { chunk: [] }\n if (!r.ok) throw new Error(`fetchThreadRelations(${opts.rootEventId}) failed: ${r.status}`)\n const body = (await r.json()) as {\n chunk?: Array<Record<string, unknown>>\n next_batch?: string\n }\n return { chunk: body.chunk ?? [], next_batch: body.next_batch }\n }\n\n /**\n * Paginate the room timeline. Returns events newest-first (dir=b) per Matrix\n * spec. The caller is responsible for reversing if it wants oldest-first.\n */\n async fetchRoomMessages(opts: {\n roomId: string\n asUserId: string\n limit?: number\n /** Opaque pagination token returned in `end` from a previous call. */\n from?: string\n /**\n * Server-side RoomEventFilter. Common keys: `types` (whitelist event types),\n * `not_types`, `not_rel_types` (e.g. `['m.thread']` to exclude thread\n * replies). Encoded as JSON into the `filter` query param.\n */\n filter?: {\n types?: string[]\n not_types?: string[]\n rel_types?: string[]\n not_rel_types?: string[]\n }\n }): Promise<{ chunk: Array<Record<string, unknown>>; end?: string }> {\n const params = new URLSearchParams({\n dir: 'b',\n limit: String(opts.limit ?? 50),\n user_id: opts.asUserId,\n })\n if (opts.from) params.set('from', opts.from)\n if (opts.filter) params.set('filter', JSON.stringify(opts.filter))\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(opts.roomId)}` +\n `/messages?${params.toString()}`\n const r = await this.fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.asToken}` },\n })\n if (!r.ok) throw new Error(`fetchRoomMessages(${opts.roomId}) failed: ${r.status}`)\n return (await r.json()) as { chunk: Array<Record<string, unknown>>; end?: string }\n }\n\n async getJoinedMembers(\n roomId: string,\n asUserId: string,\n ): Promise<{ joined: Record<string, { display_name?: string }> }> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}` +\n `/joined_members?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.asToken}` },\n })\n if (!r.ok) throw new Error(`getJoinedMembers(${roomId}) failed: ${r.status}`)\n return (await r.json()) as { joined: Record<string, { display_name?: string }> }\n }\n\n async fetchRoomName(roomId: string, asUserId: string): Promise<string | null> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}` +\n `/state/m.room.name/?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.asToken}` },\n })\n if (r.status === 404) return null\n if (!r.ok) throw new Error(`fetchRoomName(${roomId}) failed: ${r.status}`)\n const body = (await r.json()) as { name?: string }\n return body.name ?? null\n }\n\n private async sendEvent(\n roomId: string,\n asUserId: string,\n eventType: string,\n content: Record<string, unknown>,\n ): Promise<{ event_id: string }> {\n const txn = randomUUID()\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}` +\n `/send/${eventType}/${txn}?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${this.asToken}` },\n body: JSON.stringify(content),\n })\n if (!r.ok) throw new Error(`sendEvent(${eventType}) failed: ${r.status}`)\n return (await r.json()) as { event_id: string }\n }\n}\n","import type {\n TransportContextProvider,\n HistoryOptions,\n HistoryPage,\n Member,\n ChannelInfo,\n Message,\n ThreadOverview,\n ThreadOverviewPage,\n} from '@zooid/core'\nimport type { MatrixClient } from './matrix-client.js'\n\ninterface MatrixMessageEvent {\n event_id: string\n sender: string\n origin_server_ts: number\n type: string\n content?: {\n msgtype?: string\n body?: string\n 'm.relates_to'?: { rel_type?: string; event_id?: string }\n }\n unsigned?: {\n 'm.relations'?: {\n 'm.thread'?: {\n count?: number\n latest_event?: { origin_server_ts?: number }\n }\n }\n }\n}\n\nexport interface MatrixContextProviderOpts {\n client: MatrixClient\n /** AS sender_localpart user (read access). */\n asUserId: string\n /** Map of Matrix user IDs → agent names, for is_agent / agent_name flags. */\n agentBots: Map<string, string>\n}\n\nexport class MatrixContextProvider implements TransportContextProvider {\n constructor(private readonly opts: MatrixContextProviderOpts) {}\n\n async getRoomHistory(channelId: string, hopts: HistoryOptions): Promise<HistoryPage> {\n // Server-side filter: only `m.room.message` events. Without this we'd\n // burn the page budget on reactions, `eco.zoon.*` custom events, typing\n // notifications, etc., and routinely return empty pages with a stale\n // `has_more` cursor.\n const { chunk, end } = await this.opts.client.fetchRoomMessages({\n roomId: channelId,\n asUserId: this.opts.asUserId,\n limit: hopts.limit,\n from: hopts.before,\n filter: { types: ['m.room.message'] },\n })\n const messages: Message[] = []\n for (let i = chunk.length - 1; i >= 0; i--) {\n const ev = chunk[i] as unknown as MatrixMessageEvent\n const msg = this.toMessage(ev)\n if (msg) messages.push(msg)\n }\n return {\n messages,\n next_before: end,\n has_more: end !== undefined,\n }\n }\n\n async getRecentThreads(\n channelId: string,\n hopts: HistoryOptions,\n ): Promise<ThreadOverviewPage> {\n // Server-side filter: `m.room.message` only, and exclude thread replies\n // (`not_rel_types: ['m.thread']`) so the overview shows top-level entries\n // and thread roots, not the reply noise underneath them.\n const { chunk, end } = await this.opts.client.fetchRoomMessages({\n roomId: channelId,\n asUserId: this.opts.asUserId,\n limit: hopts.limit,\n from: hopts.before,\n filter: { types: ['m.room.message'], not_rel_types: ['m.thread'] },\n })\n // /messages returns newest-first; keep that order for the overview.\n const threads: ThreadOverview[] = []\n for (const ev of chunk as unknown as MatrixMessageEvent[]) {\n if (ev.type !== 'm.room.message') continue\n if (ev.content?.msgtype !== 'm.text' || typeof ev.content.body !== 'string') continue\n const relatesTo = ev.content['m.relates_to']\n if (relatesTo?.rel_type === 'm.thread') continue // skip thread replies\n const agent = this.opts.agentBots.get(ev.sender)\n const bundled = ev.unsigned?.['m.relations']?.['m.thread']\n const replyCount = bundled?.count ?? 0\n const latestTs = bundled?.latest_event?.origin_server_ts ?? ev.origin_server_ts\n threads.push({\n id: ev.event_id,\n sender: ev.sender,\n text: ev.content.body,\n timestamp: new Date(ev.origin_server_ts).toISOString(),\n is_agent: agent !== undefined,\n ...(agent !== undefined ? { agent_name: agent } : {}),\n reply_count: replyCount,\n last_activity_at: new Date(latestTs).toISOString(),\n })\n }\n return {\n threads,\n next_before: end,\n has_more: end !== undefined,\n }\n }\n\n async getThreadHistory(\n channelId: string,\n threadId: string,\n hopts: HistoryOptions,\n ): Promise<HistoryPage> {\n // Root event first (only on the first page when no pagination cursor).\n const messages: Message[] = []\n if (!hopts.before) {\n const root = (await this.opts.client.fetchEvent(\n channelId,\n threadId,\n this.opts.asUserId,\n )) as unknown as MatrixMessageEvent | null\n if (root) {\n const rootMsg = this.toMessage(root)\n if (rootMsg) messages.push({ ...rootMsg, thread_id: threadId })\n }\n }\n const { chunk, next_batch } = await this.opts.client.fetchThreadRelations({\n roomId: channelId,\n rootEventId: threadId,\n asUserId: this.opts.asUserId,\n limit: hopts.limit,\n from: hopts.before,\n })\n for (const ev of chunk as unknown as MatrixMessageEvent[]) {\n const reply = this.toMessage(ev)\n if (reply) messages.push({ ...reply, thread_id: threadId })\n }\n return {\n messages,\n next_before: next_batch,\n has_more: next_batch !== undefined,\n }\n }\n\n private toMessage(ev: MatrixMessageEvent): Message | null {\n if (ev.type !== 'm.room.message') return null\n if (ev.content?.msgtype !== 'm.text' || typeof ev.content.body !== 'string') return null\n const agent = this.opts.agentBots.get(ev.sender)\n const relatesTo = ev.content['m.relates_to']\n const threadId =\n relatesTo?.rel_type === 'm.thread' && relatesTo.event_id ? relatesTo.event_id : undefined\n return {\n id: ev.event_id,\n sender: ev.sender,\n text: ev.content.body,\n timestamp: new Date(ev.origin_server_ts).toISOString(),\n is_agent: agent !== undefined,\n ...(agent !== undefined ? { agent_name: agent } : {}),\n ...(threadId !== undefined ? { thread_id: threadId } : {}),\n }\n }\n\n async getChannelMembers(channelId: string): Promise<Member[]> {\n const { joined } = await this.opts.client.getJoinedMembers(channelId, this.opts.asUserId)\n return Object.entries(joined).map(([id, info]) => {\n const agent = this.opts.agentBots.get(id)\n return {\n id,\n name: info.display_name ?? id,\n is_agent: agent !== undefined,\n ...(agent !== undefined ? { agent_name: agent } : {}),\n }\n })\n }\n\n async getChannelInfo(channelId: string): Promise<ChannelInfo> {\n const name = await this.opts.client.fetchRoomName(channelId, this.opts.asUserId)\n return {\n id: channelId,\n name: name ?? channelId,\n transport: 'matrix',\n }\n }\n}\n","import { stringify } from 'yaml'\n\nexport interface MatrixTransportConfig {\n id: string\n url: string\n homeserver: string\n asToken: string\n hsToken: string\n senderLocalpart: string\n /** Regex covering all bot users, e.g. `@.*:example.com` */\n userNamespace: string\n /**\n * Optional regex covering aliases the AS may claim, e.g. `#.*:example.com`.\n * Required when the AS calls `createRoom` with a `room_alias_name`.\n */\n aliasNamespace?: string\n /**\n * Whether the AS exclusively owns the user_namespace. Default true.\n * Set false when humans share the namespace (e.g., `zooid dev` registers\n * a predefined admin under the same `@.*:localhost` regex).\n */\n exclusive?: boolean\n}\n\nexport function renderRegistration(c: MatrixTransportConfig): string {\n return stringify(\n {\n id: c.id,\n url: c.url,\n as_token: c.asToken,\n hs_token: c.hsToken,\n sender_localpart: c.senderLocalpart,\n rate_limited: false,\n namespaces: {\n users: [{ exclusive: c.exclusive ?? true, regex: c.userNamespace }],\n aliases: c.aliasNamespace\n ? [{ exclusive: c.exclusive ?? true, regex: c.aliasNamespace }]\n : [],\n rooms: [],\n },\n },\n { defaultStringType: 'PLAIN', defaultKeyType: 'PLAIN', singleQuote: true },\n )\n}\n","export interface MaybeMessage {\n content?: {\n 'm.mentions'?: { user_ids?: string[] }\n body?: string\n formatted_body?: string\n }\n}\n\nconst MATRIX_TO_RE = /https:\\/\\/matrix\\.to\\/#\\/(@[^\"<>\\s]+)/g\nconst RAW_USER_RE = /(@[A-Za-z0-9._\\-=/+]+:[A-Za-z0-9.\\-]+)/g\n\nexport function extractMentions(event: MaybeMessage): string[] {\n const out = new Set<string>()\n const c = event.content ?? {}\n for (const id of c['m.mentions']?.user_ids ?? []) out.add(id)\n if (c.formatted_body) {\n for (const m of c.formatted_body.matchAll(MATRIX_TO_RE)) {\n out.add(decodeURIComponent(m[1]))\n }\n }\n if (c.body && out.size === 0) {\n for (const m of c.body.matchAll(RAW_USER_RE)) out.add(m[1])\n }\n return [...out]\n}\n\n/**\n * Remove a single user-id mention from a message body. Strips the raw\n * `@local:server` form and collapses any whitespace that the strip leaves\n * behind. Other users' mentions are preserved verbatim so the agent can\n * reason about them.\n */\nexport function stripMention(body: string, userId: string): string {\n const escaped = userId.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n // Match optional surrounding whitespace so we don't leave double spaces.\n const re = new RegExp(`\\\\s*${escaped}\\\\s*`, 'g')\n return body.replace(re, ' ').replace(/\\s+/g, ' ').trim()\n}\n","import type { RoomBinding } from '@zooid/core'\nimport { extractMentions } from './mentions.js'\n\nexport type { RoomBinding }\n\nexport interface AgentBinding {\n name: string\n userId: string\n /** Optional human-readable display name. Falls back to the user_id localpart. */\n displayName?: string\n /**\n * Rooms this agent is bound to. Each entry's `alias` starts out as the\n * configured `#alias` (or `!id`) and is rewritten to the canonical room\n * ID by `BotPool.bootstrap`. Optional `powerLevel` is seeded into the\n * room's `m.room.power_levels.users` at room creation only.\n */\n rooms: RoomBinding[]\n trigger: 'mention' | 'any'\n}\n\nexport interface ThreadState {\n /** Agent names that have posted in this thread, in order. */\n participants: string[]\n /** Agent names @mentioned in the thread root event (or subsequently). */\n rootMentions: string[]\n}\n\ninterface MaybeEvent {\n type?: string\n room_id?: string\n sender?: string\n content?: {\n msgtype?: string\n 'm.relates_to'?: { rel_type?: string; event_id?: string }\n }\n}\n\nexport type RouteMatch = AgentBinding\n\nfunction inboundThreadRoot(event: MaybeEvent): string | undefined {\n const r = event.content?.['m.relates_to']\n return r?.rel_type === 'm.thread' && r.event_id ? r.event_id : undefined\n}\n\nexport function route(\n event: MaybeEvent,\n agents: AgentBinding[],\n threadStates?: Map<string, ThreadState>,\n): RouteMatch[] {\n if (event.type !== 'm.room.message') return []\n if (!event.content?.msgtype) return []\n const mentions = new Set(extractMentions(event as never))\n const matches: RouteMatch[] = []\n const threadRoot = inboundThreadRoot(event)\n const threadState = threadRoot ? threadStates?.get(threadRoot) : undefined\n\n for (const a of agents) {\n if (event.sender === a.userId) continue\n if (!a.rooms.some((r) => r.alias === event.room_id)) continue\n if (a.trigger === 'any') {\n matches.push(a)\n continue\n }\n // trigger === 'mention'\n if (mentions.has(a.userId)) {\n matches.push(a)\n continue\n }\n // Implicit trigger in a thread: most-recent-poster, or root-mention\n // inheritance if no agent has posted yet.\n if (threadState) {\n const lastPoster = threadState.participants.at(-1)\n if (lastPoster) {\n if (lastPoster === a.name) matches.push(a)\n } else if (threadState.rootMentions.includes(a.name)) {\n matches.push(a)\n }\n }\n }\n return matches\n}\n","import { MatrixClient } from './matrix-client.js'\n\nexport interface EnsureSpaceOpts {\n client: MatrixClient\n asUserId: string\n serverName: string\n spaceLocalpart: string\n preset: 'public_chat' | 'private_chat'\n /**\n * Operator MXIDs to seed at PL 100 in the space's `m.room.power_levels`\n * at creation. The AS bot is always included. Empty/absent → no override\n * (the preset's PL defaults apply). Only consulted on first creation —\n * if the alias already resolves we return the existing room untouched.\n */\n admins?: string[]\n}\n\nexport async function ensureWorkforceSpace(opts: EnsureSpaceOpts): Promise<string> {\n const alias = `#${opts.spaceLocalpart}:${opts.serverName}`\n const existing = await opts.client.resolveAlias(alias)\n if (existing) return existing\n\n const display = opts.spaceLocalpart.charAt(0).toUpperCase() + opts.spaceLocalpart.slice(1)\n const body: Record<string, unknown> = {\n room_alias_name: opts.spaceLocalpart,\n name: display,\n preset: opts.preset,\n creation_content: { type: 'm.space' },\n // A workspace is joined by invitation, not self-service. Pin the space's\n // join rule to invite regardless of preset so it can't be walked into\n // (which would otherwise satisfy every restricted child room's allow).\n initial_state: [{ type: 'm.room.join_rules', state_key: '', content: { join_rule: 'invite' } }],\n }\n if (opts.admins && opts.admins.length > 0) {\n // Invite each admin so they actually become members — PL 100 alone does\n // not grant membership in an invite-only space.\n body.invite = opts.admins\n const users: Record<string, number> = { [opts.asUserId]: 100 }\n for (const a of opts.admins) users[a] = 100\n body.power_level_content_override = { users }\n }\n return opts.client.createRoomRaw({ asUserId: opts.asUserId, body })\n}\n\nexport interface EnsureDefaultChannelOpts {\n client: MatrixClient\n asUserId: string\n serverName: string\n spaceId: string\n /** Localpart of the default channel; defaults to `general`. */\n channelLocalpart?: string\n /**\n * Operator MXIDs to seed at PL 100 in the channel's `m.room.power_levels`\n * at creation. The AS bot is always included. Empty/absent → no override\n * (the preset's PL defaults apply). Only consulted on first creation —\n * if the alias already resolves we return the existing room untouched.\n */\n admins?: string[]\n}\n\n/**\n * Ensure a space has a default channel (`#general` by default), restricted to\n * the space's members and attached as an `m.space.child`. Idempotent: returns\n * the existing room if the alias already resolves. Has no agent — it's the\n * human landing room, so it is created here at provisioning time rather than\n * via the agent-room path.\n */\nexport async function ensureDefaultChannel(opts: EnsureDefaultChannelOpts): Promise<string> {\n const localpart = opts.channelLocalpart ?? 'general'\n const alias = `#${localpart}:${opts.serverName}`\n const existing = await opts.client.resolveAlias(alias)\n if (existing) return existing\n\n let userPowerLevels: Record<string, number> | undefined\n if (opts.admins && opts.admins.length > 0) {\n userPowerLevels = { [opts.asUserId]: 100 }\n for (const a of opts.admins) userPowerLevels[a] = 100\n }\n\n const roomId = await opts.client.createRoom({\n roomAliasName: localpart,\n invite: [],\n senderUserId: opts.asUserId,\n name: localpart.charAt(0).toUpperCase() + localpart.slice(1),\n restrictedToSpaceId: opts.spaceId,\n ...(userPowerLevels ? { userPowerLevels } : {}),\n })\n await opts.client.sendStateEvent({\n roomId: opts.spaceId,\n asUserId: opts.asUserId,\n eventType: 'm.space.child',\n stateKey: roomId,\n content: { via: [opts.serverName] },\n })\n return roomId\n}\n\nexport function serverNameFromMxid(mxid: string): string {\n const colon = mxid.indexOf(':')\n if (colon < 0) {\n throw new Error(`mxid lacks server: ${mxid}`)\n }\n return mxid.slice(colon + 1)\n}\n","import type { MatrixClient } from './matrix-client.js'\nimport type { AgentBinding } from './router.js'\nimport { serverNameFromMxid } from './space-provisioner.js'\n\nexport interface BootstrapOpts {\n /** Invited to any newly-created room; absent = no invite. */\n adminUserId?: string\n /** Workforce space room ID. When set, every resolved agent room is attached as m.space.child. */\n spaceRoomId?: string\n /** AS bot user ID. Required when spaceRoomId is set; sender of the m.space.child write. */\n asUserId?: string\n /**\n * Operator MXIDs seeded at PL 100 in every agent room this pool creates.\n * Applied via `power_level_content_override.users` at room creation only —\n * never reconciled. Empty/absent = no operator entries.\n */\n adminUserIds?: string[]\n}\n\nexport class BotPool {\n constructor(\n private readonly client: Pick<\n MatrixClient,\n | 'registerBot'\n | 'invite'\n | 'joinRoom'\n | 'resolveAlias'\n | 'createRoom'\n | 'sendStateEvent'\n | 'setDisplayName'\n >,\n private readonly agents: AgentBinding[],\n ) {}\n\n async bootstrap(opts: BootstrapOpts = {}): Promise<void> {\n const aliasToId = new Map<string, string>()\n const attachedToSpace = new Set<string>()\n for (const a of this.agents) {\n const lp = localpart(a.userId)\n try {\n await this.client.registerBot(lp)\n } catch (err) {\n console.warn(`[matrix] register failed for ${a.userId}: ${(err as Error).message}`)\n }\n try {\n await this.client.setDisplayName(a.userId, a.displayName ?? lp)\n } catch (err) {\n console.warn(`[matrix] setDisplayName(${a.userId}) failed: ${(err as Error).message}`)\n }\n // Make each agent a member of the workforce space. That covers two\n // things at once: the eco.zoon.workforce roster is now backed by\n // actual space membership (so the Zoon client's member autocomplete\n // works across rooms), and every restricted child room's allow rule\n // is satisfied without per-room invites.\n if (opts.spaceRoomId && opts.asUserId) {\n try {\n await this.client.invite({\n roomId: opts.spaceRoomId,\n asUserId: opts.asUserId,\n targetUserId: a.userId,\n })\n await this.client.joinRoom(opts.spaceRoomId, a.userId)\n } catch (err) {\n console.warn(\n `[matrix] space membership for ${a.userId} failed: ${(err as Error).message}`,\n )\n }\n }\n for (let i = 0; i < a.rooms.length; i++) {\n const binding = a.rooms[i]!\n const room = binding.alias\n try {\n let resolved = room\n if (room.startsWith('#')) {\n const cached = aliasToId.get(room)\n if (cached) {\n resolved = cached\n } else {\n const existing = await this.client.resolveAlias(room)\n if (existing) {\n resolved = existing\n } else {\n const colon = room.indexOf(':')\n const aliasLocalpart = colon > 1 ? room.slice(1, colon) : room.slice(1)\n const sender = opts.adminUserId ?? a.userId\n const userPowerLevels = buildUserPowerLevels(\n opts.asUserId,\n opts.adminUserIds,\n this.agents,\n room,\n )\n resolved = await this.client.createRoom({\n roomAliasName: aliasLocalpart,\n invite: opts.adminUserId ? [opts.adminUserId] : [],\n senderUserId: sender,\n name: aliasLocalpart,\n ...(opts.spaceRoomId ? { restrictedToSpaceId: opts.spaceRoomId } : {}),\n ...(userPowerLevels ? { userPowerLevels } : {}),\n })\n }\n aliasToId.set(room, resolved)\n }\n }\n // Rewrite the binding's alias to the canonical room ID so the\n // router (which matches on event.room_id) sees a hit when Tuwunel\n // pushes events. The declared powerLevel was a creation-time\n // seed — it has no role after bootstrap.\n binding.alias = resolved\n await this.client.joinRoom(resolved, a.userId)\n\n if (\n opts.spaceRoomId &&\n opts.asUserId &&\n !attachedToSpace.has(resolved)\n ) {\n attachedToSpace.add(resolved)\n const via = serverNameFromMxid(a.userId)\n try {\n await this.client.sendStateEvent({\n roomId: opts.spaceRoomId,\n asUserId: opts.asUserId,\n eventType: 'm.space.child',\n stateKey: resolved,\n content: { via: [via] },\n })\n } catch (err) {\n console.warn(\n `[matrix] m.space.child(${resolved}) failed: ${(err as Error).message}`,\n )\n }\n }\n } catch (err) {\n console.warn(\n `[matrix] join failed (${a.userId} → ${room}): ${(err as Error).message}`,\n )\n }\n }\n }\n }\n\n findByUserId(userId: string): AgentBinding | undefined {\n return this.agents.find((a) => a.userId === userId)\n }\n\n findByName(name: string): AgentBinding | undefined {\n return this.agents.find((a) => a.name === name)\n }\n}\n\nfunction localpart(userId: string): string {\n const m = /^@([^:]+):/.exec(userId)\n if (!m) throw new Error(`bad user id: ${userId}`)\n return m[1]\n}\n\n/**\n * Build the `userPowerLevels` map for a room about to be created. Always\n * seeds the AS bot (when known) and any operator admins at PL 100; layers\n * per-agent declared PLs on top so agents land at moderator (or whatever\n * they declared) without a follow-up state write. Returns undefined when\n * the map would be empty — `createRoom` then omits the override entirely\n * and the preset's defaults apply.\n */\nfunction buildUserPowerLevels(\n asUserId: string | undefined,\n admins: string[] | undefined,\n agents: AgentBinding[],\n roomAlias: string,\n): Record<string, number> | undefined {\n const users: Record<string, number> = {}\n if (asUserId) users[asUserId] = 100\n if (admins) for (const a of admins) users[a] = 100\n for (const a of agents) {\n for (const r of a.rooms) {\n if (r.alias !== roomAlias) continue\n if (r.powerLevel !== undefined) users[a.userId] = r.powerLevel\n }\n }\n return Object.keys(users).length > 0 ? users : undefined\n}\n","import { Hono } from 'hono'\nimport { timingSafeEqual } from 'node:crypto'\nimport type { AcpRegistry, ApprovalCorrelator, RegisteredApproval } from '@zooid/core'\nimport type { AgentEvent } from '@zooid/acp-client'\nimport { MatrixClient } from './matrix-client.js'\nimport { BotPool } from './bot-pool.js'\nimport { route, type AgentBinding, type ThreadState } from './router.js'\nimport { stripMention, extractMentions } from './mentions.js'\nimport { toToolCallBody, toUpdateBody, toPlanBody, toErrorBody } from './event-encoders.js'\nimport { classify } from '@zooid/acp-client'\nimport { toMatrixHtml } from './markdown-to-matrix-html.js'\n\nexport interface CreateMatrixTransportOptions {\n agents: AcpRegistry\n approvals: ApprovalCorrelator\n client: MatrixClient\n bindings: AgentBinding[]\n hsToken: string\n /** Admin Matrix user ID. When set, BotPool.bootstrap invites this user into rooms it creates. */\n adminUserId?: string\n /** Post-turn drain: keep collecting trailing `agent_message_chunk`s until the\n * buffer is quiet for this long before flushing. Defaults to `DRAIN_QUIET_MS`.\n * Set to 0 to disable the drain (e.g. in tests). */\n drainQuietMs?: number\n /** Hard cap on the post-turn drain. Defaults to `DRAIN_MAX_MS`. */\n drainMaxMs?: number\n}\n\ninterface SessionContext {\n agent: AgentBinding\n roomId: string\n /** Always set — every session is thread-scoped via agent-promotion. */\n threadRoot: string\n}\n\ninterface MatrixEvent {\n type?: string\n event_id?: string\n origin_server_ts?: number\n room_id?: string\n sender?: string\n content?: Record<string, unknown> & {\n msgtype?: string\n body?: string\n 'm.relates_to'?: { rel_type?: string; event_id?: string }\n }\n}\n\nconst STARTUP_GRACE_MS = 5_000\nconst SEEN_EVENT_CAP = 5_000\n\n// ACP only guarantees that an agent flushes pending `session/update`\n// notifications before the `session/prompt` response in the *cancellation*\n// path; for a normal turn the ordering is unspecified. Some agents (e.g.\n// opencode) emit trailing `agent_message_chunk`s a few ms after the stopReason\n// response, so finalizing the moment `prompt()` resolves truncates the reply.\n// After the turn resolves we wait for the buffer to stay unchanged for\n// DRAIN_QUIET_MS (debounce — re-arms on each late chunk) before flushing,\n// capped at DRAIN_MAX_MS so a misbehaving stream can't hang the turn.\nconst DRAIN_QUIET_MS = 300\n// 30s upper bound on how long we wait after `session/prompt` resolves before\n// flushing whatever we have (or declaring an empty turn). Set high because\n// some agents — opencode especially — resolve the prompt promise *before*\n// the agent_message_chunk stream starts, and the chunk burst can be 5–15s\n// after that. The drain still short-circuits via DRAIN_QUIET_MS once any\n// content has settled, so this cap only kicks in for genuinely-stuck turns.\nconst DRAIN_MAX_MS = 30_000\n\nconst delay = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))\n\nfunction inboundThreadRoot(evt: MatrixEvent): string | undefined {\n const r = evt.content?.['m.relates_to']\n return r?.rel_type === 'm.thread' && r.event_id ? r.event_id : undefined\n}\n\nexport function createMatrixTransport(opts: CreateMatrixTransportOptions) {\n const { agents, approvals, client, bindings, hsToken, adminUserId } = opts\n const drainQuietMs = opts.drainQuietMs ?? DRAIN_QUIET_MS\n const drainMaxMs = opts.drainMaxMs ?? DRAIN_MAX_MS\n const pool = new BotPool(client, bindings)\n const sessions = new Map<string, SessionContext>()\n const buffers = new Map<string, string>()\n // Per-session promise tail so out-of-band events (tool_call, plan, etc.)\n // serialize on the wire even though the ACP producer doesn't await us.\n const sendQueue = new Map<string, Promise<void>>()\n // Thread participation index: keyed by thread root event_id.\n const threadStates = new Map<string, ThreadState>()\n // Drop events older than this — Tuwunel may replay a backlog after the\n // daemon was offline, and we don't want yesterday's \"@docs hi\" to fire now.\n const cutoffTs = Date.now() - STARTUP_GRACE_MS\n // Idempotency: appservice transactions are retried on 4xx/5xx/timeout, and\n // the same event_id can arrive twice. Skip ones we've already taken.\n const seenEventIds = new Set<string>()\n\n agents.onEvent = async (name, event: AgentEvent) => {\n const ctx = sessions.get(event.sessionId)\n if (!ctx) {\n console.warn(`[matrix:${name}] no session ctx for ${event.sessionId}`)\n return\n }\n\n if (event.type === 'agent_message_chunk') {\n const block = event.content as { type?: string; text?: string }\n if (block.type === 'text' && typeof block.text === 'string') {\n const current = buffers.get(event.sessionId) ?? ''\n // An empty chunk signals a new text block starting (e.g. after a tool call).\n // Insert a paragraph break so consecutive blocks don't run together.\n const prefix = block.text === '' && current.length > 0 ? '\\n\\n' : ''\n buffers.set(event.sessionId, current + prefix + block.text)\n } else {\n console.warn(`[matrix:${name}] dropped chunk block type=${block.type}`, block)\n }\n return\n }\n\n const eventType =\n event.type === 'tool_call'\n ? 'eco.zoon.tool_call'\n : event.type === 'tool_call_update'\n ? 'eco.zoon.tool_call_update'\n : 'eco.zoon.plan'\n const body =\n event.type === 'tool_call'\n ? toToolCallBody(event)\n : event.type === 'tool_call_update'\n ? toUpdateBody(event)\n : toPlanBody(event)\n body['m.relates_to'] = { rel_type: 'm.thread', event_id: ctx.threadRoot }\n const tail = (sendQueue.get(event.sessionId) ?? Promise.resolve()).then(async () => {\n try {\n await client.sendCustomEvent({\n roomId: ctx.roomId,\n asUserId: ctx.agent.userId,\n eventType,\n content: body,\n })\n } catch (err) {\n console.warn(`[matrix:${name}] sendCustomEvent(${eventType}) failed:`, err)\n }\n })\n sendQueue.set(event.sessionId, tail)\n await tail\n }\n\n agents.onApprovalRequest = async (name, req) => {\n const handle = approvals.register(name, (req as { sessionId: string }).sessionId, req, {\n timeoutMs: agents.getApprovalTimeoutMs(name),\n })\n return handle.decisionPromise\n }\n\n approvals.on('registered', (handle: RegisteredApproval) => {\n const ctx = sessions.get(handle.sessionId)\n if (!ctx) return\n const content: Record<string, unknown> = {\n approval_id: handle.approvalId,\n session_id: handle.sessionId,\n tool_call_id: handle.toolCallId,\n options: handle.options,\n }\n content['m.relates_to'] = { rel_type: 'm.thread', event_id: ctx.threadRoot }\n if (handle.toolKind !== undefined) content.tool_kind = handle.toolKind\n if (handle.toolTitle !== undefined) content.tool_title = handle.toolTitle\n if (handle.toolInput !== undefined) content.tool_input = handle.toolInput\n void client.sendCustomEvent({\n roomId: ctx.roomId,\n asUserId: ctx.agent.userId,\n eventType: 'eco.zoon.approval_request',\n content,\n })\n })\n\n const app = new Hono()\n\n function authOk(authHeader: string | undefined): boolean {\n const h = authHeader ?? ''\n if (!h.startsWith('Bearer ')) return false\n const got = h.slice(7)\n if (got.length !== hsToken.length) return false\n return timingSafeEqual(Buffer.from(got), Buffer.from(hsToken))\n }\n\n app.put('/_matrix/app/v1/transactions/:txnId', async (c) => {\n if (!authOk(c.req.header('authorization'))) {\n return c.json({ errcode: 'M_FORBIDDEN' }, 403)\n }\n const body = (await c.req.json().catch(() => ({}))) as { events?: MatrixEvent[] }\n for (const evt of body.events ?? []) {\n if (evt.event_id) {\n if (seenEventIds.has(evt.event_id)) {\n continue\n }\n seenEventIds.add(evt.event_id)\n if (seenEventIds.size > SEEN_EVENT_CAP) {\n const first = seenEventIds.values().next().value\n if (first !== undefined) seenEventIds.delete(first)\n }\n }\n if (\n evt.origin_server_ts !== undefined &&\n evt.origin_server_ts < cutoffTs &&\n evt.type === 'm.room.message'\n ) {\n console.log(\n `[matrix] dropping stale message event ${evt.event_id} ` +\n `(ts=${evt.origin_server_ts}, daemon started at ${cutoffTs + STARTUP_GRACE_MS})`,\n )\n continue\n }\n if (evt.type === 'eco.zoon.session_reset') {\n // Spec § /clear: room-scope reset is unsupported. Only thread-scoped\n // resets carry a thread relation; drop bare room-level resets silently.\n const relates = evt.content?.['m.relates_to'] as\n | { rel_type?: string; event_id?: string }\n | undefined\n const threadRoot =\n relates?.rel_type === 'm.thread' && relates.event_id ? relates.event_id : undefined\n if (!threadRoot) {\n console.log('[matrix] dropping eco.zoon.session_reset without thread relation')\n continue\n }\n console.log(`[matrix] inbound eco.zoon.session_reset in ${evt.room_id} thread=${threadRoot}`)\n for (const a of bindings) {\n agents.endSession(a.name, threadRoot)\n }\n // NB: keep threadStates intact. Per ZOD039 § /clear, only the agent's\n // session memory is wiped — thread-routing state (participants /\n // root-mentions) must survive so the next bare reply still routes to\n // the most-recently-posting agent under the same sessionKey.\n continue\n }\n if (evt.type === 'eco.zoon.interrupt') {\n const content = (evt.content ?? {}) as { session_id?: string; reason?: string }\n // Thread-relation form (client-friendly): /interrupt in a thread sends\n // an empty event with `m.relates_to: thread/<root>`. Cancel every\n // session whose threadRoot matches.\n const relates = evt.content?.['m.relates_to'] as\n | { rel_type?: string; event_id?: string }\n | undefined\n const threadRoot =\n relates?.rel_type === 'm.thread' && relates.event_id ? relates.event_id : undefined\n if (threadRoot) {\n const targets: Array<{ sessionId: string; agent: string }> = []\n for (const [sessionId, ctx] of sessions) {\n if (ctx.threadRoot === threadRoot) {\n targets.push({ sessionId, agent: ctx.agent.name })\n }\n }\n for (const t of targets) {\n console.log(\n `[matrix] interrupt session=${t.sessionId} agent=${t.agent} thread=${threadRoot}` +\n (content.reason ? ` reason=${content.reason}` : ''),\n )\n await agents.cancelSession(t.agent, t.sessionId).catch((err) => {\n console.error(`[matrix] cancelSession(${t.agent}, ${t.sessionId}) failed:`, err)\n })\n }\n continue\n }\n // Legacy form: explicit session_id in content.\n if (!content.session_id) {\n console.warn(`[matrix] eco.zoon.interrupt missing session_id (event_id=${evt.event_id})`)\n continue\n }\n const ctx = sessions.get(content.session_id)\n if (!ctx) {\n continue\n }\n console.log(\n `[matrix] interrupt session=${content.session_id} agent=${ctx.agent.name}` +\n (content.reason ? ` reason=${content.reason}` : ''),\n )\n await agents.cancelSession(ctx.agent.name, content.session_id).catch((err) => {\n console.error(`[matrix] cancelSession(${ctx.agent.name}, ${content.session_id}) failed:`, err)\n })\n continue\n }\n if (evt.type === 'eco.zoon.approval_response') {\n const content = (evt.content ?? {}) as {\n approval_id?: string\n session_id?: string\n decision?: string\n option_id?: string\n }\n if (!content.session_id || !content.approval_id || !content.decision) continue\n const decision = content.option_id\n ? { decision: content.decision, optionId: content.option_id }\n : { decision: content.decision }\n const ok = approvals.resolve(\n content.session_id,\n content.approval_id,\n decision as never,\n )\n if (!ok) console.warn(`[matrix] unknown approval ${content.approval_id}`)\n continue\n }\n logInbound(evt)\n // Agent-promotion: top-level inbound event becomes the thread root.\n // For in-thread messages the existing root is preserved.\n const promotedRoot = inboundThreadRoot(evt) ?? evt.event_id\n // Self-heal: if this is a thread reply but we have no in-memory state\n // for the root (e.g. daemon was just restarted), reconstruct it by\n // fetching the thread root + relations from the server.\n const inboundRel = inboundThreadRoot(evt)\n if (\n evt.type === 'm.room.message' &&\n inboundRel &&\n !threadStates.has(inboundRel) &&\n evt.room_id\n ) {\n try {\n const rebuilt = await rebuildThreadState(client, evt.room_id, inboundRel, bindings)\n threadStates.set(inboundRel, rebuilt)\n console.log(\n `[matrix] rebuilt threadState for ${inboundRel}: participants=${rebuilt.participants.join(',')} rootMentions=${rebuilt.rootMentions.join(',')}`,\n )\n } catch (err) {\n console.warn(`[matrix] failed to rebuild threadState for ${inboundRel}:`, err)\n }\n }\n const matches = route(evt, bindings, threadStates)\n // Suppress the no-match warning for events sent by our own bots.\n const senderIsBot = bindings.some((b) => b.userId === evt.sender)\n if (evt.type === 'm.room.message' && matches.length === 0 && !senderIsBot) {\n console.warn(\n `[matrix] no agent matched message in ${evt.room_id} from ${evt.sender}` +\n ` (bindings: ${bindings.map((b) => `${b.name}@${b.userId}[${b.trigger}]`).join(', ')})`,\n )\n }\n // Seed thread state for any agent mentions in this event.\n if (matches.length > 0 && promotedRoot) {\n let st = threadStates.get(promotedRoot)\n if (!st) {\n st = { participants: [], rootMentions: [] }\n threadStates.set(promotedRoot, st)\n }\n const msgMentions = new Set(extractMentions(evt as never))\n for (const a of bindings) {\n if (msgMentions.has(a.userId) && !st.rootMentions.includes(a.name)) {\n st.rootMentions.push(a.name)\n }\n }\n }\n for (const a of matches) {\n console.log(`[matrix] → ${a.name} (${a.userId})`)\n void runTurn(a, evt)\n .then(() => {\n if (!promotedRoot) return\n let st = threadStates.get(promotedRoot)\n if (!st) {\n st = { participants: [], rootMentions: [] }\n threadStates.set(promotedRoot, st)\n }\n if (st.participants.at(-1) !== a.name) st.participants.push(a.name)\n })\n .catch((err) => {\n console.error(`[matrix] runTurn failed for ${a.name}:`, err)\n const c = classify(err)\n const threadRoot = inboundThreadRoot(evt) ?? evt.event_id\n if (!threadRoot || !evt.room_id) return\n const body = toErrorBody(\n {\n kind: 'error',\n agentId: a.name,\n sessionId: null,\n turnId: null,\n code: c.code,\n message: err instanceof Error ? err.message : String(err),\n detail: err instanceof Error && err.stack ? err.stack.slice(0, 2000) : undefined,\n transient: c.transient,\n acp_error: c.acp_error,\n },\n threadRoot,\n )\n void client\n .sendCustomEvent({\n roomId: evt.room_id,\n asUserId: a.userId,\n eventType: 'eco.zoon.error',\n content: body,\n })\n .catch((e) => console.warn(`[matrix:${a.name}] eco.zoon.error send failed:`, e))\n })\n }\n }\n return c.json({})\n })\n\n app.get('/_matrix/app/v1/users/:userId', (c) => {\n if (!authOk(c.req.header('authorization'))) {\n return c.json({ errcode: 'M_FORBIDDEN' }, 403)\n }\n return c.json({})\n })\n app.get('/_matrix/app/v1/rooms/:alias', (c) => {\n if (!authOk(c.req.header('authorization'))) {\n return c.json({ errcode: 'M_FORBIDDEN' }, 403)\n }\n return c.json({ errcode: 'M_NOT_FOUND' }, 404)\n })\n app.post('/_matrix/app/v1/ping', (c) => {\n if (!authOk(c.req.header('authorization'))) {\n return c.json({ errcode: 'M_FORBIDDEN' }, 403)\n }\n return c.json({})\n })\n app.get('/healthz', (c) => c.text('ok'))\n\n async function runTurn(agent: AgentBinding, evt: MatrixEvent): Promise<void> {\n if (!evt.room_id || !evt.event_id) return\n const inbound = inboundThreadRoot(evt)\n // Agent-promotion: top-level inbound becomes a thread root via the agent's\n // first reply. sessionKey is always a thread root id, never the room id.\n const threadRoot = inbound ?? evt.event_id\n const sessionKey = threadRoot\n const sessionId = await agents.ensureSession(agent.name, sessionKey, evt.room_id)\n sessions.set(sessionId, { agent, roomId: evt.room_id, threadRoot })\n buffers.set(sessionId, '')\n\n const roomId = evt.room_id\n const TYPING_TTL_MS = 30_000\n const TYPING_REFRESH_MS = 25_000\n const safeTyping = (typing: boolean) =>\n client\n .setTyping({ roomId, asUserId: agent.userId, typing, timeoutMs: TYPING_TTL_MS })\n .catch((err) => console.warn(`[matrix:${agent.name}] setTyping(${typing}) failed:`, err))\n const safePresence = (presence: 'online' | 'unavailable' | 'offline') =>\n client\n .setPresence({ asUserId: agent.userId, presence })\n .catch((err) =>\n console.warn(`[matrix:${agent.name}] setPresence(${presence}) failed:`, err),\n )\n\n await safeTyping(true)\n await safePresence('unavailable')\n const refresh = setInterval(() => {\n void safeTyping(true)\n }, TYPING_REFRESH_MS)\n\n try {\n const rawBody = evt.content?.body ?? ''\n const promptText = stripMention(rawBody, agent.userId)\n await agents.prompt(agent.name, {\n threadId: sessionKey,\n channelId: evt.room_id,\n content: [{ type: 'text', text: promptText }],\n })\n // Drain: the prompt promise resolves on the stopReason response, but\n // trailing chunks may still arrive (see DRAIN_* above). Wait until the\n // buffer is quiet for DRAIN_QUIET_MS, re-arming on each new chunk.\n //\n // Subtlety: some agents (opencode in particular) resolve `session/prompt`\n // *before* the agent_message_chunk stream starts. So the buffer can be\n // empty for several seconds after prompt resolves, and only then do the\n // chunks arrive. We can't break the drain just because the buffer is\n // empty — we have to wait up to drainMaxMs for chunks to *start*. Once\n // any content arrives, the \"quiet for drainQuietMs\" rule kicks in.\n const drainStart = Date.now()\n let drained = buffers.get(sessionId) ?? ''\n while (drainQuietMs > 0 && Date.now() - drainStart < drainMaxMs) {\n await delay(drainQuietMs)\n const next = buffers.get(sessionId) ?? ''\n // Stop only when we have content AND it hasn't grown — i.e. the\n // generation has actually started and is now done. An unchanged\n // empty buffer means the stream hasn't started yet; keep waiting.\n if (next === drained && next.length > 0) break\n drained = next\n }\n const text = buffers.get(sessionId) ?? ''\n if (text.length > 0) {\n const html = toMatrixHtml(text)\n const content: { msgtype: string; body: string; [k: string]: unknown } = {\n msgtype: 'm.text',\n body: text,\n }\n // Only attach formatted_body when it adds rich-text the plain body\n // can't carry. marked wraps plain prose in <p>…</p>; if that's all\n // we'd add, skip — most clients render `body` better than a stripped\n // re-encode.\n if (html) {\n const escapedPlain =\n '<p>' +\n text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>') +\n '</p>'\n const norm = (s: string) => s.replace(/\\s+/g, ' ').trim()\n if (norm(html) !== norm(escapedPlain)) {\n content.format = 'org.matrix.custom.html'\n content.formatted_body = html\n }\n }\n await client.sendMessage({\n roomId: evt.room_id,\n asUserId: agent.userId,\n content,\n threadRoot, // every reply threads, full stop\n })\n } else {\n console.warn(\n `[matrix:${agent.name}] turn finished with empty buffer (session=${sessionId}); nothing sent to ${evt.room_id}`,\n )\n }\n } finally {\n clearInterval(refresh)\n await safeTyping(false)\n await safePresence('online')\n buffers.delete(sessionId)\n }\n }\n\n return {\n app,\n bootstrap: async (\n bootstrapOpts: {\n spaceRoomId?: string\n asUserId?: string\n adminUserIds?: string[]\n } = {},\n ) => {\n await pool.bootstrap({ adminUserId, ...bootstrapOpts })\n await Promise.allSettled(\n bindings.map((b) =>\n client.setPresence({ asUserId: b.userId, presence: 'online' }).catch((err) => {\n console.warn(`[matrix:${b.name}] initial setPresence(online) failed:`, err)\n }),\n ),\n )\n },\n pool,\n }\n}\n\n/**\n * Reconstruct the in-memory ThreadState for a thread root by fetching the\n * root event + its thread relations from the server. Used to recover the\n * implicit-routing rule from ZOD039 § Implicit triggers in threads after a\n * daemon restart wipes the in-memory cache.\n */\nexport async function rebuildThreadState(\n client: MatrixClient,\n roomId: string,\n rootEventId: string,\n bindings: AgentBinding[],\n): Promise<ThreadState> {\n const state: ThreadState = { participants: [], rootMentions: [] }\n // Impersonate an agent that's actually a member of this room (AS reads\n // require room membership). Falling through to the first binding would\n // 403 if that agent never joined the target room.\n const asUser = (bindings.find((b) => b.rooms.some((r) => r.alias === roomId)) ?? bindings[0])?.userId\n if (!asUser) return state\n\n const root = await client.fetchEvent(roomId, rootEventId, asUser)\n if (root) {\n const rootMentions = new Set(extractMentions(root as never))\n for (const a of bindings) {\n if (rootMentions.has(a.userId) && !state.rootMentions.includes(a.name)) {\n state.rootMentions.push(a.name)\n }\n }\n }\n\n const { chunk: thread } = await client.fetchThreadRelations({\n roomId,\n rootEventId,\n asUserId: asUser,\n })\n // Also seed root-mentions from any subsequent agent @mentions in the thread.\n for (const ev of thread) {\n const mentions = new Set(extractMentions(ev as never))\n for (const a of bindings) {\n if (mentions.has(a.userId) && !state.rootMentions.includes(a.name)) {\n state.rootMentions.push(a.name)\n }\n }\n const sender = (ev as { sender?: string }).sender\n const type = (ev as { type?: string }).type\n if (type === 'm.room.message' && sender) {\n const a = bindings.find((b) => b.userId === sender)\n if (a && state.participants.at(-1) !== a.name) state.participants.push(a.name)\n }\n }\n return state\n}\n\nfunction logInbound(evt: MatrixEvent): void {\n const sender = evt.sender ?? '?'\n const room = evt.room_id ?? '?'\n const type = evt.type ?? '?'\n if (type === 'm.room.message') {\n const body = evt.content?.body ?? ''\n const mentions = (evt.content?.['m.mentions'] as { user_ids?: string[] } | undefined)?.user_ids\n const mentionsStr = mentions?.length ? ` mentions=${JSON.stringify(mentions)}` : ''\n console.log(\n `[matrix] inbound msg in ${room} from ${sender}${mentionsStr}: ${truncate(body, 200)}`,\n )\n } else {\n console.log(`[matrix] inbound ${type} in ${room} from ${sender}`)\n }\n}\n\nfunction truncate(s: string, n: number): string {\n return s.length > n ? s.slice(0, n) + '…' : s\n}\n","import type {\n PlanEvent,\n TapEvent,\n ToolCallEvent,\n ToolCallUpdateEvent,\n} from '@zooid/acp-client'\n\n/** Cap any single string in rawInput so big diffs / file contents don't bloat Matrix. */\nconst RAW_INPUT_STR_MAX = 250\n\nexport function toToolCallBody(evt: ToolCallEvent): Record<string, unknown> {\n const out: Record<string, unknown> = {\n session_id: evt.sessionId,\n tool_call_id: evt.toolCallId,\n title: evt.title,\n }\n if (evt.kind !== undefined) out.kind = evt.kind\n if (evt.status !== undefined) out.status = evt.status\n if (evt.rawInput !== undefined) out.raw_input = truncateStrings(evt.rawInput, RAW_INPUT_STR_MAX)\n if (evt.locations !== undefined) out.locations = evt.locations\n return out\n}\n\n/**\n * Recursively truncates string values longer than `max` with a \"… [truncated]\"\n * suffix. Non-string scalars and structure are preserved.\n */\nfunction truncateStrings(v: unknown, max: number): unknown {\n if (typeof v === 'string') {\n return v.length > max ? v.slice(0, max) + '… [truncated]' : v\n }\n if (Array.isArray(v)) {\n return v.map((item) => truncateStrings(item, max))\n }\n if (v && typeof v === 'object') {\n const out: Record<string, unknown> = {}\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n out[k] = truncateStrings(val, max)\n }\n return out\n }\n return v\n}\n\nexport function toUpdateBody(evt: ToolCallUpdateEvent): Record<string, unknown> {\n const out: Record<string, unknown> = {\n session_id: evt.sessionId,\n tool_call_id: evt.toolCallId,\n }\n if (evt.status !== undefined) out.status = evt.status\n if (evt.kind !== undefined) out.kind = evt.kind\n // content[] carries display-ready output (text/diff/terminal); rawOutput is\n // intentionally NOT serialized — it's typically large and duplicates content.\n if (evt.content !== undefined) out.content = evt.content\n // Some ACP agents only set rawInput on a later update (not the initial\n // tool_call). Truncate strings and forward.\n if (evt.rawInput !== undefined) out.raw_input = truncateStrings(evt.rawInput, RAW_INPUT_STR_MAX)\n if (evt.locations !== undefined) out.locations = evt.locations\n return out\n}\n\nexport function toPlanBody(evt: PlanEvent): Record<string, unknown> {\n return {\n session_id: evt.sessionId,\n entries: evt.entries,\n }\n}\n\nconst RECOVERY_URLS: Partial<Record<string, string>> = {\n auth_missing: 'https://zooid.dev/docs/guides/run-in-container#authentication-that-carries-over',\n auth_invalid: 'https://zooid.dev/docs/guides/run-in-container#authentication-that-carries-over',\n mount_failed: 'https://zooid.dev/docs/guides/run-in-container#what-you-get-for-free',\n image_pull_failed: 'https://zooid.dev/docs/guides/run-in-container#skipping-the-image-prepull',\n}\n\ntype ErrorTap = Extract<TapEvent, { kind: 'error' }>\n\nexport function toErrorBody(evt: ErrorTap, threadRoot: string): Record<string, unknown> {\n const msg = evt.message.slice(0, 250)\n const out: Record<string, unknown> = {\n msgtype: 'm.notice',\n body: `⚠ [${evt.code}] ${msg}`,\n code: evt.code,\n message: msg,\n transient: evt.transient,\n 'm.relates_to': { rel_type: 'm.thread', event_id: threadRoot },\n }\n if (evt.sessionId) out.session_id = evt.sessionId\n if (evt.turnId) out.turn_id = evt.turnId\n if (evt.detail) out.detail = evt.detail.slice(0, 2000)\n if (evt.acp_error) out.acp_error = evt.acp_error\n const recovery = RECOVERY_URLS[evt.code]\n if (recovery) out.recovery = recovery\n return out\n}\n","import { marked } from 'marked'\nimport sanitizeHtml from 'sanitize-html'\n\n// Matrix-permitted HTML tags per the m.room.message spec.\n// https://spec.matrix.org/v1.11/client-server-api/#mroommessage-msgtypes\nconst ALLOWED_TAGS = [\n 'del', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a',\n 'ul', 'ol', 'sup', 'sub', 'li', 'b', 'i', 'u', 'strong', 'em', 's',\n 'code', 'hr', 'br', 'div', 'table', 'thead', 'tbody', 'tr', 'th',\n 'td', 'caption', 'pre', 'span', 'img', 'details', 'summary',\n]\n\nconst ALLOWED_ATTRIBUTES: Record<string, string[]> = {\n a: ['href', 'name', 'target'],\n img: ['width', 'height', 'alt', 'title', 'src'],\n ol: ['start'],\n code: ['class'],\n span: ['data-mx-color', 'data-mx-bg-color', 'data-mx-spoiler'],\n}\n\nconst ALLOWED_SCHEMES = ['https', 'http', 'ftp', 'mailto', 'magnet', 'matrix']\n\nmarked.setOptions({ gfm: true, breaks: false, async: false })\n\nexport function toMatrixHtml(markdown: string): string {\n if (!markdown || !markdown.trim()) return ''\n let rawHtml: string\n try {\n const out = marked.parse(markdown) as string | Promise<string>\n if (typeof out !== 'string') return ''\n rawHtml = out\n } catch {\n return ''\n }\n return sanitizeHtml(rawHtml, {\n allowedTags: ALLOWED_TAGS,\n allowedAttributes: ALLOWED_ATTRIBUTES,\n allowedSchemes: ALLOWED_SCHEMES,\n allowedSchemesByTag: { a: ALLOWED_SCHEMES },\n })\n}\n","import { MatrixClient } from './matrix-client.js'\nimport type { AgentBinding } from './router.js'\n\nexport interface WorkforceRoster {\n version: 1\n agents: { user_id: string; name: string; rooms: string[] }[]\n}\n\nexport function buildWorkforceRoster(agents: AgentBinding[]): WorkforceRoster {\n return {\n version: 1,\n agents: agents.map((a) => ({\n user_id: a.userId,\n name: a.name,\n rooms: a.rooms.map((r) => r.alias),\n })),\n }\n}\n\nexport interface PublishOpts {\n client: MatrixClient\n spaceRoomId: string\n asUserId: string\n agents: AgentBinding[]\n}\n\nexport async function publishWorkforce(opts: PublishOpts): Promise<void> {\n await opts.client.sendStateEvent({\n roomId: opts.spaceRoomId,\n asUserId: opts.asUserId,\n eventType: 'eco.zoon.workforce',\n stateKey: '',\n content: buildWorkforceRoster(opts.agents) as unknown as Record<string, unknown>,\n })\n}\n\nexport interface PublisherHandle {\n reload(): Promise<void>\n stop(): Promise<void>\n}\n\nexport interface StartOpts {\n client: MatrixClient\n spaceRoomId: string\n asUserId: string\n getAgents: () => AgentBinding[]\n}\n\nexport async function startWorkforcePublisher(opts: StartOpts): Promise<PublisherHandle> {\n await publishWorkforce({ ...opts, agents: opts.getAgents() })\n return {\n async reload() {\n await publishWorkforce({ ...opts, agents: opts.getAgents() })\n },\n async stop() {},\n }\n}\n"],"mappings":"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","names":["localpart","body","localpart","inboundThreadRoot","ctx","c","body"]}
|
|
1
|
+
{"version":3,"sources":["../src/matrix-client.ts","../src/context-provider.ts","../src/registration.ts","../src/mentions.ts","../src/router.ts","../src/space-provisioner.ts","../src/bot-pool.ts","../src/transport.ts","../src/event-encoders.ts","../src/markdown-to-matrix-html.ts","../src/workforce-publisher.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\n\nexport interface MatrixClientOptions {\n homeserver: string\n asToken: string\n fetch?: typeof globalThis.fetch\n}\n\nexport interface SendMessageInput {\n roomId: string\n asUserId: string\n content: { msgtype: string; body: string; [k: string]: unknown }\n threadRoot?: string\n}\n\nexport interface SendCustomEventInput {\n roomId: string\n asUserId: string\n eventType: string\n content: Record<string, unknown>\n}\n\nexport interface SetTypingInput {\n roomId: string\n asUserId: string\n typing: boolean\n /** ms — homeserver expects re-PUTs before this expires. Ignored when typing=false. */\n timeoutMs?: number\n}\n\nexport interface SetPresenceInput {\n asUserId: string\n presence: 'online' | 'unavailable' | 'offline'\n statusMsg?: string\n}\n\nexport class MatrixClient {\n private readonly homeserver: string\n private readonly asToken: string\n private readonly fetch: typeof globalThis.fetch\n\n constructor(opts: MatrixClientOptions) {\n this.homeserver = opts.homeserver.replace(/\\/$/, '')\n this.asToken = opts.asToken\n this.fetch = opts.fetch ?? globalThis.fetch\n }\n\n async registerBot(\n localpart: string,\n ): Promise<{ user_id: string; device_id: string } | undefined> {\n const r = await this.fetch(`${this.homeserver}/_matrix/client/v3/register`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.asToken}` },\n body: JSON.stringify({ type: 'm.login.application_service', username: localpart }),\n })\n if (r.status === 200) return (await r.json()) as { user_id: string; device_id: string }\n if (r.status === 400) {\n const body = (await r.json()) as { errcode?: string }\n if (body.errcode === 'M_USER_IN_USE') return undefined\n }\n throw new Error(`registerBot(${localpart}) failed: ${r.status}`)\n }\n\n async resolveAlias(alias: string): Promise<string | null> {\n const r = await this.fetch(\n `${this.homeserver}/_matrix/client/v3/directory/room/${encodeURIComponent(alias)}`,\n { headers: { Authorization: `Bearer ${this.asToken}` } },\n )\n if (r.status === 404) return null\n if (!r.ok) throw new Error(`resolveAlias(${alias}) failed: ${r.status}`)\n const j = (await r.json()) as { room_id: string }\n return j.room_id\n }\n\n async createRoom(opts: {\n roomAliasName: string\n invite: string[]\n senderUserId: string\n preset?: 'public_chat' | 'private_chat' | 'trusted_private_chat'\n /** Optional `m.room.name`. When set, sent in the createRoom body so the\n * room has a display name from the moment it exists. */\n name?: string\n /** When set, the room is created with a `restricted` join rule whose allow\n * condition references this space room ID — i.e. joinable by space members\n * only, rather than the whole homeserver. */\n restrictedToSpaceId?: string\n /** Explicit room version. Restricted join rules require v8+; omit to use the\n * homeserver default (modern Tuwunel defaults to v10/v11). */\n roomVersion?: string\n /**\n * Seeds `m.room.power_levels.users` at creation via\n * `power_level_content_override.users`. The caller owns the full map —\n * typically the AS bot at 100, plus operator and any agents with\n * declared PLs. Empty/absent → no override (the preset's defaults apply).\n */\n userPowerLevels?: Record<string, number>\n }): Promise<string> {\n const body: Record<string, unknown> = {\n room_alias_name: opts.roomAliasName,\n invite: opts.invite,\n preset: opts.preset ?? 'public_chat',\n }\n if (opts.name !== undefined) body.name = opts.name\n if (opts.roomVersion !== undefined) body.room_version = opts.roomVersion\n if (opts.restrictedToSpaceId !== undefined) {\n body.initial_state = [\n {\n type: 'm.room.join_rules',\n state_key: '',\n content: {\n join_rule: 'restricted',\n allow: [{ type: 'm.room_membership', room_id: opts.restrictedToSpaceId }],\n },\n },\n ]\n }\n if (opts.userPowerLevels && Object.keys(opts.userPowerLevels).length > 0) {\n body.power_level_content_override = { users: opts.userPowerLevels }\n }\n const r = await this.fetch(\n `${this.homeserver}/_matrix/client/v3/createRoom?user_id=${encodeURIComponent(opts.senderUserId)}`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.asToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n },\n )\n if (!r.ok) {\n const body = await r.text()\n throw new Error(`createRoom(${opts.roomAliasName}) failed: ${r.status} ${body}`)\n }\n const j = (await r.json()) as { room_id: string }\n return j.room_id\n }\n\n async createRoomRaw(opts: {\n asUserId: string\n body: Record<string, unknown>\n }): Promise<string> {\n const url = `${this.homeserver}/_matrix/client/v3/createRoom?user_id=${encodeURIComponent(opts.asUserId)}`\n const r = await this.fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.asToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(opts.body),\n })\n if (!r.ok) throw new Error(`createRoomRaw failed: ${r.status}`)\n const j = (await r.json()) as { room_id: string }\n return j.room_id\n }\n\n async sendStateEvent(opts: {\n roomId: string\n asUserId: string\n eventType: string\n stateKey?: string\n content: Record<string, unknown>\n }): Promise<{ event_id: string }> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(opts.roomId)}` +\n `/state/${encodeURIComponent(opts.eventType)}/${encodeURIComponent(opts.stateKey ?? '')}` +\n `?user_id=${encodeURIComponent(opts.asUserId)}`\n const r = await this.fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${this.asToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(opts.content),\n })\n if (!r.ok) throw new Error(`sendStateEvent ${opts.eventType} failed: ${r.status}`)\n return (await r.json()) as { event_id: string }\n }\n\n /**\n * Invite a user to a room. Sent as the inviter (`asUserId`) — that user\n * needs invite power in the room. Tolerates the \"already in room /\n * already invited\" responses idempotently so bootstrap can run on a\n * fresh AND a populated homeserver without branching.\n */\n async invite(opts: {\n roomId: string\n asUserId: string\n targetUserId: string\n }): Promise<void> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(opts.roomId)}/invite` +\n `?user_id=${encodeURIComponent(opts.asUserId)}`\n const r = await this.fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.asToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({ user_id: opts.targetUserId }),\n })\n if (r.ok) return\n if (r.status === 403) {\n // Tuwunel/Synapse use M_FORBIDDEN both for permission errors AND for\n // \"already a member / already invited\". Inspect the body so we only\n // swallow the idempotent case.\n //\n // Phrasings seen in the wild:\n // - Synapse: \"is already in the room\", \"is already invited\"\n // - Tuwunel: \"cannot invite user that is joined or banned\" (one\n // string for both \"joined\" and \"banned\" — we can't tell which\n // from the body; the bot-pool's outer try-catch surfaces a real\n // ban via the subsequent joinRoom failure)\n // - Generic: \"X is already a member of the room\"\n const body = await r.text()\n const idempotent =\n /already (in the room|invited|a member|joined)/i.test(body) ||\n /user that is joined/i.test(body)\n if (idempotent) return\n throw new Error(`invite(${opts.targetUserId}) failed: 403 ${body}`)\n }\n throw new Error(`invite(${opts.targetUserId}) failed: ${r.status}`)\n }\n\n async joinRoom(roomIdOrAlias: string, asUserId: string): Promise<void> {\n const url =\n `${this.homeserver}/_matrix/client/v3/join/${encodeURIComponent(roomIdOrAlias)}` +\n `?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.asToken}` },\n body: '{}',\n })\n if (!r.ok) throw new Error(`joinRoom(${roomIdOrAlias}) failed: ${r.status}`)\n }\n\n async sendMessage(input: SendMessageInput): Promise<{ event_id: string }> {\n const content: Record<string, unknown> = { ...input.content }\n if (input.threadRoot) {\n content['m.relates_to'] = { rel_type: 'm.thread', event_id: input.threadRoot }\n }\n return this.sendEvent(input.roomId, input.asUserId, 'm.room.message', content)\n }\n\n async sendCustomEvent(input: SendCustomEventInput): Promise<{ event_id: string }> {\n return this.sendEvent(input.roomId, input.asUserId, input.eventType, input.content)\n }\n\n async setTyping(input: SetTypingInput): Promise<void> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(input.roomId)}` +\n `/typing/${encodeURIComponent(input.asUserId)}` +\n `?user_id=${encodeURIComponent(input.asUserId)}`\n const body: Record<string, unknown> = { typing: input.typing }\n if (input.typing && input.timeoutMs !== undefined) body.timeout = input.timeoutMs\n const r = await this.fetch(url, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${this.asToken}` },\n body: JSON.stringify(body),\n })\n if (!r.ok) throw new Error(`setTyping(${input.roomId}, ${input.asUserId}) failed: ${r.status}`)\n }\n\n async setDisplayName(asUserId: string, displayName: string): Promise<void> {\n const url =\n `${this.homeserver}/_matrix/client/v3/profile/${encodeURIComponent(asUserId)}/displayname` +\n `?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${this.asToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({ displayname: displayName }),\n })\n if (!r.ok) throw new Error(`setDisplayName(${asUserId}) failed: ${r.status}`)\n }\n\n async setPresence(input: SetPresenceInput): Promise<void> {\n const url =\n `${this.homeserver}/_matrix/client/v3/presence/${encodeURIComponent(input.asUserId)}/status` +\n `?user_id=${encodeURIComponent(input.asUserId)}`\n const body: Record<string, unknown> = { presence: input.presence }\n if (input.statusMsg !== undefined) body.status_msg = input.statusMsg\n const r = await this.fetch(url, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${this.asToken}` },\n body: JSON.stringify(body),\n })\n if (!r.ok) throw new Error(`setPresence(${input.asUserId}) failed: ${r.status}`)\n }\n\n /**\n * Fetch a single event from a room. Used to recover thread-root context\n * after a daemon restart wipes the in-memory threadStates cache.\n */\n async fetchEvent(\n roomId: string,\n eventId: string,\n asUserId: string,\n ): Promise<Record<string, unknown> | null> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}` +\n `/event/${encodeURIComponent(eventId)}` +\n `?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.asToken}` },\n })\n if (r.status === 404) return null\n if (!r.ok) throw new Error(`fetchEvent(${eventId}) failed: ${r.status}`)\n return (await r.json()) as Record<string, unknown>\n }\n\n /**\n * Fetch replies to a thread root via the relations endpoint, oldest-first.\n * Pass `limit` and `from` for pagination; `next_batch` echoes back when\n * there are more replies. Returns `{ chunk: [] }` when the root is unknown.\n */\n async fetchThreadRelations(opts: {\n roomId: string\n rootEventId: string\n asUserId: string\n limit?: number\n from?: string\n }): Promise<{ chunk: Array<Record<string, unknown>>; next_batch?: string }> {\n const params = new URLSearchParams({\n dir: 'f',\n limit: String(opts.limit ?? 100),\n user_id: opts.asUserId,\n })\n if (opts.from) params.set('from', opts.from)\n const url =\n `${this.homeserver}/_matrix/client/v1/rooms/${encodeURIComponent(opts.roomId)}` +\n `/relations/${encodeURIComponent(opts.rootEventId)}/m.thread?${params.toString()}`\n const r = await this.fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.asToken}` },\n })\n if (r.status === 404) return { chunk: [] }\n if (!r.ok) throw new Error(`fetchThreadRelations(${opts.rootEventId}) failed: ${r.status}`)\n const body = (await r.json()) as {\n chunk?: Array<Record<string, unknown>>\n next_batch?: string\n }\n return { chunk: body.chunk ?? [], next_batch: body.next_batch }\n }\n\n /**\n * Paginate the room timeline. Returns events newest-first (dir=b) per Matrix\n * spec. The caller is responsible for reversing if it wants oldest-first.\n */\n async fetchRoomMessages(opts: {\n roomId: string\n asUserId: string\n limit?: number\n /** Opaque pagination token returned in `end` from a previous call. */\n from?: string\n /**\n * Server-side RoomEventFilter. Common keys: `types` (whitelist event types),\n * `not_types`, `not_rel_types` (e.g. `['m.thread']` to exclude thread\n * replies). Encoded as JSON into the `filter` query param.\n */\n filter?: {\n types?: string[]\n not_types?: string[]\n rel_types?: string[]\n not_rel_types?: string[]\n }\n }): Promise<{ chunk: Array<Record<string, unknown>>; end?: string }> {\n const params = new URLSearchParams({\n dir: 'b',\n limit: String(opts.limit ?? 50),\n user_id: opts.asUserId,\n })\n if (opts.from) params.set('from', opts.from)\n if (opts.filter) params.set('filter', JSON.stringify(opts.filter))\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(opts.roomId)}` +\n `/messages?${params.toString()}`\n const r = await this.fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.asToken}` },\n })\n if (!r.ok) throw new Error(`fetchRoomMessages(${opts.roomId}) failed: ${r.status}`)\n return (await r.json()) as { chunk: Array<Record<string, unknown>>; end?: string }\n }\n\n async getJoinedMembers(\n roomId: string,\n asUserId: string,\n ): Promise<{ joined: Record<string, { display_name?: string }> }> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}` +\n `/joined_members?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.asToken}` },\n })\n if (!r.ok) throw new Error(`getJoinedMembers(${roomId}) failed: ${r.status}`)\n return (await r.json()) as { joined: Record<string, { display_name?: string }> }\n }\n\n async fetchRoomName(roomId: string, asUserId: string): Promise<string | null> {\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}` +\n `/state/m.room.name/?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'GET',\n headers: { Authorization: `Bearer ${this.asToken}` },\n })\n if (r.status === 404) return null\n if (!r.ok) throw new Error(`fetchRoomName(${roomId}) failed: ${r.status}`)\n const body = (await r.json()) as { name?: string }\n return body.name ?? null\n }\n\n private async sendEvent(\n roomId: string,\n asUserId: string,\n eventType: string,\n content: Record<string, unknown>,\n ): Promise<{ event_id: string }> {\n const txn = randomUUID()\n const url =\n `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}` +\n `/send/${eventType}/${txn}?user_id=${encodeURIComponent(asUserId)}`\n const r = await this.fetch(url, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${this.asToken}` },\n body: JSON.stringify(content),\n })\n if (!r.ok) throw new Error(`sendEvent(${eventType}) failed: ${r.status}`)\n return (await r.json()) as { event_id: string }\n }\n}\n","import type {\n TransportContextProvider,\n HistoryOptions,\n HistoryPage,\n Member,\n ChannelInfo,\n Message,\n ThreadOverview,\n ThreadOverviewPage,\n} from '@zooid/core'\nimport type { MatrixClient } from './matrix-client.js'\n\ninterface MatrixMessageEvent {\n event_id: string\n sender: string\n origin_server_ts: number\n type: string\n content?: {\n msgtype?: string\n body?: string\n 'm.relates_to'?: { rel_type?: string; event_id?: string }\n }\n unsigned?: {\n 'm.relations'?: {\n 'm.thread'?: {\n count?: number\n latest_event?: { origin_server_ts?: number }\n }\n }\n }\n}\n\nexport interface MatrixContextProviderOpts {\n client: MatrixClient\n /** AS sender_localpart user (read access). */\n asUserId: string\n /** Map of Matrix user IDs → agent names, for is_agent / agent_name flags. */\n agentBots: Map<string, string>\n}\n\nexport class MatrixContextProvider implements TransportContextProvider {\n constructor(private readonly opts: MatrixContextProviderOpts) {}\n\n async getRoomHistory(channelId: string, hopts: HistoryOptions): Promise<HistoryPage> {\n // Server-side filter: only `m.room.message` events. Without this we'd\n // burn the page budget on reactions, `eco.zoon.*` custom events, typing\n // notifications, etc., and routinely return empty pages with a stale\n // `has_more` cursor.\n const { chunk, end } = await this.opts.client.fetchRoomMessages({\n roomId: channelId,\n asUserId: this.opts.asUserId,\n limit: hopts.limit,\n from: hopts.before,\n filter: { types: ['m.room.message'] },\n })\n const messages: Message[] = []\n for (let i = chunk.length - 1; i >= 0; i--) {\n const ev = chunk[i] as unknown as MatrixMessageEvent\n const msg = this.toMessage(ev)\n if (msg) messages.push(msg)\n }\n return {\n messages,\n next_before: end,\n has_more: end !== undefined,\n }\n }\n\n async getRecentThreads(\n channelId: string,\n hopts: HistoryOptions,\n ): Promise<ThreadOverviewPage> {\n // Server-side filter: `m.room.message` only, and exclude thread replies\n // (`not_rel_types: ['m.thread']`) so the overview shows top-level entries\n // and thread roots, not the reply noise underneath them.\n const { chunk, end } = await this.opts.client.fetchRoomMessages({\n roomId: channelId,\n asUserId: this.opts.asUserId,\n limit: hopts.limit,\n from: hopts.before,\n filter: { types: ['m.room.message'], not_rel_types: ['m.thread'] },\n })\n // /messages returns newest-first; keep that order for the overview.\n const threads: ThreadOverview[] = []\n for (const ev of chunk as unknown as MatrixMessageEvent[]) {\n if (ev.type !== 'm.room.message') continue\n if (ev.content?.msgtype !== 'm.text' || typeof ev.content.body !== 'string') continue\n const relatesTo = ev.content['m.relates_to']\n if (relatesTo?.rel_type === 'm.thread') continue // skip thread replies\n const agent = this.opts.agentBots.get(ev.sender)\n const bundled = ev.unsigned?.['m.relations']?.['m.thread']\n const replyCount = bundled?.count ?? 0\n const latestTs = bundled?.latest_event?.origin_server_ts ?? ev.origin_server_ts\n threads.push({\n id: ev.event_id,\n sender: ev.sender,\n text: ev.content.body,\n timestamp: new Date(ev.origin_server_ts).toISOString(),\n is_agent: agent !== undefined,\n ...(agent !== undefined ? { agent_name: agent } : {}),\n reply_count: replyCount,\n last_activity_at: new Date(latestTs).toISOString(),\n })\n }\n return {\n threads,\n next_before: end,\n has_more: end !== undefined,\n }\n }\n\n async getThreadHistory(\n channelId: string,\n threadId: string,\n hopts: HistoryOptions,\n ): Promise<HistoryPage> {\n // Root event first (only on the first page when no pagination cursor).\n const messages: Message[] = []\n if (!hopts.before) {\n const root = (await this.opts.client.fetchEvent(\n channelId,\n threadId,\n this.opts.asUserId,\n )) as unknown as MatrixMessageEvent | null\n if (root) {\n const rootMsg = this.toMessage(root)\n if (rootMsg) messages.push({ ...rootMsg, thread_id: threadId })\n }\n }\n const { chunk, next_batch } = await this.opts.client.fetchThreadRelations({\n roomId: channelId,\n rootEventId: threadId,\n asUserId: this.opts.asUserId,\n limit: hopts.limit,\n from: hopts.before,\n })\n for (const ev of chunk as unknown as MatrixMessageEvent[]) {\n const reply = this.toMessage(ev)\n if (reply) messages.push({ ...reply, thread_id: threadId })\n }\n return {\n messages,\n next_before: next_batch,\n has_more: next_batch !== undefined,\n }\n }\n\n private toMessage(ev: MatrixMessageEvent): Message | null {\n if (ev.type !== 'm.room.message') return null\n if (ev.content?.msgtype !== 'm.text' || typeof ev.content.body !== 'string') return null\n const agent = this.opts.agentBots.get(ev.sender)\n const relatesTo = ev.content['m.relates_to']\n const threadId =\n relatesTo?.rel_type === 'm.thread' && relatesTo.event_id ? relatesTo.event_id : undefined\n return {\n id: ev.event_id,\n sender: ev.sender,\n text: ev.content.body,\n timestamp: new Date(ev.origin_server_ts).toISOString(),\n is_agent: agent !== undefined,\n ...(agent !== undefined ? { agent_name: agent } : {}),\n ...(threadId !== undefined ? { thread_id: threadId } : {}),\n }\n }\n\n async getChannelMembers(channelId: string): Promise<Member[]> {\n const { joined } = await this.opts.client.getJoinedMembers(channelId, this.opts.asUserId)\n return Object.entries(joined).map(([id, info]) => {\n const agent = this.opts.agentBots.get(id)\n return {\n id,\n name: info.display_name ?? id,\n is_agent: agent !== undefined,\n ...(agent !== undefined ? { agent_name: agent } : {}),\n }\n })\n }\n\n async getChannelInfo(channelId: string): Promise<ChannelInfo> {\n const name = await this.opts.client.fetchRoomName(channelId, this.opts.asUserId)\n return {\n id: channelId,\n name: name ?? channelId,\n transport: 'matrix',\n }\n }\n}\n","import { stringify } from 'yaml'\n\nexport interface MatrixTransportConfig {\n id: string\n url: string\n homeserver: string\n asToken: string\n hsToken: string\n senderLocalpart: string\n /** Regex covering all bot users, e.g. `@.*:example.com` */\n userNamespace: string\n /**\n * Optional regex covering aliases the AS may claim, e.g. `#.*:example.com`.\n * Required when the AS calls `createRoom` with a `room_alias_name`.\n */\n aliasNamespace?: string\n /**\n * Whether the AS exclusively owns the user_namespace. Default true.\n * Set false when humans share the namespace (e.g., `zooid dev` registers\n * a predefined admin under the same `@.*:localhost` regex).\n */\n exclusive?: boolean\n}\n\nexport function renderRegistration(c: MatrixTransportConfig): string {\n return stringify(\n {\n id: c.id,\n url: c.url,\n as_token: c.asToken,\n hs_token: c.hsToken,\n sender_localpart: c.senderLocalpart,\n rate_limited: false,\n namespaces: {\n users: [{ exclusive: c.exclusive ?? true, regex: c.userNamespace }],\n aliases: c.aliasNamespace\n ? [{ exclusive: c.exclusive ?? true, regex: c.aliasNamespace }]\n : [],\n rooms: [],\n },\n },\n { defaultStringType: 'PLAIN', defaultKeyType: 'PLAIN', singleQuote: true },\n )\n}\n","export interface MaybeMessage {\n content?: {\n 'm.mentions'?: { user_ids?: string[] }\n body?: string\n formatted_body?: string\n }\n}\n\nconst MATRIX_TO_RE = /https:\\/\\/matrix\\.to\\/#\\/(@[^\"<>\\s]+)/g\nconst RAW_USER_RE = /(@[A-Za-z0-9._\\-=/+]+:[A-Za-z0-9.\\-]+)/g\n\nexport function extractMentions(event: MaybeMessage): string[] {\n const out = new Set<string>()\n const c = event.content ?? {}\n for (const id of c['m.mentions']?.user_ids ?? []) out.add(id)\n if (c.formatted_body) {\n for (const m of c.formatted_body.matchAll(MATRIX_TO_RE)) {\n out.add(decodeURIComponent(m[1]))\n }\n }\n if (c.body && out.size === 0) {\n for (const m of c.body.matchAll(RAW_USER_RE)) out.add(m[1])\n }\n return [...out]\n}\n\n/**\n * Remove a single user-id mention from a message body. Strips the raw\n * `@local:server` form and collapses any whitespace that the strip leaves\n * behind. Other users' mentions are preserved verbatim so the agent can\n * reason about them.\n */\nexport function stripMention(body: string, userId: string): string {\n const escaped = userId.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n // Match optional surrounding whitespace so we don't leave double spaces.\n const re = new RegExp(`\\\\s*${escaped}\\\\s*`, 'g')\n return body.replace(re, ' ').replace(/\\s+/g, ' ').trim()\n}\n","import type { RoomBinding } from '@zooid/core'\nimport { extractMentions } from './mentions.js'\n\nexport type { RoomBinding }\n\nexport interface AgentBinding {\n name: string\n userId: string\n /** Optional human-readable display name. Falls back to the user_id localpart. */\n displayName?: string\n /**\n * Rooms this agent is bound to. Each entry's `alias` starts out as the\n * configured `#alias` (or `!id`) and is rewritten to the canonical room\n * ID by `BotPool.bootstrap`. Optional `powerLevel` is seeded into the\n * room's `m.room.power_levels.users` at room creation only.\n */\n rooms: RoomBinding[]\n trigger: 'mention' | 'any'\n}\n\nexport interface ThreadState {\n /** Agent names that have posted in this thread, in order. */\n participants: string[]\n /** Agent names @mentioned in the thread root event (or subsequently). */\n rootMentions: string[]\n}\n\ninterface MaybeEvent {\n type?: string\n room_id?: string\n sender?: string\n content?: {\n msgtype?: string\n 'm.relates_to'?: { rel_type?: string; event_id?: string }\n }\n}\n\nexport type RouteMatch = AgentBinding\n\nfunction inboundThreadRoot(event: MaybeEvent): string | undefined {\n const r = event.content?.['m.relates_to']\n return r?.rel_type === 'm.thread' && r.event_id ? r.event_id : undefined\n}\n\nexport function route(\n event: MaybeEvent,\n agents: AgentBinding[],\n threadStates?: Map<string, ThreadState>,\n): RouteMatch[] {\n if (event.type !== 'm.room.message') return []\n if (!event.content?.msgtype) return []\n const mentions = new Set(extractMentions(event as never))\n const matches: RouteMatch[] = []\n const threadRoot = inboundThreadRoot(event)\n const threadState = threadRoot ? threadStates?.get(threadRoot) : undefined\n\n for (const a of agents) {\n if (event.sender === a.userId) continue\n if (!a.rooms.some((r) => r.alias === event.room_id)) continue\n if (a.trigger === 'any') {\n matches.push(a)\n continue\n }\n // trigger === 'mention'\n if (mentions.has(a.userId)) {\n matches.push(a)\n continue\n }\n // Implicit trigger in a thread: most-recent-poster, or root-mention\n // inheritance if no agent has posted yet.\n if (threadState) {\n const lastPoster = threadState.participants.at(-1)\n if (lastPoster) {\n if (lastPoster === a.name) matches.push(a)\n } else if (threadState.rootMentions.includes(a.name)) {\n matches.push(a)\n }\n }\n }\n return matches\n}\n","import { MatrixClient } from './matrix-client.js'\n\nexport interface EnsureSpaceOpts {\n client: MatrixClient\n asUserId: string\n serverName: string\n spaceLocalpart: string\n preset: 'public_chat' | 'private_chat'\n /**\n * Operator MXIDs to seed at PL 100 in the space's `m.room.power_levels`\n * at creation. The AS bot is always included. Empty/absent → no override\n * (the preset's PL defaults apply). Only consulted on first creation —\n * if the alias already resolves we return the existing room untouched.\n */\n admins?: string[]\n}\n\nexport async function ensureWorkforceSpace(opts: EnsureSpaceOpts): Promise<string> {\n const alias = `#${opts.spaceLocalpart}:${opts.serverName}`\n const existing = await opts.client.resolveAlias(alias)\n if (existing) return existing\n\n const display = opts.spaceLocalpart.charAt(0).toUpperCase() + opts.spaceLocalpart.slice(1)\n const body: Record<string, unknown> = {\n room_alias_name: opts.spaceLocalpart,\n name: display,\n preset: opts.preset,\n creation_content: { type: 'm.space' },\n // A workspace is joined by invitation, not self-service. Pin the space's\n // join rule to invite regardless of preset so it can't be walked into\n // (which would otherwise satisfy every restricted child room's allow).\n initial_state: [{ type: 'm.room.join_rules', state_key: '', content: { join_rule: 'invite' } }],\n }\n if (opts.admins && opts.admins.length > 0) {\n // Invite each admin so they actually become members — PL 100 alone does\n // not grant membership in an invite-only space.\n body.invite = opts.admins\n const users: Record<string, number> = { [opts.asUserId]: 100 }\n for (const a of opts.admins) users[a] = 100\n body.power_level_content_override = { users }\n }\n return opts.client.createRoomRaw({ asUserId: opts.asUserId, body })\n}\n\nexport interface EnsureDefaultChannelOpts {\n client: MatrixClient\n asUserId: string\n serverName: string\n spaceId: string\n /** Localpart of the default channel; defaults to `general`. */\n channelLocalpart?: string\n /**\n * Operator MXIDs to seed at PL 100 in the channel's `m.room.power_levels`\n * at creation. The AS bot is always included. Empty/absent → no override\n * (the preset's PL defaults apply). Only consulted on first creation —\n * if the alias already resolves we return the existing room untouched.\n */\n admins?: string[]\n}\n\n/**\n * Ensure a space has a default channel (`#general` by default), restricted to\n * the space's members and attached as an `m.space.child`. Idempotent: returns\n * the existing room if the alias already resolves. Has no agent — it's the\n * human landing room, so it is created here at provisioning time rather than\n * via the agent-room path.\n */\nexport async function ensureDefaultChannel(opts: EnsureDefaultChannelOpts): Promise<string> {\n const localpart = opts.channelLocalpart ?? 'general'\n const alias = `#${localpart}:${opts.serverName}`\n const existing = await opts.client.resolveAlias(alias)\n if (existing) return existing\n\n let userPowerLevels: Record<string, number> | undefined\n if (opts.admins && opts.admins.length > 0) {\n userPowerLevels = { [opts.asUserId]: 100 }\n for (const a of opts.admins) userPowerLevels[a] = 100\n }\n\n const roomId = await opts.client.createRoom({\n roomAliasName: localpart,\n invite: [],\n senderUserId: opts.asUserId,\n name: localpart.charAt(0).toUpperCase() + localpart.slice(1),\n restrictedToSpaceId: opts.spaceId,\n ...(userPowerLevels ? { userPowerLevels } : {}),\n })\n await opts.client.sendStateEvent({\n roomId: opts.spaceId,\n asUserId: opts.asUserId,\n eventType: 'm.space.child',\n stateKey: roomId,\n content: { via: [opts.serverName] },\n })\n return roomId\n}\n\nexport function serverNameFromMxid(mxid: string): string {\n const colon = mxid.indexOf(':')\n if (colon < 0) {\n throw new Error(`mxid lacks server: ${mxid}`)\n }\n return mxid.slice(colon + 1)\n}\n","import type { MatrixClient } from './matrix-client.js'\nimport type { AgentBinding } from './router.js'\nimport { serverNameFromMxid } from './space-provisioner.js'\n\nexport interface BootstrapOpts {\n /** Invited to any newly-created room; absent = no invite. */\n adminUserId?: string\n /** Workforce space room ID. When set, every resolved agent room is attached as m.space.child. */\n spaceRoomId?: string\n /** AS bot user ID. Required when spaceRoomId is set; sender of the m.space.child write. */\n asUserId?: string\n /**\n * Operator MXIDs seeded at PL 100 in every agent room this pool creates.\n * Applied via `power_level_content_override.users` at room creation only —\n * never reconciled. Empty/absent = no operator entries.\n */\n adminUserIds?: string[]\n}\n\nexport class BotPool {\n constructor(\n private readonly client: Pick<\n MatrixClient,\n | 'registerBot'\n | 'invite'\n | 'joinRoom'\n | 'resolveAlias'\n | 'createRoom'\n | 'sendStateEvent'\n | 'setDisplayName'\n >,\n private readonly agents: AgentBinding[],\n ) {}\n\n async bootstrap(opts: BootstrapOpts = {}): Promise<void> {\n const aliasToId = new Map<string, string>()\n const attachedToSpace = new Set<string>()\n for (const a of this.agents) {\n const lp = localpart(a.userId)\n try {\n await this.client.registerBot(lp)\n } catch (err) {\n console.warn(`[matrix] register failed for ${a.userId}: ${(err as Error).message}`)\n }\n try {\n await this.client.setDisplayName(a.userId, a.displayName ?? lp)\n } catch (err) {\n console.warn(`[matrix] setDisplayName(${a.userId}) failed: ${(err as Error).message}`)\n }\n // Make each agent a member of the workforce space. That covers two\n // things at once: the eco.zoon.workforce roster is now backed by\n // actual space membership (so the Zoon client's member autocomplete\n // works across rooms), and every restricted child room's allow rule\n // is satisfied without per-room invites.\n if (opts.spaceRoomId && opts.asUserId) {\n try {\n await this.client.invite({\n roomId: opts.spaceRoomId,\n asUserId: opts.asUserId,\n targetUserId: a.userId,\n })\n await this.client.joinRoom(opts.spaceRoomId, a.userId)\n } catch (err) {\n console.warn(\n `[matrix] space membership for ${a.userId} failed: ${(err as Error).message}`,\n )\n }\n }\n for (let i = 0; i < a.rooms.length; i++) {\n const binding = a.rooms[i]!\n const room = binding.alias\n try {\n let resolved = room\n if (room.startsWith('#')) {\n const cached = aliasToId.get(room)\n if (cached) {\n resolved = cached\n } else {\n const existing = await this.client.resolveAlias(room)\n if (existing) {\n resolved = existing\n } else {\n const colon = room.indexOf(':')\n const aliasLocalpart = colon > 1 ? room.slice(1, colon) : room.slice(1)\n const sender = opts.adminUserId ?? a.userId\n const userPowerLevels = buildUserPowerLevels(\n opts.asUserId,\n opts.adminUserIds,\n this.agents,\n room,\n )\n resolved = await this.client.createRoom({\n roomAliasName: aliasLocalpart,\n invite: opts.adminUserId ? [opts.adminUserId] : [],\n senderUserId: sender,\n name: aliasLocalpart,\n ...(opts.spaceRoomId ? { restrictedToSpaceId: opts.spaceRoomId } : {}),\n ...(userPowerLevels ? { userPowerLevels } : {}),\n })\n }\n aliasToId.set(room, resolved)\n }\n }\n // Rewrite the binding's alias to the canonical room ID so the\n // router (which matches on event.room_id) sees a hit when Tuwunel\n // pushes events. The declared powerLevel was a creation-time\n // seed — it has no role after bootstrap.\n binding.alias = resolved\n await this.client.joinRoom(resolved, a.userId)\n\n if (\n opts.spaceRoomId &&\n opts.asUserId &&\n !attachedToSpace.has(resolved)\n ) {\n attachedToSpace.add(resolved)\n const via = serverNameFromMxid(a.userId)\n try {\n await this.client.sendStateEvent({\n roomId: opts.spaceRoomId,\n asUserId: opts.asUserId,\n eventType: 'm.space.child',\n stateKey: resolved,\n content: { via: [via] },\n })\n } catch (err) {\n console.warn(\n `[matrix] m.space.child(${resolved}) failed: ${(err as Error).message}`,\n )\n }\n }\n } catch (err) {\n console.warn(\n `[matrix] join failed (${a.userId} → ${room}): ${(err as Error).message}`,\n )\n }\n }\n }\n }\n\n findByUserId(userId: string): AgentBinding | undefined {\n return this.agents.find((a) => a.userId === userId)\n }\n\n findByName(name: string): AgentBinding | undefined {\n return this.agents.find((a) => a.name === name)\n }\n}\n\nfunction localpart(userId: string): string {\n const m = /^@([^:]+):/.exec(userId)\n if (!m) throw new Error(`bad user id: ${userId}`)\n return m[1]\n}\n\n/**\n * Build the `userPowerLevels` map for a room about to be created. Always\n * seeds the AS bot (when known) and any operator admins at PL 100; layers\n * per-agent declared PLs on top so agents land at moderator (or whatever\n * they declared) without a follow-up state write. Returns undefined when\n * the map would be empty — `createRoom` then omits the override entirely\n * and the preset's defaults apply.\n */\nfunction buildUserPowerLevels(\n asUserId: string | undefined,\n admins: string[] | undefined,\n agents: AgentBinding[],\n roomAlias: string,\n): Record<string, number> | undefined {\n const users: Record<string, number> = {}\n if (asUserId) users[asUserId] = 100\n if (admins) for (const a of admins) users[a] = 100\n for (const a of agents) {\n for (const r of a.rooms) {\n if (r.alias !== roomAlias) continue\n if (r.powerLevel !== undefined) users[a.userId] = r.powerLevel\n }\n }\n return Object.keys(users).length > 0 ? users : undefined\n}\n","import { Hono } from 'hono'\nimport { timingSafeEqual } from 'node:crypto'\nimport type { AcpRegistry, ApprovalCorrelator, RegisteredApproval } from '@zooid/core'\nimport type { AgentEvent } from '@zooid/acp-client'\nimport { MatrixClient } from './matrix-client.js'\nimport { BotPool } from './bot-pool.js'\nimport { route, type AgentBinding, type ThreadState } from './router.js'\nimport { stripMention, extractMentions } from './mentions.js'\nimport { toToolCallBody, toUpdateBody, toPlanBody, toErrorBody } from './event-encoders.js'\nimport { classify } from '@zooid/acp-client'\nimport { toMatrixHtml } from './markdown-to-matrix-html.js'\n\nexport interface CreateMatrixTransportOptions {\n agents: AcpRegistry\n approvals: ApprovalCorrelator\n client: MatrixClient\n bindings: AgentBinding[]\n hsToken: string\n /** Admin Matrix user ID. When set, BotPool.bootstrap invites this user into rooms it creates. */\n adminUserId?: string\n /** Post-turn drain: keep collecting trailing `agent_message_chunk`s until the\n * buffer is quiet for this long before flushing. Defaults to `DRAIN_QUIET_MS`.\n * Set to 0 to disable the drain (e.g. in tests). */\n drainQuietMs?: number\n /** Hard cap on the post-turn drain. Defaults to `DRAIN_MAX_MS`. */\n drainMaxMs?: number\n}\n\ninterface SessionContext {\n agent: AgentBinding\n roomId: string\n /** Always set — every session is thread-scoped via agent-promotion. */\n threadRoot: string\n}\n\ninterface MatrixEvent {\n type?: string\n event_id?: string\n origin_server_ts?: number\n room_id?: string\n sender?: string\n content?: Record<string, unknown> & {\n msgtype?: string\n body?: string\n 'm.relates_to'?: { rel_type?: string; event_id?: string }\n }\n}\n\nconst STARTUP_GRACE_MS = 5_000\nconst SEEN_EVENT_CAP = 5_000\n\n// ACP only guarantees that an agent flushes pending `session/update`\n// notifications before the `session/prompt` response in the *cancellation*\n// path; for a normal turn the ordering is unspecified. Some agents (e.g.\n// opencode) emit trailing `agent_message_chunk`s a few ms after the stopReason\n// response, so finalizing the moment `prompt()` resolves truncates the reply.\n// After the turn resolves we wait for the buffer to stay unchanged for\n// DRAIN_QUIET_MS (debounce — re-arms on each late chunk) before flushing,\n// capped at DRAIN_MAX_MS so a misbehaving stream can't hang the turn.\nconst DRAIN_QUIET_MS = 300\n// 30s upper bound on how long we wait after `session/prompt` resolves before\n// flushing whatever we have (or declaring an empty turn). Set high because\n// some agents — opencode especially — resolve the prompt promise *before*\n// the agent_message_chunk stream starts, and the chunk burst can be 5–15s\n// after that. The drain still short-circuits via DRAIN_QUIET_MS once any\n// content has settled, so this cap only kicks in for genuinely-stuck turns.\nconst DRAIN_MAX_MS = 30_000\n\nconst delay = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))\n\nfunction inboundThreadRoot(evt: MatrixEvent): string | undefined {\n const r = evt.content?.['m.relates_to']\n return r?.rel_type === 'm.thread' && r.event_id ? r.event_id : undefined\n}\n\nexport function createMatrixTransport(opts: CreateMatrixTransportOptions) {\n const { agents, approvals, client, bindings, hsToken, adminUserId } = opts\n const drainQuietMs = opts.drainQuietMs ?? DRAIN_QUIET_MS\n const drainMaxMs = opts.drainMaxMs ?? DRAIN_MAX_MS\n const pool = new BotPool(client, bindings)\n const sessions = new Map<string, SessionContext>()\n const buffers = new Map<string, string>()\n // Last messageId seen per session's buffer. opencode streams each assistant\n // message under its own id with no delimiter chunk between them, so a change\n // here marks a message boundary we must break on.\n const bufferMessageIds = new Map<string, string>()\n // Per-session promise tail so out-of-band events (tool_call, plan, etc.)\n // serialize on the wire even though the ACP producer doesn't await us.\n const sendQueue = new Map<string, Promise<void>>()\n // Thread participation index: keyed by thread root event_id.\n const threadStates = new Map<string, ThreadState>()\n // Drop events older than this — Tuwunel may replay a backlog after the\n // daemon was offline, and we don't want yesterday's \"@docs hi\" to fire now.\n const cutoffTs = Date.now() - STARTUP_GRACE_MS\n // Idempotency: appservice transactions are retried on 4xx/5xx/timeout, and\n // the same event_id can arrive twice. Skip ones we've already taken.\n const seenEventIds = new Set<string>()\n\n agents.onEvent = async (name, event: AgentEvent) => {\n const ctx = sessions.get(event.sessionId)\n if (!ctx) {\n console.warn(`[matrix:${name}] no session ctx for ${event.sessionId}`)\n return\n }\n\n if (event.type === 'agent_message_chunk') {\n const block = event.content as { type?: string; text?: string }\n if (block.type === 'text' && typeof block.text === 'string') {\n const current = buffers.get(event.sessionId) ?? ''\n // Within a message, tokens carry their own leading spaces, so we\n // concatenate raw. Two signals start a *new* message block that must not\n // run together with the previous text:\n // - an empty chunk (some agents emit one between blocks, e.g. after a\n // tool call), or\n // - a change in messageId — opencode streams each assistant message\n // under its own id and emits no delimiter chunk between them, and the\n // first token of the new message has no leading space, so without\n // this they weld together (\"…one.🅿️\").\n const prevMessageId = bufferMessageIds.get(event.sessionId)\n const messageChanged =\n event.messageId !== undefined &&\n prevMessageId !== undefined &&\n event.messageId !== prevMessageId\n const needsBreak =\n current.length > 0 && (block.text === '' || messageChanged)\n const prefix = needsBreak ? '\\n\\n' : ''\n buffers.set(event.sessionId, current + prefix + block.text)\n if (event.messageId !== undefined)\n bufferMessageIds.set(event.sessionId, event.messageId)\n } else {\n console.warn(`[matrix:${name}] dropped chunk block type=${block.type}`, block)\n }\n return\n }\n\n const eventType =\n event.type === 'tool_call'\n ? 'eco.zoon.tool_call'\n : event.type === 'tool_call_update'\n ? 'eco.zoon.tool_call_update'\n : 'eco.zoon.plan'\n const body =\n event.type === 'tool_call'\n ? toToolCallBody(event)\n : event.type === 'tool_call_update'\n ? toUpdateBody(event)\n : toPlanBody(event)\n body['m.relates_to'] = { rel_type: 'm.thread', event_id: ctx.threadRoot }\n const tail = (sendQueue.get(event.sessionId) ?? Promise.resolve()).then(async () => {\n try {\n await client.sendCustomEvent({\n roomId: ctx.roomId,\n asUserId: ctx.agent.userId,\n eventType,\n content: body,\n })\n } catch (err) {\n console.warn(`[matrix:${name}] sendCustomEvent(${eventType}) failed:`, err)\n }\n })\n sendQueue.set(event.sessionId, tail)\n await tail\n }\n\n agents.onApprovalRequest = async (name, req) => {\n const handle = approvals.register(name, (req as { sessionId: string }).sessionId, req, {\n timeoutMs: agents.getApprovalTimeoutMs(name),\n })\n return handle.decisionPromise\n }\n\n approvals.on('registered', (handle: RegisteredApproval) => {\n const ctx = sessions.get(handle.sessionId)\n if (!ctx) return\n const content: Record<string, unknown> = {\n approval_id: handle.approvalId,\n session_id: handle.sessionId,\n tool_call_id: handle.toolCallId,\n options: handle.options,\n }\n content['m.relates_to'] = { rel_type: 'm.thread', event_id: ctx.threadRoot }\n if (handle.toolKind !== undefined) content.tool_kind = handle.toolKind\n if (handle.toolTitle !== undefined) content.tool_title = handle.toolTitle\n if (handle.toolInput !== undefined) content.tool_input = handle.toolInput\n void client.sendCustomEvent({\n roomId: ctx.roomId,\n asUserId: ctx.agent.userId,\n eventType: 'eco.zoon.approval_request',\n content,\n })\n })\n\n const app = new Hono()\n\n function authOk(authHeader: string | undefined): boolean {\n const h = authHeader ?? ''\n if (!h.startsWith('Bearer ')) return false\n const got = h.slice(7)\n if (got.length !== hsToken.length) return false\n return timingSafeEqual(Buffer.from(got), Buffer.from(hsToken))\n }\n\n app.put('/_matrix/app/v1/transactions/:txnId', async (c) => {\n if (!authOk(c.req.header('authorization'))) {\n return c.json({ errcode: 'M_FORBIDDEN' }, 403)\n }\n const body = (await c.req.json().catch(() => ({}))) as { events?: MatrixEvent[] }\n for (const evt of body.events ?? []) {\n if (evt.event_id) {\n if (seenEventIds.has(evt.event_id)) {\n continue\n }\n seenEventIds.add(evt.event_id)\n if (seenEventIds.size > SEEN_EVENT_CAP) {\n const first = seenEventIds.values().next().value\n if (first !== undefined) seenEventIds.delete(first)\n }\n }\n if (\n evt.origin_server_ts !== undefined &&\n evt.origin_server_ts < cutoffTs &&\n evt.type === 'm.room.message'\n ) {\n console.log(\n `[matrix] dropping stale message event ${evt.event_id} ` +\n `(ts=${evt.origin_server_ts}, daemon started at ${cutoffTs + STARTUP_GRACE_MS})`,\n )\n continue\n }\n if (evt.type === 'eco.zoon.session_reset') {\n // Spec § /clear: room-scope reset is unsupported. Only thread-scoped\n // resets carry a thread relation; drop bare room-level resets silently.\n const relates = evt.content?.['m.relates_to'] as\n | { rel_type?: string; event_id?: string }\n | undefined\n const threadRoot =\n relates?.rel_type === 'm.thread' && relates.event_id ? relates.event_id : undefined\n if (!threadRoot) {\n console.log('[matrix] dropping eco.zoon.session_reset without thread relation')\n continue\n }\n console.log(`[matrix] inbound eco.zoon.session_reset in ${evt.room_id} thread=${threadRoot}`)\n for (const a of bindings) {\n agents.endSession(a.name, threadRoot)\n }\n // NB: keep threadStates intact. Per ZOD039 § /clear, only the agent's\n // session memory is wiped — thread-routing state (participants /\n // root-mentions) must survive so the next bare reply still routes to\n // the most-recently-posting agent under the same sessionKey.\n continue\n }\n if (evt.type === 'eco.zoon.interrupt') {\n const content = (evt.content ?? {}) as { session_id?: string; reason?: string }\n // Thread-relation form (client-friendly): /interrupt in a thread sends\n // an empty event with `m.relates_to: thread/<root>`. Cancel every\n // session whose threadRoot matches.\n const relates = evt.content?.['m.relates_to'] as\n | { rel_type?: string; event_id?: string }\n | undefined\n const threadRoot =\n relates?.rel_type === 'm.thread' && relates.event_id ? relates.event_id : undefined\n if (threadRoot) {\n const targets: Array<{ sessionId: string; agent: string }> = []\n for (const [sessionId, ctx] of sessions) {\n if (ctx.threadRoot === threadRoot) {\n targets.push({ sessionId, agent: ctx.agent.name })\n }\n }\n for (const t of targets) {\n console.log(\n `[matrix] interrupt session=${t.sessionId} agent=${t.agent} thread=${threadRoot}` +\n (content.reason ? ` reason=${content.reason}` : ''),\n )\n await agents.cancelSession(t.agent, t.sessionId).catch((err) => {\n console.error(`[matrix] cancelSession(${t.agent}, ${t.sessionId}) failed:`, err)\n })\n }\n continue\n }\n // Legacy form: explicit session_id in content.\n if (!content.session_id) {\n console.warn(`[matrix] eco.zoon.interrupt missing session_id (event_id=${evt.event_id})`)\n continue\n }\n const ctx = sessions.get(content.session_id)\n if (!ctx) {\n continue\n }\n console.log(\n `[matrix] interrupt session=${content.session_id} agent=${ctx.agent.name}` +\n (content.reason ? ` reason=${content.reason}` : ''),\n )\n await agents.cancelSession(ctx.agent.name, content.session_id).catch((err) => {\n console.error(`[matrix] cancelSession(${ctx.agent.name}, ${content.session_id}) failed:`, err)\n })\n continue\n }\n if (evt.type === 'eco.zoon.approval_response') {\n const content = (evt.content ?? {}) as {\n approval_id?: string\n session_id?: string\n decision?: string\n option_id?: string\n }\n if (!content.session_id || !content.approval_id || !content.decision) continue\n const decision = content.option_id\n ? { decision: content.decision, optionId: content.option_id }\n : { decision: content.decision }\n const ok = approvals.resolve(\n content.session_id,\n content.approval_id,\n decision as never,\n )\n if (!ok) console.warn(`[matrix] unknown approval ${content.approval_id}`)\n continue\n }\n logInbound(evt)\n // Agent-promotion: top-level inbound event becomes the thread root.\n // For in-thread messages the existing root is preserved.\n const promotedRoot = inboundThreadRoot(evt) ?? evt.event_id\n // Self-heal: if this is a thread reply but we have no in-memory state\n // for the root (e.g. daemon was just restarted), reconstruct it by\n // fetching the thread root + relations from the server.\n const inboundRel = inboundThreadRoot(evt)\n if (\n evt.type === 'm.room.message' &&\n inboundRel &&\n !threadStates.has(inboundRel) &&\n evt.room_id\n ) {\n try {\n const rebuilt = await rebuildThreadState(client, evt.room_id, inboundRel, bindings)\n threadStates.set(inboundRel, rebuilt)\n console.log(\n `[matrix] rebuilt threadState for ${inboundRel}: participants=${rebuilt.participants.join(',')} rootMentions=${rebuilt.rootMentions.join(',')}`,\n )\n } catch (err) {\n console.warn(`[matrix] failed to rebuild threadState for ${inboundRel}:`, err)\n }\n }\n const matches = route(evt, bindings, threadStates)\n // Suppress the no-match warning for events sent by our own bots.\n const senderIsBot = bindings.some((b) => b.userId === evt.sender)\n if (evt.type === 'm.room.message' && matches.length === 0 && !senderIsBot) {\n console.warn(\n `[matrix] no agent matched message in ${evt.room_id} from ${evt.sender}` +\n ` (bindings: ${bindings.map((b) => `${b.name}@${b.userId}[${b.trigger}]`).join(', ')})`,\n )\n }\n // Seed thread state for any agent mentions in this event.\n if (matches.length > 0 && promotedRoot) {\n let st = threadStates.get(promotedRoot)\n if (!st) {\n st = { participants: [], rootMentions: [] }\n threadStates.set(promotedRoot, st)\n }\n const msgMentions = new Set(extractMentions(evt as never))\n for (const a of bindings) {\n if (msgMentions.has(a.userId) && !st.rootMentions.includes(a.name)) {\n st.rootMentions.push(a.name)\n }\n }\n }\n for (const a of matches) {\n console.log(`[matrix] → ${a.name} (${a.userId})`)\n void runTurn(a, evt)\n .then(() => {\n if (!promotedRoot) return\n let st = threadStates.get(promotedRoot)\n if (!st) {\n st = { participants: [], rootMentions: [] }\n threadStates.set(promotedRoot, st)\n }\n if (st.participants.at(-1) !== a.name) st.participants.push(a.name)\n })\n .catch((err) => {\n console.error(`[matrix] runTurn failed for ${a.name}:`, err)\n const c = classify(err)\n const threadRoot = inboundThreadRoot(evt) ?? evt.event_id\n if (!threadRoot || !evt.room_id) return\n const body = toErrorBody(\n {\n kind: 'error',\n agentId: a.name,\n sessionId: null,\n turnId: null,\n code: c.code,\n message: err instanceof Error ? err.message : String(err),\n detail: err instanceof Error && err.stack ? err.stack.slice(0, 2000) : undefined,\n transient: c.transient,\n acp_error: c.acp_error,\n },\n threadRoot,\n )\n void client\n .sendCustomEvent({\n roomId: evt.room_id,\n asUserId: a.userId,\n eventType: 'eco.zoon.error',\n content: body,\n })\n .catch((e) => console.warn(`[matrix:${a.name}] eco.zoon.error send failed:`, e))\n })\n }\n }\n return c.json({})\n })\n\n app.get('/_matrix/app/v1/users/:userId', (c) => {\n if (!authOk(c.req.header('authorization'))) {\n return c.json({ errcode: 'M_FORBIDDEN' }, 403)\n }\n return c.json({})\n })\n app.get('/_matrix/app/v1/rooms/:alias', (c) => {\n if (!authOk(c.req.header('authorization'))) {\n return c.json({ errcode: 'M_FORBIDDEN' }, 403)\n }\n return c.json({ errcode: 'M_NOT_FOUND' }, 404)\n })\n app.post('/_matrix/app/v1/ping', (c) => {\n if (!authOk(c.req.header('authorization'))) {\n return c.json({ errcode: 'M_FORBIDDEN' }, 403)\n }\n return c.json({})\n })\n app.get('/healthz', (c) => c.text('ok'))\n\n async function runTurn(agent: AgentBinding, evt: MatrixEvent): Promise<void> {\n if (!evt.room_id || !evt.event_id) return\n const inbound = inboundThreadRoot(evt)\n // Agent-promotion: top-level inbound becomes a thread root via the agent's\n // first reply. sessionKey is always a thread root id, never the room id.\n const threadRoot = inbound ?? evt.event_id\n const sessionKey = threadRoot\n const sessionId = await agents.ensureSession(agent.name, sessionKey, evt.room_id)\n sessions.set(sessionId, { agent, roomId: evt.room_id, threadRoot })\n buffers.set(sessionId, '')\n bufferMessageIds.delete(sessionId)\n\n const roomId = evt.room_id\n const TYPING_TTL_MS = 30_000\n const TYPING_REFRESH_MS = 25_000\n const safeTyping = (typing: boolean) =>\n client\n .setTyping({ roomId, asUserId: agent.userId, typing, timeoutMs: TYPING_TTL_MS })\n .catch((err) => console.warn(`[matrix:${agent.name}] setTyping(${typing}) failed:`, err))\n const safePresence = (presence: 'online' | 'unavailable' | 'offline') =>\n client\n .setPresence({ asUserId: agent.userId, presence })\n .catch((err) =>\n console.warn(`[matrix:${agent.name}] setPresence(${presence}) failed:`, err),\n )\n\n await safeTyping(true)\n await safePresence('unavailable')\n const refresh = setInterval(() => {\n void safeTyping(true)\n }, TYPING_REFRESH_MS)\n\n try {\n const rawBody = evt.content?.body ?? ''\n const promptText = stripMention(rawBody, agent.userId)\n await agents.prompt(agent.name, {\n threadId: sessionKey,\n channelId: evt.room_id,\n content: [{ type: 'text', text: promptText }],\n })\n // Drain: the prompt promise resolves on the stopReason response, but\n // trailing chunks may still arrive (see DRAIN_* above). Wait until the\n // buffer is quiet for DRAIN_QUIET_MS, re-arming on each new chunk.\n //\n // Subtlety: some agents (opencode in particular) resolve `session/prompt`\n // *before* the agent_message_chunk stream starts. So the buffer can be\n // empty for several seconds after prompt resolves, and only then do the\n // chunks arrive. We can't break the drain just because the buffer is\n // empty — we have to wait up to drainMaxMs for chunks to *start*. Once\n // any content arrives, the \"quiet for drainQuietMs\" rule kicks in.\n const drainStart = Date.now()\n let drained = buffers.get(sessionId) ?? ''\n while (drainQuietMs > 0 && Date.now() - drainStart < drainMaxMs) {\n await delay(drainQuietMs)\n const next = buffers.get(sessionId) ?? ''\n // Stop only when we have content AND it hasn't grown — i.e. the\n // generation has actually started and is now done. An unchanged\n // empty buffer means the stream hasn't started yet; keep waiting.\n if (next === drained && next.length > 0) break\n drained = next\n }\n const text = buffers.get(sessionId) ?? ''\n if (text.length > 0) {\n const html = toMatrixHtml(text)\n const content: { msgtype: string; body: string; [k: string]: unknown } = {\n msgtype: 'm.text',\n body: text,\n }\n // Only attach formatted_body when it adds rich-text the plain body\n // can't carry. marked wraps plain prose in <p>…</p>; if that's all\n // we'd add, skip — most clients render `body` better than a stripped\n // re-encode.\n if (html) {\n const escapedPlain =\n '<p>' +\n text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>') +\n '</p>'\n const norm = (s: string) => s.replace(/\\s+/g, ' ').trim()\n if (norm(html) !== norm(escapedPlain)) {\n content.format = 'org.matrix.custom.html'\n content.formatted_body = html\n }\n }\n await client.sendMessage({\n roomId: evt.room_id,\n asUserId: agent.userId,\n content,\n threadRoot, // every reply threads, full stop\n })\n } else {\n console.warn(\n `[matrix:${agent.name}] turn finished with empty buffer (session=${sessionId}); nothing sent to ${evt.room_id}`,\n )\n }\n } finally {\n clearInterval(refresh)\n await safeTyping(false)\n await safePresence('online')\n buffers.delete(sessionId)\n bufferMessageIds.delete(sessionId)\n }\n }\n\n return {\n app,\n bootstrap: async (\n bootstrapOpts: {\n spaceRoomId?: string\n asUserId?: string\n adminUserIds?: string[]\n } = {},\n ) => {\n await pool.bootstrap({ adminUserId, ...bootstrapOpts })\n await Promise.allSettled(\n bindings.map((b) =>\n client.setPresence({ asUserId: b.userId, presence: 'online' }).catch((err) => {\n console.warn(`[matrix:${b.name}] initial setPresence(online) failed:`, err)\n }),\n ),\n )\n },\n pool,\n }\n}\n\n/**\n * Reconstruct the in-memory ThreadState for a thread root by fetching the\n * root event + its thread relations from the server. Used to recover the\n * implicit-routing rule from ZOD039 § Implicit triggers in threads after a\n * daemon restart wipes the in-memory cache.\n */\nexport async function rebuildThreadState(\n client: MatrixClient,\n roomId: string,\n rootEventId: string,\n bindings: AgentBinding[],\n): Promise<ThreadState> {\n const state: ThreadState = { participants: [], rootMentions: [] }\n // Impersonate an agent that's actually a member of this room (AS reads\n // require room membership). Falling through to the first binding would\n // 403 if that agent never joined the target room.\n const asUser = (bindings.find((b) => b.rooms.some((r) => r.alias === roomId)) ?? bindings[0])?.userId\n if (!asUser) return state\n\n const root = await client.fetchEvent(roomId, rootEventId, asUser)\n if (root) {\n const rootMentions = new Set(extractMentions(root as never))\n for (const a of bindings) {\n if (rootMentions.has(a.userId) && !state.rootMentions.includes(a.name)) {\n state.rootMentions.push(a.name)\n }\n }\n }\n\n const { chunk: thread } = await client.fetchThreadRelations({\n roomId,\n rootEventId,\n asUserId: asUser,\n })\n // Also seed root-mentions from any subsequent agent @mentions in the thread.\n for (const ev of thread) {\n const mentions = new Set(extractMentions(ev as never))\n for (const a of bindings) {\n if (mentions.has(a.userId) && !state.rootMentions.includes(a.name)) {\n state.rootMentions.push(a.name)\n }\n }\n const sender = (ev as { sender?: string }).sender\n const type = (ev as { type?: string }).type\n if (type === 'm.room.message' && sender) {\n const a = bindings.find((b) => b.userId === sender)\n if (a && state.participants.at(-1) !== a.name) state.participants.push(a.name)\n }\n }\n return state\n}\n\nfunction logInbound(evt: MatrixEvent): void {\n const sender = evt.sender ?? '?'\n const room = evt.room_id ?? '?'\n const type = evt.type ?? '?'\n if (type === 'm.room.message') {\n const body = evt.content?.body ?? ''\n const mentions = (evt.content?.['m.mentions'] as { user_ids?: string[] } | undefined)?.user_ids\n const mentionsStr = mentions?.length ? ` mentions=${JSON.stringify(mentions)}` : ''\n console.log(\n `[matrix] inbound msg in ${room} from ${sender}${mentionsStr}: ${truncate(body, 200)}`,\n )\n } else {\n console.log(`[matrix] inbound ${type} in ${room} from ${sender}`)\n }\n}\n\nfunction truncate(s: string, n: number): string {\n return s.length > n ? s.slice(0, n) + '…' : s\n}\n","import type {\n PlanEvent,\n TapEvent,\n ToolCallEvent,\n ToolCallUpdateEvent,\n} from '@zooid/acp-client'\n\n/** Cap any single string in rawInput so big diffs / file contents don't bloat Matrix. */\nconst RAW_INPUT_STR_MAX = 250\n\nexport function toToolCallBody(evt: ToolCallEvent): Record<string, unknown> {\n const out: Record<string, unknown> = {\n session_id: evt.sessionId,\n tool_call_id: evt.toolCallId,\n title: evt.title,\n }\n if (evt.kind !== undefined) out.kind = evt.kind\n if (evt.status !== undefined) out.status = evt.status\n if (evt.rawInput !== undefined) out.raw_input = truncateStrings(evt.rawInput, RAW_INPUT_STR_MAX)\n if (evt.locations !== undefined) out.locations = evt.locations\n return out\n}\n\n/**\n * Recursively truncates string values longer than `max` with a \"… [truncated]\"\n * suffix. Non-string scalars and structure are preserved.\n */\nfunction truncateStrings(v: unknown, max: number): unknown {\n if (typeof v === 'string') {\n return v.length > max ? v.slice(0, max) + '… [truncated]' : v\n }\n if (Array.isArray(v)) {\n return v.map((item) => truncateStrings(item, max))\n }\n if (v && typeof v === 'object') {\n const out: Record<string, unknown> = {}\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n out[k] = truncateStrings(val, max)\n }\n return out\n }\n return v\n}\n\nexport function toUpdateBody(evt: ToolCallUpdateEvent): Record<string, unknown> {\n const out: Record<string, unknown> = {\n session_id: evt.sessionId,\n tool_call_id: evt.toolCallId,\n }\n if (evt.status !== undefined) out.status = evt.status\n if (evt.kind !== undefined) out.kind = evt.kind\n // content[] carries display-ready output (text/diff/terminal); rawOutput is\n // intentionally NOT serialized — it's typically large and duplicates content.\n if (evt.content !== undefined) out.content = evt.content\n // Some ACP agents only set rawInput on a later update (not the initial\n // tool_call). Truncate strings and forward.\n if (evt.rawInput !== undefined) out.raw_input = truncateStrings(evt.rawInput, RAW_INPUT_STR_MAX)\n if (evt.locations !== undefined) out.locations = evt.locations\n return out\n}\n\nexport function toPlanBody(evt: PlanEvent): Record<string, unknown> {\n return {\n session_id: evt.sessionId,\n entries: evt.entries,\n }\n}\n\nconst RECOVERY_URLS: Partial<Record<string, string>> = {\n auth_missing: 'https://zooid.dev/docs/guides/run-in-container#authentication-that-carries-over',\n auth_invalid: 'https://zooid.dev/docs/guides/run-in-container#authentication-that-carries-over',\n mount_failed: 'https://zooid.dev/docs/guides/run-in-container#what-you-get-for-free',\n image_pull_failed: 'https://zooid.dev/docs/guides/run-in-container#skipping-the-image-prepull',\n}\n\ntype ErrorTap = Extract<TapEvent, { kind: 'error' }>\n\nexport function toErrorBody(evt: ErrorTap, threadRoot: string): Record<string, unknown> {\n const msg = evt.message.slice(0, 250)\n const out: Record<string, unknown> = {\n msgtype: 'm.notice',\n body: `⚠ [${evt.code}] ${msg}`,\n code: evt.code,\n message: msg,\n transient: evt.transient,\n 'm.relates_to': { rel_type: 'm.thread', event_id: threadRoot },\n }\n if (evt.sessionId) out.session_id = evt.sessionId\n if (evt.turnId) out.turn_id = evt.turnId\n if (evt.detail) out.detail = evt.detail.slice(0, 2000)\n if (evt.acp_error) out.acp_error = evt.acp_error\n const recovery = RECOVERY_URLS[evt.code]\n if (recovery) out.recovery = recovery\n return out\n}\n","import { marked } from 'marked'\nimport sanitizeHtml from 'sanitize-html'\n\n// Matrix-permitted HTML tags per the m.room.message spec.\n// https://spec.matrix.org/v1.11/client-server-api/#mroommessage-msgtypes\nconst ALLOWED_TAGS = [\n 'del', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a',\n 'ul', 'ol', 'sup', 'sub', 'li', 'b', 'i', 'u', 'strong', 'em', 's',\n 'code', 'hr', 'br', 'div', 'table', 'thead', 'tbody', 'tr', 'th',\n 'td', 'caption', 'pre', 'span', 'img', 'details', 'summary',\n]\n\nconst ALLOWED_ATTRIBUTES: Record<string, string[]> = {\n a: ['href', 'name', 'target'],\n img: ['width', 'height', 'alt', 'title', 'src'],\n ol: ['start'],\n code: ['class'],\n span: ['data-mx-color', 'data-mx-bg-color', 'data-mx-spoiler'],\n}\n\nconst ALLOWED_SCHEMES = ['https', 'http', 'ftp', 'mailto', 'magnet', 'matrix']\n\nmarked.setOptions({ gfm: true, breaks: false, async: false })\n\nexport function toMatrixHtml(markdown: string): string {\n if (!markdown || !markdown.trim()) return ''\n let rawHtml: string\n try {\n const out = marked.parse(markdown) as string | Promise<string>\n if (typeof out !== 'string') return ''\n rawHtml = out\n } catch {\n return ''\n }\n return sanitizeHtml(rawHtml, {\n allowedTags: ALLOWED_TAGS,\n allowedAttributes: ALLOWED_ATTRIBUTES,\n allowedSchemes: ALLOWED_SCHEMES,\n allowedSchemesByTag: { a: ALLOWED_SCHEMES },\n })\n}\n","import { MatrixClient } from './matrix-client.js'\nimport type { AgentBinding } from './router.js'\n\nexport interface WorkforceRoster {\n version: 1\n agents: { user_id: string; name: string; rooms: string[] }[]\n}\n\nexport function buildWorkforceRoster(agents: AgentBinding[]): WorkforceRoster {\n return {\n version: 1,\n agents: agents.map((a) => ({\n user_id: a.userId,\n name: a.name,\n rooms: a.rooms.map((r) => r.alias),\n })),\n }\n}\n\nexport interface PublishOpts {\n client: MatrixClient\n spaceRoomId: string\n asUserId: string\n agents: AgentBinding[]\n}\n\nexport async function publishWorkforce(opts: PublishOpts): Promise<void> {\n await opts.client.sendStateEvent({\n roomId: opts.spaceRoomId,\n asUserId: opts.asUserId,\n eventType: 'eco.zoon.workforce',\n stateKey: '',\n content: buildWorkforceRoster(opts.agents) as unknown as Record<string, unknown>,\n })\n}\n\nexport interface PublisherHandle {\n reload(): Promise<void>\n stop(): Promise<void>\n}\n\nexport interface StartOpts {\n client: MatrixClient\n spaceRoomId: string\n asUserId: string\n getAgents: () => AgentBinding[]\n}\n\nexport async function startWorkforcePublisher(opts: StartOpts): Promise<PublisherHandle> {\n await publishWorkforce({ ...opts, agents: opts.getAgents() })\n return {\n async reload() {\n await publishWorkforce({ ...opts, agents: opts.getAgents() })\n },\n async stop() {},\n }\n}\n"],"mappings":"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","names":["localpart","body","localpart","inboundThreadRoot","ctx","c","body"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zooid/transport-matrix",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Matrix Application Service transport for zooid. Routes inbound Matrix messages to ACP agents and posts replies plus approval custom events back to threads.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"marked": "^18.0.4",
|
|
29
29
|
"sanitize-html": "^2.17.4",
|
|
30
30
|
"yaml": "^2.5.0",
|
|
31
|
-
"@zooid/
|
|
32
|
-
"@zooid/
|
|
31
|
+
"@zooid/core": "^0.7.4",
|
|
32
|
+
"@zooid/acp-client": "^0.7.4"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@hono/node-server": "^1.13.0",
|
package/src/transport.test.ts
CHANGED
|
@@ -995,6 +995,100 @@ describe('tool-call and plan event bridging', () => {
|
|
|
995
995
|
})
|
|
996
996
|
})
|
|
997
997
|
|
|
998
|
+
describe('agent_message_chunk message-boundary buffering', () => {
|
|
999
|
+
async function startTurn(eventId: string) {
|
|
1000
|
+
const { transport, agents, client, finishPrompt } = makeTransport()
|
|
1001
|
+
await postTxn(transport.app, {
|
|
1002
|
+
events: [
|
|
1003
|
+
{
|
|
1004
|
+
type: 'm.room.message',
|
|
1005
|
+
event_id: eventId,
|
|
1006
|
+
origin_server_ts: Date.now(),
|
|
1007
|
+
room_id: '!r:example.com',
|
|
1008
|
+
sender: '@user:example.com',
|
|
1009
|
+
content: {
|
|
1010
|
+
msgtype: 'm.text',
|
|
1011
|
+
body: 'hi',
|
|
1012
|
+
'm.mentions': { user_ids: ['@architect:example.com'] },
|
|
1013
|
+
},
|
|
1014
|
+
},
|
|
1015
|
+
],
|
|
1016
|
+
})
|
|
1017
|
+
await settleTurn()
|
|
1018
|
+
return { agents, client, finishPrompt, sessionId: 'sess-' + eventId }
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const emit = (
|
|
1022
|
+
agents: { onEvent: unknown },
|
|
1023
|
+
sessionId: string,
|
|
1024
|
+
text: string,
|
|
1025
|
+
messageId?: string,
|
|
1026
|
+
) =>
|
|
1027
|
+
(agents.onEvent as (n: string, e: unknown) => unknown)('architect', {
|
|
1028
|
+
type: 'agent_message_chunk',
|
|
1029
|
+
sessionId,
|
|
1030
|
+
content: { type: 'text', text },
|
|
1031
|
+
messageId,
|
|
1032
|
+
})
|
|
1033
|
+
|
|
1034
|
+
const sentBody = (client: { sendMessage: { mock: { calls: unknown[][] } } }) =>
|
|
1035
|
+
(client.sendMessage.mock.calls[0]![0] as { content: { body: string } }).content.body
|
|
1036
|
+
|
|
1037
|
+
it('inserts a paragraph break when messageId changes between chunks (opencode run-on)', async () => {
|
|
1038
|
+
// The exact production failure: opencode streams "…it." under one message id,
|
|
1039
|
+
// then "Filed:" under a NEW id with no delimiter chunk. Without a break they
|
|
1040
|
+
// weld into "it.Filed:".
|
|
1041
|
+
const { agents, client, finishPrompt, sessionId } = await startTurn('$mid1')
|
|
1042
|
+
await emit(agents, sessionId, 'Let me file it.', 'msg_aaa')
|
|
1043
|
+
await emit(agents, sessionId, 'Filed: done', 'msg_bbb')
|
|
1044
|
+
finishPrompt()
|
|
1045
|
+
await settleTurn()
|
|
1046
|
+
expect(client.sendMessage).toHaveBeenCalledTimes(1)
|
|
1047
|
+
expect(sentBody(client)).toBe('Let me file it.\n\nFiled: done')
|
|
1048
|
+
})
|
|
1049
|
+
|
|
1050
|
+
it('does NOT break between chunks sharing a messageId (token streaming stays intact)', async () => {
|
|
1051
|
+
// Within one message, tokens carry their own leading spaces; we must
|
|
1052
|
+
// concatenate raw or we corrupt every streamed sentence.
|
|
1053
|
+
const { agents, client, finishPrompt, sessionId } = await startTurn('$mid2')
|
|
1054
|
+
await emit(agents, sessionId, 'Hello', 'msg_aaa')
|
|
1055
|
+
await emit(agents, sessionId, ' world', 'msg_aaa')
|
|
1056
|
+
await emit(agents, sessionId, '.', 'msg_aaa')
|
|
1057
|
+
finishPrompt()
|
|
1058
|
+
await settleTurn()
|
|
1059
|
+
expect(sentBody(client)).toBe('Hello world.')
|
|
1060
|
+
})
|
|
1061
|
+
|
|
1062
|
+
it('breaks only once across a three-message run', async () => {
|
|
1063
|
+
const { agents, client, finishPrompt, sessionId } = await startTurn('$mid3')
|
|
1064
|
+
await emit(agents, sessionId, 'one.', 'msg_a')
|
|
1065
|
+
await emit(agents, sessionId, 'two.', 'msg_b')
|
|
1066
|
+
await emit(agents, sessionId, 'three.', 'msg_c')
|
|
1067
|
+
finishPrompt()
|
|
1068
|
+
await settleTurn()
|
|
1069
|
+
expect(sentBody(client)).toBe('one.\n\ntwo.\n\nthree.')
|
|
1070
|
+
})
|
|
1071
|
+
|
|
1072
|
+
it('still breaks on an empty delimiter chunk (agents that signal that way)', async () => {
|
|
1073
|
+
const { agents, client, finishPrompt, sessionId } = await startTurn('$mid4')
|
|
1074
|
+
await emit(agents, sessionId, 'before', 'msg_a')
|
|
1075
|
+
await emit(agents, sessionId, '', 'msg_a') // empty delimiter, same id
|
|
1076
|
+
await emit(agents, sessionId, 'after', 'msg_a')
|
|
1077
|
+
finishPrompt()
|
|
1078
|
+
await settleTurn()
|
|
1079
|
+
expect(sentBody(client)).toBe('before\n\nafter')
|
|
1080
|
+
})
|
|
1081
|
+
|
|
1082
|
+
it('concatenates raw when chunks carry no messageId (e.g. Claude Code)', async () => {
|
|
1083
|
+
const { agents, client, finishPrompt, sessionId } = await startTurn('$mid5')
|
|
1084
|
+
await emit(agents, sessionId, 'Hello', undefined)
|
|
1085
|
+
await emit(agents, sessionId, ' there', undefined)
|
|
1086
|
+
finishPrompt()
|
|
1087
|
+
await settleTurn()
|
|
1088
|
+
expect(sentBody(client)).toBe('Hello there')
|
|
1089
|
+
})
|
|
1090
|
+
})
|
|
1091
|
+
|
|
998
1092
|
describe('eco.zoon.interrupt handling', () => {
|
|
999
1093
|
it('dispatches cancelSession(agent.name, sessionId) for an interrupt that targets a tracked session', async () => {
|
|
1000
1094
|
const { transport, agents, finishPrompt } = makeTransport()
|
package/src/transport.ts
CHANGED
|
@@ -80,6 +80,10 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
|
|
|
80
80
|
const pool = new BotPool(client, bindings)
|
|
81
81
|
const sessions = new Map<string, SessionContext>()
|
|
82
82
|
const buffers = new Map<string, string>()
|
|
83
|
+
// Last messageId seen per session's buffer. opencode streams each assistant
|
|
84
|
+
// message under its own id with no delimiter chunk between them, so a change
|
|
85
|
+
// here marks a message boundary we must break on.
|
|
86
|
+
const bufferMessageIds = new Map<string, string>()
|
|
83
87
|
// Per-session promise tail so out-of-band events (tool_call, plan, etc.)
|
|
84
88
|
// serialize on the wire even though the ACP producer doesn't await us.
|
|
85
89
|
const sendQueue = new Map<string, Promise<void>>()
|
|
@@ -103,10 +107,26 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
|
|
|
103
107
|
const block = event.content as { type?: string; text?: string }
|
|
104
108
|
if (block.type === 'text' && typeof block.text === 'string') {
|
|
105
109
|
const current = buffers.get(event.sessionId) ?? ''
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
|
|
110
|
+
// Within a message, tokens carry their own leading spaces, so we
|
|
111
|
+
// concatenate raw. Two signals start a *new* message block that must not
|
|
112
|
+
// run together with the previous text:
|
|
113
|
+
// - an empty chunk (some agents emit one between blocks, e.g. after a
|
|
114
|
+
// tool call), or
|
|
115
|
+
// - a change in messageId — opencode streams each assistant message
|
|
116
|
+
// under its own id and emits no delimiter chunk between them, and the
|
|
117
|
+
// first token of the new message has no leading space, so without
|
|
118
|
+
// this they weld together ("…one.🅿️").
|
|
119
|
+
const prevMessageId = bufferMessageIds.get(event.sessionId)
|
|
120
|
+
const messageChanged =
|
|
121
|
+
event.messageId !== undefined &&
|
|
122
|
+
prevMessageId !== undefined &&
|
|
123
|
+
event.messageId !== prevMessageId
|
|
124
|
+
const needsBreak =
|
|
125
|
+
current.length > 0 && (block.text === '' || messageChanged)
|
|
126
|
+
const prefix = needsBreak ? '\n\n' : ''
|
|
109
127
|
buffers.set(event.sessionId, current + prefix + block.text)
|
|
128
|
+
if (event.messageId !== undefined)
|
|
129
|
+
bufferMessageIds.set(event.sessionId, event.messageId)
|
|
110
130
|
} else {
|
|
111
131
|
console.warn(`[matrix:${name}] dropped chunk block type=${block.type}`, block)
|
|
112
132
|
}
|
|
@@ -416,6 +436,7 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
|
|
|
416
436
|
const sessionId = await agents.ensureSession(agent.name, sessionKey, evt.room_id)
|
|
417
437
|
sessions.set(sessionId, { agent, roomId: evt.room_id, threadRoot })
|
|
418
438
|
buffers.set(sessionId, '')
|
|
439
|
+
bufferMessageIds.delete(sessionId)
|
|
419
440
|
|
|
420
441
|
const roomId = evt.room_id
|
|
421
442
|
const TYPING_TTL_MS = 30_000
|
|
@@ -507,6 +528,7 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
|
|
|
507
528
|
await safeTyping(false)
|
|
508
529
|
await safePresence('online')
|
|
509
530
|
buffers.delete(sessionId)
|
|
531
|
+
bufferMessageIds.delete(sessionId)
|
|
510
532
|
}
|
|
511
533
|
}
|
|
512
534
|
|