@zooid/transport-matrix 0.10.0 → 0.11.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.d.ts CHANGED
@@ -271,6 +271,13 @@ interface ThreadState {
271
271
  * Implicit triggers → Directional continuation.
272
272
  */
273
273
  callers: Record<string, string>;
274
+ /**
275
+ * Handoff arcs per sub-agent: the event ids of the agent→agent @mention
276
+ * messages that called it, in timeline order (last = current arc). Each
277
+ * call opens a fresh ACP session keyed `threadRoot|callEventId`
278
+ * ([[ZOD071]]). Append-only; rebuilt from the timeline after a restart.
279
+ */
280
+ handoffs: Record<string, string[]>;
274
281
  }
275
282
  interface MaybeEvent {
276
283
  type?: string;
package/dist/index.js CHANGED
@@ -745,6 +745,16 @@ function buildUserPowerLevels(asUserId, admins, agents, roomAlias) {
745
745
  import { Hono } from "hono";
746
746
  import { timingSafeEqual } from "crypto";
747
747
 
748
+ // src/session-keys.ts
749
+ var HANDOFF_KEY_SEP = "|";
750
+ function composeHandoffKey(threadRoot, callEventId) {
751
+ return `${threadRoot}${HANDOFF_KEY_SEP}${callEventId}`;
752
+ }
753
+ function sessionKeyFor(agentName, threadRoot, state) {
754
+ const arc = state?.handoffs[agentName]?.at(-1);
755
+ return arc ? composeHandoffKey(threadRoot, arc) : threadRoot;
756
+ }
757
+
748
758
  // src/event-encoders.ts
749
759
  var RAW_INPUT_STR_MAX = 250;
750
760
  function toToolCallBody(evt) {
@@ -1312,8 +1322,22 @@ function createMatrixTransport(opts) {
1312
1322
  return;
1313
1323
  }
1314
1324
  console.log(`[matrix] inbound dev.zooid.session_reset in ${evt.room_id} thread=${threadRoot}`);
1325
+ if (!threadStates.has(threadRoot) && evt.room_id) {
1326
+ try {
1327
+ threadStates.set(
1328
+ threadRoot,
1329
+ await rebuildThreadState(client, evt.room_id, threadRoot, bindings)
1330
+ );
1331
+ } catch (err) {
1332
+ console.warn(`[matrix] failed to rebuild threadState for reset ${threadRoot}:`, err);
1333
+ }
1334
+ }
1335
+ const st = threadStates.get(threadRoot);
1315
1336
  for (const a of bindings) {
1316
1337
  agents.endSession(a.name, threadRoot);
1338
+ for (const arc of st?.handoffs[a.name] ?? []) {
1339
+ agents.endSession(a.name, composeHandoffKey(threadRoot, arc));
1340
+ }
1317
1341
  }
1318
1342
  return;
1319
1343
  }
@@ -1402,7 +1426,7 @@ function createMatrixTransport(opts) {
1402
1426
  if (matches.length > 0 && promotedRoot) {
1403
1427
  let st = threadStates.get(promotedRoot);
1404
1428
  if (!st) {
1405
- st = { participants: [], rootMentions: [], callers: {} };
1429
+ st = { participants: [], rootMentions: [], callers: {}, handoffs: {} };
1406
1430
  threadStates.set(promotedRoot, st);
1407
1431
  }
1408
1432
  const msgMentions = new Set(extractMentions(evt));
@@ -1410,7 +1434,13 @@ function createMatrixTransport(opts) {
1410
1434
  for (const a of bindings) {
1411
1435
  if (!msgMentions.has(a.userId)) continue;
1412
1436
  if (!st.rootMentions.includes(a.name)) st.rootMentions.push(a.name);
1413
- if (senderAgent && a.name !== senderAgent.name) st.callers[a.name] = senderAgent.name;
1437
+ if (senderAgent && a.name !== senderAgent.name) {
1438
+ st.callers[a.name] = senderAgent.name;
1439
+ if (evt.event_id) {
1440
+ const arcs = st.handoffs[a.name] ??= [];
1441
+ if (!arcs.includes(evt.event_id)) arcs.push(evt.event_id);
1442
+ }
1443
+ }
1414
1444
  }
1415
1445
  }
1416
1446
  for (const a of matches) {
@@ -1419,7 +1449,7 @@ function createMatrixTransport(opts) {
1419
1449
  if (!promotedRoot) return;
1420
1450
  let st = threadStates.get(promotedRoot);
1421
1451
  if (!st) {
1422
- st = { participants: [], rootMentions: [], callers: {} };
1452
+ st = { participants: [], rootMentions: [], callers: {}, handoffs: {} };
1423
1453
  threadStates.set(promotedRoot, st);
1424
1454
  }
1425
1455
  if (st.participants.at(-1) !== a.name) st.participants.push(a.name);
@@ -1492,8 +1522,8 @@ function createMatrixTransport(opts) {
1492
1522
  if (!evt.room_id || !evt.event_id) return;
1493
1523
  const inbound = inboundThreadRoot2(evt);
1494
1524
  const threadRoot = inbound ?? evt.event_id;
1495
- const sessionKey = threadRoot;
1496
- const sessionId = await agents.ensureSession(agent.name, sessionKey, evt.room_id);
1525
+ const sessionKey = sessionKeyFor(agent.name, threadRoot, threadStates.get(threadRoot));
1526
+ const sessionId = await agents.ensureSession(agent.name, sessionKey, evt.room_id, threadRoot);
1497
1527
  sessions.set(sessionId, { agent, roomId: evt.room_id, threadRoot });
1498
1528
  buffers.set(sessionId, "");
1499
1529
  bufferMessageIds.delete(sessionId);
@@ -1541,6 +1571,7 @@ function createMatrixTransport(opts) {
1541
1571
  await agents.prompt(agent.name, {
1542
1572
  threadId: sessionKey,
1543
1573
  channelId: evt.room_id,
1574
+ contextThreadId: threadRoot,
1544
1575
  content: [...blocks, { type: "text", text: fullPromptText }]
1545
1576
  });
1546
1577
  const drainStart = Date.now();
@@ -1595,7 +1626,7 @@ function createMatrixTransport(opts) {
1595
1626
  };
1596
1627
  }
1597
1628
  async function rebuildThreadState(client, roomId, rootEventId, bindings) {
1598
- const state = { participants: [], rootMentions: [], callers: {} };
1629
+ const state = { participants: [], rootMentions: [], callers: {}, handoffs: {} };
1599
1630
  const asUser = (bindings.find((b) => b.rooms.some((r) => r.alias === roomId)) ?? bindings[0])?.userId;
1600
1631
  if (!asUser) return state;
1601
1632
  const root = await client.fetchEvent(roomId, rootEventId, asUser);
@@ -1606,7 +1637,11 @@ async function rebuildThreadState(client, roomId, rootEventId, bindings) {
1606
1637
  for (const a of bindings) {
1607
1638
  if (!rootMentions.has(a.userId)) continue;
1608
1639
  if (!state.rootMentions.includes(a.name)) state.rootMentions.push(a.name);
1609
- if (rootSenderAgent && a.name !== rootSenderAgent.name) state.callers[a.name] = rootSenderAgent.name;
1640
+ if (rootSenderAgent && a.name !== rootSenderAgent.name) {
1641
+ state.callers[a.name] = rootSenderAgent.name;
1642
+ const arcs = state.handoffs[a.name] ??= [];
1643
+ if (!arcs.includes(rootEventId)) arcs.push(rootEventId);
1644
+ }
1610
1645
  }
1611
1646
  }
1612
1647
  const { chunk: thread } = await client.fetchThreadRelations({
@@ -1618,10 +1653,17 @@ async function rebuildThreadState(client, roomId, rootEventId, bindings) {
1618
1653
  const mentions = new Set(extractMentions(ev));
1619
1654
  const evSender = ev.sender;
1620
1655
  const evSenderAgent = evSender ? bindings.find((b) => b.userId === evSender) : void 0;
1656
+ const evId = ev.event_id;
1621
1657
  for (const a of bindings) {
1622
1658
  if (!mentions.has(a.userId)) continue;
1623
1659
  if (!state.rootMentions.includes(a.name)) state.rootMentions.push(a.name);
1624
- if (evSenderAgent && a.name !== evSenderAgent.name) state.callers[a.name] = evSenderAgent.name;
1660
+ if (evSenderAgent && a.name !== evSenderAgent.name) {
1661
+ state.callers[a.name] = evSenderAgent.name;
1662
+ if (evId) {
1663
+ const arcs = state.handoffs[a.name] ??= [];
1664
+ if (!arcs.includes(evId)) arcs.push(evId);
1665
+ }
1666
+ }
1625
1667
  }
1626
1668
  const type = ev.type;
1627
1669
  if (type === "m.room.message" && evSender) {