@zooid/transport-matrix 0.11.0 → 0.11.2
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 +7 -0
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/router.test.ts +73 -1
- package/src/router.ts +7 -0
- package/src/session-keys.test.ts +39 -0
- package/src/session-keys.ts +29 -0
- package/src/transport.test.ts +339 -10
- package/src/transport.ts +54 -9
package/src/transport.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { AgentEvent, ContentBlock } from '@zooid/acp-client'
|
|
|
5
5
|
import { MatrixClient } from './matrix-client.js'
|
|
6
6
|
import { BotPool } from './bot-pool.js'
|
|
7
7
|
import { route, isMediaMsgtype, type AgentBinding, type ThreadState } from './router.js'
|
|
8
|
+
import { sessionKeyFor, composeHandoffKey } from './session-keys.js'
|
|
8
9
|
import { stripMention, extractMentions } from './mentions.js'
|
|
9
10
|
import { toToolCallBody, toUpdateBody, toPlanBody, toAvailableCommandsBody, toErrorBody } from './event-encoders.js'
|
|
10
11
|
import { classify } from '@zooid/acp-client'
|
|
@@ -536,8 +537,26 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
|
|
|
536
537
|
return
|
|
537
538
|
}
|
|
538
539
|
console.log(`[matrix] inbound dev.zooid.session_reset in ${evt.room_id} thread=${threadRoot}`)
|
|
540
|
+
// [[ZOD071]]: a thread's sessions are the thread-level one plus one per
|
|
541
|
+
// handoff arc — end them all. Reset events aren't m.room.message, so
|
|
542
|
+
// the self-heal rebuild above doesn't cover them; rebuild here if the
|
|
543
|
+
// daemon restarted since the arcs were minted.
|
|
544
|
+
if (!threadStates.has(threadRoot) && evt.room_id) {
|
|
545
|
+
try {
|
|
546
|
+
threadStates.set(
|
|
547
|
+
threadRoot,
|
|
548
|
+
await rebuildThreadState(client, evt.room_id, threadRoot, bindings),
|
|
549
|
+
)
|
|
550
|
+
} catch (err) {
|
|
551
|
+
console.warn(`[matrix] failed to rebuild threadState for reset ${threadRoot}:`, err)
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const st = threadStates.get(threadRoot)
|
|
539
555
|
for (const a of bindings) {
|
|
540
556
|
agents.endSession(a.name, threadRoot)
|
|
557
|
+
for (const arc of st?.handoffs[a.name] ?? []) {
|
|
558
|
+
agents.endSession(a.name, composeHandoffKey(threadRoot, arc))
|
|
559
|
+
}
|
|
541
560
|
}
|
|
542
561
|
// NB: keep threadStates intact. Per ZOD039 § /clear, only the agent's
|
|
543
562
|
// session memory is wiped — thread-routing state (participants /
|
|
@@ -670,7 +689,7 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
|
|
|
670
689
|
if (matches.length > 0 && promotedRoot) {
|
|
671
690
|
let st = threadStates.get(promotedRoot)
|
|
672
691
|
if (!st) {
|
|
673
|
-
st = { participants: [], rootMentions: [], callers: {} }
|
|
692
|
+
st = { participants: [], rootMentions: [], callers: {}, handoffs: {} }
|
|
674
693
|
threadStates.set(promotedRoot, st)
|
|
675
694
|
}
|
|
676
695
|
const msgMentions = new Set(extractMentions(evt as never))
|
|
@@ -679,7 +698,17 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
|
|
|
679
698
|
if (!msgMentions.has(a.userId)) continue
|
|
680
699
|
if (!st.rootMentions.includes(a.name)) st.rootMentions.push(a.name)
|
|
681
700
|
// Call edge: the (agent) sender is the caller of every agent it @mentions.
|
|
682
|
-
if (senderAgent && a.name !== senderAgent.name)
|
|
701
|
+
if (senderAgent && a.name !== senderAgent.name) {
|
|
702
|
+
st.callers[a.name] = senderAgent.name
|
|
703
|
+
// [[ZOD071]] handoff arc: every agent→agent call opens a fresh
|
|
704
|
+
// session for the callee, keyed by this call event. Recorded before
|
|
705
|
+
// runTurn dispatch so sessionKeyFor resolves the just-minted arc.
|
|
706
|
+
// Deduped: homeservers may redeliver a transaction.
|
|
707
|
+
if (evt.event_id) {
|
|
708
|
+
const arcs = (st.handoffs[a.name] ??= [])
|
|
709
|
+
if (!arcs.includes(evt.event_id)) arcs.push(evt.event_id)
|
|
710
|
+
}
|
|
711
|
+
}
|
|
683
712
|
}
|
|
684
713
|
}
|
|
685
714
|
for (const a of matches) {
|
|
@@ -689,7 +718,7 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
|
|
|
689
718
|
if (!promotedRoot) return
|
|
690
719
|
let st = threadStates.get(promotedRoot)
|
|
691
720
|
if (!st) {
|
|
692
|
-
st = { participants: [], rootMentions: [], callers: {} }
|
|
721
|
+
st = { participants: [], rootMentions: [], callers: {}, handoffs: {} }
|
|
693
722
|
threadStates.set(promotedRoot, st)
|
|
694
723
|
}
|
|
695
724
|
if (st.participants.at(-1) !== a.name) st.participants.push(a.name)
|
|
@@ -770,10 +799,14 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
|
|
|
770
799
|
if (!evt.room_id || !evt.event_id) return
|
|
771
800
|
const inbound = inboundThreadRoot(evt)
|
|
772
801
|
// Agent-promotion: top-level inbound becomes a thread root via the agent's
|
|
773
|
-
// first reply.
|
|
802
|
+
// first reply.
|
|
774
803
|
const threadRoot = inbound ?? evt.event_id
|
|
775
|
-
|
|
776
|
-
|
|
804
|
+
// [[ZOD071]]: the session key is the agent's current handoff arc when it
|
|
805
|
+
// has one, else the thread-level key. The raw threadRoot still travels
|
|
806
|
+
// separately: outbound events relate to it, and it is the context ref so
|
|
807
|
+
// zooid_get_history reads the real thread.
|
|
808
|
+
const sessionKey = sessionKeyFor(agent.name, threadRoot, threadStates.get(threadRoot))
|
|
809
|
+
const sessionId = await agents.ensureSession(agent.name, sessionKey, evt.room_id, threadRoot)
|
|
777
810
|
sessions.set(sessionId, { agent, roomId: evt.room_id, threadRoot })
|
|
778
811
|
buffers.set(sessionId, '')
|
|
779
812
|
bufferMessageIds.delete(sessionId)
|
|
@@ -836,6 +869,7 @@ export function createMatrixTransport(opts: CreateMatrixTransportOptions) {
|
|
|
836
869
|
await agents.prompt(agent.name, {
|
|
837
870
|
threadId: sessionKey,
|
|
838
871
|
channelId: evt.room_id,
|
|
872
|
+
contextThreadId: threadRoot,
|
|
839
873
|
content: [...blocks, { type: 'text', text: fullPromptText }],
|
|
840
874
|
})
|
|
841
875
|
// Drain: the prompt promise resolves on the stopReason response, but
|
|
@@ -934,7 +968,7 @@ export async function rebuildThreadState(
|
|
|
934
968
|
rootEventId: string,
|
|
935
969
|
bindings: AgentBinding[],
|
|
936
970
|
): Promise<ThreadState> {
|
|
937
|
-
const state: ThreadState = { participants: [], rootMentions: [], callers: {} }
|
|
971
|
+
const state: ThreadState = { participants: [], rootMentions: [], callers: {}, handoffs: {} }
|
|
938
972
|
// Impersonate an agent that's actually a member of this room (AS reads
|
|
939
973
|
// require room membership). Falling through to the first binding would
|
|
940
974
|
// 403 if that agent never joined the target room.
|
|
@@ -949,7 +983,11 @@ export async function rebuildThreadState(
|
|
|
949
983
|
for (const a of bindings) {
|
|
950
984
|
if (!rootMentions.has(a.userId)) continue
|
|
951
985
|
if (!state.rootMentions.includes(a.name)) state.rootMentions.push(a.name)
|
|
952
|
-
if (rootSenderAgent && a.name !== rootSenderAgent.name)
|
|
986
|
+
if (rootSenderAgent && a.name !== rootSenderAgent.name) {
|
|
987
|
+
state.callers[a.name] = rootSenderAgent.name
|
|
988
|
+
const arcs = (state.handoffs[a.name] ??= [])
|
|
989
|
+
if (!arcs.includes(rootEventId)) arcs.push(rootEventId)
|
|
990
|
+
}
|
|
953
991
|
}
|
|
954
992
|
}
|
|
955
993
|
|
|
@@ -963,10 +1001,17 @@ export async function rebuildThreadState(
|
|
|
963
1001
|
const mentions = new Set(extractMentions(ev as never))
|
|
964
1002
|
const evSender = (ev as { sender?: string }).sender
|
|
965
1003
|
const evSenderAgent = evSender ? bindings.find((b) => b.userId === evSender) : undefined
|
|
1004
|
+
const evId = (ev as { event_id?: string }).event_id
|
|
966
1005
|
for (const a of bindings) {
|
|
967
1006
|
if (!mentions.has(a.userId)) continue
|
|
968
1007
|
if (!state.rootMentions.includes(a.name)) state.rootMentions.push(a.name)
|
|
969
|
-
if (evSenderAgent && a.name !== evSenderAgent.name)
|
|
1008
|
+
if (evSenderAgent && a.name !== evSenderAgent.name) {
|
|
1009
|
+
state.callers[a.name] = evSenderAgent.name
|
|
1010
|
+
if (evId) {
|
|
1011
|
+
const arcs = (state.handoffs[a.name] ??= [])
|
|
1012
|
+
if (!arcs.includes(evId)) arcs.push(evId)
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
970
1015
|
}
|
|
971
1016
|
const type = (ev as { type?: string }).type
|
|
972
1017
|
if (type === 'm.room.message' && evSender) {
|