@zooid/transport-matrix 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -596,6 +596,33 @@ function toPlanBody(evt) {
596
596
  entries: evt.entries
597
597
  };
598
598
  }
599
+ var RECOVERY_URLS = {
600
+ auth_missing: "https://zooid.dev/docs/guides/run-in-container#authentication-that-carries-over",
601
+ auth_invalid: "https://zooid.dev/docs/guides/run-in-container#authentication-that-carries-over",
602
+ mount_failed: "https://zooid.dev/docs/guides/run-in-container#what-you-get-for-free",
603
+ image_pull_failed: "https://zooid.dev/docs/guides/run-in-container#skipping-the-image-prepull"
604
+ };
605
+ function toErrorBody(evt, threadRoot) {
606
+ const msg = evt.message.slice(0, 250);
607
+ const out = {
608
+ msgtype: "m.notice",
609
+ body: `\u26A0 [${evt.code}] ${msg}`,
610
+ code: evt.code,
611
+ message: msg,
612
+ transient: evt.transient,
613
+ "m.relates_to": { rel_type: "m.thread", event_id: threadRoot }
614
+ };
615
+ if (evt.sessionId) out.session_id = evt.sessionId;
616
+ if (evt.turnId) out.turn_id = evt.turnId;
617
+ if (evt.detail) out.detail = evt.detail.slice(0, 2e3);
618
+ if (evt.acp_error) out.acp_error = evt.acp_error;
619
+ const recovery = RECOVERY_URLS[evt.code];
620
+ if (recovery) out.recovery = recovery;
621
+ return out;
622
+ }
623
+
624
+ // src/transport.ts
625
+ import { classify } from "@zooid/acp-client";
599
626
 
600
627
  // src/markdown-to-matrix-html.ts
601
628
  import { marked } from "marked";
@@ -691,7 +718,9 @@ function createMatrixTransport(opts) {
691
718
  if (event.type === "agent_message_chunk") {
692
719
  const block = event.content;
693
720
  if (block.type === "text" && typeof block.text === "string") {
694
- buffers.set(event.sessionId, (buffers.get(event.sessionId) ?? "") + block.text);
721
+ const current = buffers.get(event.sessionId) ?? "";
722
+ const prefix = block.text === "" && current.length > 0 ? "\n\n" : "";
723
+ buffers.set(event.sessionId, current + prefix + block.text);
695
724
  } else {
696
725
  console.warn(`[matrix:${name}] dropped chunk block type=${block.type}`, block);
697
726
  }
@@ -879,6 +908,29 @@ function createMatrixTransport(opts) {
879
908
  if (st.participants.at(-1) !== a.name) st.participants.push(a.name);
880
909
  }).catch((err) => {
881
910
  console.error(`[matrix] runTurn failed for ${a.name}:`, err);
911
+ const c2 = classify(err);
912
+ const threadRoot = inboundThreadRoot2(evt) ?? evt.event_id;
913
+ if (!threadRoot || !evt.room_id) return;
914
+ const body2 = toErrorBody(
915
+ {
916
+ kind: "error",
917
+ agentId: a.name,
918
+ sessionId: null,
919
+ turnId: null,
920
+ code: c2.code,
921
+ message: err instanceof Error ? err.message : String(err),
922
+ detail: err instanceof Error && err.stack ? err.stack.slice(0, 2e3) : void 0,
923
+ transient: c2.transient,
924
+ acp_error: c2.acp_error
925
+ },
926
+ threadRoot
927
+ );
928
+ void client.sendCustomEvent({
929
+ roomId: evt.room_id,
930
+ asUserId: a.userId,
931
+ eventType: "eco.zoon.error",
932
+ content: body2
933
+ }).catch((e) => console.warn(`[matrix:${a.name}] eco.zoon.error send failed:`, e));
882
934
  });
883
935
  }
