@animalabs/agent-framework 0.5.4 → 0.5.7
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/src/agent.d.ts +20 -4
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +45 -14
- package/dist/src/agent.js.map +1 -1
- package/dist/src/framework.d.ts +153 -5
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1046 -56
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +27 -0
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +140 -1
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/types.d.ts +17 -0
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +50 -0
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +180 -0
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts +161 -0
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -0
- package/dist/src/mcpl/conversation-router.js +179 -0
- package/dist/src/mcpl/conversation-router.js.map +1 -0
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
- package/dist/src/mcpl/feature-set-manager.js +10 -1
- package/dist/src/mcpl/feature-set-manager.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +2 -0
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +2 -0
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +4 -0
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/module-registry.d.ts +3 -1
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +9 -2
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts +10 -0
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +83 -0
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +17 -0
- package/dist/src/tool-result-history.d.ts.map +1 -0
- package/dist/src/tool-result-history.js +60 -0
- package/dist/src/tool-result-history.js.map +1 -0
- package/dist/src/types/agent.d.ts +11 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +8 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +15 -0
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +48 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/test/channel-registry-routing.test.d.ts +2 -0
- package/dist/test/channel-registry-routing.test.d.ts.map +1 -0
- package/dist/test/channel-registry-routing.test.js +75 -0
- package/dist/test/channel-registry-routing.test.js.map +1 -0
- package/dist/test/conversation-router.test.d.ts +2 -0
- package/dist/test/conversation-router.test.d.ts.map +1 -0
- package/dist/test/conversation-router.test.js +160 -0
- package/dist/test/conversation-router.test.js.map +1 -0
- package/dist/test/conversation-routing.test.d.ts +7 -0
- package/dist/test/conversation-routing.test.d.ts.map +1 -0
- package/dist/test/conversation-routing.test.js +203 -0
- package/dist/test/conversation-routing.test.js.map +1 -0
- package/dist/test/framework.test.js +120 -2
- package/dist/test/framework.test.js.map +1 -1
- package/dist/test/helpers/mock-membrane.d.ts +41 -0
- package/dist/test/helpers/mock-membrane.d.ts.map +1 -0
- package/dist/test/helpers/mock-membrane.js +161 -0
- package/dist/test/helpers/mock-membrane.js.map +1 -0
- package/dist/test/inference-failure-observability.test.d.ts +2 -0
- package/dist/test/inference-failure-observability.test.d.ts.map +1 -0
- package/dist/test/inference-failure-observability.test.js +75 -0
- package/dist/test/inference-failure-observability.test.js.map +1 -0
- package/dist/test/tool-result-history.test.d.ts +7 -0
- package/dist/test/tool-result-history.test.d.ts.map +1 -0
- package/dist/test/tool-result-history.test.js +77 -0
- package/dist/test/tool-result-history.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -2
- package/src/agent.ts +53 -18
- package/src/framework.ts +1202 -59
- package/src/gate/event-gate.ts +144 -1
- package/src/gate/types.ts +17 -0
- package/src/index.ts +12 -0
- package/src/mcpl/channel-registry.ts +221 -0
- package/src/mcpl/conversation-router.ts +284 -0
- package/src/mcpl/feature-set-manager.ts +14 -1
- package/src/mcpl/index.ts +12 -0
- package/src/mcpl/server-connection.ts +4 -0
- package/src/module-registry.ts +14 -2
- package/src/modules/discord/index.ts +93 -0
- package/src/tool-result-history.ts +57 -0
- package/src/types/agent.ts +11 -0
- package/src/types/framework.ts +9 -0
- package/src/types/module.ts +16 -0
- package/src/types/trace.ts +57 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { ChannelRegistry } from '../src/mcpl/channel-registry.js';
|
|
4
|
+
/**
|
|
5
|
+
* Build a registry with a mock server whose publish result is configurable,
|
|
6
|
+
* plus capture arrays for route-failure notifications and emitted traces.
|
|
7
|
+
*/
|
|
8
|
+
function makeRegistry(publishResult) {
|
|
9
|
+
const failures = [];
|
|
10
|
+
const traces = [];
|
|
11
|
+
const publishCalls = [];
|
|
12
|
+
const mockServer = {
|
|
13
|
+
sendChannelsPublish: async (params) => {
|
|
14
|
+
publishCalls.push(params);
|
|
15
|
+
return publishResult;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
const serverRegistry = {
|
|
19
|
+
getServer: (_id) => mockServer,
|
|
20
|
+
};
|
|
21
|
+
const registry = new ChannelRegistry(serverRegistry, {}, () => { }, (e) => { traces.push(e); }, {
|
|
22
|
+
onRouteFailure: (info) => { failures.push(info); },
|
|
23
|
+
});
|
|
24
|
+
// findChannelEntry is private; reach it the same way the typing test reaches
|
|
25
|
+
// the channels map — a test-only cast, not part of the public surface.
|
|
26
|
+
const lookup = (channelId) => registry.findChannelEntry(channelId);
|
|
27
|
+
return { registry, failures, traces, publishCalls, lookup };
|
|
28
|
+
}
|
|
29
|
+
function incoming(channelId, text, channelName) {
|
|
30
|
+
return {
|
|
31
|
+
messages: [{
|
|
32
|
+
channelId,
|
|
33
|
+
messageId: 'm1',
|
|
34
|
+
author: { id: 'u1', name: 'Antra' },
|
|
35
|
+
timestamp: '2026-05-30T00:00:00.000Z',
|
|
36
|
+
content: [{ type: 'text', text }],
|
|
37
|
+
metadata: channelName ? { channelName } : undefined,
|
|
38
|
+
}],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
test('handleIncoming lazy-registers an unknown channel so it becomes a publishable locus', async () => {
|
|
42
|
+
const { registry, traces, lookup } = makeRegistry({ delivered: true });
|
|
43
|
+
// Channel "post-boot-ch" was never registered via channels/register|changed.
|
|
44
|
+
assert.equal(lookup('post-boot-ch'), undefined);
|
|
45
|
+
registry.handleIncoming('discord', incoming('post-boot-ch', 'hi', '#cairn'));
|
|
46
|
+
const entry = lookup('post-boot-ch');
|
|
47
|
+
assert.ok(entry, 'channel should be lazy-registered from the inbound message');
|
|
48
|
+
assert.equal(entry.serverId, 'discord');
|
|
49
|
+
assert.equal(entry.descriptor.label, '#cairn');
|
|
50
|
+
assert.equal(entry.descriptor.metadata?.lazyRegistered, true);
|
|
51
|
+
assert.ok(traces.some(t => t.type === 'mcpl:channel-lazy-registered'));
|
|
52
|
+
// routeSpeech now resolves the locus and publishes (no failure).
|
|
53
|
+
const res = await registry.routeSpeech('cairn', 'my reply');
|
|
54
|
+
assert.deepEqual(res, { delivered: true, channelId: 'post-boot-ch' });
|
|
55
|
+
});
|
|
56
|
+
test('routeSpeech surfaces a failure when the server reports delivered:false', async () => {
|
|
57
|
+
const { registry, failures, traces } = makeRegistry({ delivered: false });
|
|
58
|
+
registry.handleIncoming('discord', incoming('ch-x', 'hi'));
|
|
59
|
+
const res = await registry.routeSpeech('cairn', 'undeliverable reply');
|
|
60
|
+
assert.equal(res, null, 'a non-delivered send must not report success');
|
|
61
|
+
assert.equal(failures.length, 1, 'onRouteFailure should fire');
|
|
62
|
+
assert.equal(failures[0].channelId, 'ch-x');
|
|
63
|
+
assert.match(failures[0].reason, /delivered:false/);
|
|
64
|
+
assert.ok(traces.some(t => t.type === 'mcpl:speech-route-failed'));
|
|
65
|
+
});
|
|
66
|
+
test('routeSpeech surfaces a failure when there is no locus at all', async () => {
|
|
67
|
+
const { registry, failures } = makeRegistry({ delivered: true });
|
|
68
|
+
// No handleIncoming → defaultPublishChannel is null.
|
|
69
|
+
const res = await registry.routeSpeech('cairn', 'into the void');
|
|
70
|
+
assert.equal(res, null);
|
|
71
|
+
assert.equal(failures.length, 1);
|
|
72
|
+
assert.equal(failures[0].channelId, null);
|
|
73
|
+
assert.match(failures[0].reason, /no locus/);
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=channel-registry-routing.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-registry-routing.test.js","sourceRoot":"","sources":["../../test/channel-registry-routing.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-router.test.d.ts","sourceRoot":"","sources":["../../test/conversation-router.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { ConversationRouter } from '../src/mcpl/conversation-router.js';
|
|
4
|
+
const T0 = 1_750_000_000_000;
|
|
5
|
+
function makeRouter(overrides = {}) {
|
|
6
|
+
return new ConversationRouter({ templateAgent: 'trunk', ...overrides });
|
|
7
|
+
}
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Bind policy
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
test('DM message binds without a mention (default dm bind: always)', () => {
|
|
12
|
+
const router = makeRouter();
|
|
13
|
+
const decision = router.route({ channelId: 'slack:D1', mentioned: false, kind: 'dm', now: T0 });
|
|
14
|
+
assert.equal(decision.kind, 'spawn');
|
|
15
|
+
if (decision.kind === 'spawn') {
|
|
16
|
+
assert.equal(decision.generation, 1);
|
|
17
|
+
assert.match(decision.agentName, /^conversation-slack-D1-g1$/);
|
|
18
|
+
assert.equal(decision.trigger, true);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
test('channel message without mention stays unbound (default channel bind: mention)', () => {
|
|
22
|
+
const router = makeRouter();
|
|
23
|
+
const decision = router.route({ channelId: 'slack:C1', mentioned: false, kind: 'channel', now: T0 });
|
|
24
|
+
assert.equal(decision.kind, 'unbound');
|
|
25
|
+
});
|
|
26
|
+
test('channel mention spawns a fork', () => {
|
|
27
|
+
const router = makeRouter();
|
|
28
|
+
const decision = router.route({ channelId: 'slack:C1', mentioned: true, kind: 'channel', now: T0 });
|
|
29
|
+
assert.equal(decision.kind, 'spawn');
|
|
30
|
+
});
|
|
31
|
+
test('bind rule never keeps DMs unbound', () => {
|
|
32
|
+
const router = makeRouter({ bind: { dm: 'never' } });
|
|
33
|
+
const decision = router.route({ channelId: 'slack:D1', mentioned: true, kind: 'dm', now: T0 });
|
|
34
|
+
assert.equal(decision.kind, 'unbound');
|
|
35
|
+
});
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Spawn is two-phase: route() proposes, bind() commits
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
test('spawn decision does not create the binding until bind() is called', () => {
|
|
40
|
+
const router = makeRouter();
|
|
41
|
+
const d1 = router.route({ channelId: 'slack:C1', mentioned: true, kind: 'channel', now: T0 });
|
|
42
|
+
assert.equal(d1.kind, 'spawn');
|
|
43
|
+
assert.equal(router.getBinding('slack:C1'), undefined);
|
|
44
|
+
// Framework failed to spawn → next mention proposes the same generation.
|
|
45
|
+
const d2 = router.route({ channelId: 'slack:C1', mentioned: true, kind: 'channel', now: T0 + 1 });
|
|
46
|
+
assert.equal(d2.kind, 'spawn');
|
|
47
|
+
if (d2.kind === 'spawn')
|
|
48
|
+
assert.equal(d2.generation, 1);
|
|
49
|
+
if (d2.kind === 'spawn')
|
|
50
|
+
router.bind('slack:C1', d2.agentName, d2.generation, T0 + 1);
|
|
51
|
+
const d3 = router.route({ channelId: 'slack:C1', mentioned: false, kind: 'channel', now: T0 + 2 });
|
|
52
|
+
assert.equal(d3.kind, 'existing');
|
|
53
|
+
});
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Trigger policy on bound channels
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
test('bound channel: non-mentions land without triggering, mentions trigger', () => {
|
|
58
|
+
const router = makeRouter();
|
|
59
|
+
router.bind('slack:C1', 'conversation-slack-C1-g1', 1, T0);
|
|
60
|
+
const ambient = router.route({ channelId: 'slack:C1', mentioned: false, kind: 'channel', now: T0 + 1 });
|
|
61
|
+
assert.equal(ambient.kind, 'existing');
|
|
62
|
+
if (ambient.kind === 'existing')
|
|
63
|
+
assert.equal(ambient.trigger, false);
|
|
64
|
+
const mention = router.route({ channelId: 'slack:C1', mentioned: true, kind: 'channel', now: T0 + 2 });
|
|
65
|
+
assert.equal(mention.kind, 'existing');
|
|
66
|
+
if (mention.kind === 'existing')
|
|
67
|
+
assert.equal(mention.trigger, true);
|
|
68
|
+
});
|
|
69
|
+
test('bound DM: every message triggers', () => {
|
|
70
|
+
const router = makeRouter();
|
|
71
|
+
router.bind('slack:D1', 'conversation-slack-D1-g1', 1, T0);
|
|
72
|
+
const decision = router.route({ channelId: 'slack:D1', mentioned: false, kind: 'dm', now: T0 + 1 });
|
|
73
|
+
assert.equal(decision.kind, 'existing');
|
|
74
|
+
if (decision.kind === 'existing')
|
|
75
|
+
assert.equal(decision.trigger, true);
|
|
76
|
+
});
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// TTL + generations
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
test('expired() respects lastActivity refresh from route()', () => {
|
|
81
|
+
const router = makeRouter({ idleTtlMs: 1000 });
|
|
82
|
+
router.bind('slack:C1', 'conversation-slack-C1-g1', 1, T0);
|
|
83
|
+
// Ambient traffic keeps the binding alive even without triggering.
|
|
84
|
+
router.route({ channelId: 'slack:C1', mentioned: false, kind: 'channel', now: T0 + 900 });
|
|
85
|
+
assert.deepEqual(router.expired(T0 + 1500), []);
|
|
86
|
+
assert.equal(router.expired(T0 + 1901).length, 1);
|
|
87
|
+
});
|
|
88
|
+
test('rebind after expiry gets a fresh generation and agent name', () => {
|
|
89
|
+
const router = makeRouter({ idleTtlMs: 1000 });
|
|
90
|
+
const d1 = router.route({ channelId: 'slack:C1', mentioned: true, kind: 'channel', now: T0 });
|
|
91
|
+
if (d1.kind !== 'spawn')
|
|
92
|
+
assert.fail('expected spawn');
|
|
93
|
+
router.bind('slack:C1', d1.agentName, d1.generation, T0);
|
|
94
|
+
router.unbind('slack:C1'); // framework does this after the closure turn
|
|
95
|
+
const d2 = router.route({ channelId: 'slack:C1', mentioned: true, kind: 'channel', now: T0 + 5000 });
|
|
96
|
+
assert.equal(d2.kind, 'spawn');
|
|
97
|
+
if (d2.kind === 'spawn') {
|
|
98
|
+
assert.equal(d2.generation, 2);
|
|
99
|
+
assert.match(d2.agentName, /-g2$/);
|
|
100
|
+
assert.notEqual(d2.agentName, d1.agentName);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
test('channelForAgent reverse lookup', () => {
|
|
104
|
+
const router = makeRouter();
|
|
105
|
+
router.bind('slack:C1', 'fork-a', 1, T0);
|
|
106
|
+
assert.equal(router.channelForAgent('fork-a'), 'slack:C1');
|
|
107
|
+
assert.equal(router.channelForAgent('fork-b'), undefined);
|
|
108
|
+
});
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Channel classification
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
test('classifyChannel reads descriptor metadata and message channel_type', () => {
|
|
113
|
+
const im = {
|
|
114
|
+
id: 'slack:D1', type: 'slack', label: 'DM: @alice', direction: 'bidirectional',
|
|
115
|
+
address: {}, metadata: { is_im: true },
|
|
116
|
+
};
|
|
117
|
+
const mpim = {
|
|
118
|
+
id: 'slack:G1', type: 'slack', label: 'Group DM', direction: 'bidirectional',
|
|
119
|
+
address: {}, metadata: { is_mpim: true },
|
|
120
|
+
};
|
|
121
|
+
const channel = {
|
|
122
|
+
id: 'slack:C1', type: 'slack', label: '#general', direction: 'bidirectional',
|
|
123
|
+
address: {}, metadata: { is_member: true },
|
|
124
|
+
};
|
|
125
|
+
assert.equal(ConversationRouter.classifyChannel(im), 'dm');
|
|
126
|
+
assert.equal(ConversationRouter.classifyChannel(mpim), 'groupDm');
|
|
127
|
+
assert.equal(ConversationRouter.classifyChannel(channel), 'channel');
|
|
128
|
+
assert.equal(ConversationRouter.classifyChannel(undefined, { channel_type: 'im' }), 'dm');
|
|
129
|
+
assert.equal(ConversationRouter.classifyChannel(undefined, { channel_type: 'mpim' }), 'groupDm');
|
|
130
|
+
assert.equal(ConversationRouter.classifyChannel(undefined, { channel_type: 'channel' }), 'channel');
|
|
131
|
+
assert.equal(ConversationRouter.classifyChannel(undefined, undefined), 'channel');
|
|
132
|
+
});
|
|
133
|
+
test('group DMs bind on any message but trigger only on mention by default', () => {
|
|
134
|
+
const router = new ConversationRouter({ templateAgent: 'main' });
|
|
135
|
+
const ambient = router.route({ channelId: 'slack:G1', mentioned: false, kind: 'groupDm', now: T0 });
|
|
136
|
+
assert.equal(ambient.kind, 'spawn', 'group DM binds without a mention');
|
|
137
|
+
assert.equal(ambient.trigger, false, 'no firehose replies in group DMs');
|
|
138
|
+
const mention = router.route({ channelId: 'slack:G2', mentioned: true, kind: 'groupDm', now: T0 });
|
|
139
|
+
assert.equal(mention.kind, 'spawn');
|
|
140
|
+
assert.equal(mention.trigger, true, 'mention triggers');
|
|
141
|
+
});
|
|
142
|
+
test('generation counters export/hydrate round-trips and never regresses', () => {
|
|
143
|
+
const router = new ConversationRouter({ templateAgent: 'main' });
|
|
144
|
+
router.bind('slack:C1', 'conversation-slack-C1-g3', 3, T0);
|
|
145
|
+
router.bind('slack:D1', 'conversation-slack-D1-g1', 1, T0);
|
|
146
|
+
const exported = router.exportGenerations();
|
|
147
|
+
assert.deepEqual(exported, { 'slack:C1': 3, 'slack:D1': 1 });
|
|
148
|
+
const restored = new ConversationRouter({ templateAgent: 'main' });
|
|
149
|
+
restored.hydrateGenerations(exported);
|
|
150
|
+
const decision = restored.route({ channelId: 'slack:C1', mentioned: true, kind: 'channel', now: T0 });
|
|
151
|
+
assert.equal(decision.kind, 'spawn');
|
|
152
|
+
assert.equal(decision.generation, 4, 'restart must not reuse generation names');
|
|
153
|
+
// Hydration never regresses a counter that advanced in the meantime.
|
|
154
|
+
restored.bind('slack:D1', 'conversation-slack-D1-g5', 5, T0);
|
|
155
|
+
restored.hydrateGenerations(exported);
|
|
156
|
+
const d1 = restored.route({ channelId: 'slack:D2', mentioned: false, kind: 'dm', now: T0 });
|
|
157
|
+
assert.equal(d1.kind, 'spawn', 'sanity');
|
|
158
|
+
assert.equal(restored.exportGenerations()['slack:D1'], 5);
|
|
159
|
+
});
|
|
160
|
+
//# sourceMappingURL=conversation-router.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-router.test.js","sourceRoot":"","sources":["../../test/conversation-router.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for per-channel conversation routing: incoming MCPL
|
|
3
|
+
* channel messages spawn/route to fork agents instead of the primary
|
|
4
|
+
* conversation when FrameworkConfig.conversations is set.
|
|
5
|
+
*/
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=conversation-routing.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-routing.test.d.ts","sourceRoot":"","sources":["../../test/conversation-routing.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for per-channel conversation routing: incoming MCPL
|
|
3
|
+
* channel messages spawn/route to fork agents instead of the primary
|
|
4
|
+
* conversation when FrameworkConfig.conversations is set.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { AgentFramework } from '../src/index.js';
|
|
12
|
+
import { MockMembrane, createMockResponse } from './helpers/mock-membrane.js';
|
|
13
|
+
function incomingEvent(overrides) {
|
|
14
|
+
return {
|
|
15
|
+
type: 'mcpl:channel-incoming',
|
|
16
|
+
serverId: 'srv',
|
|
17
|
+
channelId: overrides.channelId,
|
|
18
|
+
messageId: overrides.messageId ?? `m-${Math.random().toString(36).slice(2)}`,
|
|
19
|
+
author: { id: 'U1', name: 'alice' },
|
|
20
|
+
content: [{ type: 'text', text: overrides.text }],
|
|
21
|
+
timestamp: new Date().toISOString(),
|
|
22
|
+
metadata: {
|
|
23
|
+
mentioned: overrides.mentioned ?? false,
|
|
24
|
+
...(overrides.channelType ? { channel_type: overrides.channelType } : {}),
|
|
25
|
+
},
|
|
26
|
+
triggerInference: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
describe('Conversation routing', () => {
|
|
30
|
+
let tempDir;
|
|
31
|
+
let membrane;
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
tempDir = mkdtempSync(join(tmpdir(), 'conv-routing-test-'));
|
|
34
|
+
membrane = new MockMembrane();
|
|
35
|
+
});
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
38
|
+
});
|
|
39
|
+
async function makeFramework(conversations = {}, storeFile = 'test.chronicle') {
|
|
40
|
+
return AgentFramework.create({
|
|
41
|
+
storePath: join(tempDir, storeFile),
|
|
42
|
+
membrane: membrane.asMembrane(),
|
|
43
|
+
agents: [
|
|
44
|
+
{ name: 'trunk', model: 'test-model', systemPrompt: 'You are the trunk.' },
|
|
45
|
+
],
|
|
46
|
+
modules: [],
|
|
47
|
+
conversations: { templateAgent: 'trunk', ...conversations },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** Make a binding look idle past the TTL and re-arm the sweep throttle —
|
|
51
|
+
* deterministic expiry instead of racing a 1ms TTL against the wall clock. */
|
|
52
|
+
function forceExpiry(framework, channelId, idleTtlMs) {
|
|
53
|
+
const binding = framework.getConversationRouter().getBinding(channelId);
|
|
54
|
+
assert.ok(binding, `binding for ${channelId} should exist before forcing expiry`);
|
|
55
|
+
binding.lastActivity = Date.now() - idleTtlMs - 1_000;
|
|
56
|
+
framework.lastConversationSweep = 0;
|
|
57
|
+
}
|
|
58
|
+
it('rejects an unknown template agent at creation', async () => {
|
|
59
|
+
await assert.rejects(() => AgentFramework.create({
|
|
60
|
+
storePath: join(tempDir, 'test.chronicle'),
|
|
61
|
+
membrane: membrane.asMembrane(),
|
|
62
|
+
agents: [{ name: 'trunk', model: 'test-model', systemPrompt: 'x' }],
|
|
63
|
+
modules: [],
|
|
64
|
+
conversations: { templateAgent: 'nope' },
|
|
65
|
+
}), /templateAgent "nope"/);
|
|
66
|
+
});
|
|
67
|
+
it('DM message spawns a fork, routes the message there, and triggers inference', async () => {
|
|
68
|
+
membrane.pushResponse(createMockResponse([{ type: 'text', text: 'Hi alice!' }]));
|
|
69
|
+
const framework = await makeFramework();
|
|
70
|
+
framework.pushEvent(incomingEvent({
|
|
71
|
+
channelId: 'slack:D1', text: 'hello there', channelType: 'im',
|
|
72
|
+
}));
|
|
73
|
+
await framework.runUntilIdle();
|
|
74
|
+
const fork = framework.getAgent('conversation-slack-D1-g1');
|
|
75
|
+
assert.ok(fork, 'fork agent should exist');
|
|
76
|
+
// Message landed in the fork's context, not the trunk's.
|
|
77
|
+
const { messages: forkMessages } = await fork.getContextManager().compile();
|
|
78
|
+
assert.ok(forkMessages.some((m) => JSON.stringify(m.content).includes('hello there')), 'fork context should contain the incoming message');
|
|
79
|
+
const trunk = framework.getAgent('trunk');
|
|
80
|
+
const { messages: trunkMessages } = await trunk.getContextManager().compile();
|
|
81
|
+
assert.ok(!trunkMessages.some((m) => JSON.stringify(m.content).includes('hello there')), 'trunk context must not receive routed messages');
|
|
82
|
+
// Inference ran for the fork.
|
|
83
|
+
assert.ok(membrane.calls.length >= 1, 'inference should have been triggered');
|
|
84
|
+
// Binding is live.
|
|
85
|
+
const router = framework.getConversationRouter();
|
|
86
|
+
assert.equal(router.getBinding('slack:D1')?.agentName, 'conversation-slack-D1-g1');
|
|
87
|
+
await framework.stop();
|
|
88
|
+
});
|
|
89
|
+
it('channel message without mention is dropped; mention spawns', async () => {
|
|
90
|
+
membrane.pushResponse(createMockResponse([{ type: 'text', text: 'On it.' }]));
|
|
91
|
+
const framework = await makeFramework();
|
|
92
|
+
framework.pushEvent(incomingEvent({ channelId: 'slack:C1', text: 'just chatting' }));
|
|
93
|
+
await framework.runUntilIdle();
|
|
94
|
+
assert.equal(framework.getAgent('conversation-slack-C1-g1'), null, 'no fork without mention');
|
|
95
|
+
assert.equal(membrane.calls.length, 0, 'no inference for unrouted messages');
|
|
96
|
+
framework.pushEvent(incomingEvent({ channelId: 'slack:C1', text: 'bot, help', mentioned: true }));
|
|
97
|
+
await framework.runUntilIdle();
|
|
98
|
+
assert.ok(framework.getAgent('conversation-slack-C1-g1'), 'mention spawns fork');
|
|
99
|
+
await framework.stop();
|
|
100
|
+
});
|
|
101
|
+
it('non-mention on a bound channel lands in context without triggering', async () => {
|
|
102
|
+
membrane.pushResponse(createMockResponse([{ type: 'text', text: 'ack' }]));
|
|
103
|
+
const framework = await makeFramework();
|
|
104
|
+
framework.pushEvent(incomingEvent({ channelId: 'slack:C1', text: 'first', mentioned: true }));
|
|
105
|
+
await framework.runUntilIdle();
|
|
106
|
+
const callsAfterSpawn = membrane.calls.length;
|
|
107
|
+
framework.pushEvent(incomingEvent({ channelId: 'slack:C1', text: 'ambient detail' }));
|
|
108
|
+
await framework.runUntilIdle();
|
|
109
|
+
const fork = framework.getAgent('conversation-slack-C1-g1');
|
|
110
|
+
const { messages } = await fork.getContextManager().compile();
|
|
111
|
+
assert.ok(messages.some((m) => JSON.stringify(m.content).includes('ambient detail')), 'ambient message should land in fork context');
|
|
112
|
+
assert.equal(membrane.calls.length, callsAfterSpawn, 'ambient message must not trigger inference');
|
|
113
|
+
await framework.stop();
|
|
114
|
+
});
|
|
115
|
+
it('fork inherits the template context', async () => {
|
|
116
|
+
membrane.pushResponse(createMockResponse([{ type: 'text', text: 'ok' }]));
|
|
117
|
+
const framework = await makeFramework();
|
|
118
|
+
// Seed the trunk (primary agent) with handbook-like content.
|
|
119
|
+
framework.getAgent('trunk').getContextManager().addMessage('user', [
|
|
120
|
+
{ type: 'text', text: 'HANDBOOK: always check the logs first.' },
|
|
121
|
+
]);
|
|
122
|
+
framework.pushEvent(incomingEvent({ channelId: 'slack:D2', text: 'hi', channelType: 'im' }));
|
|
123
|
+
await framework.runUntilIdle();
|
|
124
|
+
const fork = framework.getAgent('conversation-slack-D2-g1');
|
|
125
|
+
const { messages } = await fork.getContextManager().compile();
|
|
126
|
+
assert.ok(messages.some((m) => JSON.stringify(m.content).includes('HANDBOOK')), 'fork should inherit trunk context');
|
|
127
|
+
await framework.stop();
|
|
128
|
+
});
|
|
129
|
+
it('idle TTL runs a closure turn, unbinds, disposes the fork, and the next message spawns g2', async () => {
|
|
130
|
+
const IDLE_TTL_MS = 60_000;
|
|
131
|
+
membrane.pushResponse(createMockResponse([{ type: 'text', text: 'hello!' }]));
|
|
132
|
+
const framework = await makeFramework({ idleTtlMs: IDLE_TTL_MS });
|
|
133
|
+
framework.pushEvent(incomingEvent({ channelId: 'slack:D3', text: 'hi', channelType: 'im' }));
|
|
134
|
+
await framework.runUntilIdle();
|
|
135
|
+
const router = framework.getConversationRouter();
|
|
136
|
+
assert.ok(router.getBinding('slack:D3'), 'spawn should leave a live binding');
|
|
137
|
+
const g1 = framework.getAgent('conversation-slack-D3-g1');
|
|
138
|
+
assert.ok(g1, 'g1 fork should exist after spawn');
|
|
139
|
+
// Force expiry deterministically and provide the closure-turn response.
|
|
140
|
+
forceExpiry(framework, 'slack:D3', IDLE_TTL_MS);
|
|
141
|
+
membrane.pushResponse(createMockResponse([{ type: 'text', text: 'Final report posted.' }]));
|
|
142
|
+
// Nudge the loop so the sweep runs.
|
|
143
|
+
framework.pushEvent({ type: 'external-message', source: 'test', content: 'tick', metadata: {} });
|
|
144
|
+
await framework.runUntilIdle();
|
|
145
|
+
assert.equal(router.getBinding('slack:D3'), undefined, 'binding should be gone after TTL');
|
|
146
|
+
const { messages } = await g1.getContextManager().compile();
|
|
147
|
+
assert.ok(messages.some((m) => JSON.stringify(m.content).includes('engagement is closing')), 'closure prompt should be in the fork context');
|
|
148
|
+
assert.equal(framework.getAgent('conversation-slack-D3-g1'), null, 'closed fork should be disposed once its closure turn finished');
|
|
149
|
+
// Next DM spawns a fresh generation.
|
|
150
|
+
membrane.pushResponse(createMockResponse([{ type: 'text', text: 'hi again' }]));
|
|
151
|
+
framework.pushEvent(incomingEvent({ channelId: 'slack:D3', text: 'back again', channelType: 'im' }));
|
|
152
|
+
await framework.runUntilIdle();
|
|
153
|
+
assert.ok(framework.getAgent('conversation-slack-D3-g2'), 'rebind spawns generation 2');
|
|
154
|
+
await framework.stop();
|
|
155
|
+
});
|
|
156
|
+
it('restart does not reuse generation names or re-seed an existing namespace', async () => {
|
|
157
|
+
membrane.pushResponse(createMockResponse([{ type: 'text', text: 'noted' }]));
|
|
158
|
+
const fw1 = await makeFramework({}, 'restart.chronicle');
|
|
159
|
+
fw1.getAgent('trunk').getContextManager().addMessage('user', [
|
|
160
|
+
{ type: 'text', text: 'HANDBOOK-V1: always check the logs first.' },
|
|
161
|
+
]);
|
|
162
|
+
fw1.pushEvent(incomingEvent({ channelId: 'slack:D9', text: 'case one', channelType: 'im' }));
|
|
163
|
+
await fw1.runUntilIdle();
|
|
164
|
+
assert.ok(fw1.getAgent('conversation-slack-D9-g1'), 'first engagement spawns g1');
|
|
165
|
+
await fw1.stop();
|
|
166
|
+
// Restart: same store, fresh framework. The generation counter must come
|
|
167
|
+
// back from Chronicle so the next engagement is g2 in a fresh namespace.
|
|
168
|
+
membrane.pushResponse(createMockResponse([{ type: 'text', text: 'fresh' }]));
|
|
169
|
+
const fw2 = await makeFramework({}, 'restart.chronicle');
|
|
170
|
+
fw2.pushEvent(incomingEvent({ channelId: 'slack:D9', text: 'case two', channelType: 'im' }));
|
|
171
|
+
await fw2.runUntilIdle();
|
|
172
|
+
assert.equal(fw2.getAgent('conversation-slack-D9-g1'), null, 'g1 must not be resurrected');
|
|
173
|
+
const g2 = fw2.getAgent('conversation-slack-D9-g2');
|
|
174
|
+
assert.ok(g2, 'restart spawns the next generation, not generation 1 again');
|
|
175
|
+
const { messages } = await g2.getContextManager().compile();
|
|
176
|
+
const handbookCopies = messages.filter((m) => JSON.stringify(m.content).includes('HANDBOOK-V1')).length;
|
|
177
|
+
assert.equal(handbookCopies, 1, 'fresh namespace is seeded with exactly one template copy');
|
|
178
|
+
assert.ok(!messages.some((m) => JSON.stringify(m.content).includes('case one')), 'a new generation must not inherit the previous engagement history');
|
|
179
|
+
await fw2.stop();
|
|
180
|
+
});
|
|
181
|
+
it('forks cannot open channels or close foreign ones', async () => {
|
|
182
|
+
membrane.pushResponse(createMockResponse([{ type: 'text', text: 'hi' }]));
|
|
183
|
+
const framework = await makeFramework();
|
|
184
|
+
framework.pushEvent(incomingEvent({ channelId: 'slack:C7', text: 'bot, help', mentioned: true }));
|
|
185
|
+
await framework.runUntilIdle();
|
|
186
|
+
const forkName = 'conversation-slack-C7-g1';
|
|
187
|
+
assert.ok(framework.getAgent(forkName), 'fork should exist');
|
|
188
|
+
const failures = [];
|
|
189
|
+
framework.onTrace((e) => {
|
|
190
|
+
if (e.type === 'tool:failed') {
|
|
191
|
+
failures.push({ tool: e.tool, error: e.error });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
const fw = framework;
|
|
195
|
+
fw.dispatchChannelToolCall(forkName, { id: 't1', name: 'channel_open', input: { channelId: 'slack:C8' } });
|
|
196
|
+
fw.dispatchChannelToolCall(forkName, { id: 't2', name: 'channel_close', input: { channelId: 'slack:C8' } });
|
|
197
|
+
assert.equal(failures.length, 2, 'both foreign channel operations should be rejected');
|
|
198
|
+
assert.ok(failures[0].error.includes('cannot open channels'), 'channel_open is rejected outright');
|
|
199
|
+
assert.ok(failures[1].error.includes('closing slack:C8 is not allowed'), 'foreign channel_close is rejected');
|
|
200
|
+
await framework.stop();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
//# sourceMappingURL=conversation-routing.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-routing.test.js","sourceRoot":"","sources":["../../test/conversation-routing.test.ts"],"names":[],"mappings":"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"}
|