@zooid/transport-matrix 0.11.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +12 -1
- package/dist/index.js +50 -9
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/context-provider.test.ts +118 -0
- package/src/context-provider.ts +22 -3
- package/src/event-encoders.test.ts +69 -1
- package/src/event-encoders.ts +41 -1
- package/src/transport.test.ts +122 -2
- package/src/transport.ts +43 -8
package/dist/index.d.ts
CHANGED
|
@@ -177,7 +177,18 @@ declare class MatrixClient {
|
|
|
177
177
|
|
|
178
178
|
interface MatrixContextProviderOpts {
|
|
179
179
|
client: MatrixClient;
|
|
180
|
-
/**
|
|
180
|
+
/**
|
|
181
|
+
* The **agent's own** Matrix user (`@{workstation}.{name}:server`), which
|
|
182
|
+
* every read is impersonated as via `?user_id=`. Not the AS bot: that would
|
|
183
|
+
* read every room on the homeserver and make this class the only thing
|
|
184
|
+
* standing between an agent and someone else's conversation.
|
|
185
|
+
*
|
|
186
|
+
* This is load-bearing. It is what makes the homeserver — not our own
|
|
187
|
+
* bookkeeping — the authorization boundary for context reads, so a room or
|
|
188
|
+
* thread the agent is not in fails at Matrix with 403/404. Anything that
|
|
189
|
+
* widens who can name a room (a CLI, a new tool parameter) is safe only
|
|
190
|
+
* while this holds.
|
|
191
|
+
*/
|
|
181
192
|
asUserId: string;
|
|
182
193
|
/** Map of Matrix user IDs → agent names, for is_agent / agent_name flags. */
|
|
183
194
|
agentBots: Map<string, string>;
|
package/dist/index.js
CHANGED
|
@@ -331,7 +331,8 @@ var MatrixContextProvider = class {
|
|
|
331
331
|
const threads = [];
|
|
332
332
|
for (const ev of chunk) {
|
|
333
333
|
if (ev.type !== "m.room.message") continue;
|
|
334
|
-
if (ev.content?.msgtype !== "m.text" || typeof ev.content.body !== "string")
|
|
334
|
+
if (ev.content?.msgtype !== "m.text" && ev.content?.msgtype !== "m.notice" || typeof ev.content.body !== "string")
|
|
335
|
+
continue;
|
|
335
336
|
const relatesTo = ev.content["m.relates_to"];
|
|
336
337
|
if (relatesTo?.rel_type === "m.thread") continue;
|
|
337
338
|
const agent = this.opts.agentBots.get(ev.sender);
|
|
@@ -405,7 +406,7 @@ var MatrixContextProvider = class {
|
|
|
405
406
|
...threadId !== void 0 ? { thread_id: threadId } : {}
|
|
406
407
|
};
|
|
407
408
|
}
|
|
408
|
-
if (msgtype !== "m.text" || typeof body !== "string") return null;
|
|
409
|
+
if (msgtype !== "m.text" && msgtype !== "m.notice" || typeof body !== "string") return null;
|
|
409
410
|
return {
|
|
410
411
|
id: ev.event_id,
|
|
411
412
|
sender: ev.sender,
|
|
@@ -821,7 +822,11 @@ var RECOVERY_URLS = {
|
|
|
821
822
|
function toErrorBody(evt, threadRoot) {
|
|
822
823
|
const msg = evt.message.slice(0, 250);
|
|
823
824
|
const out = {
|
|
824
|
-
msgtype:
|
|
825
|
+
// No msgtype: dev.zooid.error is not m.room.message, so the field is
|
|
826
|
+
// meaningless here — it was a vestige of copying the message-body shape.
|
|
827
|
+
// Its presence used to force careful push-rule `before` positioning
|
|
828
|
+
// (ZNC025 §10); that positioning is kept regardless, since it also
|
|
829
|
+
// protects rules for event types that never carried the field.
|
|
825
830
|
body: `\u26A0 [${evt.code}] ${msg}`,
|
|
826
831
|
code: evt.code,
|
|
827
832
|
message: msg,
|
|
@@ -836,6 +841,25 @@ function toErrorBody(evt, threadRoot) {
|
|
|
836
841
|
if (recovery) out.recovery = recovery;
|
|
837
842
|
return out;
|
|
838
843
|
}
|
|
844
|
+
var PREVIEW_MAX = 140;
|
|
845
|
+
function toTurnEndBody(evt, threadRoot) {
|
|
846
|
+
const preview = evt.lastMessage?.trim().replace(/\s+/g, " ");
|
|
847
|
+
return {
|
|
848
|
+
// `body` stays the turn-boundary summary: it is what a generic Matrix
|
|
849
|
+
// client renders for this event, and the prose is already its own message
|
|
850
|
+
// in the timeline. The preview below exists only for the push, which
|
|
851
|
+
// cannot see that message — agent prose is `m.notice`, deliberately
|
|
852
|
+
// silenced by `.m.rule.suppress_notices` so a chatty turn doesn't fire one
|
|
853
|
+
// push per chunk ([[ZNC025]] §10). Without it the only notification the
|
|
854
|
+
// user gets says an agent finished and nothing about what it said.
|
|
855
|
+
body: evt.producedOutput ? `${evt.agentId} finished` : `${evt.agentId} finished without output`,
|
|
856
|
+
...preview ? { last_message: preview.slice(0, PREVIEW_MAX) } : {},
|
|
857
|
+
agent_id: evt.agentId,
|
|
858
|
+
session_id: evt.sessionId,
|
|
859
|
+
produced_output: evt.producedOutput,
|
|
860
|
+
"m.relates_to": { rel_type: "m.thread", event_id: threadRoot }
|
|
861
|
+
};
|
|
862
|
+
}
|
|
839
863
|
|
|
840
864
|
// src/transport.ts
|
|
841
865
|
import { classify } from "@zooid/acp-client";
|
|
@@ -1153,7 +1177,11 @@ function createMatrixTransport(opts) {
|
|
|
1153
1177
|
const pendingCommands = /* @__PURE__ */ new Map();
|
|
1154
1178
|
const buildTextContent = (text) => {
|
|
1155
1179
|
const content = {
|
|
1156
|
-
|
|
1180
|
+
// m.notice, not m.text: .m.rule.suppress_notices silences the
|
|
1181
|
+
// chunk-storm of agent prose server-side (ZNC025 §10) instead of every
|
|
1182
|
+
// client having to filter it. dev.zooid.error carries the same tweak
|
|
1183
|
+
// for the same reason.
|
|
1184
|
+
msgtype: "m.notice",
|
|
1157
1185
|
body: text
|
|
1158
1186
|
};
|
|
1159
1187
|
const html = toMatrixHtml(text);
|
|
@@ -1167,11 +1195,13 @@ function createMatrixTransport(opts) {
|
|
|
1167
1195
|
}
|
|
1168
1196
|
return content;
|
|
1169
1197
|
};
|
|
1198
|
+
const lastFlushed = /* @__PURE__ */ new Map();
|
|
1170
1199
|
const flushBuffer = (sessionId) => {
|
|
1171
1200
|
const ctx = sessions.get(sessionId);
|
|
1172
1201
|
const text = buffers.get(sessionId) ?? "";
|
|
1173
1202
|
if (!ctx || text.length === 0) return false;
|
|
1174
1203
|
buffers.set(sessionId, "");
|
|
1204
|
+
lastFlushed.set(sessionId, text);
|
|
1175
1205
|
flushedCounts.set(sessionId, (flushedCounts.get(sessionId) ?? 0) + 1);
|
|
1176
1206
|
const content = buildTextContent(text);
|
|
1177
1207
|
const tail = (sendQueue.get(sessionId) ?? Promise.resolve()).then(async () => {
|
|
@@ -1584,19 +1614,30 @@ function createMatrixTransport(opts) {
|
|
|
1584
1614
|
drained = next;
|
|
1585
1615
|
}
|
|
1586
1616
|
flushBuffer(sessionId);
|
|
1617
|
+
} finally {
|
|
1618
|
+
clearInterval(refresh);
|
|
1619
|
+
await safeTyping(false);
|
|
1620
|
+
await safePresence("online");
|
|
1587
1621
|
await (sendQueue.get(sessionId) ?? Promise.resolve());
|
|
1588
|
-
|
|
1622
|
+
const producedOutput = (flushedCounts.get(sessionId) ?? 0) > 0;
|
|
1623
|
+
if (!producedOutput) {
|
|
1589
1624
|
console.warn(
|
|
1590
1625
|
`[matrix:${agent.name}] turn finished with empty buffer (session=${sessionId}); nothing sent to ${evt.room_id}`
|
|
1591
1626
|
);
|
|
1592
1627
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1628
|
+
await client.sendCustomEvent({
|
|
1629
|
+
roomId: evt.room_id,
|
|
1630
|
+
asUserId: agent.userId,
|
|
1631
|
+
eventType: "dev.zooid.turn.end",
|
|
1632
|
+
content: toTurnEndBody(
|
|
1633
|
+
{ agentId: agent.name, sessionId, producedOutput, lastMessage: lastFlushed.get(sessionId) },
|
|
1634
|
+
threadRoot
|
|
1635
|
+
)
|
|
1636
|
+
}).catch((e) => console.warn(`[matrix:${agent.name}] turn.end send failed:`, e));
|
|
1597
1637
|
buffers.delete(sessionId);
|
|
1598
1638
|
bufferMessageIds.delete(sessionId);
|
|
1599
1639
|
flushedCounts.delete(sessionId);
|
|
1640
|
+
lastFlushed.delete(sessionId);
|
|
1600
1641
|
sendQueue.delete(sessionId);
|
|
1601
1642
|
}
|
|
1602
1643
|
}
|