884
936
  }
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 }): 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 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 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 { extractMentions } from './mentions.js'\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 rooms: string[]\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.includes(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\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 return opts.client.createRoomRaw({\n asUserId: opts.asUserId,\n body: {\n room_alias_name: opts.spaceLocalpart,\n name: display,\n preset: opts.preset,\n creation_content: { type: 'm.space' },\n },\n })\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\nexport class BotPool {\n constructor(\n private readonly client: Pick<\n MatrixClient,\n 'registerBot' | 'joinRoom' | 'resolveAlias' | 'createRoom' | 'sendStateEvent' | '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 for (let i = 0; i < a.rooms.length; i++) {\n const room = a.rooms[i]\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 resolved = await this.client.createRoom({\n roomAliasName: aliasLocalpart,\n invite: opts.adminUserId ? [opts.adminUserId] : [],\n senderUserId: sender,\n name: aliasLocalpart,\n })\n }\n aliasToId.set(room, resolved)\n }\n }\n // Store the canonical room_id on the binding so the router (which\n // matches on event.room_id) sees a hit when Tuwunel pushes events.\n a.rooms[i] = 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","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 } from './event-encoders.js'\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}\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\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 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 buffers.set(event.sessionId, (buffers.get(event.sessionId) ?? '') + 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 })\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 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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;') +\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 (bootstrapOpts: { spaceRoomId?: string; asUserId?: string } = {}) => {\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.includes(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 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","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) => ({ user_id: a.userId, name: a.name, rooms: a.rooms })),\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","inboundThreadRoot","ctx"]}
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 }): 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 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 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 { extractMentions } from './mentions.js'\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 rooms: string[]\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.includes(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\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 return opts.client.createRoomRaw({\n asUserId: opts.asUserId,\n body: {\n room_alias_name: opts.spaceLocalpart,\n name: display,\n preset: opts.preset,\n creation_content: { type: 'm.space' },\n },\n })\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\nexport class BotPool {\n constructor(\n private readonly client: Pick<\n MatrixClient,\n 'registerBot' | 'joinRoom' | 'resolveAlias' | 'createRoom' | 'sendStateEvent' | '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 for (let i = 0; i < a.rooms.length; i++) {\n const room = a.rooms[i]\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 resolved = await this.client.createRoom({\n roomAliasName: aliasLocalpart,\n invite: opts.adminUserId ? [opts.adminUserId] : [],\n senderUserId: sender,\n name: aliasLocalpart,\n })\n }\n aliasToId.set(room, resolved)\n }\n }\n // Store the canonical room_id on the binding so the router (which\n // matches on event.room_id) sees a hit when Tuwunel pushes events.\n a.rooms[i] = 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","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}\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\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 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 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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;') +\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 (bootstrapOpts: { spaceRoomId?: string; asUserId?: string } = {}) => {\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.includes(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) => ({ user_id: a.userId, name: a.name, rooms: a.rooms })),\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","inboundThreadRoot","ctx","c","body"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zooid/transport-matrix",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
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/acp-client": "^0.7.0",
32
- "@zooid/core": "^0.7.0"
31
+ "@zooid/acp-client": "^0.7.1",
32
+ "@zooid/core": "^0.7.1"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@hono/node-server": "^1.13.0",
@@ -8,6 +8,7 @@ import {
8
8
  toToolCallBody,
9
9
  toUpdateBody,
10
10
  toPlanBody,
11
+ toErrorBody,
11
12
  } from './event-encoders.js'
12
13
 
13
14
  describe('toToolCallBody', () => {
@@ -122,3 +123,88 @@ describe('toPlanBody', () => {
122
123
  })
123
124
  })
124
125
  })
126
+
127
+ describe('toErrorBody', () => {
128
+ const threadRoot = '$root-event-id'
129
+
130
+ it('encodes a full error TapEvent including acp_error and recovery URL', () => {
131
+ const body = toErrorBody(
132
+ {
133
+ kind: 'error',
134
+ agentId: 'alice',
135
+ sessionId: 'sess-1',
136
+ turnId: 'turn-1',
137
+ code: 'auth_missing',
138
+ message: 'Authentication required',
139
+ detail: 'claude-agent-acp returned RequestError on session/prompt',
140
+ transient: false,
141
+ acp_error: { code: -32000, message: 'Authentication required' },
142
+ },
143
+ threadRoot,
144
+ )
145
+ expect(body).toMatchObject({
146
+ msgtype: 'm.notice',
147
+ body: '⚠ [auth_missing] Authentication required',
148
+ session_id: 'sess-1',
149
+ turn_id: 'turn-1',
150
+ code: 'auth_missing',
151
+ message: 'Authentication required',
152
+ detail: 'claude-agent-acp returned RequestError on session/prompt',
153
+ transient: false,
154
+ acp_error: { code: -32000, message: 'Authentication required' },
155
+ 'm.relates_to': { rel_type: 'm.thread', event_id: '$root-event-id' },
156
+ })
157
+ expect(body.recovery).toMatch(/^https:\/\/zooid\.dev\/docs\//)
158
+ })
159
+
160
+ it('truncates message to 250 chars and detail to 2000 chars', () => {
161
+ const body = toErrorBody(
162
+ {
163
+ kind: 'error',
164
+ agentId: 'a',
165
+ sessionId: 's',
166
+ turnId: 't',
167
+ code: 'internal',
168
+ message: 'x'.repeat(500),
169
+ detail: 'y'.repeat(5000),
170
+ transient: false,
171
+ },
172
+ threadRoot,
173
+ )
174
+ expect((body.message as string).length).toBe(250)
175
+ expect((body.detail as string).length).toBe(2000)
176
+ })
177
+
178
+ it('omits turn_id when null and omits acp_error when undefined', () => {
179
+ const body = toErrorBody(
180
+ {
181
+ kind: 'error',
182
+ agentId: 'a',
183
+ sessionId: 's',
184
+ turnId: null,
185
+ code: 'container_exit',
186
+ message: 'Container exited',
187
+ transient: true,
188
+ },
189
+ threadRoot,
190
+ )
191
+ expect(body.turn_id).toBeUndefined()
192
+ expect(body.acp_error).toBeUndefined()
193
+ })
194
+
195
+ it('omits session_id when null (failure preceded session/new)', () => {
196
+ const body = toErrorBody(
197
+ {
198
+ kind: 'error',
199
+ agentId: 'a',
200
+ sessionId: null,
201
+ turnId: null,
202
+ code: 'image_pull_failed',
203
+ message: 'pull failed',
204
+ transient: true,
205
+ },
206
+ threadRoot,
207
+ )
208
+ expect(body.session_id).toBeUndefined()
209
+ })
210
+ })
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  PlanEvent,
3
+ TapEvent,
3
4
  ToolCallEvent,
4
5
  ToolCallUpdateEvent,
5
6
  } from '@zooid/acp-client'
@@ -64,3 +65,31 @@ export function toPlanBody(evt: PlanEvent): Record<string, unknown> {
64
65
  entries: evt.entries,
65
66
  }
66
67
  }
68
+
69
+ const RECOVERY_URLS: Partial<Record<string, string>> = {
70
+ auth_missing: 'https://zooid.dev/docs/guides/run-in-container#authentication-that-carries-over',
71
+ auth_invalid: 'https://zooid.dev/docs/guides/run-in-container#authentication-that-carries-over',
72
+ mount_failed: 'https://zooid.dev/docs/guides/run-in-container#what-you-get-for-free',
73
+ image_pull_failed: 'https://zooid.dev/docs/guides/run-in-container#skipping-the-image-prepull',
74
+ }
75
+
76
+ type ErrorTap = Extract<TapEvent, { kind: 'error' }>
77
+
78
+ export function toErrorBody(evt: ErrorTap, threadRoot: string): Record<string, unknown> {
79
+ const msg = evt.message.slice(0, 250)
80
+ const out: Record<string, unknown> = {
81
+ msgtype: 'm.notice',
82
+ body: `⚠ [${evt.code}] ${msg}`,
83
+ code: evt.code,
84
+ message: msg,
85
+ transient: evt.transient,
86
+ 'm.relates_to': { rel_type: 'm.thread', event_id: threadRoot },
87
+ }
88
+ if (evt.sessionId) out.session_id = evt.sessionId
89
+ if (evt.turnId) out.turn_id = evt.turnId
90
+ if (evt.detail) out.detail = evt.detail.slice(0, 2000)
91
+ if (evt.acp_error) out.acp_error = evt.acp_error
92
+ const recovery = RECOVERY_URLS[evt.code]
93
+ if (recovery) out.recovery = recovery
94
+ return out
95
+ }
package/src/transport.ts CHANGED
@@ -6,7 +6,8 @@ import { MatrixClient } from './matrix-client.js'
6
6
  import { BotPool } from './bot-pool.js'
7
7
  import { route, type AgentBinding, type ThreadState } from './router.js'
8
8
  import { stripMention, extractMentions } from './mentions.js'
9
- import { toToolCallBody, toUpdateBody, toPlanBody } from './event-encoders.js'
9
+ import { toToolCallBody, toUpdateBody, toPlanBody, toErrorBody } from './event-encoders.js'
10
+ import { classify } from '@zooid/acp-client'
10
11
  import { toMatrixHtml } from './markdown-to-matrix-html.js'
11
12
 
12
13
  export interface CreateMatrixTransportOptions {
@@ -74,7 +75,11 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
74
75
  if (event.type === 'agent_message_chunk') {
75
76
  const block = event.content as { type?: string; text?: string }
76
77
  if (block.type === 'text' && typeof block.text === 'string') {
77
- buffers.set(event.sessionId, (buffers.get(event.sessionId) ?? '') + block.text)
78
+ const current = buffers.get(event.sessionId) ?? ''
79
+ // An empty chunk signals a new text block starting (e.g. after a tool call).
80
+ // Insert a paragraph break so consecutive blocks don't run together.
81
+ const prefix = block.text === '' && current.length > 0 ? '\n\n' : ''
82
+ buffers.set(event.sessionId, current + prefix + block.text)
78
83
  } else {
79
84
  console.warn(`[matrix:${name}] dropped chunk block type=${block.type}`, block)
80
85
  }
@@ -323,6 +328,31 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
323
328
  })
324
329
  .catch((err) => {
325
330
  console.error(`[matrix] runTurn failed for ${a.name}:`, err)
331
+ const c = classify(err)
332
+ const threadRoot = inboundThreadRoot(evt) ?? evt.event_id
333
+ if (!threadRoot || !evt.room_id) return
334
+ const body = toErrorBody(
335
+ {
336
+ kind: 'error',
337
+ agentId: a.name,
338
+ sessionId: null,
339
+ turnId: null,
340
+ code: c.code,
341
+ message: err instanceof Error ? err.message : String(err),
342
+ detail: err instanceof Error && err.stack ? err.stack.slice(0, 2000) : undefined,
343
+ transient: c.transient,
344
+ acp_error: c.acp_error,
345
+ },
346
+ threadRoot,
347
+ )
348
+ void client
349
+ .sendCustomEvent({
350
+ roomId: evt.room_id,
351
+ asUserId: a.userId,
352
+ eventType: 'eco.zoon.error',
353
+ content: body,
354
+ })
355
+ .catch((e) => console.warn(`[matrix:${a.name}] eco.zoon.error send failed:`, e))
326
356
  })
327
357
  }
328
358
  }